• ホーム
  • PyTorch
  • PyTorch チュートリアルにトライ 2 (初めてのニューラルネットワーク)
アイキャッチ画像

PyTorch チュートリアルにトライ 2 (初めてのニューラルネットワーク)

PyTorchへの入門として、公式のチュートリアルをなぞってみました。

目次

  1. ニューラルネットワークを構成する
  2. ネットワークの定義
  3. 損失関数
  4. バックプロパゲーション
  5. ネットワークパラメータ(重み)の更新

ニューラルネットワークを構成する

torch.nnパッケージでニューラルネットワークを構成します。

一般的な学習手順は下記のようになります。

  1. 学習用のパラメータ(重み)を持ったニューラルネットワークの定義

  2. 入力データセットの反復

  3. ネットワークへ入力

  4. ロス(アウトプットと正解の差)の計算

  5. 勾配をネットワークへ反映

  6. ネットワークのパラメータ(重み)の更新

ネットワークの定義

本家のチュートリアル では、いきなりコードが出てきます。

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
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # アフィン変換: 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):
        # 2x2 マックスプーリング
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 2 マックスプーリング
        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) # ネットワークを表示

input = torch.randn(1, 1, 32, 32)
out = net(input) # 順方向の計算
print(out) # 計算結果の出力

いきなりですが、どういう意味なのか読んでみます。

Netというクラスでネットワークの定義をします。このクラスは、nn.Module を継承します。

このクラスのコンストラクター(init関数のところ)で、各レイヤの定義をします。nn.Conv2Dクラスが畳み込み層、nn.Linearが全結合層でしょう。

forward関数のところで、各層の接続の定義をします。

入力を1層目の畳み込み層に入れて、ReLUを通して、マックスプーリングする。 その結果を2層目の畳み込み層に入れて、ReLUを通して、マックスプーリングする。 その結果をバッチ平均かなにかをする。 その結果を1つ目の全結合層に入れる。 その結果を2つ目の全結合層に入れる。 その結果を3つ目の全結合層に入れて、結果を出力する。

このようなネットワークになっていると思います。

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

input = torch.randn(1, 1, 32, 32) は32x32の入力用のテンソルの作成です。実際に作成されるテンソルはこんな感じです。

tensor([[[[-0.5220, -1.6928, -0.9406,  ...,  2.1751,  0.2899,  0.2584],
          [ 0.0203,  0.5668, -0.7520,  ...,  1.3077,  2.4590, -1.3431],
          [ 0.6680, -0.7802,  0.5965,  ..., -0.6531,  0.3242,  2.3200],
          ...,
          [-0.7891, -0.5755,  0.9293,  ...,  0.9320, -1.5337, -0.6887],
          [-1.4799,  0.3989,  0.7141,  ..., -0.2446,  0.8885,  0.5325],
          [ 0.1494, -0.0864, -1.7887,  ...,  0.6360, -1.1674, -0.2106]]]])

このテンソルをネットワークに入力してえられた結果がoutです。

tensor([[ 0.0168,  0.0290, -0.0848, -0.0415, -0.0657,  0.1007, -0.0072, -0.0478,
          0.0835,  0.0479]], grad_fn=<AddmmBackward>)

損失関数

損失関数に、順方向の計算結果と正解を入れるとロスの計算が行われます。

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

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        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):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        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()

# ここまで、先のコードと同じ

input = torch.randn(1, 1, 32, 32)
output = net(input)

target = torch.randn(10)  # 仮の正解
target = target.view(1, -1)  # 次元合わせ
criterion = nn.MSELoss() # 損失関数の定義

loss = criterion(output, target) # ロスの計算
print(target)
print(output)
print(loss)

このとき、正解と出力と損失は、それぞれ下記のようになります。

tensor([[-0.7186, -0.5552,  2.2533, -0.5693,  2.2440, -0.7123, -0.2076,  0.5381,
          1.1651, -0.2314]])
tensor([[-0.0279, -0.0775,  0.0070, -0.0736, -0.1249,  0.1223, -0.0707,  0.1014,
         -0.1306, -0.0382]], grad_fn=<AddmmBackward>)
tensor(1.4231, grad_fn=<MseLossBackward>)

バックプロパゲーション

入力から出力を経て損失まで、tensorで計算してきました。ということは、tensorのbackwardの機能を使って、lossからinputまでの勾配を計算することができます。

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

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        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):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        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()
input = torch.randn(1, 1, 32, 32)
output = net(input)
target = torch.randn(10)  # 仮の正解
target = target.view(1, -1)  # 次元合わせ
criterion = nn.MSELoss() # 損失関数の定義
loss = criterion(output, target) # ロスの計算

# ここまで、先のコードと同じ

net.zero_grad()     # パラメータをゼロにセットする

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

loss.backward()

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

net.zero_grad()でパラメータを0にして、loss.backward()で勾配を計算する前後の1層目の畳み込み層のパラメータを表示します。

conv1.bias.grad before backward
None
conv1.bias.grad after backward
tensor([-0.0071,  0.0053, -0.0020, -0.0003,  0.0058,  0.0050])

backward前の結果がチュートリアルと少し違っていますが、おそらくチュートリアルの方はこのタイミングでは計算が2回目になっているからだと思います。

ネットワークパラメータ(重み)の更新

ネットワークのパラメータは、下記のような数式を使って更新します。

weight = weight - learning_rate * gradient

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__()
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        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):
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        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()
input = torch.randn(1, 1, 32, 32)
target = torch.randn(10)  # 仮の正解
target = target.view(1, -1)  # 次元合わせ
criterion = nn.MSELoss() # 損失関数の定義

# ここまで、先のコードと同じ

import torch.optim as optim

optimizer = optim.SGD(net.parameters(), lr=0.01)
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

print(loss)
print(criterion(net(input), target))

パラメータの更新にはオプティマイザーを使用します。

上の例は、学習率を0.01にしてSGDオプティマイザーを設定し、1回パラメータを更新するというものです。

最後に、パラメータ更新前後のロスを計算しています。

tensor(1.1795, grad_fn=<MseLossBackward>)
tensor(1.1589, grad_fn=<MseLossBackward>)

ロスが少し小さくなります。

この計算を繰り返していくわけです。

公開日

広告