.. _sec_model_construction:
層とモジュール
==============
ニューラルネットワークを最初に導入したとき、
私たちは単一の出力をもつ線形モデルに焦点を当てた。
ここでは、モデル全体がたった1つのニューロンだけで構成されている。
単一のニューロンは、 (i) ある入力集合を受け取り、 (ii)
それに対応するスカラー出力を生成し、 (iii)
関心のある目的関数を最適化するために更新可能な関連パラメータの集合をもつ、
ことに注意してほしい。
その後、複数の出力をもつネットワークについて考え始めると、
ベクトル化された算術を活用して、
ニューロンの層全体を表現できるようになった。 個々のニューロンと同様に、
層も (i) 入力の集合を受け取り、 (ii) 対応する出力を生成し、 (iii)
調整可能なパラメータの集合によって記述される。
softmax回帰を扱ったときには、 1つの層そのものがモデルであった。
しかし、その後MLPを導入した後でも、
モデルは同じ基本構造を保っていると考えることができた。
興味深いことに、MLPでは、 モデル全体とその構成要素である層の両方が
この構造を共有している。 モデル全体は生の入力(特徴量)を受け取り、
出力(予測)を生成し、
パラメータ(すべての構成層のパラメータを合わせたもの)を持つ。
同様に、各個別の層は入力(前の層から供給される)を受け取り、
出力(次の層への入力)を生成し、
さらに、次の層から逆向きに流れてくる信号に応じて更新される
調整可能なパラメータの集合を持つ。
ニューロン、層、モデルで
十分な抽象化が得られているように思えるかもしれないが、
実際には、個々の層より大きく、 モデル全体よりは小さい構成要素について
述べると便利なことがよくある。
たとえば、コンピュータビジョンで非常に人気のある
ResNet-152アーキテクチャは、 数百もの層を持っている。
これらの層は、繰り返し現れる\ *層のグループ*\ のパターンから構成されている。こうしたネットワークを1層ずつ実装するのは面倒になりがちである。
この懸念は単なる仮説ではない。こうした
設計パターンは実際によく見られる。 上で述べたResNetアーキテクチャは、
認識と検出の両方で2015年のImageNetおよびCOCOのコンピュータビジョン競技会を制し
:cite:`He.Zhang.Ren.ea.2016`
、今なお多くの視覚タスクで定番のアーキテクチャである。
層がさまざまな繰り返しパターンで配置された 同様のアーキテクチャは、
自然言語処理や音声を含む他の分野でも 今や至るところに見られる。
こうした複雑なネットワークを実装するために、
ニューラルネットワークの\ *モジュール*\ という概念を導入する。
モジュールは単一の層、 複数の層からなる構成要素、
あるいはモデル全体そのものを表すことができる!
モジュール抽象化を用いる利点の1つは、
それらをより大きな成果物へと組み合わせられることである。
しかも、その組み合わせはしばしば再帰的に行える。これは
:numref:`fig_blocks`
に示されている。任意の複雑さをもつモジュールを必要に応じて生成するコードを定義することで、
驚くほど簡潔なコードで 複雑なニューラルネットワークを実装できる。
.. _fig_blocks:
.. figure:: ../img/blocks.svg
複数の層がモジュールにまとめられ、より大きなモデルの繰り返しパターンを形成する。
プログラミングの観点では、モジュールは\ *クラス*\ として表現される。
そのサブクラスはすべて、入力を出力へ変換する
順伝播メソッドを定義しなければならず、
必要なパラメータを保存しなければならない。 なお、モジュールの中には
パラメータをまったく必要としないものもある。
最後に、モジュールは勾配を計算するための
逆伝播メソッドを備えていなければならない。 幸いなことに、自動微分
(:numref:`sec_autograd` で導入)
によって提供される裏方の魔法のおかげで、
自分自身のモジュールを定義するときに 私たちが気にする必要があるのは
パラメータと順伝播メソッドだけである。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
import torch
from torch import nn
from torch.nn import functional as F
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
from mxnet import np, npx
from mxnet.gluon import nn
npx.set_np()
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
from typing import List
from d2l import jax as d2l
from flax import linen as nn
import jax
from jax import numpy as jnp
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
import tensorflow as tf
.. raw:: html
.. raw:: html
まず、MLPを実装するために使ったコードを再確認しよう
(:numref:`sec_mlp`)。 次のコードは、
256ユニットとReLU活性化をもつ全結合隠れ層1つと、
10ユニットをもつ全結合出力層1つ(活性化関数なし)からなるネットワークを生成する。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = nn.Sequential(nn.LazyLinear(256), nn.ReLU(), nn.LazyLinear(10))
X = torch.rand(2, 20)
net(X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
torch.Size([2, 10])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
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).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
[07:07:55] ../src/storage/storage.cc:196: Using Pooled (Naive) StorageManager for CPU
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
(2, 10)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = nn.Sequential([nn.Dense(256), nn.relu, nn.Dense(10)])
# get_key is a d2l saved function returning jax.random.PRNGKey(random_seed)
X = jax.random.uniform(d2l.get_key(), (2, 20))
params = net.init(d2l.get_key(), X)
net.apply(params, X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
(2, 10)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
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).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
TensorShape([2, 10])
.. raw:: html
.. raw:: html
この例では、\ ``nn.Sequential``
をインスタンス化し、実行される順序で層を引数として渡すことで
モデルを構築した。 要するに、\ ``nn.Sequential`` は特別な種類の
``Module`` を定義する ものであり、PyTorch
におけるモジュールを表すクラスである。 これは構成要素である ``Module``
の順序付きリストを保持する。 2つの全結合層はいずれも ``Linear``
クラスのインスタンスであり、 それ自体が ``Module``
のサブクラスであることに注意してほしい。
順伝播(\ ``forward``\ )メソッドも非常に単純である。
リスト内の各モジュールを連結し、
それぞれの出力を次の入力として渡していく。
なお、これまで私たちはモデルの出力を得るために ``net(X)``
という構文でモデルを呼び出してきた。 これは実際には ``net.__call__(X)``
の省略形にすぎない。
カスタムモジュール
------------------
モジュールの仕組みを理解する最も簡単な方法は、
自分で1つ実装してみることかもしれない。 その前に、
各モジュールが提供しなければならない基本機能を 簡単にまとめておこう。
1. 順伝播メソッドの引数として入力データを受け取る。
2. 順伝播メソッドが値を返すことで出力を生成する。出力の形状は入力と異なっていてもよい。たとえば、上のモデルの最初の全結合層は任意次元の入力を受け取るが、256次元の出力を返す。
3. 出力の入力に関する勾配を計算する。これは逆伝播メソッドを通じてアクセスできる。通常、これは自動的に行われる。
4. 順伝播計算を実行するために必要なパラメータを保存し、それらへアクセスできるようにする。
5. 必要に応じてモデルパラメータを初期化する。
次のスニペットでは、 隠れユニット256個の隠れ層1つと
10次元の出力層1つをもつMLPに対応するモジュールを ゼロから記述する。
以下の ``MLP``
クラスは、モジュールを表すクラスを継承していることに注意してほしい。
親クラスのメソッドに大きく依存し、
独自に実装するのはコンストラクタ(Pythonでは ``__init__``
メソッド)と順伝播メソッドだけである。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class MLP(nn.Module):
def __init__(self):
# Call the constructor of the parent class nn.Module to perform
# the necessary initialization
super().__init__()
self.hidden = nn.LazyLinear(256)
self.out = nn.LazyLinear(10)
# 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(F.relu(self.hidden(X)))
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class MLP(nn.Block):
def __init__(self):
# Call the constructor of the MLP parent class nn.Block to perform
# the necessary initialization
super().__init__()
self.hidden = nn.Dense(256, activation='relu')
self.out = nn.Dense(10)
# 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
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class MLP(nn.Module):
def setup(self):
# Define the layers
self.hidden = nn.Dense(256)
self.out = nn.Dense(10)
# 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(nn.relu(self.hidden(X)))
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class MLP(tf.keras.Model):
def __init__(self):
# Call the constructor of the parent class tf.keras.Model to perform
# the necessary initialization
super().__init__()
self.hidden = tf.keras.layers.Dense(units=256, activation=tf.nn.relu)
self.out = tf.keras.layers.Dense(units=10)
# 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
まず順伝播メソッドに注目しよう。 これは ``X`` を入力として受け取り、
活性化関数を適用した隠れ表現を計算し、 ロジットを出力することがわかる。
この ``MLP`` 実装では、 両方の層がインスタンス変数である。
これが妥当である理由を理解するために、 ``net1`` と ``net2``
という2つのMLPをインスタンス化し、
それぞれを異なるデータで学習させる場面を想像してほしい。 当然、それらは
2つの異なる学習済みモデルを表すはずである。
私たちはコンストラクタの中で MLPの層をインスタンス化し
その後、順伝播メソッドが呼ばれるたびにこれらの層を呼び出す。
いくつか重要な点に注意してほしい。 まず、カスタマイズした ``__init__``
メソッドは ``super().__init__()`` を通じて親クラスの ``__init__``
メソッドを呼び出しており、 これにより、多くのモジュールに共通する
定型コードを何度も書き直す手間を省いている。
次に、2つの全結合層をインスタンス化し、 それらを ``self.hidden`` と
``self.out`` に代入している。 新しい層を実装しない限り、
逆伝播メソッドやパラメータ初期化について 心配する必要はない。
これらのメソッドはシステムが自動的に生成する。 試してみよう。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = MLP()
if tab.selected('mxnet'):
net.initialize()
net(X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
torch.Size([2, 10])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = MLP()
if tab.selected('mxnet'):
net.initialize()
net(X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
(2, 10)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = MLP()
params = net.init(d2l.get_key(), X)
net.apply(params, X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
(2, 10)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = MLP()
if tab.selected('mxnet'):
net.initialize()
net(X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
TensorShape([2, 10])
.. raw:: html
.. raw:: html
モジュール抽象化の大きな利点は、その柔軟性である。
モジュールをサブクラス化して、 層(たとえば全結合層クラス)、
モデル全体(上の ``MLP`` クラスのようなもの)、
あるいは中間的な複雑さをもつさまざまな構成要素を作れる。 この柔軟性は、
今後の章を通じて活用していく。 たとえば、
畳み込みニューラルネットワークを扱うときなどである。
.. _subsec_model-construction-sequential:
Sequentialモジュール
--------------------
ここで ``Sequential`` クラスの仕組みを もう少し詳しく見てみよう。
``Sequential`` は他のモジュールを
数珠つなぎにするために設計されていたことを思い出してほしい。
独自の簡略版 ``MySequential`` を作るには、
次の2つの重要なメソッドを定義すれば十分である。
1. モジュールを1つずつリストに追加するメソッド。
2. 追加された順序と同じ順でモジュールの連鎖を入力に通す順伝播メソッド。
次の ``MySequential`` クラスは、デフォルトの ``Sequential``
クラスと同じ機能を提供する。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class MySequential(nn.Module):
def __init__(self, *args):
super().__init__()
for idx, module in enumerate(args):
self.add_module(str(idx), module)
def forward(self, X):
for module in self.children():
X = module(X)
return X
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. 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 method, 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
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class MySequential(nn.Module):
modules: List
def __call__(self, X):
for module in self.modules:
X = module(X)
return X
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class MySequential(tf.keras.Model):
def __init__(self, *args):
super().__init__()
self.modules = args
def call(self, X):
for module in self.modules:
X = module(X)
return X
.. raw:: html
.. raw:: html
``__init__`` メソッドでは、\ ``add_modules``
メソッドを呼び出してすべてのモジュールを追加する。これらのモジュールは後で
``children`` メソッドからアクセスできる。
このようにしてシステムは追加されたモジュールを把握し、
各モジュールのパラメータを適切に初期化する。
``MySequential`` の順伝播メソッドが呼び出されると、
追加された各モジュールが 追加された順序で実行される。
これで、\ ``MySequential`` クラスを使って MLPを再実装できる。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = MySequential(nn.LazyLinear(256), nn.ReLU(), nn.LazyLinear(10))
net(X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
torch.Size([2, 10])
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = MySequential()
net.add(nn.Dense(256, activation='relu'))
net.add(nn.Dense(10))
net.initialize()
net(X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
(2, 10)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = MySequential([nn.Dense(256), nn.relu, nn.Dense(10)])
params = net.init(d2l.get_key(), X)
net.apply(params, X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
(2, 10)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = MySequential(
tf.keras.layers.Dense(units=256, activation=tf.nn.relu),
tf.keras.layers.Dense(10))
net(X).shape
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
TensorShape([2, 10])
.. raw:: html
.. raw:: html
``MySequential`` のこの使い方は、 以前 ``Sequential``
クラスについて書いたコード (:numref:`sec_mlp` で説明)
とまったく同じであることに注意してほしい。
順伝播メソッド内でコードを実行する
----------------------------------
``Sequential`` クラスはモデル構築を簡単にし、
自分でクラスを定義しなくても
新しいアーキテクチャを組み立てられるようにしてくれる。
しかし、すべてのアーキテクチャが単純な数珠つなぎとは限らない。
より高い柔軟性が必要な場合には、 独自のブロックを定義したくなる。
たとえば、順伝播メソッドの中で
Pythonの制御フローを実行したいことがあるだろう。
さらに、あらかじめ定義されたニューラルネットワーク層に頼るだけでなく、
任意の数学演算を行いたいこともある。
これまでのところ、 ネットワーク内のすべての演算は
ネットワークの活性化値と そのパラメータに対して作用してきた。
しかし時には、 前の層の結果でも更新可能なパラメータでもない項を
組み込みたいことがある。 これらを\ *定数パラメータ*\ と呼ぶ。 たとえば、
:math:`f(\mathbf{x},\mathbf{w}) = c \cdot \mathbf{w}^\top \mathbf{x}`
という関数を計算する層が欲しいとしよう。 ここで :math:`\mathbf{x}`
は入力、\ :math:`\mathbf{w}` はパラメータ、 :math:`c`
は最適化中に更新されない指定の定数である。 これを次のように
``FixedHiddenMLP`` クラスとして実装する。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. 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))
self.linear = nn.LazyLinear(20)
def forward(self, X):
X = self.linear(X)
X = F.relu(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
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class FixedHiddenMLP(nn.Block):
def __init__(self):
super().__init__()
# Random weight parameters created with the get_constant method
# 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
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class FixedHiddenMLP(nn.Module):
# Random weight parameters that will not compute gradients and
# therefore keep constant during training
rand_weight: jnp.array = jax.random.uniform(d2l.get_key(), (20, 20))
def setup(self):
self.dense = nn.Dense(20)
def __call__(self, X):
X = self.dense(X)
X = nn.relu(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 jnp.abs(X).sum() > 1:
X /= 2
return X.sum()
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. 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
このモデルでは、 重み(\ ``self.rand_weight``\ )が
インスタンス化時にランダムに初期化され、その後は定数となる
隠れ層を実装している。 この重みはモデルパラメータではないため、
逆伝播によって更新されることはない。
その後、ネットワークはこの「固定」層の出力を 全結合層へ通す。
出力を返す前に、 私たちのモデルは少し変わったことをした。 :math:`\ell_1`
ノルムが1より大きいかどうかを条件にした while ループを回し、
条件を満たすまで出力ベクトルを2で割り続けた。 最後に、\ ``X``
の各要素の和を返した。
私たちの知る限り、標準的なニューラルネットワークで
この操作を行うものはない。 この特定の操作が実世界のどんなタスクにも
役立つとは限らない。 ここでの目的は、任意のコードを
ニューラルネットワーク計算の流れに 組み込む方法を示すことだけである。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = FixedHiddenMLP()
if tab.selected('mxnet'):
net.initialize()
net(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor(0.0036, grad_fn=)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = FixedHiddenMLP()
if tab.selected('mxnet'):
net.initialize()
net(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array(0.52637565)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = FixedHiddenMLP()
params = net.init(d2l.get_key(), X)
net.apply(params, X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
Array(-0.13264906, dtype=float32)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
net = FixedHiddenMLP()
if tab.selected('mxnet'):
net.initialize()
net(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
さまざまなモジュールの組み立て方を 組み合わせて使うこともできる。
次の例では、モジュールを いくつか創造的な方法で入れ子にしている。
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class NestMLP(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(nn.LazyLinear(64), nn.ReLU(),
nn.LazyLinear(32), nn.ReLU())
self.linear = nn.LazyLinear(16)
def forward(self, X):
return self.linear(self.net(X))
chimera = nn.Sequential(NestMLP(), nn.LazyLinear(20), FixedHiddenMLP())
chimera(X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
tensor(-0.0192, grad_fn=)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. 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)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
array(0.97720534)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. code:: python
class NestMLP(nn.Module):
def setup(self):
self.net = nn.Sequential([nn.Dense(64), nn.relu,
nn.Dense(32), nn.relu])
self.dense = nn.Dense(16)
def __call__(self, X):
return self.dense(self.net(X))
chimera = nn.Sequential([NestMLP(), nn.Dense(20), FixedHiddenMLP()])
params = chimera.init(d2l.get_key(), X)
chimera.apply(params, X)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
Array(-0.05416024, dtype=float32)
.. raw:: html
.. raw:: html
.. raw:: latex
\diilbookstyleinputcell
.. 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)
.. raw:: latex
\diilbookstyleoutputcell
.. parsed-literal::
:class: output
.. raw:: html
.. raw:: html
要約
----
個々の層はモジュールになり得る。 多くの層が1つのモジュールを構成できる。
多くのモジュールが1つのモジュールを構成できる。
モジュールはコードを含むことができる。
モジュールは、パラメータ初期化や逆伝播を含む多くの雑務を引き受けてくれる。
層やモジュールの順次連結は ``Sequential`` モジュールによって処理される。
演習
----
1. ``MySequential``
をPythonのリストにモジュールを保存するよう変更すると、どのような問題が起こるか?
2. 2つのモジュール、たとえば ``net1`` と ``net2``
を引数に取り、順伝播で両方のネットワークの連結出力を返すモジュールを実装せよ。これは\ *並列モジュール*\ とも呼ばれる。
3. 同じネットワークの複数インスタンスを連結したいとする。同じモジュールの複数インスタンスを生成するファクトリ関数を実装し、それを使ってより大きなネットワークを構築せよ。