PyTorch チュートリアルにトライ 2 (初めてのニューラルネットワーク)
PyTorchへの入門として、公式のチュートリアルをなぞってみました。
目次
ニューラルネットワークを構成する
torch.nnパッケージでニューラルネットワークを構成します。
一般的な学習手順は下記のようになります。
学習用のパラメータ(重み)を持ったニューラルネットワークの定義
入力データセットの反復
ネットワークへ入力
ロス(アウトプットと正解の差)の計算
勾配をネットワークへ反映
ネットワークのパラメータ(重み)の更新
ネットワークの定義
本家のチュートリアル では、いきなりコードが出てきます。
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>)
ロスが少し小さくなります。
この計算を繰り返していくわけです。
公開日
広告