【PyTorchチュートリアル③】NEURAL NETWORKS

#はじめに
[前回](https://qiita.com/sudominoru/items/bfe5a3f6499d0ecf8143)に引き続き、PyTorch [公式チュートリアル](https://pytorch.org/tutorials/) の第3弾です。
今回は [NEURAL NETWORKS](https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html) を進めます。

#Neural Networks（ニューラルネットワーク）

PyTorch では、ニューラルネットワークを torch.nn パッケージを使用して構築します。
モデルの構築は、nn.Module を継承して定義します。
nn.Moduleには、レイヤーと、出力を返す forward メソッドを実装します。
![mnist.png](https://pytorch.org/tutorials/_images/mnist.png)

上の絵（フロー）は、画像の数字を判定するモデルです。
入力（INPUT 32 × 32）を受け取り、複数のレイヤーを通過して、結果（ 0から9 ）を出力します。

各レイヤーで次元は以下のように変化します。
INPUT（32×32）⇒ C1（6×28×28）⇒ S2（6×14×14）⇒ C3（16×10×10）⇒ S4（16×5×5）⇒ F5（120）⇒ F6（84）⇒ OUTPUT（10）

ニューラルネットワークの学習手順は以下です。

- ニューラルネットワークを定義する
- データセットを入力する
- (ネットワークを介した入力の処理)
- 損失を計算する
- 勾配をネットワークのパラメーターに反映する（誤差逆伝播）
- ネットワークの重みを更新する

それぞれ見ていきます。

## ネットワークを定義する

上の絵のネットワークを定義すると以下のコードになります。

```Python
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, 5x5 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        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)
```

```Python
Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, 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)
)
```

init で各レイヤーを定義します。
torch.nn.Module は、入力層から始まり係数や重みを計算し次の層へ渡すという計算を繰り返します。
これを順伝播 (forward propagation)と呼びます。
torch.nn.Module は「順伝播型ニューラルネットワーク」です。
この順伝播の計算を forward メソッドに記述します。
上記のコードは、絵の内容をコードで表しています。
そして、順伝播の計算結果は、Module.parameters() メソッドで取得できます。

```Python
params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight
```

```Python
10
torch.Size([6, 1, 5, 5])
```

## データセットを入力する

[前回](https://qiita.com/sudominoru/items/bfe5a3f6499d0ecf8143)の「[Autograd: 自動微分](https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autograd-tutorial-py)」で見たように、ニューラルネットワークも勾配( gradient )を計算することができます。
以下のように定義したニューラルネットワークに適したインプット（32x32）を入力することで勾配が計算されます。
（サンプルコードとは少し異なりますが、勾配を計算できるよう「requires_grad=True」を追加しています。）

```Python
input = torch.randn(1, 1, 32, 32, requires_grad=True)
out = net(input)
print(out)
```

```Python
tensor([[-0.0769,  0.1520, -0.0556,  0.0384, -0.0847,  0.1081, -0.0190, -0.0522,
         -0.0523, -0.0007]], grad_fn=<AddmmBackward>)
```

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

以下で、バックプロパゲーション（Back-propagation：誤差逆伝播）の結果を出力できます。

```Python
print(input.grad)
```

## 損失を計算する

損失関数( Loss Function )は、ネットワークが出力した値と教師データを比較し、学習結果がどの程度教師データと離れているかを評価します。
nnパッケージにはいくつかの[損失関数( Loss Function )](https://pytorch.org/docs/stable/nn.html#loss-functions)があります。
基本的な損失関数に、nn.MSELoss があります。MSELoss は平均二乗誤差(Mean squared error)と呼ばれ、学習結果と教師データの差を二乗し、平均を求めます。

```Python
output = net(input)
target = torch.randn(10)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

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

output にはニューラルネットワークの学習結果が保持されています。
target には教師データを設定します。（今回はランダム値です）
損失関数に MSELoss を設定します。
loss には 学習結果と教師データの誤差が計算されます。

```Python
tensor(1.1126, grad_fn=<MseLossBackward>)
```

## 誤差逆伝播

ニューラルネットワークの学習の目的は、誤差を最小化することです。
最小化するためのアルゴリズム（最適化アルゴリズム）はいろいろありますが、共通することは、勾配を計算することが必要な点です。
勾配は誤差逆伝播法によって計算できます。
Pytorchでは、loss.backward() を実行するだけで勾配を計算できます。
以下のコードで、loss.backward() を呼び出す前後の conv1 の勾配を確認できます。

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

```Python
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0033,  0.0033,  0.0027,  0.0030,  0.0031, -0.0053])
```

## ネットワークの重みを更新する

先ほども触れましたが、学習は誤差（損失関数）を最小化するようにパラメータを調整します。
そのためのアルゴリズム（最適化アルゴリズム）で最も基本的なものは「確率的勾配降下法」(stochastic gradient descent, SGD)です。
このチュートリアルでは、確率的勾配降下法(SGD)の細かい内容には触れられていませんが、簡単なコードで実装できます。

```Python
import torch.optim as optim

# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)

# in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update
```


確率的勾配降下法( SGD )以外にも、Nesterov-SGD、Adam、RMSProp などのさまざまな最適化アルゴリズムがあります。
[torch.optim](https://pytorch.org/docs/stable/optim.html) パッケージにいろいろな最適化アルゴリズム（オプティマイザ）が用意されています。


#最後に

以上が、PyTorch の3つ目のチュートリアル「Neural Networks」の内容です。
基本的なニューラルネットワークが少し理解できた気がします。

[次回](https://qiita.com/sudominoru/items/61f57946799e67cedd47)は4つ目のチュートリアル「TRAINING A CLASSIFIER」を進めてみたいと思います。

#履歴
2020/04/22 初版公開
2020/04/29 次回のリンク追加