LoginSignup
7
3

PyTorchでMNISTを扱ってみた (機械学習)

Last updated at Posted at 2022-12-21

初心者がPyTorchを使ってみたく,PyTorchを用いてMNISTを扱ってみました!
その際のメモ書きです.

目標

今回は,PyTorchを用いて機械学習モデルの作成を目指す.

準備

ライブラリのインポート

PyTorchを扱う際に必要な以下のライブラリをインポートする.

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

インポートしたパッケージの詳細は以下のようになっている.

パッケージ 名前
torch PyTrochのリストを扱うもの
torch.nn ネットワークの構築
torch.nn.funcctional 様々な関数の使用
torch.optim 最適化アルゴリズムの使用
torchvision 画像処理に関係する処理の使用
torchvision.transform 画像変換機能の使用

乱数シードの固定

今回は,乱数シードを一度に設定する関数を作成する.

def setup_all_seed(seed=0):
    # numpyに関係する乱数シードの設定
    np.random.seed(seed)
    
    # pytorch
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True

MNIST dataset

まずはじめに,MNIST datasetがどのような形をしているのか理解をする.
slide3-1

#訓練データ
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_dataの中身を見てみる.

print(len(train_dataset))
train_dataset[0]
60000
(tensor([[[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
           0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
           0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
           0.0000, 0.0000, 0.0000, 0.0000],
                                 <中略>
          [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
           0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
           0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
           0.0000, 0.0000, 0.0000, 0.0000]]]), 5)

 以上より,60000枚ものデータが,tensor([画像データ(28*28のサイズ)], ラベル)の形で格納されていることが確認できる.
 また,以下のコードで,画像データと正解ラベルを分けることができる.

fig, label = train_dataset[0]
print("fig : {}, label : {}".format(fig,label))
print("fig.size() : {}".format(fig.size()))
plt.imshow(fig.view(-1,28), cmap='gray')
fig : tensor([[[0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
          0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
          0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
          0.0000, 0.0000, 0.0000, 0.0000],
                                 <中略>
         [0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
          0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
          0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000,
          0.0000, 0.0000, 0.0000, 0.0000]]])
label : 5
fig.size() : torch.Size([1, 28, 28])

fig3-1

学習

 今回は,ミニバッチ学習にて学習を行う機械学習モデルの作成を目指す.
DeepLearning.jpg

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=True)

ネットワークモデルの定義

DeepLearning.jpg

__init__()部分では,モデルの形状を定義する.
forward()部分では,モデルの順伝播の計算の流れを定義する.
この時,出力のy の部分で,活性化関数を通していない理由は次の節で説明をする.

class Net(nn.Module):
    def __init__(self, input_size, hidden1_size, hidden2_size, output_size):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(input_size, hidden1_size)
        self.fc2 = nn.Linear(hidden1_size, hidden2_size)
        self.fc3 = nn.Linear(hidden2_size, output_size)

    def forward(self, x): # x : 入力
        z1 = F.relu(self.fc1(x))
        z2 = F.relu(z1)
        y = self.fc3(z1)
        return y

モデルのインスタンス化

input_size = 28*28
hidden1_size = 1024
hidden2_size = 512
output_size = 10

device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = Net(input_size, hidden1_size, hidden2_size, output_size).to(device)
print(model)

実行結果

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)
)

学習の準備

損失関数と最適化法の指定

以下のように損失関数と最適化法の指定を行う.
今回は,クラス分類問題であるので,交差エントロピー誤差関数を用いた.
また,最適化法は,確率的勾配降下法を用いて学習を行うこととする.
ここで,前節で定義したネットワークモデルのforward()関数内の出力する部分を見ると,何も関数を通していないことがわかる.通常であれば,クラス分類問題では,それぞれの分類先の確率を示すsotfmax()関数が用いられるのが一般的である.しかし,今回定義したネットワークモデルの順伝播の出力層部分の活性化関数にsoftmax()関数が使われていない理由は,PyTorchのnn.CrossEntropyLoss()関数にsoftmax()関数の活性化関数部分が含まれている為,順伝播部分に活性化関数の記載が必要無い ということである.

# 損失関数 criterion:基準
# CrossEntropyLoss:交差エントロピー誤差関数
criterion = nn.CrossEntropyLoss()

# 最適化法の指定 optimizer:最適化
# SGD:確率的勾配降下法
optimizer = optim.SGD(model.parameters(), lr=0.01)

1epochの訓練を行う関数の定義

def train_model(model, train_loader, criterion, optimizer, device='cpu'):

    train_loss = 0.0
    num_train = 0

    # 学習モデルに変換
    model.train()

    for i, (images, labels) in enumerate(train_loader):
        # batch数をカウント
        num_train += len(labels)

        images, labels = images.view(-1, 28*28).to(device), labels.to(device)

        # 勾配を初期化
        optimizer.zero_grad()

        # 推論(順伝播)
        outputs = model(images)

        # 損失の算出
        loss = criterion(outputs, labels)

        # 誤差逆伝播
        loss.backward()

        # パラメータの更新
        optimizer.step()

        # lossを加算
        train_loss += loss.item()
    
    # lossの平均値を取る
    train_loss = train_loss / num_train

    return train_loss

検証データによるモデル評価を行う関数の定義

def test_model(model, test_loader, criterion, optimizer, device='cpu'):

    test_loss = 0.0
    num_test = 0

    # modelを評価モードに変更
    model.eval()

    with torch.no_grad(): # 勾配計算の無効化
        for i, (images, labels) in enumerate(test_loader):
            num_test += len(labels)
            images, labels = images.view(-1, 28*28).to(device), labels.to(device)
            outputs = model(images)
            loss = criterion(outputs, labels)
            test_loss += loss.item()
        
        # lossの平均値を取る
        test_loss = test_loss / num_test
    return test_loss

モデル学習を行う関数の定義

def lerning(model, train_loader, test_loader, criterion, opimizer, num_epochs, device='cpu'):

    train_loss_list = []
    test_loss_list = []

    # epoch数分繰り返す
    for epoch in range(1, num_epochs+1, 1):

        train_loss = train_model(model, train_loader, criterion, optimizer, device=device)
        test_loss = test_model(model, test_loader, criterion, optimizer, device=device)
        
        print("epoch : {}, train_loss : {:.5f}, test_loss : {:.5f}" .format(epoch, train_loss, test_loss))

        train_loss_list.append(train_loss)
        test_loss_list.append(test_loss)
    
    return train_loss_list, test_loss_list

学習

num_epochs = 10
train_loss_list, test_loss_list = lerning(model, train_loader, test_loader, criterion, optimizer, num_epochs, device=device)
epoch : 1, train_loss : 0.00872, test_loss : 0.00844
epoch : 2, train_loss : 0.00728, test_loss : 0.00594
epoch : 3, train_loss : 0.00450, test_loss : 0.00343
epoch : 4, train_loss : 0.00291, test_loss : 0.00249
epoch : 5, train_loss : 0.00227, test_loss : 0.00207
epoch : 6, train_loss : 0.00194, test_loss : 0.00182
epoch : 7, train_loss : 0.00174, test_loss : 0.00165
epoch : 8, train_loss : 0.00161, test_loss : 0.00150
epoch : 9, train_loss : 0.00151, test_loss : 0.00143
epoch : 10, train_loss : 0.00144, test_loss : 0.00140

学習推移のグラフ化

plt.plot(range(len(train_loss_list)), train_loss_list, c='b', label='train loss')
plt.plot(range(len(test_loss_list)), test_loss_list, c='r', label='test loss')
plt.xlabel("epoch")
plt.ylabel("loss")
plt.legend()
plt.grid()
plt.show()

pytorch_MNIST_result

学習結果の確認

学習結果を確認するために,test_datasetの最初の10個のデータを順伝播させた結果をみてみることにする.
モデルのフォワードプロパゲーションの実行は,modelオブジェクトを関数のように呼び出すだけで順伝播を行うことができる.
正確には,__call__メソッドの呼び出しを行っている.引数には,入力データをテンソル型で渡せば,返り値として,出力結果がテンソル型で戻ってくる.
今回のモデルは,分類問題であり,0から9の手書き数字の推論を行うモデルとして設計し,出力層は10個のレイヤから成立している.
出力層の10個のレイヤには,0である確率から9である確率までの10個の数字それぞれに対して確率で出力される.
正確には,順伝播部分に確率に変換するsoftmax()関数を活性化関数として入れていない為,順伝播の出力自体は,確率ではないが,大小関係に変化はないので,そのまま値が大きいものが,確率として高いことになり,一番確率が高いと判断した数字を推論の結果としている.
モデルの順伝播の返り値として,出力層の結果が返されているが,出力層の10個のレイヤが,テンソル型の配列で返却される.今回の分類は,0から9の数字の分類なので,都合よく,返される配列のインデックスが分類する数字とみなせばよい為,順伝播の返却値のテンソル型配列の中の最大値のインデックスを返却するtorch.argmax()を用いて分類結果としている.

plt.figure(figsize=(20, 10))
for i in range(10):
    image, label = test_dataset[i]
    image = image.view(-1, 28*28).to(device)

    # 推論
    prediction_label = torch.argmax(model(image))

    ax = plt.subplot(1, 10, i+1)

    plt.imshow(image.detach().to('cpu').numpy().reshape(28, 28), cmap='gray')
    ax.axis('off')
    ax.set_title('label : {}\n Prediction : {}'.format(label, prediction_label), fontsize=15)
plt.show()

pytorch_MNIST_result.png

結果として,10個のデータの内の正解数は9個で概ね分類が上手くいっていることがわかる.
9番目のデータは人間でも6に見間違える人がいそう...笑

7
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3