目次
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() # 重みの更新