1. fukuit

    No comment

    fukuit
Changes in body
Source | HTML | Preview
@@ -1,355 +1,370 @@
さて、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```みたいなのをどうやって保存できるんだろうか?
+### そして疑問
+
+学習の過程で、以下のようにして誤差逆伝播をしているのだけれど、どうしてこれで、```net = Net()```で定義したネットワークに反映されているのかがまだ理解できていない。
+
+```python
+outputs = net(inputs)
+loss = criterion(outputs, labels)
+loss.backward()
+optimizer.step()
+```
+
+この```loss.backward()```と```optimizer.step()で、どうして次のepochで```net(inputs)```による```outputs```の値が変化するのだろう。```optimizier```がインスタンスになる時には、```net.parameters()```が引数として渡されているけれど、それにしても```loss```とは特に何かのfunction()を通してやりとりしていないような気がしてしまうし。
+
+ここは、よく解説を読んでみようと思う。
+
----
## 本日のコード
- 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難しいぞ。