Python

【PyTorch入門】多層パーセプトロンでMNISTを分類

【PyTorch入門】多層パーセプトロンでMNISTを分類

 

 

  • PyTorchでディープラーニングを実装してみたい
  • PyTorchが難しくてよくわからない…

本記事では、その悩みを解決していきます。

また、本記事は『とりあえずPytorchで機械学習を実装してみたい!』という方と『Pytorchを深く学びたい!』という方の両者に対応しています。

Pytorchを深く学びたい方は、本記事内で詳細説明のリンクを紹介するので参考にしてください。

本記事の内容

  1. PyTorchで多層パーセプトロンを実装するための用意
  2. PyTorchを利用してデータを読み込む
  3. PyTorchでネットワークを定義
  4. 定義したネットワークを学習

本記事を読むメリット

  1. PyTorchでディープラーニングを構築する基本が学べる

 

 

PyTorchで多層パーセプトロンを実装するための用意

 

Google Colaboratoryを使用する場合は、PyTorchは最初からインストールされているので問題はありません。

Anaconda等を使用する場合は、下記のコードでPytorchを事前にインストールしてください。

 

$ pip3 install torch torchvision

 

Google Colaboratoryを使用する場合は下記の記事を参照し導入してください。

 

【10分で完了】Google Colabのインストール法・使い方Google Colabは、GPUを使用できるため、低コストかつ高速で機械学習の実装を行う際に必要不可欠なツールです。本記事では、Google Colabのインストール方法と注意事項を10〜15分程度でまとめました。...

 

 

必要なライブラリをインストール

 

多層パーセプトロンを実装するのに必要なライブラリをインポートしましょう。

具体的には、以下を実行しましょう。

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

# グラフのスタイルを指定
plt.style.use('seaborn-darkgrid')

 

ざっくりとインポートしたパッケージを紹介します。

torch Pytorchの基本となるパッケージ
torch.nn ネットワーク等を定義するためのパッケージ
torch.nn.functional さまざまな関数を使用するためのパッケージ
torch.optim 最適化アルゴリズムを提供するパッケージ
torchvision 画像処理に関する操作を提供するパッケージ
torchvision.transform 画像変換機能を提供するパッケージ

この段階で覚える必要はありません、「こういうパッケージを使用するのか..」という感じで雰囲気のみつかんでおけば大丈夫です。

また、『numpy』, 『matplotlib』は可視化等に使用します。

初めて聞いたという方は下記を参考に学んでください!

 

Tensorの使用

 

Pytorchで機械学習を実行する場合、Pytorch独特の配列(Tensor)を利用します。

PytorchのTensorを初めて聞いたという方は下記を参考に勉強してから先に進みましょう。

 

【Pytorch】tensor型とは(知らないとまずいものをまとめてみた)PyTorchのTensorに関してまとめてみました。この中で紹介しているTensorの関数やメソッドは、どれも機械学習で必要不可欠なものばかりです。15分程度で読み終わるので一読して頭を整理させましょう。...

 

 

PyTorchからDataの読み込み(Dataset, DataLoader)

 

PyTorchで学習を行う場合、データをDataset, DataLoderという形で読み込みます。

イメージできには、各データを一つのデータベース的なものに格納するのがDatasetです。

そして、そのDatasetをDataLoderに渡すことで、ミニバッチ単位でデータを取り出すことができます。

 

  1. Dataset : データを一つのデータベースにまとめる
  2. DataLoader : データをミニバッチ単位で取り出す 

詳しく解説していきます。

 

Dataset : 各データを一つのデータベースにまとめる

 

今回は、MNISTというPytorchのサンプルデータをインポートし、データセットに格納していきます。

具体的には、下記のコードでインポートし、データセットを作成することができます。

train_dataset = torchvision.datasets.MNIST(root='./data',
                                           train=True,
                                           transform=transforms.ToTensor(),
                                           download=True)

test_dataset = torchvision.datasets.MNIST(root='./data',
                                           train=False,
                                           transform=transforms.ToTensor(),
                                           download=True)

 

train_dataset, test_datasetには訓練データの入力と出力が一まとまりのデータベースにまとまっているようなイメージです!

また、引数の『transform=transforms.ToTensor()』でデータをPytorchで学習できるようにTensor型に変換しています。

とりあえず、Pytorchで機械学習を実行してみたいという方は先に進んでください。

着実にPyTorchを学びたいという方は下記を参考にして『Dataset』の詳細を学んでから進みましょう。

PytorchのDatasetを徹底解説(自作データセットも作れる)PyTorchのDataset作成方法を徹底的に解説しました。本記事を読むことで、Numpy, PandasからDatasetを作成したり、自作のDatasetを作成しモジュール化する作業を初心者の方でも理解できるように徹底的に解説しました。...

 

Transform部分について詳しく知りたい方は下記を参考にしてください

 

PytorchのTransformを使い方(自作Transform)『PytorchのTransformsパッケージが何をやっているかよくわからん...』という方のために本記事を作成しました。本記事では、transformsを体系的に(複数の処理を行う、自作Transform)を解説しました。...

 

 

DataLoader : dataをミニバッチに適した形に変換

 

DataLoaderは、Datasetを入力としてミニバッチごとにデータを取り出すことができます。

今回は、ミニバッチデータサイズを256としてDataLoaderを作成します。

具体的には、下記のコードで実装することができます。

batch_size = 256


train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           batch_size=batch_size,
                                           shuffle=True)

test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                           batch_size=batch_size,
                                           shuffle=False)

 

これで、PyTorchによるデータの読み込みは終了です。

同様に、とりあえずPytorchを使用して機械学習を実装してみたいという方はこのまま進んでください!

『DataLoader』の動作を確認してから先に進みたいという方は下記を参考にしてください。

 

【徹底解説】PytorchのDataLoaderの使い方PytorchのDataLoaderを具体的な例を使って解説しました。『DataLoaderが実はよくわからない...』という方は、具体的な動作を徹底解説したので、本記事を参考にしてください。...

 

次章では、使用するネットワーク(多層パーセプトロン)を構築していきましょう。

 

PyTorchでネットワークを定義

 

Pytorchでは主にネットワークを定義する方法が二つありますが、今回はカスタマイズ性の高い方法を用いてネットワークを定義します。

今回はGPUを使用するため、ネットワークをGPUに転送するプロセスも含み以下の2段階で解説していきます。

  1. ネットワークを定義
  2. ネットワークをGPUに転送する

GPUを使用できない方も②で詳しく説明するので安心してください。

 

ネットワークを定義

 

今回は、『nn.Module(ネットワークのモジュールのクラス)』を引き継ぐ方法でネットワークを定義します。

num_classes = 10

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(28*28, 1024)
        self.fc2 = nn.Linear(1024, 512)
        self.fc3 = nn.Linear(512, num_classes)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

 

『num_classes』で出力の数を指定しています。

これまで同様、とりあえずPytorchで機械学習を行いたいという方は先に進んでください!

ネットワークの定義方法をより詳しく理解したいという方は下記を参考にしてください(一度本記事を読んでから戻ってきても良いです)

 

Pytorchのネットワークの書き方を徹底解説Pytorchのネットワークの書き方を二つ説明しました。一つ目はSequentialを使用する方法で、二つ目はネットワークを自作モジュール化する方法を説明しました。また、二つのメリット・デメリットを具体例を通じて理解できるように工夫しました。...

 

ネットワークをGPUに転送する

 

以下のコードで、ネットワークをGPUに転送することができます。

device = 'cuda' if torch.cuda.is_available() else 'cpu'
net = Net().to(device)

 

  • 1行目 : GPUが利用できる場合はGPUを利用し、そうでない場合は、CPUを利用するように設定
  • 2行目:ネットワークをGPUに転送

 

以下のように入力することでネットワーク構造を確認することができます。

print(net)

 

<出力>

Net(
  (fc1): Linear(in_features=784, out_features=1024, bias=True)
  (fc2): Linear(in_features=1024, out_features=512, bias=True)
  (fc3): Linear(in_features=512, out_features=10, bias=True)
)

 

また、『net.parameters()』でパラメータの情報を取得することができます。

下記を入力して確かめてみてください。

list(net.parameters())

 

次はいよいよネットワークを学習させます!

 

ネットワークを学習

 

PyTorchでは以下のようにネットワークを学習することができます。

  1. 損失関数と最適化手法を設定
  2. ネットワークを学習
  3. 順伝搬
  4. 検証

 

損失関数と最適化手法を設定

 

まずは、学習に使用する損失関数と最適化アルゴリズムを以下のように設定しましょう。

# 損失関数の設定
criterion = nn.CrossEntropyLoss()

# 最適化手法を設定
optimizer = optim.SGD(net.parameters(), lr=0.01)

 

今回は、損失関数として『交差エントロピー誤差関数』を使用し、最適化アルゴリズムとしては、『確率的勾配降下法』を使用します。

optimizerの第一引数には、ネットワークのパラメータを入力します。

 

ネットワークの学習

 

以下のようなコードでネットワークを学習することができます。

# エポック数
num_epochs = 40

# lossをプロットするためのリスト
train_loss_list = []
val_loss_list = []

for epoch in range(num_epochs):
    #epoch毎に初期化
    train_loss = 0
    val_loss = 0
    # trainモードへ切替
    net.train()
    # ミニバッチで分割して読み込む
    for i, (images, labels) in enumerate(train_loader):
        # viewで1次元配列に変更
        # toでgpuに転送
        images, labels = images.view(-1, 28*28).to(device), labels.to(device)
        # 勾配をリセット
        optimizer.zero_grad()
        # 順伝搬の計算
        outputs = net(images)
        # lossを計算
        loss = criterion(outputs, labels)
        # lossのミニバッチ分を加算
        train_loss += loss.item()
        # 逆伝搬の計算
        loss.backward()
        # 重みの更新
        optimizer.step()
    # 平均lossと平均accuracyを計算
    mean_train_loss = train_loss / len(train_loader.dataset)

    # 評価モードに切り替え
    net.eval()
    # 評価するとき勾配を計算しないように
    with torch.no_grad():
        for j, (images, labels) in enumerate(test_loader):
            images, labels = images.view(-1, 28*28).to(device), labels.to(device)
            outputs = net(images)
            loss = criterion(outputs, labels)
            val_loss += loss.item()
        
        mean_val_loss = val_loss / len(test_loader.dataset)

    # 訓練データのlossと検証データのlossのログ
    print(f'Epoch [{epoch+1}], train_Loss : {mean_train_loss:.4f}, val_Loss : {mean_val_loss:.4f}')
    
    # append
    train_loss_list.append(mean_train_loss)
    val_loss_list.append(mean_val_loss)

 

<output>

Epoch , train_Loss : 0.0087, val_Loss : 0.0084
Epoch , train_Loss : 0.0072, val_Loss : 0.0058
Epoch , train_Loss : 0.0044, val_Loss : 0.0034
                         :
                         :
                         :
Epoch [38/40], train_Loss : 0.0008, val_Loss : 0.0008
Epoch [39/40], train_Loss : 0.0008, val_Loss : 0.0008
Epoch [40/40], train_Loss : 0.0008, val_Loss : 0.0008

 

ポイントを簡単に箇条書きで説明していきます(後述で詳しく説明するのでここでは雰囲気を掴んでください!)

  1. optimizer.zero_grad() : 勾配を0にリセットする(勾配はbackwardメソッドが実行されるたびに積算されるため必要)
  2. loss.backward() : lossに関する微分がネットワーク全体で計算される
  3. optimizer.step() : 勾配を使って重みが更新される
  4. with torch.no_grad() : ボックス内では勾配の計算を追わない(検証ステップだから勾配計算は不要)

わざわざ、逆伝搬を実装しなくても『loss.backward()』一発で逆伝搬を実行してくれるのは驚きですね!

今回は、学習がうまくいっているかを判断するために、validationデータとtrainデータの誤差関数を表示するように実装しています。

一つのセルのコードですが、少し複雑なので、順伝搬フェイズと検証フェイズに分けてゆっくり解説します。

 

順伝搬フェイズ

 

1エポックの順伝搬は以下のように書かれています(lossを加算する部分は省いでいます)

# trainモードへ切替
    net.train()

    # ミニバッチで分割して読み込む
    for i, (images, labels) in enumerate(train_loader):
        # viewで1次元配列に変更
        # toでgpuに転送
        images, labels = images.view(-1, 28*28).to(device), labels.to(device)
        # 勾配をリセット
        optimizer.zero_grad()
        # 順伝搬の計算
        outputs = net(images)
        # lossを計算
        loss = criterion(outputs, labels)
        # 逆伝搬の計算
        loss.backward()
        # 重みの更新
        optimizer.step()

 

1epoch単位の順伝搬の各コードの意味

  1. net.train() : trainモードに変換
  2. optimizer.zero_grad() : 勾配の値をリセット
  3. outputs : 順伝搬の出力を計算
  4. loss : 出力とミニバッチのラベルからコスト関数を計算
  5. loss.backward() : lossの勾配を計算
  6. optimizer.step() : パラメータを更新

基本的にPytorchの学習の流れはどんな複雑なモデルでもこの枠組みが適応できます。

 

検証フェイズ

 

1エポック単位の検証フェイズを解説します(lossを加算する部分は省きます)

# 評価モードに切り替え
net.eval()
# 評価するときは勾配を余計な勾配計算を回避
with torch.no_grad():
     for j, (images, labels) in enumerate(test_loader):
          images, labels = images.view(-1, 28*28).to(device), labels.to(device)
          outputs = net(images)
          loss = criterion(outputs, labels)

 

1epochの検証フェイズ

  1. net.eval() : networkを評価モードに変換
  2. with torch.no_grad() : 逆伝搬が必要ないので余計な勾配計算を回避
  3. outputs : outputsを計算

 

余計な計算を避けるために、Pytorchの自動微分機能をoffにして評価を行います。

 

訓練誤差と検証誤差をプロット

 

訓練誤差と検証誤差をプロットし、学習が上手くいっているか確認しましょう。

<Input>

# 誤差をプロットしてみる
fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(range(num_epochs), train_loss_list, c='b', label='train loss')
ax.plot(range(num_epochs), val_loss_list, c='r', label='test loss')
ax.set_xlabel('epoch', fontsize='20')
ax.set_ylabel('loss', fontsize='20')
ax.set_title('training and validation loss', fontsize='20')
ax.grid()
ax.legend(fontsize='25')
plt.show()

 

<Output>

誤差関数をプロット訓練誤差(train loss)と検証誤差(test loss)をプロット

 

無事に、訓練誤差と検証誤差が大きく乖離することなく適切に学習されていることが視覚的にチェックできましたね。

 

まとめ

 

PyTorchを利用し多層パーセプトロンを構築し、MNISTデータセットの分類を行いました。

もう一度これまでの手順をまとめておきましょう。

  1. PyTorchで多層パーセプトロンを実装するための用意
  2. PyTorchを利用してデータを読み込む
  3. PyTorchでネットワークを定義
  4. 定義したネットワークを学習

 

本記事でPyTorchを使用し機械学習(ディープラーニング)を行う流れはつかめたと思います。

次は、各パッケージ(Dataset, DataLoader等)を正しく理解することが大切です。

これらを理解しないと少しカスタマイズしただけで、Pytorchがバグだらけになり解決策がよくわからないという状況に陥ります…

ここまで、とりあえず動かしてみたという方は下記の記事を参考にしてさらに深く学んでください。

 

ABOUT ME
努力のガリレオ
【運営者】 : 東大で理論物理を研究中(経歴)東京大学, TOEIC950点, NASA留学, カナダ滞在経験有り, 最優秀塾講師賞, オンライン英会話講師試験合格, ブログと独自コンテンツで収益6桁達成 【編集者】: イングリッシュアドバイザーとして勤務中(経歴)中学校教諭一種免許取得[英語],カナダ留学経験あり, TOEIC650点