目次
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. ニューラルネットワークの定義
実際に、ニューラルネットワークモデルを作って見ましょう。
以下のコードを実行してみます。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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します。
1 2 3 | 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という計算になっています。
01 02 03 04 05 06 07 08 09 10 | 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の順にデータが流れていきます。
01 02 03 04 05 06 07 08 09 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 |
作ったモデルを確認します。
1 2 | 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
に格納します。
1 2 3 | 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
の勾配を計算。
1 2 | net.zero_grad() out.backward(torch.randn( 1 , 10 )) |
4. Loss Function (損失関数)
損失関数は、目的となる答えとニューラルネットワークが出力した結果がどれだけ離れているかを計算します。
nn
パッケージには、様々な損失関数があります。
中でも最もシンプルなのがnn.MSELoss
です。
この損失関数では、実際の答えとニューラルネットワークの出力との平均二乗誤差(mean-squared error)を計算します。
1 2 3 4 5 6 7 | 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
属性を使います。これによって、このような計算グラフをみることができます。
1 2 3 4 | input - > conv2d - > relu - > maxpool2d - > conv2d - > relu - > maxpool2d - > view - > linear - > relu - > linear - > relu - > linear - > MSELoss - > loss |
ここで、loss.backward()
を呼ぶと全体の計算グラフを微分することができます。さらに、requires_grad=True
となっているテンソルは、.grad
属性を持ち、勾配が蓄積されていきます。
どのようにloss
を追っていくかは、以下のコードで確認します。
1 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 0x7f60ae3a2f60
AddmmBackward object at 0x7f60ae3a2eb8
AccumulateGrad object at 0x7f60ae3a2f28
5. Backprop (誤差逆伝播)
誤差逆伝播法は、loss.backward()
で実行することができます。
誤差逆伝播をする前に、net.zero_grad()
で勾配を一度リセットする必要があります。
conv1
における逆伝播前後の勾配をloss.backward()
を使って、見てみます。
1 2 3 4 5 6 7 8 9 | 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)は、重みを更新する上でよく使われる最適化アルゴリズムで、以下の式で表されます。
1 | weight = weight - learning_rate * gradient |
SGDは、Pythonコードで以下のようにして実装することができます。
この例では、学習率を0.01として、ニューラルネットワークにあるすべての重み(net.parameters())を更新しています。
1 2 3 | 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を使う場合は以下のようなコードになります。
01 02 03 04 05 06 07 08 09 10 11 | 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() # 重みの更新 |