PyTorch (5) Multilayer Perceptron
今回は多層パーセプトロンでMNIST。おなじみ。
import torch import torch.nn as nn import torchvision import torchvision.datasets as dsets import torchvision.transforms as transforms # Hyperparameters input_size = 784 hidden_size = 500 num_classes = 10 num_epochs = 50 batch_size = 100 learning_rate = 0.001
入力層は28 x 28 = 784ユニット、隠れ層が500ユニット、出力層が0-9の10クラスという構成。
# MNIST Dataset (Images and Labels) train_dataset = dsets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True) test_dataset = dsets.MNIST(root='./data', train=False, transform=transforms.ToTensor()) # Dataset Loader (Input Pipline) 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)
MNISTはPyTorchの標準機能で読み込める。PyTorchでデータを扱うにはDataSet
とDataLoader
の2つのクラスが重要。DataSet
はデータセットのまとまりを表していて、DataLoader
にDataSet
をセットすることでミニバッチ単位でロードできるようになる。MNIST
はDataSet
を継承したMNISTデータセットを扱うための組み込みクラス。カスタムデータセットを作成する方法はあとで試そう。
transforms
を使うといろいろなデータ前処理ができる。ここでは読み込んだ画像データ(PIL.Image.Image
)をテンソルに変換する ToTensor()
だけを指定。
print(len(train_dataset)) # 60000 print(len(test_dataset)) # 10000 print(len(train_loader)) # 600 print(len(test_loader)) # 100
DataSetのlenはサンプル数を返し、DataLoaderのlenはミニバッチ数を返すので注意!
# 1データだけ取得 image, label = iter(train_loader).next() print(type(image)) # <class 'torch.FloatTensor'> print(type(label)) # <class 'torch.LongTensor'> print(image.size()) # torch.Size([100, 1, 28, 28]) print(label.size()) # torch.Size([100])
<class 'torch.Tensor'> <class 'torch.Tensor'> torch.Size([100, 1, 28, 28]) torch.Size([100])
DataLoader
から1バッチ分のデータを取り出すには iter()
で囲んでから next()
を呼び出す。テストしたいときに便利!
可視化のコード。
# 可視化 import matplotlib.pyplot as plt import numpy as np %matplotlib inline def imshow(img): npimg = img.numpy() # [c, h, w] => [h, w, c] plt.imshow(np.transpose(npimg, (1, 2, 0))) images, labels = iter(train_loader).next() images, labels = images[:25], labels[:25] imshow(torchvision.utils.make_grid(images, nrow=5, padding=1)) plt.axis('off')
画像をブロック上に配置する make_grid()
がとても便利!
class MultiLayerPerceptron(nn.Module): def __init__(self, input_size, hidden_size, num_classes): super(MultiLayerPerceptron, self).__init__() self.fc1 = nn.Linear(input_size, hidden_size) self.relu = nn.ReLU() self.fc2 = nn.Linear(hidden_size, num_classes) def forward(self, x): # print(x.size()) out = self.fc1(x) # print(out.size()) out = self.relu(out) # print(out.size()) out = self.fc2(out) # print(out.size()) return out
多層パーセプトロンを構築。PyTorchはモデル構築の方法もいろいろある。Kerasのように Sequential
を使ったり、Relu
をFunctionにしたり。今後はいろいろ試していこう。
forward()
の処理では途中結果を普通に print
できるのでデバッグがとてもやりやすい。下のように1バッチ分をモデルに入力して途中出力のサイズを確認したりできる。
model = MultiLayerPerceptron(input_size, hidden_size, num_classes) # テスト image, label = iter(train_loader).next() print("befire view:", image.size()) image = image.view(-1, 28 * 28) print("after view:", image.size()) output = model(image) print(output.size())
上で view
はNumPyの reshape
みたいな関数。出力は
befire view: torch.Size([100, 1, 28, 28]) after view: torch.Size([100, 784]) torch.Size([100, 10])
あとはこれまでとほとんど一緒。
criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) def train(train_loader): model.train() running_loss = 0 for batch_idx, (images, labels) in enumerate(train_loader): images = images.view(-1, 28 * 28) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) running_loss += loss.item() loss.backward() optimizer.step() train_loss = running_loss / len(train_loader) return train_loss def valid(test_loader): model.eval() running_loss = 0 correct = 0 total = 0 with torch.no_grad(): for batch_idx, (images, labels) in enumerate(test_loader): images = images.view(-1, 28 * 28) outputs = model(images) loss = criterion(outputs, labels) running_loss += loss.item() _, predicted = torch.max(outputs, 1) correct += (predicted == labels).sum().item() total += labels.size(0) val_loss = running_loss / len(test_loader) val_acc = float(correct) / total return val_loss, val_acc loss_list = [] val_loss_list = [] val_acc_list = [] for epoch in range(num_epochs): loss = train(train_loader) val_loss, val_acc = valid(test_loader) print('epoch %d, loss: %.4f val_loss: %.4f val_acc: %.4f' % (epoch, loss, val_loss, val_acc)) # logging loss_list.append(loss) val_loss_list.append(val_loss) val_acc_list.append(val_acc)
running_lossの計算はちょっと注意。PyTorchの損失関数のデフォルトは、size_average=True
になっている。つまり、蓄積したlossをミニバッチのサンプル数で割った平均が返される仕様になっている。そのため、ミニバッチ単位でlossを蓄積していって最後にtrain_loss
を計算するときはミニバッチ数 len(train_loader)
で割って平均とする。
ここらへんは人によって実装方法が違う。たとえば、Transfer Learning Tutorialの実装だとlossをサンプル数倍したものをrunning_loss
に蓄積していって最後にミニバッチ数ではなく、サンプル数 len(train_set)
で割っている。
あとrunning_lossに加えるときはtensorのまま加えずに loss.item()
としてテンソルから数値に変換してから加えるようにしたほうがよいようだ。
評価時は勾配は不要なので with torch.no_grad()
をつける。
こんな感じでログが出力される。
epoch 0, loss: 2.2276 val_loss: 2.1488 val_acc: 0.5230 epoch 1, loss: 2.0729 val_loss: 1.9791 val_acc: 0.7124 epoch 2, loss: 1.8919 val_loss: 1.7798 val_acc: 0.7401 epoch 3, loss: 1.6870 val_loss: 1.5648 val_acc: 0.7583 epoch 4, loss: 1.4785 val_loss: 1.3595 val_acc: 0.7808 epoch 5, loss: 1.2890 val_loss: 1.1827 val_acc: 0.8003 epoch 6, loss: 1.1307 val_loss: 1.0397 val_acc: 0.8160 epoch 7, loss: 1.0047 val_loss: 0.9280 val_acc: 0.8243 epoch 8, loss: 0.9059 val_loss: 0.8407 val_acc: 0.8337 epoch 9, loss: 0.8282 val_loss: 0.7718 val_acc: 0.8405 epoch 10, loss: 0.7664 val_loss: 0.7166 val_acc: 0.8482
グラフ化。
import matplotlib.pyplot as plt %matplotlib inline # plot learning curve plt.figure() plt.plot(range(num_epochs), loss_list, 'r-', label='train_loss') plt.plot(range(num_epochs), val_loss_list, 'b-', label='val_loss') plt.legend() plt.xlabel('epoch') plt.ylabel('loss') plt.grid() plt.figure() plt.plot(range(num_epochs), val_acc_list, 'g-', label='val_acc') plt.legend() plt.xlabel('epoch') plt.ylabel('acc') plt.grid()
そろそろGPUないときつくなってきたので次回からGPU使おう!