.. _sec_model_construction: Lớp và khối =========== Khi chúng tôi lần đầu tiên giới thiệu các mạng thần kinh, chúng tôi tập trung vào các mô hình tuyến tính với một đầu ra duy nhất. Ở đây, toàn bộ mô hình chỉ bao gồm một tế bào thần kinh duy nhất. Lưu ý rằng một tế bào thần kinh đơn (i) lấy một số tập hợp các đầu vào; (ii) tạo ra một đầu ra vô hướng tương ứng; và (iii) có một tập hợp các tham số liên quan có thể được cập nhật để tối ưu hóa một số chức năng quan tâm. Sau đó, một khi chúng tôi bắt đầu suy nghĩ về các mạng có nhiều đầu ra, chúng tôi đã tận dụng số học vector hóa để mô tả toàn bộ một lớp tế bào thần kinh. Cũng giống như các tế bào thần kinh riêng lẻ, các lớp (i) lấy một tập hợp các đầu vào, (ii) tạo ra các đầu ra tương ứng, và (iii) được mô tả bởi một tập hợp các tham số có thể điều chỉnh. Khi chúng tôi làm việc thông qua hồi quy softmax, một lớp duy nhất chính nó là mô hình. Tuy nhiên, ngay cả khi chúng tôi sau đó giới thiệu MLP, chúng ta vẫn có thể nghĩ về mô hình là giữ lại cấu trúc cơ bản tương tự này. Điều thú vị là đối với MLP, cả toàn bộ mô hình và các lớp cấu thành của nó đều chia sẻ cấu trúc này. Toàn bộ mô hình lấy đầu vào thô (các tính năng), tạo ra các đầu ra (dự đoán) và sở hữu các tham số (các tham số kết hợp từ tất cả các lớp cấu thành). Tương tự như vậy, mỗi lớp riêng lẻ ăn vào đầu vào (được cung cấp bởi lớp trước đó) tạo ra các đầu ra (đầu vào cho lớp tiếp theo) và sở hữu một tập hợp các tham số có thể điều chỉnh được cập nhật theo tín hiệu chảy ngược từ lớp tiếp theo. Mặc dù bạn có thể nghĩ rằng tế bào thần kinh, lớp và mô hình cung cấp cho chúng ta đủ trừu tượng để đi về kinh doanh của chúng tôi, hóa ra chúng ta thường thấy thuận tiện khi nói về các thành phần lớn hơn một lớp riêng lẻ nhưng nhỏ hơn toàn bộ mô hình. Ví dụ, kiến trúc ResNet-152, rất phổ biến trong tầm nhìn máy tính, sở hữu hàng trăm lớp. Các lớp này bao gồm các mô hình lặp lại của \* nhóm lớp\*. Thực hiện một mạng như vậy một lớp tại một thời điểm có thể phát triển tẻ nhạt. Mối quan tâm này không chỉ là giả thuyết, các mẫu thiết kế như vậy là phổ biến trong thực tế. Kiến trúc ResNet được đề cập ở trên đã giành được các cuộc thi tầm nhìn máy tính ImageNet và COCO 2015 cho cả công nhận và phát hiện :cite:`He.Zhang.Ren.ea.2016` và vẫn là một kiến trúc đi đến cho nhiều nhiệm vụ thị giác. Các kiến trúc tương tự trong đó các lớp được sắp xếp theo nhiều mẫu lặp lại khác nhau hiện đang phổ biến trong các lĩnh vực khác, bao gồm xử lý ngôn ngữ tự nhiên và lời nói. Để thực hiện các mạng phức tạp này, chúng tôi giới thiệu khái niệm về một mạng nơ-ron\* block\*. Một khối có thể mô tả một lớp duy nhất, một thành phần bao gồm nhiều lớp, hoặc toàn bộ mô hình chính nó! Một lợi ích của việc làm việc với trừu tượng khối là chúng có thể được kết hợp thành các hiện vật lớn hơn, thường đệ quy. Điều này được minh họa trong :numref:`fig_blocks`. Bằng cách xác định mã để tạo ra các khối phức tạp tùy ý theo yêu cầu, chúng ta có thể viết mã nhỏ gọn đáng ngạc nhiên và vẫn triển khai các mạng nơ-ron phức tạp. .. _fig_blocks: .. figure:: ../img/blocks.svg Multiple layers are combined into blocks, forming repeating patterns of larger models. Từ quan điểm lập trình, một khối được biểu diễn bằng một lớp \* \*. Bất kỳ lớp con nào của nó phải xác định một hàm tuyên truyền chuyển tiếp biến đổi đầu vào của nó thành đầu ra và phải lưu trữ bất kỳ tham số cần thiết nào. Lưu ý rằng một số khối không yêu cầu bất kỳ tham số nào cả. Cuối cùng một khối phải sở hữu một hàm backpropagation, cho mục đích tính toán gradient. May mắn thay, do một số phép thuật hậu trường được cung cấp bởi sự khác biệt tự động (được giới thiệu trong :numref:`sec_autograd`) khi xác định khối của riêng mình, chúng ta chỉ cần lo lắng về các thông số và chức năng lan truyền chuyển tiếp. `**Để bắt đầu, chúng tôi xem lại mã mà chúng tôi đã sử dụng để thực hiện MLPs** <:numref:%60sec_mlp_concise%60>`__. Mã sau tạo ra một mạng với một lớp ẩn được kết nối hoàn toàn với 256 đơn vị và kích hoạt ReLU, tiếp theo là lớp đầu ra được kết nối hoàn toàn với 10 đơn vị (không có chức năng kích hoạt). .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python from mxnet import np, npx from mxnet.gluon import nn npx.set_np() net = nn.Sequential() net.add(nn.Dense(256, activation='relu')) net.add(nn.Dense(10)) net.initialize() X = np.random.uniform(size=(2, 20)) net(X) .. parsed-literal:: :class: output array([[ 0.06240274, -0.03268593, 0.02582653, 0.02254181, -0.03728798, -0.04253785, 0.00540612, -0.01364185, -0.09915454, -0.02272737], [ 0.02816679, -0.03341204, 0.03565665, 0.02506384, -0.04136416, -0.04941844, 0.01738529, 0.01081963, -0.09932579, -0.01176296]]) Trong ví dụ này, chúng tôi xây dựng mô hình của mình bằng cách khởi tạo một ``nn.Sequential``, gán đối tượng trả về cho biến ``net``. Tiếp theo, chúng ta liên tục gọi hàm ``add`` của nó, nối các lớp theo thứ tự chúng nên được thực thi. Tóm lại, ``nn.Sequential`` định nghĩa một loại đặc biệt ``Block``, lớp trình bày một khối trong Gluon. Nó duy trì một danh sách đặt hàng của ``Block``\ s cấu thành. Chức năng ``add`` chỉ đơn giản là tạo điều kiện cho việc bổ sung mỗi ``Block`` liên tiếp vào danh sách. Lưu ý rằng mỗi lớp là một đối tượng của lớp ``Dense`` mà chính nó là một lớp con của ``Block``. Chức năng lan truyền chuyển tiếp (``forward``) cũng rất đơn giản: nó chuỗi mỗi ``Block`` trong danh sách với nhau, truyền đầu ra của mỗi chức năng làm đầu vào cho tiếp theo. Lưu ý rằng cho đến bây giờ, chúng tôi đã gọi các mô hình của chúng tôi thông qua xây dựng ``net(X)`` để có được đầu ra của chúng. Điều này thực sự chỉ là viết tắt cho ``net.forward(X)``, một thủ thuật Python slick đạt được thông qua chức năng ``Block`` của lớp ``__call__``. .. raw:: html
.. raw:: html
.. code:: python import torch from torch import nn from torch.nn import functional as F net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10)) X = torch.rand(2, 20) net(X) .. parsed-literal:: :class: output tensor([[ 0.2154, -0.2353, -0.0835, 0.0533, 0.0887, 0.0856, -0.1998, -0.0017, 0.3100, -0.1677], [ 0.2044, -0.2373, -0.1043, 0.0157, -0.0221, -0.0964, -0.2414, -0.0525, 0.3308, -0.0361]], grad_fn=) Trong ví dụ này, chúng tôi xây dựng mô hình của mình bằng cách khởi tạo một ``nn.Sequential``, với các lớp theo thứ tự mà chúng nên được thực hiện thông qua dưới dạng đối số. Nói tóm lại, ``nn.Sequential`` định nghĩa một loại đặc biệt của ``Module``, lớp trình bày một khối trong PyTorch. Nó duy trì một danh sách đặt hàng của ``Module``\ s cấu thành. Lưu ý rằng mỗi lớp trong hai lớp được kết nối hoàn toàn là một thể hiện của lớp ``Linear`` mà chính nó là một lớp con của ``Module``. Chức năng lan truyền chuyển tiếp (``forward``) cũng rất đơn giản: nó chuỗi mỗi khối trong danh sách với nhau, truyền đầu ra của mỗi như đầu vào cho tiếp theo. Lưu ý rằng cho đến bây giờ, chúng tôi đã gọi các mô hình của chúng tôi thông qua xây dựng ``net(X)`` để có được kết quả đầu ra của chúng. Điều này thực sự chỉ là viết tắt cho ``net.__call__(X)``. .. raw:: html
.. raw:: html
.. code:: python import tensorflow as tf net = tf.keras.models.Sequential([ tf.keras.layers.Dense(256, activation=tf.nn.relu), tf.keras.layers.Dense(10), ]) X = tf.random.uniform((2, 20)) net(X) .. parsed-literal:: :class: output Trong ví dụ này, chúng tôi xây dựng mô hình của mình bằng cách khởi tạo một ``keras.models.Sequential``, với các lớp theo thứ tự mà chúng nên được thực hiện thông qua dưới dạng đối số. Tóm lại, ``Sequential`` định nghĩa một loại đặc biệt của ``keras.Model``, lớp trình bày một khối trong Keras. Nó duy trì một danh sách đặt hàng của ``Model``\ s cấu thành. Lưu ý rằng mỗi lớp trong hai lớp được kết nối hoàn toàn là một thể hiện của lớp ``Dense`` mà chính nó là một lớp con của ``Model``. Chức năng lan truyền chuyển tiếp (``call``) cũng rất đơn giản: nó chuỗi mỗi khối trong danh sách với nhau, truyền đầu ra của mỗi như đầu vào cho tiếp theo. Lưu ý rằng cho đến bây giờ, chúng tôi đã gọi các mô hình của chúng tôi thông qua xây dựng ``net(X)`` để có được đầu ra của chúng. Đây thực sự chỉ là viết tắt cho ``net.call(X)``, một thủ thuật Python slick đạt được thông qua hàm ``__call__`` của lớp Block. .. raw:: html
.. raw:: html
Một khối tùy chỉnh ------------------ Có lẽ cách dễ nhất để phát triển trực giác về cách thức hoạt động của một khối là tự thực hiện một. Trước khi chúng tôi triển khai khối tùy chỉnh của riêng mình, chúng tôi tóm tắt ngắn gọn các chức năng cơ bản mà mỗi khối phải cung cấp: .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
1. Nhập dữ liệu đầu vào dưới dạng đối số cho chức năng tuyên truyền chuyển tiếp của nó. 2. Tạo ra một đầu ra bằng cách có hàm tuyên truyền chuyển tiếp trả về một giá trị. Lưu ý rằng đầu ra có thể có một hình dạng khác với đầu vào. Ví dụ, lớp kết nối hoàn toàn đầu tiên trong mô hình của chúng ta ở trên sẽ nhập một đầu vào của chiều tùy ý nhưng trả về đầu ra của kích thước 256. 3. Tính gradient của đầu ra của nó đối với đầu vào của nó, có thể được truy cập thông qua chức năng truyền ngược của nó. Thông thường điều này xảy ra tự động. 4. Lưu trữ và cung cấp quyền truy cập vào các tham số cần thiết để thực hiện tính toán tuyên truyền chuyển tiếp. 5. Khởi tạo các tham số mô hình khi cần thiết. .. raw:: html
.. raw:: html
1. Nhập dữ liệu đầu vào dưới dạng đối số cho chức năng tuyên truyền chuyển tiếp của nó. 2. Tạo ra một đầu ra bằng cách có hàm tuyên truyền chuyển tiếp trả về một giá trị. Lưu ý rằng đầu ra có thể có một hình dạng khác với đầu vào. Ví dụ: lớp kết nối hoàn toàn đầu tiên trong mô hình của chúng ta ở trên sẽ nhập một đầu vào của kích thước 20 nhưng trả về đầu ra của kích thước 256. 3. Tính gradient của đầu ra của nó đối với đầu vào của nó, có thể được truy cập thông qua chức năng truyền ngược của nó. Thông thường điều này xảy ra tự động. 4. Lưu trữ và cung cấp quyền truy cập vào các tham số cần thiết để thực hiện tính toán tuyên truyền chuyển tiếp. 5. Khởi tạo các tham số mô hình khi cần thiết. .. raw:: html
.. raw:: html
1. Nhập dữ liệu đầu vào dưới dạng đối số cho chức năng tuyên truyền chuyển tiếp của nó. 2. Tạo ra một đầu ra bằng cách có hàm tuyên truyền chuyển tiếp trả về một giá trị. Lưu ý rằng đầu ra có thể có một hình dạng khác với đầu vào. Ví dụ, lớp kết nối hoàn toàn đầu tiên trong mô hình của chúng ta ở trên sẽ nhập một đầu vào của chiều tùy ý nhưng trả về đầu ra của kích thước 256. 3. Tính gradient của đầu ra của nó đối với đầu vào của nó, có thể được truy cập thông qua chức năng truyền ngược của nó. Thông thường điều này xảy ra tự động. 4. Lưu trữ và cung cấp quyền truy cập vào các tham số cần thiết để thực hiện tính toán tuyên truyền chuyển tiếp. 5. Khởi tạo các tham số mô hình khi cần thiết. .. raw:: html
.. raw:: html
Trong đoạn mã sau, chúng tôi mã hóa một khối từ đầu tương ứng với một MLP với một lớp ẩn với 256 đơn vị ẩn, và một lớp đầu ra 10 chiều. Lưu ý rằng lớp ``MLP`` bên dưới kế thừa lớp đại diện cho một khối. Chúng ta sẽ dựa rất nhiều vào các hàm của lớp cha, chỉ cung cấp hàm tạo riêng của chúng ta (hàm ``__init__`` trong Python) và hàm tuyên truyền chuyển tiếp. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python class MLP(nn.Block): # Declare a layer with model parameters. Here, we declare two # fully-connected layers def __init__(self, **kwargs): # Call the constructor of the `MLP` parent class `Block` to perform # the necessary initialization. In this way, other function arguments # can also be specified during class instantiation, such as the model # parameters, `params` (to be described later) super().__init__(**kwargs) self.hidden = nn.Dense(256, activation='relu') # Hidden layer self.out = nn.Dense(10) # Output layer # Define the forward propagation of the model, that is, how to return the # required model output based on the input `X` def forward(self, X): return self.out(self.hidden(X)) .. raw:: html
.. raw:: html
.. code:: python class MLP(nn.Module): # Declare a layer with model parameters. Here, we declare two fully # connected layers def __init__(self): # Call the constructor of the `MLP` parent class `Module` to perform # the necessary initialization. In this way, other function arguments # can also be specified during class instantiation, such as the model # parameters, `params` (to be described later) super().__init__() self.hidden = nn.Linear(20, 256) # Hidden layer self.out = nn.Linear(256, 10) # Output layer # Define the forward propagation of the model, that is, how to return the # required model output based on the input `X` def forward(self, X): # Note here we use the funtional version of ReLU defined in the # nn.functional module. return self.out(F.relu(self.hidden(X))) .. raw:: html
.. raw:: html
.. code:: python class MLP(tf.keras.Model): # Declare a layer with model parameters. Here, we declare two fully # connected layers def __init__(self): # Call the constructor of the `MLP` parent class `Model` to perform # the necessary initialization. In this way, other function arguments # can also be specified during class instantiation, such as the model # parameters, `params` (to be described later) super().__init__() # Hidden layer self.hidden = tf.keras.layers.Dense(units=256, activation=tf.nn.relu) self.out = tf.keras.layers.Dense(units=10) # Output layer # Define the forward propagation of the model, that is, how to return the # required model output based on the input `X` def call(self, X): return self.out(self.hidden((X))) .. raw:: html
.. raw:: html
Trước tiên chúng ta hãy tập trung vào chức năng lan truyền về phía trước. Lưu ý rằng phải mất ``X`` làm đầu vào, tính toán biểu diễn ẩn với chức năng kích hoạt được áp dụng và đầu ra các bản ghi của nó. Trong triển khai ``MLP`` này, cả hai lớp đều là các biến thể. Để xem lý do tại sao điều này là hợp lý, hãy tưởng tượng khởi tạo hai MLP s, ``net1`` và ``net2``, và đào tạo chúng trên các dữ liệu khác nhau. Đương nhiên, chúng tôi mong đợi họ đại diện cho hai mô hình đã học khác nhau. Chúng ta khởi tạo các lớp MLP trong hàm tạo và sau đó gọi các lớp này trên mỗi lần gọi đến hàm tuyên truyền chuyển tiếp. Lưu ý một vài chi tiết chính. Đầu tiên, chức năng ``__init__`` tùy chỉnh của chúng tôi gọi chức năng ``__init__`` của lớp mẹ thông qua ``super().__init__()`` tiết kiệm cho chúng tôi nỗi đau của việc đặt lại mã boilerplate áp dụng cho hầu hết các khối. Sau đó, chúng tôi khởi tạo hai lớp được kết nối hoàn toàn của chúng tôi, gán chúng cho ``self.hidden`` và ``self.out``. Lưu ý rằng trừ khi chúng ta thực hiện một toán tử mới, chúng ta không cần phải lo lắng về hàm backpropagation hoặc khởi tạo tham số. Hệ thống sẽ tự động tạo ra các chức năng này. Hãy để chúng tôi thử điều này ra. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python net = MLP() net.initialize() net(X) .. parsed-literal:: :class: output array([[-0.03989595, -0.10414709, 0.06799038, 0.05245074, 0.0252606 , -0.00640342, 0.04182098, -0.01665318, -0.02067345, -0.07863816], [-0.03612847, -0.07210435, 0.09159479, 0.07890773, 0.02494171, -0.01028665, 0.01732427, -0.02843244, 0.03772651, -0.06671703]]) .. raw:: html
.. raw:: html
.. code:: python net = MLP() net(X) .. parsed-literal:: :class: output tensor([[-0.0966, 0.1394, -0.1274, 0.3354, 0.1572, -0.0922, -0.0228, -0.2199, -0.1109, -0.2341], [-0.0501, 0.1194, -0.0566, 0.2452, 0.2531, -0.1992, 0.0007, -0.2337, -0.1568, -0.2085]], grad_fn=) .. raw:: html
.. raw:: html
.. code:: python net = MLP() net(X) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Một đức tính chính của trừu tượng khối là tính linh hoạt của nó. Chúng ta có thể phân lớp một khối để tạo ra các lớp (chẳng hạn như lớp lớp lớp được kết nối hoàn toàn), toàn bộ mô hình (chẳng hạn như lớp ``MLP`` ở trên) hoặc các thành phần khác nhau có độ phức tạp trung gian. Chúng tôi khai thác tính linh hoạt này trong suốt các chương sau, chẳng hạn như khi giải quyết các mạng thần kinh phức tạp. The Sequential Block -------------------- Bây giờ chúng ta có thể xem xét kỹ hơn cách thức hoạt động của lớp ``Sequential``. Nhớ lại rằng ``Sequential`` được thiết kế để chuỗi các khối khác với nhau. Để xây dựng ``MySequential`` đơn giản hóa của riêng mình, chúng ta chỉ cần định nghĩa hai hàm chính: 1. Một hàm để nối các khối từng cái một vào một danh sách. 2. Một chức năng tuyên truyền chuyển tiếp để truyền một đầu vào thông qua chuỗi các khối, theo thứ tự như chúng được nối thêm. Lớp ``MySequential`` sau đây cung cấp chức năng tương tự của lớp ``Sequential`` mặc định. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python class MySequential(nn.Block): def add(self, block): # Here, `block` is an instance of a `Block` subclass, and we assume # that it has a unique name. We save it in the member variable # `_children` of the `Block` class, and its type is OrderedDict. When # the `MySequential` instance calls the `initialize` function, the # system automatically initializes all members of `_children` self._children[block.name] = block def forward(self, X): # OrderedDict guarantees that members will be traversed in the order # they were added for block in self._children.values(): X = block(X) return X Hàm ``add`` thêm một khối duy nhất vào từ điển có thứ tự ``_children``. Bạn có thể tự hỏi tại sao mỗi Gluon ``Block`` sở hữu một thuộc tính ``_children`` và tại sao chúng tôi sử dụng nó hơn là chỉ định một danh sách Python chính mình. Tóm lại, lợi thế chính của ``_children`` là trong quá trình khởi tạo tham số khối của chúng tôi, Gluon biết nhìn vào bên trong từ điển ``_children`` để tìm các khối phụ có tham số cũng cần được khởi tạo. .. raw:: html
.. raw:: html
.. code:: python class MySequential(nn.Module): def __init__(self, *args): super().__init__() for idx, module in enumerate(args): # Here, `module` is an instance of a `Module` subclass. We save it # in the member variable `_modules` of the `Module` class, and its # type is OrderedDict self._modules[str(idx)] = module def forward(self, X): # OrderedDict guarantees that members will be traversed in the order # they were added for block in self._modules.values(): X = block(X) return X Trong phương pháp ``__init__``, chúng tôi thêm từng mô-đun vào từ điển được đặt hàng ``_modules`` từng cái một. Bạn có thể tự hỏi tại sao mỗi ``Module`` đều sở hữu một thuộc tính ``_modules`` và tại sao chúng ta sử dụng nó thay vì chỉ tự xác định một danh sách Python. Tóm lại, lợi thế chính của ``_modules`` là trong quá trình khởi tạo tham số mô-đun của chúng tôi, hệ thống biết nhìn vào bên trong từ điển ``_modules`` để tìm các mô-đun phụ có tham số cũng cần được khởi tạo. .. raw:: html
.. raw:: html
.. code:: python class MySequential(tf.keras.Model): def __init__(self, *args): super().__init__() self.modules = [] for block in args: # Here, `block` is an instance of a `tf.keras.layers.Layer` # subclass self.modules.append(block) def call(self, X): for module in self.modules: X = module(X) return X .. raw:: html
.. raw:: html
Khi hàm tuyên truyền chuyển tiếp ``MySequential`` của chúng tôi được gọi, mỗi khối được thêm vào được thực hiện theo thứ tự mà chúng được thêm vào. Bây giờ chúng ta có thể triển khai lại MLP bằng cách sử dụng lớp ``MySequential`` của chúng tôi. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python net = MySequential() net.add(nn.Dense(256, activation='relu')) net.add(nn.Dense(10)) net.initialize() net(X) .. parsed-literal:: :class: output array([[-0.0764568 , -0.01130233, 0.04952145, -0.04651389, -0.04131571, -0.05884131, -0.06213811, 0.01311471, -0.01379425, -0.02514282], [-0.05124623, 0.00711232, -0.00155933, -0.07555379, -0.06675334, -0.01762914, 0.00589085, 0.0144719 , -0.04330775, 0.03317727]]) .. raw:: html
.. raw:: html
.. code:: python net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10)) net(X) .. parsed-literal:: :class: output tensor([[-0.3238, -0.0582, 0.3331, 0.0385, -0.0048, -0.5133, 0.1399, 0.1480, -0.1763, 0.0246], [-0.3501, -0.0605, 0.1158, -0.0148, -0.0063, -0.4010, 0.0360, 0.0430, -0.1625, 0.0111]], grad_fn=) .. raw:: html
.. raw:: html
.. code:: python net = MySequential( tf.keras.layers.Dense(units=256, activation=tf.nn.relu), tf.keras.layers.Dense(10)) net(X) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Lưu ý rằng việc sử dụng ``MySequential`` này giống với mã mà chúng tôi đã viết trước đây cho lớp ``Sequential`` (như được mô tả trong :numref:`sec_mlp_concise`). Thihành mã trong chức năng tuyên truyền chuyển tiếp --------------------------------------------------- Lớp ``Sequential`` giúp việc xây dựng mô hình dễ dàng, cho phép chúng tôi lắp ráp các kiến trúc mới mà không cần phải xác định lớp học của riêng mình. Tuy nhiên, không phải tất cả các kiến trúc đều là chuỗi daisy đơn giản. Khi cần tính linh hoạt cao hơn, chúng tôi sẽ muốn xác định các khối của riêng mình. Ví dụ, chúng ta có thể muốn thực thi luồng điều khiển của Python trong hàm tuyên truyền chuyển tiếp. Hơn nữa, chúng ta có thể muốn thực hiện các phép toán tùy ý, không chỉ dựa vào các lớp mạng thần kinh được xác định trước. Bạn có thể nhận thấy rằng cho đến bây giờ, tất cả các hoạt động trong mạng của chúng tôi đã hoạt động dựa trên các kích hoạt mạng và các thông số của mạng của chúng tôi. Tuy nhiên, đôi khi, chúng ta có thể muốn kết hợp các thuật ngữ không phải là kết quả của các lớp trước đó cũng như các tham số có thể cập nhật. Chúng tôi gọi các tham số \* không đổi này\*. Ví dụ, chúng ta muốn một lớp tính hàm :math:`f(\mathbf{x},\mathbf{w}) = c \cdot \mathbf{w}^\top \mathbf{x}`, trong đó :math:`\mathbf{x}` là đầu vào, :math:`\mathbf{w}` là tham số của chúng ta và :math:`c` là một số hằng số được chỉ định không được cập nhật trong quá trình tối ưu hóa. Vì vậy, chúng tôi thực hiện một lớp ``FixedHiddenMLP`` như sau. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python class FixedHiddenMLP(nn.Block): def __init__(self, **kwargs): super().__init__(**kwargs) # Random weight parameters created with the `get_constant` function # are not updated during training (i.e., constant parameters) self.rand_weight = self.params.get_constant( 'rand_weight', np.random.uniform(size=(20, 20))) self.dense = nn.Dense(20, activation='relu') def forward(self, X): X = self.dense(X) # Use the created constant parameters, as well as the `relu` and `dot` # functions X = npx.relu(np.dot(X, self.rand_weight.data()) + 1) # Reuse the fully-connected layer. This is equivalent to sharing # parameters with two fully-connected layers X = self.dense(X) # Control flow while np.abs(X).sum() > 1: X /= 2 return X.sum() .. raw:: html
.. raw:: html
.. code:: python class FixedHiddenMLP(nn.Module): def __init__(self): super().__init__() # Random weight parameters that will not compute gradients and # therefore keep constant during training self.rand_weight = torch.rand((20, 20), requires_grad=False) self.linear = nn.Linear(20, 20) def forward(self, X): X = self.linear(X) # Use the created constant parameters, as well as the `relu` and `mm` # functions X = F.relu(torch.mm(X, self.rand_weight) + 1) # Reuse the fully-connected layer. This is equivalent to sharing # parameters with two fully-connected layers X = self.linear(X) # Control flow while X.abs().sum() > 1: X /= 2 return X.sum() .. raw:: html
.. raw:: html
.. code:: python class FixedHiddenMLP(tf.keras.Model): def __init__(self): super().__init__() self.flatten = tf.keras.layers.Flatten() # Random weight parameters created with `tf.constant` are not updated # during training (i.e., constant parameters) self.rand_weight = tf.constant(tf.random.uniform((20, 20))) self.dense = tf.keras.layers.Dense(20, activation=tf.nn.relu) def call(self, inputs): X = self.flatten(inputs) # Use the created constant parameters, as well as the `relu` and # `matmul` functions X = tf.nn.relu(tf.matmul(X, self.rand_weight) + 1) # Reuse the fully-connected layer. This is equivalent to sharing # parameters with two fully-connected layers X = self.dense(X) # Control flow while tf.reduce_sum(tf.math.abs(X)) > 1: X /= 2 return tf.reduce_sum(X) .. raw:: html
.. raw:: html
Trong mô hình ``FixedHiddenMLP`` này, chúng tôi triển khai một lớp ẩn có trọng lượng (``self.rand_weight``) được khởi tạo ngẫu nhiên khi khởi tạo và sau đó là hằng số. Trọng lượng này không phải là một tham số mô hình và do đó nó không bao giờ được cập nhật bằng cách truyền ngược. Sau đó, mạng truyền đầu ra của lớp “cố định” này thông qua một lớp được kết nối hoàn toàn. Lưu ý rằng trước khi trả lại đầu ra, mô hình của chúng tôi đã làm một cái gì đó bất thường. Chúng tôi chạy một vòng lặp trong khi, thử nghiệm với điều kiện định mức :math:`L_1` của nó lớn hơn :math:`1` và chia vector đầu ra của chúng tôi cho :math:`2` cho đến khi nó thỏa mãn điều kiện. Cuối cùng, chúng tôi trả lại tổng các mục trong ``X``. Theo kiến thức của chúng tôi, không có mạng thần kinh tiêu chuẩn nào thực hiện hoạt động này. Lưu ý rằng hoạt động cụ thể này có thể không hữu ích trong bất kỳ nhiệm vụ thực tế nào. Quan điểm của chúng tôi chỉ là chỉ cho bạn cách tích hợp mã tùy ý vào luồng tính toán mạng thần kinh của bạn. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python net = FixedHiddenMLP() net.initialize() net(X) .. parsed-literal:: :class: output array(0.52637565) .. raw:: html
.. raw:: html
.. code:: python net = FixedHiddenMLP() net(X) .. parsed-literal:: :class: output tensor(0.1593, grad_fn=) .. raw:: html
.. raw:: html
.. code:: python net = FixedHiddenMLP() net(X) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Chúng ta có thể trộn và kết hợp các cách khác nhau để lắp ráp các khối với nhau. Trong ví dụ sau, chúng ta tổ các khối theo một số cách sáng tạo. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
.. code:: python class NestMLP(nn.Block): def __init__(self, **kwargs): super().__init__(**kwargs) self.net = nn.Sequential() self.net.add(nn.Dense(64, activation='relu'), nn.Dense(32, activation='relu')) self.dense = nn.Dense(16, activation='relu') def forward(self, X): return self.dense(self.net(X)) chimera = nn.Sequential() chimera.add(NestMLP(), nn.Dense(20), FixedHiddenMLP()) chimera.initialize() chimera(X) .. parsed-literal:: :class: output array(0.9772054) .. raw:: html
.. raw:: html
.. code:: python class NestMLP(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU()) self.linear = nn.Linear(32, 16) def forward(self, X): return self.linear(self.net(X)) chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP()) chimera(X) .. parsed-literal:: :class: output tensor(0.0163, grad_fn=) .. raw:: html
.. raw:: html
.. code:: python class NestMLP(tf.keras.Model): def __init__(self): super().__init__() self.net = tf.keras.Sequential() self.net.add(tf.keras.layers.Dense(64, activation=tf.nn.relu)) self.net.add(tf.keras.layers.Dense(32, activation=tf.nn.relu)) self.dense = tf.keras.layers.Dense(16, activation=tf.nn.relu) def call(self, inputs): return self.dense(self.net(inputs)) chimera = tf.keras.Sequential() chimera.add(NestMLP()) chimera.add(tf.keras.layers.Dense(20)) chimera.add(FixedHiddenMLP()) chimera(X) .. parsed-literal:: :class: output .. raw:: html
.. raw:: html
Hiệu quả -------- .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
Người đọc avid có thể bắt đầu lo lắng về hiệu quả của một số hoạt động này. Rốt cuộc, chúng ta có rất nhiều tra cứu từ điển, thực thi mã và rất nhiều thứ Pythonic khác diễn ra trong những gì được cho là một thư viện deep learning hiệu suất cao. Các vấn đề về `global interpreter lock `__ của Python được biết đến nhiều. Trong bối cảnh học sâu, chúng ta có thể lo lắng rằng (các) GPU cực nhanh của chúng ta có thể phải đợi cho đến khi một CPU nhỏ chạy mã Python trước khi nó có được một công việc khác để chạy. Cách tốt nhất để tăng tốc Python là tránh nó hoàn toàn. Một cách mà Gluon làm điều này là bằng cách cho phép *hybridization*, sẽ được mô tả sau. Ở đây, trình thông dịch Python thực thi một khối lần đầu tiên nó được gọi. Thời gian chạy Gluon ghi lại những gì đang xảy ra và lần sau xung quanh nó ngắn mạch gọi đến Python. Điều này có thể đẩy nhanh mọi thứ đáng kể trong một số trường hợp nhưng cần phải cẩn thận khi dòng chảy kiểm soát (như trên) dẫn xuống các nhánh khác nhau trên các đường đi khác nhau qua mạng. Chúng tôi khuyên người đọc quan tâm kiểm tra phần lai (:numref:`sec_hybridize`) để tìm hiểu về việc biên soạn sau khi kết thúc chương hiện tại. .. raw:: html
.. raw:: html
Người đọc avid có thể bắt đầu lo lắng về hiệu quả của một số hoạt động này. Rốt cuộc, chúng ta có rất nhiều tra cứu từ điển, thực thi mã và rất nhiều thứ Pythonic khác diễn ra trong những gì được cho là một thư viện deep learning hiệu suất cao. Các vấn đề về `global interpreter lock `__ của Python được biết đến nhiều. Trong bối cảnh học sâu, chúng ta có thể lo lắng rằng (các) GPU cực nhanh của chúng ta có thể phải đợi cho đến khi một CPU nhỏ chạy mã Python trước khi nó có được một công việc khác để chạy. .. raw:: html
.. raw:: html
Người đọc avid có thể bắt đầu lo lắng về hiệu quả của một số hoạt động này. Rốt cuộc, chúng ta có rất nhiều tra cứu từ điển, thực thi mã và rất nhiều thứ Pythonic khác diễn ra trong những gì được cho là một thư viện deep learning hiệu suất cao. Các vấn đề về `global interpreter lock `__ của Python được biết đến nhiều. Trong bối cảnh học sâu, chúng ta có thể lo lắng rằng (các) GPU cực nhanh của chúng ta có thể phải đợi cho đến khi một CPU nhỏ chạy mã Python trước khi nó có được một công việc khác để chạy. Cách tốt nhất để tăng tốc Python là tránh nó hoàn toàn. .. raw:: html
.. raw:: html
Tóm tắt ------- - Lớp là các khối. - Nhiều lớp có thể bao gồm một khối. - Nhiều khối có thể bao gồm một khối. - Một khối có thể chứa mã. - Các khối chăm sóc rất nhiều dịch vụ vệ sinh, bao gồm khởi tạo tham số và truyền ngược. - Các kết nối tuần tự của các lớp và khối được xử lý bởi khối ``Sequential``. Bài tập ------- 1. Những loại vấn đề sẽ xảy ra nếu bạn thay đổi ``MySequential`` để lưu trữ các khối trong một danh sách Python? 2. Thực hiện một khối lấy hai khối làm đối số, giả sử ``net1`` và ``net2`` và trả về đầu ra nối của cả hai mạng trong tuyên truyền chuyển tiếp. Đây còn được gọi là một khối song song. 3. Giả sử rằng bạn muốn nối nhiều phiên bản của cùng một mạng. Triển khai một hàm factory tạo ra nhiều trường hợp của cùng một khối và xây dựng một mạng lớn hơn từ nó. .. raw:: html
mxnetpytorchtensorflow
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html