ニューラルネットワークによる関数近似
はじめに
PyTorchを用いてニューラルネットワークで関数近似を行うプログラムを作成する。
必要なモジュールのインポート
必ずしも必要でないが、ここではtorch.utils.data
をインポートしtorch.utils.data.TensorDataset
、torch.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
データの準備
x
とy=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()
学習したモデルと関数値との比較
ほぼ一致していることが確認できる。
その他の関数での例
関数get_dataset()
内の数学関数部分を変えれば計算できる。
sin(x)
ほぼ一致している。
sqrt(s)
x = 0 付近でずれあるが、その他はほぼ一致している。
exp(cos(x))
この例ではxの範囲を0〜5としている。x = 0 および x = 5 付近でずれが大きめ。
外挿領域の予測精度
exp(x)を 0 <= x < 1 の範囲で学習させたが、試しにその範囲外を計算してみると合わない。
所感
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()