前言
Unity ML-Agents是一个类似于gym的一个3D强化学习仿真平台,你可以用它现成的环境来训练模型,也能通过其开发规范自己设计一个仿真环境用于训练。在使用它的时候,由于它不像gym一样有个方法可以自主选择是否渲染,而我在官方的资料中也没翻到如何设置为不渲染的模式,这样一来使用的时候必须显示图形化界面,而一些Linux服务器(尤其是那些只提供Jupyter Lab的服务器)可能并没有提供足够的权限来为之配置。当手中有一台服务器,却没法用它来跑东西是一件很难受的事。好在,遇上我这么一个爱折腾Linux的人说什么也得给它部署上去,于是探索的旅程从此开启。
可用工具
- Xvfb: 一个轻量的虚拟X server。
- chroot:root权限下用于设置一个目录为根目录,并在这个新的根目录下里面运行命令。
- Proot : 一个无root权限可用的类chroot+mount工具。
- ubuntu rootfs: 一个小型的ubuntu根文件系统,利用chroot可实现类似于切换到一个新的系统中。
原理
利用proot进入到ubuntu rootfs,利用rootfs里面的Xvfb搭建一个虚拟X server用于unity引擎显示画面,并且在里面运行ML-Agents环境。
尝试运行ML-Agents环境
- 运行环境:一台只提供Jupyter Lab的64位服务器,无root权限,无X server和Xvfb。
- 所用rootfs:ubuntu 16.04的rootfs
- 测试ML-Agents:Roller Ball
接下来将按照如下步骤进行:
- 点击这里或使用百度网盘(提取码:sutd)下载rootfs压缩包,将rootfs压缩包上传到jupyter并解压到某个位置。
- 点击这里或使用百度网盘(提取码:x788)下载ML-Agents压缩包,上传ML-Agents文件并解压到rootfs下的某个位置,比如rootfs的根目录下,并根据自己的可执行文件的位置修改如下命令添加执行权限:
chmod +x linux/rollerball.x86_64
- 下载静态编译版的proot 可执行文件,重命名为proot,上传到jupyter,同样利用chmod命令为proot添加执行权限:
chmod +x proot
- 在jupyter新建一个终端,根据你的文件位置修改如下命令并执行启动Xvfb
PROOT_NO_SECCOMP=1 ./proot -b /dev -b /proc -r ./ubuntu-rootfs/ Xvfb :1 -screen 0 800x800x16
其中PROOT_NO_SECCOMP是proot支持的一个标识,由于我用的服务器用了seccomp(一种内核安全机制),因此设置了这个变量,没设置的情况下运行之后的proot命令会报错“proot info: pid 4349: terminated with signal 11”;proot的-b参数代表要挂载哪个目录到rootfs根目录下面,由于Unity运行时需要读取/proc和/dev下的文件,因此需要挂载这两个目录;-r参数代表rootfs的根目录所在目录;最后加上在rootfs下所需执行的命令Xvfb,其他可用参数详见这里。Xvfb的参数 :1 与DISPLAY环境变量含义一致,-screen后的第一个0代表第一个屏幕,800x800x16代表屏幕配置为800x800 16位深色彩。 - 自定义ML-Agents启动文件,具体操作为新建文件(如myrollerball.x86_64,文件名可任意,但后缀用于mlagents库识别,不能缺失),其中的路径根据自己实际情况作修改,文件内容如下
#!/bin/sh
export PROOT_NO_SECCOMP=1
export DISPLAY=:1
./proot -b /proc -b /dev -r ./ubuntu-rootfs/ /linux/rollerball.x86_64 $1 $2
其中, 环境变量DISPLAY对应上面的Xvfb参数 :1,用于设置GUI应用显示在哪个端口。$1和$2表示该脚本接收的第一个参数和第二个参数,由于mlagents库启动环境会传入两个参数 --mlagents-port
和 端口号
,因此需要同时将这两个参数也传到ML-Agents可执行文件。
- 使用python API对接ML-Agents,代码如下
from mlagents_envs.environment import UnityEnvironment
env_name = './myrollerball.x86_64'
env = UnityEnvironment(file_name=env_name, base_port=5004)
env.reset()
如无报错成功执行即为连接成功,最后记得使用env.close()关闭连接。