grbl1.1+Arduino CNCシールドV3.5+bCNCを使用中。
BluetoothモジュールおよびbCNCのPendant機能でスマホからもワイヤレス操作可能。
その他、電子工作・プログラミング、機械学習などもやっています。
MacとUbuntuを使用。

CNCマシン全般について:
国内レーザー加工機と中国製レーザー加工機の比較
中国製レーザーダイオードについて
CNCミリングマシンとCNCルーターマシンいろいろ
その他:
利用例や付加機能など:
CNCルーター関係:



*CNCマシンの制作記録は2016/04/10〜の投稿に書いてあります。


2019年1月17日木曜日

TSP(巡回セールスマン問題):その2

前回のTSP(巡回セールスマン問題)の続きです。前回のKaggleコンペでは都市が197769もあり、計算するにもかなり時間がかかったので、都市数を減らしたサンプルで最適化アルゴリズムについて確かめてみました。

総当たりで計算すると都市数が10を超えるあたりからきつくなっていくので、真のスコアと比較検証するには少なめの都市数にする必要があります。


ライブラリ:
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from numba import jit, prange
from itertools import permutations
from tqdm import tqdm_notebook as tqdm
np.random.seed(seed=42)
まずは必要そうなライブラリ。numbaでどれだけ高速になるかはわかりませんが一応入れておきます。組み合わせのパターンを求めるにはitertools.permutationsを用いました。


都市数や座標の設定:
num = 10                                      # the number of cities
path = np.concatenate([np.arange(num), [0]])  # path starts from 0 and ends with 0
X = np.random.random(num)                     # X-coordinates of cities
Y = np.random.random(num)                     # Y-coordinates of cities
XY = X + Y * 1j                               # complex numbers of X and Y
都市は合計10箇所、0〜9までの番号を割り当てておきます。最後に0に戻るので経路としては
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
の11要素になります。
それぞれにランダムでXとYの座標を与え、距離を求めるための複素数XYも用意しておきます。


スコア関数:
def score(path, a, b):
    c = XY[path[a: b+1]]
    sc = np.sum(np.abs(np.diff(c)))
    return sc
都市aからbに至るスコア(道のり)を求めるための関数を用意しておきます。複素数XYの差分をnp.abs()を使って求めます。


経路のプロット:
def plot_path(path):
    PX = [X[i] for i in path]
    PY = [Y[i] for i in path]
    plt.figure(figsize=(5,5))
    plt.axis('equal')
    plt.plot(PX, PY)
    plt.scatter(PX, PY)
    plt.scatter(PX[0], PY[0], s=80, c='r', marker='o')
    for i in range(num):
        plt.text(PX[i], PY[i]+0.03, s=i, fontsize=12, color='gray')
    print('Total Score: ', score(path, 0, len(path)))
開始点を赤点表示。巡回順のナンバリングもしておきます。
初期経路はランダムなので表示させるとこのような感じになります。
4と5がほぼ重なっていて見にくいですがこのままで。


真のスコアを求める:
総当たりで計算する場合、12都市以上になると何時間もかかるため、都市数を指定できるような関数にしました。10都市の場合は巡回すると11都市になり、開始点と到着点の二点を除いた9都市の組み合わせすべてについて計算します。9都市なら3分、10都市なら30分以上かかります。
def opt_n(path, n):
    for i in tqdm(range(1, len(path)-n)):
        perm = np.array(path[i:i+n])
        combs = np.array(list(permutations(perm)))
        sc1 = score(path, i-1, i+n+1)
        sc3 = np.inf
        for comb in combs:
            copy = np.concatenate([path[:i], comb, path[i+n:]])
            sc2 = score(copy, i-1, i+n+1)
            if sc1 > sc2:
                if sc3 > sc2:
                    sc3 = sc2
                    path = copy.copy()
                    print('Improved to: ', score(path, 0, num))
    return path


二方向の欲張り法:
ランダムな初期経路に対して最適化していってもいいかもしれませんが、欲張り法でもう少しまともな経路をつくっておきます。
スタート地点から最も近い都市をつなぎ続けていきます。スタート地点から二方向に都市をつなげていき、その二つの経路を中点でつなぎます。
@jit('i8[:](i8[:], i8, i8)')
def closest_num(path, num, index):
    copy = path.copy()
    d = np.array([np.abs(XY[index] - XY[i]) for i in copy])
    arg = d.argsort()
    closest_index = np.array([copy[i] for i in arg])[:num]
    return closest_index

@jit('i8[:](i8[:])', parallel=True)
def find_path(path):
    copy = path[1:-1].copy()
    half = len(copy)//2 + 1
    path1 = np.array([0])
    path2 = np.array([0])
    for i in tqdm(prange(half)):
        if len(copy) > 0:
            c1 = closest_num(copy, 1, path1[-1])
            path1 = np.append(path1, c1)
            copy = np.delete(copy, np.where(copy==c1))
        if len(copy) > 1:  
            c2 = closest_num(copy, 1, path2[-1])
            path2 = np.append(path2, c2)
            copy = np.delete(copy, np.where(copy==c2))
        
    print('Path_1:', path1, '\nPath_2:', path2[::-1])
    return np.concatenate([path1, path2[::-1]])
どのくらい効果的かはわかりませんが、一応使ってみます。

ランダムよりはまともな経路になりますが、まだまだ改良の余地があります。


二点間反転:
欲張り法で得た経路を最適化するアルゴリズムです。任意の二点間を選び、その区間を反転して繋ぎ直します。
@jit('i4[:](i4[:], i4)')
def opt_n_flip(path, n):
    for i in range(0, len(path)-n-1):
        n1 = i
        n2 = n1 + n
        sc1 = score(path, n1-1, n2+1)
        rev = path[n1: n2+1][::-1]
        copy = np.concatenate([path[:n1], rev, path[n2+1:]])
        sc2 = score(copy, n1-1, n2+1)
        if sc1 > sc2:
            path = copy.copy()
            print('Improved to: ', score(path, 0, len(path)-1))
    return path

@jit(parallel=True)
def opt_flip_loop(path, n, num):
    for i in tqdm(prange(n, n+num)): 
        path = opt_n_flip(path, i)
    return path
まずは隣り合わせの二都市、次は一つ離れた二都市、そして二つ離れた二都市という具合にN個分離れた二都市までを評価して、向上が見られれば随時更新していきます。
最適化の結果、都市数が少ないためか、ほぼ正解にたどり着いたようです。


一点移動挿入:
or-optというアルゴリズムに近い方法です。経路から一つだけずれている都市を最適な場所に移動してくれます。
def longest(path):
    sc_arr = - np.array([np.sum(np.abs(np.diff(np.array(XY[path[i-1: i+1]])))) for i in tqdm(range(1, len(path)-1))])
    arg = sc_arr.argsort()    
    return arg

def closest_n(path, n, index):
    copy = path[1:-1].copy()
    if path[index] in copy:
        copy = np.delete(copy, np.where(copy==path[index]))
    d = np.array([np.abs(XY[index] - XY[i]) for i in copy])
    arg = d.argsort()
    closest_index = np.array([copy[i] for i in arg])[:n]
    return closest_index

@jit('i8[:](i8[:])', parallel=True)
def or_opt(path):
    lon = longest(path)
    print('Initial Score: ', score(path, 0, num))
    for i in tqdm(lon):
        sc3 = np.inf
        c5 = closest_n(path, 10, path[i])
        for c in c5:
            j = np.where(path==c)[0][0]
            copy = np.delete(path, i)
            copy = np.insert(copy, j, path[i])
            if i >= j:
                sc1 = score(path, j-1, i+1)
                sc2 = score(copy, j-1, i+1)
            else:
                sc1 = score(path, i-1, j+1)
                sc2 = score(copy, i-1, j+1)
            if sc1 > sc2:
                if sc3 > sc2:
                    sc3 = sc2
                    path = copy.copy()
                    print('Improved to: ', score(path, 0, num))
    return path
まず二点間距離の降順リストをつくり、その都市の最も近くにある都市(上位5都市)の次の位置へ移動しスコアが向上すれば更新します。
最適化しても前回と結果は同じです。おそらくこれが正解の経路。


正解の経路:
最後に真のスコアを求めるためにつかったopt_nで再度最適化させます(都市数が多い場合はせいぜい10都市までの組み合わせによる最適化)。今回の場合は都市数が少ないのでopt_nに9を代入すれば正解が得られます(以下)。組み合わせは9!=362880通り(約3分)。


全体の流れ:
・ランダムに経路を生成
・欲張り法で経路を生成(find_path)
・二点間反転(opt_n_flip)
・一点移動挿入(or_opt)
・組み合わせ最適化(opt_n)

都市数が少ない場合は二点間反転だけでも真の経路になる場合がありました。ひとつのアルゴリズムを一度実行しただけでは最適化不十分なので、スコアが向上しなくなるまで複数回繰り返します。
都市数を20、30と増やしていくと、真の経路を求めるには時間がかかりすぎるので比較評価することができません。よって最適化によってスコアを徐々に向上していくしかありません。
複数のアルゴリズムをスコア向上しなくなるまで実行することで、ぱっと見は最適解になっているように見えますが、ランダムで生成した経路を最適化した場合と欲張り法のあとで最適化した場合とで結果が異なるので初期経路次第という感じです。今回は欲張り法でしか初期経路を生成していないので、もう少し他の方法も試してみる必要がありそうです。焼きなまし法というのもあるらしく、最初から最短都市や最少スコアを優先するアルゴリズムにするのではなく、許容値を設定して多少スコアオーバーしても採用するなどしたほうがいいかもしれません(この部分に関しては次回)。


以下は都市数100の場合(正解の経路とは限らない):

初期ランダム経路
Total Score:  52.8760351727298

欲張り法(二方向)による経路
Total Score:  9.979255125288242
まだ交差している経路があります。

二点間反転による最適化
Total Score:  8.764946875917067
交差していた経路がなくなって、ほぼ最適解。

一点移動挿入による最適化
Total Score:  8.463720661190624
細部の修正と最適化(わずかながらスコアは向上しています)。
ここまでは数秒で終了。

組み合わせ数8による最適化(8-opt)
Total Score:  8.435705379605803
さらなる最適化(右下の部分が改善)。
経路の組み合わせが8!=40320通りあるので、この工程だけ時間がかかります(1分43秒)。
よく見ると、赤点近くの98と99を83と82につなげたほうがスコアが向上しそうです。まだ赤点付近が上手く最適化できないので改善の余地あり。

対策:
局所的には最適化されスコアは向上しましたが、初期経路によってその後の最適化が左右されてしまうという弱点があります。初期経路生成の工夫をするか、後からでも大規模な経路変更が可能になる方法を探さないといけないかもしれません。最初に多少スコアオーバーしても許容する大規模な経路変換を行ったのちに局所的な最適化を行って、結果的にスコアが向上するような二段階の最適化をするといいのかもしれません。

続き:convex hull:凸包/ギフト包装法/Graham scanアルゴリズム
関連:Traveling Salesman Problem:巡回セールスマン問題について(まとめ)





2019年1月11日金曜日

巡回セールスマン問題について

ここしばらくは「巡回セールスマン問題」について試行錯誤していました。きっかけは年末から開催されていたKaggleの「Traveling Santa 2018 - Prime Paths」コンペです(締め切り:2019年1月10日)。Kaggleでは年末恒例のイベントコンペのようで、一応賞金もでます(賞金総額:$25,000)。
ディープラーニングでは解けないようですが、面白そうなのでやってみることにしました(やってみれば得られることも多そうなので)。


巡回セールスマン問題(Traveling Salesman Problem/TSP):
TSPとは、地図上にある複数の都市をセールスマンが一筆書きのように移動して、その合計移動距離が最小になる経路を求めるアルゴリズムです。地図上の都市が少なければ(約20箇所以下)、移動順番の組み合わせを総当たりで計算し最短経路を探し出すことができますが、都市数が増えるほど組み合わせの数は爆発的に増えてしまい、スーパーコンピュータを使っても数万年かかってしまうようです。すべての組み合わせを計算することができないため、近似計算、最適化などの方法によって少しでも正解に近づくように工夫するようです。TSPサイト


今回のKaggleコンペルール:
基本はTSPにおける最短経路探索ですが、都市数は197769もあり、当然ながら総当たりで計算することが不可能。
都市名は0〜197768までのナンバリングされた数値であり、それぞれの都市には(X,Y)の座標値が割り当てられています。都市名0を開始点として都市間を移動し続け、最終的に巡回してまた0に戻るように都市の訪問順のリストをつくりあげて提出します。
さらに今回の特別ルールとして、都市から都市へ移動する際に、10ステップごとにペナルティが課されるときがあります。10ステップごとに一つ手前(9ステップ目)が素数の都市名であればペナルティなし、素数でない場合は9ステップ目から10ステップ目までの移動距離が1.1倍されてしまいます。要はできるだけ10ステップごとに9ステップ目が素数の都市名を通るように移動することが望ましいことになります。

実際どのくらいの都市数で、どのような経路になるかというと以下の画像のような感じです。
トナカイとソリの画像がそのまま都市の配置になっています。青い線が経路。これだけ都市数があるので、早々簡単には求められないということがわかります。


始めるにあたって:
まずTSPについては、一度は見たことがあるような問題ですが、詳しくは知らないのであくまでビギナーとして参加になります。調べていくとやはりTSPの研究者(数学者)がいるので、その人たちには到底敵いません。問題自体は至ってシンプルなのですが、解けそうで解けないという感じでしょうか。
KaggleのKernelsにはベースラインとなるコードが参加者によって掲示されているので、まずはそこをチェック。


TSP Solverライブラリ:
Kernelsを見るとTSP Solverというライブラリがいくつか存在し、それで計算すると総移動距離(スコア)が1533242前後になるようです。このスコアが少ないほど上位になるというわけです。多くの人は、このようなライブラリで数時間計算させベースとなる経路を得たのち、最適化アルゴリズムで少しづつ経路を調整してスコアを縮めていくようです。


アルゴリズム:
最適化アルゴリズムで少しずつスコアを縮めていくことはできますが、ある程度最適化してしまうと、それ以上スコアの変動はなくなってしまい手詰まりに陥ってしまいます。最初のTSPライブラリでかなりいいスコアを出しておかないときついという感じ。数日間連続計算させることでいい結果がでるときもありますが、今回はせいぜい一晩を上限としてプログラムを組んでみることにしてみました。組み合わせの問題でもあるので、組み合わせ数が多いほど有利になりますが、その分計算量も莫大になってしまいます。
とりあえずは、もう少しTSPの仕組みを理解しようとTSPライブラリを使わず自力でやってみることにしました。


欲張り法:
まずスタート地点から移動する際に、最も近い都市に移動し続けていけばいいのでは?と直感的に考えてしまいます。どうやら調べてみると、これは「欲張り法」というらしい。ということから、以下の関数が必要なので自前でつくることから始めてみました。

・二都市間の距離を測定する関数(単純距離)
・二都市間の移動経路のスコアを計算する関数(素数のペナルティも含め)
・ある都市に最も近い都市を求める関数(欲張り法用)


複素数で距離を求める:
(x1,y1)と(x2,y2)の二点間の距離を求めるには、

d = sqrt((x1-x2)**2 + (y1-y2)**2)

となりますが、pythonにおいては複素数がすぐに使えることから、y座標に1j(虚数)を掛けて各点を実部と虚部の一つの式にして計算できるようです。

p1 = x1 + y1*1j
p2 = x2 + y2*1j
d = abs(p1-p2)

都市が197768もあるので、順番に各都市との距離などを計算させると結構時間がかかります。できるだけ計算コストを抑えた工夫も必要そうです。上記の違いでどれだけ高速になっているかの検証はしていませんが、このような計算方法もあるというのが分かりました。


numbaで高速計算:
基本的にはnumpyで計算するためGPUを使えないことからどうしても計算が遅くなってしまいます。Kernelsを見ると、numbaを使えば高速計算や並行処理が可能になるらしく、慣れないながらも使ってみることにしました。numbaの最も簡単な使い方はdef関数前にデコレータとして@jitを追加させるだけです。

from numba import jit

@jit
def func(x):
  y = some_calculation...
  return y

このほかcudaを使う方法などもあるようですが、少々複雑なので今回は簡単な方法をできる範囲で使ってみることにしてみました。いずれにせよ、Kernelsからはこのような細かいテクニックが手に入るので結構勉強になります。


欲張り法の結果:
最短距離で繋いでいくので、そこそこいい結果がでるのではないかと期待していましたが、スコアは約1800000。TSPライブラリと比較すると約2割ほど多くなってしまいます。最初のうちは最も近い都市へ移動していくので効率的ですが、残りの都市が少なくなって行くに従って近くの都市も少なくなってしまうため、最短の都市であっても遠くの都市を選ばざるを得なくなってしまうからなのでしょう。

二方向の欲張り法:
欲張り法だと後半の都市選択が厳しくなってくることと、巡回ルートにしなければいけないことから、開始点から二方向に欲張り法でルートを繋げていくことにしてみました。一方が開始点からの順方向の移動で、もう一方が開始点までの逆方向の移動を最終的に中間地点でつなぐというやり方です。
しかしながらこの方法でもスコアは1800000程度で決定的といえるほど効果がありませんでした。単純に近い都市だけをつなぐだけだと進む方角の優先順位がないので、もう少し賢い方法が必要そうです。局所的に都市の分布密度が高い方へ優先的に移動するなどしたほうがよさそうですが、計算コストがかかりすぎるのでそこまではやらないことに。
ただ、あとからの最適化アルゴリズムでもスコアを縮めていくことは可能なので、とりあえず欲張り法で繋げておいてもいいのかもしれませんが、実際どうなのか(専門家ではないので分からない)?
このようにいろいろ試行錯誤しているとTSPの様々な問題が見えてきて、どうすればいいのか徐々に分かってきて興味も湧いてきます。


最適化アルゴリズム:
TSPライブラリであれ欲張り法であれ一旦経路をつくってから、それを調整していくアルゴリズムについてです。2-optや3-optあるいはk-optと言われるアルゴリズムがあるようです。


連続する二点入れ替え:
まず思いつくのが、任意の二点を入れ替えてみてスコアを計算し、もしスコア向上が得られれば採用するというアルゴリズム。このように何か最適化の処理をした後にスコアを評価して、結果がよければそれを採用するというやり方にいくつかあるようです。
最も簡単なのは連続する二点を入れ替える方法。

0 - 1 - 2 - 3 -(4)-(5)- 6 - 7 - 8 - 9 (初期状態)

4と5を入れ替える。

0 - 1 - 2 - 3 -(5)-(4)- 6 - 7 - 8 - 9 (変更後)

3〜6(4、5の前後)までの範囲のスコアを比較計算して向上すれば採用。


二点間反転:
また、遠く離れた二点間を入れ替える場合は、二点だけでなくその間の都市も含めて反転させてつなぎ直すという方法もあります。

0 - 1 - 2 -(3 - 4 - 5 - 6)- 7 - 8 - 9 (初期状態)

3から6を反転する。

0 - 1 - 2 -(6 - 5 - 4 - 3)- 7 - 8 - 9 (変更後)

2〜7(3〜6の前後)までのスコアを比較計算して向上すれば採用。


連続する三点入れ替え:

0 - 1 - 2 - 3 -(4)-(5)-(6)- 7 - 8 - 9 (初期状態)

(4, 5, 6)の三点の組み合わせすべて(6通り)を比較計算する。

0 - 1 - 2 - 3 -(4)-(5)-(6)- 7 - 8 - 9
0 - 1 - 2 - 3 -(4)-(6)-(5)- 7 - 8 - 9
0 - 1 - 2 - 3 -(5)-(4)-(6)- 7 - 8 - 9
0 - 1 - 2 - 3 -(5)-(6)-(4)- 7 - 8 - 9
0 - 1 - 2 - 3 -(6)-(4)-(5)- 7 - 8 - 9
0 - 1 - 2 - 3 -(6)-(5)-(4)- 7 - 8 - 9

3〜7までの範囲を比較計算し、最もスコアの少ない組み合わせを採用。

同様に四点、五点と増やしていけばスコアは向上するけれども、その分組み合わせのパターンも増大するので、ほどほどにしておかないと時間がかかりすぎる(せいぜい五点程度)。


組み合わせ用ライブラリ:
上記のような組み合わせのパターンを導き出すライブラリとしてsympy.utilities.iterables.multiset_permutationsがあります。これを使えば簡単に組み合わせパターンをリストとして取り出せます。


一点を任意の位置へ移動:
また別の方法としてシンプルなのが、一点を異なる場所へ入れ直す方法。

0 - 1 - 2 -(3)- 4 - 5 - 6 - 7 - 8 - 9 (初期状態)

3を1から順に挿入していき比較計算しスコア向上すれば更新する。

0 -(3)- 1 - 2 - 4 - 5 - 6 - 7 - 8 - 9
0 - 1 -(3)- 2 - 4 - 5 - 6 - 7 - 8 - 9
0 - 1 - 2 -(3)- 4 - 5 - 6 - 7 - 8 - 9
0 - 1 - 2 - 4 -(3)- 5 - 6 - 7 - 8 - 9
0 - 1 - 2 - 4 - 5 -(3)- 6 - 7 - 8 - 9
0 - 1 - 2 - 4 - 5 - 6 -(3)- 7 - 8 - 9
0 - 1 - 2 - 4 - 5 - 6 - 7 -(3)- 8 - 9
0 - 1 - 2 - 4 - 5 - 6 - 7 - 8 -(3)- 9
0 - 1 - 2 - 4 - 5 - 6 - 7 - 8 - 9 -(3)

アルゴリズムとしてはシンプルですが、これを開始点0を除いた全ての都市(197768箇所)において順に比較計算させていくと197768*197767回計算させることになり、かなり時間がかかってしまうので、移動したい点に最も近い点を上位五点まで選択しておき、それらのうちでスコア向上が見られるときにだけ採用とするなどの工夫をしました。


素数用ライブラリ:
この他、このコンペ特有のルールとなる素数に関しては、sympy.isprimeをつかうことで、その数が素数かどうかをすぐに判定できます。


最適化の結果:
上記の方法だけでもある程度最適化はできましたが、やはり最初のTSPライブラリによる結果がいいほうが最終的なスコアがよくなってしまいます。自前の欲張り法による初期経路の場合は、一度の最適化でもかなりスコアを向上させることができましたが(もともと改善の余地が多いため)、TSPライブラリによる初期経路の場合は、すでにかなり改善されているためか1%も向上しません。数時間回してやっと1箇所(0.0001%程度)改善するくらいでしょうか。
もっと強力なコンピュータか計算の高速化の工夫も必要そうなので、単なるTSPだけの問題というわけでもなくなってきて奥が深いという感じ。今回はPythonを使いましたが、C++でやったほうがよさそうです。

この他にもいくつか最適化するアルゴリズムを考えてみましたが、計算コストがかかりすぎることから諦めたものもあります(一通り計算させると1ヶ月以上かかってしまうなど)。
順位的には大したことはありませんでしたが、今回もまたTSPという未経験の分野にチャレンジしたことでいろいろと収穫がありました。やはりKaggleに参加するとモチベーションもあがり勉強にもなります。もうコンペ自体は終わってしまいましたが、引き続きTSPの最適化アルゴリズムを考えてみたくなります。

TSP:その2へ続く
関連:Traveling Salesman Problem:巡回セールスマン問題について(まとめ)


Kaggleで勝つデータ分析の技術
門脇 大輔 阪田 隆司 保坂 桂佑 平松 雄司
技術評論社
売り上げランキング: 363

2018年11月20日火曜日

Kaggle Digital Recognizer(MNIST): Keras, fit_generator() + hyperopt

Kaggle Digital Recognizer(MNIST)の続きです。前回から少しだけ内容を変えてみたらベストスコアがでました。
改良点は以下です。

fit_generator():
前回は、通常の訓練model.fit()を使った後にData Augmentationとしてmodel.fit_generator()を追加して二段階で訓練しましたが、今回は最初からfit_generator()だけで訓練してみることにしました。

BatchNormalization:
CNNに関しては前回よりも層を少なくして、conv2dの後に必ずBatchNormalization()を入れ、プーリング層(学習なし)を使わずにstrides=2のconv2d()(学習あり)で1/2にダウンサンプリングすることにしました。

Hyperopt:
Hyperoptに関しては前回同様Dropout率だけを最適化しています(合計3箇所)。探索回数はとりあえず10回。

スコア:
結果はこれまでのベストスコアである0.99771(Top 5%)まで向上しました。
この辺りまで来るとスコアを0.001上げるのはかなり至難の技で、正直0.997以上になるとは期待していませんでした。しかし予想以上に満足できる結果が得られたので、Digital Decognizer(MNIST)に関してはひと段落ついたという感じです。

ただし、調べれば調べるほど興味深い項目が登場してきて、今後試してみたいのは:
・他のMNISTデータセットで今回のモデルの精度を確かめてみる(KaggleのMNISTデータセットにオーバーフィッティングしていないかどうかの検証)。
・今回はHyperoptによってDropout率を自動的に決定させましたが、Dropoutを一般化したDropconnectというのもあるらしく、それを使うとどうなるか(Keras Dropconnect Implementation)?

Kaggleで勝つデータ分析の技術
門脇 大輔 阪田 隆司 保坂 桂佑 平松 雄司
技術評論社
売り上げランキング: 363


追記:
その後、既存のkeras.datasets.mnistのデータセット(60000+10000)で検証してみると0.997以上の正解率となりました。おそらくKaggleのMNISTデータも多数含まれているので似たような結果となったのだと思います(しかし偶然KaggleのMNISTデータだけにオーバーフィットしすぎているというわけでもなさそう)。

2018年11月16日金曜日

Kaggle Digital Recognizer(MNIST): Hyperopt + Data Augmentation

引き続きKaggle Digital Recognaizer(MNIST)のスコア向上のため、今回はHyperoptData Augmentationを組み合わせてみました。
結果として、これまで0.995前後(Top 18%)のスコアがでていましたが、今回の方法で0.99671(Top 9%)まで向上しました。それでも28000個あるデータのうち92個が間違っているということになります。


今回の方法:

・畳み込み層を増やしてもう少し特徴量検出できるようにする
・あまり自動調節させるハイパーパラメータは増やさない
・バッチサイズやデータ分割などは一般的な値にしておく
・Dropout率でモデル全体の精度を調整

要は、Hyperoptによるハイパーパラメータ最適化はDropout率(合計5個)だけに絞り、その他は固定。
探索回数は20回(GTX1060で約2時間)。

前回までと異なるのは:
・ダブルの畳み込み層をもう一式追加
・Dense層出力ユニット数の増加
・最後にData Augmentationでの訓練を追加
ということになります。

Data Augmantation(訓練データの変形加工:水増し)するためにKerasのImageDataGeneratorを使用しました。
ImageDataGeneratorを使うには元データのshapeを(-1, 28, 28, 1)にしておく必要があるようです。これまではCNNモデル入力層でkeras.layers.Reshape()を使って(784, )から(-1, 28, 28, 1)に変換していましたが、ImageDataGeneratorの入力次元数とモデルの入力次元数が異なるためエラーがでてしまい、モデルに入力する前に(-1, 28, 28, 1)へ変換することにしました。

流れとしては:
・訓練画像を元にHyperoptで最適化
・最適化されたモデルをData Augmentationで追加訓練
という二段階の訓練です。
Data Augmentationの訓練は結構すぐに収束してしまいましたが、その訓練の差なのかほんのわずか向上しました。


まとめ:
個人的には、これまでスコア0.995が壁になっていて、手動でいろいろ試してみたけれどもなかなか超えられませんでした。今回のスコアは期待していた以上に良かったので、MNISTに関してはもうこの辺で充分かと思いますが、あと試してみたいのは以下。
・最初からImageDataGeneratorで訓練
・交差検証(KFold

基本的なMNIST分類問題ですが、こうやってスコア向上を目標に試してみると、いろんなテクニックが見つかりかなり勉強になります。基本的なCNNアルゴリズムだけでなく、その他の方法も組み合わせることでわずかながらでも向上するということが分かったのもよかったです。

関連:
Kaggle Digital Decognizer(MNIST): Keras, fit_generator() + hyperopt


直感 Deep Learning ―Python×Kerasでアイデアを形にするレシピ
Posted with Amakuri at 2018.12.21
Antonio Gulli, Sujit Pal
オライリージャパン
販売価格 ¥3,672

2018年11月14日水曜日

GPyOpt: Digital Recognizer(MNIST) CNN Keras ハイパーパラメータ最適化

引き続きハイパーパラメータ最適化として、今回はGPyOptを使ってみました。これまでHyperasHyperoptを試して見ましたが、ベイズ最適化でも採用しているアルゴリズムが微妙に違うようなので試してみたという感じです。
個人的にはHyperoptが一番使いやすく感じましたが、GPyOptは以前scikit-learnで試したベイス最適化に近いアルゴリズムだったのでもう少し理解を深めてみようかと。
まだ手探り段階なので、使い方に関しては後から追記するかもしれません。


使い方:

ハイパーパラメータの設定:
まずはMNISTモデルにおけるハイパーパラメータの設定からです。今回はやや少なめで。

最適化するハイパーパラメータ:
・各層のドロップアウト率:連続値
・Dense層出力ユニット数:離散値
・validation_splitの比率:連続値

GPyOptではハイパーパラメータを以下のようなフォーマットで書きます。
params = [
    {'name': 'Dropout_0',        'type': 'continuous',  'domain': (0.0, 0.5)},
    {'name': 'Dropout_1',        'type': 'continuous',  'domain': (0.0, 0.5)},
    {'name': 'Dropout_2',        'type': 'continuous',  'domain': (0.0, 0.5)},
    {'name': 'Dropout_3',        'type': 'continuous',  'domain': (0.0, 0.5)},
    {'name': 'Dense_0',          'type': 'discrete',    'domain': (128, 256, 512)},
    {'name': 'Dense_1',          'type': 'discrete',    'domain': (64,128, 256)},
    {'name': 'validation_split', 'type': 'continuous',  'domain': (0.1, 0.3)}
]

リスト化されたディクショナリーで(ここを参考に)、

・'name' : パラメータ名
・'type' : 'continuout'(連続値)、'discrete'(離散値)、'categorical'(分類値)
・'domain' : 適用範囲または選択肢を()で括る

となるようです。


CNNモデルの構築:
次にモデルを構築します。前回同様MNIST分類用のCNNを使います。このモデルからはベイズ最適化するための評価値となるlossかaccが求められればいいのですが、

・loss
・acc
・model
・history

の4種類を戻り値にしておきました。
model.fit()させてEarlyStoppingで打ち切りになった最後のval_lossとval_accの値を参照しています。
    loss = hist.history['val_loss'][-1]
    acc = hist.history['val_acc'][-1]
modelやhistoryは不要ですが、後から参照するかもしれないので一応入れておきました(使うかどうかは分からない)。
return loss, acc, model, hist

上記パイパーパラメータに対応する変数部分には、x[:, 0]などと引数にインデックス番号をつけるようですが、どれが何番目かはわかりにくいのでハイパーパラメータの'name'から参照できる関数をつくってみました。
model.add(Dropout(Param('Dropout_0'), seed=seed))
このように書き込めばx[:, 0]へ自動変換してくれます。Hyperoptなどでもディクショナリーのキーを使っていたので、このほうが個人的には使いやすいかと(リスト内容を変えた場合にインデックス番号だと、他の番号も変わってしまうのが面倒なので)。
注意点として、最初に書いたハイパーパラメータはリストであるのに対して、この変数は2次元のndarrayに変換されてから代入されるようです。この変換関数は以下(cnn_model関数内)。
    def Param(p_name):
        p_index = [p['name'] for p in params].index(p_name)
        p_type = params[p_index]['type']
        
        if type(x) is np.ndarray:
            if p_type == 'continuous':
                return float(x[:, p_index])
            else:
                return int(x[:, p_index])
        else: # list
            if p_type == 'continuous':
                return float(params[p_index]['domain'])
            else:
                return int(params[p_index]['domain'])
後で最適化されたハイパーパラメータリストを直接渡せるようにしてあります。引数がndarrayならx[:,0]のような2次元ndarray、listならlist内のスカラー値へ変換後代入。また今回の場合、離散値はすべて整数だったのでintかfloatかも振り分けています。


フィッティング関数:
上記CNNモデルを以後のベイズ最適化関数GPyOpt.methods.BayesianOptimization()に直接渡してもいいのですが、CNNモデルからは4種類の値を出力することにしたので、このf(x)関数を間にはさんで必要な評価値だけを渡せるようにしました。今回はaccを評価値として渡すことにし、最小化するためにマイナス反転して-accにしています。
前述のように引数のxは二次元のnumpy.ndarrayになるようです。今回は7種類のハイパーパラメータがあるので、x.shapeは(1,7)になります。最初に設定したハイパーパラメータはリストでありndarrayではないので、この辺をいじる場合は変換するなどの工夫が必要です(このサンプルを参照)。
実際は、
def f(x):
    x = np.atleast_2d(x)
    fs = np.zeros((x.shape[0],1))
    for i in range(x.shape[0]):
        loss, acc, model, hist = cnn_model(x)
        fs[i] += np.log(acc)*(-1)
    return fs
このように書いたほうがいいのかもしれませんが、戻り値は1次元のndarrayだったので、今回は省略して以下のようにしました。対数変換したほうがいいのかもしれませんが効果の違いは検証していません。
def f(x):
    loss, acc, model, hist = cnn_model(x)
    return -acc

ベイズ最適化関数:
GPyOpt.methods.BayesianOptimization()に先程のf(x)関数とハイパーパラメータリストparamsを渡し、その他初期探索値や獲得関数などを決めます。獲得関数はデフォルトではEIになっていますが'EI_MCMC'を選んでみました。'EI_MCMC'を選択する場合は、model_typeで'GP_MCMC'を選んでおかなければいけないようです。
initial_design_numdataは20に設定しましたが、これはどのくらいがいいのかは不明(デフォルト:5)。探索する前のランダムな開始点の数なのかもしれませんが、今回の7次元に対してどのくらいが適当なのか?探索点は徐々に追加されながらフィッティングしていくと思うのでデフォルトの5でもいいのかもしれません。入れた回数だけループするようです(20回で約1時間)。
こまかな設定がいくつかありますが、まだ使いながら試している段階です。

次に、run_optimization(max_iter=50)で最適化が始まります。イテレーションを50回に設定しました。7種類のハイパーパラメータに対してどのくらいが適当なのかはまだ不明(ハイパーハイパーパラメータ)。50回で約4時間かかりました。
ループが終了すれば最適なハイパーパラメータが見つかったことになります。設定した回数より早く終わることもあります。


最適化されたハイパーパラメータの取得:
以下で結果を取得することができます。
x_best = opt.x_opt
print([i for i in x_best])

y_best = opt.fx_opt
print(y_best)
そうすると、
[0.1732254530746627, 0.39555160207057505, 0.14877909656106353, 0.07323704794308367, 128.0, 128.0, 0.1471216716379693]
-0.9945388349514563
と値が出てきて、最初のリストが最適化された各ハイパーパラメータ。
下の値はそのときのロス値。今回はaccをマイナス反転してあるのでaccの値と同じ。精度0.994以上でているのでまあまあの結果です。


最適化されたハイパーパラメータをモデルに適用:
上記結果と同時にベストモデルやベストウェイトを直接取り出したいのですが、そのような方法がGPyOptにはないようなので、最適化されたハイパーパラメータをCNNモデルに入れ直して再度訓練させてみました。
一応、上記ハイパーパラメータリストを元々のディクショナリー型のリストへ移し替えてからCNNモデルに渡しています。CNNモデルの引数がlistの場合はスカラー値を各変数に代入するような関数にしています。
CNNモデルはEarlyStopping機能をつけているので15ループで収束してくれました(4分25秒)。
このモデルを利用して提出用データを予測します。


まとめと結果(スコア):
最終的にスコアは0.99457でした。まあまあいい結果です(それでも手動調整のベストスコアである0.99528には達していない)。約6時間でこの結果ですが、もっと回せば向上するかはわからないです。これ以上のスコアを出すには、data augmentationでデータを水増しするなど必要かもしれません。
GpyOptはHyperoptに比べるとやや使いにくいという印象でした(サンプルも少ない)。しかしやりたいことに応じて使いやすく改造すればいいのかもしれません。もともとのアルゴリズム自体は優れていると思うので、いくつかを同時に試して結果的にいい方を選ぶ感じでしょうか。時間的にもHyperoptのほうが速いかもしれませんが、どのライブラリであっても数時間はかかるので時間よりも精度がでるほうがいいと思います(仕事で使っているわけではないので)。
このほか気になるライブラリとして、SkoptKoptPyBOSpearMintなどありますが、とりあえずはもう十分かと。

これまでは機械学習理論やアルゴリズムの種類を覚えていくことが面白かったのですが、Kaggleをきっかけにスコア(精度)を少しでもあげようとすることにも興味を持てたのはよかったです。実際使ってみて、その結果から次にどうすればいいのかという具体的な疑問が次のモチベーションになるので、より理解も深まりつつ面白くなっていく感じです。

追記:
その後、4つのDropout率だけをハイパーパラメータとして最適化した結果スコア:0.99557まで向上(これまでのベストスコアは0.99524)。
その他のハイパーパラメータは以下のように固定。
validation data:test_size=0.15
Dense_0 output units: 256
Dense_1 output units: 128
batch_size=32

そして最適化においては以下の探索回数に設定。
initial_design_numdata=30(2h 29mins)
max_iter=100(stop at 52: 7h 47mins)
max_iterは最大100回に設定しましたが途中52回で収束し停止しました。
合計で10時間30分(GTX1060で)。

関連:
Kaggle Digital Recognizer(MNIST): Hyperopt + Data Augmentation
Kaggle Digital Decognizer(MNIST): Keras, fit_generator() + hyperopt



機械学習スタートアップシリーズ ベイズ推論による機械学習入門 (KS情報科学専門書)
Posted with Amakuri at 2018.12.21
須山 敦志
講談社
販売価格 ¥3,024(2018年12月21日20時40分時点の価格)

2018年11月10日土曜日

Hyperopt:Digital Recognizer(MNIST)のハイパーパラメータの最適化



今回は、HyperoptでMNISTのハイパーパラメータの最適化を行いました。Hyperoptは前回試したHyperasの元となっているライブラリです。Hyperasはシンプルに使える反面やや扱いにくい部分(慣れていないだけかもしれませんが)もあったため、大元のHyperoptで書き直してみました。

データは前回同様KaggleのDigital Recognizer(MNIST)で、最適化するハイパーパラメータは以下です。

・Dropout率
・Dense層出力ユニット数
・batch_size数
・validation_splitの比率

Hyperoptの使い方:

ハイパーパラメータのディクショナリー設定:
まずハイパーパラメータのディクショナリーを以下のようなフォーマットで用意します。

params = {
    'Dense_0':          hp.choice('Dense_0', [128, 256, 512]),
    'Dense_1':          hp.choice('Dense_1', [64, 128, 256]),
    'Dropout_0':        hp.uniform('Dropout_0', 0.0, 1.0),
    'Dropout_1':        hp.uniform('Dropout_1', 0.0, 1.0),
    'Dropout_2':        hp.uniform('Dropout_2', 0.0, 1.0),
    'Dropout_3':        hp.uniform('Dropout_3', 0.0, 1.0),
    'batch_size':       hp.choice('batch_size', [16, 32, 64]),
    'validation_split': hp.uniform('validation_split', 0.1, 0.3)
}

離散値の場合はhp.choice()、連続値の場合はhp.uniform()を使います。このあたりはHyperasと同じような感じです。このほか乱数用のhp.randint()や正規分布用のhp.normal()などいくつかあります(ここに書いてあります)。


ハイパーパラメータの挿入と戻り値の設定:
次はモデルの構築です。MNISTデータの前処理をしておいてから、CNNを用いてMNIST分類モデルを構築します。そして最適化したい変数の部分(以下の場合:CNN層内のドロップアウト率)に、

model.add(Dropout(params['Dropout_0'], seed=seed))

という感じで挿入しておきます。
model.compile()、model.fit()したあと model.evaluate()でlossとaccを求めて、その値を戻り値とします。サンプルなどではlossのかわりにaccを評価値として次のfmin()関数に渡していますが、どちらがいいのかは不明。または、hist=fit()のhistoryからhist.history['val_loss'][-1]で最後のロス値を取得する方法でもいいのかもしれません(あるいは'val_acc')。尚、accを渡す場合はマイナスをかけて最大値を最小値に反転させておく必要があります。
またモデルなどその他の値やオブジェクトを渡すときはディクショナリーにするといいようです。ディクショナリーにする場合は、次のfmin()関数に値を渡すために'loss'と'status'のキーが最低含まれていないといけないようです。今回は追加でモデルも含めたので以下のような戻り値としました。あとでベストmodelを参照する場合は追加しておくといいと思います。

return {'loss': -acc, 'status': STATUS_OK, 'model': model}


最適化:
最後に、best=fmin()で最適なパラメータを見つけます。fmin()へモデルとハイパーパラメータディクショナリーを渡し、探索回数などを指定して最適化します。探索回数は多いほどいいと思いますが、それなりに時間はかかります(数時間とか)。
trialsには探索結果の記録が保持されるので後で参照します。

trials = Trials()
best = fmin(fn=cnn_model, 
            space=params, 
            algo=tpe.suggest, 
            max_evals=20, 
            trials=trials,
            verbose=1,
            rstate=np.random.RandomState(seed))

fn:CNNモデル(前述の'loss', 'status', 'model'が戻り値)
space:パイパーパラメータのディクショナリー
algo:使用するアルゴリズム(TPEなのでこのまま)
max_evals:探索回数
trials:探索記録保持先
verbose:ログ出力
rstate:乱数固定


結果参照:
best=fmin()からは最適化されたパラメータのディクショナリーが出力されます。そのままだと、hp.choice()の場合リストのインデックス番号が返されるので、

space_eval(params, best)

で実際の値に変換出力してくれます(以下)。

{'Dense_0': 512,
 'Dense_1': 256,
 'Dropout_0': 0.19796353174591008,
 'Dropout_1': 0.30328292011950164,
 'Dropout_2': 0.7005074297830172,
 'Dropout_3': 0.3974900176858912,
 'batch_size': 64,
 'validation_split': 0.16617354953831512}

あらかじめtrials=Trials()と定義しておけば、trialsの中に全ての情報が記録されるので、必要に応じて値やモデルを参照することができます。

trials.best_trial['result']

で以下が出力されます(複数回探索した中でのベストの結果)。lossはaccをマイナス反転したものなのでaccのこと、modelはそのときのベストモデル、statusは処理が無事完了なら'ok'。

{'loss': -0.9935714285714285,
 'model': <keras.engine.sequential.Sequential at 0x7fc5c3da87f0>,
 'status': 'ok'}

ベストモデルは、

best_model = trials.best_trial['result']['model']

によって参照することができるので、このモデルを使ってpredict()することができます。

まとめ:
前回のHyperasよりも使い勝手はよさそうです。それほど面倒なコーディングをすることもないので、個人的にはHyperoptのほうが便利かと。要は、パイパーパラメータディクショナリーとモデルを最適化関数に入れれば答えがでてくるということです。
詳しいドキュメントがないので(ここくらい)、細かな使い方はわからないのですが(ソースを読み解くしかないかも)、いろいろ応用できそうです。
今回はmax_evals=20で20回探索(NVIDIA GTX1060で49分)した結果、スコアは0.99257でした。まあまあの結果でしたが、実際100回以上(数時間)は回したほうがいいのかもしれません。

こちらのサイトでは様々なベイズ最適化ライブラリーを比較しており、時間的にはHyperoptが一番速いようです。10次元以下の最適化であればPyBOが優れているようで、それ以上の次元ではどれも遅くなるようです。また20次元や40次元になるとほとんどのライブラリが最適化できなくなるようで、Spearmintが20次元でも機能していたようです。

追記1:
その後100回(約6時間)回してみましたがスコアは0.99185という結果。validationセットでのスコアは0.995だったので向上しましたが、オーバーフィッティング気味だったったのか結果的にはいまいち。いずれにせよ0.992前後が限界という感じ。CNNの層を少し改造するか、kerasのImageDataGeneratorでデータ水増しした方がいいのかもしれません。

追記2:
validation_splitを0.2に固定して、Dropout率とDense層出力ユニット数だけをハイパーパラメータとして10回ほど探索すると0.99442まで向上しました。普通に考えてvalidation dataは少ないほどval_accは上がってしまうと思うので固定にしたほうがよさそうです。

関連:
GPyOpt: Digital Recognizer(MNIST) CNN Keras ハイパーパラメータ最適化
Kaggle Digital Recognizer(MNIST): Hyperopt + Data Augmentation
Kaggle Digital Decognizer(MNIST): Keras, fit_generator() + hyperopt


2018年11月9日金曜日

HyperasでDigital Recognizer(MNIST)を試す


前回AutokerasでMNISTを自動化分類してみましたが、今回は特定のハイパーパラメータを最適化するHyperasHyperoptのkerasラッパー)を試してみました。最適化にはいろいろ種類があって、HyperasはTree-structured Parzen Estimators (TPE)というガウス過程の欠点を補った方法のようです。グリッドサーチよりも計算量が少なく、ベイズ最適化のように探索していくようですが、微妙に計算方法が異なるようです。


使い方:
Hyperasの使い方は非常に簡単で、
例えばドロップアウト率を0から1の範囲で最適化したい場合は、

Dropout({{uniform(0, 1)}})

複数の選択肢がある場合は、

optimizer={{choice(['adam', 'rmsprop', 'SGD'])}}

を通常の変数部分に入れて行くだけです。ディクショナリーを用意する必要もないので楽です。

ちなみに、今回最適化したハイパーパラメータは以下。
・Dropout率
・Dense層出力ユニット数
・batch_size数
・validation_splitの比率


ルール:
ただし少しだけルールがあるようです(このあたりがやや使いにくいかも)。
HyperasはHyperoptのラッパーで、一旦コーディングした内容をそのままHyperoptへ翻訳変換してしまうようです。そのためまずdata関数とmodel関数を作り(カプセル化するフォーマット)、その後dataとmodelをoptim.minimize()へ渡してモデル探索します。基本的には以下のようなフォーマット。

def data():
  データの読み込み
  データの前処理
  return X_train, Y_train, X_test, Y_test

def model(X_train, Y_train, X_test, Y_test):
  model = Sequential()
  ...
  model.compile()
  model.fit()
  ...
  return{'loss':-acc, 'status': STATUS_OK, 'model': model}

best_run, best_model = optim.minimize(model, data, ...)


それから、data関数とmodel関数外で定義したグローバル変数はdata関数とmodel関数のスコープに届かないようです(importしたモジュールなどは問題ない)。よってグローバル変数などは、新たにdata関数とmodel関数内に定義しないといけなさそうです。globalを使えばいいのかも。
それと引数や戻り値の変数名も一致させておかないとエラーがでました。
また、model関数内で{{uniform()}}や{{choice()}}を含んだ行を#でコメントアウトしても、Hyperoptへ変換される際には最適化する変数として読み込まれてしまうので消去する必要があります。

最初なぜエラーがでるのかわかりませんでしたが、Hyperoptへそのまま翻訳変換するためそういうルールのようです。そういうことから、やや複雑なことをするならHyperoptを使ったほうがいいかもしれません。

ということで、このルールに戸惑いましたが何とかコーディングしたものが以下。

最適化されたハイパーパラメータはbest_runによって出力されます。

{'Dense': 64,
 'Dense_1': 64,
 'Dropout': 0.21099660541107612,
 'Dropout_1': 0.29327102196615873,
 'Dropout_2': 0.7302305870589935,
 'Dropout_3': 0.258985915829989,
 'batch_size': 32,
 'validation_split': 0.10388179991112252}

今回のハイパーパラメータはこんな感じ。
デフォルトだとchoice型のハイパーパラメータはリストのindex番号として出力されるので(例えば上記の'Dense': 64がデフォルトだと'Dense': 0と表示される)、optim.minimize()内に

eval_space=True

を加えておけば、リスト内の実際の値として出力されます。
また今回は、max_evals=10としました。これは探索回数のことだと思うのですが、最低どのくらいが必要かは試してみないとわかりません(ハイパーパラメータのハイパーパラメータ)。探索回数が多いほど正確に予測できると思いますが、その分時間もかかってしまいます。以前ベイズ最適化を試した時は15回くらいの探索でブラックボックス関数にほぼ近似していましたが、Hyperas(Hyperopt)のTPEアルゴリズムにおいてはどうなのか?



まとめ:
まだ使い方に慣れていないためか、スコアは0.98371(max_evals=10)でいまいちでした。何回かやり直してみたので、それなりに時間がかかってしまいました(エラーや失敗も含めて)。そうなると、まだまだ手動調節(スコア:099528)のほうがMNISTの場合であれば早いという感じです。
ただ、手っ取り早く試すにはHyperasはいいのかもしれません。少し使い勝手が悪いので、流れがわかればHyperoptへ移行してもいいと思います。
このほかKeras用の最適化ライブラリでTalosというものも試してみました。Talosもシンプルで簡単ですが、グリッドサーチやランダムサーチをベースにしているのか、最適化範囲や選択肢が増えると探索にとてつもなく時間がかかってしまったので一旦中止。
次は、Hyperoptで実装しなおすか、GpyOptを試してみようかと思います。

追記その1:
その後、max_evals=20で探索すると0.99200までスコアがあがりました。

追記その2:
さらに、max_evals=100まで試すと0.99271まで向上。
以下が最適化されたハイパーパラメータ:

{'Dense': 256,
 'Dense_1': 128,
 'Dropout': 0.01772328721174527,
 'Dropout_1': 0.4978692970747428,
 'Dropout_2': 0.13740853439432676,
 'Dropout_3': 0.01305834864014449,
 'batch_size': 32,
 'validation_split': 0.12455270876039955}

結構まっとうな値です。たまにDropout:0.7以上がでるときもありますが、今回はどれも0.5以下。
Denseの出力ユニット数はそれぞれ選択肢の最大値256と128なので、もう一段階512と256へ上げてもいいかもしれません。
GPU:GTX1060(6GB)で数時間回せばこのような結果が出せるのでそれなりに使えそうです。探索回数であるmax_evalsは最低でも100、もしくは500や1000で1日中回すのがいいかもしれません。

人気の投稿