Deep Karmaning

技術系の話から日常のことまで色々と書きます

Pytorchでニューラルネットを構築して多クラス分類を試みる

概要

今回はPytorchでニューラルネットでの多クラス分類を試してみました。

Pytorchはkerasやchainerに並ぶDeep Learningフレームワークです。特に研究用途でよく使われているようです。

Pytorchに関しては以下の記事が詳しいので是非参考にしてください。

www.procrasist.com

ウェブには画像データを使ったMNISTをやってる記事はいくつか見つかる一方で、もっと簡単なデータで試した事例がなかったのでirisデータを使って多クラス分類のニューラルネットをやってみようというのがモチベーションです。

MNISTで参考になった記事は以下。

www.madopro.net

qiita.com

コード

今回はirisデータを使いますので、まずはirisデータを読み込みます。 一旦データフレームにしているのは、個人的に加工しやすい形だと感じるからです。

import pandas as pd
from sklearn import datasets

iris = datasets.load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['target'] = iris.target_names[iris.target]

次にクラスのラベルを数値に変換したり、 学習データと検証データに分けて、型をPytorchで扱えるものに変換します。

import torch
import numpy as np

#ラベルを数値化
y = np.array(df['target'].astype('category').cat.codes).astype(float)
X = np.array(df.iloc[:, :4])

#学習データと検証データを分割
from sklearn.model_selection import train_test_split
train_X, val_X, train_y, val_y = train_test_split(
    X, y, test_size = 0.2, random_state=71)


# tensor型に変換
train_X = torch.Tensor(train_X)
val_X = torch.Tensor(val_X)
train_y = torch.LongTensor(train_y)
val_y = torch.LongTensor(val_y)

次にネットワークの定義です。

import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable

torch.manual_seed(71) #seed固定、ネットワーク定義前にする必要ありそう

class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(4, 100)
        self.fc2 = nn.Linear(100, 50)
        self.fc3 = nn.Linear(50, 3)

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

model = Net()
print(model)

以下のように出力されると思いますが、

Net(
  (fc1): Linear(in_features=4, out_features=100, bias=True)
  (fc2): Linear(in_features=100, out_features=50, bias=True)
  (fc3): Linear(in_features=50, out_features=3, bias=True)
)

シンプルな3層のニューラルネットワークです。 活性化関数はreluを使っていて最後の出力の活性化関数がsoftmaxです。

学習は以下で行います。

ここではデータを流すときにmini batchは作っていないのですが、本来はbatch毎にデータを流す方がいいのかもしれないです(ご指摘ください)。

#学習
import torch.optim as optim

optimizer = optim.SGD(model.parameters(), lr=0.02)
train_loss = []
train_accu = []
i = 0

model.train() #学習モード
for epoch in range(100):
    data, target = Variable(train_X), Variable(train_y)#微分可能な型
    optimizer.zero_grad() #勾配初期化
    output = model(data) #データを流す
        
    loss = F.nll_loss(output, target) #loss計算
    loss.backward()    #バックプロパゲーション
    train_loss.append(loss.data.item())
    optimizer.step()   # 重み更新
        
    prediction = output.data.max(1)[1] #予測結果
    accuracy = prediction.eq(target.data).sum().numpy() / len(train_X) #正解率
    train_accu.append(accuracy)
    
    if i % 10 == 0:
        print('Train Step: {}\tLoss: {:.3f}\tAccuracy: {:.3f}'.format(i, loss.data.item(), accuracy))
    i += 1
    
print('Train Step: {}\tLoss: {:.3f}\tAccuracy: {:.3f}'.format(i, loss.data.item(), accuracy))

結果は以下のように出て、stepが進むと精度が上がっていく様子が見られます。 学習がうまくいっていそうです。

Train Step: 0    Loss: 1.152 Accuracy: 0.317
Train Step: 10  Loss: 0.954 Accuracy: 0.683
Train Step: 20  Loss: 0.835 Accuracy: 0.683
Train Step: 30  Loss: 0.711 Accuracy: 0.683
Train Step: 40  Loss: 0.616 Accuracy: 0.692
Train Step: 50  Loss: 0.547 Accuracy: 0.717
Train Step: 60  Loss: 0.496 Accuracy: 0.783
Train Step: 70  Loss: 0.457 Accuracy: 0.867
Train Step: 80  Loss: 0.425 Accuracy: 0.917
Train Step: 90  Loss: 0.399 Accuracy: 0.925
Train Step: 100 Loss: 0.377 Accuracy: 0.942

最後に検証用データで精度を確認します。

#精度検証
model.eval() #推論モード

outputs = model(Variable(val_X))
_, predicted = torch.max(outputs.data, 1)
print('Accuracy: {:.3f}'.format(predicted.eq(val_y).sum().numpy() / len(predicted)))

正解率は、

Accuracy: 0.867

いい感じに学習できていますね!

今回使ったコードの全体は以下になります。

pytorch_nueral_net.ipynb · GitHub

所感

やはりkerasよりも少し難しいと思いました。

kerasではネットワークを定義してデータをfitさせれば完了ですが、Pytorchはネットワークを定義したあとにデータを流して、ロスを計算してパラメータ更新して等の処理を明示的に書く必要があります(結局用意された関数実行するだけなのでそんなに手間ではないのですが)。そこが慣れるまで大変そうかなと思います。

ただ一方でkerasよりも自由度はあると思いますし、最新の論文がPytorchで実装されていたりするということで、使いこなす価値はあると思うというのが個人的な印象です。

それでは間違い等ありましたらご指摘お願いいたします。