このようなゲームが一区切りつき、とりあえず完成ということになりました。以下の記事ではゲームとしての側面を語ったのですが、技術的な側も忘れる前に語っておきたいと思っています。
概要
このゲームでは gif 画像のように「虫」と呼んでいる人口生命体が登場します。これは、ニューラルネットワークで実装した強化学習モデルを種毎に(同じ見た目の虫毎に)一つ持っており、すべての行動はそのモデルによって決定づけられています。そして、それぞれの個体が起こした行動と結果(報酬)を基に、一定間隔でモデルの改善を行っています。
つまり、虫たちは脳を共有した状態と言い換えてもいいかもしれません。ここは実際の生命とはずれてるようにも見えますが、個体同士でコミュニケーションによって自分が経験していないことまでも自分の行動に反映できるという特性を極端にしたものだと考えることもできるのではないでしょうか。
当然モデルのサイズや種の種類が増えれば遅くなるのは避けられませんが、できるだけメインループを1秒間に100回1を目指して調整していました。以下の画像では 1405匹の生きている虫が存在していますが、1秒間に37.59ループ(EPS: 37.59 2)とある程度高速に動作させられたと思っています。
計算環境はこのミニPCで、8コア16スレッド、RAM 16GB というスペックのものを使いました。ちなみに自宅に設置しておいて、cloudflare tunnel 経由でインターネットアクセスを行っているので、運用費は電気代(月1000円くらい)だけでした。このスペックの VPS を借りようとすると毎月7000円以上は余裕でかかるのでそれに比べるとかなり安いと思います。cloudflare ありがたや...
そして、これらの要素を見て気づかれるかもしれないのですが、虫の行動のシミュレーションはすべてミニPC上で行っていて、それをプレイヤーのブラウザまで同期していました。つまり、どこからアクセスしたとしても全く同じ表示を見ることができます。
これにより、このゲームを初期化することができるのは自分だけになります。もしこのゲームを永遠に動かし続けた場合、強化学習でほかの虫と相互作用を起こしながら、生きるため、繁殖するために行動の最適化を続けた「人工生命」は本物の命と区別できないという見方もできるのではないか...。みたいなことを考えながら設計した記憶があります。
まあ、そんな儚い命たちも私の電気代を節約するというだけの名目で簡単に停止されてしまうんですけどね。
それに、AI の研究者でも仕事でも使ってないような人が作り(簡単に基礎だけは勉強しましたが)、しかも高速化を重視して実装した強化学習モデルが簡単に命を宿すわけもありませんでした。虫たちに安定感はなく、ある程度まで増えたと思ったら身投げ3して自滅してしまいました。
これをネズミのユートピアの実験4だとか、滅亡に向かう社会の縮図みたいに言い換えてもよかったのですが無理がありそうです。虫たちには社会性どころか、他者を明確に区別することもできなければ、仲間打ちも実装されてませんので...。それでも何となくは知性を感じる動きができたので、不満は残りますが見ていて面白いものにはなりました。
アーキテクチャ
システム全体はこういうアーキテクチャになっています。Docker + docker-compose で管理していました。
コンポーネントを簡単に説明すると以下のようになっています。
- Simulator: 虫の行動をシミュ―レーションしたり学習を行う。定期的に kafka へ虫の位置やステータスを publish し続けている Node.js アプリ
- Kafka, zookeeper: メッセージブローカー
- Streamer: Kafka から取り出した虫の情報を受け取って、クライアントに websocket で1秒毎に送信する Node.js アプリ。ユーザーが増えたときにここがボトルネックになりそうだったのでスケーラブルにできるよう設計してみました
- Viewer: ゲーム用のHTMLを配信する Next.js サーバー
- Nginx: リバースプロキシ
- Cloudflared: Cloudflare tunnels 用の proxy
Simulator の内部動作
Simulator の内部動作はざっくりこのような感じになってます。強化学習の推論や学習は Node.js の worker_thread(スレッドの仕組み)によって並列的に行っていて、CPU コアを頑張って有効利用するという工夫をしています。もう少し詰められそうな気はしなくもないんですが Node.js はまあまあ速かったです。
(1) では周囲の虫を高速に検索する必要があり、このようなnpmライブラリを作ったりしました。