菅間修正済み 2025/05/15
【原題】LEARNING PYTORCH WITH EXAMPLES
【原著】Justin Johnson
【元URL】https://pytorch.org/tutorials/beginner/pytorch_with_examples.html
【翻訳】電通国際情報サービスISID AIトランスフォーメーションセンター 徳原 光
【日付】2020年11月18日
【チュトーリアル概要】
「2層から構成される全結合型のReLUネットワーク」の訓練を、Numpyのみを用いて実装するところから始め、徐々にPyTorchの各要素を追加していき、最終的にPyTorchでのニューラルネットワークを訓練例を実装します。
本チュートリアルではPyTorch の基本的な概念を、簡単に完結するサンプル例を用いて解説します。
PyTorch の中心となる特徴は以下の2つです。
実行例として、全結合型のReLUネットワークを使用します。
このネットワークは一層の隠れ層を持ち、勾配降下法によりネットワークの出力と正しい出力のユークリッド距離を最小にすることで、ランダムなデータに適合するように訓練されます。
参考
本ページの最後に、本チュートリアルで解説するPyTorchの各要素について、個別例を掲載しています。
PyTorchを紹介する前に、まずはNumpyを使ってネットワークを実装します。
Numpyはn次元の配列オブジェクトとそれらの配列を操作するための様々な関数を用意しています。
Numpyは科学計算の一般的なフレームワークですが、計算グラフやディープラーニング、勾配の解析については対応していません。
ですが、Numpyを使用することで簡単に、ネットワークを介する順方向と逆方向のパスを手動で実装でき、2層のネットワークにランダムなデータを学習させることができます。
# -*- coding: utf-8 -*-
import numpy as np
# N:バッチサイズ D_in:入力層の次元数
# H:隠れ層の次元数 D_out: 出力層の次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# 乱数により入力データと目標となる出力データを生成
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)
# 乱数による重みの初期化
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)
learning_rate = 1e-6
for t in range(500):
# 順伝播: 予測値yの計算
h = x.dot(w1)
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2)
# 損失の計算と表示
loss = np.square(y_pred - y).sum()
if t % 100 == 99:
print(t, loss)
# 逆伝搬:損失に対するW1とw2の勾配の計算
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.T.dot(grad_y_pred)
grad_h_relu = grad_y_pred.dot(w2.T)
grad_h = grad_h_relu.copy()
grad_h[h < 0] = 0
grad_w1 = x.T.dot(grad_h)
# 重みの更新
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
99 595.2286394490034 199 2.2796256148503877 299 0.012407668371458822 399 7.459388200494001e-05 499 4.804021841854132e-07
日本語訳注:はじめ大きかった損失が学習を行うことで限りなく0に近づきます。
学習後のネットワークの出力を確認すると、最初にランダムに定義した正解データとほぼ一致することが分かります。
#日本語訳追記:
#学習前の重みを定義(前の学習で使用された重みの初期値とは異なります)
w1_unlearned = np.random.randn(D_in, H)
w2_unlearned = np.random.randn(H, D_out)
#学習前の出力を算出
h = x.dot(w1_unlearned)
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2_unlearned)
print(f"学習前出力:{np.round(y_pred[0], decimals=2)}")
#学習によって得られた重みで出力を算出
h = x.dot(w1)
h_relu = np.maximum(h, 0)
y_pred = h_relu.dot(w2)
print(f"学習後出力:{np.round(y_pred[0], decimals=2)}")
#目的の出力yとの比較
print(f"目的の出力:{np.round(y[0], decimals=2)}")
学習前出力:[ 98.13 356.58 24.84 -88.57 361.59 -55.75 -129.95 -61.94 24.65 206.73] 学習後出力:[ 0.51 -0.75 0.59 1.51 -0.76 -0.67 -0.42 -0.14 0.31 -2.07] 目的の出力:[ 0.51 -0.75 0.59 1.51 -0.76 -0.67 -0.42 -0.14 0.31 -2.07]
Numpyは素晴らしいフレームワークですが、GPUを利用した数値計算の高速化をすることができません。
近年のディープニューラルネットワークの場合、GPUの利用することで50倍以上の高速化が実現できます。
残念ながら現代のディープラーニングを扱うには、Numpyでは十分と言えません。
ここで、PyTorch の最も基本的な概念であるTensorを紹介します。
PyTorch のTensorは、概念的には Numpy 配列に対応するものです。
Tensorはn次元の配列であり、PyTorchではこれらのTensorを操作するための様々な関数を用意しています。
裏側では、PyTorchのTensorは計算グラフの構築、勾配の追跡を行っています。
また科学計算の汎用的なツールとしても、Tensorは役立ちます。
Numpy とは異なり、PyTorch tenosrはGPUを利用して数値計算を高速化することができます。
簡単に専用のデータ型に変換するだけで、PyTorch TensorをGPU上で操作することができます。
ここではPyTorch Tensorsを使って、2層のネットワークにランダムデータを学習させています。
先ほどのNumpyの例のように、ネットワークを通して学習させるために、手動でネットワークを介する順伝播と逆伝播の経路を実装する必要があります:
# -*- coding: utf-8 -*-
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPUで実行する場合はコメントアウトを解除してください。
# N:バッチサイズ D_in:入力層の次元数
# H:隠れ層の次元数 D_out: 出力層の次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# 乱数により入力データと目標となる出力データを生成
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
# 乱数による重みの初期化
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)
learning_rate = 1e-6
for t in range(500):
# 順伝播: 予測値yの計算
h = x.mm(w1)
h_relu = h.clamp(min=0)
y_pred = h_relu.mm(w2)
# 損失の計算と表示
loss = (y_pred - y).pow(2).sum().item()
if t % 100 == 99:
print(t, loss)
# 逆伝搬:損失に対するW1とw2の勾配の計算
grad_y_pred = 2.0 * (y_pred - y)
grad_w2 = h_relu.t().mm(grad_y_pred)
grad_h_relu = grad_y_pred.mm(w2.t())
grad_h = grad_h_relu.clone()
grad_h[h < 0] = 0
grad_w1 = x.t().mm(grad_h)
# 確率的勾配降下法による重みの更新
w1 -= learning_rate * grad_w1
w2 -= learning_rate * grad_w2
99 729.8086547851562 199 3.817234992980957 299 0.03243345022201538 399 0.0005787887494079769 499 7.290346547961235e-05
さきほどの実装例では、ニューラルネットワークの順伝播の経路(関数)と逆伝播の経路(関数)の両方を、手で実装しなければなりませんでした。
逆伝播を手動で実装する作業は、小さな2層ネットワークでは大した問題ではありませんが、大規模で複雑なネットワークではたちまち困難となります。
ありがたいことに、自動微分の機能を使用すれば、ニューラルネットワークの逆伝播の経路における計算を自動化することが可能です。
PyTorch の autograd パッケージはまさにこの機能を提供しています。
autogradを使用する場合、まず計算グラフでネットワークの順伝搬の経路を定義します。
計算グラフのノードにはTensorが対応し、エッジは入力Tensorから出力Tensorを算出する関数になります。
このグラフを逆伝播することで、勾配を簡単に計算することができます。
この説明は複雑に聞こえますが、実際にはとても簡単に使用することができます。
各Tensorは計算グラフのノードを表します。
xが属性パラメータにx.requires_grad=Trueを持つTensorの場合、x.gradはとあるスカラー値に対するxの勾配を(また異なる)Tensorで保持しています。
PyTorch Tensorsとautogradを使い2層のネットワークを実装ししましょう。
PyTorch Tensorsとautogradを使用すれば、逆伝播の経路を手動で定義する必要がなくなります。
# -*- coding: utf-8 -*-
import torch
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0")
# GPUで実行する場合はコメントアウトを解除してください。
# torch.backends.cuda.matmul.allow_tf32 = False
# GPUで実行する場合はコメントアウトを解除してください。
# 上の行でTensorFloat32を無効にしています。
# TensorFloat32を用いると精度を犠牲にして、ネットワークの高速化を図ることができます。
# TensorFloat32は多くのモデルにおいて有効ですが、このチュートリアルで取り扱う簡易的な
# モデルでは、精度が下がることが原因で損失が収束しないという問題が発生します。
# 詳細については、以下を参照してください:
# https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices
# N:バッチサイズ D_in:入力層の次元数
# H:隠れ層の次元数 D_out: 出力層の次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# 乱数により入力データと目標となる出力データを保持するTensorを生成
# 逆伝播の際にこのTensorに対する勾配を計算する必要がない場合は、
# requires_grad=Falseを指定します。
# 日本語訳注:デフォルトではrequires_gradはFalseが設定されています。
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
# 乱数による重みを表すTensorの定義
# 逆伝播の際、このTensorに対する勾配を計算する場合は、requires_grad=Trueを指定します
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
# 順伝播: Tensorの演算を利用して予測結果yの算出
# ここはTensorsを使用した順伝播の計算と全く同じ操作ですが、逆伝播の経路を手動で
# 定義していないので、順伝播の途中の値を保持しておく必要はありません。
y_pred = x.mm(w1).clamp(min=0).mm(w2)
# Tensorを用いた損失の計算と表示
# 損失は形状[1,]のTensorになります。
# loss.item() は損失を表すTensorの持つ値をスカラー値で返します
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# autograd を使用して逆伝播の計算をします。
# backward()によりrequires_gradにTrueが設定されているすべてのTensorに対して、
# 損失の勾配を計算を行います。これにより、w1.gradとw2.gradはそれぞれw1とw2に
# 対する損失の勾配を保持するTensorになります。
loss.backward()
# 確率的勾配降下法を使って手動で重みを更新します。
# 重みには requires_gradにTrue が設定されていますが、ここではautogradによる
# 追跡を避ける必要があるので、torch.no_grad()のブロック内で実行します。
# 別の方法としては、weight.dataとweight.grad.dataを利用する方法があります。
# tensor.dataはtensorと保存領域を共有するTensorを返しますが、履歴は追跡されません。
# torch.optim.SGDを利用して追跡を回避することも可能です。
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 重みの更新後、手動で勾配を0に初期化
w1.grad.zero_()
w2.grad.zero_()
99 574.0633544921875 199 5.216662406921387 299 0.08640798926353455 399 0.002048665890470147 499 0.0001755372795742005
実際のところautograde演算子は、Tensor上で動作する2つの関数を備えています。
forward(順伝播)関数は、入力のTensorから出力のTensorを計算します。
backward(逆伝播)関数は、とあるスカラー値の出力Tensorの勾配を受け取り、同じスカラー値に対応する入力Tensorの勾配を計算します。
PyTorchでは、torch.autograd.Functionを継承したサブクラスを定義しforward、backwardを実装することで、簡単に独自のautograd演算子を定義することができます。
インスタンスを構築して関数のように呼び出し、入力データを含むTensorを渡すことで、新しく独自に定義したautograd 演算子を使用することができます。
以下の例では、ReLUの非線形性を利用するために独自にautograd関数を定義し、使用することで2層ネットワークを実装します:
# -*- coding: utf-8 -*-
import torch
class MyReLU(torch.autograd.Function):
"""
torch.autograd.Functionをサブクラス化し、Tensors上で動作する順伝播経路と
逆伝播経路を定義することで、独自のautograd Functionsを実装することができます。
"""
@staticmethod
def forward(ctx, input):
"""
順伝播の経路では入力を含むTensorを受け取り、出力を含むTensorを返します。
ctxは逆伝播の際に必要な情報を格納するコンテキストオブジェクトです。
ctx.save_for_backwardメソッドを使用して、任意のオブジェクトを一時的に保持し、
逆伝播時に使用することができます。
"""
ctx.save_for_backward(input)
return input.clamp(min=0)
@staticmethod
def backward(ctx, grad_output):
"""
逆伝播の経路では、出力に対する損失の勾配を含むTensorを受け取り、
入力に対する損失の勾配を計算する必要があります。
"""
input, = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[input < 0] = 0
return grad_input
dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0")
# GPUで実行する場合はコメントアウトを解除してください。
# torch.backends.cuda.matmul.allow_tf32 = False
# GPUで実行する場合はコメントアウトを解除してください。
# 上の行でTensorFloat32を無効にします。
# TensorFloat32を用いると精度を犠牲にして、ネットワークの高速化を図ることができます。
# TensorFloat32は多くのモデルにおいて有効ですが、このチュートリアルで取り扱う簡易的な
# モデルでは、精度が下がることが原因で損失が収束しないという問題が発生します。
# 詳細については、以下を参照してください:
# https://pytorch.org/docs/stable/notes/cuda.html#tensorfloat-32-tf32-on-ampere-devices
# N:バッチサイズ D_in:入力層の次元数
# H:隠れ層の次元数 D_out: 出力層の次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# 乱数により入力データと目標となる出力データを表すTensorを生成
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
# 乱数による重みを表すTensorの定義
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
# 関数を適用するには、Function.applyメソッドを用います。
# reluと命名しておきます。
relu = MyReLU.apply
# 順伝播:独自のautograd操作を用いてReLUの出力を算出することで予想結果yを計算します。
y_pred = relu(x.mm(w1)).mm(w2)
# 損失の計算と表示
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# autogradを利用して逆伝播を実施
loss.backward()
# 確率的勾配降下法を用いた重みの更新
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 重みの更新後、手動で勾配を0に初期化
w1.grad.zero_()
w2.grad.zero_()
99 573.1611328125 199 3.8259620666503906 299 0.04694724082946777 399 0.0009570475667715073 499 9.914684778777882e-05
複雑な演算子を定義し自動的に導関数を得るには、計算グラフとautogradは非常に強力な手法と言えます。
しかしながら、大規模なニューラルネットワークを実装する場合は、autogradをそのまま利用するのは少し細かすぎて、面倒かもしれません。
ニューラルネットワークを構築する際、レイヤーに演算処理を組み込むことがよくあります。
このレイヤー内には学習可能なパラメータを持ち、訓練中に最適化されるものもあります。
TensorFlowでは、KerasやTensorFlow-Slim、TFLearnのようなパッケージが、ニューラルネットワークの構築に有用な計算グラフそのものを高レベルに抽象化しています。
PyTorchでは、nnパッケージが同じ機能を提供しています。
nnパッケージは、ニューラルネットワークのレイヤーとほぼ同等な、モジュールのセットを定義しています。
モジュールは入力Tensorを受け取り、出力Tensorを計算しますが、学習可能なパラメータを含むTensorなどの内部状態を保持していることもあります。
またnnパッケージは、通常ニューラルネットワークを訓練する際に使われる便利な損失関数のセットを定義しています。
以下の例では、nnパッケージを使用して 2 層ネットワークを実装しています:
# -*- coding: utf-8 -*-
import torch
# N:バッチサイズ D_in:入力層の次元数
# H:隠れ層の次元数 D_out: 出力層の次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# 乱数により入力データと目標となる出力データを表すTensorを生成
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# nnパッケージを利用し、レイヤーの連なりとしてモデルを定義します。
# nn.Sequentialは他のモジュールを並べて保持することで、それぞれのモジュールを順番に
# 実行し、出力を得ます。各Linearモジュールは線形関数を使用して入力から出力を計算し、
# 重みとバイアスを内部のTensorで保持します。
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
# nnパッケージには、一般的な損失関数が含まれています。
# 今回は損失関数として平均二乗誤差(MSE)を使用します。
loss_fn = torch.nn.MSELoss(reduction='sum')
learning_rate = 1e-4
for t in range(500):
# 順伝播: モデルを流れる入力xから予測値yを計算します。
# Pythonのモジュールオブジェクトは__call__演算子をオーバーライドするため、
# 関数のように呼び出すことができます。これにより、入力データのTensorを
# モジュールに渡すことで、出力データのTensorを得ることができます。
y_pred = model(x)
# 損失の計算と表示
# 損失関数にyの予測値と正解の値を持つTensorを渡すことで損失を持つTenosrを
# 得ることができます。
loss = loss_fn(y_pred, y)
if t % 100 == 99:
print(t, loss.item())
# 逆伝播の前に勾配を0に初期化
model.zero_grad()
# 逆伝播: モデルの学習可能なすべてのパラメータに対して損失の勾配を計算
# 内部では、requires_grad=TrueとなっているすべてのTensorにそれぞれのモデルのパラメータが
# 保持されているので、モデルが持つ学習可能なパラメータの勾配をすべて計算することできます。
loss.backward()
# 確率的勾配降下法を用いた重みの更新
# 各々のパラメータはTensorなので、これまでと同じ方法で勾配を参照することができます。
with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad
99 2.750652313232422 199 0.03932018205523491 299 0.0010913192527368665 399 4.720408105640672e-05 499 2.65761468654091e-06
ここまで、学習可能なパラメータを保持しているTensorを手動で演算することでモデルの重みを更新してきました
(autogradによる計算履歴の追加を避けるために、torch.no_grad()や.dataを使用しました)。
このような手法は確率的勾配降下法のようなシンプルな最適化アルゴリズムではそれほど負担になりませんが、実際にはAdaGrad、RMSProp、Adamなどのより高度なオプティマイザーを使ってニューラルネットワークを訓練することがよくあります。
PyTorchのoptimパッケージは最適化アルゴリズムの手続きを抽象化し、一般的に使用されている、各種最適化アルゴリズムを用意しています。
以下の例では、nnパッケージを使用して以前と同様にモデルを定義しますが、optimパッケージが提供するAdamアルゴリズムを使用してモデルを最適化します:
import torch
# N:バッチサイズ D_in:入力層の次元数
# H:隠れ層の次元数 D_out: 出力層の次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# 乱数により入力データと目標となる出力データを表すTensorを生成
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# nnパッケージを用いてモデルと損失関数を定義
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(reduction='sum')
# optimパッケージを使用して、モデルの重みを更新するオプティマイザを定義します。
# ここではAdamを使用します。optimパッケージには他にも多くの最適化アルゴリズムが存在ます。
# Adamのコンストラクタの最初の引数により、オプティマイザがどのTensorを更新するか指定できます。
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
# 順伝播:入力xから予測値yをモデルで算出します。。
y_pred = model(x)
# 損失の計算と表示
loss = loss_fn(y_pred, y)
if t % 100 == 99:
print(t, loss.item())
# 逆伝播に入る前に、更新されることになる変数(モデルの学習可能な重み)の勾配を
# optimaizerを使用して0に初期化します。
# これは、デフォルトで.backward()が呼び出される度に勾配がバッファに蓄積されるため
# 必要になる操作です(上書きされるわけではない)。
# 詳しくはtorch.autograd.backwardのドキュメントを参照してください。
optimizer.zero_grad()
# 逆伝播:モデルのパラメータに対応する損失の勾配を計算
loss.backward()
# オプティマイザのstep関数を呼び出すことでパラメータを更新
optimizer.step()
99 76.77259826660156 199 1.5349969863891602 299 0.022764280438423157 399 0.0002311780262971297 499 1.3200049124861835e-06
場合によっては、既存のモジュールにある一連の演算処理よりも、複雑なモデルを使用したいケースがあるかもしれません。
そのような場合には、nn.Moduleをサブクラス化し、他のモジュールやTensor上の他のautograd操作を利用して入力Tensorから出力Tensorを生成するforwardメソッドを定義することで、ユーザー独自のモジュールを改変、構築することができます。
以下の例では、2 層ネットワークをカスタムモジュールのサブクラスとして実装しています:
# -*- coding: utf-8 -*-
import torch
class TwoLayerNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
"""
コンストラクタにより2つのnn.Linearモジュールをインスタンスとし定義し
それらをメンバ変数に割り当てます。
"""
super(TwoLayerNet, self).__init__()
self.linear1 = torch.nn.Linear(D_in, H)
self.linear2 = torch.nn.Linear(H, D_out)
def forward(self, x):
"""
forward関数は入力データのTensorを受け入れ、出力データのTensorを返します。
Tensorの任意の演算子と同様に、コンストラクタで定義されたモジュールを使用できます。
"""
h_relu = self.linear1(x).clamp(min=0)
y_pred = self.linear2(h_relu)
return y_pred
# N:バッチサイズ D_in:入力層の次元数
# H:隠れ層の次元数 D_out: 出力層の次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# 乱数により入力データと目標となる出力データを表すTensorを生成
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# 上で定義したクラスをインスタンス化してモデルを構築
model = TwoLayerNet(D_in, H, D_out)
# 損失関数とオプティマイザを定義します。
# model.parameters()を呼び出すことで、モデルのメンバ変数である2つのnnn.Linearモジュールの
# 学習可能なパラメータをSGDのコンストラクタの引数として渡すことができます。
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
# 順伝播:入力xから予測値yをモデルで算出します。
y_pred = model(x)
# 損失の計算と表示
loss = criterion(y_pred, y)
if t % 100 == 99:
print(t, loss.item())
# 勾配を0に初期化し、逆伝播を実行することで重みを更新
optimizer.zero_grad()
loss.backward()
optimizer.step()
99 2.5520431995391846 199 0.045577406883239746 299 0.0014395791804417968 399 5.797167614218779e-05 499 2.7120522645418532e-06
動的グラフと重みの共有の実例として、非常に奇妙なモデルを実装してみます。
以下のネットワークは全結合型のReLUネットワークであり、順伝播の実装において1から4の乱数を生成し、その乱数の数だけ隠れ層を並べます。
そして、すべての隠れ層で同じ重みを使用して計算し、一番最後に配置された隠れ層への入力が算出されます。
繰り返し処理を実装するために通常のPythonの制御フローを使用することが可能です。
さらに、順伝播の経路を定義する際に同じモジュールを複数回再利用するので、中間の隠れ層の間で重みが共有されます。
このようなモデルは、nnジュールのサブクラスとして簡単に実装することができます:
# -*- coding: utf-8 -*-
import random
import torch
class DynamicNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
"""
コンストラクタにより、順伝播で使用する3層のnn.Linearのインスタンスを定義します。
"""
super(DynamicNet, self).__init__()
self.input_linear = torch.nn.Linear(D_in, H)
self.middle_linear = torch.nn.Linear(H, H)
self.output_linear = torch.nn.Linear(H, D_out)
def forward(self, x):
"""
順伝播の経路の実装では、ランダムに0から3までの値を選択し、
その数だけ中間層のモジュールを再利用することで、隠れ層の出力を計算します。
それぞれの順伝播の経路を表す計算グラフは動的に変化するので、
繰り返しや、条件分岐といったPythonの標準的なフロー制御を利用して
順伝播の経路を定義することができます。
この結果から確認できるように、計算グラフを定義する際に、問題なく何度も同じ
モジュールを使いまわすことができるのは、一度しかモジュールを利用することが
できなったLua Torchから大きく改善したところと言えます。
"""
h_relu = self.input_linear(x).clamp(min=0)
for _ in range(random.randint(0, 4)):
h_relu = self.middle_linear(h_relu).clamp(min=0)
y_pred = self.output_linear(h_relu)
return y_pred
# N:バッチサイズ D_in:入力層の次元数
# H:隠れ層の次元数 D_out: 出力層の次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# 乱数により入力データと目標となる出力データを表すTensorを生成
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)
# 上で定義したクラスをインスタンス化してモデルを構築します。
model = DynamicNet(D_in, H, D_out)
# 損失関数とオプティマイザを定義します。
# この奇妙なモデルを通常の確率勾配降下法で訓練するのは難しいので、モーメンタムを使用します。
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
# 順伝播:入力xから予測値yをモデルで算出します。
y_pred = model(x)
# 損失の計算と表示
loss = criterion(y_pred, y)
if t % 100 == 99:
print(t, loss.item())
# 勾配を0に初期化し、逆伝播を実行することで重みを更新します
optimizer.zero_grad()
loss.backward()
optimizer.step()
99 23.979511260986328 199 15.366857528686523 299 2.112053871154785 399 20.67656707763672 499 0.6824043989181519
本チュートリアルで実施してきた、各実行例は以下からひとつずつ確認できます。