好きをブチ抜く

「好き」をブチ抜く

本、映画、科学、哲学、心理、すごい人の考え方など。あらゆる情報を編集したい。

【PyTorch使ってみた】深層学習入門実践 手書き数字画像データMNIST

記事の内容

今回は、深層学習(ディープラーニング)を手軽に実装するためのライブラリである「pytorch」を紹介します。
実際に、深層学習入門の定番である、手書き文字認識にチャレンジしてみます。

・深層学習の基礎はなんとなくわかっている
・pytorchを使った実装をざっと確認したい
こんな方におすすめの記事になります。

pytorchをつかうための簡単な説明と、実装をまとめます。それでは、目次をご覧ください。

深層学習をざっくりと

教師あり学習で、深層学習をする場合、二つのフェイズにわけて実装する。学習と推論だ。

学習フェイズでは、出力結果と期待される結果に誤差がうまれる。この誤差を表すのが、誤差関数(損失関数)だ。この誤差を小さくするために、結合パラメータを調整する方法が、勾配降下法である。深層学習では、全部ではないが複数個ずつデータを使うミニバッチ学習が一般的である。

学習フェイズの流れ

深層学習の学習フェイズにおけるざっくりとした流れ。
1 推論フェイズと同様に入力から出力を求める
2 期待する出力と実際の出力の誤差関数をもとめる
3 誤差に対する各重みの微分値をバックプロパゲーションで求める
4 重みの微分値に応じて、重みを更新する
5 1に戻る



PyTorch

1 データの前処理
2 DataLoaderの作成
3 ネットワークの構築
4 誤差関数と最適化手法の設定
5 学習と推論の設定
6 学習と推論の実行
この流れに沿って、実装してみる。
今回、この記事で紹介する説明とコードは、おもに「つくりながら学ぶ 深層強化学習」という本を参考にしています。

1 データの前処理

データは、おきまりの「手書き数字の画像データMNIST」を使用。

# 手書き数字の画像データMNISTをダウンロード

from sklearn.datasets import fetch_openml
mnist = fetch_openml('mnist_784', version=1, data_home=".")  # data_homeは保存先を指定
# 1. データの前処理(画像データとラベルに分割し、正規化)

print(mnist.data[2020])
X = mnist.data / 255  # 0~255を0~1に正規化
y = mnist.target


# 注意‼ MNISTのデータセットの変更により、ラベルが数値データになっていないので、NumPyの配列の数値型に変換

import numpy as np
y = np.array(y)
y = y.astype(np.int32)


print(X[2020])
print(y)
type(y[0]) #変換後の型を確認

2 DataLoaderへの変換

使用するデータを「DataLoader」という変数へ変換してあげる必要がある。

# 2. DataLoderの作成

import torch
from torch.utils.data import TensorDataset, DataLoader
from sklearn.model_selection import train_test_split

# 2.1 データを訓練とテストに分割(6:1)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=1/7, random_state=0)

# 2.2 データをPyTorchのTensorに変換
X_train = torch.Tensor(X_train)
X_test = torch.Tensor(X_test)
y_train = torch.LongTensor(y_train)
y_test = torch.LongTensor(y_test)

# 2.3 データとラベルをセットにしたDatasetを作成
ds_train = TensorDataset(X_train, y_train)
ds_test = TensorDataset(X_test, y_test)

# 2.4 データセットのミニバッチサイズを指定した、Dataloaderを作成
loader_train = DataLoader(ds_train, batch_size=64, shuffle=True) #訓練データはランダムにしたい
loader_test = DataLoader(ds_test, batch_size=64, shuffle=False)

3 ネットワークの構築

kerasの書き方に近い。

# 3. ネットワークの構築
# Keras風の書き方 

from torch import nn

model = nn.Sequential()
model.add_module('fc1', nn.Linear(28*28*1, 100)) #28*28*1=784の入力、出力は100
model.add_module('relu1', nn.ReLU())
model.add_module('fc2', nn.Linear(100, 100)) #入力100、出力100
model.add_module('relu2', nn.ReLU())
model.add_module('fc3', nn.Linear(100, 10)) #出力は、0~9の10

print(model)

4 誤差関数と最適化手法

# 4. 誤差関数と最適化手法の設定

from torch import optim

# 誤差関数の設定
loss_fn = nn.CrossEntropyLoss()  # クロスエントロピー誤差

# 勾配降下法(Adam)
optimizer = optim.Adam(model.parameters(), lr=0.01)

5 学習と推論

1 epochの間に、64個ずつデータを使用してミニバッチ学習を進める。全データを使用したら、1 epoch終了。
注意点:実行する際には、それぞれ学習モード、推論モードにネットワークを設定する必要がある。

# 5-1. 学習1回でやることを定義します

def train(epoch):
    model.train()  # 学習モードに切り替える

    # データローダーから1ミニバッチずつ取り出して計算する
    for data, targets in loader_train:
      
        optimizer.zero_grad()  # 一度計算された勾配結果を0にリセット
        outputs = model(data)  # 入力dataをinputし、出力を求める
        loss = loss_fn(outputs, targets)  # 出力と訓練データの正解との誤差を求める
        loss.backward()  # 誤差のバックプロパゲーションを求める
        optimizer.step()  # バックプロパゲーションの値で重みを更新する

    print("epoch{}:終了\n".format(epoch))
# 5-2. 推論1回でやることを定義します
# Chainerのtrainer.extend(extensions.Evaluator())に対応するものはない


def test():
    model.eval()  # ネットワークを推論モードに切り替える
    correct = 0

    # データローダーから1ミニバッチずつ取り出して計算する
    with torch.no_grad():  # 微分は推論では必要ない
        for data, targets in loader_test:

            outputs = model(data)  # 入力dataをinputし、出力を求める

            # 推論する
            _, predicted = torch.max(outputs.data, 1)  # 確率が最大のラベルを求める
            correct += predicted.eq(targets.data.view_as(predicted)).sum()  # 正解と一緒だったらカウントアップ

    # 正解率を出力
    data_num = len(loader_test.dataset)  # データの総数
    print('\nテストデータの正解率: {}/{} ({:.0f}%)\n'.format(correct,
                                                   data_num, 100. * correct / data_num))

6 実行

3 epochの学習してみる。

# 6. 学習と推論の実行
for epoch in range(3):
    train(epoch)

test()

今回の場合、実行結果は、「テストデータの正解率: 9610/10000 (96%)」だった。