1. fukuit

    Posted

    fukuit
Changes in title
+PyTorchでMNIST
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,355 @@
+
+さて、PyTorchである。
+Keras+TensorFlowに不満は何もないけれど、会社で使わせてもらっているPCはCPUがAVX命令に対応してないせいで、もう```pip install tensorflow```で最新版をインストールしても動作しなくなっちゃってる[^1]。
+だったら、この機会にPyTorchの書き方も覚えてみるか、くらいの軽いイキオイで。流行ってるしね。
+
+[^1]: version 1.5.0までは動作するので、```pip install -U tensorflow=1.5.0```する。
+
+## PyTorchのインストール
+
+CUDAを使えない環境では、PyPIで簡単にインストールできる。
+
+```bash
+% pip install -U torch torchvision
+```
+
+### Tutorialを参照する
+
+何はなくとも、[チュートリアル](https://pytorch.org/tutorials/)である。[公式のチュートリアルのNeural Networkのところ](https://pytorch.org/tutorials/beginner/blitz/neural_networks_tutorial.html)を見ると、「Chainerに似ている」と言われるのもなんとなく理解できる。
+
+## MNISTを実行
+
+MNISTを実装してみるにあたって、[公式のCIFAR10のチュートリアル](https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html)を参考にする。
+
+### MNISTデータのダウンロード
+
+Chainerでいう```chainer.datasets.mnist.get_mnist(withlabel=True, ndim=3)```とか、Kerasでいう```keras.datasets.mnist.load_data()```に相当するヤツがPyTorchにもある。
+[torchvision.datasetsのドキュメント](https://pytorch.org/docs/stable/torchvision/datasets.html#mnist)参照。
+
+ちなみに、いつも気軽に使っているMNISTは、[THE MNIST DATABASEof handwritten digits](http://yann.lecun.com/exdb/mnist/)というサイトが一時配布元である。
+
+- [train-images-idx3-ubyte.gz](http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz): training set images (9912422 bytes)
+- [train-labels-idx1-ubyte.gz](http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz): training set labels (28881 bytes)
+- [t10k-images-idx3-ubyte.gz](http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz): test set images (1648877 bytes)
+- [t10k-labels-idx1-ubyte.gz](http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz): test set labels (4542 bytes)
+
+ということで、ダウンロードしておく。こういう時、```lynx```と```wget```を使う派。
+
+```bash
+% brew install lynx wget
+% `lynx -dump http://yann.lecun.com/exdb/mnist/ | grep "gz$" | sed -e 's/[4-7]\./wget/'`
+```
+
+あ、これ面倒くさいヤツだ。やっぱり、素直に```torchvision.datasets.MNIST()```を使うことにする。
+これで、データがダウンロードされ、```data```フォルダに保存され、```transform```を通じて正規化する。
+
+```
+import torch
+import torchvision
+import torchvision.transforms as transforms
+import numpy as np
+
+transform = transforms.Compose(
+ [transforms.ToTensor(),
+ transforms.Normalize((0.5, ), (0.5, ))])
+trainset = torchvision.datasets.MNIST(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.MNISTs(root='./data',
+ train=False,
+ download=True,
+ transform=transform)
+testloader = torch.utils.data.DataLoader(testset,
+ batch_size=4,
+ shuffle=False,
+ num_workers=2)
+
+classes = tuple(np.linspace(0, 9, 10, dtype=np.uint8))
+
+```
+
+この```transforms.Compose(...)```で定義したフィルタを使って、MNISTの各ピクセルの値の数値の範囲を0〜1から、-1〜1の範囲の分布になるように変換する(らしい)。
+
+とりあえず、```torchvision.transforms.Normalize()```のドキュメントを見てみると、次のようになっている。
+
+> class torchvision.transforms.Normalize(mean, std)
+> Normalize a tensor image with mean and standard deviation. Given mean: ```(M1,...,Mn)``` and std: ```(S1,..,Sn)``` for n channels, this transform will normalize each channel of the input torch.*Tensor
+
+-1〜1の範囲で、平均0.5、標準偏差0.5になるように正規化してある、と。[公式のCIFAR10のチュートリアル](https://pytorch.org/tutorials/beginner/blitz/cifar10_tutorial.html)に、そう書いてある。
+
+> The output of torchvision datasets are PILImage images of range [0, 1]. We transform them to Tensors of normalized range [-1, 1].
+
+### NeuralNetの定義
+
+以前に書いた「[Keras(+Tensorflow)でMNISTしてみる](https://qiita.com/fukuit/items/b3fa460577a0ea139c88)」と同じmodelを作ることにしようかな。
+
+```python
+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(1, 32, 3) # 28x28x32 -> 26x26x32
+ self.conv2 = nn.Conv2d(32, 64, 3) # 26x26x64 -> 24x24x64
+ self.pool = nn.MaxPool2d(2, 2) # 24x24x64 -> 12x12x64
+ self.dropout1 = nn.Dropout2d()
+ self.fc1 = nn.Linear(24 * 24 * 64, 128)
+ self.dropout2 = nn.Dropout2d()
+ self.fc2 = nn.Linear(128, 10)
+
+ def forward(self, x):
+ x = F.relu(self.conv1(x))
+ x = self.pool(F.relu(self.conv2(x)))
+ x = self.dropout1(x)
+ x = x.view(-1, 24 * 24 * 64)
+ x = F.relu(self.fc1(x))
+ x = self.dropout2(x)
+ x = self.fc2(x)
+ return x
+
+```
+
+これは、```net.py```というファイル名で保存しておく。
+
+```python
+from net import Net
+
+net = Net()
+```
+
+### loss関数と最適化の設定をする
+
+公式ドキュメントがCrossEntroyとSGDを使う設定になっているので、ソレに習う。
+
+```python
+import torch.optim as optim
+
+criterion = nn.CrossEntropyLoss()
+optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
+```
+
+[loss functionsのドキュメント](https://pytorch.org/docs/stable/nn.html#loss-functions)によると、MSELoss()とかももちろんある。
+
+[optim.SGD()のドキュメント](https://pytorch.org/docs/stable/_modules/torch/optim/sgd.html)を見ると、```learning rate```以外には、```momentum```とか```weight_decay```とか```dampening```とか```nesterov```とか設定できる。
+深い意味はないけれど、ちょっと設定を変えてみるか。
+
+```python
+import torch.optim as optim
+
+criterion = nn.CrossEntropyLoss()
+optimizer = optim.SGD(net.parameters(), lr=0.0005, momentum=0.99,
+=True)
+```
+
+### 学習する
+
+scikit系の関数や、あるいは```Keras```とかだったら、```fit()```を呼ぶところだけど、pytorchでは、そうはいかないらしい。
+
+```python
+for epoch in range(epochs):
+ running_loss = 0.0
+ for i, (inputs, labels) in enumerate(trainloader, 0):
+ # 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('[{:d}, {:5d}] loss: {.3f}'
+ .format(epoch + 1, i + 1, running_loss / 2000))
+ running_loss = 0.0
+
+print('Finished Training')
+```
+
+こんなの、自分で考えて書けるようになると思えないな。
+
+### 学習結果の確認
+
+先に定義したtestloaderを使う。
+
+```python
+correct = 0
+total = 0
+
+with torch.no_grad():
+ for (images, labels) in testloader:
+ outputs = net(images)
+ _, predicted = torch.max(outputs.data, 1)
+ total += labels.size(0)
+ correct += (predicted == labels).sum().item()
+print('Accuracy: {:.2f} %%'.format(100 * float(correct/total)))
+```
+
+結果は、次のようになった。
+
+```
+Accuracy: 96.98 %%
+```
+
+[Keras+TensorFlowでこの間書いたCNN](https://qiita.com/fukuit/items/b3fa460577a0ea139c88)と比べると、まあまあ悪くない。
+
+## 課題
+
+さて、Kerasで書いた時みたいに、```history```みたいなのをどうやって保存できるんだろうか?
+
+----
+
+## 本日のコード
+
+- https://github.com/fukuit/Python_SelfLearning/blob/master/pytorch_lesson/mnist/net.py
+
+```python:net.py
+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(1, 32, 3)
+ self.conv2 = nn.Conv2d(32, 64, 3)
+ self.pool = nn.MaxPool2d(2, 2)
+ self.dropout1 = nn.Dropout2d()
+ self.fc1 = nn.Linear(9216, 128)
+ self.dropout2 = nn.Dropout2d()
+ self.fc2 = nn.Linear(128, 10)
+
+ def forward(self, x):
+ x = F.relu(self.conv1(x))
+ x = self.pool(F.relu(self.conv2(x)))
+ x = self.dropout1(x)
+ x = x.view(-1, 9216)
+ x = F.relu(self.fc1(x))
+ x = self.dropout2(x)
+ x = self.fc2(x)
+ return x
+```
+
+- https://github.com/fukuit/Python_SelfLearning/blob/master/pytorch_lesson/mnist/mnist.py
+
+```python:mnist.py
+'''
+PyTorch MNIST sample
+'''
+import argparse
+import time
+import numpy as np
+
+import torch
+import torch.nn as nn
+import torch.nn.functional as F
+from torch.utils.data import DataLoader
+import torchvision
+import torchvision.transforms as transforms
+from torchvision.datasets import MNIST
+import torch.optim as optim
+
+from net import Net
+
+
+def parser():
+ '''
+ argument
+ '''
+ parser = argparse.ArgumentParser(description='PyTorch MNIST')
+ parser.add_argument('--epochs', '-e', type=int, default=2,
+ help='number of epochs to train (default: 2)')
+ parser.add_argument('--lr', '-l', type=float, default=0.01,
+ help='learning rate (default: 0.01)')
+ args = parser.parse_args()
+ return args
+
+
+def main():
+ '''
+ main
+ '''
+ args = parser()
+
+ transform = transforms.Compose(
+ [transforms.ToTensor(),
+ transforms.Normalize((0.5, ), (0.5, ))])
+
+ trainset = MNIST(root='./data',
+ train=True,
+ download=True,
+ transform=transform)
+ testset = MNIST(root='./data',
+ train=False,
+ download=True,
+ transform=transform)
+
+ trainloader = DataLoader(trainset,
+ batch_size=4,
+ shuffle=True,
+ num_workers=2)
+ testloader = DataLoader(testset,
+ batch_size=4,
+ shuffle=False,
+ num_workers=2)
+
+ classes = tuple(np.linspace(0, 9, 10, dtype=np.uint8))
+
+ # model
+ net = Net()
+
+ # define loss function and optimier
+ criterion = nn.CrossEntropyLoss()
+ optimizer = optim.SGD(net.parameters(),
+ lr=args.lr, momentum=0.95, nesterov=True)
+
+ # train
+ for epoch in range(args.epochs):
+ running_loss = 0.0
+ for i, (inputs, labels) in enumerate(trainloader, 0):
+ # 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('[{:d}, {:5d}] loss: {:.3f}'
+ .format(epoch+1, i+1, running_loss/2000))
+ running_loss = 0.0
+ print('Finished Training')
+
+ # test
+ correct = 0
+ total = 0
+ with torch.no_grad():
+ for (images, labels) in testloader:
+ outputs = net(images)
+ _, predicted = torch.max(outputs.data, 1)
+ total += labels.size(0)
+ correct += (predicted == labels).sum().item()
+ print('Accuracy: {:.2f} %%'.format(100 * float(correct/total)))
+
+
+if __name__ == '__main__':
+ start_time = time.time()
+ main()
+ print('elapsed time: {:.3f} [sec]'.format(time.time() - start_time))
+```
+
+うーん、PyTorch難しいぞ。