とりあえずPyTorchで遊んでみた

#はじめに

この記事ではWindowsでのPyTorchのインストールから、Irisの分類を**ざっくり**やっていきます。

#関連リンク
[PyTorch v0.4 導入](https://www.pytry3g.com/entry/2018/04/27/225711)
[PyTorch-NLPを使ってみる](https://www.pytry3g.com/entry/2018/07/08/145922)
[skorch入門](https://www.pytry3g.com/entry/skorch-tutorial)

#環境

- python 3.6.2
- Anaconda 4.3.27
- Windows10

#PyTorch

ざっくり説明すると、Facebookが開発していてChainerの様にコードが書け、
Tensorflowとは異なり研究向けのディープラーニングライブラリです。

#Install
※2018年7月12日追記
**Windowsも公式からインストールができるようになりました。**

Windowsは[公式](http://pytorch.org/)からはインストールできないので、[Anaconda Cloud](https://anaconda.org/peterjc123/pytorch)から、
コマンドプロンプトを開いて下のようにインストールします。

```shell
conda install -c peterjc123 pytorch
```

※追記
Bash on Windowsにインストールする方法
[Bash on WindowsでPyTorchを使えるようにする。](https://qiita.com/pytry3g/items/235653085d2bce5d47a9)

#Iris

ここからはPyTorchを使ったIrisデータセットの分類を**ざっくり**書いていきます。

##データセット
Irisデータセットとは150件の花のデータセットで、setosa, versicolor, virginicaという3つのクラスに分類されていて、それぞれがく片(Sepal)と花弁(Petal)の長さと幅の４つの特徴量をもっています。

##データセットの準備

scikit-learnからデータを読み込んだあと正規化し、訓練データとテストデータに分けます。
~~後ほど説明しますが、~~誤差関数にはCrossEntropyLossを使います。CrossEntropyLossの入力の型はFloatTensor, 出力はLongTensorである必要があります。出力のtargetの型はnp.int32になっているので、targetの型をnp.int64に変換します。詳しいことは[こちら](http://pytorch.org/docs/master/tensors.html)を参考にしてください。
target(クラス)はone-hotではなくラベルで与えます。

```python
# Load dataset...
iris_dataset = datasets.load_iris()

data = iris_dataset.data
# Normalization...
data = sp.stats.zscore(data, axis=0)
# 0 -> setosa
# 1 -> versicolor
# 2 -> virginica
target = iris_dataset.target

# np.int64 -> Longtensor
target = target.astype(np.int64)

# Split data into train and test set...
# Training size -> 120
# Test size -> 30
train_X, test_X, train_Y, test_Y = train_test_split(data, target, test_size=0.2)
```


##モデル
入力層、隠れ層、出力層からなるニューラルネットワークです。
Chainerの様にモデルを書くことができます。

```python

class NeuralNetwork(nn.Module):
    def __init__(self, n_in, n_units, n_out):
        super(NeuralNetwork, self).__init__()
        self.l1 = nn.Linear(n_in, n_units)
        self.l2 = nn.Linear(n_units, n_out)

    def forward(self, x):
        h = F.relu(self.l1(x))
        y = self.l2(h)
        return y
```


##学習

訓練データを使ってミニバッチ学習をします。
学習部分もChainerの様に書けます。

```python

# Training...
for epoch in range(epochs):
    train_X, train_Y = shuffle(train_X, train_Y)
    for i in range(n_batch):
        start = i * batch_size
        end = start + batch_size
        x = Variable(torch.FloatTensor(train_X[start:end]))
        t = Variable(torch.LongTensor(train_Y[start:end]))
        optimizer.zero_grad()
        y = model(x)
        loss = criterion(y, t)
        loss.backward()
        optimizer.step()
```


##モデルのテスト

テストデータを使って学習したモデルのテストをします。
テストデータをFloatTensorにした後、Variableに変換します。
それをモデルに渡すことでモデルからの出力を得られます。
torch.maxを使うことにより一つの入力に対する出力の中で最も高い値のindexを得ることができ、それがモデルの予測したラベルとなります。詳しいことは[こちら](http://pytorch.org/docs/master/torch.html?highlight=torch%20max#torch.max)を参考してください。


```python
# Test...
test_case = Variable(torch.FloatTensor(test_X), requires_grad=False)
result = model(test_case)
predicted = torch.max(result, 1)[1]
print("{:.2f}".format(sum(p == t for p, t in zip(predicted.data, test_Y)) / len(test_X)))
```

40epochで９割を超える正答率を得ることができました。

```:結果
0.93
```

#全体のコード

```python
import argparse
import torch
import numpy as np
import matplotlib.pyplot as plt
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as O
import scipy.stats as sp
from torch.autograd import Variable
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.utils import shuffle


class NeuralNetwork(nn.Module):
    def __init__(self, n_in, n_units, n_out):
        super(NeuralNetwork, self).__init__()
        self.l1 = nn.Linear(n_in, n_units)
        self.l2 = nn.Linear(n_units, n_out)

    def forward(self, x):
        h = F.relu(self.l1(x))
        y = self.l2(h)
        return y


parser = argparse.ArgumentParser()
parser.add_argument('--batchsize', '-b', type=int, default=20,
                    help="Number of minibatch size...")
parser.add_argument('--epochs', '-e', type=int, default=40,
                    help="Number of epochs...")
parser.add_argument('--learning_rate', '-lr', type=float, default=0.1,
                    help="Learning rate...")
args = parser.parse_args()
batch_size = args.batchsize
epochs = args.epochs
learning_rate = args.learning_rate

# Load dataset...
iris_dataset = datasets.load_iris()
# data type -> numpy.ndarray
data = iris_dataset.data
# Normalization...
data = sp.stats.zscore(data, axis=0)
# 0 -> setosa
# 1 -> versicolor
# 2 -> virginica
target = iris_dataset.target

# np.float32 -> FloatTensor
# np.int64 -> Longtensor
target = target.astype(np.int64)

# Split data into train and test set...
# Training size -> 120
# Test size -> 30
train_X, test_X, train_Y, test_Y = train_test_split(data, target, test_size=0.2)

n_in = len(data[0])
n_units = 5
n_out = len(iris_dataset.target_names)
n_batch = len(train_X) // batch_size
history = {"loss": []}

# Model + Loss + Optimizer
model = NeuralNetwork(n_in, n_units, n_out)
criterion = nn.CrossEntropyLoss()
optimizer = O.Adam(model.parameters(), lr=learning_rate)

# Training...
for epoch in range(epochs):
    train_X, train_Y = shuffle(train_X, train_Y)
    for i in range(n_batch):
        start = i * batch_size
        end = start + batch_size
        x = Variable(torch.FloatTensor(train_X[start:end]))
        t = Variable(torch.LongTensor(train_Y[start:end]))
        optimizer.zero_grad()
        y = model(x)
        loss = criterion(y, t)
        loss.backward()
        optimizer.step()
    history["loss"].append(loss.data[0])

#print(history["loss"][-1])
plt.title("loss")
plt.plot(range(epochs), history["loss"])
plt.show()


# Test...
test_case = Variable(torch.FloatTensor(test_X), requires_grad=False)
result = model(test_case)
predicted = torch.max(result, 1)[1]
print("{:.2f}".format(sum(p == t for p, t in zip(predicted.data, test_Y)) / len(test_X)))

```