PyTorchでディープラーニング

PyTorchって何だ?

最近、Microsoft Azureによってサポートされ、とても身近になったPyTorch(パイトーチ)ですが、TensorFlow、KerasやChainerに続いて人気急上昇の注目すべき人工知能フレームワークとなっています。

Scikit-learnやTensorFlowと同様に、PyTorchもネット上で良質な情報に簡単にアクセスできるようになってきたため、コミュニティが非常に活発であることが理解できます。

PyTorchは、日本の機械学習ベンチャー企業であるPreferred Networks社で開発された人工知能フレームワークであり、Chainerの系統を継承しています。米Facebook社によってChainerからフォークされた新しいオープンソースの人工知能フレームワークとして2018年5月から提供が開始されました。

PyTorchは、Chainerの最大の特徴である「Define by Run」と呼ばれるアプローチが採用されています。「Define by Run」は、データを入力しながら計算グラフ(ネットワーク構造)を構築する方式であり、入力データの構造(配列や次元)に合わせて、計算グラフの計算方法を動的に変更することが可能です。このため、入力データの構造が異なる自然言語処理において有効な方法として一般的に認識されています。

一方、TensorFlowやKerasは、「Define and Run」と呼ばれるアプローチが採用されています。「Define and Run」は、計算グラフ(ネットワーク構造)をはじめに定義してからデータを流していきます。そのため、入力データの構造(配列や次元)が異なるケースに対応しにくいことがあります。

ただし、「Define and Run」が「Define by Run」より劣っているという議論には何の意味もありません。なぜならば、それぞれにメリットとデメリットがあるからです。「Define and Run」は、静的に計算グラフを作成するため最適化が行いやすく、「Define by Run」は、動的に計算グラフを作成できるが最適化が難しいといった側面があるからです。

PyTorchは、Numpyのような直感的な使いやすさ、GPU環境での実行のしやすさという観点においては、他のオープンソース機械学習フレームワークに比べて大きな利点です。

まずは、PyTorchを実際に使用してみて、体験的に理解していこうとおもいます。

PyTorchを動かしてみる

人工知能のトライアルでおなじみのIris(アヤメ)のデータを使用して、PyTorchで線形分類を実践していきます。PyTorchの線形分類のコードは、Pythonでコーディングしていきます。

まずは、モジュールpandas、Numpy、sklearnとPyTorchの関連ライブラリをインポートしていきます。

import pandas as pd
import numpy as np

from sklearn.model_selection import train_test_split

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

次にIris.csvファイルをpandasに読み込んで、説明変数と目的変数に分解します。また、sklearnのtrain_test_splitメソッドを使用して、訓練用データとテスト用データに分けていきます。

#アヤメデータのcSVをpandasで読み込み、データとラベルの変数に分解する。
csv = pd.read_csv('iris.csv')
data_csv = csv[['SepalLength','SepalWidth','PetalLength','PetalWidth']]
label_csv = csv[['Name']]

#アヤメはカテゴリとして整数で分類してnumpyのarrayに格納する。
Y = np.array(label_csv['Name'].astype('category').cat.codes).astype(int)
#データをnumpyのarrayに格納する。
X = np.array(data_csv)

#学習用データとテスト用データを分ける。
train_data,test_data,train_label,test_label = train_test_split(X,Y,test_size=0.3,random_state=0)

PyTorchは、torch.Tensorという行列に格納された説明変数と目的変数のデータを処理します。操作はとても簡単で、torch.Tensorメソッドに説明変数または目的変数を引数として与えるだけです。また、requires_grad_(True)を与えることで、勾配計算に使用する変数であるかどうかを定義することができます。

#データをPyTorchのテンソルに変換する。
train_x = torch.Tensor(train_data).requires_grad_(True)
test_x = torch.Tensor(test_data).requires_grad_(True)
train_y = torch.LongTensor(train_label).requires_grad_(False)
test_y = torch.LongTensor(test_label).requires_grad_(False)

次に計算グラフを定義します。まずはじめに計算グラフを定義するクラスを作成します。このクラスは、nn.Moduleをスーパークラスとして継承する必要があります。クラスのインスタンスが作成された時点で計算グラフの定義が設定され、インスタンスに説明変数のデータが渡された時点で計算グラフに従って学習が実行されます。

今回の学習ではミニバッチの設定はしておらず、単一データごとに逐次重みを更新していきます。データ数が少ない場合は、逐次学習(オンライン学習)でも問題はないとおもいます。

#勾配計算のオプション設定
optimizer = optim.Adam(model.parameters(), lr=0.02)
train_loss = []
train_accu = []
num_iter = 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.backward()
  train_loss.append(loss.data.item())
  #重み更新
  optimizer.step()

  prediction = torch.max(output, 1)[1]
  accuracy = prediction.eq(target.data).sum().numpy() / len(train_x)
  train_accu.append(accuracy)

  if num_iter % 10 == 0:
    print('学習ステップ: {}\t損失コスト: {:.3f}\t正解率: {:.3f}'.format(num_iter, loss.data.item(), accuracy)) 

  num_iter += 1

print('学習ステップ: {}\t損失コスト: {:.3f}\t正解率: {:.3f}'.format(num_iter, loss.data.item(), accuracy))

学習結果

学習ステップ: 0 損失コスト: 0.031 正解率: 0.981
学習ステップ: 10 損失コスト: 0.665 正解率: 0.829
学習ステップ: 20 損失コスト: 0.230 正解率: 0.914
学習ステップ: 30 損失コスト: 0.100 正解率: 0.943
学習ステップ: 40 損失コスト: 0.063 正解率: 0.971
学習ステップ: 50 損失コスト: 0.045 正解率: 0.990
学習ステップ: 60 損失コスト: 0.036 正解率: 0.981
学習ステップ: 70 損失コスト: 0.035 正解率: 0.981
学習ステップ: 80 損失コスト: 0.032 正解率: 0.990
学習ステップ: 90 損失コスト: 0.031 正解率: 0.981
学習ステップ: 100 損失コスト: 0.030 正解率: 0.981

100回の反復学習で正解率のピークは99.0%に達していますが、最終回では正解率が98.1%に下がってしまいました。

#推論モードに設定
model.eval()

outputs = model(Variable(test_x))
predicted = torch.max(outputs, 1)[1]
print('正解率: {:.3f}'.format(predicted.eq(test_y).sum().numpy() / len(predicted)))

正解率: 0.978

テスト用データによる正解率は97.8%となり、学習時の正解率と比較しても過学習などの影響はなく、問題のないレベルと考えることができます。

まとめ(感想)

このトライアルでは、PyTorchを使用してアヤメの線形分類を実践しました。

PyTorchのコーディングは非常に簡単というのが率直な感想です。また、PyTorchはGPUの利用も簡単に設定できるため、環境さえ準備できればビッグデータ使用したモデル学習も比較的簡単に実行できそうです。PyTorchを使うことでディープラーニングのハードルが一段下がったように感じます。

Microsoft社がAZURE上でPyTorchを正式にサポートしていますので、今後はクラウド上でPyTorchを利用した人工知能の開発が盛んにおこなわれることになるのではないでしょうか。

これから人工知能の開発に携わるエンジニアやディープラーニングを勉強したい方は、ぜひPyTorchにチャレンジしてみてはいかがでしょうか。

Python

前の記事

OpenCVで物体認識