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.

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

Last updated at Posted at 2021-12-09

前回

今回の目標

前回行った深層学習の改善を行う予定でしたが、ミスを見つけたのでまずはその修正を行います。
そののち改善のための実験を行います。

ここから本編

ミス修正

データ集めの際、オセロの試合が終わった時に盤面を元に戻していませんでした。これにより、第二試合以降は始まった瞬間に終了しており十分なデータが集められていませんでした。
修正は以下の通り。

data = []
result = []

turn_vari = [i for i in range(5, 61, 5)]
run = learn(0, 0, turn_vari, eva=eva)

for i in range(10):
    print("\r[" + "#" * (i+1) + " " * (10-i+1) + "]", end="")
    for black in PLAY_WAY:
        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])

data = np.array(data).astype(np.float32)
result = np.array(result).astype(np.float32)

print("\r[" + "#" * 10 + "]")

さらに、盤面の記録にもミスがありました。
これまでの方法だとビットシフトしすぎて後半がすべて0になってしまっていました。

    # これまで
    def data_set(self, data: list, turn_num: int) -> None:
        data.append([])
        if self.turn:
            my = ["b_u", "b_d"]
            opp = ["w_u", "w_d"]
        else:
            my = ["w_u", "w_d"]
            opp = ["b_u", "b_d"]
        for i in range(64):
            data[-1].append(int((self.bw[my[i >= 32]] & (1 << i)) != 0))
            data[-1].append(int((self.bw[opp[i >= 32]] & (1 << i)) != 0))

    # 修正
    def data_set(self, data: list, turn_num: int) -> None:
        data.append([])
        if self.turn:
            my = ["b_u", "b_d"]
            opp = ["w_u", "w_d"]
        else:
            my = ["w_u", "w_d"]
            opp = ["b_u", "b_d"]
        for i in range(32):
            if self.bw[my[0]] & 1 << i:
                data[-1].append(1)
            else:
                data[-1].append(0)
            if self.bw[opp[0]] & 1 << i:
                data[-1].append(1)
            else:
                data[-1].append(0)
        for i in range(32):
            if self.bw[my[1]] & 1 << i:
                data[-1].append(1)
            else:
                data[-1].append(0)
            if self.bw[opp[1]] & 1 << i:
                data[-1].append(1)
            else:
                data[-1].append(0)

データ集めのミスを修正すると今度は学習しなくなったので、まずturn_variを変更しました。データ量が少ないから学習しなかったのでは? と考えたためです。

# turn_vari = [i for i in range(5, 61, 5)]
turn_vari = [i for i in range(1, 61)]

また、よく考えると目的変数はマイナスになることもあるので、マイナス値の存在しないreluを活性化関数にするのは問題がありました。
ということでとりあえずtanhに変更。

class Net(chainer.Chain):
    def __init__(self, n_in, n_hidden, n_out):
        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_out)
    
    def forward(self, x):
        h = F.tanh(self.l1(x))
        h = F.tanh(self.l2(h))
        h = self.l3(h)

        return h

batch_sizeが10ではほぼ学習しなかったので50にすると改善しました。これに関しては追って検証します。

batch_size = 50

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)

また前回のn_batch変数が何もしていないことに気づいたので消しました。チュートリアル内にあったので書いてみたのですが、なぜ書いてあったのかは分かりません。

他の条件は前回と同じで再学習。
結果は以下。

mean squared error of each epoch.png

mean absolute error of each epoch.png

test MSE: 371.1285
test MAE: 14.7923

前回と比べると見劣りする結果ですが、何とか学習はしてくれました。

実験

ファイル構造

実験のためのファイル構造は以下の通り。

experiment
├── fig
│   └── ※実験データをグラフ化した画像が入る※
├── Net_box
│   └── ※ニューラルネットワークを定義したPythonファイルが入る※
├── BitBoard.py   ・・・オセロのための基本クラスを定義
├── deep_learn.py  ・・・深層学習のためのクラスを定義
├── osero_learn.py  ・・・データ集めのためのクラスを定義
└── run.ipynb    ・・・実験実行のためのファイル

BitBoard.py

変更はありません。

osero_learn.py

記事冒頭で示した以外の変更はありません。

deep_learn.py

インポート部分

下2行が上述したクラスです。

from copy import deepcopy

import numpy as np
from chainer.datasets import TupleDataset, split_dataset_random
from chainer.iterators import SerialIterator
import chainer
import chainer.functions as F
from chainer import optimizers
from chainer.optimizer_hooks import WeightDecay
from chainer.dataset import concat_examples
import matplotlib.pyplot as plt

from BitBoard import osero
from osero_learn import learn

定義部分

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

check_point = [i for i in range(1, 61)]

コンストラクタ

とにかくインスタンス変数をたくさん作りました。

class deep_learn:
    def __init__(self):
        self.PLAY_WAY = PLAY_WAY
        self.eva = eva
        self.data = []
        self.result = []
        self.osero = learn(0, 0, check_point)
        self.num = 1
        self.n_epoch = 50
        self.batch_size = 50
        class Inner_Net(chainer.Chain):
            pass
        self.Net = Inner_Net
        self.lr = 0.01
        self.optimizer = optimizers.SGD(lr=self.lr)
        self.WeightDecay = 0.00001
        self.hook_f = WeightDecay(self.WeightDecay)
        self.xlabel1, self.xlabel2 = "epoch", "epoch"
        self.ylabel1, self.ylabel2 = "mean squared error", "mean abolute error"
        self.fig_name1, self.fig_name2 = "MSE of each epoch", "MAE of each epoch"
        self.save_dir = "fig"

set_data

学習のためのデータ集めをするメソッド。

    def set_data(self):
        self.data = []
        self.result = []

        for i in range(self.num):
            for black in self.PLAY_WAY:
                for white in self.PLAY_WAY:
                    self.osero.setup()
                    self.osero.black_method = black
                    self.osero.white_method = white
                    self.osero.eva = self.eva
                    data_ele, result_ele = self.osero.play()
                    for data_each_turn in data_ele:
                        self.data.append(data_each_turn)
                    for result_each_turn in result_ele:
                        self.result.append([result_each_turn])

        self.data = np.array(self.data).astype(np.float32)
        self.result = np.array(self.result).astype(np.float32)

        self.dataset = TupleDataset(self.data, self.result)
        train_val, self.test = split_dataset_random(\
            self.dataset,
            int(len(self.dataset) * 0.7),
            seed = 0
        )
        self.train, self.valid = split_dataset_random(\
            train_val,
            int(len(train_val) * 0.7),
            seed = 0
        )
        self.train_iter = SerialIterator(
            self.train,
            batch_size = self.batch_size,
            repeat = True,
            shuffle = True
        )

fit

学習を行うメソッド。

    def fit(self):
        net = self.Net()

        optimizer = self.optimizer
        optimizer.setup(net)

        for param in net.params():
            if param.name != "b":
                param.update_rule.add_hook(self.hook_f)
        
        results_train = {
            "MSE": [],
            "MAE": []
        }
        results_valid = {
            "MSE": [],
            "MAE": []
        }

        self.train_iter.reset()

        for epoch in range(self.n_epoch):
            while True:
                train_batch = self.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()

                self.optimizer.update()

                if self.train_iter.is_new_epoch:
                    with chainer.using_config("train", False), chainer.using_config("enable_backprop", False):
                        x_valid, t_valid = concat_examples(self.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
        
        self.results_train = results_train
        self.results_valid = results_valid
        self.net = net

plot

学習過程での平均二乗誤差と平均絶対誤差をグラフ化するメソッド。

    def plot(self):
        fig = plt.figure(figsize=(10, 10))
        plt.plot(self.results_train["MSE"], label="train")
        plt.plot(self.results_valid["MSE"], label="valid")
        plt.legend()
        plt.xlabel(self.xlabel1)
        plt.ylabel(self.ylabel1)
        plt.title(self.fig_name1)
        plt.savefig(self.save_dir + "/" + self.fig_name1)
        plt.clf()
        plt.close()

        fig = plt.figure(figsize=(10, 10))
        plt.plot(self.results_train["MAE"], label="train")
        plt.plot(self.results_valid["MAE"], label="valid")
        plt.legend()
        plt.xlabel(self.xlabel2)
        plt.ylabel(self.ylabel2)
        plt.title(self.fig_name2)
        plt.savefig(self.save_dir + "/" + self.fig_name2)
        plt.clf()
        plt.close()

cal_test_error

テストデータでの平均二乗誤差と平均絶対誤差を計算するメソッド。

    def cal_test_error(self):
        x_test, t_test = concat_examples(self.test)
        with chainer.using_config("train", False),\
             chainer.using_config("enable_backprop", False):
            y_test = self.net(x_test)
            MSE_test = F.mean_squared_error(y_test, t_test)
            MAE_test = F.mean_absolute_error(y_test, t_test)
        return MSE_test, MAE_test

Net_box

ニューラルネットワークを定義したファイルを置いたフォルダ。

plane.py

ミス修正の時に用いたクラス。

from chainer import Chain
import chainer.links as L
import chainer.functions as F

class Net(Chain):
    def __init__(self):
        super().__init__()
        n_in = 128
        n_hidden = 128
        n_out = 1
        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_out)

    def __call__(self, x):
        h = F.tanh(self.l1(x))
        h = F.tanh(self.l2(h))
        h = self.l3(h)

        return h

n_layer.py

層の数のみ変更したクラス。

from chainer import Chain
import chainer.links as L
import chainer.functions as F

n_in = 128
n_hidden = 128
n_out = 1

class Net2(Chain):
    def __init__(self):
        super().__init__()
        with self.init_scope():
            self.l1 = L.Linear(n_in, n_hidden)
            self.l2 = L.Linear(n_hidden, n_out)
    
    def __call__(self, x):
        h = F.tanh(self.l1(x))
        h = self.l2(h)
        
        return h


class Net3(Chain):
    def __init__(self):
        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_out)
    
    def __call__(self, x):
        h = F.tanh(self.l1(x))
        h = F.tanh(self.l2(h))
        h = self.l3(h)
        
        return h


class Net4(Chain):
    def __init__(self):
        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
    

class Net5(Chain):
    def __init__(self):
        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_hidden)
            self.l5 = 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 = F.tanh(self.l4(h))
        h = self.l5(h)
        
        return h

kind_function.py

活性化関数の種類のみ変更したクラス。

from chainer import Chain
import chainer.links as L
import chainer.functions as F

functions_arr = [
    F.relu,
    F.tanh,
    F.elu,
    F.leaky_relu,
    F.rrelu,
    F.selu
]

functions_name = [
    "relu",
    "tanh",
    "elu",
    "leaky_relu",
    "rrelu",
    "selu"
]

func = None

class Net(Chain):
    def __init__(self):
        super().__init__()
        n_in = 128
        n_hidden = 128
        n_out = 1
        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_out)

    def __call__(self, x):
        h = func(self.l1(x))
        h = func(self.l2(h))
        h = self.l3(h)

        return h

run.ipynb

まず、以下のインポートを行います。

from deep_learn import *
import Net_box.plane as plane

データ量変更

全ての手法の総当たり戦を、全部で何通りするかをnum変数で表します。

run = deep_learn()
run.Net = plane.Net

num_arr = [1, 10, 50]
run.save_dir = "fig/data_quantity"
i = 1
MSE_test = []
MAE_test = []

for num in num_arr:
    print("\r%d/%d" % (i, len(num_arr)), end="")
    run.num = num
    run.fig_name1 = "MSE of each epoch (data quantity is %d)" % num
    run.fig_name2 = "MAE of each epoch (data quantity is %d)" % num
    run.set_data()
    run.fit()
    run.plot()
    test_error = run.cal_test_error()
    MSE_test.append(test_error[0])
    MAE_test.append(test_error[1])
    i += 1

結果はこちら。

MSE of each epoch (data quantity is 1).png
MSE of each epoch (data quantity is 10).png
MSE of each epoch (data quantity is 50).png
MAE of each epoch (data quantity is 1).png
MAE of each epoch (data quantity is 10).png
MAE of each epoch (data quantity is 50).png

MSE:  [variable(187.12271), variable(328.72275), variable(488.58707)]
MAE:  [variable(9.387474), variable(13.689546), variable(17.651814)]

意外にもデータ量を増やすほど上手く学習できないと分かりました。1が最優秀という結果に。
またデータ量1および10のとき、どちらもエポック数10回まではトレーニングデータと確認データの精度はほぼ同じですが、それ以降は異なっています。理由は分かりません。
また、平均絶対誤差において、最初は1より10の方が誤差が小さいですが、10回以降では逆転しています。平均二乗誤差ではそうはなっておらず、10回目以降の誤差の値が異なるのみでした。平均二乗誤差に大きな差がないにもかかわらず平均絶対誤差が異なる理由として、データ量10では全体的に小さい誤差が多いが、たまに大きな誤差が混じっていると考えられます。
十分学習した時の誤差は、平均も二乗も1が勝っていました。

バッチサイズ変更

run = deep_learn()
run.Net = plane.Net

batch_arr = [5, 10, 25, 50, 75, 100, 150, 200, 300]
run.save_dir = "fig/batch_size"
i = 1
MSE_test = []
MAE_test = []

run.set_data()

for batch_size in batch_arr:
    print("\r%d/%d" % (i, len(batch_arr)), end="")
    run.batch_size = batch_size
    run.fig_name1 = "MSE of each epoch (batch size is %d)" % batch_size
    run.fig_name2 = "MAE of each epoch (batch size is %d)" % batch_size
    run.fit()
    run.plot()
    test_error = run.cal_test_error()
    MSE_test.append(test_error[0])
    MAE_test.append(test_error[1])
    i += 1

結果はこちら。

MSE of each epoch (batch size is 5).png
MSE of each epoch (batch size is 10).png
MSE of each epoch (batch size is 25).png
MSE of each epoch (batch size is 50).png
MSE of each epoch (batch size is 75).png
MSE of each epoch (batch size is 100).png
MSE of each epoch (batch size is 150).png
MSE of each epoch (batch size is 200).png
MSE of each epoch (batch size is 300).png

MAE of each epoch (batch size is 5).png
MAE of each epoch (batch size is 10).png
MAE of each epoch (batch size is 25).png
MAE of each epoch (batch size is 50).png
MAE of each epoch (batch size is 75).png
MAE of each epoch (batch size is 100).png
MAE of each epoch (batch size is 150).png
MAE of each epoch (batch size is 200).png
MAE of each epoch (batch size is 300).png

MSE:  [variable(208.4556), variable(205.58469), variable(212.24141), variable(205.57669), variable(223.43636), variable(193.73814), variable(199.80457), variable(215.08165), variable(213.957)]
MAE:  [variable(9.421841), variable(9.583598), variable(9.503884), variable(9.683197), variable(9.855479), variable(9.166233), variable(8.903477), variable(9.325055), variable(9.886406)]

増やすほど結果がよくなるような、単純な結果にはなりませんでした。
最終結果はどれもほぼ同じでしたが、収束までの時間に差がありました。バッチ数を減らすほど少ないエポック数で最適解に近づいているようです。しかしバッチ数が少ないほど1つのエポックにかかる時間は増加します。
最優秀を決めるとすれば、テストデータでの誤差が小さく、収束までの実時間もそう長くない150でしょうか。

層の数を変更

import Net_box.n_layer as n_layer

run = deep_learn()

Net_arr = [n_layer.Net2, n_layer.Net3, n_layer.Net4, n_layer.Net5]
run.save_dir = "fig/n_layer"
i = 1
MSE_test = []
MAE_test = []

run.set_data()

for Net in Net_arr:
    print("\r%d/%d" % (i, len(Net_arr)), end="")
    run.Net = Net
    run.fig_name1 = "MSE of each epoch (number of layer is %d)" % (i + 1)
    run.fig_name2 = "MAE of each epoch (number of layer is %d)" % (i + 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

結果はこちら。

MSE of each epoch (number of layer is 2).png
MSE of each epoch (number of layer is 3).png
MSE of each epoch (number of layer is 4).png
MSE of each epoch (number of layer is 5).png
MAE of each epoch (number of layer is 2).png
MAE of each epoch (number of layer is 3).png
MAE of each epoch (number of layer is 4).png
MAE of each epoch (number of layer is 5).png

MSE:  [variable(108.787415), variable(100.24213), variable(100.931335), variable(117.44594)]
MAE:  [variable(7.6647296), variable(7.145885), variable(7.118979), variable(7.5313835)]

層の数を増やすほど精度は上がると予想していましたがそうではないようです。こちらも、バッチサイズと同じく最終的な誤差はほぼ同じ結果となりました。
また、層の数が増えるほど収束に時間がかかるようです。これは、層の数が増えるほど調整しなければならないパラメータが増えるためだと思われます。
テストデータでの最優秀は4層ですが、20付近で一度誤差が大きくなっていること、また3層の方が収束が早く精度もそう変わらないことから3層が最優秀と考えました。

活性化関数の種類を変更

前回用いたrelu、今回用いたtanh、マイナス値の存在するelu、leaky_relu、rrelu、seluについて調べました。

import Net_box.kind_function as kind_f

run = deep_learn()

f_arr = kind_f.functions_arr
f_name = kind_f.functions_name
run.save_dir = "fig/kind_function"
i = 1
MSE_test = []
MAE_test = []

run.set_data()

for function in f_arr:
    print("\r%d/%d" % (i, len(f_arr)), end="")
    kind_f.func = function
    run.Net = kind_f.Net
    run.fig_name1 = "MSE of each epoch (function is %s)" % f_name[i - 1]
    run.fig_name2 = "MAE of each epoch (function is %s)" % f_name[i - 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

結果は、reluとtanh以外nanになりました。

MSE of each epoch (function is relu).png
MSE of each epoch (function is tanh).png
MAE of each epoch (function is relu).png
MAE of each epoch (function is tanh).png

['relu', 'tanh', 'elu', 'leaky_relu', 'rrelu', 'selu']
MSE:  [variable(555.87366), variable(204.87903), variable(nan), variable(nan), variable(nan), variable(nan)]
MAE:  [variable(19.49682), variable(9.199303), variable(nan), variable(nan), variable(nan), variable(nan)]

reluとtanh以外はデータが取れず、reluは全く学習していないことがグラフからも分かります。
消去法でtanhを選ぶべきでしょう。

学習率を変更

run = deep_learn()
run.Net = plane.Net

lr_arr = [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3]
run.save_dir = "fig/lr"
i = 1
MSE_test = []
MAE_test = []

run.set_data()

for lr in lr_arr:
    print("\r%d/%d" % (i, len(lr_arr)), end="")
    run.lr = lr
    run.fig_name1 = "MSE of each epoch (lr is %f).png" % lr
    run.fig_name2 = "MAE of each epoch (lr is %f).png" % lr
    run.fit()
    run.plot()
    test_error = run.cal_test_error()
    MSE_test.append(test_error[0])
    MAE_test.append(test_error[1])
    i += 1

MSE of each epoch (lr is 0.000100).png
MSE of each epoch (lr is 0.000500).png
MSE of each epoch (lr is 0.001000).png
MSE of each epoch (lr is 0.005000).png
MSE of each epoch (lr is 0.010000).png
MSE of each epoch (lr is 0.025000).png
MSE of each epoch (lr is 0.050000).png
MSE of each epoch (lr is 0.100000).png
MSE of each epoch (lr is 0.200000).png
MSE of each epoch (lr is 0.300000).png

MAE of each epoch (lr is 0.000100).png
MAE of each epoch (lr is 0.000500).png
MAE of each epoch (lr is 0.001000).png
MAE of each epoch (lr is 0.005000).png
MAE of each epoch (lr is 0.010000).png
MAE of each epoch (lr is 0.025000).png
MAE of each epoch (lr is 0.050000).png
MAE of each epoch (lr is 0.100000).png
MAE of each epoch (lr is 0.200000).png
MAE of each epoch (lr is 0.300000).png

MSE:  [variable(170.00241), variable(171.05908), variable(181.1228), variable(192.46042), variable(161.02898), variable(163.08765), variable(170.65488), variable(177.69069), variable(170.5684), variable(163.26263)]
MAE:  [variable(8.892699), variable(8.849699), variable(9.052602), variable(9.407764), variable(8.696438), variable(8.609232), variable(8.800114), variable(8.914179), variable(8.9228115), variable(8.645447)]

最終結果に大きな差異はなかったですが、0.3については30以降、確認データの誤差が大きくなっているのが気になりました。
0.01が最優秀でした。

最適化関数を変更

URLに載っていた最適化関数の中で、chainerで実装されていたものをすべて試してみました。

URL

# https://qiita.com/ZoneTsuyoshi/items/8ef6fa1e154d176e25b8

from chainer import optimizers

opt_arr = [
    optimizers.SGD(),
    optimizers.MomentumSGD(),
    optimizers.AdaGrad(),
    optimizers.RMSprop(),
    optimizers.AdaDelta(),
    optimizers.Adam(),
    optimizers.RMSpropGraves(),
    optimizers.SMORMS3(),
    optimizers.AMSGrad(),
    optimizers.AdaBound(),
    optimizers.AMSBound()
]

opt_name = [
    "SGD",
    "MomentumSGD",
    "AdaGrad",
    "RmSprop",
    "AdaDelta",
    "Adam",
    "RMSpropGraves",
    "SMORMS3",
    "AMSGrad",
    "AdaBound",
    "AMSBound"
]

run = deep_learn()
run.Net = plane.Net

run.save_dir = "fig/optimizer"
i = 1
MSE_test = []
MAE_test = []

run.set_data()

for opt in opt_arr:
    print("\r%d/%d" % (i, len(opt_arr)), end="")
    run.optimizer = opt
    run.fig_name1 = "MSE of each epoch (optimizer is %s)" % opt_name[i - 1]
    run.fig_name2 = "MAE of each epoch (optimizer is %s)" % opt_name[i - 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

MSE of each epoch (optimizer is SGD).png
MSE of each epoch (optimizer is MomentumSGD).png
MSE of each epoch (optimizer is AdaGrad).png
MSE of each epoch (optimizer is RmSprop).png
MSE of each epoch (optimizer is AdaDelta).png
MSE of each epoch (optimizer is Adam).png
MSE of each epoch (optimizer is RMSpropGraves).png
MSE of each epoch (optimizer is SMORMS3).png
MSE of each epoch (optimizer is AMSBound).png
MSE of each epoch (optimizer is AdaBound).png
MSE of each epoch (optimizer is AMSGrad).png

MAE of each epoch (optimizer is SGD).png
MAE of each epoch (optimizer is MomentumSGD).png
MAE of each epoch (optimizer is AdaGrad).png
MAE of each epoch (optimizer is RmSprop).png
MAE of each epoch (optimizer is AdaDelta).png
MAE of each epoch (optimizer is Adam).png
MAE of each epoch (optimizer is RMSpropGraves).png
MAE of each epoch (optimizer is SMORMS3).png
MAE of each epoch (optimizer is AMSGrad).png
MAE of each epoch (optimizer is AdaBound).png
MAE of each epoch (optimizer is AMSBound).png

['SGD', 'MomentumSGD', 'AdaGrad', 'RmSprop', 'AdaDelta', 'Adam', 'RMSpropGraves', 'SMORMS3', 'AMSGrad', 'AdaBound', 'AMSBound']
MSE:  [variable(140.35452), variable(351.79584), variable(362.52658), variable(130.66309), variable(105.02744), variable(230.22269), variable(200.24446), variable(137.35747), variable(74.48441), variable(104.179436), variable(163.56358)]
MAE:  [variable(7.8985395), variable(15.806046), variable(14.46501), variable(7.234192), variable(6.230004), variable(10.566997), variable(10.3872595), variable(8.752416), variable(6.492539), variable(5.505145), variable(8.154634)]

MomentumSGDは誤差もあり得ないほど大きいし学習も全くしていない尖った結果になりました。SGDは問題なく学習できているので、この場合一昔前の勾配情報は必要ない、むしろ悪手であると考えられます。またMomentumSGDのグラフを見ると波打っていること、また盤面から最終結果を予測するという今回の課題から、評価関数の谷は非常にたくさんあると考えられます。
AdaGradも個性的な結果になりました。各変数ごとに勾配に応じて重みを更新するアルゴリズムですが、非常にゆっくり学習していることから、評価関数には非常に深い谷が存在する可能性もあると考えました。
RmSpropのグラフは大きな山が複数できる結果になりました。これはある程度過去の勾配情報は無視するアルゴリズムなのですが、それが(山を除き)SGDに近い形のグラフを作っているということは、やはり評価関数には大きな谷があるということでしょうか。よく分かりません。
AdaDelta、Adam、RMSpropGraves、SMORMS3、AMSGrad、AdaBound、AMSBoudnは順調に学習できていることが分かります。
AdaDeltaはRMSpropの次元数を合わせたものですが、その考え方は有効であることがここから分かります。
Adamは綺麗に収束していますが、最終的な誤差はいまいちでした。
テストデータで最優秀、かつ収束も綺麗で早かったAdaBoundを採用しようと思います。

重み減衰係数を変更

run = deep_learn()
run.Net = plane.Net

WD_arr = [0.000001, 0.000005, 0.00001, 0.00002, 0.00005, 0.0001, 0.0005, 0.001]
run.save_dir = "fig/WeightDecay"
i = 1
MSE_test = []
MAE_test = []

run.set_data()

for WD in WD_arr:
    print("\r%d/%d" % (i, len(WD_arr)), end="")
    run.WeightDecay = WD
    run.fig_name1 = "MSE of each epoch (coefficient of WeiteDecay is %f).png" % WD
    run.fig_name2 = "MAE of each epoch (coefficient of WeiteDecay is %f).png" % WD
    run.fit()
    run.plot()
    test_error = run.cal_test_error()
    MSE_test.append(test_error[0])
    MAE_test.append(test_error[1])
    i += 1

MSE of each epoch (coefficient of WeiteDecay is 0.000001).png
MSE of each epoch (coefficient of WeiteDecay is 0.000005).png
MSE of each epoch (coefficient of WeiteDecay is 0.000010).png
MSE of each epoch (coefficient of WeiteDecay is 0.000020).png
MSE of each epoch (coefficient of WeiteDecay is 0.000050).png
MSE of each epoch (coefficient of WeiteDecay is 0.000100).png
MSE of each epoch (coefficient of WeiteDecay is 0.000500).png
MSE of each epoch (coefficient of WeiteDecay is 0.001000).png

MAE of each epoch (coefficient of WeiteDecay is 0.001000).png
MAE of each epoch (coefficient of WeiteDecay is 0.000001).png
MAE of each epoch (coefficient of WeiteDecay is 0.000005).png
MAE of each epoch (coefficient of WeiteDecay is 0.000010).png
MAE of each epoch (coefficient of WeiteDecay is 0.000020).png
MAE of each epoch (coefficient of WeiteDecay is 0.000050).png
MAE of each epoch (coefficient of WeiteDecay is 0.000100).png
MAE of each epoch (coefficient of WeiteDecay is 0.000500).png

MSE:  [variable(323.6231), variable(337.4172), variable(317.3086), variable(345.4895), variable(360.81476), variable(347.40695), variable(317.54373), variable(336.22287)]
MAE:  [variable(11.564267), variable(11.702841), variable(11.491479), variable(12.228395), variable(12.072598), variable(11.99491), variable(11.335259), variable(11.767622)]

どれもあまり大きな違いはない結果になりました。
テストデータでの最優秀は0.00001でした。

更新回数を変更

変更と書きましたが、実際は行けるところまで学習させてみただけです。

run = deep_learn()
run.Net = plane.Net

run.save_dir = "fig/n_epoch"

run.set_data()

run.n_epoch = 1000
run.fig_name1 = "MSE of each epoch (n_epohc is %d)" % run.n_epoch
run.fig_name2 = "MAE of each epoch (n_epoch is %d)" % run.n_epoch

run.fit()
run.plot()
MSE_test, MAE_test = run.cal_test_error()

MSE of each epoch (n_epohc is 1000).png
MAE of each epoch (n_epoch is 1000).png

MSE:  variable(173.23218)
MAE:  variable(8.805884)

1000回もする必要はさすがになかったようです。
今まで通り50回でも十分な精度は出ます。
精度検証は50回で行い、本番の学習は念のため100回で行こうと思います。

データ集めの際のターン数を変更

完全に失念していました。
check_pointについて、変更しながらデータ集めをし学習させてみます。

import osero_learn as ol
import deep_learn as dl

run = dl.deep_learn()
run.Net = plane.Net

run.save_dir = "fig/check_point"

cp_arr = [
    [i for i in range(1, 61)],
    [i for i in range(5, 61, 5)],
    [i for i in range(10, 61, 10)]
]
cp_name = [
    "every 1",
    "every 5",
    "every 10"
]
i = 1
MSE_test = []
MAE_test = []

for cp in cp_arr:
    print("\r%d/%d" % (i, len(cp_arr)), end="")
    run.osero = ol.learn(0, 0, cp)
    run.set_data()
    run.fig_name1 = "MSE of each epoch (turn is %s)" % cp_name[i - 1]
    run.fig_name2 = "MAE of each epoch (turn is %s)" % cp_name[i - 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

実行結果はこちら。

MSE of each epoch (turn is every 1).png
MSE of each epoch (turn is every 5).png
MSE of each epoch (turn is every 10).png

MAE of each epoch (turn is every 1).png
MAE of each epoch (turn is every 5).png
MAE of each epoch (turn is every 10).png

['every 1', 'every 5', 'every 10']
MSE:  [variable(134.67699), variable(360.08517), variable(555.0388)]
MAE:  [variable(7.625587), variable(15.11074), variable(18.970434)]

言うまでもなくevery 1が最適ですね。

重み減衰を変更

こちらも失念しておりました。
chainerに実装されているものをすべて試すつもりでしたが、GradientClippingのみなぜかエラーが出て実行できなかったため試せていません。
引数については大体で設定しました。

import chainer.optimizer_hooks as hf

run = deep_learn()
run.Net = plane.Net

run.save_dir = "fig/hook_function"

hf_arr = [
    hf.WeightDecay(0.00001),
    hf.Lasso(0.00001),
    # hf.GradientClipping(1.0),
    hf.GradientHardClipping(-1.0, 1.0),
    hf.GradientNoise(0.3),
    hf.GradientLARS()
]
hf_name = [
    "WeightDecay",
    "Lasso",
    # "GradientClippint",
    "GradientHardClipping",
    "GradientNoise",
    "GradientLARS"
]
i = 1
MSE_test = []
MAE_test = []

run.set_data()

for hook_f in hf_arr:
    print("\r%d/%d" % (i, len(hf_arr)), end="")
    run.hook_f = hook_f
    run.fig_name1 = "MSE of each epoch (hook function is %s)" % hf_name[i - 1]
    run.fig_name2 = "MAE of each epoch (hook function is %s)" % hf_name[i - 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

MSE of each epoch (hook function is WeightDecay).png
MSE of each epoch (hook function is Lasso).png
MSE of each epoch (hook function is GradientHardClipping).png
MSE of each epoch (hook function is GradientNoise).png
MSE of each epoch (hook function is GradientLARS).png

MAE of each epoch (hook function is WeightDecay).png
MAE of each epoch (hook function is Lasso).png
MAE of each epoch (hook function is GradientHardClipping).png
MAE of each epoch (hook function is GradientNoise).png
MAE of each epoch (hook function is GradientLARS).png

['WeightDecay', 'Lasso', 'GradientHardClipping', 'GradientNoise', 'GradientLARS']
MSE:  [variable(173.38802), variable(165.7673), variable(149.28122), variable(170.66235), variable(334.34393)]
MAE:  [variable(8.827514), variable(8.926463), variable(8.31733), variable(8.63603), variable(13.599926)]

WeightDecayとLasso以外はネット上に全く情報がなく、考察のしようがないです。
とりあえずGradientLARSはうまく学習できていないことしか分かりません。
テストデータでの誤差が小さく、収束もまだ余裕がありそうに見えるGradientHardClippingを採用したいと思います。

まとめ

今回の実験で分かった、オセロ学習において有効であろうパラメータを以下に示します。

  • データ量 5x5の25試合、全ターンを記録
  • バッチサイズ 150
  • 層の数 3
  • 活性化関数 tanh
  • 学習率 0.01
  • 最適化関数 AdaBound
  • 重み減衰 GradientHardClipping
  • 更新回数 50回で十分、本番に使用するモデルは100回

重み減衰係数については、WeightDecayの時のみ有効なデータなのでここには書きません。
また、記事が長くなってきたので、GradientHardClippingのパラメータやこれらを組み合わせての実験は次回行いたいと思います。

フルバージョン

次回は

前述した通り、GradientHardClippingのパラメータを変更しての実験およびまとめに記した条件を組み合わせての実験を行います。

次回

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?