【python】Optunaの使い方練習
こんにちは。今回はDeep Learningにおいて重要なニューラルネットワークのハイパーパラメータを自動で提案してくれるライブラリ、Optunaについてまとめていきます。これは、スパゲティコードの治療回にて供養が必要となった勉強用プログラムの第一弾です。ソースコードはここに載せています。それでは早速観ていきましょう。 プログラムの概要図 このプログラムは大きく分けて2種類のプログラムから構成されます。一つはニューラルネットワークの構成(ハイパーパラメータ)を決定するプログラム、もう一つはその結果をもとに実際にニューラルネットワークを作成するプログラムです。この2つのプログラムは両方ともデータ生成プログラムをimportしているために、下記の通り構造がやや複雑になっています。 小話 これを実装するにあたり、TuningHyperParameter.pyとload_studyから呼ばれているData_preparing.py、およびそこから呼び出されているCalculate_payoff.pyは2つまとめてlibraryフォルダを用意して分けました。したがってdirectory直下にはTuningHyperparameter.py, load_study.py, dataフォルダ, libraryフォルダしかありません。よって半年ぶりにこのコードを読んだ時も、TuninghyperParameter.pyとload_study.pyの関係さえ思い出せればなんとかプログラム設計を読み解くことができました。でも、上図をREADMEに添付するだけでこんなのはわかるので、いかにドキュメンテーションが大切か思い知らされますね。 TuningHyperParameter.pyの概要 このプログラムの中にはTQNetworkクラス(Tuning Q networkより)が用意されていて、何もなければオブジェクト作成時に下記のrun_study_mainが走るようになっています。 run_study_main() study = optuna.create_study()、study.optimize()の行が一番大切で、そこでハイパーパラメータの探索回数や結果の保存場所を規定しています。それ以外は探索結果のパラメータを表示する部分なので対して重要では無いですが、study.best_trialで各種パラメータが返ってくるのが便利ですね。詳細はoptuna.study.Studyのリファレンスを参照。ちなみに上記プログラム中で出てくるself.objectiveは同クラス内の別の関数を指しています。それが下記です。 objective() これだけ読んでもさっぱりだとは思いますが、最初の7行でデータを作成して、次の2行でモデルとoptimizerの候補を作り、optuna君に学習最適化をしてもらってmodelとoptimizerを最適化するという流れになっていることだけ分かれば良いと思います。このコードを丸コピする場合もdata_creator関数を自分用に改造すれば同じコードで行けるはずです。 create_model() ここでは実際にニューラルネットワークのレイヤ数をいくつにするか、ノードをいくつにするか、weight_decayをいくつにするかなどを探索できるようにしています。optunaから呼び出される細かい数値は変更可能ですが構成はあまり変える必要はないかなと思っています。 create_optimizer() optimizerについても探索させることができますが、正直Adamで固定しても良いかなと思っています。このコードを書いたときは探索させる気があったのでcreate_model()と同様の書き方で実装しています。この部分で重要なのはlearning_rateのチューニングです。学習率は低すぎても高すぎてもうまく行かないので、Optunaのような自動探索機に決めてもらえるのは結構嬉しい。 load_study.pyの概要 create_model() load_studyの主要部分であるこの関数、実はTuningHyperParameter.pyのcreate_model()とほぼ同じです。違う点は、探索結果のstudyから最適値をもらってmodelを構成する点です。一応下記にコードを載せて置きます。 learn_main() モデルを作ることができたら、それを使って学習を行います。これはごくフツーのmodel.fit()で良いので簡単です。学習が終わったらmodel.save()で保存して終了です。 まとめ スパゲティコード供養第一弾ということで、Optunaの使い方と応用を紹介させていただきました。自分のコードの稚拙さや改善ポイントの多さに気付かされますね。俺・・頭痛が治ったら、もっとちゃんとドキュメンテーションしながらプログラムを組むんだ・・(強い決意)。ということで、千里の道も一歩から。次回もお楽しみに。
【哲学】ポケモンGOバトルリーグでLegendになった話
こんにちは。RockinWoolです。今回はいつもと違って人生観の話をしようと思います。若輩の身ではありますが、勉強やスポーツに対する考え方をここで残しておいて将来の自分に活が入れられるようにしておきたく記事にしました。人生は人それぞれなので考え方は沢山あるのは承知ですが、こんな考え方もあるんだと思っていただけると嬉しいです。 そもそもポケモンGOバトルリーグとは? 皆さんご存知の大人気?スマホゲームのポケモンGO。このポケモンGOの中にある対戦要素がポケモンGOバトルリーグです。本家ポケモンのレート対戦に近くポケモンの厳選が必要なところも同じですが、ポケモンGOには孵化システム等が無いので厳選のためには対象ポケモンを乱獲するしかありません。つまり、普段からどれだけポケモンGOを起動してポケモンを捕獲しているかが大切になります。従って、長期間にわたって廃人レベルで活動している人には基本的に勝てません。 哲学: 趣味でやるにしても一度は頂点に立ちたい このようにポケモンGOバトルリーグは新規にはハードルが高めです。しかし、個人的な哲学になりますが、趣味でゲームをしていたとしても一度は頂点にたどり着きたいと思っています。これは「持論を述べる前に実績を残せ」という考え方です。実績が無いのに他人に難癖を付けるような人にはなりたくない。ですが、私たちは悲しいことに必ず老害になってしまいます。しかし、実績のない老害なんて最低です。せめて自分は実績を残してから疎まれたい、それがこの哲学の根本です。 実現方針: 100勝ち越しを目指す さて、ポケモンGOバトルリーグで頂点、すなわちLegendランクを達成するためにはどのようにすればよいのか。そのためには、GOバトルリーグのシステムを理解する必要があります。 レートシステムの穴 GOバトルリーグは5戦1セットでレートが計算されます。例えば3勝2敗したセットはレートが+10され、2勝3敗されたセットは-10されるといった感じです。そして、大体初期レートは1900~2200くらいであることが多いです。日々5セットのみ対戦できるという対戦ゲーにしては謎の縛りがあるので、体感ですが一日25戦を戦うと平均±30の変動が発生します(個人的には頭痛がちょうど発生するくらいなのでちょうどよい)。さて、この条件でLegendに行くにはどうすれば良いのか?実はこのシステム、同じ勝率でも勝ち越してさえいれば、総対戦数が大きくなればレートも高くなるのです。そして、3勝2敗の1勝ち越しで+10であることから初期レート2000から始めると100勝ち越しすれば良いことも分かります。従って、毎日可能な限り試合をこなすことでレートをハックできるのです。 特殊リーグという救済処置を活用する ポケモンGOバトルリーグは1週間ごとに対戦ルールが変わります。対戦ルールは大きく分けると4つ存在し、通常スーパー、通常ハイパー、通常マスター、そして特殊リーグです。しかしながら、通常スーパー、ハイパー、マスターにおける実用的なポケモンは決まっていて、しかも常人では育成の難しいものばかりです。なので、凡人でも勝つ見込みがあるのは特殊リーグに限られます。また、特殊リーグ内でも強いポケモンは決まっていますが、育成難易度の低いポケモンが多く初心者でも何とかやっていくことができます。 動画サイトで環境を把握して育成しておく ターゲットを特殊リーグに据えて、目標を100勝ち越しとしたところで、結局ポケモンがいないと勝つことができません。そこで、特殊リーグの過去の環境を事前に調べて育成するポケモンを決めておきました。結果的にこれは好転したり暗転したりしてうまくいかなかったのですが、良い勉強にはなりました。 パーティを固定する 廃人ではないため、特殊リーグ期間中にパーティを変更することは非常に難しいです。育成コストもそうですが、立ち回りやポケモンに対する知識が不足しているためです。なので、一つのパーティをマスターして、技術を磨くしか方法がありません。環境を読み違えればほぼ即死です。何回か読み間違えたんですが。この方法の良いところは、一見勝てないパーティにも技術で逆転できることに気づいたりすると逆に爆勝ちできるようになる点です。あとは大抵の負けが自責になるので、パーティ負けといって逃げる癖がつかないことですね。 実践 エスパーカップ(9/16 ~ 9/30) 前回のエスパーカップはGギャロップとカラマネロが強いと言われており、今回もその傾向は大きく変わらないと予測してGギャロップとカラマネロを軸にしつつ、その2体にやや不利程度で戦えるGヤドランを採用しました。 同発時に攻撃力を参照するという救い 実はこのパーティ、凡人が育成したため各ポケモンの個体値が良くないです。しかし、このことが逆に功を奏しました。それが必殺技を同時発動した際に攻撃力が高い方が先に打てるという仕様に強い点です。廃人になると攻撃力を下げて総合的な耐久力を上げるSCP優先の育成が可能になりますが、こちらはCPが可能な限り1500に近くなる個体を選んでいるので、SCP優先個体に比べると攻撃力が高いのです。これによって育成を頑張っている人よりも先制できることが多くなり、かなり良い勝率をたたき出しました。この時点の最高レートは2600程度でした。 陽光カップ(10/1 ~ 10/7, 10/14~10/21) 陽光カップはハガネール、ヤルキモノ、グライガーの3体が強いと言われていたので、この3体でパーティを組みました。最初はヤルキモノを先頭にしていたのですが、あまりにもヤルキモノが警戒されていたので先頭をSハガネールにしたらなんとかなりました。やや負け越したパーティでした。 日跨ぎで対戦してはいけない…
【日記】スパゲティコードの治療#001
こんにちは。最近めっきり寒くなってきましたね。寒さに起因して頭痛もひどくなってきましたが、柚子しょうが紅茶が結構効くのでなんとかやってこれています。今日のお題はスパゲティコードの治療です。 なぜ急にスパゲティコードを治そうと思ったのか 先日、Ryuの自己管理室さんの公開している【完全版】”超ハードワーク”を実現する3つの脳ハック術を観ました。かなり自己啓発な内容で個人的には結構アレルギーが出る内容でしたが、一方で「最初のハードルを下げる」というのは結構使えそうだなと感じました。特にメンタルをやってからは何もせずに生きているだけの日も多かったので、それならばコードくらい触っていよう。そうして行き着いたのが、過去の自分のコードを治療することでした。 自分のスパゲティコードの特徴 まずは自己分析から。自分のコードの特徴は、とにかく機能が多くて整理されていないこと。傾向としては以下があげられます。 この状態では半年も経てば無事にレガシーになります。逆にいえば問題点は明確なので、今回はドキュメントを書きながらいらないところを消し、コードのルールを作って次につなげていきましょう。 実際の事例 いらなすぎる勉強用プログラムたち どうやらこのプログラムを作った頃、コンテナやMySQLなどのDBMSにハマっていたこともあってそういった技術の勉強用にプログラムを作っていたようです。プログラム実行環境はAWS上のコンテナを想定し、専用のDockerfileとビルドスクリプトまで用意している始末。もちろん、これらはプログラム本体とは切り分けて管理したほうが良いですね。他にも、tensorflowに初めて触った関係から、これらの試験的なコードも保存してありました。これは個別にGithubに上げて、このブログで解説するのが良いでしょう。ともかく、勉強用プログラムや環境構築スクリプトと本番プログラムは混ぜないように! 環境構築部分や勉強用プログラムとメイン部分は別プロジェクトにしましょう。 足りない説明 いらなすぎる勉強用プログラムたちに付随する形になりますが、あたらしいことを実践した際はメモくらい残しておいてくれ、過去の自分!!全然何をするためのものか分からないプログラムばっかり残っているではないか。なんか面白いプログラムを作ったらこまめに日記としてブログに上げて、いつでも振り返られるようにしましょう。そして、どうしても本番プログラムに入れる際はブログへのリンクでも良いから説明を入れておきましょう。しかしプログラミングって奥が深いというか、ちゃんとドキュメントを残しておかないと、ほぼすべての作業がパアになってしまう気がしますね。 どっちがmainかわからないプログラム プログラムの主であるメインプログラムと、それを用いた応用を行うプログラム、そしてテストを行うプログラムと、いくつかのプログラムから構成されるプロジェクトがある場合は、ちゃんとフォルダわけしましょう。今回はメインとなるのはデータを計算するプログラム、応用部分はデータをもとに図を作成するプログラム、テストはメインプログラムの動作チェック用でした。なので、応用とテストはそれぞれvisualization, testと分けて管理したほうが良かったですね。データはdataフォルダを作って管理していただけに、惜しかったというかなんというか。 どうやって入れたか不明のライブラリ 環境設定済みであれば問題なく動くけれど、そもそもこのライブラリどうやって入れたの問題が発生しました。いやーoptunaのことなんですけどね。多分conda経由で入れたと思うんだけど、環境構築でバグらないようにするためにも、ちゃんとどうやって入れたかは残しておくべきでした。メインプログラムに影響するようなライブラリはブログへのリンクをREADMEに貼るなどして対策することが必要ですね。 いったんのまとめ ここまで書いてきて感じたのは、スパゲティコードになる落とし穴は結構あって一記事ではまとめきれないということです。なのでこれを#001として、不定期連載という感じでやっていこうとおもいます。昨日図書館へ行ってリーダブルコードを借りてきましたので、次回はリーダブルコードと比較しながら改善して行こうと思います。それとは別に勉強用コードの供養もやっていきます。どっちが先に記事化されるかはわかりませんが、【完全版】”超ハードワーク”を実現する3つの脳ハック術でも毎日の進歩を確認することが大切といっていた気がしますので、少しペースを速めて頑張ろうと思います。それでは!また次回!
【Atcoder日記】ABC #329のメモ
こんにちは。最近Pythonとc++の連携ができるようになってからc++の技術が上がってきたと感じているので、今日は久し振りにAtcoderに手を出してみました。Python軸でAtcoderに参加していた時は、制限時間の壁があまりにもキツすぎて解けているのに間に合わないということがよくありました。その点C++であれば問題ない!はず。結果としては60分でABCの3問正解という感じでした。(頭痛のせいで60分以上の作業は正直きつい)それでは、RockinWool自己流の解答をご笑覧あれ。 A問題(Spread) 難易度:超かんたん 入力文字に対して、スペースを開けて返答しろという非常に簡単な問題。A問題の中でも過去一簡単なのでは?と感じました。c++のstringの使い方さえ間違わなければ大丈夫。それと最後の文字以降にスペースを入れてはいけないとのことだったのでif文で分岐させました。(個人的には、この難易度でも20行近く書く必要のあるc++って不便だなと感じるなど) B問題(Next) 難易度: 普通 B問題はごく普通の難易度。もしかしたら過去と比べて簡単な方かもしれない。最大値の次に大きな数を答えるということで、最大値が複数ある場合にも対応できるかどうかが問われている。今回はvectorとsortを使って、入力を降順に並び替えたのちに最大値よりも小さくなった瞬間の数を返答するようにした。 C問題(Count xxx) 難易度: 比較的簡単 C問題は過去と比べかなり簡単な方かも。文字の組み合わせを答えろと言われているが、要は連続した同一文字からなる文字列の長さを足し合わせれば良い。例えばaaabbccだと3+2+2=7という感じ。問題はaaabbaaだと3+2=5となる点(aは3と2のパターンがあるが、大きい方を採用する)。この考え方を実現するにはa:3,b:2のようなpythonで言うdictionary型のデータが必要であることに気づく。ということはunordered_map<char,int>といったデータを用意して代入していけば良い。 この解答で少し不満なのは、ループ部分でi番目の文字とi+1番目の文字を比べている仕様上、最終文字とその直前が違う場合にif文を組まなければならない点。例えば最初のif文では入力が1文字である場合の例外を設けているし、以降にもif(i==inputstr.size()-2)の時に分岐するようにひと手間かけている。もう少し美しく書けたかもしれないが、制限時間中には思いつかなかった。しかし、過去のC問題に比べると例外処理の少なさから、非常に簡単な問題だったと感じる。ただ、個人的にはABC正答できたのは嬉しい。 結び 今回の問題は結構簡単に感じましたが、もしかしたら自分の能力が上がってきているのかもと自信が付きました。これからもpythonとc++をどんどん連携させて、c++をバリバリ書けるようになっていきたいですし、それを記事にしていこうと思いますので温かい目で見守ってください!それでは、よいプログラミング生活を!
C++の備忘録: 乱数生成、配列(vector)、連想配列(map)
こんにちは。11月に入ったら急に頭痛が復活してきてしまったRockinWoolです。去年くらいから冬服のようなピッチリとして重い服を着ると、肩こりが悪化して頭痛が発生する気がしています。同じような症状の人がいたら嬉しいですが、この症状がある限りスーツを着て仕事するのは無理なんじゃないかと思ってしまいます。まあ、それ以前に職場で壊したメンタルを治すところからなんですが。さて、今回のテーマはC++で詰んだポイントの備忘録まとめです。前回の記事で紹介したpybind11を使ってpythonからc++で作成したバイナリを叩けるようになってから、ずーっとc++でコーディングしてました。これが結構楽しいけれど、反面覚えることが多くてpythonほど簡単には書けない・・・。そんなこんなで備忘録を残していつでも振り返られるようにしておこうと思います。 乱数生成 まず最初にびっくりしたのがc++での乱数生成のクセの強さ。pythonだとrandom.randn(min,max)一行で書けるような内容がc++だとそうもいかない。結局自作でrandnに相当するものを作成することになりました。 この関数を作成した際に引っかかったのはstd::uniform_int_distribution distribution(min, max-1);のmax-1の部分。実はpythonのrangeとかと違って、min≦乱数≦maxと出てきてしまうのです。したがってmax-1としないと挙動が異なってしまいます。というか、c++でもforループを組むときはfor(int i=0; i<N; i++)みたいに0~N-1の範囲で回すのに、なんで乱数を生成する関数だけ仕様が違うんだ・・。ということで、範囲外アクセスを生み出して一敗しました。チキショー。 配列(Vector) c++の配列であるvectorは、慣れれば結構使いやすいと感じています。こいつのすごいところはpybind11経由でpythonから呼ばれた際に、c++側関数の返り値をvector<int>やvector<vector<int>>としてもpython側からlist型として認識されるところ!(これってどっちかというとpythonがすごいんだけど)。逆にc++で自作の構造体をstructで作って、それを返り値としてpythonに渡そうとしたら失敗しました。やっぱり1関数1機能の規則に則って一度に複数個の返り値を返さないことが大切ですね。ちょっとクセが強いなと思ったのは、初期化時の表現。例えば下記のようなプログラムの場合、jam_counterはN個の要素すべてが0で初期化された一次元配列になります。 要素の呼び出し方はCやPythonと同じようにjam_counterといった感じになります。その他、vectorで怖いなと思ったのはpush_back関数。 上記はvector<int>型のdeclared_locに要素を”追加”している命令。配列の長さが後から変わるって結構怖くないか・・?ってC経験者だと感じてしまいます。Pythonのlist型にもappendとかあったけど、あれはメモリ管理とかまったく気にしなくても良かったので安心して使ってましたけど、c++のvectorもあれと同じくらい信用してもいいのかな〜。 連想配列(map,unordered_map) vector<int>がlist型に対応しているのに対して、こちらはdictionary型に似ていると感じました。デフォルトのmapだとKeyの順序まで覚えるらしいけど、ほとんどの場合はそこまで記憶しなくて良いのでunordered_mapで良いかなと感じています。使い方は下記のような感じ。 第一引数のintはKeyのtypeを指定し、第2引数は値のtypeを指定している。今回は整数を指定すると、それに応じた配列が返ってくるような連想配列型elementPositionを定義したということになる。これだけだとCの構造体でも同じことができそうだけど、連想配列型はc++のautoを用いたループと相性が良い。例えばこんな感じ。 上記は引数のvector<int>の要素中に同じ整数が含まれていたら、重複を報告するような関数。要素ではなく要素の位置番号をvalue, そしてKeyはvector<int>の各値を持つことによってこれを実現している。注目したいのはconst auto& entry : elementPositionsの部分。const宣言によってループのbodyではentry変数が変化しないことを保証し、autoによって連想配列型であることを実行時に勝手に判定してもらう。そしてauto&であることからelementPositionsを参照して使用することが指定されている。こうすれば、構造体では難しかった全Keyや全Valueの探索が超!楽ちん。今回もconst auto&によって全valueをチェックして重複があるかだけを確認できる。これ、pythonでも実装は結構難しいかなと感じているので結構すごいなと思っています。 まとめ C++はクセが強いけれど、できることも結構すごいのではないか?と感じ始めました。pybind11を使えばpythonの便利なところとc++の高速さを両立できるので革命が起きています。また、何かしらの備忘録を残そうと思っていますので、良ければ記事への励ましコメントなどをよろしくお願いします。モチベアップに繋がります。もちろん、記事で間違った内容などがありましたらご指摘よろ。それではまた次回~~~。
【pybind11】Pythonが重すぎるのでC++に手伝ってもらう
こんにちは。RockinWoolです。今日はpybind11を使ってpythonの一部処理をC++に置換して高速化することを目指します。私はかねてからPythonの処理があまりにも遅すぎることに悩んでました。このせいで、実験がうまくいかなかったり、プログラムの完了までに1ヶ月近い時間がかかったりしていました。ChatGPTにこのことを相談したらpybindが初心者向けで良いとの回答をもらったので、今回はこの悩みを解消すべく環境構築していきます。 pybind11の導入 今回はanaconda環境に導入する方法を示します。pipで管理している人はcondaコマンドやLinuxのPATH情報を適宜入れ替えて貰えれば問題無いと思います。まずはpybind11をcondaでインストールします。 ビルドの設定 次にCMakeLists.txtの準備をします。余談ですが、今回作成するプログラムはnetworkxで作成した複雑ネットワークを取り扱うことを考えています。ですので、C++のファイル名はload_network.cppとしてtoolsetライブラリ内に配置するとしました。つまり、CMakeLists.txtから見て下記のような配置になっています。 CMakeLists.txt|-build |- load_network(ビルド後生成物。pythonで呼べる)|-toolset |-load_network.cpp この状態におけるCMakeLists.txtはこんな感じになります。condaのbase環境にpybind11を入れているので、特定の環境に入れている場合はenvs//includeのように置き換えてください。 cmake_minimum_required(VERSION 3.2)project(pybind_test VERSION 0.1.0)find_package(pybind11 REQUIRED)find_package(Python REQUIRED)include_directories(/home//anaconda3/include)include_directories(/home//anaconda3/include/pybind11)link_directories(/home//anaconda3/lib)pybind11_add_module(load_network ./toolset/load_network.cpp) C++プログラムの本体 そしてload_network.cppの本体を下記のようにします。 1行目はpybind11を使うために必要な呪文、2行目はpythonのリスト型を扱うために必要な呪文という感じです。プログラム的には引数をそのままリターンする関数なので特別な処理はしていません。PYBIND11_MODULE(load_network, m)以降は完全に元記事を参考にしただけなので意味は良くわかっていませんが、ビルド後に関数呼び出しをする際に必要になる設定だと思っています。上記のload_network.cppをCMakeLists.txtを使ってビルドすればload_network~~.soができるので、これをtest.pyと同じ階層においてtest.pyから呼び出せば機能します。なので次はtest.pyを作りましょう。 呼び出し元のPythonプログラム あまり言及することはありませんが、c++で作ったライブラリが存在していると仮定してそのまま実装すれば機能します。す、すごい! まとめ pybind11はcondaコマンド一発でインストールできるので、かなり使いやすいと感じました。また、cmakeに馴染んでいることが条件になってきそうなので、c++と併せて勉強しなければと感じました。これを使って自作ライブラリとか作れればかっこいいなとか思いつつ、次回に向けて精進します。ここまで読んでくださってありがとうございました。