【PyTorch】チュートリアル(日本語版 )③ 〜NEURAL NETWORKS(ニューラルネットワーク)〜

目次


1. 目的
2. NEURAL NETWORKS(ニューラルネットワーク)
3. ニューラルネットワークの定義
4. Loss Function (損失関数)
5. Backprop (誤差逆伝播)
6. 重み(weights)の更新


1. 目的

PyTorchのチュートリアルNeural Networksを参考にPyTorchでニューラルネットワークの構築と学習について学ぶ。

2. NEURAL NETWORKS(ニューラルネットワーク)

PyTorchのニューラルネットワークは、torch.nnパッケージで作ることができます。

チュートリアル②では、autogradを紹介しました。

これからご紹介するnnでニューラルネットのモデルを定義をすることができます。nn.Moduleには、様々なレイヤー(convolution, activation functionなど)やoutputを返すforward(input)メソッドがあります。

ニューラルネットワークの基本的なトレーニング手順は以下の通りです。

  • ニューラルネットワーク(モデル)の適宜
  • データをニューラルネットワークに入力
  • loss (実際の正解とどれだけ異なっていたか)を計算
  • lossが小さくなるようにニューラルネットワークのパラメータを更新

3. ニューラルネットワークの定義

実際に、ニューラルネットワークモデルを作って見ましょう。
以下のコードを実行してみます。

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)

以上の長いコードをいくつかのパートに分けて、説明していきます。

まず最初に、必要なパッケージをimportします。

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

次に、class Net(nn.Module):でニューラルネットワークモデル用のクラスを定義します。

使用するレイヤーの定義はここでされます。
この例では、2DのConvolutionレイヤーとAffine(fc)レイヤーが使われています。
nn.Conv2d(1, 6, 3)は入力画像のチャネルが1、畳み込み後の出力が8チャネル、kernelが3×3になります。

nn.Linear(16 * 6 * 6, 120)は、入力が1666、出力が120であることを示しています。
nn.Conv2d(6, 16, 3)から1枚の画像サイズ6×6が16枚、出力されるのでそれらをflattenにするため、16 * 6 * 6という計算になっています。

    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 __init__(self):で定義したレイヤーを使って実際にモデルを構築しています。
この場合、Conv1 > ReLU > Max pooling > Conv2 > ReLU > Max pooling > fc1 > ReLU > fc2 > ReLU > fc3の順にデータが流れていきます。

    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

作ったモデルを確認します。

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)
)

ニューラルネットワークの定義ができたので、このモデルに入力するためのデータを作成します。
乱数をつかって1チャネル32×32のデータを作って、先程のモデルに入力します。
このとき出力結果をoutに格納します。

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

tensor([[ 0.0658, 0.1263, -0.0645, -0.0134, 0.0501, -0.0124, 0.0317, -0.0344,
0.1009, -0.0080]], grad_fn=)

最後のレイヤーは(fc3): Linear(in_features=84, out_features=10, bias=True)なので10個のテンソルが出力されます。

一度、勾配(gradient)をゼロに初期化後、ニューラルネットワークからの出力outの勾配を計算。


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

4. Loss Function (損失関数)

損失関数は、目的となる答えとニューラルネットワークが出力した結果がどれだけ離れているかを計算します。
nnパッケージには、様々な損失関数があります。
中でも最もシンプルなのがnn.MSELossです。
この損失関数では、実際の答えとニューラルネットワークの出力との平均二乗誤差(mean-squared error)を計算します。

output = net(input)
target = torch.randn(10)  # 適当にダミーの答えを乱数で作成
target = target.view(1, -1)  # 出力と答えのサイズをそろえる。
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

tensor(0.4848, grad_fn=)

OutputからInputにかけてのlossを追う場合には、.grad_fn属性を使います。これによって、このような計算グラフをみることができます。

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

ここで、loss.backward()を呼ぶと全体の計算グラフを微分することができます。さらに、requires_grad=Trueとなっているテンソルは、.grad属性を持ち、勾配が蓄積されていきます。

どのようにlossを追っていくかは、以下のコードで確認します。

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 0x7f60ae3a2f60
AddmmBackward object at 0x7f60ae3a2eb8
AccumulateGrad object at 0x7f60ae3a2f28

5. Backprop (誤差逆伝播)

誤差逆伝播法は、loss.backward()で実行することができます。
誤差逆伝播をする前に、net.zero_grad()で勾配を一度リセットする必要があります。
conv1における逆伝播前後の勾配をloss.backward()を使って、見てみます。

net.zero_grad()     # 勾配をゼロに

print('conv1.bias.grad before backward')
print(net.conv1.bias.grad) # 逆伝播前のconv1の勾配

loss.backward() # 逆伝播

print('conv1.bias.grad after backward')
print(net.conv1.bias.grad) # 逆伝播後のconv1の勾配

conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0111, -0.0162, 0.0002, -0.0016, -0.0076, -0.0027])

6. 重み(weights)の更新

確率勾配降下法(SGD: stochastic gradient descent)は、重みを更新する上でよく使われる最適化アルゴリズムで、以下の式で表されます。

weight = weight - learning_rate * gradient

SGDは、Pythonコードで以下のようにして実装することができます。
この例では、学習率を0.01として、ニューラルネットワークにあるすべての重み(net.parameters())を更新しています。

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

ただし、この最適化アルゴリズムはSGD, Nesterov-SGD, Adam, RMSPropなど様々であり、使い分けた場合があります。
その場合には、torch.optimを使うとよいです。
SGDを使う場合は以下のようなコードになります。

import torch.optim as optim

# 最適化アルゴリズムの作成
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # 勾配のリセット
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # 重みの更新

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください