Smile Engineering Blog

ジェイエスピーからTipsや技術特集、プロジェクト物語を発信します

PyTorch超入門!

PyTorchを始めてみよう!

普段、TensorFlowで深層学習のソースを書いてた私……のはずなのですが、いろいろあってPyTorchを勉強する羽目(?)になってしまいました。が、

そもそもPyTorchなんてこれまでまったく触ったことないよ〜!!

これではあかん!!ということで、今回は勉強がてらPyTorchの超かんたんなプログラム(100ステップ未満!!)を書いてみることにしました。

そのお題とは、、PyTorchで論理演算!!!

f:id:jspnet:20191215214748p:plain

……って、なんてリソースの無駄使いなのでしょう(汗)

話の経緯

1. OSSでTensorFlow2.0はいろいろあった件

とあるOSSをTensorFlow2.0を使って書いていたのですが、そのレビュー中(?)に某(Uで始まる)Linuxディストリビューションの偉い方に、こんな指摘を受けたわけです。

TensorFlow2.0ってpipを19.0以上に上げないとダメなの!??

はい。その通りです。(うん知ってた……)

まぁPythonなので、venvとかAnacondaとか使えば影響はなさそうに見えますが、よりにもよってこのOSSは常駐アプリを想定していたわけで、それだとあかんでしょ!!となったわけです。やりようによっては回避策はあるとは思いますが、それでも利便性を考慮すると……ですね。

というわけで、制約が(TensorFlowに比べたら)少ないPyTorchを使おうか……という話になったわけです。。。

2. Chainer開発停止!??な件

こちらはお仕事のお話。いつもお世話になってる研究員の方はおよそChainerを使って仕事をされてたのですが、、、

Chainerが開発停止してPyTorchへ移行!??

という話が突如出てきたわけでございます。

preferred.jp

いろいろ感慨深いものがありますね……

というわけで、やっぱしPyTorchを勉強しよう!という話になったわけでしたとさ。

そしたらさっそくPyTorch使ってみるよ!!

今回はとりあえず使い方を覚えるということを最優先にしますので、ごくごく簡単なプログラムを書いてみようと思います。そのお題は、、、

論理演算 AND & OR & XOR をPyTorchで計算してみよう

です。ぶっちゃけ本来は1行の計算式で書けてしまう内容ですが、お勉強が最優先なので、細かいことは抜きにして、ひとまず書いてみましょう!

一番下にソース全文を貼り付けておきますが、全部で100ステップ未満の簡単なスクリプトなので手軽に試せると思います。(私の環境ではPyTorch1.3を使用)

その前に、、、PyTorchのインストール方法については環境によっていろいろ異なります。
下記のPyTorch公式サイトでご確認ください。

pytorch.org

ソースコードを書いてみるよ〜

データの用意

学習用データ

"""
学習データリスト
(入力) (論理演算種別 one-hot)
       AND OR XOR
0, 0,  1,  0, 0    => 0 と 0 の論理積
"""
train_list = [
    # 論理積 (AND)
    [0., 0., 1., 0., 0.],  # 0
    [0., 1., 1., 0., 0.],  # 0
    [1., 0., 1., 0., 0.],  # 0
    [1., 1., 1., 0., 0.],  # 1
    # 論理和 (OR)
    [0., 0., 0., 1., 0.],  # 0
    [0., 1., 0., 1., 0.],  # 1
    [1., 0., 0., 1., 0.],  # 1
    [1., 1., 0., 1., 0.],  # 1
    # 排他的論理和 (XOR)
    [0., 0., 0., 0., 1.],  # 0
    [0., 1., 0., 0., 1.],  # 1
    [1., 0., 0., 0., 1.],  # 1
    [1., 1., 0., 0., 1.]   # 0
]

学習時のデータとして、論理積論理和排他的論理和用のデータ各4つずつ、計12個のデータを用意しました。
あくまでPyTorch勉強用なので、過学習などについては考慮しません!(きっぱし)

  • 各データのインデックス0と1が、入力データ
  • 各データのインデックス2〜4が、論理積 or 論理和 or 排他的論理和(のワンホットベクトル)

となっています。

学習用(答え)データ

"""
答えリスト
※上の学習データリストの答えをリスト化したもの
"""
answer_list = [
    [0.], [0.], [0.], [1.],  # 論理積の答え
    [0.], [1.], [1.], [1.],  # 論理和の答え
    [0.], [1.], [1.], [0.]   # 排他的論理和の答え
]

こちらは答えのリストです。※画像識別のラベルに当たる部分
リストの入れ子にすることを忘れずに!! ←私は最初忘れて学習が進まなくて焦った(汗)

PyTorchは入力データにエラーがあってもとりあえず止まらずに動き出してしまうようなので、エラーっぽい何かが表示されたら必ず確認するようにしましょう!!

モデル定義

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.dense_1 = nn.Linear(5, 32)
        self.dense_2 = nn.Linear(32, 32)
        self.dense_3 = nn.Linear(32, 1)

    def forward(self, x):
        x = F.relu(self.dense_1(x))
        x = F.relu(self.dense_2(x))
        x = self.dense_3(x)
        return x

モデル定義はPytthonクラスで作成しました。全3層の(テキトーな)モデルです。
この辺りはChainerやTensorFlowのEagerモードとあまり変わりはないですね。
特にChainerとは近いものを感じます。

学習部

# モデル定義
model = Model()

# 誤差関数設定 ※二乗誤差
criterion = nn.MSELoss()

# 最適化の設定
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 学習リストを変換
x_list = torch.Tensor(train_list)
y_list = torch.Tensor(answer_list)

# 学習開始 ※500エポック
model.train()
for i in range(500):
    # 学習データを入力して損失値を取得
    output = model(x_list)
    loss = criterion(output, y_list)

    # 勾配を初期化してBackProp
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    print("[%d] loss: %f" % (i, loss))

続いて学習部です。(というより本スクリプトのメイン)

およそChainerやTensorFlowのEgarモードとあまり変わらない気がしますが、ひとつ「おっ」と感じたのは、誤差関数の設定について。PyTorchではエポックで回す前に最初に定義しておくこともできるんですね。

評価部

# 評価
print("----- Test -----")
model.eval()
for x_data in train_list:
    x_in = torch.Tensor(x_data)
    y_out = model(x_in)

    if x_data[2] == 1.:
        y_str = "and"
    elif x_data[3] == 1.:
        y_str = "or"
    else:
        y_str = "xor"
    print("%d %s %d = %f" % (x_data[0], y_str, x_data[1], y_out))

最後に評価部。ここについては説明不要でしょうか。

こんな感じの出力結果

[0] loss: 0.399621
[1] loss: 0.273303
 〜中略〜
[499] loss: 0.000271
----- Test -----
0 and 0 = -0.043499
0 and 1 = 0.014589
1 and 0 = 0.029351
1 and 1 = 0.990595
0 or 0 = -0.001442
0 or 1 = 0.999004
1 or 0 = 0.997085
1 or 1 = 0.997021
0 xor 0 = 0.009579
0 xor 1 = 0.991775
1 xor 0 = 0.997622
1 xor 1 = 0.000943

まぁ〜およそ合ってる?

まとめ

Chainerと同様、Define by Runで動くので、直感的にソースコードが書けてしまう印象がありますね。
PyTorchは研究用途で使われることも多いそうで、今後も発展が期待されます。

ソースコード全文

最後に本日紹介したソースコード全文を貼り付けておきます。

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

"""
学習データリスト
(入力) (論理演算種別 one-hot)
       AND OR XOR
0, 0,  1,  0, 0    => 0 と 0 の論理積
"""
train_list = [
    # 論理積 (AND)
    [0., 0., 1., 0., 0.],  # 0
    [0., 1., 1., 0., 0.],  # 0
    [1., 0., 1., 0., 0.],  # 0
    [1., 1., 1., 0., 0.],  # 1
    # 論理和 (OR)
    [0., 0., 0., 1., 0.],  # 0
    [0., 1., 0., 1., 0.],  # 1
    [1., 0., 0., 1., 0.],  # 1
    [1., 1., 0., 1., 0.],  # 1
    # 排他的論理和 (XOR)
    [0., 0., 0., 0., 1.],  # 0
    [0., 1., 0., 0., 1.],  # 1
    [1., 0., 0., 0., 1.],  # 1
    [1., 1., 0., 0., 1.]   # 0
]

"""
答えリスト
※上の学習データリストの答えをリスト化したもの
"""
answer_list = [
    [0.], [0.], [0.], [1.],  # 論理積の答え
    [0.], [1.], [1.], [1.],  # 論理和の答え
    [0.], [1.], [1.], [0.]   # 排他的論理和の答え
]


class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.dense_1 = nn.Linear(5, 32)
        self.dense_2 = nn.Linear(32, 32)
        self.dense_3 = nn.Linear(32, 1)

    def forward(self, x):
        x = F.relu(self.dense_1(x))
        x = F.relu(self.dense_2(x))
        x = self.dense_3(x)
        return x


# モデル定義
model = Model()

# 誤差関数設定 ※二乗誤差
criterion = nn.MSELoss()

# 最適化の設定
optimizer = optim.SGD(model.parameters(), lr=0.1)

# 学習リストを変換
x_list = torch.Tensor(train_list)
y_list = torch.Tensor(answer_list)

# 学習開始 ※500エポック
model.train()
for i in range(500):
    # 学習データを入力して損失値を取得
    output = model(x_list)
    loss = criterion(output, y_list)

    # 勾配を初期化してBackProp
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    print("[%d] loss: %f" % (i, loss))

# 評価
print("----- Test -----")
model.eval()
for x_data in train_list:
    x_in = torch.Tensor(x_data)
    y_out = model(x_in)

    if x_data[2] == 1.:
        y_str = "and"
    elif x_data[3] == 1.:
        y_str = "or"
    else:
        y_str = "xor"
    print("%d %s %d = %f" % (x_data[0], y_str, x_data[1], y_out))