少し前に書いたKerasでのhello world的なコードをpytorchへ移植してみたときのメモ。
MNISTの文字認識のあれです。
準備
インストールしたものは、torch / torchvision / pillow / tqdm / matplotlibの5つです。全部pip installからインストールできました。
コード
いろいろネットをさまよいながらコピペしながら移植したので、いろんなコードの張り合わせになってます。やはりKerasよりコードは長くなっちゃいますね。
基本的に用語が同じなので、まぁなんとなくで移植はできした。学習時間が長いので、学習からその学習データの保存までと、読み込みから推論するまでの構成に分けました。
from tqdm import tqdm
import matplotlib.pyplot as plt
from PIL import Image
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
def train_model(model, train_loader, criterion, optimizer, device='cpu'):
train_loss = 0.0
num_train = 0
correct = 0
# 学習モデルに変換
model.train()
# 進捗の表示
pbar = tqdm(train_loader)
for i, (images, labels) in enumerate(pbar):
# batch数をカウント
num_train += len(labels)
images, labels = images.to(device), labels.to(device)
# 勾配を初期化
optimizer.zero_grad()
# 推論(順伝播)
outputs = model(images)
# 推論の結果を取得
preds = outputs.argmax(dim=1)
# 損失の算出
loss = criterion(outputs, labels)
# 誤差逆伝播
loss.backward()
# パラメータの更新
optimizer.step()
# lossを加算
train_loss += loss.item()
# accuracyを加算
correct += (preds == labels).sum()
# lossの平均値を取る
train_loss = train_loss / num_train
# 正解率を取る
accuracy = correct / num_train
return train_loss, accuracy
def test_model(model, test_loader, criterion, device='cpu'):
test_loss = 0.0
num_test = 0
correct = 0
# modelを評価モードに変更
model.eval()
with torch.no_grad(): # 勾配計算の無効化
for i, (images, labels) in enumerate(test_loader):
num_test += len(labels)
images, labels = images.to(device), labels.to(device)
outputs = model(images)
preds = outputs.argmax(dim=1)
correct += (preds == labels).sum()
loss = criterion(outputs, labels)
test_loss += loss.item()
test_loss = test_loss / num_test
accuracy = correct / num_test
return test_loss, accuracy
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_accuracy = train_model(model, train_loader, criterion, optimizer, device=device)
test_loss, test_accuracy = test_model(model, test_loader, criterion, device=device)
print("epoch : {}, train_accuracy : {:.5f}, test_accuracy : {:.5f}" .format(epoch, train_accuracy, test_accuracy))
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
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 20, 5, padding="same"),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Dropout(0.25)
)
self.layer2 = nn.Sequential(
nn.Conv2d(20, 50, 5, padding="same"),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Dropout(0.25)
)
self.flaten1 = nn.Flatten()
self.layer3 = nn.Sequential(
nn.Linear(7*7*50, 256),
nn.Sigmoid(),
nn.Linear(256, 128),
nn.Sigmoid(),
nn.Dropout(0.2),
nn.Linear(128, 10),
nn.Softmax()
)
def forward(self, x): # x : 入力
x = self.layer1(x)
x = self.layer2(x)
x = self.flaten1(x)
x = self.layer3(x)
return x
#訓練データ
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)
batch_size = 128
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)
model = Net()
print(model)
# print(list(model.parameters()))
# 学習に使うパラメータ
optimizer = optim.Adam(model.parameters(), lr=0.01)
criterion = nn.CrossEntropyLoss()
num_epochs = 5
train_loss_list, test_loss_list = lerning(model, train_loader, test_loader, criterion, optimizer, num_epochs)
# 学習データの保存
torch.save(model.state_dict(), "./mnist.pth")
LossとかAccuracyも自分で計算しないといけないんですね。TorchEvalっていう別ライブラリはあるみたいですが。
これを実行すると
100%|██████████| 469/469 [00:28<00:00, 16.41it/s]
epoch : 1, train_accuracy : 0.85400, test_accuracy : 0.96820
epoch : 1, train_loss : 0.01261, test_loss : 0.01181
100%|██████████| 469/469 [00:32<00:00, 14.58it/s]
epoch : 2, train_accuracy : 0.95360, test_accuracy : 0.96230
epoch : 2, train_loss : 0.01180, test_loss : 0.01185
100%|██████████| 469/469 [00:30<00:00, 15.47it/s]
epoch : 3, train_accuracy : 0.94738, test_accuracy : 0.96850
epoch : 3, train_loss : 0.01185, test_loss : 0.01180
100%|██████████| 469/469 [00:32<00:00, 14.39it/s]
epoch : 4, train_accuracy : 0.94225, test_accuracy : 0.96400
epoch : 4, train_loss : 0.01188, test_loss : 0.01184
100%|██████████| 469/469 [00:38<00:00, 12.21it/s]
epoch : 5, train_accuracy : 0.94200, test_accuracy : 0.95290
epoch : 5, train_loss : 0.01189, test_loss : 0.01193
なんとなくうまくいっているように見えます。1epoch目から全然伸びない傾向も似てます。ただ正答率がKerasで作ってた時よりちょっと低いなぁ。何かミスってるかな。
続いて、訓練データの先頭6つからの推論
# 学習データの読み込み
model.load_state_dict(torch.load("./mnist.pth", map_location=torch.device('cpu')))
# test data 6枚だけ画像の表示と推論
fig = plt.figure()
for i in range(6):
image, label = test_dataset[i]
image = image.view(1, 1, 28, 28).to('cpu')
# 推論
prediction_label = torch.argmax(model(image))
print('label : {}\n Prediction : {}'.format(label, prediction_label))
fig.add_subplot(2,3,i+1)
plt.imshow(image.detach().to('cpu').numpy().reshape(28, 28), cmap = "gray")
なんかはまったのが、image.view(1,1,28,28)の記述。4次元のテンソルにしておかないとflattenのところで引っかかりました。訓練時には1次元目にbatch数が入るみたいなのですが、推論時にはそれが消えていて掛け算のサイズが合わないよ。って怒られました。
自分で手書きした外にある画像ファイル(28x28のグレースケールのTiff)からの推論
# 画像データの読み込みとテンソル化
to_tensor = transforms.ToTensor()
testimage = to_tensor(Image.open("testdata.tif")).unsqueeze(0)
# 推論
predict = torch.argmax(model(testimage))
print("predict: {}".format(predict))
plt.imshow(testimage.detach().to('cpu').numpy().reshape(28, 28), cmap = "gray")
なのでここでもunsqueezeで次元追加。
まぁ忘れないようにメモしておきます。
コメント