Blitzの最終回です。今回は、TRAINING A CLASSIFIERです。(以下翻訳です)
分類器の学習
さてこれまでのtutorialで、どのようにNeural Network (NN)が定義され、lossが計算され、重みが更新されるかを学んできました。いよいよnetworkの学習をしてみましょう。
Dataについて
一般に、画像、テキスト、音声や動画のdataを扱う場合、標準python packageを使って、dataをnumpy arrayにloadします。そしてこのarrayをtorch.*Tensorに変換します。
- 画像に対しては、PillowやOpenCV packageが有用です
- 音声に対しては、scipyやlibrosa packageが、
- テキストに対しては、直接PythonやCythonを用いたloadや、あるいはNLTKやSpaCyが便利です
特に画像系については、torchvisionと呼ばれるpackageがあり、Imagenet, CIFAR10, MNISTなど汎用datasetのdata loaderや画像のdata変換器、すなわちtorchvision.datasetsやdorch.utils.data.DataLoaderといったmethodが用意されています。
これは非常に便利かつ定型codeをいちいち書く手間が省けます。
このtutorialではCIFAR10 datasetを使います。飛行機、自動車、鳥、猫、鹿、犬、蛙、馬、船、truckといったclassがあります。CIFAR10は、3x32x32すなわち32×32のpixel sizeの3-channel color画像で構成されます。
cifar10
画像分類器の学習
以下の順に勉強していきます:
- torchvisionを使ってCIFAR10の学習およびtest datasetをloadし正規化する
- 畳み込みNNを定義する
- loss関数を定義する
- 学習dataを用いてnetworkを学習する
- Test dataを用いてnetworkをtestする
CIFAR10のloadと正規化
torchvisionを使うとCIFAR10のloadは超簡単です。
import torch import torchvision import torchvision.transforms as transforms
torchvisionから出力されたdatasetは、[0, 1]のdataで構成されるPILImage画像です。これを[-1, 1]に正規化したtensorに変換します。
Note: Windows上で動作させていてBrokenPipeErrorが出たら、torch.utils.data.DataLoader()のnum_workerを0にしてみてください。
transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
Out:
Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz Extracting ./data/cifar-10-python.tar.gz to ./data Files already downloaded and verified
文字ばかりでは面白くないので、学習dataを見てみましょう。
import matplotlib.pyplot as plt import numpy as np # functions to show an image def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) plt.show() # get some random training images dataiter = iter(trainloader) images, labels = dataiter.next() # show images imshow(torchvision.utils.make_grid(images)) # print labels print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
Out:
frog deer car plane
畳み込みNNの定義
前回のNN sectionからNNをcopyし、(前回は1-channel画像でしたが)3-channel画像に変更しましょう。
import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 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 net = Net()
Loss関数とoptimizerの定義
Cross-Entropy lossとmomentum付きSGD optimizerを分類器に適用しましょう。
import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
Networkの学習
ここからが面白いところです。dataを繰り返し取り込む単純なloopを組み、networkに入力を与え、最適化していきます。
for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # get the inputs; data is a list of [inputs, labels] inputs, labels = data # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 2000 == 1999: # print every 2000 mini-batches print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)) running_loss = 0.0 print('Finished Training')
Out:
[1, 2000] loss: 2.131 [1, 4000] loss: 1.813 [1, 6000] loss: 1.666 [1, 8000] loss: 1.568 [1, 10000] loss: 1.510 [1, 12000] loss: 1.445 [2, 2000] loss: 1.398 [2, 4000] loss: 1.372 [2, 6000] loss: 1.353 [2, 8000] loss: 1.332 [2, 10000] loss: 1.296 [2, 12000] loss: 1.274 Finished Training
学習済みmodelを保存しましょう:
PATH = './cifar_net.pth' torch.save(net.state_dict(), PATH)
PyTorchのmodelの保存の詳細はこちら。
Test dataを用いたnetworkのtest
学習用datasetを2回まわしてnetworkの学習を行いました。しかしnetworkがきちんと学習しているか確認する必要があります。
確認は、NNが出力する推定class labelと正解labelを比較することによって行います。推定が正しければ、正解試行としてlistに追加します。
それではまず、test用datasetから画像を読み取り表示してみましょう。
dataiter = iter(testloader) images, labels = dataiter.next() # print images imshow(torchvision.utils.make_grid(images)) print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
Out:
GroundTruth: cat ship ship plane
次に、先ほど保存したmodelを再loadしましょう。(実際には保存したり再loadしたりする必要はありませんが、どうやるのか示すためです)
net = Net() net.load_state_dict(torch.load(PATH))
これらの画像に対してNNが何だと思っているのかを見てみましょう:
outputs = net(images)
出力は10 classのenergy(確率)を示します。あるclassに対してより高いenergyであるならば、networkはその画像がその特定のclassであると思っていることになります。では最も高いenergyのindexを見てみましょう:
_, predicted = torch.max(outputs, 1) print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
Out:
Predicted: cat ship ship ship
なかなか良い結果ですね。
ではdataset全体に対してnetworkがどのように動作するか見てみましょう。
correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print('Accuracy of the network on the 10000 test images: %d %%' % ( 100 * correct / total))
Out:
Accuracy of the network on the 10000 test images: 54 %
当てずっぽう(10 classからrandomに1 class選ぶ)だと10%の正解確率になりますから、それよりも随分良い結果ですね。Networkは何かを学習しているようです。
どのclassが良く推定できていて、どのclassが悪いのでしょうか:
class_correct = list(0. for i in range(10)) class_total = list(0. for i in range(10)) with torch.no_grad(): for data in testloader: images, labels = data outputs = net(images) _, predicted = torch.max(outputs, 1) c = (predicted == labels).squeeze() for i in range(4): label = labels[i] class_correct[label] += c[i].item() class_total[label] += 1 for i in range(10): print('Accuracy of %5s : %2d %%' % ( classes[i], 100 * class_correct[i] / class_total[i]))
Out:
Accuracy of plane : 64 % Accuracy of car : 52 % Accuracy of bird : 48 % Accuracy of cat : 44 % Accuracy of deer : 35 % Accuracy of dog : 23 % Accuracy of frog : 74 % Accuracy of horse : 55 % Accuracy of ship : 73 % Accuracy of truck : 69 %
では次は何をしましょうか?
これらのNNをGPUで走らせるにはどうしたら良いのでしょうか?
GPUでの学習
TensorをGPUに転送したように、NNをGPUに転送できます。
まず動作させるdeviceとして、一つ目のcuda deviceを定義してみましょう。(CUDAが利用可能である前提です)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # Assuming that we are on a CUDA machine, this should print a CUDA device: print(device)
Out:
cuda:0
以下deviceはCUDA device前提です。
このとき、次のmethodがすべてのmoduleを再帰的に確認し、変数とbufferをCUDA tensorに変換してくれます:
net.to(device)
ただし、入力や目標値も毎stepGPUに送る必要があります:
inputs, labels = data[0].to(device), data[1].to(device)
どうしてCPUと比較して顕著に早くなったように見えないかって?それはnetworkがとても小さいからです。
練習: Networkのchannel幅を大きくしてみましょう。(一つ目のnn.Conv2dの第2引数と二つ目のnn.Conv2dの第1引数を大きくします – 同じ値を入れます)速度の向上が見えましたか?
達成したこと:
- PyTorchのTensor libraryとNNの概要の理解
- 小さなNNでの学習と画像分別
複数GPUsでの学習
もっと速度の向上を見たかったら、複数GPUを使ってみましょう。
こちらOption: Dataの並列処理を参照ください。
次は何をしましょうかね ?
- Train neural nets to play video games
- Train a state-of-the-art ResNet network on imagenet
- Train a face generator using Generative Adversarial Networks
- Train a word-level language model using Recurrent LSTM networks
- More examples
- More tutorials
- Discuss PyTorch on the Forums
- Chat with other users on Slack
全scriptの実行時間: ( 2 minutes 25.029 seconds)
さいごに
一通り画像の分類をやってみました。プログラム中に「何これ ? 」というのがいくつもあったかも知れませんが、簡単なプログラムを書いて、中身を確認すると良いですね。PyTorchでNNの学習やtestを行うための大枠はわかったかなーという感じです。引き続きPyTorchを使って行きましょう !