はじめに
PyBullet (Python上で動く物理シミュレータ) を使用して,ロボットアームを可視化して,動かしたい.
前記事では,エージェントの学習方法として "Q学習 (off-policy TD学習)" を採用して,PyBullet 内の2軸ロボットアームの強化学習を実装した.
前記事の問題点は,Q学習が "Q-tableで行動価値関数を管理する" ため,状態数が増えるにつれてメモリが線形増加する点である.本記事で実装している2軸ロボットアームでは状態数が $1,369$ ($37 \times 37$) と管理可能な範囲であるが,6軸ロボットアームで同様の離散化を行うと $37^6 \approx 2.57 \times 10^9$ 状態となり,メモリと学習時間が爆発的に増大する.
本記事では,エージェントの学習方法として "DQN (Deep Q-Network)" を採用する.DQNでは,Q-tableの代わりに "ニューラルネットワーク" でQ関数を近似するため,状態空間が大きくなってもパラメータ数はネットワークの重みの数で固定できる.
グリッパー付きの2軸ロボットアームを使用して,干渉物が存在しない環境下にて,複数のカメラから把持物体の位置を取得して,物体を把持する(下図はPyBullet上のグリッパー付き2軸ロボットアームである).赤枠がグリッパー,緑枠が把持したい物体,青枠がカメラ,青色の球が初期位置である.

本記事を実施してできる最終的なものは下記動画の通りである.エージェントの学習方法は "DQN" である.
下記は,学習中の強化学習による経路生成の動画である.
下記は,学習後の強化学習による経路生成の動画である.
本記事で実装すること
- エージェントの学習方法は"DQN"
本記事では実装できないこと (将来実装したい内容)
エージェントの学習手法
- Actor-Critic
- PPO (Proximal Policy Optimization)
動作環境
- macOS Sequoia (バージョン15.5)
- Python3 (3.13.3)
- PyBullet (3.25) (物理シミュレータ)
- Numpy (2.3.0) (数値計算用ライブラリ)
- PyTorch (2.7.0) (深層学習用ライブラリ)
PyBullet のインストール方法
PyBullet のインストール方法については下記にて,説明したため,割愛する.
PyBulletの使用方法
Pythonの物理シミュレータであるPyBulletの使用方法について説明する.
下記リンクのPyBullet公式で調べながら,PyBulletを使用している.
上記リンクを開くと,下図のようなサイトに飛ぶ.使用方法を調べるときは,下図の赤枠内の「PYBULLET QUICKSTART GUIDE」タグをクリックする.

「PYBULLET QUICKSTART GUIDE」タグをクリックすると,下図のようなドキュメントを見ることができる.基本的には,ドキュメントに記載されている関数の使用方法を見て,ソースコードを作成している.

PyBulletの関数の引数や戻り値をもっと知りたいのでしたら,上記ドキュメントを見た方がわかりやすいです.
2軸ロボットアームの定義
2軸ロボットアームの定義については下記にて,説明したため,割愛する.
グリッパーの定義
グリッパーの定義については下記にて,説明したため,割愛する.
グリッパー付き2軸ロボットアームのURDF作成
グリッパー付き2軸ロボットアームのURDF作成については下記にて,説明したため,割愛する.
URDFに関しては,下記にて説明したため,割愛する.
カメラによる把持物体の2次元位置を取得
PyBulletでカメラ情報を取得する方法については下記にて,説明したため,割愛する.
強化学習に関して
強化学習は機械学習の一つの種類である.強化学習とは,自分で試行錯誤をしてデータを集め,詰めたデータから良い行動を学習することである.
強化学習のイメージは下図の通りである.
1:「環境」から「エージェント」に対して,「状態」を渡す
2:「エージェント」が「状態」より,「行動」を選択する
3:「エージェント」から「環境」に対して,「行動」を実行する
4:「環境」が「行動」より,「状態」を更新する
5:「環境」から「エージェント」に対して,「報酬」を渡す
6:「エージェント」が「報酬」より,「学習」する
ここで,用語の定義をする.
| 用語 | 用語の定義 |
|---|---|
| エージェント | 行動の主体者 |
| 環境 | エージェントと相互作用を行う対象 |
| 状態 | エージェントの行動によって置かれるエージェントの状況 |
| 行動 | エージェントが起こすアクション |
| 報酬 | エージェントの行動に対する環境からのフィードバック |
本記事での "環境" は "PuBullet上のロボットと環境" である."エージェント" は "ロボットを動かすための頭脳" である.
以下では,本記事で使用する "環境" と "エージェント" を説明していく.
強化学習の環境に関して
強化学習の環境に関しては下記にて,説明したため本記事では割愛する.
強化学習のエージェントに関して
強化学習のエージェントに関して説明する.強化学習の全体像の中で,下図の赤枠で囲んだ「エージェント」を説明する.
エージェントは,環境から「状態」を受け取り,「行動」を行い,「報酬」を得て,次の「状態」へ遷移する.下記のように1エピソードが完了するまで,「状態(S)」,「行動(A)」,「報酬(R)」,「状態(S)」のループとなる.
$S_{0}, A_{0}, R_{0}, S_{1}, A_{1}, R_{1}, ...$
行動価値関数に関して
行動価値関数については下記にて,説明したため本記事では割愛する.
DQN (Deep Q-Network) に関して
エージェントの学習方法の一種である "DQN" に関して説明する.DQNは2013年にDeepMindが発表した手法 (Mnih et al., 2015) であり,Q学習のQ-tableをニューラルネットワークで置き換えたものである.
Q学習とDQNの違いを下表にまとめた.
| 比較項目 | Q学習 | DQN |
|---|---|---|
| Q関数の表現 | テーブル (辞書型) | ニューラルネットワーク |
| パラメータ数 | 状態数 × 行動数 | ネットワークの重み数 (固定) |
| 状態空間 | 離散値のみ | 連続値・離散値どちらも対応 |
| 経験の再利用 | なし (都度学習) | 経験再生バッファ |
| 学習安定化 | なし | ターゲットネットワーク |
| 収束速度 | 状態数が少なければ速い | 状態数が多くても対応可能 |
ニューラルネットワークによるQ関数の近似
DQNでは,Q関数をパラメータ $\theta$ を持つニューラルネットワーク $Q(s, a; \theta)$ で近似する.ニューラルネットワークの入力は "状態 $s$",出力は "各行動のQ値" である.
\displaylines{
Q(s, a; \theta) \approx Q^*(s, a) \\
\\
\theta ... \text{ニューラルネットワークの重みパラメータ} \\
Q^*(s, a) ... \text{最適行動価値関数}
}
本記事では,入力が2次元(関節1・関節2の状態インデックスを $[0, 1]$ に正規化),隠れ層が2層,出力が9次元(行動数)の全結合ニューラルネットワークを使用する.ネットワークの構造を下表にまとめた.
| 層 | 種類 | ユニット数 | 活性化関数 |
|---|---|---|---|
| 入力層 | 全結合 | 2 (状態次元数) | - |
| 隠れ層1 | 全結合 | 64 | ReLU |
| 隠れ層2 | 全結合 | 64 | ReLU |
| 出力層 | 全結合 | 9 (行動数) | - |
経験再生 (Experience Replay) に関して
DQNでは,"経験再生バッファ" と呼ばれるメモリに過去の経験 $(s_t, a_t, r_t, s_{t+1}, done_t)$ を蓄積し,学習時にミニバッチをランダムサンプリングする.
経験再生が必要な理由は,Q学習のように "直前の経験で即時更新する" と,連続したステップの経験が相関しているため,ネットワークが不安定に振動するからである.ランダムサンプリングにより相関を壊すことで,学習が安定する.
経験再生バッファの役割を下表にまとめた.
| 役割 | 説明 |
|---|---|
| 経験の蓄積 | $(s_t, a_t, r_t, s_{t+1}, done_t)$ をバッファに保存 |
| 相関の除去 | ランダムサンプリングで連続経験の相関を断ち切る |
| 経験の再利用 | 同じ経験を複数回の学習に使い回す (サンプル効率向上) |
ターゲットネットワーク (Target Network) に関して
DQNでは,Q値の更新目標 (TD目標値) の計算に "ターゲットネットワーク ($\theta^-$)" を使用する.ターゲットネットワークは一定ステップごとにメインネットワーク ($\theta$) からコピーされ,それ以外の期間は固定される.
ターゲットネットワークが必要な理由は,メインネットワーク ($\theta$) だけで更新すると,更新対象と更新目標が同じネットワークになるため,"移動する目標を追いかける" 問題 (Moving Target Problem) が発生するからである.ターゲットネットワークを固定することで,TD目標値が安定し,学習が収束しやすくなる.
| ネットワーク | パラメータ | 更新タイミング |
|---|---|---|
| メインネットワーク | $\theta$ | 毎ステップ (勾配降下法) |
| ターゲットネットワーク | $\theta^-$ | $N$ ステップごとに $\theta^- \leftarrow \theta$ |
DQNの損失関数と更新式
DQNの損失関数は以下の通りである.
\displaylines{
L(\theta) = \mathbb{E} \left[ \left( \underbrace{r_t + \gamma \max_{a'} Q(s_{t+1}, a'; \theta^-)}_{\text{TD目標値 (ターゲットネットワーク使用)}} - \underbrace{Q(s_t, a_t; \theta)}_{\text{メインネットワークの出力}} \right)^2 \right] \\
\\
\text{ただし,エピソード終了時 (}done = True\text{) は} \\
r_t + \gamma \max_{a'} Q(s_{t+1}, a'; \theta^-) \rightarrow r_t \quad \text{(次状態が存在しないため)}
}
損失 $L(\theta)$ を最小化するように,Adam オプティマイザで $\theta$ を更新する.ターゲット値の計算には $\theta^-$(固定されたターゲットネットワーク)を使い,メインネットワーク $\theta$ は更新しない (勾配を切る torch.no_grad()).
DQNの学習フロー
DQNの学習フローを以下に示す.
1:初期状態 $s_0$ を取得する
2:ε-greedy方策で行動 $a_t$ を選択する
3:行動 $a_t$ を実行して,報酬 $r_t$・次の状態 $s_{t+1}$・終了フラグ $done$ を得る
4:経験 $(s_t, a_t, r_t, s_{t+1}, done)$ を経験再生バッファに追加する
5:バッファに十分な経験が溜まったら,ミニバッチをランダムサンプリングして $L(\theta)$ を最小化するように $\theta$ を更新する
6:$N$ ステップごとに $\theta^- \leftarrow \theta$ (ターゲットネットワーク更新)
7:$s_t \leftarrow s_{t+1}$ として2に戻る
DQNはQ学習と同じ "off-policy" の学習手法であるため,学習フローはQ学習と類似している.主な違いは "経験をバッファに貯めてからミニバッチで更新する" 点と "ターゲットネットワークを使ってTD目標値を計算する" 点である.
行動の選択方法 (ε-greedy法)
行動の選択方法 (ε-greedy法) に関しては下記にて,説明したため本記事では割愛する.
ソースコード一覧
本記事で使用する全ソースコードの一覧を下表にまとめる.フォルダ名が記載されているものは,そのフォルダを作成して,フォルダ内にファイルを保存してください.
URDFファイル
| ファイル名 | 概要 |
|---|---|
camera_back_to_front.urdf |
手前方向(-Y方向)を向いているカメラの定義 |
camera_front_to_back.urdf |
奥行き方向(+Y方向)を向いているカメラの定義 |
camera_left_to_right.urdf |
右方向(+X方向)を向いているカメラの定義 |
camera_right_to_left.urdf |
左方向(-X方向)を向いているカメラの定義 |
camera_up_to_down.urdf |
下方向(-Z方向)を向いているカメラの定義 |
grasp_object.urdf |
把持物体の定義 |
robot_2dof.urdf |
グリッパーなし2軸ロボットアームの定義 |
environment_2dof.urdf |
2軸ロボットアームの環境を定義 |
robot_2dof_hand.urdf |
グリッパー付き2軸ロボットアームの定義 |
Pythonファイル
| ファイル名 | 概要 |
|---|---|
constant.py |
定数の定義 (DQN追加) |
main.py |
全体的なメイン処理 |
pybullet_grasp.py |
PyBulletの把持物体 |
pybullet_gripper.py |
PyBulletのグリッパー |
pybullet_robot.py |
PyBulletのロボット |
pybullet_camera.py |
PyBulletのカメラ |
pybullet_agent_controller.py |
PyBulletのエージェント制御 (DQN対応・done引数追加) |
BaseAgent.py |
PyBulletのエージェントの抽象クラス |
RandomAgent.py |
PyBulletのランダムに動くエージェント |
MonteCarloAgent.py |
PyBulletのモンテカルロ法で学習するエージェント |
SarsaAgent.py |
PyBulletのSARSAで学習するエージェント |
QLearningAgent.py |
PyBulletのQ学習で学習するエージェント |
DQNAgent.py |
PyBulletのDQNで学習するエージェント |
pybullet_environment_controller.py |
PyBulletの強化学習用の環境制御 |
BaseEnvironment.py |
PyBulletの強化学習用の環境の抽象クラス |
DiscretizeEnvironment.py |
PyBulletの強化学習用の離散値環境 |
pybullet_main.py |
PyBulletのメイン処理 (DQNループ) |
pybullet_main.py の変更点に関して
pybullet_main.py の __learning() メソッドをDQN用に変更した点について説明する.
Q学習とDQNの学習ループの違いを下表にまとめた.
| 比較項目 | Q学習 (part4) | DQN (part5) |
|---|---|---|
| 最初の行動選択 | whileループ内で選択 | whileループ内で選択 (同じ) |
add() の呼び出し |
add(s, a, r, s') |
add(s, a, r, s', done) |
| Q値の更新タイミング |
add() で即時更新 |
add() 内で経験バッファに追加 → ミニバッチ学習 |
update() の役割 |
ε更新のみ | ε更新のみ (同じ) |
DQNで done フラグを渡す理由は,エピソード終了時 ($done = True$) にTD目標値の計算から次状態の項を除外するためである.$done = True$ のとき,次状態は存在しないため $r + \gamma \max_{a'} Q(s', a'; \theta^-)$ の代わりに $r$ を使う.
DQNの __learning() の主要部分を抜粋すると以下の通りとなる (Q学習との差分は done フラグのみ).
while not done:
# 行動の選択 (ε-greedy方策)
action = self.__agent.get_action(state)
# 1ステップ実行: (s, a) → (s', r, done)
next_state, reward, done = self.__env.step(action)
# 経験バッファに追加 + ミニバッチ学習 (done フラグを渡す点がQ学習と異なる)
self.__agent.add(state, action, reward, next_state, done)
# 状態の更新 (s ← s')
state = next_state
# エピソード終了: 探索確率 ε の更新
self.__agent.update()
DQNAgent.py の実装に関して
DQNAgent.py では,Q学習 (QLearningAgent.py) と比較して,以下の点が異なる.
ニューラルネットワーク (_QNetwork)
Q-tableの代わりに,PyTorchで実装した2層MLPでQ関数を近似する.入力として渡す状態インデックスは $[0, 1]$ に正規化する (インデックス / (状態数 - 1)).
class _QNetwork(nn.Module):
def __init__(self, state_dim, n_action, hidden_dim=64):
super().__init__()
self.layers = nn.Sequential(
nn.Linear(state_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, n_action)
)
def forward(self, x):
return self.layers(x)
add() メソッドの役割変更
Q学習では add() で即時Q値更新を行っていたが,DQNでは以下の2つの処理を行う.
- 経験 $(s, a, r, s', done)$ を経験再生バッファに追加
- バッファに
_MIN_REPLAY_SIZE件以上溜まったら,ミニバッチをサンプリングしてネットワークを更新
ターゲットネットワークの更新
_TARGET_UPDATE_INTERVAL ステップごとにターゲットネットワークの重みをメインネットワークからコピーする.
パラメータファイル
DQNは dqn_param.pth (PyTorchのモデルファイル形式) にメインネットワークの重みを保存する.
| パラメータ | 値 |
|---|---|
| 割引率 γ | 0.9 |
| オプティマイザ | Adam |
| 学習率 (Adam) | 0.001 |
| 隠れ層のユニット数 | 64 |
| 経験バッファサイズ | 10,000 |
| ミニバッチサイズ | 64 |
| 学習開始最小経験数 | 500 |
| ターゲット更新間隔 | 100ステップ |
| 探索確率の最小値 ε_min | 0.1 |
| 探索確率の最大値 ε_max | 0.9 |
| 探索確率の減衰率 | 0.9999 |
PyBulletのメイン処理 (pybullet_main.py)
p.connect() 関数の引数である p.DIRECT と p.GUI に関しては下記にて,説明したため本記事では割愛する.
PyBulletでロボットを動かす
上記にて,ソースコードを説明した.
main.py ファイルを実施することによって,PyBullet上のロボットを動かしていく.
経路生成の動画
DQNに動くエージェントによる経路生成の動画を載せる.
動画1:学習時の経路生成の動画
動画2:学習完了後の経路生成の動画
DQNはQ学習と比べてニューラルネットワークで状態間の類似性を汎化できるため,より効率的に良い方策を学習することが期待される.また,状態空間が大きくなっても対応できるため,将来的に6軸ロボットアームへの適用も見据えた手法となっている.
おわりに
本記事では,Pythonを使用して,下記内容を実装しました
- エージェントの学習方法としてDQNを採用して,PyBullet 内の2軸ロボットアームの強化学習
次記事では,下記内容を実装していきます.
- エージェントの学習方法としてActor-Critic (方策勾配法) を採用して,PyBullet 内の2軸ロボットアームの強化学習
参考文献
本記事を作成する上で参考にした内容を記載する.
参考記事
参考論文
- Mnih, V. et al. (2015). Human-level control through deep reinforcement learning. Nature, 518, 529–533.
参考書籍
- ゼロから作るDeep Learning ❹ ―強化学習編



