MNIST


# MNIST

```Python:import文
import torch
# 基本モジュール
import torchvision
# 画像関連 
import torchvision.transforms as transforms 
import torchvision.datasets as datasets
# 画像用データセット諸々
import numpy as np

import torch.nn as nn 
# ネットワーク構築用
# torch.nn パッケージ全体は、単一のサンプルではなくサンプルのミニバッチの入力をサポートするだけ
import torch.nn.functional as F 
# ネットワーク用の様々な関数

import torch.optim as optim

import matplotlib.pyplot as plt
# グラフプロット用
```
## データのダウンロード
```Python
# データの成形
Transform = transforms.Compose(
    [
     transforms.ToTensor(),
     # tensor変換
     transforms.Normalize((0.5,), (0.5,))
     # 正規化
    ]
)
```

- `torchvision.transforms.Compose`
    - 引数で渡されたlistを先頭から順に実行していく
    - `[trasforms.ToTensor(),transforms.Normalize((0.5,), (0.5,))]`
    - Tensor化　→　正規化
    - このTransformフィルタによって、MNISTの各ピクセルの値の数値の範囲を`0〜1`から`-1〜1`の範囲の分布になるように変換する
- `transforms.ToTensor()`
    - Tensor変換
- `transforms.Normalize()`
    - 正規化
    - 引数は(訓練データの平均, 分散)
    - 入力画像を平均0, 分散1に標準化する

```Python:訓練用データセット定義
trainset = datasets.MNIST(
    root = '/Users/tefu/Git/MNIST',
    # 格納ディレクトリ
    download = True,
    # Webから取得する
    train = True,
    transform = Transform
)
```

- `trainset`
    - ニューラルネットワークの訓練用データ
- `root`
    - 格納ディレクトリをパス指定
- `download`
    - Webから取得する
    - 取得済みの場合、`download`は無視される
- `train = True`
    - 訓練データを指定
    - `False`のときテストデータ
    - `/Users/tefu/Git/MNIST/processed/training.pt`からデータを生成する
- `transform`
    - Transformフィルタにかける

```Python
trainloader = torch.utils.data.DataLoader(
    trainset,
    batch_size = 100,
    # バッチサイズを指定
    shuffle = True,
    num_workers = 2
    # 並行処理の数
)
```

- PyTorchでは、`Dataloader`という形でデータを取り扱う
- `Dataloader`には、バッチサイズごとにデータとラベルがまとめられている
- データは`torch.tensor`というテンソルの形で扱うが、`Dataloader`におけるデータの形は`（batch, channel, dimension）`という順番になっている

- `torch.utils.data`
    - データセット読み込み関連の関数
- `torch.utils.data.DataLoader()`
    -  第一引数にデータセットを取る
    -  先ほど定義した`trainset`を置く
-  `trainset`
    -  MNISTのデータを扱うためのクラスインスタンス
-  ミニバッチ法
    1. N個の訓練データからランダムにn個を取り出す
    2. n個の学習を行う
    3. 再度ランダムにn個取り出し、取り出したデータの総計がN個になるまで繰り返す
- `batch_size = n`
    - 一度に取り出すデータの数を指定 
    - `n`はデータセットの全データ数を割り切れる値
- `shuffle = True`
    - データの参照の仕方をランダムにする
- `num_workers`
    - 並行処理を行う数を指定
    - デフォルトはメイン一つのみ

```Python:testset
testset = datasets.MNIST(
    root = '/Users/tefu/Git/MNIST',
    download = True,
    train = False,
    transform = Transform
)

testloader = torch.utils.data.DataLoader(
    testset, 
    batch_size = 100,
    shuffle = False, 
    num_workers = 2
)
```

- `testset`
    - ニューラルネットワークの性能テスト用データ

## プロット用データ記録リスト
```Python
history = {
    'train_loss' : [],
    'test_loss' : [],
    'test_acc' : [],
}
```

## ニューラルネットワーク構築
```Python
class Net(nn.Module) :

    def __init__(self) :
    # ネットワーク層の定義
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 32, 3)
        self.conv2 = nn.Conv2d(32, 64, 3)
        # 畳み込み層
        
        self.pool = nn.MaxPool2d(2, 2)
        # プーリング層

        self.dropout1 = nn.Dropout2d()
        # ドロップアウト

        self.fc1 = nn.Linear(12 * 12 * 64, 128)
        # 全結合のネットワーク

        self.dropout2 = nn.Dropout2d()
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
    # 順伝播の計算
        x = F.relu(self.conv1(x))
        # self.conv1(x)のニューロンの出力をReLUによって変換
        
        x = self.pool(F.relu(self.conv2(x)))
        x = self.dropout1(x)
        x = x.view(-1, 12 * 12 * 64)
        # 1次元データに変えて全結合層へ

        x = F.relu(self.fc1(x))
        x = self.dropout2(x)
        x = self.fc2(x)
        return x

net = Net()
# Networkの使用を宣言
```

- `class Net(nn.Module)`
    - `torch.nn.Module`クラスを継承して、オリジナルのネットワークを構築

#### ネットワーク層
- `def __init__(self)`
    - ネットワーク層の定義
    - `__init__` → 初期化メソッド
- `nn.Conv2d()`
    - 畳み込み層
    - 引数は`(入力する画像の奥行き, 出力する画像の奥行き, フィルタのサイズ(タプル)（但し正方形の場合は整数に省略可能）)`
    - 画像には縦横以外にも奥行きがあり、この奥行きをチャンネルという
    - 画像の場合奥行きは色に対応しており、RGBだとチャンネル数は3、モノクロだと１となる
    - フィルタは必ず入力層と同じチャンネル数を持つものとして自動的に設定される
    - `conv2`の入力データは32チャンネル(conv1の出力結果を利用しているため`conv1の出力チャンネル＝conv2の入力チャンネル`となる)、出力は64チャンネルである　→　奥行き32の64個のフィルターが畳み込まれた
- `MaxPool2d()`
    - プーリング層
    - 特徴を強調する
    - 引数は`(pool_size, strides)`
    - `MaxPool2d(2, 2)`
        - `pool_size` ダウンスケールする係数を決める値。2つの整数のタプル`（垂直， 水平）`で表すが、`垂直＝水平`の時整数に省略可能
        - `pool_size(2, 2)`　→　画像をそれぞれの次元で半分にする　
        - `strides` フィルタを適用する位置の間隔 → `(2, 2)`
- `Dropout`
    - 学習時に一部のニューロンをわざと非活性化させることによって、過学習を防ぐ手法
- `nn.Linear()`
    - 全ユニット（ノード）が結合されている全結合のネットワーク
    - 入力データに線形変換を適用するクラス
    - 引数は（入力サイズ、出力サイズ）
    - `12 * 12 * 64` →　その前までの3次元のデータを１次元にしたもの

#### 順伝播
- `def forward(self, x)`
    - 順伝播の計算
- `F.relu(self.conv1(x))`
    - `ReLU` 入力が負のときは0、正のときはそのまま入力を出力するユニット
- `view`
    - １つ目の引数に-1を入れることで、２つ目の引数で指定した値にサイズ数を自動的に調整する関数
    - Tensorの要素数が指定したサイズ数に合わない（割り切れない）場合、エラーになる
    - サイズ数の指定も可能
- `net = Net()`
    - `Net()`クラスのインスタンスとなる`net`を生成
    - 定義した初期化関数`__init__()`が実行される
    - `net`がパラメータやNetworkの構造を保持している

## GPUの利用
```Python:CUDA
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

net.to(device)
# netをcuda上で動かす
```

## 損失関数の定義
```Python:
criterion = nn.CrossEntropyLoss()
# 交差エントロピー損失関数
optimizer = optim.SGD(net.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0025)
# パラメータの更新
```

- `nn.CrossEntropyLoss()`
- `optimizer`
    - 最適化のインスタンスを指定
- `SGD`
    - パラメータの更新手法
    - 引数は`(params, lr)`
        - `params`  更新したいパラメータ
        - 微分可能でなければならない
        - 今回は作成したNetworkのmodelのパラメータを入力
        - `lr`  学習率（learning rate）
        - `float型`を渡す
        - `momentum` 各パラメーターグループのサイクルにおける運動量
        - `weight_decay` 重み減衰
    - 確率的勾配降下法
        - 損失関数`criterion`を微分し、勾配を求める
        - この勾配にマイナスを欠けた方向に`net.parameters()`(重み)を移動させることで`criterion`を小さくする
        - 微分変数の移動を制御する値が`lr`


## 学習を行う
```Python
for epoch in range(100) :
# エポック数を指定
    running_loss = 0.0
    # 損失関数の累積値を0に初期化
    for i, (inputs, labels) in enumerate(trainloader, 0) :
        # ミニバッチ毎に学習をループさせる

        inputs = inputs.to(device)
        labels = labels.to(device)
        # 入力データをGPUへ送る

        optimizer.zero_grad()
        # パラメータの勾配を0に初期化

        outputs = net(inputs)
        loss = criterion(outputs, labels)
        # outputsとlabelsの損失関数を求める
        # 出力と訓練データの正解との誤差を求める
        loss.backward()
        # lossの勾配を計算
        optimizer.step()
        # 計算した勾配を用いて`net.parameters()`を移動
        # 重みの更新
        
        running_loss += loss.item()
        # 損失関数の累積を計算

        if i % 1000 == 999 :
        # 1000バッチごとに損失関数の平均値を表示
            print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 100))
            running_loss = 0.0
            # 損失関数の累積値を0に初期化
     
  history['train_loss'].append(loss.cpu().item())
print('Finished Training')
```

## 学習結果の確認
```Python
correct = 0
total = 0
test_loss = 0

with torch.no_grad():
    for (images, labels) in testloader:

        images = images.to(device)
        labels = labels.to(device)
        # GPUに送る

        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        # 確率が最大のラベルを取得
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
        # 正解ならば正解数をカウントする

        history['test_loss'].append(test_loss / total)
        history['test_acc'].append(float(correct / total))

print('Accuracy: %.2f %%' % (100 * float(correct/total)))
```

- `torch.no_grad()`
    - パラメータの保存をしない → メモリ溢れを防ぐ

```Python:Out
99.17%
```

## データ数の確認
```Python
print(len(history['train_loss']))
print(len(history['test_loss']))
print(len(history['test_acc']))
```

## グラフにプロット
```Python
# 結果の出力と描画
plt.figure()
plt.plot(range(1, 101), history['train_loss'], label='train_loss')
# x, yのsizeを揃える
plt.plot(range(1, 501), history['test_loss'], label='test_loss')
plt.xlabel('epoch')
plt.legend()
plt.savefig('loss.png')

plt.figure()
plt.plot(range(1, 501), history['test_acc'])
plt.title('test accuracy')
plt.xlabel('epoch')
plt.savefig('test_acc.png')
```
![loss.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459214/bd3e8b33-814f-3d9a-0794-25e99b65df36.png)

![test_acc (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459214/6f09e070-fb6c-6c76-4b25-9dc7fbfdbdf2.png)

# 参考文献
- [PyTorchでMNIST](https://qiita.com/fukuit/items/215ef75113d97560e599)
- [【PyTorch】MNISTのサンプルを動かしてみた](http://kaga100man.com/2019/01/05/post-84/)
- [CNN in MNIST with PyTorch (PyTorchの基本メモ)](http://nobunaga.hatenablog.jp/entry/2018/09/23/093112)
- [【PyTorch入門】PyTorchで手書き数字(MNIST)を学習させる](https://rightcode.co.jp/blog/information-technology/pytorch-mnist-learning)
- [PyTorchでMNISTする](https://www.sambaiz.net/article/205/)
- [PyTorchによるディープラーニング実装入門（２）](https://book.mynavi.jp/manatee/detail/id=89558)
- [pyTorchのtransforms,Datasets,Dataloaderの説明と自作Datasetの作成と使用](https://qiita.com/mathlive/items/2a512831878b8018db02)
- [Pytorchのニューラルネットワーク(CNN)のチュートリアル1.3.1の解説](https://qiita.com/poorko/items/c151ff4a827f114fe954)