0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

コンピュータとオセロ対戦32 ~深層学習で勝敗予測、実験2~

Last updated at Posted at 2021-12-11

前回

今回の目標

前回やり残していた実験を行います。

ここから本編

ファイル構造などは前回と全く同じです。

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)

実行結果はこちら。

MSE of each epoch (parameter is -1.0, 1.0).png
MSE of each epoch (parameter is 0.0, 1.0).png
MSE of each epoch (parameter is -1.0, 0.0).png
MSE of each epoch (parameter is -2.0, 2.0).png
MSE of each epoch (parameter is -0.5, 0.5).png
MSE of each epoch (parameter is -0.1, 0.1).png
MSE of each epoch (parameter is -3.0, 3.0).png
MSE of each epoch (parameter is -5.0, 5.0).png

MAE of each epoch (parameter is -1.0, 1.0).png
MAE of each epoch (parameter is 0.0, 1.0).png
MAE of each epoch (parameter is -1.0, 0.0).png
MAE of each epoch (parameter is -2.0, 2.0).png
MAE of each epoch (parameter is -0.5, 0.5).png
MAE of each epoch (parameter is -0.1, 0.1).png
MAE of each epoch (parameter is -3.0, 3.0).png
MAE of each epoch (parameter is -5.0, 5.0).png

[[-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)

実行結果はこちら。

image.png

見事に全て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"
)

結果はこちら。

mean squared error each epoch.png
mean absolute error each epoch.png

精度確認

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)

フルバージョン

次回は

やっと精度の高いモデルが完成しましたので、これと対戦できるプログラムを作成します。

次回

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?