化学系エンジニアがAIを学ぶ

PyTorchでディープラーニング、強化学習を学び、主に化学工学の問題に取り組みます

ニューラルネットワークで論理演算子 OR を学習する

はじめに

業務にて深層強化学習の利用を目指す。まずは簡単な例からということで、PyTorchを用いてニューラルネットワークで論理演算子 OR を学習するプログラムを作成する。

論理演算子 OR のモデル

0または1を持つ2つの入力x1, x2の少なくとも一方が1である場合に1を、そうでない場合に0を返すモデルを作成する。

(x1, x2) → model → 0 または1

具体的には、

(0, 0) → model → 0

(1, 0) → model → 1

(0, 1) → model → 1

(1, 1) → model → 1

必要なモジュールのインポート

import numpy
import torch
import torch.nn as nn
import torch.nn.functional as F
from matplotlib import pyplot as plt

ニューラルネットワークモデルの定義

入力2つ、出力は2つとしている。中間層は1層とし、そのノード数は3つとしている。出力は答えが0のときは出力の1つめが大きくなるように、答えが1のときは出力の2つめが大きくなるように、というように分類問題として扱う(後に示すとおり分類問題に合わせた損失関数を選ぶ必要がある)。中間層の活性化関数はReLUとし、出力層はl2の計算値をそのまま出力している。

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.l1 = nn.Linear(2, 3)
        self.l2 = nn.Linear(3, 2)

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

データの準備

torch.tensorとしてデータを準備するが、入力データx_trainはtorch.float型で、分類問題とするため出力データy_trainは整数型(long)にする必要がある。学習データx_trainy_trainは下記のパターンに対応する。

(0, 0) → model → 0

(1, 0) → model → 1

(0, 1) → model → 1

(1, 1) → model → 1

x_train = torch.tensor([[0,0],[0,1],[1,0],[1,1]], dtype=torch.float)
y_train = torch.LongTensor(numpy.array([0,1,1,1]))

損失関数、最適化関数の設定

損失関数と最適化関数を適宜選ぶ必要がある。最適化関数についてはlearning_rateを設定する必要がある。

model = Model() #モデル登録
criterion  = nn.CrossEntropyLoss() #損失関数 (クロスエントロピー誤差)
learning_rate = 1e-1
optimizer  = torch.optim.Adam(model.parameters(), lr=learning_rate) #最適化関数 (Adam)

学習

optimizer.zero_grad()をして、loss = criterion(y_model, y_train)でモデル計算値と答えの誤差を求め、 loss.backward()して、optimizer.step()を繰り返せばよい。

loss_log = [] #学習状況のプロット用
epoch = 100
for t in range(epoch):
    optimizer.zero_grad() #計算された勾配を0にリセットする
    y_model = model(x_train) #モデルの計算値算出
    loss = criterion(y_model, y_train) #モデル計算値と答えを比較し誤差を求める
    loss.backward() #誤差からバックプロパゲーション
    optimizer.step() #バックプロパゲーションから重み更新
    print(t, loss.item()) #各エポックで誤差を表示
    loss_log.append(loss.item()) #学習状況の記録

学習結果の確認

## 学習状況のプロット
plt.plot(loss_log)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.yscale('log')
plt.show()

##学習したモデルの正答率を出す
outputs = model(x_train) # 2つのnodeの出力のtensorが得られる。大きい方がモデルの出した答え
_, predicted = torch.max(outputs.data, 1) # modelの答えを0か1かのtensorに変換する
correct = (y_train == predicted).sum().item() # 正答数を求める。
print("正答率: {} %".format(correct/len(predicted)*100.0))

結果

学習状況

変化が小さくなるepoch数60回あたりで学習が十分できていると思われる。 ただし計算させるたびにlossの変化は変わるし、上で設定したlearning_rateの値次第でも変わる。

f:id:schemer1341:20190104001517p:plain:w500
図 学習状況(epoch数に対するlossの変化)

学習したモデルの正答率

正答率: 100.0 %

参考

全コード

# -*- coding: utf-8 -*-
import numpy
import torch
import torch.nn as nn
import torch.nn.functional as F
from matplotlib import pyplot as plt

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.l1 = nn.Linear(2, 3)
        self.l2 = nn.Linear(3, 2)

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

x_train = torch.tensor([[0,0],[0,1],[1,0],[1,1]], dtype=torch.float)
y_train = torch.LongTensor(numpy.array([0,1,1,1]))

model = Model() #モデル登録
criterion  = nn.CrossEntropyLoss() #損失関数 "criterion"の変数名とするのが一般的な模様
learning_rate = 1e-1
optimizer  = torch.optim.Adam(model.parameters(), lr=learning_rate) #最適化関数

loss_log = [] #学習状況のプロット用
epoch = 100
for t in range(epoch):
    optimizer.zero_grad() #計算された勾配を0にリセットする
    y_model = model(x_train) #モデルの計算値算出
    loss = criterion(y_model, y_train) #モデル計算値と答えを比較し誤差を求める
    loss.backward() #誤差からバックプロパゲーション
    optimizer.step() #バックプロパゲーションから重み更新
    print(t, loss.item()) #各エポックで誤差を表示
    loss_log.append(loss.item()) #学習状況の記録

plt.plot(loss_log)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.yscale('log')
plt.show()

##学習したモデルの正答率を出す
outputs = model(x_train) # 2つのnodeの出力のtensorが得られる。大きい方がモデルの出した答え
_, predicted = torch.max(outputs.data, 1) # modelの答えを0か1かのtensorに変換する
correct = (y_train == predicted).sum().item() # 正答数を求める。
print("正答率: {} %".format(correct/len(predicted)*100.0))