PyTorchでニューラルネットワークを使ってMNISTデータを学習させる

# はじめに

こんにちは、川島です！
ずっと、Qiitaで投稿しようと思っていて、やっと実行できるようになりました！（＾＾）

今日はPyTorchでニューラルネットワークを使ってMNIST手書き数字のデータを学習させるプログラムの解説をしていきたいと思います。

このトピック自体は、目新しいわけではありませんが
ただ、今までの記事の中で、なかなか、細部まで丁寧に説明する物が少なくて

この記事では、なるべくうるさいぐらいに詳細に説明をしていきたいと考えて、書いてみたいと思います。

とは言っても、ほとんどの説明が、コメントに書いてあります。辛抱強く、コメントを一行ずつ、読みましょう！

ソースコードをみながら始めよう！

# 必要なモジュールなどをimportする 

```
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------

import torch

print(torch.__version__)

import torchvision.transforms as transforms
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torch.autograd import Variable
import torch.nn as nn

import torch.optim as optimizer
```

# 学習の回数、バッチサイズなどの設定

勉強のために、ここを色々変えながら、プログラムを実行してみてください。

```
# -----------------------------------------------------------------------------
# ミニバッチのバッチサイズ
BATCH_SIZE = 4
# 最大学習回数
MAX_EPOCH = 2
# 進捗出力するバッチ数
PROGRESS_SHOW_PER_BATCH_COUNT=1000
```

# MLP(マルチレイヤーパーセプトロン)クラスの定義

ここでは、3層構造のニューラルネットワークを定義します。注意すべきは、これは畳み込みニューラルネットワークではないことです。

```
# -----------------------------------------------------------------------------
# 　マルチレイヤーパーセプトロンクラスの定義
class MLP(nn.Module):
    def __init__(self):
        '''
        層ごとに定義する、例えば、活性化関数などは、次forward()で定義する
        '''
        super().__init__()
        # 入力層
        self.layer1 = nn.Linear(28 * 28, 100)
        # 中間層（隠れ層）
        self.layer2 = nn.Linear(100, 50)
        # 出力層
        self.layer3 = nn.Linear(50, 10)

    def forward(self, input_data):
        '''
        ネットワークの（順伝播）の定義（つなげる）
        '''
        # input_dataをフォーマット変換します
        # -1は自動的に変換する
        input_data = input_data.view(-1, 28 * 28)
        # 前の層からきたinput_dataをlayer1に渡します
        input_data = self.layer1(input_data)
        # 前の層からきたinput_dataをlayer2に渡します
        input_data = self.layer2(input_data)
        # 前の層からきたinput_dataをlayer3に渡します
        input_data = self.layer3(input_data)
        return input_data


# 学習用モデルのインスタンスを生成します
model = MLP()
```

# 学習データの用意


```
# -----------------------------------------------------------------------------
# 学習データの準備をします
#
print('---------- 学習のデータの準備 ----------')
data_folder = '~/data'
transform = transforms.Compose([
    # データの型をTensorに変換する
    transforms.ToTensor()
])

# 学習データ
train_data_with_labels = MNIST(
    data_folder, train=True, download=True, transform=transform)

train_data_loader = DataLoader(
    train_data_with_labels, batch_size=BATCH_SIZE, shuffle=True)

# 検証データ
test_data_with_labels = MNIST(
    data_folder, train=False, download=True, transform=transform)
test_data_loader = DataLoader(
    test_data_with_labels, batch_size=BATCH_SIZE, shuffle=True)
```
# 学習するための準備

学習するための、損失関数、学習率などの指定をします。
この辺り、ニューラルネットワークとは何か、という理解が前提となります。
前提知識をもう一回確認したい方は、私のnoteをご参照ください。

https://note.com/kawashimaken/n/nfeebd2502b87?magazine_key=me13f2d6e0ab8

```
# -----------------------------------------------------------------------------
# 学習の用意をします
# 損失関数は交差エントロピー誤差関数を使います
lossResult = nn.CrossEntropyLoss()
# SGD
optimizer = optimizer.SGD(model.parameters(), lr=0.01)

print('---------- 学習開始します ----------')
# 学習開始します
for epoch in range(MAX_EPOCH):
    # 誤差の初期設定
    total_loss = 0.0
    # enumerateはindexをデータを分解してくれます
    for i, data in enumerate(train_data_loader):

        # dataから学習対象データと教師ラベルデータのバッチを取り出します
        train_data, teacher_labels = data

        # 入力をtorch.autograd.Variableに変換します
        train_data, teacher_labels = Variable(train_data), Variable(
            teacher_labels)

        # 計算された勾配情報を削除（リセット、クリア）します
        optimizer.zero_grad()

        # モデルに学習データを与えて予測をします
        outputs = model(train_data)

        # lossとwによる微分計算します
        loss = lossResult(outputs, teacher_labels)
        # 勾配を計算します
        loss.backward()

        # 最適化のステップを一回実行します（パラメーターを更新します、たくさんのoptimizerの共通の処理）
        optimizer.step()

        # loss.item()はlossを数値に変換します、誤差を累計します
        total_loss += loss.item()

        # PROGRESS_SHOW_PER_BATCH_COUNTミニバッチずつ、進捗を表示します
        if i % PROGRESS_SHOW_PER_BATCH_COUNT == PROGRESS_SHOW_PER_BATCH_COUNT-1:
            print('i=',i)
            print(
                '学習進捗：[EPOCH:%d, %dバッチx%d -> %d枚学習完了]　学習誤差（loss）: %.3f' % (epoch + 1, i + 1, BATCH_SIZE, (i + 1) * BATCH_SIZE,
                                                                     total_loss / PROGRESS_SHOW_PER_BATCH_COUNT))
            # 計算用誤差をリセットします
            total_loss = 0.0

print('学習終了')
```
# 検証

学習が終わり、学習済みモデルをゲットしたら、次は、その学習済みモデルを実際に「使って」推論してみて、この学習済みモデルがいかに、正確かを検証します。

ほぼ、必須のステップだと言ってもいいと思います。

```

# -----------------------------------------------------------------------------
# 検証：全ての検証画像データに対しての正解率を計算します
print('---------- 全ての検証画像データに対しての正解率を計算します ----------')
# 全体のデータ数（計測対象数）
total = 0
# 正解カウンター
count_when_correct = 0

#
for data in test_data_loader:
    # 検証データローダーからデータを取り出した上、アンパックします
    test_data, teacher_labels = data
    # テストデータを変換した上、モデルに渡して、判定してもらいます
    results = model(Variable(test_data))
    # 予測を取り出します
    print(torch.max(results, 1))
    # 結果：
    # torch.return_types.max(
    # values=tensor([1.2185, 5.8557, 2.8262, 4.7874], grad_fn=<MaxBackward0>),
    # indices=tensor([2, 8, 8, 8]))
    # torch.max(tensor, axis)
    # 　values  indices
    #     ↓        ↓
    #     _    predicted
    _, predicted = torch.max(results.data, 1)
    # 一つずつ、推論結果配列の最大値（最も確信しているラベル）取り出します
    # 使わないものはよく、アンダーバーにします。（使い捨て）
    # ここでは、axis=1なので、行ごとに最大値を取り出すという意味になります
    print('_', _)
    # 結果：それぞれの最大値そのものが入っています
    # tensor([1.6123, 5.6203, 3.0886, 3.8317], grad_fn=<MaxBackward0>)
    print('predicted', predicted)
    # 結果：「最大値は何番目なのか」(index location)が入っています
    # tensor([3, 9, 1, 0])
    #
    # print('teacher_labels',teacher_labels)
    # 結果：
    # teacher_labels
    # tensor([3, 5, 3, 8])
    # teacher_labels
    # tensor([3, 5, 1, 7])
    # ...
    # ...
    #
    # print('teacher_labels.size(0)',teacher_labels.size(0))
    # teacher_labels.size(0) 4
    total += teacher_labels.size(0)
    count_when_correct += (predicted == teacher_labels).sum()

print('count_when_correct:%d' % (count_when_correct))
print('total:%d' % (total))

print('正解率：%d / %d = %f' % (count_when_correct, total,
                            int(count_when_correct) / int(total)))
```


# まとめ
いかがですか？これで、ある程度、PyTorchでニューラルネットワークを使って、画像認識させることのイメージが着いたでしょうか？
ほぼ、全部、「コメント読め」的な感じになっちゃいましたが、いいねが多ければ、まったこのような記事を投稿しようと思います。
よろしくお願い致します。

# ソースコードはこちら

https://github.com/kawashimaken/salon/blob/master/pytorch/mnist.py

また、コメントを更新する場合もありますので、最新のコードは、GitHubで管理しますので、よかったら、フォローか、スターか、ブックマークをしてください。m(_._)m