MkItYs

MkItYs > 音楽・漫画・VR・自律制御 > 

images

人工生命のベンチマーク:Evolution Gym, X Window (XQuartz) , Docker

images

人工生命のベンチマーク「Evolution Gym 」について、設定と利用のしかたです。[※1]



※1
環境構築は次で検証していますが、手順は、Linux + Docker / Windows + WSL2 + Docker (CE) にも流用できるはずです:
Apple silicon + XQuartz + Docker

関連


コンテナを導入する:Docker
高機能のウィンドウシステム:X Window System (XQuartz) , Docker, OpenGL, Tcl/Tk, Xvfb, x11vnc

検証


PC:MacBook Air M1 2020
OS(ホスト):macOS 11.31
OS(ゲスト):Ubuntu 20.04 LTS
ウィンドウシステム:X Window (XQuartz 2.8.5)
コンテナ:Docker Desktop 4.13.1

背景


ある目的のための最適なカタチって、どんなものでしょう?

ヒトをふくめ、生物には進化過程や生命維持によるさまざまな制約がありますよねーーたとえば、車輪があれば平坦な地形を高速に移動できるのに、そのような構造を獲得した動物はほぼいなかったり。[※1]

でも機械なら、より多様なカタチを現実に作ることができます。さらに仮想現実の機械なら、より柔軟に・簡単に試すことができます。[※2]

このアプリ「Evolution Gym 」は、いろいろな目的のための最適なカタチやうごき、そしてそれらを獲得するためのやり方を、仮想現実の機械=ソフトウェアの人工生命を使って探るものです(構成と動作、そしてそれらを最適化する手法のベンチマークにすることを目標にしています)。

Evolution Gym
images
images

※1
車輪のように回転するパーツだと、そこに栄養を送ったり神経を延ばしたりするのが難しいためですが、それでも一部の小さな生物は、回転する構造を獲得していたりします(グールドの「ニワトリの歯」〜「車輪なき王国」でも語られる大腸菌やシロアリの腸内細菌が有名ですが、ほかにもいるようですね)ーーさらに歯車を持つ生物もいたり(2013年に発見されたようです):
https://gendai.media/articles/-/83552
※2
じっさいクルマも、レーシングカーから建設機械まで、膨大な数のカタチとうごきについて、実用性とコストのトレードオフが極限まで試されてきました。

概要


アプリは、MIT人工知能研究所の研究者が提供しています。なおベースには、強化学習のベンチマーク(OpenAI Gym)を採用しています。[※1]

https://arxiv.org/abs/2201.09863

要素:カタチとうごき 〜 構成と動作(デザインとコントロール)

このアプリでは、4種類のブロック(「ボクセル」)から、人工生命(「ロボット」)を作ります:

黒()……固定ブロック:剛体/Rigid Voxel
灰()……固定ブロック:軟体/Soft Voxel
青()……可動ブロック:垂直方向に伸び縮み/Vertical Actuator
橙()……可動ブロック:水平方向に伸び縮み/Horizontal Actuator

これでできるものはいわゆるソフトロボット(アクチュエータ=バネ)になりますが、汎用の構造を探るベースにもなるものです。[※2]


要素:やり方 〜 手法(アルゴリズム)

アプリのサンプルでは、2種類の手法(アルゴリズム)を最適化に使っています:[※3]

カタチ/構成に対して……進化計算(GA, BO, CPPN-NEAT )
うごき/動作に対して……強化学習(PPO )

これらのアルゴリズムは、まずいろいろなカタチを進化計算で試し(外側のループ)、それらのカタチに対して、いろいろなうごきを強化学習で試す(内側のループ)、という分担になっています。[※4]


要素:しごと 〜 課題(タスク)

ロボットは環境(「ワールド」)におかれ、そこでさまざまなしごとをこなします(歩く、登る、道具をつかう、形状をかえる……)。

これらのしごとに対して、最適なカタチ・うごきを、うまいやり方で探ることになります。


戦略

進化計算+強化学習の場合は、まずワールドで、いくつかのボクセルをランダムに組み合わせます。これでいくつかの個体のロボットを作り、うまくしごとをこなしたものを、次の世代に残しますーーこのサイクル(進化・学習)を繰り返すことで、ロボットをそれぞれのしごとに最適化していきます。

images

とはいえこのような戦略でも、達成が難しいしごとがあるようですねーーより高度なアルゴリズムが望まれるので、その検証の場としてもこのアプリを使ってほしいとのこと。



※1
OpenAI GymはすでにGymnasium に引き継がれていますが、このアプリは古い方を使っていますーー新しい方に修正したいなら、次が参考になるかもしれません:
強化学習のためのシンプルな環境:Farama Gymnasium, OpenAI Gym, Docker, X Window System, Pyglet, pygame, tkinter
※2
なら車輪などを実現するモータや歯車、リンクやカムやはどうかというと、これは複数のソフトロボットの協調で実現できるはずです(協調作業はこのアプリのサンプルにはまだありませんし、論文でも明確には言及されていませんがーーこれら小さなソフトロボットたちは、より大きな機構を構成するベースになりうるものです)。なお自分自身が回転するという課題も、ベンチーマークのひとつにあったりします:
images
※3
ベイズ最適化(BO)は進化計算ではないのですが、より大きなくくりのブラックボックス最適化のひとつです。
※4
動作の制御(コントラーラ)にも進化計算を使った試みや、べつの強化学習を使った試みもあります(いずれも岡瑞起氏/斉藤拓己氏による検証ですーー進化計算の方が優れている課題もあるみたいですね):
https://note.com/mizuki_oka/n/n7536b0bdff1f
https://note.com/mizuki_oka/n/nece36941088c

設置:共通


設置のしかたは、次に書かれています:

https://github.com/EvolutionGym/evogym

ここでは、コンテナを使って設置しますーーなおフォルダ構成は、次を前提にしています:

${HOME}/sys/vir/vir130 …… コンテナの設定フォルダ
${HOME}/app_pkg/vir130 …… ライブラリ群のフォルダ

ローカルでパッケージを取得します:

$ cd ${HOME}/app_pkg/vir130
$ git clone --depth 1 --recurse-submodules https://github.com/EvolutionGym/evogym.git

コンテナの初期状態を設定し:

${HOME}/sys/vir/vir130/Dockerfile
FROM ubuntu:20.04

RUN apt-get -y update
RUN apt-get -y upgrade
RUN apt-get -y install python3
RUN apt-get -y install pip
RUN ln -s /usr/bin/python3.8 /usr/bin/python

コンテナを作り:

$ docker build --no-cache -t ubuntu:vir130 ${HOME}/sys/vir/vir130

起動します(また適宜、コンテナの状態をイメージに反映させます):

# 起動する:
$ docker run -it --rm -v ${HOME}/app/opt:/opt -v ${HOME}/app_pkg:/pkg --name cnt130 ubuntu:vir130

# 反映する:
$ docker commit cnt130 ubuntu:vir130

コンテナ上で、各種ライブラリを設置します:

$ apt install xorg-dev
Geographic area: 6
Time zone: 79
$ apt install libglu1-mesa-dev
$ apt install libglew-dev # 拡張機能:GLEW
$ apt install mesa-utils # 動作検証のため:OpenGL

$ apt install git
$ apt install cmake

$ cd /pkg/vir130/evogym
$ pip install -r requirements.txt
$ python setup.py install

# バージョンを指定し入れ替え:1.23
$ pip install numpy==
...
$ pip uninstall -y numpy
$ pip install numpy==1.23.5

ローカルでX サーバを起動し:

$ xhost +

コンテナで次を実行しますーーロボットが歩く画面が表示されたら、設置は完了です:

$ export DISPLAY=host.docker.internal:0.0
$ export LIBGL_ALWAYS_INDIRECT=1
$ glxinfo | grep -i render
...
OpenGL renderer string: Apple M1
$ glxgears # 動作検証:OpenGL
...

$ cd /pkg/vir130/evogym/examples
$ python gym_test.py

設置:共通:デザインツール


ワールドやロボットを手描きで作ることができる、デザインツールが用意されています。

https://github.com/EvolutionGym/evogym-design-tool
images

ローカルでパッケージを取得します:

$ cd ${HOME}/app_pkg/vir130
$ git clone --depth 1 https://github.com/EvolutionGym/evogym-design-tool.git

コンテナ上で、必要なライブラリを設置します:

$ apt install python3-tk

次のコマンドで、編集画面を表示します:

$ export DISPLAY=host.docker.internal:0.0

$ cd /pkg/vir130/evogym-design-tool/src
$ python main.py

このツールで作ったワールドやロボットのファイル(JSON形式)は、次に置かれます:

evogym-design-tool/src/exported

これらのファイルをチュートリアルやサンプルで使うときは、次に置きます:

evogym/tutorials/world_data
evogym/examples/world_data

設置:個別:macOS


以下、macOS 特有の問題への対応です:


対処:アニメーションの表示

アプリはアニメーションをX Windowで表示しますが、macOS のXQuartz では表示前に失敗するようです。[※1]

このようなときは(ローカルのX サーバが使えないときは)、コンテナでX サーバを起動させ、その映像をローカルのVNC アプリに送る、というやり方もあります:

コンテナは(ポートを開けるために)次のように起動し:

$ docker run -it --rm -p 5900:5900 -v ${HOME}/app/opt:/opt -v ${HOME}/app_pkg:/pkg --name cnt130 ubuntu:vir130

コンテナ上で、次のライブラリ群を設置します:

$ apt install xvfb
$ apt install x11vnc

アニメーションを表示するときは、次のサーバ群を起動させておき:

$ export n=1
$ export DISPLAY=:${n}
$ Xvfb :${n} -screen 0 1200x600x24 &
$ x11vnc -display :${n} -forever -passwd ${password} &

ローカルでは、次にアクセスしておきます:[※2]

vnc://localhost:5900/

試したいコマンドを実行すると、結果がローカルの画面に表示されます。


対処:デザインツール

デザインツールの方は(tkinter を使っているので)XQuartz で表示できますーーただサブウィンドウへの操作が起きると(警告ウィンドウへの操作など)、ウィンドウ全体がクラッシュしてしまいます。[※3]

とりあえずUIのスクリプトを修正し、エディット/セーブ/ロード時にサブウィンドウを表示させないことで対処していますがーー

evogym-design-tool/src/gui.py
def update_gs_click(...):
  ... if new_width < self.old_gs_width: ... else: # この間をコメントアウト
def load_click(...):
  ... if len(self.objects.items()) > 0: ... else: # この間をコメントアウト
def save_click(...):
  ... if os.path.exists(save_path): ... else: # この間をコメントアウト

対処

確実に利用するなら、ホストサーバをべつに用意し(Ubuntu, X Window, xrdp)、ローカルからリモートデスクトップ(RDP )で操作するのが無難でしょうけど……とはいえこういうアプリはローカルで動かしたい、というのもありますしね……



※1
Segmentation faultで失敗しますーーOpenGLは有効になっているので(glxgearsは表示される)、XQuartz のOpenGLが対応できないGLEWの機能を使っているのかもしれません。次の事例では、ソフウェアレンダリングを強制するため、Mesaのソースからのコンパイルで対応できたようですが:
https://discourse.vtk.org/t/using-vtk-from-within-a-docker-container-on-os-x-intel-arm/9375
※2
macOS のデスクトップのメニューから、VNC アプリの画面を開きます:
移動 > サーバへ接続
※3
次のイシューにあるやり方を試してみましたが、結果は同じでした(クラッシュ)。ただこの対処なら、たしかにTK()の前にglfw.init() が実行されているので、うまくいきそうなんですけどね……:
https://github.com/EvolutionGym/evogym-design-tool/issues/4

利用:進化・学習のチュートリアル


アプリには、ロボットを進化・学習させるための雛形が用意されています:

https://github.com/EvolutionGym/evogym/tree/main/tutorials

この雛形に、試したいアルゴリズムを組み込んでいくことになりますーー基本的な使い方(観察のしかた/報酬の与え方)は、次に解説されています:

https://evolutiongym.github.io/tutorials/new-env.html

次のコマンドで、この雛形を実行できます:

$ cd /pkg/vir130/evogym/tutorials
$ python visualize_simple_env.py

作ったロボットのふるまいを見てみる

ロボットを自分で作り、そのふるまいをとりあえず見てみたいなら、このチュートリアルの雛形が使えます。



このロボットのデザインは、前田高志氏のドット絵サイトで提供されている「右を向いたペンギン」「左を向いたペンギン」を参考にしています:
https://dotown.maeda-design-room.net/

最初に、次のスクリプトの次の箇所をコメントアウトしておきます(ランダムに生成されるロボットを、ワールドに加えないようにします):

evogym/tutorials/envs/simple_env.py
#self.world.add_from_array('robot', body, 1, 1, connections=connections)

作ったロボットのふるまいを見てみる[その1]

いちばんかんたんなのは、ワールドに直接ロボットを加えるやり方ですーーワールドのファイルをデザインツールで編集します(このとき、加えたロボットのオブジェクトは「robot 」という名前にします):

evogym/tutorials/world_data/simple_walker_env.json

作ったロボットのふるまいを見てみる[その2]

ロボットだけのファイルを作り、それを読み込んでワールドに加えることもできますーースクリプトには、次のような記述を加えます(このときワールドにあるモノとぶつかりそうなら、なにもないところに動かしておく必要があります)。[※1]

evogym/tutorials/envs/simple_env.py
from evogym import EvoWorld, WorldObject

robot = WorldObject.from_json('/pkg/vir130/evogym/tutorials/world_data/my_robot_001.json') # ロボットのファイルから読み込む
robot.set_pos(1,1) # てきとうなところに動かす
self.world.add_object(robot) # ワールドにロボットを加える

作ったロボットのふるまいを見てみる[その3]

ロボットはテキストファイルだけで作ることもできますーーボクセルには、種類ごとに番号が割り当ててあります:

evogym/evogym/utils.py
VOXEL_TYPES = {
    'EMPTY': 0,
    'RIGID': 1,
    'SOFT': 2,
    'H_ACT': 3,
    'V_ACT': 4,
    'FIXED': 5,
}

この番号を、テキストファイルの上でロボットのカタチに並べるだけです:[※2]

0 0 4 4 4 4 0 0
0 0 4 4 1 4 0 0
0 0 4 4 4 4 3 0
0 0 4 4 4 4 0 4
4 4 4 4 4 4 4 4
4 0 4 2 2 4 0 0
0 0 4 2 2 4 0 0
0 3 3 0 0 3 3 0

スクリプトには、次のような記述を加えます(この場合、スクリプトからロボットに名前をつける必要があります):

evogym/tutorials/envs/simple_env.py
import numpy as np
from evogym import EvoWorld, WorldObject

body = np.loadtxt('/pkg/vir130/evogym/tutorials/world_data/my_robot_001.txt', dtype='int') # ロボットのテキストファイルから読み込む
robot = WorldObject.from_array('robot', body) # 名前をつける
robot.set_pos(1,1) # てきとうなところに動かす
self.world.add_object(robot) # ワールドにロボットを加える

作ったロボットのふるまいを見てみる[その4]

次のようなスクリプトを書けば、挙動の大きさ(アクチュエータであるバネの強さ)でロボットのふるまいがどう変わるか、確かめておくことができます:

a.py
import sys
import numpy as np
from gym import spaces
from evogym import EvoWorld, WorldObject
from evogym.envs import EvoGymBase

a_low = sys.argv[1]
a_high = sys.argv[2]
a_path = sys.argv[3]

g_name = 'robot'

class Try(EvoGymBase):
  def __init__(self):
    self.world = EvoWorld()
    if a_path.endswith('.json'):
      robot = WorldObject.from_json(a_path)
    if a_path.endswith('.txt'):
      robot = WorldObject.from_array(g_name, np.loadtxt(a_path, dtype='int'))
    self.world.add_object(robot)
    EvoGymBase.__init__(self,self.world)
    num_actuators = self.get_actuator_indices(g_name).size
    self.action_space = spaces.Box(low=float(a_low), high=float(a_high), shape=(num_actuators,), dtype=np.float)
    self.default_viewer.track_objects(g_name) # ロボットにカメラを合わせる

env = Try()

while True:
  env.step({g_name: env.action_space.sample()})
  env.render()

次のように使います(数字はバネの制限の最小値と最大値、ファイル形式はJSON/TXTどちらでも可):

$ python a.py 0.5 1.5 /pkg/vir130/evogym/tutorials/world_data/my_robot_001.json
$ python a.py 0.5 1.5 /pkg/vir130/evogym/tutorials/world_data/my_robot_001.txt


※1
原点は左下になります。
※2
次が参考になります(この形式のロボット群が置いてあります):
https://github.com/sttkm/ComparisonOnEvogym/tree/main/robot_files

利用:進化・学習のためのリファレンス


以下、スクリプトを組むときのリファレンスです:

https://evolutiongym.github.io/documentation/evogym/base.html

次のクラス群が提供されています:

envs.EvoGymBase …… 進化・学習のための環境(ApenAIのgym.Env を継承しています)
EvoWorld …… ワールドの参照と操作
WorldObject …… ワールドに投入するオブジェクトの参照と操作(ロボットなど)
EvoSim …… ロボットのふるまいの参照と操作(アクチュエータなど)
EvoViewer …… ワールドのカメラの参照と操作

利用:進化・学習のサンプル


いくつかのアルゴリズム(進化計算や強化学習)が実装されたサンプルもあります:

https://github.com/EvolutionGym/evogym/tree/main/examples

次のようなコマンドで、ロボットを進化・学習させることができます:

$ cd /pkg/vir130/evogym/examples

# 強化学習(PPO )で、自分で作った/生成されたロボットに、うごきを学習させる:
$ python run_group_ppo.py --algo ppo --use-gae --lr 2.5e-4 --clip-param 0.1 --value-loss-coef 0.5 --num-processes 4 --num-steps 128 --num-mini-batch 4 --log-interval 100 --use-linear-lr-decay --entropy-coef 0.01 --eval-interval 50

# 進化計算(GA)と強化学習(PPO )で、ロボットのカタチとうごきを進化・学習させる:
$ python run_ga.py --env-name "Walker-v0" --algo ppo --use-gae --lr 2.5e-4 --clip-param 0.1 --value-loss-coef 0.5 --num-processes 4 --num-steps 128 --num-mini-batch 4 --log-interval 100 --use-linear-lr-decay --entropy-coef 0.01 --eval-interval 50

ここで指定したスクリプト(run_group_ppo.py, run_ga.py, ...)は、たんに引数つきで必要な関数を呼び出しているだけですーーなので、これらのファイルの引数を編集することで、いろいろなパターンを試すことができます。それぞれの引数の意味は、サンプルのページで解説されています:

https://github.com/EvolutionGym/evogym/tree/main/examples

またコマンドラインの引数は、すべて強化学習(PPO )のものです。これらの意味は、次の元サイトを参照とありますが……:[※1]

https://github.com/ikostrikov/pytorch-a2c-ppo-acktr-gail

進化・学習の結果は、次のようなコマンドで確認できます(指定した個体や世代のアニメーションが表示されます):

$ python visualize.py --env-name "Walker-v0"
Enter experiment name: test_ga
Enter generation number: 3
Enter robot rank: 3
Enter num iters: 1000

全個体・全世代のアニメーションを、ファイルに生成することもできます(GIF 形式):[※2]

$ python make_gifs.py

生成されたアニメーションのファイルは、次のフォルダ下の試みごとのフォルダ群に保存されます:[※3][※4]

evogym/examples/saved_data/all_media/...


※1
このサイトの記述だけだとあまり参考にならないと思うので、OpenAIのStable Baselinesのガイドを挙げておきます:
https://stable-baselines.readthedocs.io/en/master/modules/ppo1.html
※2
このコマンドも、(アニメーションをリアルタイムに表示こそしないものの、実行時には)描画できるデバイスが指定されている必要があります。
※3
これは「歩く」というしごとを、進化計算(GA)+強化学習(PPO )で試みたもののひとつですーー上から第0世代〜第3世代のロボット群ですが、歩かないものは淘汰され、歩くものからはそれなりにカタチとうごきを引き継ぎ、最後は速く歩けるものが残っていますね(この試みでは、第2と第3世代の右端のロボットが、このしごと(「歩く」)にもっとも適応した(=最適化された)ロボットです)。
※4
次の試みとも結果が微妙に違っているのが面白かったりーーなおこちらは、人工生命研究の歴史から「Evolution Gym 」について解説した、岡瑞起氏よる記事です:
https://note.com/mizuki_oka/n/n1719412f641b