PyBulletでURDF(Unified Robot Description Format) URDF解説編(4)
前回はロボットtr1のurdfモデルを作りました。
今回はロボットtr1をPyBulletでシミュレーションさせます。
1.plane.urdf(床)の準備
シミュレーションするには地上に相当する床を定義する必要があります。マニュアルを見るとコード上で定義できるはずですが、うまくいかなかったので各種サンプルと同様urdfで定義しました。
次のコードをパソコン上にplane.urdfとして保存します。縦横10mで厚さ1cm、摩擦係数(friction)=1.0の床を定義しています。
<?xml version="1.0" ?>
<robot name="plane">
<link name="planeLink">
<contact>
<lateral_friction value="1"/>
</contact>
<inertial>
<origin rpy="0 0 0" xyz="0 0 0"/>
<mass value=".0"/>
<inertia ixx="0" ixy="0" ixz="0" iyy="0" iyz="0" izz="0"/>
</inertial>
<visual>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<box size="10 0.01 10"/>
</geometry>
<material name="Gray">
<color rgba="0.8 0.8 0.8 1" />
</material>
</visual>
<collision>
<origin rpy="0 0 0" xyz="0 0 0"/>
<geometry>
<box size="10 0.01 10"/>
</geometry>
</collision>
</link>
</robot>
2.PyBulletで動かしてみる
いつもの通りColaboratoryを使います。
(1)ライブラリのインストール
!apt-get update
!apt-get -qq -y install xvfb freeglut3-dev ffmpeg
!pip -q install pyglet
!pip -q install pyopengl
!pip -q install pyvirtualdisplay
!apt-get install x11-utils
!pip install pybullet
(2)plane.urdfをパソコンからColaboratoryにアップロード
!rm p*.urdf
from google.colab import files
uploaded = files.upload()
(3)tr1.urdfをパソコンからColaboratoryにアップロード
tr1.urdfは前回の記事で作ったものを使います。
!rm t*.urdf
from google.colab import files
uploaded = files.upload()
(4)PyBulletで動かしてみます。
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
import time
from pyvirtualdisplay import Display
import pybullet as p
from pybullet_utils import bullet_client
from IPython.display import HTML
from matplotlib.animation import PillowWriter
#表示の準備
display = Display(visible=0, size=(1024, 768))
display.start()
import os
os.environ["DISPLAY"] = ":" + str(display.display) + "." + str(display.screen)
#PyBullet開始
p.connect(p.DIRECT)
#PCの場合はp.GUI接続もあり
#p.connect(p.GUI)
#PyBulletで複数モデルをシミュレーションする場合はclientを作成し、
#以降はpの代わりに_pを使う(参考)。
#_p=bullet_client.BulletClient()
#urdfファイルをPyBulletに読み込む
IDtr1 = p.loadURDF("tr1.urdf",basePosition = [0,0.3,0])
p.loadURDF("plane.urdf",basePosition = [0,0,0], useFixedBase=True)
#表示のための投影マトリクス計算
proj_matrix = p.computeProjectionMatrixFOV(
fov=60, aspect=320/240,
nearVal=0.1, farVal=100.0)
#jointコントローラ
for joint in range(p.getNumJoints(IDtr1)):
info = p.getJointInfo(IDtr1, joint)
print(info)
# info[2]=4は固定軸
if info[2] != 4:
p.setJointMotorControl2(IDtr1, joint, p.POSITION_CONTROL, targetPosition=0.0, force=10000,positionGain = 0.8)
#シミュレーション ---------------------------------------------------
#画像保存リスト初期化
frames = []
#重力
p.setGravity(0, -9.8, 0)
#step time
p.setTimeStep(0.005)
for i in range(100):
tr1pos, tr1orn = p.getBasePositionAndOrientation(IDtr1)
#表示のためのカメラ設定
#cameraTargetPosition 対象の位置
#distance 対象からカメラの距離
#upAxisIndex 1:y軸が上下 2:z軸が上下
view_matrix = p.computeViewMatrixFromYawPitchRoll(
cameraTargetPosition=tr1pos,
distance=0.4,
yaw=-60,
pitch=-10,
roll=0,
upAxisIndex=1)
#描画計算
(_, _, px, _, _) = p.getCameraImage(1024,1024,viewMatrix=view_matrix,projectionMatrix=proj_matrix,renderer=p.ER_BULLET_HARDWARE_OPENGL)
#pyplotで画面に表示させるためリスト形式をnumpy array形式に変換
#alphaを削除して表示
rgb_array = np.array(px)
rgb_array = rgb_array[:, :, :3]
frames.append(rgb_array)
p.stepSimulation()
p.disconnect()
#シミュレーション終了 -----------------------------------------------
#動画作成
fig = plt.figure(figsize=(3.2, 2.5))
plt.axis('off')
plt.subplots_adjust(left=0, right=1, bottom=0, top=1)
images = []
for f in frames:
image = plt.imshow(f)
images.append([image])
ani = matplotlib.animation.ArtistAnimation(fig, images, interval=30, repeat_delay=1)
#gifを保存する場合はGoogleDriveをマウントすること
#ani.save("/content/drive/My Drive/images.gif", writer="pillow")
HTML(ani.to_jshtml())
jointの一覧が表示された後、次のような動画が表示されます。
カメラがロボットに追従しているのでわかりずらいですが、5cmくらい降下して着地した時、jointが衝撃で曲がったのを制御で角度0に戻しています。
ちなみにロボットは正面から見ています。
(5)PyBulletの解説
- PyBulletの開始、終了
p.connect(p.DIRECT):開始 p.disconnect():終了 - tr1.urdfを読み込み、tr1の原点を高さ0.3mの位置に配置します。以降ロボットtr1をIDtr1で指定します。
IDtr1 = p.loadURDF("tr1.urdf",basePosition = [0,0.3,0]) - plane.urdfを原点に読み込み、固定します。
p.loadURDF("plane.urdf",basePosition = [0,0,0], useFixedBase=True) - IDtr1のjoint情報を一覧で表示します。
for joint in range(p.getNumJoints(IDtr1)):
info = p.getJointInfo(IDtr1, joint)
print(info)
表示内容の詳細は、PyBullet Quickstart Guideを参照してください。
- jointの制御を指定します。上記のforループ内で実施しています。jointのinfo[2]はjoint種別を示していて、4はFix jointです。TR1のjointはFixとRevolutionしかないので、Revolutionについて制御を指定しています。
if info[2] != 4:
p.setJointMotorControl2(IDtr1, joint, p.POSITION_CONTROL, targetPosition=0.0, force=10000,positionGain = 0.8)
p.POSITION_CONTROL:制御方式。まずは位置制御としました。targetPosition :位置目標値
force :最大トルク(N・m)。10000N・mは約10kg・cmに相当positionGain :位置制御のゲイン。指定しないと踏ん張らなかったので - 重力を指定します。y軸を上下方向に使っているので、y軸のマイナス方向に重力を指定します。
p.setGravity(0, -9.8, 0) - 1回の計算のタイムステップを指定します。タイムステップが長いとjointの制御が発散してしまいます。今回は5msにしています。
p.setTimeStep(0.005) - 繰り返し各ステップを演算します。とりあえず100ステップなので0.5秒を計算しています。
for i in range(100):
・・・・
p.stepSimulation() - ロボットTR1の中心座標を取得してカメラをセッティング、画像を作ってリストframesに追加します。
tr1pos, tr1orn = p.getBasePositionAndOrientation(IDtr1)
view_matrix = p.computeViewMatrixFromYawPitchRoll(
・・・・・
frames.append(rgb_array)
つづく
これ(ロボットモデルtr1)を使ってPyBulletで具体的に何をさせるか、現在考え中・・・です。PyBulletがurdfのエラー箇所を出力してくれないので、planeを追加するだけでも結構苦戦してしまいました。
PyBulletでURDF(Unified Robot Description Format) URDF解説編(3)
前回は既存のロボットモデルhumanoid.urdfの中身を見ていきました。
今回はオリジナルのモデルを作成してみます。
1.計画
某ロボットキットの写真を眺めながら、こんな図を書いてみました。某ロボットキッとは軸の位置、寸法はもちろん異なります。
可動軸の数は胴体1、脚6×2=12、手3×2=6、合計19です。
2.URDFファイル
とにかくわかる範囲で作ってみます。以下は現状の制限やら、注意点やら。
- URDFのファイル名に”-”(マイナス)を使うとPyBulletが読み込んでくれませんでした。
- jointにrevoluteを使っています。revoluteは<axis/>で回転軸を指定する他<limit/>で軸の可動範囲(lower、upper)、最大回転速度(velocity)、力(effort)等の制限を指定してあげる必要があるとのことなので(ROSのホームページ解説から)、後で見直すのを前提に適当に指定しています。
- linkの慣性モーメント(inertia)と質量(mass)も後で見直すのを前提に適当な値を入れています。
以下に作ったurdfファイルを添付します。上の図と見比べながら見ると理解しやすいと思います。
<?xml version="1.0"?>
<robot name="tr1">
<link name="base" >
<inertial>
<origin rpy = "0 0 0" xyz = "0 0 0" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
</link>
<link name="root" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 0.015 0.000" />
<mass value = "0.400" />
<inertia ixx = "0.02" ixy = "0" ixz = "0" iyy = "0.02" iyz = "0" izz = "0.02" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 0.015 0.000" />
<geometry>
<box size = "0.030 0.030 0.060" />
</geometry>
</collision>
</link>
<joint name="root" type="fixed" >
<parent link = "base" />
<child link = "root" />
<origin rpy = "0 0 0" xyz = "0.000 0.000 0.000" />
</joint>
<!-- chest -->
<link name="chest" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 0.0325 0.000" />
<mass value = "0.400" />
<inertia ixx = "0.02" ixy = "0" ixz = "0" iyy = "0.02" iyz = "0" izz = "0.02" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 0.0325 0.000" />
<geometry>
<box size = "0.030 0.045 0.080" />
</geometry>
</collision>
</link>
<joint name="chest" type="revolute" >
<parent link = "root" />
<child link = "chest" />
<origin rpy = "0 0 0" xyz = "0.000 0.030 0.000" />
<axis xyz = "0 0 -1" />
<limit effort="10000.0" lower="0" upper="3.14" velocity="0.5"/>
</joint>
<!-- neck -->
<link name="neck" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 0.025 0.000" />
<mass value = "0.050" />
<inertia ixx = "0.02" ixy = "0" ixz = "0" iyy = "0.02" iyz = "0" izz = "0.02" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 0.025 0.000" />
<geometry>
<sphere radius = "0.020" />
</geometry>
</collision>
</link>
<joint name="neck" type="fixed" >
<parent link = "chest" />
<child link = "neck" />
<origin rpy = "0 0 0" xyz = "0.000 0.060 0.000" />
</joint>
<!-- right foot -->
<link name="right_hip_tw" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.025 -0.0025" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.010 -0.0025" />
<geometry>
<box size = "0.030 0.002 0.025" />
</geometry>
</collision>
</link>
<joint name="right_hip_tw" type="revolute" >
<parent link = "root" />
<child link = "right_hip_tw" />
<origin rpy = "0 0 0" xyz = "0.000 0.000 0.020" />
<axis xyz = "0 -1 0" />
<limit effort="10000.0" lower="0" upper="3.14" velocity="0.5"/>
</joint>
<link name="right_hip_rl" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 0.000 0.010" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 0.000 0.010" />
<geometry>
<box size = "0.030 0.020 0.040" />
</geometry>
</collision>
</link>
<joint name="right_hip_rl" type="revolute" >
<parent link = "right_hip_tw" />
<child link = "right_hip_rl" />
<origin rpy = "0 0 0" xyz = "0.000 -0.030 -0.005" />
<axis xyz = "-1 0 0" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="right_hip_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.0325 0.000" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.0325 0.000" />
<geometry>
<box size = "0.030 0.085 0.030" />
</geometry>
</collision>
</link>
<joint name="right_hip_fb" type="revolute" >
<parent link = "right_hip_rl" />
<child link = "right_hip_fb" />
<origin rpy = "0 0 0" xyz = "0.000 -0.025 0.010" />
<axis xyz = "0 0 1" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="right_knee_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.040 0.000" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.040 0.000" />
<geometry>
<box size = "0.030 0.050 0.030" />
</geometry>
</collision>
</link>
<joint name="right_knee_fb" type="revolute" >
<parent link = "right_hip_fb" />
<child link = "right_knee_fb" />
<origin rpy = "0 0 0" xyz = "0.000 -0.065 0.000" />
<axis xyz = "0 0 -1" />
<limit effort="10000.0" lower="0" upper="3.14" velocity="0.5"/>
</joint>
<link name="right_anklee_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.030 0.005" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.030 0.005" />
<geometry>
<box size = "0.030 0.020 0.040" />
</geometry>
</collision>
</link>
<joint name="right_anklee_fb" type="revolute" >
<parent link = "right_knee_fb" />
<child link = "right_anklee_fb" />
<origin rpy = "0 0 0" xyz = "0.000 -0.055 0.000" />
<axis xyz = "0 0 -1" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="right_anklee_rl" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.024 0.010" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.024 0.010" />
<geometry>
<box size = "0.040 0.002 0.050" />
</geometry>
</collision>
</link>
<joint name="right_anklee_rl" type="revolute" >
<parent link = "right_anklee_fb" />
<child link = "right_anklee_rl" />
<origin rpy = "0 0 0" xyz = "0.000 -0.030 -0.005" />
<axis xyz = "1 0 0" />
<limit effort="10000.0" lower="-0.5" upper="0.5" velocity="0.5"/>
</joint>
<!-- right hand -->
<link name="right_shoulder_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 0.000 0.000" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
</link>
<joint name="right_shoulder_fb" type="revolute" >
<parent link = "chest" />
<child link = "right_shoulder_fb" />
<origin rpy = "0 0 0" xyz = "0.000 0.045 0.045" />
<axis xyz = "0.0 0.0 1.0" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="right_shoulder_rl" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.010 0.010" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.010 0.000" />
<geometry>
<box size = "0.030 0.040 0.020" />
</geometry>
</collision>
</link>
<joint name="right_shoulder_rl" type="revolute" >
<parent link = "right_shoulder_fb" />
<child link = "right_shoulder_rl" />
<origin rpy = "0 0 0" xyz = "0.000 0.000 0.025" />
<axis xyz = "-1 0 0" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="right_shoulder_twist" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.025 0.010" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.025 0.000" />
<geometry>
<box size = "0.020 0.040 0.030" />
</geometry>
</collision>
</link>
<joint name="right_shoulder_twist" type="fixed" >
<parent link = "right_shoulder_rl" />
<child link = "right_shoulder_twist" />
<origin rpy = "0 0 0" xyz = "0.000 -0.030 0.000" />
</joint>
<link name="right_elbow_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.035 0.000" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.035 0.000" />
<geometry>
<box size = "0.020 0.040 0.020" />
</geometry>
</collision>
</link>
<joint name="right_elbow_fb" type="revolute" >
<parent link = "right_shoulder_twist" />
<child link = "right_elbow_fb" />
<origin rpy = "0 0 0" xyz = "0.000 -0.035 0.000" />
<axis xyz = "0 0 1" />
<limit effort="10000.0" lower="0" upper="3.14" velocity="0.5"/>
</joint>
<link name="right_wrist" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.0125 0.000" />
<mass value = "0.020" />
<inertia ixx = "0.02" ixy = "0" ixz = "0" iyy = "0.02" iyz = "0" izz = "0.02" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.0125 0.000" />
<geometry>
<sphere radius = "0.0125" />
</geometry>
</collision>
</link>
<joint name="right_wrist" type="fixed" >
<parent link = "right_elbow_fb" />
<child link = "right_wrist" />
<origin rpy = "0 0 0" xyz = "0.000 -0.060 0.000" />
</joint>
<!-- left foot -->
<link name="left_hip_tw" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.025 0.0025" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.010 0.0025" />
<geometry>
<box size = "0.030 0.002 0.025" />
</geometry>
</collision>
</link>
<joint name="left_hip_tw" type="revolute" >
<parent link = "root" />
<child link = "left_hip_tw" />
<origin rpy = "0 0 0" xyz = "0.000 0.000 -0.020" />
<axis xyz = "0 -1 0" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="left_hip_rl" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 0.000 -0.010" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 0.000 -0.010" />
<geometry>
<box size = "0.030 0.020 0.040" />
</geometry>
</collision>
</link>
<joint name="left_hip_rl" type="revolute" >
<parent link = "left_hip_tw" />
<child link = "left_hip_rl" />
<origin rpy = "0 0 0" xyz = "0.000 -0.030 0.005" />
<axis xyz = "-1 0 0" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="left_hip_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.0325 0.000" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.0325 0.000" />
<geometry>
<box size = "0.030 0.085 0.030" />
</geometry>
</collision>
</link>
<joint name="left_hip_fb" type="revolute" >
<parent link = "left_hip_rl" />
<child link = "left_hip_fb" />
<origin rpy = "0 0 0" xyz = "0.000 -0.025 -0.010" />
<axis xyz = "0 0 1" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="left_knee_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.040 0.000" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.040 0.000" />
<geometry>
<box size = "0.030 0.050 0.030" />
</geometry>
</collision>
</link>
<joint name="left_knee_fb" type="revolute" >
<parent link = "left_hip_fb" />
<child link = "left_knee_fb" />
<origin rpy = "0 0 0" xyz = "0.000 -0.065 0.000" />
<axis xyz = "0 0 -1" />
<limit effort="10000.0" lower="0" upper="3.14" velocity="0.5"/>
</joint>
<link name="left_anklee_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.030 -0.005" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.030 -0.005" />
<geometry>
<box size = "0.030 0.020 0.040" />
</geometry>
</collision>
</link>
<joint name="left_anklee_fb" type="revolute" >
<parent link = "left_knee_fb" />
<child link = "left_anklee_fb" />
<origin rpy = "0 0 0" xyz = "0.000 -0.055 0.000" />
<axis xyz = "0 0 -1" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="left_anklee_rl" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.024 -0.010" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.024 -0.010" />
<geometry>
<box size = "0.040 0.002 0.050" />
</geometry>
</collision>
</link>
<joint name="left_anklee_rl" type="revolute" >
<parent link = "left_anklee_fb" />
<child link = "left_anklee_rl" />
<origin rpy = "0 0 0" xyz = "0.000 -0.030 0.005" />
<axis xyz = "1 0 0" />
<limit effort="10000.0" lower="-0.5" upper="0.5" velocity="0.5"/>
</joint>
<!-- left hand -->
<link name="left_shoulder_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 0.000 0.000" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
</link>
<joint name="left_shoulder_fb" type="revolute" >
<parent link = "chest" />
<child link = "left_shoulder_fb" />
<origin rpy = "0 0 0" xyz = "0.000 0.045 -0.045" />
<axis xyz = "0.0 0.0 1.0" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="left_shoulder_rl" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.010 -0.010" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.010 0.000" />
<geometry>
<box size = "0.030 0.040 0.020" />
</geometry>
</collision>
</link>
<joint name="left_shoulder_rl" type="revolute" >
<parent link = "left_shoulder_fb" />
<child link = "left_shoulder_rl" />
<origin rpy = "0 0 0" xyz = "0.000 0.000 -0.025" />
<axis xyz = "-1 0 0" />
<limit effort="10000.0" lower="-1.507" upper="1.507" velocity="0.5"/>
</joint>
<link name="left_shoulder_twist" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.025 -0.010" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.025 0.000" />
<geometry>
<box size = "0.020 0.040 0.030" />
</geometry>
</collision>
</link>
<joint name="left_shoulder_twist" type="fixed" >
<parent link = "left_shoulder_rl" />
<child link = "left_shoulder_twist" />
<origin rpy = "0 0 0" xyz = "0.000 -0.030 0.000" />
</joint>
<link name="left_elbow_fb" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.035 0.000" />
<mass value = "0.0001" />
<inertia ixx = "0.0001" ixy = "0" ixz = "0" iyy = "0.0001" iyz = "0" izz = "0.0001" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.035 0.000" />
<geometry>
<box size = "0.020 0.040 0.020" />
</geometry>
</collision>
</link>
<joint name="left_elbow_fb" type="revolute" >
<parent link = "left_shoulder_twist" />
<child link = "left_elbow_fb" />
<origin rpy = "0 0 0" xyz = "0.000 -0.035 0.000" />
<axis xyz = "0 0 1" />
<limit effort="10000.0" lower="0" upper="3.14" velocity="0.5"/>
</joint>
<link name="left_wrist" >
<inertial>
<origin rpy = "0 0 0" xyz = "0.000 -0.0125 0.000" />
<mass value = "0.020" />
<inertia ixx = "0.02" ixy = "0" ixz = "0" iyy = "0.02" iyz = "0" izz = "0.02" />
</inertial>
<collision>
<origin rpy = "0 0 0" xyz = "0.000 -0.0125 0.000" />
<geometry>
<sphere radius = "0.0125" />
</geometry>
</collision>
</link>
<joint name="left_wrist" type="fixed" >
<parent link = "left_elbow_fb" />
<child link = "left_wrist" />
<origin rpy = "0 0 0" xyz = "0.000 -0.060 0.000" />
</joint>
</robot>
以前の記事を参考にしながらColaboratoryで表示させます。
①上記のURDFファイルをtr1.urdfという名前でパソコン上の適当な場所に保存します。
②URDF解説編(1)のColaboratoryのソースの一部を、以下の通り変更します。
・urdfファイル名 赤字が変更後
#urdfファイルをPyBulletに読み込む
p.loadURDF("tr1.urdf")
・カメラ設定
#表示のためのカメラ設定
view_matrix = p.computeViewMatrixFromYawPitchRoll(
cameraTargetPosition=[0.0,-0.05,0.0],
distance=0.4,
yaw=90,
pitch=0,
roll=0,
upAxisIndex=1)
ーーーーーーーーーーーーーーーーーーーーーーーーー
で、実行すると次のような図が表示されます。
upAxisIndex=1とするとY軸が表示の上下方向に設定されます。
2020/2/29追記
jointを見直しました。jointの回転方向を実際に回転させて確認し、上記tr1.urdfモデルを修正、入替えました。jointのプラスの回転方向は、joint定義内のaxisで定義したベクトルの右ねじ方向になります。回転角はrad(ラジアン、2πで360°)です。
jointはPyBulletで読み込んだ時に自動的に番号が与えられます。urdfファイル内の定義順ではなく、linkの接続順のようです。tr1のPyBullet読み込み後のjoint番号および回転方向の一覧は次の通り。
0:root fix 1:chest revolute +方向 かがむ(0~3.14) 2:neck fix 3:right_shoulder_fb revolute +方向 手を前に出す(-1.507~1.507) 4:right_shoulder_rl revolute +方向 手を右方向に出す(-1.507~1.507) 5:right_shoulder_twist fix 6:right_elbow_fb revolute +方向 ひじをたたむ(0~3.14) 7:right_wrist fix 8:left_shoulder_fb revolute +方向 手を前に出す(-1.507~1.507) 9:left_shoulder_rl revolute +方向 手を右方向に出す(-1.507~1.507) 10:left_shoulder_twist fix 11:left_elbow_fb revolute +方向 ひじをたたむ(0~3.14) 12:left_wrist fix 13:right_hip_tw revolute +方向 足を出した時右側にひねる(-1.507~1.507) 14:right_hip_rl revolute +方向 足を右横に出す(-1.507~1.507) 15:right_hip_fb revolute +方向 足を前に出す(-1.507~1.507) 16:right_knee_fb revolute +方向 ひざを曲げる(0~3.14) 17:right_anklee_fb revolute +方向 足首を後ろに蹴る(-1.507~1.507) 18:right_anklee_rl revolute +方向 前を向いて右ねじの方向にひねる(-0.5~0.5) 19:left_hip_tw revolute +方向 足を出した時右側にひねる(-1.507~1.507) 20:left_hip_rl revolute +方向 足を右横に出す(-1.507~1.507) 21:left_hip_fb revolute +方向 足を前に出す(-1.507~1.507) 22:left_knee_fb revolute +方向 ひざを曲げる(0~3.14) 23:left_anklee_fb revolute +方向 足首を後ろに蹴る(-1.507~1.507) 24:left_anklee_rl revolute +方向 前を向いて右ねじの方向にひねる(-0.5~0.5)
つづく
次回からPyBulletで少しづつ動かしていこうと思います。revoluteの設定等が現状は適当なので、適宜見直していきます。(見直しました)
強化学習 カテゴリーの記事一覧 - やってみた!
PyBulletでURDF(Unified Robot Description Format) URDF解説編(2)
前回はURDFの基本構造の紹介と、Colaboratoryを使ってPyBulletにhumanoid.urdfを読み込ませ、表示させる方法を紹介しました。
今回はhumanoid.urdfの中身を見ていきます。
1.PyBulletのHumanoid.urdfの用途について
PyBulletのプロジェクトにあるhumanoid.urdfは宙返り(backflip)学習サンプル用の人型モデルのようです。PyBulletサイトに簡単な紹介がありました。
基になっているサイトはこちらで、モーションキャプチャーデータをガイドとし、物理シミュレータPyBullet内のロボット制御を深層強化学習させるといった興味深いものです。宙返りだけでなく、さまざまな動きに挑戦しています。
DeepMimic: Example-Guided Deep Reinforcement Learning of Physics-Based Character Skills
2.humanoid.urdfの中身
humanoid.urdfはinertialとcollisionのみを定義していてvisualは定義してませんが、前回の記事で紹介した様にPyBulletで読み込むとちゃんと表示されます。visualの代わりにcollisionのgeometryが表示されている様です。
humanoid.urdfは次のリンク先にあります。リンク先を開いて画面右上のRawボタンを押すとhumanoid.urdfをダウンロードできます。
リンク :bullet3/humanoid.urdf at master · bulletphysics/bullet3 · GitHub
urdfは前回の記事で紹介した通り、ロボットを部品であるlink(parent、child)をjointで結合して定義します。jointはparent linkの座標系をjoint座標系に変換します。child linkの座標系はjoint座標系と同じになります。
humanoid.urdfを読み、linkを整理すると次の図になります。
各linkの慣性モーメント(Inertia)は、Inertiaマトリクスの要素9つのうち、同じ値になる3つを除いた6つで定義します。また、通常はInertiaマトリクスの対角要素以外は0になります。
URDFの単位はmass(質量)はkg、長さはm、inertia(慣性モーメント)はkgm2です。
次にhumanoid.urdfのjointの位置を整理します。
ロボットの基準位置は腰の中心で、脚の付け根のjointと同じ高さにあります。URDF読み込み時にスケール1/4としているのでhumanoidの高さは約1.62mです。重量は各linkのmassを単純に加算すると45kgでした。
黒の線はjointのoriginで定義されている座標変換です。例えば次のように定義されていたとすると、
<link name="right_hip" >
</link>
<joint name="right_hip" type="spherical" >
<parent link="root" />
<child link="right_hip" />
</joint>
<link name="right_knee" >
<collision>
<origin rpy = "-1.570796 0 0" xyz = "0.000000 -0.800000 0.000000" />
<geometry>
<capsule length="1.240000" radius="0.200000"/>
</geometry>
</collision>
</link>
<joint name="right_knee" type="revolute" >
<parent link="right_hip" />
<child link="right_knee" />
<origin rpy = "0 0 0" xyz = "0.0 -1.686184 0.0" />
</joint>
link "right_knee"の座標原点はparent_linkであるright_hipの原点=joint "right_hip"からxyz = "0.0 -1.686184 0.0"だけ 移動します。上図の黒の線はこの移動量を示しています。同様に青の線はlinkの中で定義されているcollisionのorignと等しく、linkの原点=joint位置からの移動量-0.8を示しています。
また、linkの円筒形の回転は単位がラジアンなので、-1.57=-2/π = 90度回転させています。
humanoidを見ると、URDFでのロボット定義は基本的にlinkのinertial、collisionを与えてjointで結合するだけでよさそうです。
なお、humanoidのjointはspherical×8、revloute×4の計12でした(固定であるfixedを除きます)。
つづく
URDFの仕組みがある程度わかったところで、次回はオリジナルのロボットを定義してみようと思います。
PyBulletでURDF(Unified Robot Description Format) URDF解説編(1)
前回までは3D物理シミュレータBulletのpythonラッパーPyBulletで動くGym,HumanoidFlagrun(Harder)BulletEnv-v0を使い深層強化学習を試してみました。
本記事では、オリジナルのロボットのシミュレーション環境を構築できる様、まずはURDFについて調べてみます。
URDF(Unified Robot Description Format) はロボットの構造を示すテキストファイルで、ROS(Robot Operating System)で規定されています。PyBulletでもURDFで定義されたロボットモデルを扱うことができ、URDFはロボットシミュレーションモデルのデファクトスタンダードに近いものになりつつあります。
なお、OPEN AI GymのモデルはMJCFと呼ばれるMuJoCo形式で記述されています。PyBulletはMJCFにも対応しているようです。
注意
URDFに関するPyBulletとROSの互換性については確認していません。本記事のURDFファイルはPyBulletで動作確認しており、ROSでは動かない可能性があります。
また、URDFのタグの解説はROSのチュートリアルを参考にしており、タグ個別にPyBulletで動作確認していません。そのためタグ解説で紹介していても、PyBulletで動かないタグがあるかも知れません。
参考にしたROSのURDF(xml)のタグマニュアル、チュートリアルのページです。
Bullet(PyBullet)のホームぺージです。PyBulletのドキュメントにURDFタグの解説が見あたりませんでした。ただし、PyBulletのGithub上にサンプルのURDFがたくさんあります。
STL(3Dデータ)作成に使えそうなフリーの3D CADです。現時点では試せていませんが、忘れないようにリンクを貼っておきます。
1.URDFの初歩
前書きが長くなってしまいましたが、ROSのチュートリアルのページの内容を参考にURDFのフォーマットを見ていきます。
urdf/Tutorials/Building a Visual Robot Model with URDF from Scratch - ROS Wiki
まずは、実際に次のURDFを例に意味を調べます。
<?xml version="1.0"?>
<robot name="origins">
<link name="base_link">
<visual>
<geometry>
<cylinder length="0.6" radius="0.2"/>
</geometry>
</visual>
</link>
<link name="right_leg">
<visual>
<geometry>
<box size="0.6 0.1 0.2"/>
</geometry>
<origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
</visual>
</link>
<joint name="base_to_right_leg" type="fixed">
<parent link="base_link"/>
<child link="right_leg"/>
<origin xyz="0 -0.22 0.25"/>
</joint>
</robot>
URDFタグの解説
- <?xml version="1.0"?>
xmlバージョン1.0形式のファイルであることを宣言しています。 - <robot name="origins">~</robot> urdf/XML/robot - ROS Wiki
ロボットの定義を示すelement(要素)です。ロボットに含まれる各要素は全てこの間に記述します。ロボットは手、足、胴体などの構成部品linkと、接続部を示すjointで構成されます。 - <link name="xxxx">~</link> urdf/XML/link - ROS Wiki
linkの定義を示すelementです。
<inertial>~</inertial>(optional) シミュレーション用の質量、慣性モーメント
<origin>(optional) link座標系から見た重心位置、姿勢
<inertia> 慣性モーメント(kgm2)
<mass> 質量(kg)
<visual>~</visual>(optional) 表示用データ
<origin>(optional) link座標系から見た形状の中心位置、姿勢
<geometry>(required) 形状
<box>,<cylinder>,<sphere> 標準で用意されている形状
<mesh> meshで定義される任意の形状、3DCAD等で作成します
<material> (optional) 色、テクスチャ
表示の外形、座標
<collision>~</collision>(optional) 衝突判定用データ
<origin> (optional) visualのoriginと同じ
<geometry> visualのgeometryと同じ
- <joint name="xxxx" type="aaaa">~</joint> urdf/XML/joint - ROS Wiki
jointの定義を示すelementです。
typeには次の種類があります(PyBullet)。
・revolute 軸方向に回転、上限、下限あり
・spherical 3軸自由回転(ボール接続)。ROSのマニュアルに本jointの説明を見つけられなかったのですがPyBulletで実際に使われています。互換性は?
・continuous 軸方向に回転、制限なし
・prismatic 軸方向にスライド、上限、下限あり
・fixed 固定
・floating 3軸方向、3軸回転
・planar 軸に垂直な平面上に移動可能
<parent> (required) joint接続される側の親link
<child> (required) joint接続する子link
<origin> (optional) jointの座標系=子linkの座標系を与えます。親linkの座標系で指示(親link座標系→子link座標系への変換)します。省略すると子linkの座標系=親linkの座標系になります。
<axis>(optional: defaults to (1,0,0)) jointの回転軸をjointの座標系(=子linkの座標系)で示します。軸の指定が必要無いtypeにはaxisは影響しません。
<limit> (required only for revolute and prismatic joint)
URDFでは次のようにロボットを定義します。
(1)linkで部品を定義し、jointで親linkと子linkを接続します。
(2)jointの座標系と子linkの座標系は同じです。joint定義中のoriginで親linkの座標系からjointの座標系(=子linkの座標系)への変換を定義します。
(3)jointが回転できる場合、回転中心はjoint座標系(=子link座標系)の原点です。回転軸はjoint座標系で別途設定します。
上のURDF例は下図を記述しています。
2.PyBulletでhumanoidモデルを表示させてみる
次にPyBulletのサンプルにあったhumanoidモデル(humanoid.urdf)をPyBulletで表示させてみます。ここではColaboratoryを使いますが、ローカル環境を使うとExample Browser等が使えるので、ローカル環境を構築して試してみてもいいかも。
フォルダ:bullet3/examples/pybullet/gym/pybullet_data/humanoid/humanoid.urdf
リンク :bullet3/humanoid.urdf at master · bulletphysics/bullet3 · GitHub
(1)humanoid.urdfのダウンロード
リンク先を開いて画面右上のRawボタンを押しhumanoid.urdfをダウンロードして、ダウンロードフォルダからパソコン上の任意の場所に移動、保存します。
(2)Colaboratoryノートブックの作成
ColaboratoryでPython3の新しいノートブックを作成、ランタイムのタイプはGPU無しで問題ありません。
ノートブックに適当な名前をつけます。
(3)ライブラリをインストールします
!apt-get update
!apt-get -qq -y install xvfb freeglut3-dev ffmpeg
!pip3 -q install pyglet
!pip3 -q install pyopengl
!pip3 -q install pyvirtualdisplay
!apt-get install x11-utils
!pip3 install pybullet
(4)humanoid.urdfをパソコンからColaboratoryにアップロードします
なお、Colaboratoryのアップロードは上書きしてくれないので、事前にurdfファイルを削除しています。
!rm *.urdf
from google.colab import files
uploaded = files.upload()
上記を実行すると表示される「ファイル選択」ボタンを押してパソコンに保存したhumanoid.urdfを選択します。
(5)PyBulletで表示させます
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
from pyvirtualdisplay import Display
import pybullet as p
from pybullet_utils import bullet_client
import time
#表示の準備
display = Display(visible=0, size=(1024, 768))
display.start()
import os
os.environ["DISPLAY"] = ":" + str(display.display) + "." + str(display.screen)
#PyBullet開始
p.connect(p.DIRECT)
#PCの場合はp.GUI接続もあり
#p.connect(p.GUI)
#PyBulletで複数モデルをシミュレーションする場合はclientを作成し、
#以降はpの代わりに_pを使う(参考)。
#_p=bullet_client.BulletClient()
#urdfファイルをPyBulletに読み込む
p.loadURDF("humanoid.urdf")
#表示のためのカメラ設定
view_matrix = p.computeViewMatrixFromYawPitchRoll(
cameraTargetPosition=[4,-4,3],
distance=2.,
yaw=30,
pitch=-30,
roll=0,
upAxisIndex=2)
#表示のための投影マトリクス計算
proj_matrix = p.computeProjectionMatrixFOV(
fov=60, aspect=320/240,
nearVal=0.1, farVal=100.0)
#描画計算
(_, _, px, _, _) = p.getCameraImage(512,512,viewMatrix=view_matrix,projectionMatrix=proj_matrix,renderer=p.ER_BULLET_HARDWARE_OPENGL)
#pyplotで画面に表示させるためリスト形式をnumpy array形式に変換
#alphaを削除して表示
rgb_array = np.array(px)
rgb_array = rgb_array[:, :, :3]
plt.axis('off')
plt.imshow(rgb_array)
p.disconnect()
こんな図(humanoidが横たわった図)が表示されるはずです。
基本的動作についてはコメントを挿入しているので説明不要だと思います。若干補足します。
・p.connect(p.DIRECT)
p.DIRECTはPyBulletへの接続(データ、通信授受の方法)を示す引数で、直接データ授受するという意味です。この他、GUI:グラフィカル環境経由でデータ授受、UDP:UDP通信でデータ授受等いくつかのオプションがあります。
ColaboratoryではGUIは使えませんが、ローカルPCで使う場合はGUIの方が便利かも(試していません・・・)。
・#_p=bullet_client.BulletClient()
クライアント別にPyBulletを起動、独立した環境を構築できます。主にPyBulletサーバ用でしょうか。参考にこんな使い方もできますという意図としてコメントとして残していますが、基本的には必要ありません。
PyBulletのコマンドの詳細は次を参照してください。
3.まとめ
- 3D物理シミュレータのPythonラッパーPyBulletで使えるロボットモデル定義ファイルURDFの基本構造を解説しました。
PythonベースであるPyBulletでロボットモデルを構築すれば、無理なく通常の深層学習フレームワークを使って学習させることができるはずです。 - PyBulletのサンプルにあったロボットモデルhumanoid.urdfをPyBulletに読み込ませて表示させる方法を紹介しました。
つづく
次回は実際のhumanoid.urdfの定義(ファイルの中身)を見てみます。
PyBullet-HumanoidFlagrunHarderBulletEnv-v0(4)
次はHumanoidFlagrunを学習させてみます。Humanoidの学習に使ったソースコードのenvを定義している箇所を入れ替え(コメントアウト)て動かします。ソースは前々回のこちらの記事を見てください。
ちなみに、学習を進めても全然報酬が増えず、Google Colaboratoryの時間制限がやっかいになってきたので、ローカルランタイムで動かすことにしました。深層強化学習のようにニューラルネットワークがさほど大きくなければ、GPU無しのノートPCでもそこそこ動きます。
Colaboratoryをローカルランタイムで動かす手順はこちらまで。
1. HumanoidFlagrun
PyBullet-HumanoidFlagrunHarderBulletEnv-v0(2)の記事中のソースコードのenvを定義している行のコメントアウトを入れ替えて学習を始めました。が、進捗はあまり良くありません。もしかするとBATCH_SIZEを256→512に増やすとか、パラメータを見直した方がいいのかも(ただしBATCH増やすと遅くなるかもしれません)。
1月28日
ここまでで、既に4日くらい学習させたところです。10episodeで約1000stepくらい、報酬は数10程度ですが、7,8回に1回くらい400~500の報酬を得ることがあります。昨日は数百の報酬を得ることが無かったので少しは進歩しているかも。
episode,step,reward,entropy= 61920 4624222 78.53 -15.45
episode,step,reward,entropy= 61930 4625295 528.01 -18.26
episode,step,reward,entropy= 61940 4626463 47.31 -13.27
1月29日
episode,step,reward,entropy= 65630 5051473 81.35 -19.37
episode,step,reward,entropy= 65640 5052775 88.59 -18.67
episode,step,reward,entropy= 65650 5053755 30.02 -23.14
これだけ報酬が増えないと毎stepに学習する必要はなくて、1eisodeごとまとめて学習した方が同じpolicyでの経験データを複数とれ、いいような気がしてきました。
一旦とめて、学習をまとめて行うよう修正して再開しようとしたらJupyter動かず・・。結局 pip install --upgrade ipykernel でipykernelをアップグレードしたら動くようになり、Jupyter自体を再起動して実行。リプレイメモリがクリアされるためか、再開直後はエントロピーが-17目標のところ-80ぐらいになってしまいます・・・・。
しばらくこれ(学習を1episode後まとめて実施)で続けてみます。
1月30日
報酬、1epsodeあたりのstepともにあまり進捗が見られません。ノートPCローカルランタイムだと1日60万stepぐらいしか進みませんね。
episode,step,reward,entropy= 71540 5656722 25.3 -22.67
episode,step,reward,entropy= 71550 5657850 57.45 -24.81
episode,step,reward,entropy= 71560 5659017 8.71 -28.54
1月31日
夜中にchromeが再起動したみたいで、ローカルランタイムとの接続が切れてしまいました。再接続しようとしても接続できず・・・、途中経過がわかりません。ランタイムを再立ち上げするとリプレイメモリがまたクリアされてしまうので、このまま継続します。
現状はこんな感じ。まともに方向転換できません。
episode,step,reward,entropy= 77180 6371987 23.72 -24.85
2月1日
若干ですが続くようになったようです。
episode,step,reward,entropy= 78070 6533278 662.51 -23.91
episode,step,reward,entropy= 78080 6534833 25.39 -39.83
episode,step,reward,entropy= 78090 6536899 -169.43 -27.97
2月7日
2月1日は10episodeでstep1500程度でした。今日は1episodeごとに表示させるようにしましたが、1epsodeでstep700~1000くらい転ばないようになりました。ただ動きはあいかわらず遅くて、その場でまわっている感じで2月1日とあまり印象は変わりません。動画は容量が大きいので掲載は控えます。
episode,step,reward,entropy= 85001 8354165 21.44 -38.86
episode,step,reward,entropy= 85002 8355165 952.15 -25.06
episode,step,reward,entropy= 85003 8355819 785.67 -33.10
2月12日
あまり変化は感じません・・・。ちょっと動きが小さくなったかも。
episode,step,reward,entropy= 88007 9524293 119.69 -43.60
episode,step,reward,entropy= 88008 9525049 972.15 -25.15
episode,step,reward,entropy= 88009 9525964 330.92 -29.31
2月21日
1週間くらい前、パソコンがWindowsの自動更新で再起動して止まってしまいました。学習再開させたのですがリプレイバッファがクリアされてしまったこともあってか、下手くそになってしまったようです。
episode,step,reward,entropy= 107007 14334431 -115.99 -34.55
episode,step,reward,entropy= 107008 14334492 -52.40 -48.09
episode,step,reward,entropy= 107009 14334604 27.14 -35.02
3月4日
ちょっと改善
episode,step,reward,entropy= 121005 19731822 108.4 -45.02
episode,step,reward,entropy= 121006 19732237 -221.58 -35.62
episode,step,reward,entropy= 121007 19733237 419.08 -27.24
episode,step,reward,entropy= 121008 19733624 -131.60 -30.38
3月11日
episode,step,reward,entropy= 134001 25347138 -16.19 -35.51
episode,step,reward,entropy= 134002 25347741 -64.82 -33.36
episode,step,reward,entropy= 134003 25348148 125.58 -32.08
episode,step,reward,entropy= 134004 25348868 686.83 -30.18
・・・あまり改善しないこともあり、この辺でおしまいにします。
2. HumanoidFlagrunHarder
少し並行させてGoogleホストで学習をさせて様子を見ていました。数日学習させたところ、転倒後に何とかもがいて起き上がろうとし始めました。12時間ごとにリプレイメモリがクリアされるので、今一つちゃんと学習できるか半信半疑でしたが学習進んでいるようです。
episode,step,reward,entropy= 30005 5358689 -622.11 -167.97
2月12日
学習が進んだら転んだ後、動かなくなっちゃいました。ダメかも。
episode,step,reward,entropy= 66003 12217441 -320.21 -176.93
gifが重いけどご容赦を。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーー
HumanoidFlagrunHarderの記事は、この(4)でおしまいです!
学習継続中なので、目立った進捗があればこのページを逐次更新します。
Colabortoryをローカルランタイムで使ってみる
2020年1月30日 改訂1
エラーが表示されjupyterがうまく実行されず、ipykernelをアップグレードした件について、最後に追記しました。
-------------------------------------
Google Colaboratoryは大変便利なのですが、90分と12時間の時間制限がたまに傷です。実はColaboratoryはGoogleが用意しているホストの他、ローカルのjupyterランタイムに接続して使うことができ、この場合は時間制限はありません(当たり前か)。
ちょっと試してみて、画像認識のように大量のデータを使うわけではなければ、GPUを持たないノートPCでも結構使い物になることがわかりましたので、使い方を紹介します。
1.注意事項
手っ取り早く環境構築するためWindows10ベースでjupyterローカルランタイムを構築しました。そのためGoogleのホスト(Linux)とは一部互換性がありません。例えば、以下のような違いがあります。
- Googleドライブをマウントできません。Googleドライブをマウントするライブラリgoogle.colabがLinuxでしか使えないライブラリを使っているためです。
- Open AI gymの拡張env、gym[Box2D]、gym[classic_control]等を使えません。Open AI gymの拡張envがWindows用に実装されていないためです(Open AI gymのWindows実装自体が実験的実装とされていて拡張部は実装されていません)。
- その他、Linux特有の処理は実行できません。Colaboratoryで実行している深層強化学習のソースも若干修正が必要になります。
2.インストール
(1)anaconda
pythonの実行環境anacondaをインストールします。jupyter、pythonの各種ライブラリも同時にインストールされます。pythonバージョン3.x用をインストールします。
ユーザ全員か、自分だけかを聞かれるので自分だけ(Just Me)としました。
(2)Pytorch
スタート-Anaconda-Anaconda Promptを起動します。Pytorhのホームぺージでインストール設定を選択するとインストールコマンドが表示されます。
例えばWindows、conda(Anacondaのこと)、GPU無しの場合は以下のコマンドをAnaconda Promptで実行します。
conda install pytorch torchvision cpuonly -c pytorch
(3)Open AI gym
以下のコマンドでインストールします。
pip install gym
(4)PyBullet
以下のコマンドでインストールします。
!pip install pybullet
以下のコマンドでインストールします。
pip install jupyter_http_over_ws
jupyter serverextension enable --py jupyter_http_over_ws
3.ローカルランタイム起動
- スタート-Anaconda-Anaconda Promptを起動します。
- 以下のコマンドでjupyterを起動します。googleからのアクセスを許可します。
jupyter notebook --NotebookApp.allow_origin='https://colab.research.google.com' --port=8888 --NotebookApp.port_retries=0
Prompt画面にColaboratoryからの接続用キーが表示されるのでコピーします。
コピーできない場合はPromptを右クリックしてプロパティーオプションー簡易編集モードにチェックをいれます。
4.Colaboratoryから接続する
Golaboratoryの接続-ローカルランタイムに接続
バックエンドURLに先ほどコピーした接続先をペーストして接続を押します。
5.Colaboratoryのソースリスト修正
深層強化学習のソースリストを例に修正箇所を記します。
(1)ファイルシステム
Googleドライブは使えないのでファイルの読み書きはローカルパソコン上に行います。カレントディレクトリは「ローカルディスク」-「ユーザ」-「ユーザ名」になります。
私はここにjupyterといったフォルダを作って使っています。この場合、ソースリスト中のファイル指定先を例えば次のように変更します。
'/content/drive/My Drive/model_Humanoid_SAC'
→ 'jupyter/model_Humanoid_SAC'
(2)ライブラリインストール
- apt-getは使えませんのでコメントアウトします。
- pip3をpipに変更します。pip3はpython3用pipの意味でpython2のpipと区別するためのものです。今回はpython3しかインストールされておらずpipがpython3にあてがわれていてpip3は使えません。
- 'gym[Box2D]'、'gym[classic_control]'は使えないのでコメントアウトします。
(3)Colaboratoryでの画像表示設定をコメントアウト
Linux用の命令でディスプレイドライバを持たないGoogleホスト用なのでコメントアウトします。Windowsではこれらの命令を実行しなくても画像データを生成してくれます。
#display = Display(visible=0, size=(1024, 768))
#display.start()
#import os
#os.environ["DISPLAY"] = ":" + str(display.display) + "." + str(display.screen)
これで時間を気にすることなく思う存分学習させることができます。
最近Colaboratoryの使い過ぎでGPUが割り当てられなくなっていたところですし、深層強化学習ならGPUなくてもたいして変わらないし。
6.追記(2020年1月30日)
jupyterにうまく接続できず、コンソールに次のようなエラー表示がされたことがあります。パソコンを再起動しても効果なし。
ValueError: signal only works in main thread
以下の通りipykernelをアップグレードしたら解消しました。
pip install --upgrade ipykernel
Humanoid flag runについて
ちなみにHumanoid flag run 全然報酬が上昇しません・・・。1日200万stepづつ学習は進んでるんですが・・・。いったい何日かかることやら。
では!
PyBullet-HumanoidFlagrunHarderBulletEnv-v0(3)
今回はSoft Actor-Critic(SAC)について備忘録ということで解説します。以下の論文は初期のSACをさらに改良したものです。
1.深層強化学習の理解に必要な事項の整理
まずは、各記号、考え方の整理など。
行動価値関数が最大になる行動を出力する方策関数を学習するのが目的です。
行動価値関数と方策関数をニューラルネットワークで表現します。
①実際に乱数で試行錯誤した経験(状態st、行動at、報酬r、次の状態st+1)から、Bellman方程式を使って行動価値関数Qを学習させます。この時にBellman方程式中で経験記録に無い値、すなわち次回の行動at+1が出てくるため、これはその時最新の方策関数を使って推定します。
②行動価値関数を更新したら、その時の経験を使ってQを最大化する方策関数を学習させます。
①と②を繰り返して行動価値関数Qと方策関数πのニューラルネットワークを学習させます。
2.Soft Actor Critic(SAC)
SACは初期の論文のもの(Open AIのSpining Upのやり方)と、後から発表された改良型のものがありますが、ここでは改良型について説明します。
- 報酬と方策のエントロピー(ばらつきの大きさ)の将来に渡る総合計を最大化する方策を求めます。エントロピーが加わるところが従来と異なるところです。より大きなエントロピーを持つ方策(方策がばらついても結果(報酬)が良い方策分布)を学習させます。不安定な極所的最適解が排除され、学習が安定して進むと思われます。
- 行動価値関数Qを報酬の将来総和と次回ステップ以降のエントロピー将来総和で定義すると、確率的なBellman方程式は次のように書けます。
学習時は0.5×(左辺-右辺)の2乗をLoss関数として定義し、Loss関数が最小になる様、ネットワークの重みを更新します。ソースリストの次の箇所で計算しています。なお後述するテクニックを使っています。
①右辺の計算 next_actions:as+1、next_logp_pis:t+1のエントロピー×ー1
#Qターゲット#Qターゲット next_mus,next_actions,next_logp_pis = policy_net(next_state_batch) next_q1s = q1_target_net(torch.cat([next_state_batch, next_actions], 1)) next_q2s = q2_target_net(torch.cat([next_state_batch, next_actions], 1)) next_qs = torch.min(next_q1s, next_q2s) q_targets = reward_batch + GAMMA * (1.0-done_batch) * (next_qs - alpha * next_logp_pis)
②Loss関数の計算
#Q1ネット #loss関数 # q1_loss = 0.5 * F.mse_loss(q1_net(torch.cat([state_batch, action_batch],1)),q_targets) 中身がわかるような記述に変更 q1_loss = 0.5 * torch.mean((q1_net(torch.cat([state_batch, action_batch],1)) - q_targets)**2) #ネットの学習 q1_optimizer.zero_grad() #誤差逆伝搬 q1_loss.backward(retain_graph=True) #重み更新 q1_optimizer.step()
- DDPGの改良型であるTD3で使われたテクニックclipped double-Qを使います。Qネットワークを2つ(Q1、Q2)それぞれ学習させて、小さい方を使うことでQの過大学習によるpolicyの破壊を予防します。
- Bellman方程式を使ってQを学習する際Qが不安定にならないように、右辺の計算に使うQは別に定義(ターゲット関数)したものを使います。Qのターゲット関数はQに遅れて追従するようにします。
tau = 0.005 #q1,2 targetネットのソフトアップデート #学習の収束を安定させるためゆっくり学習するtarget netを作りloss関数の計算に使う。 #学習後のネット重み×tau分を反映させ、ゆっくり追従させる。 for target_param, local_param in zip(q1_target_net.parameters(), q1_net.parameters()): target_param.data.copy_(tau*local_param.data + (1.0-tau)*target_param.data) for target_param, local_param in zip(q2_target_net.parameters(), q2_net.parameters()): target_param.data.copy_(tau*local_param.data + (1.0-tau)*target_param.data)
- 方策(policy)はSquashed Gaussian Policyを使います(tanhで-1~1の範囲に押しつぶしたGaussian Policy関数の意味です)。最初に通常のGaussian policiesを計算し、その後squashします。
ある状態stを入力値として、行動の平均 μ(st)、log分散 log σ(st)をニューラルネットワークで推定、平均にノイズを加算して行動を導きます。
エントロピーを次の式で計算した後、squashします。
- Qの学習結果をpolicyに反映します。Qは最初のエントロピーHを含んでいないので、Qにエントロピーを加えたものが最大になるようなpolicy πを学習させます。実際はLoss関数 Jπ =-1×(Qにエントロピーを加えたもの)と定義し、Loss関数が最小になるようにpolicy πを学習させて上記と同じことをしています。
#policyネットのloss関数 mus,actions,logp_pis = policy_net(state_batch) q1s = q1_net(torch.cat([state_batch,actions],1)) q2s = q2_net(torch.cat([state_batch,actions],1)) qs = torch.min(q1s, q2s) p_loss = torch.mean(alpha * logp_pis - qs) #policyネットの学習 p_optimizer.zero_grad() #誤差逆伝搬 p_loss.backward(retain_graph=True) #重み更新 p_optimizer.step()
- あらかじめエントロピーの目標値を決め、その値になるようにtemperature parameter α(alpha)を自動調整します。エントロピーの目標値は、以下の論文によると単純にactionの次元1つあたりー1として、-1×actionの要素数(次元)としたとのことです。
[1812.11103] Learning to Walk via Deep Reinforcement Learning
Loss関数Jαを次のように定義し、Jαが小さくなるようαを更新します。 ①Ht > H (目標エントロピーの方が大)
αを増やすとJαが減るのでαの更新でαが増加します。αが増加すると、Qの学習時にエントロピーの影響が大きくなり、エントロピーは増加します。
②Ht < H (目標エントロピーの方が小)
αを減らすとJαが減るのでαの更新でαが減少します。αが減少するとQにおけるエントロピーの影響が小さくなり、学習が進むとエントロピーは減少していきます。alpha_loss = -torch.mean(log_alpha*(target_entropy + logp_pis)) #log_alpha更新 alpha_optimizer.zero_grad() #誤差逆伝搬 alpha_loss.backward(retain_graph=True) #log_alpha更新 alpha_optimizer.step() #alpha更新 alpha = log_alpha.exp()
3.パラメータについて
パラメータについて気付いたことをまとめておきます。
- BATCH_SIZE = 256
学習に使う経験数。以外に影響が大きく、少ないと学習が安定せず進まなく(報酬が飽和)します。humanoidの場合、128では少なすぎでした。少ないとQが滑らかに更新されず、いびつになってしまうのではないかと想像しています。 - GAMMA = 0.99
報酬に対する割引率。具体的な影響については未経験。 - lr=3e-4(optim.Adam)
ニューラルネットワーク更新時の大きさの係数。大きいと更新後のネットワークがいびつになり、学習が不安定になりやすい。小さいと学習に時間がかかる。 - tau = 0.005
ターゲット関数の更新係数。この係数分だけ最新のネットワークの重みに変わる。変更したことがないので影響については何とも言えないが、大きすぎると学習が不安定になりやすいと考えられます。
以上、今回はSACについてまとめてみました。
つづく