PyTorch (6) Convolutional Neural Network
今回は畳み込みニューラルネットワーク。MNISTとCIFAR-10で実験してみた。
MNIST
import numpy as np import torch import torch.nn as nn import torchvision.datasets as dsets import torchvision.transforms as transforms # Hyperparameters num_epochs = 10 batch_size = 100 learning_rate = 0.001 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(device)
今回からGPU対応した。PyTorchはKerasと違ってGPUモードにするために明示的にコーディングが必要。ここがちょっと面倒><
# 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を読み込むDataSetとDataLoaderを作成。前回(2018/2/4)と同じ。
class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.layer1 = nn.Sequential( nn.Conv2d(1, 16, kernel_size=5, padding=2), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(2)) self.layer2 = nn.Sequential( nn.Conv2d(16, 32, kernel_size=5, padding=2), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(2)) self.fc = nn.Linear(7 * 7 * 32, 10) def forward(self, x): out = self.layer1(x) out = self.layer2(out) out = out.view(out.size(0), -1) out = self.fc(out) return out
- CNNはブロック単位で処理した方がよいのでブロック(Conv+BN+ReLU+Pooling)ごとにまとめて
Sequential
を使うとわかりやすくなる。Kerasっぽく書けるのでいい! Conv2d
やBatchNorm2d
はKerasと違って入力と出力のユニットサイズを省略できない。- サイズを自分で計算するのが面倒ならば、モデルの途中結果サイズを
print(out.size())
で出力してみるとよい。
# テスト model = CNN().to(device) images, labels = iter(train_loader).next() print(images.size()) images = images.to(device) outputs = model(images)
torch.Size([100, 1, 28, 28])
モデルオブジェクトの作成。
model = CNN().to(device) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
GPUモードで動かすには
- モデルを
to(device)
でGPUに転送する - テンソルデータも
to(device)
でGPUに転送する
の2つだけ実装すればよい。Kerasより面倒だけど意外に簡単。
print(model)
するといい感じで構造が表示される。
CNN( (layer1): Sequential( (0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2)) (1): BatchNorm2d(16, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (layer2): Sequential( (0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2)) (1): BatchNorm2d(32, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True) (2): ReLU() (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (fc): Linear(in_features=1568, out_features=10, bias=True) )
次は訓練ループ。これまでとほとんど同じ。
def train(train_loader): model.train() running_loss = 0 for batch_idx, (images, labels) in enumerate(train_loader): images = images.to(device) labels = labels.to(device) 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.to(device) labels = labels.to(device) outputs = model(images) loss = criterion(outputs, labels) running_loss += loss.item() predicted = outputs.max(1, keepdim=True)[1] correct += predicted.eq(labels.view_as(predicted)).sum().item() total += labels.size(0) val_loss = running_loss / len(test_loader) val_acc = 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) # save the trained model np.save('loss_list.npy', np.array(loss_list)) np.save('val_loss_list.npy', np.array(val_loss_list)) np.save('val_acc_list.npy', np.array(val_acc_list)) torch.save(model.state_dict(), 'cnn.pkl')
Batch Normalizationなど学習時と推論時で挙動が変わるレイヤを使う場合はモデルのモードに要注意!
model.train()
で訓練モードmodel.eval()
で評価モード
に切り替える必要がある。切り替えなくてもエラーにはならないが性能が出なかったりする。Kerasにはなかったので忘れやすい!
また先に書いたようにGPUモードで動かすときはテンソルを下のように to(device)
でGPUに送る必要がある!
images = images.to(device) labels = labels.to(device)
GPUで動かすと下のようになった。
epoch 0, loss: 0.1682 val_loss: 0.0545 val_acc: 0.9819 epoch 1, loss: 0.0498 val_loss: 0.0398 val_acc: 0.9865 epoch 2, loss: 0.0386 val_loss: 0.0330 val_acc: 0.9886 epoch 3, loss: 0.0292 val_loss: 0.0351 val_acc: 0.9887 epoch 4, loss: 0.0248 val_loss: 0.0296 val_acc: 0.9902 epoch 5, loss: 0.0203 val_loss: 0.0320 val_acc: 0.9894 epoch 6, loss: 0.0179 val_loss: 0.0342 val_acc: 0.9886 epoch 7, loss: 0.0144 val_loss: 0.0309 val_acc: 0.9894 epoch 8, loss: 0.0121 val_loss: 0.0285 val_acc: 0.9912 epoch 9, loss: 0.0109 val_loss: 0.0361 val_acc: 0.9898
前回、多層パーセプトロンでの精度が85%くらいだったので99.3%出るCNNはすごく性能がよいことがわかる。
CIFAR-10
次は同じことをCIFAR-10でやってみよう。
import numpy as np 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 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(device)
num_epochs = 30 batch_size = 128 transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # [0, 1] => [-1, 1] ]) train_set = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=4) test_set = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size, shuffle=False, num_workers=4) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
- CIFAR-10のデータは画像のピクセルが [0, 1] ではなく、[-1, 1] になるように標準化している。
num_workers
を指定するとファイルの読み込みが並列化される(CPUのコアが複数ある場合はすごく速くなる!)
いくつかサンプルを描画してみよう。
import matplotlib.pyplot as plt import numpy as np %matplotlib inline def imshow(img): # unnormalize [-1, 1] => [0, 1] img = img / 2 + 0.5 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[:16], labels[:16] imshow(torchvision.utils.make_grid(images, nrow=4, padding=1)) plt.axis('off')
いい感じ。
参考のチュートリアルに従ってCNNの簡単なモデルを定義してみた。上のMNISTよりシンプルという・・・
class CNN(nn.Module): def __init__(self): super(CNN, self).__init__() self.conv1 = nn.Conv2d(3, 6, kernel_size=5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, kernel_size=5) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x
ReLUは nn.ReLU()
で層として定義する場合もあるが、パラメータがないので F.relu()
のように関数として使うこともできる。層で書いておくとモデルを print
したときに表示される(さっきの例)。
model = CNN().to(device)
print(model)
CNN( (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1)) (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) (fc1): Linear(in_features=400, out_features=120, bias=True) (fc2): Linear(in_features=120, out_features=84, bias=True) (fc3): Linear(in_features=84, out_features=10, bias=True) )
この表示方法はシンプルだがとてもわかりやすい。
あとはこれまでと同じ。
criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) def train(train_loader): model.train() running_loss = 0 for i, (images, labels) in enumerate(train_loader): images, labels = images.to(device), labels.to(device) 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 i, (images, labels) in enumerate(test_loader): images, labels = images.to(device), labels.to(device) outputs = model(images) loss = criterion(outputs, labels) running_loss += loss.item() predicted = outputs.max(1, keepdim=True)[1] correct += predicted.eq(labels.view_as(predicted)).sum().item() total += labels.size(0) val_loss = running_loss / len(test_loader) val_acc = 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) print('Finished training') # save the trained model np.save('loss_list.npy', np.array(loss_list)) np.save('val_loss_list.npy', np.array(val_loss_list)) np.save('val_acc_list.npy', np.array(val_acc_list)) torch.save(model.state_dict(), 'cnn.pkl')
結果をプロットしてみよう。
import numpy as np 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()
今回はチュートリアルのシンプルなCNNなので63%くらい。あとでチューニングしてみよう!
参考
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使おう!
参考
PyTorch (4) Logistic Regression
次は〜ロジスティック回帰(Logistic Regression)!ロジスティック回帰は、回帰とつくけど分類のアルゴリズムで、隠れ層がなく、活性化関数にシグモイド関数(2クラス分類のとき)、ソフトマックス関数(多クラス分類のとき)を使ったニューラルネットとしてモデル化できる。IrisとMNIST(Notebook参照)のデータセットで実装してみた。
Irisデータセット
import torch import torch.nn as nn import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler
PyTorchとともにscikit-learnの関数もいろいろ活用するのでインポート。
# hyperparameters input_size = 4 num_classes = 3 num_epochs = 10000 learning_rate = 0.01
Irisデータセットは特徴量が4つ(sepal length、sepal width、petal length、petal width)なので入力ユニット数は4にした。またクラス数が3つ(Setosa、Versicolour、Virginica)なので出力ユニット数は3にした。
iris = load_iris() X = iris.data y = iris.target print(X.shape) # (150, 4) print(y.shape) # (150, )
データのロードはscikit-learnのload_iris()関数で簡単にできる。辞書で返ってくるが data
でデータ本体が target
でクラスラベルが取得できる。クラスラベルは1-of-Kになっていないので注意!PyTorchはクラスラベルを自分で1-of-Kに変換しなくてもクラスラベルのまま扱える。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=5) print(X_train.shape) # (100, 4) print(X_test.shape) # (50, 4) print(y_train.shape) # (100, ) print(y_test.shape) # (50, )
訓練データとバリデーションデータに分割。これもscikit-learnの関数を使えば簡単にできる。
# データの標準化 scaler = StandardScaler() X_train = scaler.fit_transform(X_train) X_test = scaler.transform(X_test) # print(np.mean(X_train, axis=0)) # [ -2.47274423e-15 3.85247390e-16 -4.26603197e-16 -7.66053887e-17] # print(np.std(X_train, axis=0)) # [ 1. 1. 1. 1.]
データの各特徴量ごとに平均0、標準偏差1になるようにデータを標準化する。Irisデータでは標準化しなくても学習はできたけどやったほうが学習が安定すると思う。print
してみると4つの特徴量それぞれで平均が0、標準偏差が1になってるのがわかる。
class LogisticRegression(nn.Module): def __init__(self, input_size, num_classes): super(LogisticRegression, self).__init__() self.linear = nn.Linear(input_size, num_classes) def forward(self, x): out = self.linear(x) return out
ロジスティック回帰モデルの定義。見た目は線形回帰のときとまったく同じ。実際、多クラスのロジスティック回帰は linear
を通したあとに softmax
を通すのだがPyTorchはモデルには含めないでロジットをそのまま返すのが流儀のようだ。なぜかというと損失関数を計算する torch.nn.CrossEntropyLoss
の中に softmax
の計算が含まれているため。
なぜこんな仕様なのか?と思って調べてみたら softmax
が必要なのは訓練時の損失計算のときだけで、推論時には必要ないので入れない方が効率がよいとのこと。推論時はそもそも softmax
を通してわざわざ確率にしなくてもロジットのまま大小比較ができるためだ。
Kerasだとモデルの最後に softmax
の活性化関数も含めてモデル出力は確率にしていた。こんな感じで。
# Kerasの例 model = Sequential() model.add(Dense(512, activation='relu', input_shape=(784,))) model.add(Dropout(0.2)) model.add(Dense(512, activation='relu')) model.add(Dropout(0.2)) model.add(Dense(num_classes, activation='softmax')) <= ここ!
なので推論のときもforwardするだけで確率で出てきた。
上のようなPyTorchのモデルだとforwardの出力は確率になってないので要注意!確率にしたいときは自分で nn.functional.softmax()
を使う必要がある。上のForumにもあるようにPyTorchの動的グラフの特性をいかして訓練時と推論時で分けるのもよいかもね。
if self.training: # code for training else: # code for inference
次はモデルのオブジェクトを作ってlossとoptimizerを定義。
model = LogisticRegression(input_size, num_classes) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
確認のためnn.CrossEntropyLoss()
のソースコードを見てみると softmax
がちゃんと含まれているのが確認できる。
def cross_entropy(input, target, weight=None, size_average=True, ignore_index=-100, reduce=True): return nll_loss(log_softmax(input, 1), target, weight, size_average, ignore_index, reduce)
次はいよいよ訓練ループ!ここは前回と大体同じ。
def train(X_train, y_train): inputs = torch.from_numpy(X_train).float() targets = torch.from_numpy(y_train).long() optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step() return loss.item() def valid(X_test, y_test): inputs = torch.from_numpy(X_test).float() targets = torch.from_numpy(y_test).long() outputs = model(inputs) val_loss = criterion(outputs, targets) # 精度を求める _, predicted = torch.max(outputs, 1) correct = (predicted == targets).sum().item() val_acc = float(correct) / targets.size(0) return val_loss.item(), val_acc loss_list = [] val_loss_list = [] val_acc_list = [] for epoch in range(num_epochs): perm = np.arange(X_train.shape[0]) np.random.shuffle(perm) X_train = X_train[perm] y_train = y_train[perm] loss = train(X_train, y_train) val_loss, val_acc = valid(X_test, y_test) if epoch % 1000 == 0: 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)
いくつか注意点
- エポックごとにデータをシャッフルする
- PyTorchではデータは
FloatTensor
でラベルはLongTensor
にする必要がある。Irisデータの特徴量はfloat64 (double)
型になっているため テンソルをfloat()
でキャストする必要がある。ラベルはもともとint64 (long)
型なのでキャストは不要だったが念のためlong()
でキャスト criterion = nn.CrossEntropyLoss
に渡す正解ラベルは 1-of-Kにする必要がない! 0, 1, 2, 3というラベルのカテゴリのまま渡せる
最後にlossとaccのグラフ描くとこんな感じできれいに学習できているのが確認できた。
# 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.figure() plt.plot(range(num_epochs), val_acc_list, 'g-', label='val_acc') plt.legend()