Category: Python

【渋滞学】セル・オートマトンの実装

頭痛のひどいRockinWoolです。しかし、時期的にはかなり忙しいのでプログラミングもはかどっています。最近の趣味は公園で散歩をしながら肩をほぐすことですね。さて、ちょっと前に渋滞学という本を読んだのですが、その中にあったセル・オートマトンの実装をずっとやってみたいなと思いながらやっていなかったのでやりました。今回はpybind11を使ってセル・オートマトン部分をC++で作り、作図部分やデータ整理部分をpythonに任せる形にしています。実装はGithubにこっそり上げて置きます。 プログラムの構成 まず、メインプログラムは実験を行ってデータをcsvファイルに出力するmain.pyとcsvを元に作図をするdraw.pyの2つから構成することにしました。このようにすれば、main.pyはpybind11を使って連携するがdraw.pyはピュアpythonの実装で良くなるので便利でした。ざっとフォルダ階層を書くと下のような感じです。 CA.cppはCA.hppで定義された関数をpythonから使用できるようにしていて、細かい実装はcaHistory.cppに書いてあります。余談ですが、CAはCellAutomatonの略、historyとつけているのはセル・オートマトンでどの位置にエージェントがいたかどうかを返すことを明確にしたかったからです。 main.pyの概要 まずは基本となるmain.pyの解説から。難しい処理はC++に任せてしまっているので中身は超単純です。1~100個のエージェントを召喚してセル・オートマトンをして、その情報を保存することを2回繰り返しています。1回目は1個から始めて100個まで増やして混雑具合を見ています。2回目はその逆で100個から減らしていってます。cl.caHistory()でセル・オートマトンの実験をしていますが、その時に計算される混雑の回数congestionはゲッターであるget_congestion()を実装して入手する形になっています。これはpybind11の仕様でメンバ変数を直接参照できないことと、メンバ変数は基本的にprivateにしてゲッターを使ってアクセスするというベストプラクティスに沿った実装をしていることの2点があります。 それでは、このmain.pyで使用されるcaHistory(), get_congestion(), add_agent(), remove_agent()を実装していきましょう。 CA.cppの実装 こちらも中身は単純で、pybindのincludeを行う部分と関数をpython側に共有させる部分の2つから構成されています。書き方が少し独特ですが4つの関数が公開されるのがわかります。 CA.hppの内容 先程の4つの関数のプロトタイプ宣言を行っている部分です。ちなみに前回の記事で書きましたとおり、コンストラクタも作らないといけないのでpublicには5つの関数が登録されています。またprivateにはそれらの関数が使う情報を登録しています。update()関数はセル・オートマトンの具体的動作を規定していて、前のセルにエージェントがいたら止まるなどの挙動を定義しています。これをcaHistory()ではint回呼び出してシミュレートするわけですね。 caHistory.cppの実装 記事のほとんどがこのプログラムの内容で埋まってしまっていますが、実際はかなり単純なプログラムです。ほとんどの分量はupdate()関数の条件文で埋まっているのでcaHistory(), add_agent(), remove_agent(), get_congestion()の本文は2~5行程度になっています。それぞれ与えられた役割が動作するようになっています。 draw.pyによる描画結果 draw.pyの実装は大した内容では無いので割愛します。main.pyを実行して得られたcsvファイルをベースにdraw.pyが書いた図が下記です。 まとめ 今回はpybind11を使ってセル・オートマトンを実装してみました。内容が少しヘビーになってしまったかもしれませんが、興味のある方はコードの改善点などドシドシ送ってください。それでは。

【pybind11】クラスを登録するには

こんにちは。RockinWoolです。今日はC++とpythonの連携をするにあたって少し躓いた部分があったので紹介していきたいと思います。未だによくわかっていないバグもあったので、その辺で悩んでいる人たちへの一助になれれば幸いです。それでは早速見ていきましょう。 CMakeLists.txtの書き方 CMake Error at CMakeLists.txtのエラー fatal error: pybind11/pybind11.h: そのようなファイルやディレクトリはありません CmakeLists.txtにちゃんと書いているにも関わらずpybind11が見つからないというエラー。実はこのエラーはadd_libraryとtarget_link_librariesが無い普通のc++ファイルをビルドした場合には発生しないエラーです。原因は不明ですが、main.cppの最初に#include <pybind11/pybind11.h>を書けば大丈夫で、クラスを定義しているヘッダファイル(例えばutil.hのように名前をつけていた)に#include <pybind11/pybind11.h>を書くとバグることがわかりました。もしかしたらライブラリにするcppファイルとmain.cppのフォルダ階層が異なるとうまく下位プログラムからpybind11を呼び出せない感じなのかもしれません。とにかくmain.cppに書きましょう。 TypeError: build.{library}.{class}: No constructor defined! 上記2つのエラーを乗り越えて無事にビルドが完了しても、pythonからうまく呼び出せるかは別問題です。そして、pythonから呼び出す際にはコンストラクタの実装が必要不可欠のようです。なので、コンストラクタを定義してあげましょう。さらにコンストラクタを登録する必要もあります。main.cppにコンストラクタを登録する方法は下記を参考に。 まとめ 書き出してみるとちゃんと解決策のあるエラーだったのですが、C++はこういう仕様が隠れていて調べないとわからないみたいなエラーが多くて難しいですね。その点PYthonはかんたんだなと改めて感じます。それではここまで見てくださってありがとうございました。次回もお楽しみに。

【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の使い方と応用を紹介させていただきました。自分のコードの稚拙さや改善ポイントの多さに気付かされますね。俺・・頭痛が治ったら、もっとちゃんとドキュメンテーションしながらプログラムを組むんだ・・(強い決意)。ということで、千里の道も一歩から。次回もお楽しみに。

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++と併せて勉強しなければと感じました。これを使って自作ライブラリとか作れればかっこいいなとか思いつつ、次回に向けて精進します。ここまで読んでくださってありがとうございました。

Ubuntuのカスタマイズ #2.5 Tensorflow-GPU(GTX-3080ti固有のバグ?)

こんにちは。最近頭痛に対抗して運動をしているRockinWoolです。会社から休職の手当と書類が送られてくるのですが、よりにもよって体調を崩す原因となった上司から送られてくるので、毎回憂鬱になります。個人的にはお金をいただけても全然嬉しくないというか、理不尽な目に合わなければ頭痛とお付き合いする必要もなかったしお金ももっと入っていただろうので、単純に互いに損しているだけなんですよね。 さて、今日はちょっと前に上げていたTensorflow-GPUのセットアップが固有のGPUでは失敗する現象を発見したので、これについて解決策を補足させていただきます。 GTX-3080Tiを積んだPCでTensorflow-gpuがセットアップできない まずはバグの概要から。上記GPUを積んだ状態でTensorflow-gpuをインストールすると次のような状況に遭遇します。なおlibdevice not found at ./libdevice.10.bcの問題は既に解決済みであるとします。上記エラーの解決方法は前回記事を参考にしてください。 エラーメッセージは下記の通りです。 正: ptxasが足りていない, 誤: ptxasが起動できない さて、エラーメッセージが出たらまずは同様の事例が無いかWebで確認!(クソザコ思考)。似たようなエラーとしてはここのIssueなんかがありました。解決法として、ptxasへのPATHを追加することが挙げられていたので早速挑戦してみると・・・、あれ?find / -name "*ptxas*"で何もヒットしないぞ?しかもエラーメッセージに書いてあるnvptx_compilerすらヒットしない。これは、つまりパッケージが足りていないということでは? condaでcuda-nvvcをインストールする必要があった さて、パッケージが不足していることは分かったのですが、それじゃあ一体何を入れれば良いのかということになりました。ということで、心当たりのあるパッケージを一通り入れたり外したりした結果、最終的にはここに書いてある下記のコードを実行する必要があったという結論になりました。 このためだけに、Ubuntuの再インストールとかいろいろ大規模に調査したのですが、結果としてはtensorflow-gpuのインストール時にcuda-nvccが自動的に入らないことが問題だったようです。こんなの他にも同じような症状の人が発生しそうですね・・。 まとめ とりあえずINTERNAL: Failed to launch ptxasのエラーで悩んでいる方はconda install -c nvidia cuda-nvccでnvccをインストールすることを試してみてください。今の所固有GPUでのエラーっぽいのですが、より多くの人がこの方法で救われると嬉しいですね。解決方法発見までのプロセスについてもツッコミがあれば改善したいと思います。ここまで読んでくれてありがとうございました。