
強化学習への道1
最近はシリアルリンク 2足歩行ロボットの製作を頑張っとります。
足踏み動作もしっかり足上げして安定してきたので、そろそろ歩行に移行しようかと思っていたところ。。。
足踏み動作も整ったし前後のバランス調整して歩行と思ったけど。。
強化学習ってのが気になりだしたぜ。
一旦そっち勉強してみようかな。 pic.twitter.com/hBfzsHW4Jk— HomeMadeGarbage (@H0meMadeGarbage) May 5, 2025
強化学習の衝撃をくらった!
一旦ロボット製作をおいといて強化学習について勉強しようと思います。
目次
衝撃の出会い
はっきりいって強化学習なんて使い物にならないと思っていました。
強化学習をうたって成果出してるところって大概が自社でロボット製作しているので ものすごくチューニングしまくってるんじゃないかと考えていたのです (実際そうかもしれないけど)。
↓強化学習をなめていた時の私
ご覧ください。
足を伸ばしてから縮めるタイミングを手動で調整したらこんなにも高度のあるジャンプが実現されました。これは強化学習よりも真心をこめた手動チューニングの方が優れていることを如実に証明しています。 https://t.co/E2kVNccnDd pic.twitter.com/HaVIaIjnVj
— HomeMadeGarbage (@H0meMadeGarbage) April 8, 2025
そんなノリでサーボモータを使用した自作のBDXロボット(Open_Duck_Mini )の歩行もうまくいかないだろとふんでいました。
(実際すり足でフラフラの動画しかみてなかったので)
歩行に成功した機体は現れたのだろうか?? https://t.co/rMYMvshbSt
— HomeMadeGarbage (@H0meMadeGarbage) May 5, 2025
したらこの動画が出てきたの
It’s getting pretty good for a very cheap robot ! pic.twitter.com/fBdlRM2ZjZ
— Antoine Pirrone (@antoinepirrone) May 5, 2025
すり足ではあるけど、明確に歩いてる!
サーボモータ STS3215 のスピードでIMUセンシングに追従しながらの動作は絶対無理だと思っていたので どうせ周期動作でタイミング調整してるだけだろとも思ったのですが、
そうではなく強化学習による関数で物理追従して歩行しているとのことでした。
It’s not a temporal sequence. The policy takes as input a vector of observations (containing sensor data and a last 3 actions) and outputs motor actions
— Antoine Pirrone (@antoinepirrone) May 5, 2025
衝撃でした。。。
IMUセンサ値と直近の動作を元に強化学習データで動作させることによって、スピードに劣るサーボでも歩行が実現できているのです。
センサ値を受けてからフィードバックして動作という従来の手法では絶対にできないことを強化学習がやってのけることを目の当たりにしたのです。
ありがたいことに強化学習による実機動作のSim2Realフローのまとめも教えていただきました。
コレをもとに俺も強化学習勉強してみるか。。。
以上が強化学習との出会いと衝撃そしてやってみようと思った経緯です。
Sim2Realの流れ
教えてもらったまとめを元に自分なりに調べたSim2Realの流れを現状の理解で記載します。
この強化学習への道が進むにしたがって解像度が上がるものと考えています。
Sim
学習のためにMuJoCoという物理演算シミュレータソフトを使用
MuJoCoで物理シミュレーションを行うためにMJCF(MuJoCo XMLフォーマット)でロボット筐体やアクチュエータの構造を記載する。
強化学習
MuJoCo環境をベースに所望の動作実現に向けて報酬付与による強化学習を実施
現状では明確にどう実施するのか不明
Real
強化学習で得た学習関数をワンボードPCもしくはマイコンに実装して、Sim同様にセンサ値などを入力して所望の動作をさせる。
現状では学習関数のデータ形式や使用するチップは明確になっていない。
MuJoCo
強化学習の第一歩としてまずは物理シミュレータのMuJoCoと仲良くなることにしました。
とりあえずコチラでアプリをダウンロード
https://github.com/google-deepmind/mujoco/releases
ヒューマノイドのMJCFデータも以下で入手して表示させてみた
https://github.com/google-deepmind/mujoco/tree/main/model/humanoid
モデル以外何も指定してないから立てないけど
立ってられへん pic.twitter.com/9vKa5pf1nt
— HomeMadeGarbage (@H0meMadeGarbage) May 6, 2025
アプリの使い方は以下の動画を参考に勉強した
MJCFでは以下のようにSTLファイルがそのまま導入できました。
これは便利!
1 2 3 |
<asset> <mesh name="mymesh" file="model.stl" /> </asset> |
STLファイルはメートル単位で出力する必要があります。
2足歩行ロボットのSTL取り込んでみた。
今のところ全然面白くない#強化学習への道 pic.twitter.com/i1rLxxX4Fl
— HomeMadeGarbage (@H0meMadeGarbage) May 7, 2025
Python版 MuJoCo
ダウンロードしたexeアプリはMJCF形式のデータを表示させたりマウスで動かしたりできますが、プログラム制御などはできません。
Python版MuJoCo パッケージは pip install mujocoで導入します。
導入やPythonコードはChatGPTに教えてもらいながら実施しました。
やっとMuJoCoでキューブ表示できた。。。
こっから筐体モデル作ってモータもモデル化して
強化学習して学習モデルをなにかしらのチップに移行してSim2Realかぁ今のところできる気がしない#強化学習への道 pic.twitter.com/TPqbwcpNmu
— HomeMadeGarbage (@H0meMadeGarbage) May 6, 2025
MuJoCoで倒立振子
MuJoCoの練習のためにまずは倒立振子をやってみました。
まずはMJCFで倒立振子を記述 (ほとんどChatGPTにやってもらった)
棒や軸を記述してヒンジでジョイント
倒立振子#強化学習への道 pic.twitter.com/7HF41minPu
— HomeMadeGarbage (@H0meMadeGarbage) May 7, 2025
次にPythonでジョイント部にモータを仕込んでPD制御して倒立させます。
パラメータは手動で調整しました。
長さ80cmの棒
PD制御 手動パラメータ調整で見事倒立 pic.twitter.com/7ZBgVWbuBc— HomeMadeGarbage (@H0meMadeGarbage) May 7, 2025
コードは以下のとおり
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import mujoco import mujoco.viewer import numpy as np import time # モデル定義(viewerなし) model = mujoco.MjModel.from_xml_string(""" <mujoco> <option timestep="0.01" gravity="0 0 -9.81"/> <worldbody> <body name="origin" pos="0 0 0"> <!-- Y軸(緑) --> <geom name="y_axis" type="capsule" fromto="0 0 0 0 -0.5 0" size="0.01" rgba="0 1 0 1"/> </body> <body name="base" pos="0 0 0"> <geom type="box" size="0.2 0.2 0.02" rgba="0.6 0.6 0.6 1"/> <body name="pendulum" pos="0 0 0.02"> <joint name="hinge" type="hinge" axis="0 1 0" range="-90 90" limited="true" damping="1.0"/> <inertial pos="0 0 0.4" mass="1.0" diaginertia="0.1 0.1 0.1"/> <geom type="capsule" fromto="0 0 0 0 0 0.8" size="0.02" rgba="0.2 0.6 0.8 1"/> </body> </body> </worldbody> <actuator> <motor joint="hinge" ctrlrange="-10 10" gear="1"/> </actuator> </mujoco> """) data = mujoco.MjData(model) data.qpos[0] = np.deg2rad(20) # 初期角度20度 # ゲイン Kp = 13.0 Kd = 5.0 # Viewerつきループ with mujoco.viewer.launch_passive(model, data) as viewer: while viewer.is_running(): theta = data.qpos[0] omega = data.qvel[0] torque = -Kp * theta - Kd * omega disturbance = np.random.normal(loc=0.0, scale=1.0) data.ctrl[0] = torque + disturbance mujoco.mj_step(model, data) viewer.sync() # ステップ後の状態をviewerに反映 time.sleep(model.opt.timestep) # 描画ループ安定化 |
安定倒立させないためにdisturbanceとして乱数で外乱を付与しています。
リアクションホイール
MuJoCoの記載方法やシミュレーション方法もだいぶ見えてきましたので、いよいよ強化学習でSim2Realとしけこみたいのですが
いきなりロボットは厳しいと考えました。
そこでまずはブラシレスモータによる1軸のリアクションホイール姿勢制御モジュールを強化学習の題材とします。
モータは冒頭の2足歩行ロボットにも用いた5010 360KV ブラシレスモータの使用を想定。
この姿勢制御モジュールでSim2Realできるようになれば、2足歩行ロボットへの応用に耐えうる知識と良いモータモデルが手に入ってることを期待しています。
モデル作成
Fusion360でモジュールを設計し、筐体やモータのSTLファイルを生成
STLを取り込んでモジュールのMJCFを記述してMuJoCoアプリで表示させてみた。
いきなりロボットは絶対無理なのでSHISEIGYOでSim2Realを目指す。
まずはSimで実機のようにパラメータ調整しての倒立を目指す。
コレでSim2Realできるころにはロボットに耐えうる知識とモータモデルが手に入ってることでしょう#強化学習への道 pic.twitter.com/4lHJeOQiL4
— HomeMadeGarbage (@H0meMadeGarbage) May 7, 2025
筐体やモータの記述がうまくいっていることを確認できました。
モジュールの筐体やモータの質量や重心位置、慣性モーメントを記載することでより精度の高いシミュレーションが可能になるということでFusion360でマテリアル指定してデータを入手しました。
Fusion360で物性データかなり詳しく出るんだね
慣モメまでマテリアルまじめにやればかなりいいモデルになるね
充填率とかは係数で処理かな pic.twitter.com/iZ8npIwN8Q— HomeMadeGarbage (@H0meMadeGarbage) May 7, 2025
物理定数を追記することで動きが更にリアルになりました。
Fusion360とMuJoCoで単位が違うので変換して記載 (ChatGPTに一括変換してもらった)
筐体やモータの慣モメ明記したら現実的な動きになった
あとはブラシレスモータの電気的モデル化の勘所を掴みたい
何となくmotorエレメントそのままでイケる気もするが
実機でバックアノかけつつかな#強化学習への道 pic.twitter.com/puIOUgnViQ— HomeMadeGarbage (@H0meMadeGarbage) May 8, 2025
手動倒立
モデルが生成できたので、実機と同様にフィードバックゲインを手動で調整して倒立させます。
フィードバックの式は以下のとおり
モータトルク = Kp * 機体角度 + Kd * 機体角速度 + Kw * モータ回転速度
パラメータ調整することで倒立できることを確認できました!
リアクションホイール
手動でパラメータ調整して倒立実現
もう実機と同じ感覚やね 職人だから俺さていよいよ強化学習すっか#MuJoCo #強化学習への道 pic.twitter.com/U2s99RkPdU
— HomeMadeGarbage (@H0meMadeGarbage) May 8, 2025
Pythonコードは以下のとおり
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
import mujoco import mujoco.viewer import time import numpy as np # モデル定義(XML文字列) model = mujoco.MjModel.from_xml_string(""" <mujoco> <option timestep="0.01"/> <asset> <mesh name="case" file="case.stl"/> <mesh name="motor" file="motor.stl"/> <mesh name="wheel" file="wheel.stl"/> </asset> <worldbody> <!-- 照明:上から照らす --> <light name="light1" pos="0 0 3" dir="0 0 -1" directional="true" castshadow="false"/> <body name="case" pos="0 0 0.0"> <joint type="free"/> <inertial pos="0.000 0.027478 0.038948" mass="0.049217" diaginertia="3.084924e-5 4.0411796e-5 2.5326781e-5"/> <geom type="mesh" mesh="case"rgba="0.4 0.4 0.4 1"/> <!-- モーター(親) --> <body name="motor" pos="0 0 0"> <inertial mass="0.032212" diaginertia="3.289826e-6 5.548697e-6 3.290785e-6" pos="0 0.009627 0.034116"/> <geom type="mesh" mesh="motor" rgba="0.8 0.8 0.85 1"/> <!-- ホイール(回転する子) --> <body name="wheel" pos="0 0 0"> <!-- Z軸まわりに回転(0 0 1) --> <joint name="wheel_hinge" type="hinge" axis="0 1 0" pos="0 0 0.034116" damping="0.00005"/> <inertial mass="0.036643" diaginertia="7.825643e-6 1.4733674e-5 7.825642e-6" pos="0 0.004659 0.034116"/> <geom type="mesh" mesh="wheel" rgba="0.8 0.8 0.85 1"/> </body> </body> </body> <!-- 地面 --> <geom type="plane" size="20 20 0.1" pos="0 0 0" rgba="0.0 0.5 0.0 1"/> </worldbody> <actuator> <motor joint="wheel_hinge" ctrlrange="-1.0 1.0" gear="0.0265"/> </actuator> </mujoco> """) # ←←← これが必須やぞ!!データを生成しないと動かん data = mujoco.MjData(model) Kp = 100 Kd = 10 Kw = 0.2 def quat_to_pitch(q): w, x, y, z = q sinp = 2.0 * (w * y - z * x) if abs(sinp) >= 1: pitch = np.sign(sinp) * np.pi / 2 else: pitch = np.arcsin(sinp) return pitch with mujoco.viewer.launch_passive(model, data) as viewer: while viewer.is_running(): quat = data.qpos[3:7] theta_y = quat_to_pitch(quat) omega_y = data.qvel[4] joint_id = mujoco.mj_name2id(model, mujoco.mjtObj.mjOBJ_JOINT, "wheel_hinge") qvel_index = model.jnt_dofadr[joint_id] # ← これが qvel[] に対応するindex wheel_speed = data.qvel[qvel_index] torque = Kp * theta_y + Kd * omega_y + Kw * wheel_speed disturbance = np.random.normal(loc=0.0, scale=0.5) data.ctrl[0] = torque + disturbance mujoco.mj_step(model, data) viewer.sync() time.sleep(model.opt.timestep) |
STLファイルを読み込んでモジュールを組むように記述
さらに物理定数も記載
ブラシレスモータはMuJoCoのmotor エレメントでモデル化しています。
トルク比のgearやジョイント部の摩擦係数dampingの調整で実機を模擬しています。
モータのモデル化は今後精査が必要と考えとります。
おわりに
自分には一切関係ないと思っていた強化学習と衝撃的な出会いがあり、このたび向き合うこととなり本道を開設する運びとなりました。
また明確でないことも多いですが、一歩一歩着実に勉強したいと思います。
自分がどうすれば理解して応用まで持って行けるかは おじさんなので良く分かっております。
とりあえずリアクションホイールについてMoJuCoで実機のように古典制御のパラメータ手動調整で倒立させるところまではきました。
次回は強化学習に着手しつつSimモデルの精度向上を目指します。
人は変われる
ねぇそうでしょ?