学習の最適化に纏わる考察
評価関数の学習をやっている人達における、バッチサイズや学習率に関する話は、
アラサー男子の健康トークのような立ち位置であると言えます。
どういう理屈かはさっぱりわからないですが、100万局面づつ更新しながら10セット学習するのと、1000万局面を1回学習するのとでは、何故か1000万局面を1回学習させるほうが強くなるようなんですよ。不思議ですね。
— 桜丸@mEssiah_β1 (@sakuramaru7777) 2018年3月9日
私自身は、学習におけるパラメタ調整は落とし穴のようなものだと考えています。
微調整した所でそこまで強くはならないのですが、設定を誤ると学習を大失敗させる ポテンシャルはあるからです。
故に、どういうケースでどういう失敗をするのかを考えておくことは有意義と言えましょう。
【取り敢えず実験】
以下のコードを実行すると、バッチサイズ、学習率を変えながら1次元問題の最適化が出来ます。
勾配は -x + 乱数 なので、理想的にはx=0になってほしいところです
import random import sys import math def getgrad(x,batchsize): grad = 0 for i in range(batchsize): grad += -x + random.uniform(-1.0,1.0) return grad def adagrad(initx, batchsize, epochsize, eta): x = initx g2 = 0 save = 10 # 適宜変える for i in range(epochsize): g = getgrad(x,batchsize) g2 += g*g x += eta * g / math.sqrt(g2) if i % save == 0: print(x) adagrad(float(sys.argv[1]),int(sys.argv[2]), int(sys.argv[3]), float(sys.argv[4]))
【バッチサイズを変えながら計算してみる】
データ数を1000万、batchsizeを1,100,1000に変えた結果が此方です。
横軸が食わせた教師の数、縦軸がxの値(0に収束するはず)です。
青:1000 赤:100 黄:1 です
基本的に収束した後の綺麗さはバッチサイズが大きいほど、
収束速度はバッチサイズが小さいほど早くなります。
【バッチサイズを大きくすることの数理的な意味】
雑に言えば、バッチサイズの平方根に比例してノイズに対する耐性がつきます。
というのも、各特徴量に対してホワイトノイズ(統計誤差)が乗ったものが勾配になると考えれば、
真値から特徴量がずれていた時に勾配が真値の方向を向くか否かは真の勾配とノイズの
SN比に依存しており、ノイズレベルはサンプル数の平方根に反比例するからです。
【将棋の学習への適用】
将棋では一度の学習でxx局面を使うという形でバッチサイズを決めていますが、
各々の特徴量について、バッチ毎に何回出てくるかは異なります。
故に特定の特徴量についてバッチサイズや学習率を良くした所で、その他の特徴量に
ついても設定が最適化されているかといえば、それは結構怪しいです。
一番避けたいのは出現回数が少ない特徴量が変な値を持つことで、マイナーな局面について
大幅な読み誤りをしてしまうことです。それらを避けるには
・出現回数の少ない特徴量は更新させない or 学習率を下げる(河童絞りでよく使う)
・学習速度を犠牲にバッチサイズを大きくしておく(雑巾絞りの追加学習でよく使う)
が有効ですが、最強の解決策は
・教師データの数を課金して増やす
ことです。しかし、私自身は課金してデータを増やすのはあまりよい戦略だとは思っていません。
というのも、教師データの質に問題があれば数をどんなに増やしても強くはならないからです。
【私が考える学習パラメタ弄り論】
学習に失敗した際に、何より先に知るべきなのは教師データの質に問題があるのか量に問題があるかです。
基本的にはバッチサイズを大きくしたり河童絞りのような手を使って、変な学習をさせないことを重視して(必要ならReMUとかを使って)
時間をかけて勝率を測定してみるのが良いと思ってます。