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

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

ニューラルネットワークによるばらつきのあるデータへの近似

はじめに

前回は関数値そのものを学習させたが、今回は関数値をもとに作成したばらつきのあるデータを学習させて 関数をニューラルネットワークで表現できるかを見てみる。

以下の記事で同様の内容がtensorflowを用いて紹介されているが、 ここではPyTorchを使う。

ニューラルネットワークは任意の関数を表現できるのか? - Qiita

データの準備

ここでの関数はnumpy.sin(x)としている。xの値はランダムに選んだ。yの値は関数値に、正規分布に従う 乱数を加えてデータをばらつかせた。

## 必要なモジュールのインポート
import numpy
import torch
import torch.nn as nn
import torch.nn.functional as F
from matplotlib import pyplot as plt

## データ作成
data_num = 200
x = numpy.random.rand(data_num)*6 # 0 - 6 の範囲の一様乱数
y = numpy.sin(x) + numpy.random.randn(data_num)*0.3 # 正規分布に従う乱数を足す
x_train = torch.FloatTensor(x.reshape(data_num, 1))
y_train = torch.FloatTensor(y.reshape(data_num, 1))

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

入力1つ、出力1つ。中間層は1層とし、ノード数は2個とした。 中間層の活性化関数はReLUとし、出力層はl2の計算値をそのまま出力させた。。

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

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

ニューラルネットワークモデルの登録

損失関数に平均二乗誤差nn.MSELossを使用した。learning_rateは試行錯誤して 良さそうなところに決めた。

model = Model()
criterion = nn.MSELoss()
learning_rate = 1e-1
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

学習

epoch数は試行錯誤して設定したが、計算の初期値次第で学習状況は多少変わる模様。

epoch = 100
for t in range(epoch):
    optimizer.zero_grad()
    y_model = model(x_train)
    loss = criterion(y_model, y_train)
    loss.backward()
    optimizer.step()
    print(t, loss.item()) # 学習状況の表示

学習結果の確認

plt.plot(x, y, '.', label='data')
x = numpy.linspace(0, 6, data_num)
x_model = torch.FloatTensor(x.reshape(data_num, 1))
y_model = model(x_model).detach().numpy().reshape(1, data_num)[0]
plt.plot(x, y_model, label='model', linewidth=3)
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.show()

結果

計算させるたびに多少結果が変わるが、下図のようにだいぶ無理矢理な感じとなった。epoch数を増やしてもほとんど状況に変化なかった。

f:id:schemer1341:20190109234003p:plain:w500
図1 データとモデル値の比較

なお、活性化関数をsigmoidに変えると無理矢理感は低下した。

f:id:schemer1341:20190109235006p:plain:w500
図2 活性化関数をsigmoidとした場合のデータとモデル値の比較

さらに中間層を2層(ノード数 20 ,40)、epoch数=1000で計算するとフィット感は改善した。

f:id:schemer1341:20190110000954p:plain:w500
図3 中間層数、epoch数を増やした場合のデータとモデル値の比較

epoch数を5000回まで増やすと合わせすぎた感じとなり、いわゆる過学習状態になっていると思われる。。

f:id:schemer1341:20190110001406p:plain:w500
図4 epoch数をさらに増やした場合のデータとモデル値の比較

所感

epoch数、中間層ノード数、層の深さなど、適切に選ぶにはそこそこの知識・経験が要りそう。

参考

ニューラルネットワークは任意の関数を表現できるのか? - Qiita

メモ: PyTorch TensorDataset、DataLoader について

はじめに

PyTorchのtorch.utils.data.TensorDatasettorch.utils.data.DataLoaderの使い方についてのメモを記す。

torch.utils.data.TensorDataset

同じ要素数の2つのtensorを渡し、その組を得る。

import numpy
import torch
import torch.utils.data

x = numpy.array([[1], [2], [3], [4], [5], [6]])
y = numpy.array([[10], [20], [30], [40], [50], [60]])
x = torch.tensor(x)
y = torch.tensor(y)
dataset = torch.utils.data.TensorDataset(x, y)

中身をそのまま表示させると、TensorDatasetというオブジェクトであることが示される。

>>> dataset
<torch.utils.data.dataset.TensorDataset object at 0x7f4e91fdca20>

[ ]で要素を取り出せる。Tensorの組がタプルになっている。

>>> dataset[0]
(tensor([1.]), tensor([10.]))

>>> for x, y in dataset:
...     print(x, y)
... 
tensor([1.]) tensor([10.])
tensor([2.]) tensor([20.])
tensor([3.]) tensor([30.])
tensor([4.]) tensor([40.])
tensor([5.]) tensor([50.])
tensor([6.]) tensor([60.])

torch.utils.data.DataLoader

torch.utils.data.DataLoaderにTensorDatasetを渡すと得られ、ミニバッチを返すiterableなオブジェクトとなる。 batch_sizeでミニバッチのデータの数を指定できる(defaultは1)。shuffleをTrueとするとTensorDatasetの中身の 組の順序がシャッフルされる(defaultはFalse)。

batch_size  = 3 # ミニバッチのデータの数
data_loader = torch.utils.data.DataLoader(dataset, 
                       batch_size=batch_size, shuffle=True)

forで中身を取り出すと次のようにミニバッチサイズ数ごとにデータが取り出される(この例では3つ)。順序が シャッフルされていることが確認できる。

>>> for x, y in data_loader:
...     print(x, y)
... 
tensor([[5.], [4.], [1.]]) tensor([[50.], [40.], [10.]])
tensor([[3.], [6.], [2.]]) tensor([[30.], [60.], [20.]])

ミニバッチ数を4、shuffle=Falseとすると次の通りとなる。データは全部で6組なので、2回めに取り出す際は残りの2組だけとなっている。 データの順序はシャッフルされずにもとのままとなっている。

>>> data_loader = torch.utils.data.DataLoader(dataset,
... batch_size=4, shuffle=False)
>>> for x, y in data_loader:
...     print(x, y)
... 
tensor([[1.], [2.], [3.], [4.]]) tensor([[10.], [20.], [30.], [40.]])
tensor([[5.], [6.]]) tensor([[50.], [60.]])

DataLoaderを用いた例

ニューラルネットワークによる関数近似 - 化学系エンジニアがAIを学ぶ

参考

ニューラルネットワークによる関数近似

はじめに

PyTorchを用いてニューラルネットワーク関数近似を行うプログラムを作成する。

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

必ずしも必要でないが、ここではtorch.utils.dataをインポートしtorch.utils.data.TensorDatasettorch.utils.data.DataLoaderを 用いて、ミニバッチ学習をさせることにする。 TensorDataset、DataLoaderの使い方について→メモ: PyTorch TensorDataset、DataLoader について - 化学系エンジニアがAIを学ぶ

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

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

入力1つ、出力1つとしている。中間層は2層とし、それぞれのノード数は20, 40個としている。 中間層の活性化関数はReLUとし、出力層はl3の計算値をそのまま出力している。

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.l1 = nn.Linear(1, 20)
        self.l2 = nn.Linear(20, 40)
        self.l3 = nn.Linear(40, 1)

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

データの準備

xy=f(x)の組としてTensorDatasetを使用する。ここでは関数としてnumpy.exp(x)を入れているが、 ここに任意の関数を入れることができる。 xはn個の0〜1の間のランダム値を取る。1次元の配列のままでは扱えないので (n, 1)の形にreshapeさせている。さらにtorch.FloatTensorの形にしてTensorDatasetを得ている。

def get_dataset(n):
    x = numpy.random.random(n) #0〜1のランダム値を取る
    y = numpy.exp(x) #ここを変えれば各種関数で計算できる
    x = x.reshape(n, 1) #(n, 1)の配列にする必要がある
    y = y.reshape(n, 1) #(n, 1)の配列にする必要がある
    x = torch.FloatTensor(x) # floatでないといけない
    y = torch.FloatTensor(y) # floatでないといけない
    return torch.utils.data.TensorDataset(x, y)

TensorDatasetをtorch.utils.data.DataLoaderを渡すことでミニバッチを返すiterableオブジェクトが得られる。

data_number = 10000 #準備するデータの数
batch_size  = 1000 # 1つのミニバッチのデータの数
data_loader = torch.utils.data.DataLoader(get_dataset(data_number), batch_size=batch_size)

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

損失関数には平均2乗誤差、最適化関数にAdamを使用。

model = Model()
criterion = nn.MSELoss()
learning_rate = 1e-2
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

学習

DataLoaderを用いて1ミニバッチごとに学習を繰り返す。

loss_log = [] # 学習状況のプロット用
epoch = 100
for t in range(epoch):
    for xt, yt in data_loader: # 1ミニバッチずつ計算
        optimizer.zero_grad() 
        y_model = model(xt)
        loss = criterion(y_model, yt)
        loss.backward()
        optimizer.step()
    print(t, loss.item())
    loss_log.append(loss.item())

学習結果の確認

##学習したモデルと関数を比較する
x = numpy.linspace(0, 1, 100)
plt.plot(x, numpy.exp(x), label='exp(x)')
x_model = torch.FloatTensor(x.reshape(100,1))
y_model = model(x_model)
y = y_model.detach().numpy().reshape(1,100)[0] # プロット用に変換、detach()が必要
plt.plot(x, y, label='model')
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.show()

y_modelの中身をnumpyの1次元配列に変換している。 detach()なしでやると以下のErrorが出る。これを参考にdetach()を入れた。

RuntimeError: Can't call numpy() on Variable that requires grad. Use var.detach().numpy() instead.

結果

学習状況

以下のコードで学習状況を確認できる。 変化が小さくなるepoch数70回あたりで学習が十分できていると思われる。 learnig_rate = 1e-1では loss が不安定に変化したため 1e-2 に設定した。

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

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

学習したモデルと関数値との比較

ほぼ一致していることが確認できる。

f:id:schemer1341:20190104213229p:plain:w500
図2 学習したモデルと関数値との比較

その他の関数での例

関数get_dataset()内の数学関数部分を変えれば計算できる。

sin(x)

ほぼ一致している。

f:id:schemer1341:20190105011912p:plain:w500
図3 sin(x)との比較

sqrt(s)

x = 0 付近でずれあるが、その他はほぼ一致している。

f:id:schemer1341:20190105011751p:plain:w500
図4 sqrt(x)との比較

exp(cos(x))

この例ではxの範囲を0〜5としている。x = 0 および x = 5 付近でずれが大きめ。

f:id:schemer1341:20190105012215p:plain:w500
図5 exp(cos(x))との比較

外挿領域の予測精度

exp(x)を 0 <= x < 1 の範囲で学習させたが、試しにその範囲外を計算してみると合わない。

f:id:schemer1341:20190105013433p:plain:w500
図6 exp(x)との比較 x = 0 〜 2

所感

sqrt(x)やexp(cos(x))ではモデルと関数値がずれる部分が生じたが、これを合わせるためにはどうするのか?層を増やす?中間層のnode数を増やす?

参考

全コード

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

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.l1 = nn.Linear(1, 20)
        self.l2 = nn.Linear(20, 40)
        self.l3 = nn.Linear(40, 1)

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

def get_dataset(n):
    x = numpy.random.random(n) #0〜1のランダム値を取る
    y = numpy.exp(x) #ここを変えれば各種関数で計算できる
    x = x.reshape(n, 1) #(n, 1)の配列にする必要がある
    y = y.reshape(n, 1) #(n, 1)の配列にする必要がある
    x = torch.FloatTensor(x) # floatでないといけない
    y = torch.FloatTensor(y) # floatでないといけない
    return torch.utils.data.TensorDataset(x, y)

data_number = 10000 #準備するデータの数
batch_size  = 1000 # 1つのミニバッチのデータの数
data_loader = torch.utils.data.DataLoader(get_dataset(data_number), batch_size=batch_size)

model = Model()
criterion = nn.MSELoss()
learning_rate = 1e-2
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

loss_log = [] # 学習状況のプロット用
epoch = 100
for t in range(epoch):
    for xt, yt in data_loader: # 1ミニバッチずつ計算
        optimizer.zero_grad() 
        y_model = model(xt)
        loss = criterion(y_model, yt)
        loss.backward()
        optimizer.step()
    print(t, loss.item())
    loss_log.append(loss.item())

x = numpy.linspace(0, 1, 100)
plt.plot(x, numpy.exp(x), label='exp(x)')
x_model = torch.FloatTensor(x.reshape(100,1))
y_model = model(x_model)
y = y_model.detach().numpy().reshape(1,100)[0] # プロット用に変換、detach()が必要
plt.plot(x, y, label='model')
plt.legend()
plt.xlabel('x')
plt.ylabel('y')
plt.show()

ニューラルネットワークで論理演算子 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))