【Python】Pythonで簡単Deep Learning入門

# はじめに
この記事は**2020/12/05**時点での情報を元にした記事です。
PyTorchは現在でも頻繁に更新がなされているため、この記事を含め、なるべく最新の記事を参照することをおすすめします。

最近人気上昇中のPytorchに触れる機会があったのでまとめてみました。
ニューラルネットワークは理論を学んでからでないとプログラムがほとんど読めないと思うので、<a href= "https://qiita.com/hatt0519/items/3188382ad85a8654cc42" >こちら</a>の方の記事とかを最初に見ることをお勧めします。
理論については今度、用語集のようなものか理論だけを説明する記事を作るつもりなので、お待ちください。
今回は内容が少し難しいので、小分けしたコードを説明していき、最後に全ソースコードを載せる方針でいきます。

pytorchの公式チュートリアルから引用している部分もあります。
かなりわかりやすく書いてあったので、そちらも見てみるといいかもしれませんね。
<a href= "https://pytorch.org/tutorials/" >PyTorch公式チュートリアル</a>
公式ドキュメントも他のライブラリに比べてわかりやすいと思うので、行き詰まったら参照してみてください（個人の感想です）
<a href= "https://pytorch.org/docs/stable/index.html" >PyTorch公式ドキュメント</a>
# 対象者
・Pythonがある程度書ける人
・ニューラルネットワークについての知識がある程度ある人
・PyTorchに触れてみたいけど、公式チュートリアルが英語で近寄りがたいと思ってる人
# 環境
python 3.8.5
scikit-learn 0.231
pytorch 1.7.0
#importするもの
私は今回jupyter labを使ったので一括importはしてないのですが、import類はここにまとめておきます。

```import.py
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
```

# データの準備
データ集めがめんどくさいので、今回はsklearnにあるirisのデータセットを使います。

```data.py
iris = datasets.load_iris()
```

この記事の最後で、学習したモデルの評価をしたいので、データを「訓練データ」と「評価データ」に分けておきます。
この辺の処理は他の機械学習手法でもお馴染みですね。

```split.py
X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.25 ,random_state=0)
```

次に、pytorchで扱えるように、tensor(テンソル)と呼ばれる行列のようなものに変換します。
(実際には行列とは少し異なりますが、今回の主題ではないのでスルーします)

```convert_tensor.py
x = torch.tensor(X_train,dtype = torch.float)
y = torch.tensor(y_train)
```

いよいよニューラルネットワークの構築です。
以下がネットワークの簡易的な図です。
irisデータセットのデータのラベルが4つ、分類先が3つなので入力層と出力層のユニット数はそれぞれ３と４になります。
隠れ層(中間層)は**２層**で、簡単のためにそれぞれ　10層にしました。
入力層(x)と隠れ層(h¹)、隠れ層(h¹)と隠れ層(h²)、隠れ層(h²)と出力層(y)の間でそれぞれの活性化関数が計算を行っています
![network1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/842766/9651ed53-2230-0247-9862-da25a19284bd.png)

今回のネットワークの活性化関数にはReluとsoftmaxを採用しました。
活性化関数はfc1,fc2,fc3とし、下記のように定義します。
他クラス分類なので、隠れ層(h²)〜出力層(y)間のみsoftmaxにしてみました。
ここには載せませんが、採用していない時に比べて損失関数が大幅に下がったので、正解だったようです。

```Neural_net.py
class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # x(入力)のユニット数は4 
        self.fc1 = nn.Linear(4, 10)
        # 隠れ層1のユニット数は10
        self.fc2 = nn.Linear(10, 10)
        # 隠れ層2のユニット数は10
        self.fc3 = nn.Linear(10, 3)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.log_softmax(self.fc3(x),dim=1)
        return x
```

ここからモデル、最適化アルゴリズム、損失関数のインスタンスを作成します。
最適化アルゴリズムには**SGD(確率的勾配降下法)**を用いていますが、ご存知の方はAdamなりを使ってみてもいいかもしれません。
今回、学習率は**0.01**としています。

```model.py
# 学習モデルのインスタンスを作成
model = Net()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 損失関数の定義
criterion = nn.CrossEntropyLoss()
```

学習の実行を行います。
エポック数(学習回数)は**5000回**ほどにしておきました。
エポック数が大きいほど損失関数を最小化する処理を行いますが、これを単純に大きくすれば損失関数が極端に小さくなるということでもありません。これについては様々な議論がされているので、興味のある方は調べてみてください。
このフェーズでは実際にニューラルネットワークに通して、損失関数を最小にするように計算します。

```learning.py
sum_loss = 0.0 
epoch = 5000
for i in range(1,epoch):
    # 勾配の初期化
    optimizer.zero_grad()
    # 説明変数xをネットワークにかける
    output = model(x)
    # 損失関数の計算
    loss = criterion(output, y)
    # 勾配の計算 
    loss.backward()
    # パラメタの更新
    optimizer.step()
    
    sum_loss += loss.item()
    if i % 1000 == 0:
        print("loss : {0}".format(sum_loss/i))
```

実行結果

```
loss : 0.44236164616048335
loss : 0.2636471394151449
loss : 0.1921398006466528
loss : 0.15398433133307846
success!
```

学習モデルの評価を行います。今回は整備されたデータセットを使っているので、精度が高くなっていますね。

```cross_validation.py
outputs = net(torch.tensor(X_test, dtype = torch.float))
_, predicted = torch.max(outputs.data, 1)
y_predicted = predicted.numpy()
accuracy = 100 * np.sum(predicted.numpy() == y_test) / len(y_predicted)
print('accuracy: {:.1f}%'.format(accuracy))
```

実行結果

```
accuracy: 97.4%
```

# ソースコード全文
```all.py
from sklearn import datasets
from sklearn.model_selection import train_test_split
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # x(入力)のユニット数は4 
        self.fc1 = nn.Linear(4, 10)
        # 隠れ層1のユニット数は10
        self.fc2 = nn.Linear(10, 10)
        # 隠れ層2のユニット数は10
        self.fc3 = nn.Linear(10, 3)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.log_softmax(self.fc3(x),dim=1)
        return x

iris = datasets.load_iris()

X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, test_size=0.25 ,random_state=0)

x = torch.tensor(X_train,dtype = torch.float)
y = torch.tensor(y_train)

# 学習モデルのインスタンスを作成
model = Net()
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 損失関数の定義
criterion = nn.CrossEntropyLoss()

sum_loss = 0.0 
epoch = 5000
for i in range(1,epoch):
    # 勾配の初期化
    optimizer.zero_grad()
    # 説明変数xをネットワークにかける
    output = model(x)
    # 損失関数の計算
    loss = criterion(output, y)
    # 勾配の計算 
    loss.backward()
    # パラメタの更新
    optimizer.step()

    sum_loss += loss.item()
    if i % 1000 == 0:
        print("loss : {0}".format(sum_loss/i))

outputs = net(torch.tensor(X_test, dtype = torch.float))
_, predicted = torch.max(outputs.data, 1)
y_predicted = predicted.numpy()
accuracy = 100 * np.sum(predicted.numpy() == y_test) / len(y_predicted)
print('accuracy: {:.1f}%'.format(accuracy))
```

# おわりに
どうでしたでしょうか。
内容的には中々に難しいとは思いますが、エポック数やユニット数、隠れ層(中間層)の数もまだまだ改善の余地があると思うので、興味が湧いた方はこれを元に改造してみてください。
初めてのPyTorchなので、記事内に間違っている箇所などがあれば、ズバズバ言っていただけると助かります。その都度修正させていただきます。
PyTorchを触ってみて、割と直感的に書けて楽しくなってきたので、近々また記事を書くかもしれません。