Edited at

optuna をマラソンマッチのパラメータ探索に使う


TL; DR


  • subprocess つかって渡す

  • ちゃんと動くし、複数パラメータ弄るなら便利そう


optunaとは

PFN のリリースより

Optuna はハイパーパラメータの最適化を自動化するためのソフトウェアフレームワークです。

とのこと。使用例とかを見るに明らかに機械学習のハイパーパラメータを調整する目的で開発されています。

公式 Docs

chokudai さん1がこう言ってるのを見て自分でもやってみました。


導入

pip install optuna

以上。

なんかしら RDB が有ると嬉しいので sqlite3 あたりが入ってなければ入れておく。


マラソンで使う


実行コード側の調整

マラソンの提出コードは python ではない 2 のでそこら辺を何とかする必要があります。



HTTF 2019 予選 における 私の提出コード を例にします。3 L 埋めと命令圧縮あり、中央一列 D なしの条件です。

入力に関しては既にランダム入力を生成する関数を用意していたので、今回は考えないことにします。

私のコードについてざっくり言うと、盤面のどこか一点を .LR で置き換えながら焼きなまししているだけです。このままでは焼きなましの開始温度以外調整するべきパラメータが見当たらなかったので、交換している部分のコードを


submit.cpp

char LR = (prng.rnd()&1)?'L':'R';

LR = (mother.board[i][j]=='.' ? LR : '.');

こう


optuna.cpp

char LR = (prng.rnd()&1)?'L':'R';

LR = ( LRprob * UINT_MAX > (double)prng.rnd() ? LR : '.');
if (mother.board[i][j]==LR) continue;

することで、無理やり調整対象のパラメータ LRprob を生成することにしました。

今回最適化したいのは 30 テストケースの得点合計なので、 test() 関数の最後の出力先を stdout に向けておきます。

あとは開始温度 startTemp 共々コマンドライン引数で調整できるようにすれば完成です。4


optuna.cpp

int main(int argc, char *argv[]){

ios::sync_with_stdio(false);

startTemp = atof(argv[1]); //const は外してます
LRprob = atof(argv[2]);

//exec();
test();
}



optuna を実行する

標準入出力で頑張るので subprocess を使います。


opt.py

import subprocess

import optuna

def marathon(trial):
temp = trial.suggest_loguniform('temp', 1, 10) #なんとなく log。もとの提出コード中では 5.0
lrrate = trial.suggest_uniform('LRrate', 0.0, 1.0)
cmd = ["./optuna.cpp.out", str(temp), str(lrrate)]

bak = subprocess.run(cmd, stdout=subprocess.PIPE).stdout
return -(int(bak))

if __name__ == '__main__':
study = optuna.Study(study_name='httf', storage='sqlite:///hoge.db')
study.optimize(marathon, n_trials=1000) #時間と相談


optuna が頑張ってくれるのは最小化問題なので marathon() 関数の返り値に - を付けます。storage の名前は適当

optuna.cpp.outopt.py を同一ディレクトリに入れて、

optuna create-study --study-name "httf" --storage "sqlite:///hoge.db" # 同時実行するときは先に create-study しておかないと怒られる

nohup seq 12 | parallel -j 12 -n0 python opt.py & # 12 並列

してしばらく待ちます。複数プロセス立ち上げる 5 だけで、storage の中身見ながらよしなにしてくれるらしい。


結果を見る

まだ beta ですが html でビジュアライズしてくれます(要:pip install bokeh)。

optuna dashboard --storage sqlite:///hoge.db --study httf --out out.html

bokeh_plot.png

結果の方は python 書いても取り出せると思いますが、db ファイル叩けばすぐに見れます。

sqlite3 hoge.db

sqlite> select * from trials where value = (select MIN(value) from trials);
599|1|COMPLETE|-134157.0|2018-12-09 21:43:03.166319|2018-12-09 21:44:11.182819
sqlite> select * from trial_params where trial_id = (select trial_id from trials where value = (select MIN(value) from trials));
1200|599|LRrate|0.348037400953956|{"name": "UniformDistribution", "attributes": {"low": 0.0, "high": 1.0}}
1197|599|temp|5.11890825193514|{"name": "LogUniformDistribution", "attributes": {"low": 1, "high": 10}}

適当に plt してみる。たしかに真ん中あたりが赤い

figure.png

LRprob の方はともかく、開始温度はほぼ 5.0 なので、提出時点でのパラメータ調整がうまく行ってることが証明されました(うれしい)


提出してみる

しました

手元で 134157 点がサーバ上で 134116 点。結構近いですね…

optuna 前が 132891 点だったので地味ながら向上しています。


まとめ

optuna より subprocess の使い方を調べてたら使えるようになった感じ。とても使いやすいです。

optuna を回す時間が確保できる長期間マラソンにおいては使えるケースが多そうです。6 焼きなましの温度だけであれば人力でもある程度は詰められていることがわかったので、もっと多変数の系に適用してやっと本領発揮なのかなあという気がします。

焼きなましに限らず、コード内で乱数を使っているとどうしても結果が怪しくなります。今回で言えば(本番環境での評価に合わせて)30 ケースの合計値での評価でしたが各ケースの点は相当ブレてるので、そこら辺も加味して使っていきたいです。





  1. 競プロサイト AtCoder の社長で、マラソンマッチが国内で一番 / 世界でも有数のつよいひと。この週末は忙しそうだった。 



  2. 遅いので。chokudai さんは C#, 他にも C++ がメジャー。 



  3. 本番終わったあとで他の人の知見を元にチューニングしたせいで、ただでさえ乏しい可読性が欠片もなくなってるんですけど ゆるして 



  4. 競プロ勢なので、ここらへんの変数をグローバルにポン置きしても心が痛まない 



  5. GNU parallel についてはここ(日本語) とか man とか 



  6. HTTF (8 時間) だとパラメータ詰めるより先にやることがあるだろうという感じですが