食べて戦い生殖する、Pythonで生み出す「人工生命」とは
コンピューターの得意分野の1つがシミュレーションだ。自分でプログラムを書けば、架空生物の生命すらシミュレートできる。本特集ではプログラミング言語「Python」を使って人工生命を作る方法を解説する。
Pythonで「人工生命」のプログラムを作ってみましょう。「人工生命」といっても、非常にシンプルな生命のシミュレーションです。
生命ですから「寿命」がある。「寿命」は生きているうちに減っていく。「食べ物」を得ることで少し寿命が復活する。敵に遭遇すれば「バトル」になり、条件を満たせば「生殖」して「子」を作り、生命群となる…といったシミュレーションを、プログラムで実現します。具体的には、以下のように段階的にプログラムを作成・調整していきます。
このような人工生命は、英語で「Artificial Life」、略して「AL」と呼ばれます。コンピュータグラフィックスの世界では、1980年代から人気の分野です。そのオリジンの1つが、Craig Reynolds氏による「Boids」(ボイド。https://en.wikipedia.org/wiki/Boids)です。Boidsは、「Bird-oid」(鳥もどき)を略した、氏による造語です。鳥同士は飛行する際、「衝突しないように互いに距離を取る」「他の鳥が飛ぶ方向や速度に合わせようとする」「群れの中心に向かおうとする」といった行動をとります。この単純な3つのルールをプログラムに実装しただけで、驚くほどリアルな鳥の群れがシミュレートできたのです。このことは当時、世の中に衝撃を与えました。
また、1994年にKarl Sims氏によって発表された「Evolved Virtual Creatures」(https://www.karlsims.com/evolved-virtual-creatures.html)では、動物が世代を重ねて「進化」する様子がシミュレーションされています。意外なやり方で高速に移動する方法を獲得したり、敵から餌を奪い取るように「進化」したりする様子は、今見ても驚異的です。
こうしたALのアルゴリズムは洗練を重ねて、今の映画やゲームにおける群集表現に多用されています
人工生命の開発準備
開発環境の構築
本稿では、簡易的な人工生命をPythonで作ります。Pythonは、バージョン3.7以上でプログラムの動作を確認しています。開発環境には、「Anaconda」の「Jupyter Notebook」を用います。記事中のコード(リスト)をJuputer Notebookで上から順番に実行して行きます。Anacondaのインストール方法やJupyter Notebookの使い方については、末尾のコラムをご覧ください。
また、人工生命をアニメーション表示するため、外部ライブラリとして「pygame」を利用します。pygameは2000年に初版がリリースされ、現在でも活発に開発が続けられています。
まず、pipコマンドを使って、pygameをインストールします。以下をJupyter Notebookのセルに入力し、実行(Run)してください。
pygameをすでにインストールしてあれば、「Requirement already satisfied」で始まるメッセージがすぐに表示されます。新規インストールであれば、さまざまな表示を出しながらインストールが進みます。しばらく時間がかかるかも知れませんが気長に待ってください。警告(Warning)はほとんどの場合無視して大丈夫です。もしエラー(Error)が出た場合は、PythonやJupyter Notebookの環境を見直してください。
pygameがインストールされたら、importでpygameモジュールを導入します。そして、初期化メソッドのinitを呼び出します。成功すると、pygameのバージョンや、「Hello from the pygame community.」といったメッセージが出力されます。
pygameが準備できたら、プログラミング開始です。
まずは、人工生命を表示するウインドウの大きさ(WIDTH、HEIGHT)、アニメーションの更新速度(FPS。Frames Per Second。1秒間に何コマの絵で更新するか)などを決めます。リスト1をセルに入力して実行します。
次に、このウインドウをユーザーがどのように操作できるようにするかを設定します(リスト2)。ウインドウの「閉じる」ボタンを押せるようにしたり、メニューから「Quit」「終了」を選べるようにしたりします。また、プログラムを停止させることを選んだかどうかの判定(quit)、画面の消去(clear)、画面の更新(updateやタイミングの調整もここ)などといった関数を定義しておきます。
次にリスト3を実行します。ここでは、mathモジュールをimportし、ngon関数を定義しています。mathは計算に使う関数をまとめたものです。ngon関数は、n角形を描画する関数です。作成する人工生命は、ウインドウ内に多角形で表現されます。
定義の中では、
と、あまりほかのプログラミング言語では見かけない書き方をしています。これはPythonの強力な機能の1つである「内包表記」と呼ばれる書き方です。ここでは、0からn-1まで変数iを変化させながら(「for i in range(n)」の部分)、このiを用いて、「i*2*math.pi/n – math.pi/2 + rot」を羅列したリストを作っています。結果としてこのリストには、円をn分割してできる単位円上の正n角形の頂点が格納されます。ただし、rotによって全体の傾きを制御できます。また、「- math. pi/2」によって、数学座標系でyが上方向に伸びているところを、グラフィックス座標系でyが下方向に伸びるように変換しています。さらにこのリストに格納された角度を基に、三角関数を用いて指定された位置と大きさで、描画できるグラフィックス座標列を得ます。
この後も、この記法「内包表記」はたびたび登場しますので、適宜説明します。
人工生命の「基盤」を定義
まず、人工生命の「基盤」となる部分を作ります。そのコードがリスト4です。
最初に、ウインドウ上で表現される座標系を、±1.5で設定しています(XMIN , XMAX, YMIN , YMAX)。主な計算は±1の範囲で行いたいので、少し余裕を持っての設定です。
次に設定しているEntityクラスは、シミュレーション対象の「生命」を表すためのものです。色、位置、大きさ、生き/死に、の情報を持ちます。色は、光の三原色であるRGB(赤、緑、青)ではなく、HSB(hue: 色相、saturation: 彩度、brightness: 明度)で指定するようにします。こうする方が、同じ色で明るさや鮮やかさだけを変えるのに便利なのです。
位置(x、y)は、インスタンス生成時に乱数を用いて±1の範囲で決めます。大きさは、radiusで好きなように指定できます。生き/死に(alive)は、生きている間だけシミュレーションを動かして、画面上に表示を行います。drawメソッドは、画面上にEntityを表示します。drawメソッドは、±1の座標系から画面座標系への変換も行うupdateメソッドの中から呼び出されることを前提にしています。collideWithメソッドは、他のEntityオブジェクトとの衝突判定を行います。ここでの衝突判定は、「Entityオブジェクトは円形である」という前提で行うことにしています。
このEntityクラスは、このあとに定義する生命の基盤クラスになります。Entityから派生した各種サブクラスを活用する前提で、最低限の機能を備えています。その際、drawメソッドをカスタマイズして見かけの変更を行い、updateメソッドのカスタマイズで挙動の変更を行います。
ここでもPythonらしいコードの書き方をしています。冒頭部分「XMIN, XMAX = -1.5, 1.5」のところです。これによって、XMINには-1.5、XMAXには1.5を一気に代入できます。複数の値を組みにして使える「タプル」という機能です。これは、最近ではC++やC#など、他の言語でも使えるようになってきました。気軽に複数の値を扱えるので、プログラムがシンプルに書けます。関数の戻り値を複数にすることも自然に書けて便利です。また、変数aとbの中身を交換したいときも、「a, b = b, a」と書くだけです。ちょっとしたことですが、プログラミングのストレスを少し減らしてくれます。
人工生命の「基盤」をテスト
リスト4で定義したEntityクラスがうまくサブクラス化できて、個々の生命の個性を表現する基盤になっているかどうかを、まずは簡単なプログラムで試してみます。そのプログラムが、リスト5です。
リスト5をJupyter Notebookで実行すると、図1のようなウインドウが表示されて、アニメーションが行われます。
リスト5は、CとBという名前で2種類のEntityのサブクラスを派生させています。Cは円状(Circle)に動くクラスで、Bはボヨンボヨン(BoyonBoyon)と膨らんだり縮んだりする動きをするクラスです。Cは色と大きさを変えて2種類、Bは1つだけ配置しました。速度は1フレーム当たりの「角速度」(ω。omega)で指定するようにしています(このテスト専用の仕様です)。Cは原点を中心として半径1の円上を回転運動します。BはスーパークラスのEntityの挙動を引き継ぎ、ランダムな場所に出現し、大きさが脈動します。
そして、「screen = pygame.display.set_mode((WIDTH, HEIGHT))」で、pygameの表示を設定します。その後、アプリとしての「イベントループ」を開始します。そのループの中で必要な処理を順次行い、1フレーム分の処理が終わったら、指定された画面更新速度(FPS)分だけ待ちを入れて、再びループの先頭に戻ります。ユーザーによる終了指示をquitでチェックし、quitがTrueになるとループを抜け、「pygame.display.quit()」でpygameの表示を終了します。
プログラムを終了させるときは、ウインドウを閉じるか、メニューから終了操作を行ってください。Jupyter Notebookの出力結果に「人工生命基盤テスト done」と表示されればプログラムは終了しています。次のコードの実行に進むには、この終了操作が必要です(mac OSの場合、ドックにpygameのアイコンが出たままになりますが、気にしなくても大丈夫です)。これを忘れると、ウインドウ上の画面表示が切り替わらないことがあります。その場合は、ウインドウを閉じるボタンをクリックしてください。ほとんどの場合、これで新たに起動したプログラムの表示に切り替わります。まれに、このような操作を繰り返すうちに先に進めなくなる場合もあります。そのときは、Jupyter Notebookのメニューの「Kernel」から「Restart & Clear Output」を選び、最初からやり直してください。
リスト5をちょっとだけいじって、アニメーションの動きを理解してみましょう。
リスト5の(1)の「cs = [C(hue=0, omega=0.1), C(hue=0.2, omega=0.2)]」の部分における「hue= 」の後ろの数値を変えると、回転運動を行う物体の色が変わります。数値は、0~1の範囲で指定できます。また、omegaの後ろの数値は、角速度の指定です。大きい数値にすると速く回転します。(2)の「bs = [B(hue=0.4, omega=0.1)]」の部分も同様ですが、こちらのomegaの値は、大きさの変化に反映されます。
また、class Bの定義中のupdateメソッドにおける(3)の「self.radius = 1.0*math.fabs(math.cos(self._theta))」にある1.0を変えると、変化における最大サイズが変わります。
以上で、基盤部分の構築と確認が終わりました。いよいよ人工生命を作ります。
本稿では、Pythonのプログラムを実行する環境として「Anaconda」のIndividual Editionに含まれている「Jupyter Notebook」を使います。
にアクセスして、「Download」をクリックし、パソコンのOSに合ったインストーラーをダウンロードして、セットアップしてください。本稿ではWindows 10の64ビット版を使っています。
インストーラーをダブルクリックしてAnacondaをインストールします。基本的には標準の設定のまま、「Next」ボタンだけでインストールを完了します。
セットアップが終わったら、Windowsのスタートメニューから、「Anaconda」→「Jupyter Notebook」を選択し、Webブラウザで「Jupyter Notebook」を表示します。Jupyter Notebookが起動したら、右上にある「New」をプルダウンして、「Python 3」を選択します。すると、「In [ ]:」という表示のある「セル」が表示されます。このセルにコード(リスト)を入力して、上にあるツールバーの「Run」を押して実行します。すると、入力したコードの実行結果が表示されます。次のコードを入力する際は、Jupyter Notebookのツールバー左上にある「+」ボタンを押してセルを追加し、そこに次のコードを入力して実行します。この繰り返しで処理を行っていきます。