【詳細（？）】pytorch入門　〜CIFAR10をCNNする〜

# 1. はじめに
pytorch初心者によるpytorch入門です．

こういう新しいフレームワークを使う時はexampleを見て，そこで使われている関数などをひたすらググりまくる or ドキュメントを読む or いじるのが一番の近道と考えているので，そのメモです．

似たようなやり方でpytorch入門しようとしている人にとってはこの記事で時間の節約になると思います．（なってくれると嬉しい．）


というわけで，CIFAR10でCNNをやる[cifar10-tutorial](http://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#sphx-glr-beginner-blitz-cifar10-tutorial-py)のコードの解読というかググり作業を行います．


# 2. 環境の準備
## 2.1. 環境
- Python 3.6.1 :: Anaconda 4.4.0 (64-bit)

## 2.2. インストール
pytorchのインストールは超絶簡単で，公式サイトで自分の環境をポチポチしていくと，インストール用のコードを表示してくれます．僕の環境の場合は以下のような感じでした．

http://pytorch.org/
![Screen Shot 2017-09-06 at 22.59.46.png](https://qiita-image-store.s3.amazonaws.com/0/164264/533f14d7-895b-3d10-a17d-4e7bd7d67cc6.png "Screen Shot 2017-09-06 at 22.59.46.png")

```shell
pip install http://download.pytorch.org/whl/cu80/torch-0.2.0.post3-cp36-cp36m-manylinux1_x86_64.whl 
pip install torchvision
```

# 3. データのロード，下処理
まずはデータのロードと下処理を行なっている[このコード](http://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#loading-and-normalizing-cifar10)を見ていきます．

```python
import torch
import torchvision
import torchvision.transforms as transforms

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
```

## 3.1. torchvisionとは
[torchvision](http://pytorch.org/docs/master/torchvision/torchvision.html)とは，pytorchのコンピュータービジョン用のパッケージで，データのロードや下処理用の関数やらが入っているらしいです．

## 3.2. データの下処理

`transforms.Compose`で，データをロードした後に行う下処理の関数を構成します．

`ToTensor()`は名前の通りデータをpytorchの定義する`torch.Tensor`というテンソルに型を変更します．

`transforms.Normalize`の引数が`torch.Tensor`であることから，リストの初めから順に関数を実行してくのでしょう．

[```transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)```](http://pytorch.org/docs/master/torchvision/transforms.html#torchvision.transforms.Normalize)は，引数の一つ目のタプルが　RGBの各チャンネルの平均を表し，二つ目のタプルが標準偏差を表します．これらの平均と標準偏差にあわせて正規化します．

つまり

```python
transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
```

これで，データをpytorch用のテンソル型に変えて，正規化を行う関数を準備したことになります．

## 3.3. torchvision.datasets.CIFAR10とは
[ ```torchvision.datasets.CIFAR10``` ](http://pytorch.org/docs/master/torchvision/datasets.html#cifar)は名前の通りCIFAR10のデータをロードするためのクラスです．

`download=True`だったら，rootの位置にデータを保存します．

MNISTやCIFAR10などは，データセットがすでに訓練用とテスト用に分かれています．（混ぜて自分で分割してもいいと思いますが）

CIFAR10では，60000のイメージがあり，そのうち50000が訓練用で，10000がテスト用とされています．`train=True`にすると，あらかじめ訓練用とされている50000の方をロードし，`train=False`で10000のテスト用のデータをロードします．

引数`transform`で先ほど`transforms.Compose`で作った下処理の一連の流れを渡すと，ロードした後に渡した下処理を実行してくれます．

## 3.4. DataLoaderとは

[DataLoader](http://pytorch.org/docs/master/data.html#torch.utils.data.DataLoader)は，ロードしたデータセットに[sampler](http://pytorch.org/docs/master/_modules/torch/utils/data/sampler.html)クラスというデータをサンプルする用のオブジェクトをくっつけたものです．
確認すると確かにsamplerがくっついています．

```
trainloader.sampler
# <torch.utils.data.sampler.RandomSampler at 0x7f9099e13ef0>
```

[sampler](http://pytorch.org/docs/master/_modules/torch/utils/data/sampler.html)には見たところ，ランダムサンプリング，順番にサンプリング，重みをつけてサンプリングなどがありました．

[DataLoader](http://pytorch.org/docs/master/data.html#torch.utils.data.DataLoader)の引数にsamplerというのがあるので，ここで自分で定義したsamplerを渡してあげれば良い予感がするので試してみましょう．

ここでは，結果がわかりやすいように，ある１つのデータのみをサンプリングするように重み付けして，サンプリングしてみます．
データ数が少なくて楽なのでテストデータで試します．

```python
import numpy as np

# 一つの画像だけ1の重みベクトルを作る．
weights = np.zeros(10000) # テストデータの数は10000
weights[300] = 1. # 300は適当
num_samples = 4 # サンプリングする回数

# WeightedRandomSamplerを試す．
my_sampler = torch.utils.data.sampler.WeightedRandomSampler(weights, num_samples, replacement=True)
my_testloader = torch.utils.data.DataLoader(testset, batch_size=4,shuffle=False, num_workers=2, sampler=my_sampler)

my_testiter = iter(my_testloader)
images, labels = my_testiter.next()

# imshow関数は次に説明するけどちょっと先走って使う．
def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))

imshow(torchvision.utils.make_grid(images))
```

![fig2.png](https://qiita-image-store.s3.amazonaws.com/0/164264/9f34fc03-45a1-3777-0b83-027e4cb7020a.png "fig2.png")

おぉ，カエルばっかだ．

## 3.5. 画像のプロット

```python
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline

# functions to show an image
def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))

# get some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()

# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
```

![fig1.png](https://qiita-image-store.s3.amazonaws.com/0/164264/e6b44695-fa1d-08a1-ced3-dfbdad493807.png "fig1.png")


## 3.6. unnormalizeとは
```img = img / 2 + 0.5```の部分にコメントでunnormalizeとありますが，これをunnormalizeというのは若干ミスリードな気がします．normalizeされているといえばされていますし．

[plt.imshow](https://matplotlib.org/devdocs/api/_as_gen/matplotlib.pyplot.imshow.html)の入力が[0,1]なので，それに合わせてずらしています．

## 3.7. img.numpy()とは

```python
type(img) # torch.FloatTensor
```

[torch.Tensor](http://pytorch.org/docs/master/tensors.html#torch.Tensor])は，pytorchで扱うテンソルの型で，「要素のデータ型＋Tensor」という名前になっています．この場合FloatTensorなので，float，つまり32bitの浮動小数点です．

[```ドキュメント```](http://pytorch.org/docs/master/tensors.html#torch.Tensor.numpy)に返されるndarrayと元のテンソルは同じ領域をシェアしているからどっちか変更するともう片方も変更されるとあります．

チェックしてみましょう．

```python
a = torch.FloatTensor([1])
b = a.numpy()

# ndarrayの方を変更する
b[0] = 2

# 元のテンソルも2になる．
print("original tensor: ", a) # original tensor: 2
print("ndarray : ", b) # ndarray :  [ 2.]

# 別のメモリ領域を参照している．
print(id(a)) # 140024484781832
print(id(b)) # 140024044621056
```

おぉ，ndarrayの方の変更が元のテンソルにも反映されていますね．
確保しているメモリ領域は異なるので，値を同じに保つようにして仮想的に同じ領域をシェアしているように動作させているのでしょう．

## 3.8. plt.imshow(np.transpose(npimg, (1, 2, 0)))とは

```python
npimg = img.numpy()
npimg2 = np.transpose(npimg, (1, 2, 0))

print(npimg.shape) # (3, 36, 138)
print(npimg2.shape) # (36, 138, 3)
```

ドキュメントを見ると，[`plt.imshow`](https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.imshow)の引数は，(n, m, RGB)というように並んでいなくてはなりません．
npimgはもともと(RGB, 縦，横)と並んでいるので，```np.transpose```の第二引数の順に並び替えています．

## 3.9. dataiter = iter(trainloader)とは

```python
dataiter = iter(trainloader)

print(type(trainloader))
# <class 'torch.utils.data.dataloader.DataLoader'>

print(type(dataiter))
# <class 'torch.utils.data.dataloader.DataLoaderIter'>
```

iter()でDataLoaderで定義された`__iter__`が呼ばれ，`DataLoaderIter`を返すようになっています．

通常のイテレーターと異なり，batch_sizeずつデータを渡さないといけないので，専用のイテレーターを定義してくれているようです．（[コード](http://pytorch.org/docs/master/_modules/torch/utils/data/dataloader.html)）

これによって，`dataiter.next()`を呼ぶごとにnバッチ目，n+1バッチ目と繰り返しデータを取得してくれます．

## 3.10. make_gridとは

```python
img = torchvision.utils.make_grid(images)
print(type(images)) # <class 'torch.FloatTensor'>
print(images.size) # torch.Size([4, 3, 32, 32])
print(type(img)) # <class 'torch.FloatTensor'>
print(img.size) # torch.Size([3, 36, 138])
```

ドキュメントは[torchvision.utils.make_grid](http://pytorch.org/docs/master/torchvision/utils.html#torchvision.utils.make_grid)です．

`make_grid`関数では，複数の画像を横に並べてくれます．
`make_grid`の引数は４次元のテンソルなのに対し，返り値は３次元のテンソルになっています．引数のテンソルが[画像の枚数，RGB, 縦，横]の４次元テンソルだったのが，画像の枚数の次元がなくなっています．

そして，ドキュメントにある通り，デフォルトでは```padding=2```なので上と下に2ずつ追加されて32が36に，各画像の間と両端に2ずつ追加されて，32*n+2*(n+1)=138　(n=4)つまり横に関しては32が138になっています．

# 4. モデルの定義
[このコード](http://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#define-a-convolution-neural-network)を見ていきます．

```python
from torch.autograd import Variable
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(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


net = Net()
```

## 4.1. Variableとは
[`Variable`](http://pytorch.org/docs/master/autograd.html#torch.autograd.Variable)は，torch.Tensorをラップして，勾配のデータなどを保持できるようにするものです．

Variableは，テンソルを包んでいるので，Variableに入っているデータをみたい時にはワンステップ必要です．

```python
a = torch.FloatTensor([1.])
a2 = Variable(a)

print(type(a)) # <class 'torch.FloatTensor'>
print(type(a2)) # <class 'torch.autograd.variable.Variable'>
print(type(a2.data)) # <class 'torch.FloatTensor'>

print(a.numpy()) # [ 1.]
print(a2.data.numpy()) # [ 1.]
```

上のように，`.data`にテンソルが格納されています．[（参考）](https://discuss.pytorch.org/t/how-to-transform-variable-into-numpy/104/2)

ちなみに，勾配の情報は

```
print(a2.grad) # None
```

ここに保存されています．
今は何もないのでNoneが入っています．`None`か`Variable`のみ受け付けるようです．

```
a2.grad = Variable(torch.FloatTensor([100]))
print(a2.grad) # Variable containing: 100
```

裏側でこんな感じで代入していい感じにやっているのでしょう．
そしてVariableしか受け付けなくなっているということは，そこにも勾配の情報を保存する予定というわけで，

```
a2.grad.grad =  Variable(torch.FloatTensor([200]))
print(a2.grad.grad) # Variable containing: 200
```

高階導関数はこのようにしていじくるのでしょうか？面白いですね．

## 4.2. nn.Moduleとは

```python
class Net(nn.Module):
```

[nn.Module](http://pytorch.org/docs/master/nn.html#torch.nn.Module)クラスはbaseクラスで，これにforwardなど定義されており，何かモデルを作るときはこれを継承します．
（実際にはforwardに具体的な何かが定義されているわけではなく，継承した後に代入されることを想定しているようです．）

## 4.3. nn.Conv2dとは

[nn.Conv2d](http://pytorch.org/docs/master/nn.html#torch.nn.Conv2d)の引数は，
左から，インプットのチャンネルの数，アウトプットのチャンネルの数，カーネルサイズです．

このモデル定義の時点で畳み込み層のパラメータであるフィルターに0付近のランダムな値を入れて準備してくれています．
それは以下のように確認できます．

```python
conv1 = nn.Conv2d(3, 6, 5)
print(conv1.weight)

Parameter containing:
(0 ,0 ,.,.) = 
 -0.0011 -0.1120  0.0351 -0.0488  0.0323
 -0.0529 -0.0126  0.1139 -0.0234 -0.0729
  0.0384 -0.0263 -0.0903  0.1065  0.0702
  0.0087 -0.0492  0.0519  0.0254 -0.0941
  0.0351 -0.0556 -0.0279 -0.0641 -0.0790

(0 ,1 ,.,.) = 
 -0.0738  0.0853  0.0817 -0.1121  0.0463
 -0.0266  0.0360  0.0215 -0.0997 -0.0559
  0.0441 -0.0151  0.0309 -0.0026  0.0167
 -0.0534  0.0699 -0.0295 -0.1043 -0.0614
 -0.0820 -0.0549 -0.0654 -0.1144  0.0049
 ...
 [torch.FloatTensor of size 6x3x5x5]
```

カーネルサイズを5にしたので，(アウトプットのチャンネル数，RGB，カーネルサイズ，カーネルサイズ)のテンソルが準備されています．ちなみに，

```python
conv1 = nn.Conv2d(3, 6, (5,1))
print(conv1.weight)

Parameter containing:
(0 ,0 ,.,.) = 
  0.2339
 -0.0756
  0.0604
 -0.0185
 -0.0975
 ...
```

というように正方形以外もできるようです．

また，このパラメーターは

```python
type(conv1.weight)
# torch.nn.parameter.Parameter
```

このようにParameterオブジェクトで定義しており，これは[`Variable`](http://pytorch.org/docs/master/autograd.html#torch.autograd.Variable)のサブクラスで，パッと見た感じ，表示用の関数が定義されていて，VariableだとVariable containingと出ますが，ParameterではParameter containing:と出るぐらいしか違いがないのではないでしょうか．

## 4.4. nn.MaxPool2dとは

[nn.MaxPool2d](http://pytorch.org/docs/master/nn.html#torch.nn.MaxPool2d)がMAXプーリング層です．主に使う引数はkernel_size, stride, paddingぐらいでしょうか．

というわけで，適応な画像に畳み込みとMAXプーリングをしてどのように変換されるか確認して見ましょう．

```python
images, labels = dataiter.next()
print(images.size())
print(type(images))
image_plot = images[0][1].numpy()
plt.imshow(image_plot, cmap='Greys', interpolation='nearest')
plt.show()

# モデルの定義
img_input = Variable(images)
conv = nn.Conv2d(3, 1, 3, padding=1)
pool = nn.MaxPool2d(3, padding=1, stride=1)

# フォワード
conv_output = conv(img_input)
pool_output = pool(conv_output)
print(pool_output.size())

# プロット
conv_plot = conv_output[0][0].data.numpy()
conv_plot 
plt.imshow(conv_plot, cmap='Greys', interpolation='nearest')
plt.show()

pool_plot = pool_output[0][0].data.numpy()
plt.imshow(pool_plot, cmap='Greys', interpolation='nearest')
plt.show()
```

- オリジナルの画像
![fig3.png](https://qiita-image-store.s3.amazonaws.com/0/164264/647b7713-8ae2-3eef-46a3-0b272cd153e4.png "fig3.png")
馬ですね．

- 畳み込み後の画像
![fig4.png](https://qiita-image-store.s3.amazonaws.com/0/164264/2e9ad624-774b-1493-cf08-f52fcbb5dd0d.png "fig4.png")
ここで，畳み込みはフィルターのパラメーターをまだ学習していないので，ランダムな０付近のパラメータになっていて，どれも同じぐらいの値なのであまり違いはないようです．

- 畳み込み&MAXプーリング後の画像
![fig5.png](https://qiita-image-store.s3.amazonaws.com/0/164264/8930541f-8388-5fcd-ec37-1fba118a46e2.png "fig5.png")
MAXプーリングは3*3のMAXをとってぼやける感じになっているのがわかりますね．

# 4.5. 目的関数の定義，最適化手法の定義
次に[このコード](http://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#define-a-loss-function-and-optimizer)を見ていきます．

```python
import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
```

## 4.6. nn.CrossEntropyLoss()とは
[nn.CrossEntropyLoss()](http://pytorch.org/docs/master/nn.html#torch.nn.CrossEntropyLoss)では，目的関数となるオブジェクトを定義しています．

## 4.7.optim.SGDとは
[```torch.optim```](http://pytorch.org/docs/master/optim.html)は最適化の色々なアルゴリズムを定義しています．SGDやAdamなど．

`torch.optim`に定義されている最適化アルゴリズムの中で，ここでは[optim.SGD](http://pytorch.org/docs/master/optim.html#torch.optim.SGD)を使用します．

`optim.SGD`の引数として`net.parameters()`を渡しています．
`net.parameters()`は，モデルに定義されたパラメーター（`torch.nn.parameter.Parameter`）をジェネレーターにして返してくれるようです．

```python
type(net.parameters())
# generator

type(net.parameters().__next__())
# torch.nn.parameter.Parameter

print(net.parameters().__next__())

Parameter containing:
(0 ,0 ,.,.) = 
 -0.0998  0.0035 -0.0438 -0.1150 -0.0435
  0.0310 -0.0750 -0.0405 -0.0745 -0.1095
 -0.0355  0.0065 -0.0225  0.0729 -0.1114
  0.0708 -0.0170 -0.0253  0.1060  0.0557
  0.1057  0.0873  0.0793 -0.0309 -0.0861
  ...
```

optimizerが保持しているオブジェクトを確認すると，

```python
optimizer.__dict__

{'param_groups': [{'dampening': 0,
   'lr': 0.001,
   'momentum': 0.9,
   'nesterov': False,
   'params': [Parameter containing:
    (0 ,0 ,.,.) = 
      0.0380 -0.1152  0.0761  0.0964 -0.0555
     -0.0325 -0.0455 -0.0755  0.0413 -0.0589
      0.0116  0.1136 -0.0992 -0.1149 -0.0414
     -0.0611  0.0827 -0.0906  0.0631  0.0170
      0.0903 -0.0816 -0.0690  0.0470 -0.0578
  ...

```

`net.parameters()`で私たパラメーターも含めて，モデルに含まれるパラメーターを全部保持してくれています．


# 5. モデルの訓練
訓練するための[コード](http://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#train-the-network)を見ていきます．

```python
for epoch in range(2):  # loop over the dataset multiple times

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 第二引数は，スタート位置で，0なのでenumerate(trainloader)と同じ
        # https://docs.python.org/3/library/functions.html#enumerate
        
        # get the inputs
        inputs, labels = data

        # wrap them in Variable
        inputs, labels = Variable(inputs), Variable(labels)

        # zero the parameter gradients
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.data[0]
        if i % 2000 == 1999:    # print every 2000 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')
```

## 5.1. optimizer.zero_grad()とは
先ほど確認したように，```optimizer```はすべてのパラメーターを保持しています．

[`optimizer.zero_grad()`](http://pytorch.org/docs/master/optim.html#torch.optim.Optimizer.zero_grad)では，これらの保持しているVariableのgradを初期化してくれるらしいです．
多分全部Noneにしているんだと思います．

## 5.2. outputs = net(inputs)とは

```python
outputs = net(inputs)

print(type(outputs))
# <class 'torch.autograd.variable.Variable'>

print(outputs.size())
# torch.Size([4, 10])

outputs
Variable containing:
-2.4825 -4.4286  2.2041  3.4353  2.0734  2.8198  1.9374  0.7751 -2.6798 -3.1932
-1.7512 -4.6657  2.7911  3.9570  0.7931  5.9005 -0.8023  2.9664 -4.3328 -3.2921
 2.4015  2.8962  0.9330 -1.2107 -0.0525 -2.2119 -1.2474 -2.6026 -0.1120  0.4869
-1.3042 -2.7538  1.0985 -0.2462  3.7435  1.1724 -1.4233  6.6892 -3.8201 -2.3132
[torch.FloatTensor of size 4x10]
```

`net`に渡すことによって，目的関数まで通した最終的な出力が帰ってきていることがわかります．

## 5.3. loss = criterion(outputs, labels)とは
コメントでは，forward + backward + optimizeと書いてありますが，forwardのメソッドが見えません．

これは実は，CrossEntropyLossは__call__でforwardを呼ぶようになっており，つまり，

```python
loss = criterion(outputs, labels)
loss = criterion.forward(outputs, labels)
```

この二つは同じことをしています．
なので`loss = criterion(outputs, labels)`がforwardになっています．

## 5.4. loss.backward()とは

`loss`はVariableオブジェクトです．

```python
type(loss)
# torch.autograd.variable.Variable
```

[Variable.backward()](http://pytorch.org/docs/master/autograd.html#torch.autograd.Variable.backward)は，中でこの[torch.autograd.backward()](http://pytorch.org/docs/master/autograd.html#torch.autograd.backward)を呼びます．

それではこの`.backward()`が何をしているかというと，目的関数に対して，それに含まれるパラメーターの微分係数を求めています．
簡単な例で試してみましょう．

```python
x = torch.autograd.Variable(torch.Tensor([3,4]), requires_grad=True)
# requires_grad=Trueで，このVariableは微分するぞと伝える

print("x.grad : ", x.grad)
# None
# この時点ではまだ何も入っていない．

# 適当に目的関数を作る．
y = x[0]**2 + 5*x[1]  + x[0]*x[1]
# x[0]の導関数 : 2*x[0] + x[1]
# x[0]の微分係数 : 2*3 + 4 = 10
# x[1]の導関数 : 5 + x[0]
# x[1]の微分係数 : 5 + 3 = 8

y.backward()
# torch.autograd.backward(y)　でも良い．

print("x.grad : ", x.grad)
# 10
# 8

# .zero_grad()の代わり
x.grad = None
```

目的関数`y`の，入力データ点での微分係数になっていますね．
ここで注意すべきなのは，backwardは損失関数についてなので，backwardするyはスカラーでなくてはなりません．

例えば，

```python
y = x
y.backward()

# RuntimeError: grad can be implicitly created only for scalar outputs
```

とすると，スカラーにしろと怒られます．

## 5.5. optimizer.step()とは

[.step()](http://pytorch.org/docs/master/optim.html#torch.optim.SGD.step)は，`.backward()`で計算した勾配を元に，パラメーターを更新してくれます．
チェックして見ましょう．

```python
optimizer.zero_grad()
outputs = net(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()

print(net.parameters().__next__())
Parameter containing:
(0 ,0 ,.,.) = 
 -0.0839  0.1434 -0.0371 -0.1394 -0.0277
```

何回か実行すると（同じデータで最適化してますが）

```python
print(net.parameters().__next__())
Parameter containing:
(0 ,0 ,.,.) = 
 -0.0834  0.1436 -0.0371 -0.1389 -0.0276
```

というように，少しずつパラメーターが更新されています．

# 6. Validationする
テストデータに対してモデルの予測を行います．[このコード](http://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html#train-the-network)です．

```python
correct = 0
total = 0
for data in testloader:
    images, labels = data
    outputs = net(Variable(images))
    _, predicted = torch.max(outputs.data, 1)
    total += labels.size(0)
    correct += (predicted == labels).sum()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))
```

```python
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
    images, labels = data
    #print("images type : ", type(images))
    #print("images.shape : ", images.shape)
    outputs = net(Variable(images))
    _, predicted = torch.max(outputs.data, 1)
    c = (predicted == labels).squeeze()
    for i in range(4):
        label = labels[i]
        class_correct[label] += c[i]
        class_total[label] += 1


for i in range(10):
    print('Accuracy of %5s : %2d %%' % (
        classes[i], 100 * class_correct[i] / class_total[i]))

# Accuracy of plane : 51 %
# Accuracy of   car : 54 %
# Accuracy of  bird : 53 %
# Accuracy of   cat : 33 %
# Accuracy of  deer : 41 %
# Accuracy of   dog : 50 %
# Accuracy of  frog : 54 %
# Accuracy of horse : 65 %
# Accuracy of  ship : 70 %
# Accuracy of truck : 67 %
```


## 6.1. .squeeze()とは
見慣れないやつはこれくらいでしょうか．
[torch.squeeze](http://pytorch.org/docs/master/torch.html#torch.squeeze)
テンソルの次元の中で，1のものを消します．
squeezeは絞るという意味だそうです．

# 7. 終わりに
## 7.1. 便利な何か

~~`net.parameters`で大体どんなモデルかわかります．~~(2017/10/27修正)

nn.Moduleの`__repr__`を，モデルを見やすく表示してくれるように定義してくれているので，以下のようにして大体どんなモデルかわかります．

```
In [22]: net
Out[22]:
Net (
  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
  (pool): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear (400 -> 120)
  (fc2): Linear (120 -> 84)
  (fc3): Linear (84 -> 10)
)
```

## 7.2. 感想
- pytorchの抽象化の仕方はかなり参考になる．
- 使いやすそう．