PyTorch 1.3 : Getting Started : ニューラルネットワーク

PyTorch 1.3 : Getting Started : ニューラルネットワーク (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/09/2019 (1.3.1)

* 本ページは、PyTorch 1.3 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

ニューラルネットワーク

ニューラルネットワークは torch.nn パッケージを使用して構築できます。

autograd を簡単に見た今、nn はモデルを定義してそれらを微分するために autograd に依拠します。nn.Module は層、そして出力を返すメソッド forward(input) を含みます。

例えば、数字画像を分類するこのネットワークを見てください :

 

convnet

 
それは単純な feed-forward ネットワークです。それは入力を取り、それを幾つかの層を次々に通して供給し、そして最後に出力を与えます。

ニューラルネットワークのための典型的な訓練手続きは以下のようなものです :

  • 幾つかの学習可能なパラメータ (or 重み) を持つニューラルネットワークを定義する
  • 入力のデータセットに渡り反復する
  • 入力をネットワークを通して処理する
  • 損失を計算する (出力が正解からどのくらい遠いか)
  • 勾配をネットワークのパラメータに逆伝播する
  • ネットワークの重みを更新する、典型的には単純な更新ルールを使用します :
    weight = weight – learning_rate * gradient

 

ネットワークを定義する

このネットワークを定義しましょう :

import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 3x3 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 6 * 6, 120)  # 6*6 from image dimension
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)
Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

貴方は forward 関数を定義しなければならないだけです、そして backward 関数 (そこでは勾配が計算されます) は autograd を使用して貴方のために自動的に定義されます。forward 関数では任意の Tensor 演算が使用できます。

モデルの学習可能なパラメータは net.parameters() で返されます。

params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight
10
torch.Size([6, 1, 3, 3])

ランダムな 32×32 入力を試してみましょう。
Note: この net (LeNet) の想定される入力サイズは 32×32 です。MNIST データセット上でこのネットを使用するためには、データセットからの画像を 32×32 にリサイズしてください。

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[-0.0315,  0.1054,  0.1189,  0.0473,  0.0564, -0.0528, -0.0297,  0.0778,
          0.0320, -0.0666]], grad_fn=<AddmmBackward>)

総てのパラメータの勾配バッファをゼロにしてランダムな勾配で backprop します :

net.zero_grad()
out.backward(torch.randn(1, 10))

 
[Note]

torch.nn はミニバッチをサポートするだけです。torch.nn パッケージ全体は、単一のサンプルではない、サンプルのミニバッチである入力をサポートするだけです。

例えば、nn.Conv2d は サンプル数 x チャネル数 x 高さ x 幅 – nSamples x nChannels x Height x Width の 4D Tensor を取ります。

もし貴方が単一のサンプルを持つ場合には、fake バッチ次元を付加するために単に input.unsqueeze(0) を使用してください。

 
更に進める前に、ここまでに見た総てのクラスをおさらいしましょう。

復習:

  • torch.Tensor – backward() のような autograd 演算のためのサポートを持つ多次元配列です。tensor に関する勾配もまた保持します。
  • nn.Module – ニューラルネットワーク・モジュール。パラメータをカプセル化する便利な方法で、それらを GPU に移し、エクスポートし、ロードする等のためのヘルパーを持ちます。
  • nn.Parameter – Tensor の一種で、それは Module への属性として割り当てられる時には自動的にパラメータとして登録されます。
  • autograd.Function – autograd 演算の forward と backward 定義を実装します。総ての Tensor 演算は少なくとも単一の Function ノードを作成し、これは Tensor を作成した関数に接続してその履歴をエンコードします。

 
この時点で、以下をカバーしました :

  • ニューラルネットワークを定義する
  • 入力を処理して backward を呼び出す。

 
Still Left:

  • 損失を計算する
  • ネットワークの重みを更新する

 

損失関数

損失関数は入力の (output, target) ペアを取って、output が target からどのくらい遠く離れているかを推定する値を計算します。

nn パッケージの下には幾つかの異なる 損失関数 があります。単純な損失は : nn.MSELoss で、これは入力とターゲット間の平均二乗誤差 (mean-squared error) を計算します。

例えば :

output = net(input)
target = torch.randn(10)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)
tensor(1.3008, grad_fn=<MseLossBackward>)

 
さて、.grad_fn 属性を利用して、backward 方向に損失を追う場合、このような計算グラフを見るでしょう :

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss

そして、loss.backward() を呼び出す時、グラフ全体は損失に関して微分され、requires_grad=True を持つグラフの総ての Tensor は勾配が累積されたそれらの .grad Tensor を持ちます。

例として、backward の 2,3 のステップを追いましょう :

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU
<MseLossBackward object at 0x7fb2a5c5ee10>
<AddmmBackward object at 0x7fb2a5c5eeb8>
<AccumulateGrad object at 0x7fb2a5c5eeb8>

 

Backprop

誤差を逆伝播するために行なわなければならないことの総ては loss.backward() です。けれども既存の勾配をクリアする必要があります、そうでないなら勾配は既存の勾配に累積されます。

今は loss.backward() を呼び出します、そして backward 前後の conv1 の bias 勾配を見てみます。

net.zero_grad()     # zeroes the gradient buffers of all parameters

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)

loss.backward()

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0029, -0.0248, -0.0094, -0.0121, -0.0108,  0.0112])

さて、損失関数をどのように使用するかを見ました。

Read Later:

ニューラルネットワーク・パッケージは深層ニューラルネットワークのビルディングブロックを形成する様々なモジュールと損失関数を含みます。文書による完全なリストは こちら です。

The only thing left to learn is:

  • ネットワークの重みを更新する。

 

重みを更新する

実際に使用される最も単純な更新ルールは確率的勾配降下 (SGD, Stochastic Gradient Descent) です :
weight = weight – learning_rate * gradient

これは単純な python コードを使用して実装できます :

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

けれども、ニューラルネットワークを使用する時、SGD, Nesterov-SGD, Adam, RMSProp 等のような様々な異なる更新ルールを使用することを望むでしょう。これを可能にするために、小さいパッケージ : torch.optim を構築しました、これはこれら総てのメソッドを実装しています。それを使用するのは非常に単純です :

import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

Note:
どのように勾配バッファが optimizer.zero_grad() を使用して手動でゼロに設定されなければならないかを注視してください。これは Backprop セクションで説明されたように勾配が累積されるからです。

 
以上