今回の目標
前回やり残していた実験を行います。
ここから本編
ファイル構造などは前回と全く同じです。
run.ipynb
GradientHardClippingのパラメータ
from chainer.optimizer_hooks import GradientHardClipping as GHC
from deep_learn import *
import Net_box.plane as plane
################################
run = deep_learn()
run.Net = plane.Net
par_arr = [
[-1., 1.],
[0., 1.],
[-1., 0.],
[-2., 2.],
[-0.5, 0.5],
[-0.1, 0.1],
[-3., 3.],
[-5., 5.]
]
run.save_dir = "fig/parameter"
i = 1
MSE_test = []
MAE_test = []
run.set_data()
for par in par_arr:
print("\r%d/%d" % (i, len(par_arr)), end="")
run.hook_f = GHC(par[0], par[1])
run.fig_name1 = "MSE of each epoch (parameter is %.1f, %.1f).png" % (par[0], par[1])
run.fig_name2 = "MAE of each epoch (parameter is %.1f, %.1f).png" % (par[0], par[1])
run.fit()
run.plot()
test_error = run.cal_test_error()
MSE_test.append(test_error[0])
MAE_test.append(test_error[1])
i += 1
################################
print(par_arr)
print("MSE: ", MSE_test)
print("MAE: ", MAE_test)
実行結果はこちら。
[[-1.0, 1.0], [0.0, 1.0], [-1.0, 0.0], [-2.0, 2.0], [-0.5, 0.5], [-0.1, 0.1], [-3.0, 3.0], [-5.0, 5.0]]
MSE: [variable(138.40294), variable(540.5586), variable(530.9654), variable(158.3173), variable(155.85709), variable(248.41429), variable(152.12968), variable(163.57144)]
MAE: [variable(7.6048646), variable(19.716103), variable(19.489712), variable(8.066726), variable(8.328745), variable(12.342105), variable(7.896567), variable(8.243191)]
全体を見ると、制限が小さすぎたり、負の数または正の数のみにしかなれなかったりするとうまく学習できないようです。また、制限が大きいと収束までの時間が短いこともわかりました。ただ、大きければいいというものでもないようです。
テストデータでの最小誤差を出したのは[-1., 1.]でした。
組み合わせ
import Net_box.n_layer as n_layer
from chainer.optimizers import AdaBound
import pandas as pd
bs_arr = [100, 150, 200]
net_arr = [
[2, n_layer.Net2],
[3, n_layer.Net3],
[4, n_layer.Net4]
]
lr_arr = [0.01, 0.025, 0.3]
par_arr = [[-i, i] for i in range(1, 6)]
run = deep_learn()
run.optimizer = AdaBound()
i = 1
data = {
"batch_size": [],
"num_of_layer": [],
"lr": [],
"lower_bound": [],
"upper_bound": [],
"MSE_test": [],
"MAE_test": []
}
run.set_data()
for bs in bs_arr:
print("\rbs[%d/%d]" % (i, len(bs_arr)), end="")
run.batch_size = bs
for net in net_arr:
print(".", end="")
run.Net = net[1]
for lr in lr_arr:
run.lr = lr
for par in par_arr:
run.hook_f = GHC(par[0], par[1])
run.fit()
test_error = run.cal_test_error()
data["batch_size"].append(bs)
data["num_of_layer"].append(net[0])
data["lr"].append(lr)
data["lower_bound"].append(par[0])
data["upper_bound"].append(par[1])
data["MSE_test"].append(test_error[0])
data["MAE_test"].append(test_error[1])
i += 1
################################
data = pd.DataFrame(data)
data.to_csv("data.csv")
前回の学習で上手く行ったパラメータを使用し、バッチサイズ、層の数、学習率、そしてGradientHardClippingのパラメータに関しては少しずらした値も同時に検証しました。
最終的なテストデータでの誤差、そしてその時のパラメータをcsvファイルに保存しました。
analyse.ipynb
import pandas as pd
import numpy as np
data = pd.read_csv("data.csv")
data_sorted = data.sort_values(by="MAE_test", inplace=False)
data_sorted.head(30)
実行結果はこちら。
見事に全て4層でした。
このデータだけだと層の数以外は特徴が読めないので、トップ50のデータを取り出し特徴を探してみました。
data_top = data_sorted.head(50)
for par_name in data.columns:
if par_name == "Unnamed: 0" or par_name == "MSE_test" or par_name == "MAE_test":
continue
print(par_name)
for par in data_top[par_name].unique():
print("\t%s: %d" % (par, len(data_top.query("%s==%s" % (par_name, par)))))
実行結果はこちら。
batch_size
150: 16
100: 18
200: 16
num_of_layer
4: 45
3: 5
lr
0.3: 16
0.025: 18
0.01: 16
lower_bound
-3: 11
-1: 10
-5: 10
-4: 10
-2: 9
upper_bound
3: 11
1: 10
5: 10
4: 10
2: 9
層の数以外は接戦となりました。
まだよく分からないですね。
今度はワースト50の特徴を見てみました。
data_worst = data.sort_values(by="MAE_test", inplace=False, ascending=False).head(50)
for par_name in data.columns:
if par_name == "Unnamed: 0" or par_name == "MSE_test" or par_name == "MAE_test":
continue
print(par_name)
for par in data_worst[par_name].unique():
print("\t%s: %d" % (par, len(data_worst.query("%s==%s" % (par_name, par)))))
その結果は、
batch_size
150: 17
200: 17
100: 16
num_of_layer
2: 45
3: 5
lr
0.025: 16
0.01: 16
0.3: 18
lower_bound
-3: 10
-2: 9
-4: 10
-5: 10
-1: 11
upper_bound
3: 10
2: 9
4: 10
5: 10
1: 11
どれも平均的な結果となりました。
トップとワースト、両方の結果を見ると、
- バッチサイズ 200が望ましい、100は避けるべき
- 学習率 0.025が望ましい、0.01は避けるべき
- bound -3~3が望ましい、-1~1は避けるべき
という特徴が見えてきました。といっても僅差ですので、「絶対に避けるべき!」というほどでもないとは思います。
lrは使っていませんでした。
model.ipynb
最後に、これまで求めたパラメータを用いて本番用のモデルを作成したいと思います。
データ集め
データ量は5x5x60の1500です。
from copy import deepcopy
import numpy as np
from osero_learn import learn
from BitBoard import osero
################################
PLAY_WAY = deepcopy(osero.PLAY_WAY)
del PLAY_WAY["human"]
PLAY_WAY = PLAY_WAY.values()
eva = [[
1.0, -0.6, 0.6, 0.4, 0.4, 0.6, -0.6, 1.0,
-0.6, -0.8, 0.0, 0.0, 0.0, 0.0, -0.8, -0.6,
0.6, 0.0, 0.8, 0.6, 0.6, 0.8, 0.0, 0.6,
0.4, 0.0, 0.6, 0.0, 0.0, 0.6, 0.0, 0.4,
0.4, 0.0, 0.6, 0.0, 0.0, 0.6, 0.0, 0.4,
0.6, 0.0, 0.8, 0.6, 0.6, 0.8, 0.0, 0.6,
-0.6, -0.8, 0.0, 0.0, 0.0, 0.0, -0.8, -0.6,
1.0, -0.6, 0.6, 0.4, 0.4, 0.6, -0.6, 1.0
] for i in range(2)]
################################
data = []
result = []
turn_vari = [i for i in range(1, 61)]
run = learn(0, 0, turn_vari, eva=eva)
i = 1
for black in PLAY_WAY:
print("\r%d/%d" % (i, len(PLAY_WAY)), end="")
for white in PLAY_WAY:
run.setup()
run.black_method = black
run.white_method = white
run.eva = eva
data_ele , result_ele = run.play()
for data_each_turn in data_ele:
data.append(data_each_turn)
for result_each_turn in result_ele:
result.append([result_each_turn])
i += 1
data = np.array(data).astype(np.float32)
result = np.array(result).astype(np.float32)
学習部分
from chainer.datasets import TupleDataset
from chainer.datasets import split_dataset_random
from chainer.iterators import SerialIterator
import chainer
import chainer.links as L
import chainer.functions as F
from chainer import optimizers
from chainer.optimizer_hooks import GradientHardClipping
from chainer.dataset import concat_examples
ニューラルネットワーク
全結合層四層で、活性化関数はtanhを用います。
class Net(chainer.Chain):
def __init__(self):
n_in = 128
n_hidden = 128
n_out = 1
super().__init__()
with self.init_scope():
self.l1 = L.Linear(n_in, n_hidden)
self.l2 = L.Linear(n_hidden, n_hidden)
self.l3 = L.Linear(n_hidden, n_hidden)
self.l4 = L.Linear(n_hidden, n_out)
def __call__(self, x):
h = F.tanh(self.l1(x))
h = F.tanh(self.l2(h))
h = F.tanh(self.l3(h))
h = self.l4(h)
return h
パラメータ
バッチサイズが200、GradientHardClippingに与える制限の絶対値が3、エポック数が100です。
batch_size = 200
bound = 3
n_epoch = 100
データセット
dataset = TupleDataset(data, result)
train_val, test = split_dataset_random(dataset, int(len(dataset) * 0.7), seed=0)
train, valid = split_dataset_random(train_val, int(len(train_val) * 0.7), seed=0)
train_iter = SerialIterator(train, batch_size=batch_size, repeat=True, shuffle=True)
各手法
最適化手法はAdaBound、正規化手法はGradientHardClippingを用います。GradientHardClippingの引数については前述した説明の通りです。
net = Net()
optimizer = optimizers.AdaBound()
optimizer.setup(net)
for param in net.params():
if param.name != "b":
param.update_rule.add_hook(GradientHardClipping(-bound, bound))
学習開始
results_train = {
"MSE": [],
"MAE": []
}
results_valid = {
"MSE": [],
"MAE": []
}
train_iter.reset()
for epoch in range(n_epoch):
print("\r%d/%d" % (epoch + 1, n_epoch), end="")
while True:
train_batch = train_iter.next()
x_train, t_train = concat_examples(train_batch)
y_train = net(x_train)
MSE_train = F.mean_squared_error(y_train, t_train)
MAE_train = F.mean_absolute_error(y_train, t_train)
net.cleargrads()
MSE_train.backward()
optimizer.update()
if train_iter.is_new_epoch:
with chainer.using_config("train", False), chainer.using_config("enable_backprop", False):
x_valid, t_valid = concat_examples(valid)
y_valid = net(x_valid)
MSE_valid = F.mean_squared_error(y_valid, t_valid)
MAE_valid = F.mean_absolute_error(y_valid, t_valid)
results_train["MSE"].append(MSE_train.array)
results_train["MAE"].append(MAE_train.array)
results_valid["MSE"].append(MSE_valid.array)
results_valid["MAE"].append(MAE_valid.array)
break
グラフ作成
import matplotlib.pyplot as plt
def two_plot(y, xlabel, ylabel, title, save_dir):
fig = plt.figure(figsize=(10, 10))
plt.plot(y[0], label="train")
plt.plot(y[1], label="valid")
plt.minorticks_on()
plt.grid(which="major")
plt.grid(which="minor", linestyle="--")
plt.title(title)
plt.xlabel(xlabel)
plt.ylabel(ylabel)
plt.legend()
plt.savefig(save_dir + "/" + title)
plt.clf()
plt.close()
################################
two_plot(
[results_train["MSE"], results_valid["MSE"]],
"epoch",
"mean squared error",
"mean squared error each epoch",
"fig/model"
)
two_plot(
[results_train["MAE"], results_valid["MAE"]],
"epoch",
"mean absolute error",
"mean absolute error each epoch",
"fig/model"
)
結果はこちら。
精度確認
x_test, t_test = concat_examples(test)
with chainer.using_config("train", False), chainer.using_config("enable_backprop", False):
y_test = net(x_test)
MSE_test = F.mean_squared_error(y_test, t_test)
MAE_test = F.mean_absolute_error(y_test, t_test)
print("test MSE: {:.4f}".format(MSE_test.array))
print("test MAE: {:.4f}".format(MAE_test.array))
結果はこちら。
test MSE: 80.2214
test MAE: 5.8251
機械学習での最高記録は、各ターンごとの平均絶対誤差の平均を求めると約13ですので、深層学習の方が高い精度を誇っています。
モデル保存
chainer.serializers.save_npz("deep_AI.net", net)
フルバージョン
次回は
やっと精度の高いモデルが完成しましたので、これと対戦できるプログラムを作成します。