PyTorch 0.4.1 examples (コード解説) : 画像分類 – MNIST (MLP)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/25/2018 (0.4.0 & 0.4.1)
* 本ページは、github 上の以下の pytorch/examples と keras/examples レポジトリのサンプル・コードを参考にしています:
- https://github.com/pytorch/examples/tree/master/mnist
- https://github.com/keras-team/keras/tree/master/examples
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
MNIST MLP
PyTorch 0.4.x の自作のサンプルをコードの簡単な解説とともに提供しています。
初級チュートリアル程度の知識は仮定しています。
最初は定番の MNIST 画像分類タスクのための MLP モデルから始めます。
準備
まずは最初に torch モジュールをインポートします :
import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torch.utils.data as data
0.4.x の作法で device を設定します :
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print("device", device)
ついでにシードも設定しておきましょう :
torch.manual_seed(1)
ハイパーパラメータ
そして必要最小限のハイパーパラメータを設定します :
batch_size = 100 num_classes = 10 epochs = 20
MNIST データセット
最初にデータセットを作成しますが、Torch Vision と NumPy 経由の 2 通りでデータを取得する方法を示します :
TorchVision
TorchVision を利用する場合には、torchvision.datasets.MNIST で直接データセットを作成できます :
import torchvision # MNIST Dataset ds_train = torchvision.datasets.MNIST(root='./data', train=True, transform=torchvision.transforms.ToTensor(), download=True) ds_test = torchvision.datasets.MNIST(root='./data', train=False, transform=torchvision.transforms.ToTensor()) print("dataset created successfully.")
NumPy
NumPy データを得るために、便宜的に tf.keras.datasets を利用してデータをロードします :
import tensorflow as tf # the data, split between train and test sets (x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data() x_train = x_train.reshape(60000, 784).astype('float32') x_test = x_test.reshape(10000, 784).astype('float32') # 正規化 x_train /= 255 x_test /= 255 y_train = y_train.astype('long') y_test = y_test.astype('long')
そして .from_numpy でデータセットを作成します :
ds_train = data.TensorDataset(torch.from_numpy(x_train), torch.from_numpy(y_train)) ds_test = data.TensorDataset(torch.from_numpy(x_test), torch.from_numpy(y_test))
データローダ
データセットから data.DataLoader でデータローダが作成できます。
データセットを TorchVision から作成した場合でも NumPy 経由で作成した場合でも同じです :
dataloader_train = data.DataLoader(dataset=ds_train, batch_size=batch_size, shuffle=True) dataloader_test = data.DataLoader(dataset=ds_test, batch_size=batch_size, shuffle=False)
モデル定義
nn.Module を継承して MLP モデルを定義します。
特に難しい話しはないのですが、完全結合層は Dense ではなく Linear になります。
Dropout 層も追加しました :
class MLPModel (nn.Module): def __init__(self): super(MLPModel, self).__init__() self.fc1 = nn.Linear(784, 512) self.fc2 = nn.Linear(512, 512) self.fc3 = nn.Linear(512, num_classes) self.dropout1 = nn.Dropout2d(0.2) self.dropout2 = nn.Dropout2d(0.2) def forward(self, x): x = F.relu(self.fc1(x)) x= self.dropout1(x) x = F.relu(self.fc2(x)) x = self.dropout2(x) return self.fc3(x) # return F.log_softmax(self.fc3(x))
モデルをインスタンス化して device に転送します :
model = MLPModel().to(device)
インスタンスを直接プリントすると、層の情報が得られます :
print(model)
Out:
MLPModel( (fc1): Linear(in_features=784, out_features=512, bias=True) (dropout1): Dropout2d(p=0.2) (fc2): Linear(in_features=512, out_features=512, bias=True) (dropout2): Dropout2d(p=0.2) (fc3): Linear(in_features=512, out_features=10, bias=True) )
損失関数と optimizer
損失関数としては nn.CrossEntropyLoss() を使用します。
nn.NLLLoss() を使用する場合には、モデル定義の forward 関数で F.log_softmax(self.fc3(x)) を返す必要があります :
criterion = nn.CrossEntropyLoss() #criterion = nn.NLLLoss()
optimizer は何でも良いですが、ここでは SGD を使用しましょう :
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.5) #optimizer = optim.Adam(model.parameters(), lr=0.01)
訓練コード (per epoch)
各エポックの訓練コードです。
バッチは最初に device に転送します。また、TorchVision 経由で得たバッチの images の shape は (100, 1, 28, 28) ですので、reshape してやります (NumPy 経由でデータセットを作成した場合にはこの操作は必要ありません)。
100 ステップ毎に損失値を表示してやります :
def train(epoch): model.train() steps = len(ds_train)//batch_size for step, (images, labels) in enumerate(dataloader_train, 1): 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() if step % 100 == 0: print ('Epoch [%d/%d], Step [%d/%d], Loss: %.4f' % (epoch, epochs, step, steps, loss.item()))
評価コード (per epoch)
各エポックの最後にテスト・データセットで評価します。
単純に正解数をカウントしています :
def eval(): model.eval() correct = 0 total = 0 with torch.no_grad(): for (images, labels) in dataloader_test: images, labels = images.view(-1, 28*28).to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) correct += (predicted == labels).sum().item() total += labels.size(0) print("Val Acc : %.4f" % (correct/total))
訓練の実行
次のループで訓練が実行できます :
for epoch in range(1, epochs+1): train(epoch) eval()
モデルは pickle でセーブできます :
torch.save(model.state_dict(), 'model.pkl')
実行時出力です :
Epoch [1/20], Step [100/600], Loss: 2.1855 Epoch [1/20], Step [200/600], Loss: 1.9285 Epoch [1/20], Step [300/600], Loss: 1.1925 Epoch [1/20], Step [400/600], Loss: 0.8519 Epoch [1/20], Step [500/600], Loss: 0.8498 Epoch [1/20], Step [600/600], Loss: 0.5263 Val Acc : 0.8605 Epoch [2/20], Step [100/600], Loss: 0.4407 Epoch [2/20], Step [200/600], Loss: 0.5408 Epoch [2/20], Step [300/600], Loss: 0.5178 Epoch [2/20], Step [400/600], Loss: 0.4496 Epoch [2/20], Step [500/600], Loss: 0.3611 Epoch [2/20], Step [600/600], Loss: 0.4713 Val Acc : 0.9018 ... Epoch [20/20], Step [100/600], Loss: 0.1126 Epoch [20/20], Step [200/600], Loss: 0.1069 Epoch [20/20], Step [300/600], Loss: 0.0975 Epoch [20/20], Step [400/600], Loss: 0.0664 Epoch [20/20], Step [500/600], Loss: 0.0999 Epoch [20/20], Step [600/600], Loss: 0.1520 Val Acc : 0.9699
TensorBoard の利用
tensorboardX モジュールを使用すれば、TensorBoard が利用できます。
SummaryWriter をインスタンス化して訓練/評価ルーチンに渡してやります :
from tensorboardX import SummaryWriter writer = SummaryWriter() for epoch in range(1, epochs+1): train(epoch, writer) eval(epoch, writer) writer.close()
訓練ルーチンでは、便宜上、グローバルステップをカウントする global_step を追加して、SummaryWriter の .add_scalar() メソッドで損失を書き込みます :
global_step = 0 def train(epoch, writer): model.train() steps = len(ds_train)//batch_size for step, (images, labels) in enumerate(dataloader_train, 1): global global_step global_step += 1 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() if step % 100 == 0: print ('Epoch [%d/%d], Step [%d/%d], Loss: %.4f' %(epoch, epochs, step, steps, loss.item())) writer.add_scalar('train/train_loss', loss.item() , global_step)
評価ルーチンはエポック毎ですので、epoch カウントを利用して検証精度を書き込みます :
def eval(epoch, writer): model.eval() correct = 0 total = 0 with torch.no_grad(): for (images, labels) in dataloader_test: images, labels = images.view(-1, 28*28).to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) correct += (predicted == labels).sum().item() total += labels.size(0) print("Val Acc : %.4f" % (correct/total)) writer.add_scalar('eval/val_acc', correct*100/total, epoch)
以上