Courses

強化学習(RL)は、エージェントが環境との相互作用を通じて、目標達成のための最適な戦略を学ぶ機械学習の一分野です。データを取り込んで処理する必要がある教師あり学習アルゴリズムとは大きく異なります。強化学習はデータを必要とせず、代わりに環境と報酬システムから学習して、より良い意思決定を行います。
たとえば、マリオのビデオゲームでは、キャラクターがランダムな行動(例:左に動く)を取ると、その行動に基づいて報酬を受け取ることがあります。行動後、エージェント(マリオ)は新しい状態になり、このプロセスはキャラクターがステージの最後に到達するか、やられてしまうまで繰り返されます。
このエピソードは、マリオが報酬を最大化することで環境をうまく進めるようになるまで、何度も繰り返されます。

画像:著者作成
強化学習は次の5つの簡単な手順に分解できます。
- エージェントは環境内の初期状態にいる。
- 特定の戦略に基づいて行動を取る。
- その行動に基づいて報酬または罰を受け取る。
- 過去の手から学習し、戦略を最適化する。
- 最適な戦略が見つかるまで、このプロセスを繰り返す。
詳しくは、強化学習入門チュートリアルをご覧ください。コード例を通して、強化学習の仕組みをさらに探ります。
このチュートリアルでは、Q学習について学び、なぜDeep Q学習が必要なのかを理解します。さらに、NumpyとGymnasiumを使って、Q学習アルゴリズムをゼロから作成・学習させる方法を学びます。
注:機械学習が初めての方は、強化学習とQ学習の理解を深めるために、Machine Learning Scientist with Pythonキャリアトラックの受講をおすすめします。
Q学習とは?
Q学習は、エージェントの現在の状態に基づいて最適な一連の行動を見つける、モデルフリー・価値ベース・オフポリシーのアルゴリズムです。「Q」はquality(品質)の略で、将来の報酬を最大化するうえで、その行動がどれだけ価値があるかを表します。
モデルベースのアルゴリズムは、遷移関数と報酬関数を用いて最適方策を推定し、モデルを構築します。対照的に、モデルフリーのアルゴリズムは、遷移関数や報酬関数を用いず、経験を通じて自らの行動の結果を学習します。
価値ベースの手法は価値関数を学習し、どの状態がより価値が高いかを判断して行動を選びます。一方、方策ベースの手法は方策そのものを直接学習し、与えられた状態でどの行動を取るべきかを学びます。
オフポリシーでは、ある方策とは異なる方策に基づく行動を評価・更新します。反対に、オンポリシーアルゴリズムは、行動に用いるのと同じ方策を評価・改善します。
Q学習の主要用語
Q学習の仕組みに入る前に、基礎を理解するためにいくつかの用語を確認しましょう。
- 状態(s):環境内でのエージェントの現在位置。
- 行動(a):特定の状態でエージェントが取る一手。
- 報酬:各行動に対してエージェントが受け取る報酬や罰。
- エピソード:新たな行動が取れない段階の終わり。目標を達成するか失敗したときに起こる。
- Q(St+1, a):特定の状態で行動を取ったときの、最適なQ値の期待値。
- Q(St, At):Q(St+1, a) の現在の推定値。
- Qテーブル:状態と行動の組に対するQ値を保持する表。
- 時間差分(TD):現在と直前の状態・行動を用いて、Q(St+1, a) の期待値を推定するために用いる手法。
Q学習はどのように機能するのか?
凍った湖の例を使って、Q学習の動作を詳しく学びます。この環境では、エージェントは穴に落ちずにスタートからゴールまで凍った湖を渡る必要があります。最良の戦略は、最短経路でゴールに到達することです。

Gif:著者作成
Qテーブル
エージェントは、環境内の各状態に対する期待報酬に基づいて、可能な限り最適な行動を取るためにQテーブルを用います。簡単に言えば、Qテーブルは行動と状態の集合に対するデータ構造で、Q学習アルゴリズムを使ってこの表の値を更新します。
Q関数
Q関数はベルマン方程式を用い、状態(s)と行動(a)を入力として受け取ります。この方程式により、状態価値や状態–行動価値の計算が簡略化されます。
画像出典:freecodecamp.org
Q学習アルゴリズム

画像:著者作成
Qテーブルの初期化
まずQテーブルを初期化します。列は行動数に、行は状態数に基づいて表を作成します。
この例では、キャラクターは上・下・左・右に移動できます。可能な行動は4つで、状態は4つ(開始、待機、誤った経路、終了)です。誤った経路は穴に落ちることとみなしても構いません。Qテーブルは0で初期化します。

画像:著者作成
行動の選択
2番目のステップはシンプルです。開始時はエージェントはランダムに行動(下または右)を選び、2回目以降は更新されたQテーブルを使って行動を選択します。
行動の実行
行動の選択と実行は、学習ループが止まるまで何度も繰り返されます。最初の行動と状態はQテーブルを使って選ばれます。この時点ではQテーブルの全ての値は0です。
その後、エージェントは下に移動し、ベルマン方程式を使ってQテーブルを更新します。各手ごとにQテーブルの値を更新し、同時に最良の行動を決めるためにも使用します。
初期段階では、エージェントは探索モードで環境を探索するためにランダムな行動を選びます。イプシロン・グリーディ戦略は、探索と活用のバランスを取る簡単な方法です。イプシロンは探索を選ぶ確率を表し、探索する可能性が小さくなると活用が増えます。
開始時はイプシロン率が高く、エージェントは探索モードです。環境を探索するにつれてイプシロンは減少し、エージェントは環境を活用し始めます。探索の過程で、反復を重ねるごとに、エージェントはQ値の推定に自信を深めます。

画像:著者作成
凍った湖の例では、エージェントは環境を知らないため、開始時にランダムな行動(下に移動)を取ります。上の画像のように、Qテーブルはベルマン方程式で更新されます。
報酬の測定
行動を取った後、結果と報酬を測定します。
- ゴールに到達:+1
- 誤った経路(穴に落ちる):0
- 待機または凍った湖上の移動:0
Qテーブルの更新
式を使ってQ(St, At)を更新します。これは前のエピソードで推定されたQ値、学習率、時間差分誤差を利用します。時間差分誤差は、即時報酬、割引された将来報酬の最大期待値、そして以前の推定Q値を用いて計算されます。
このプロセスは、Qテーブルが更新され、Q値関数が最大化されるまで何度も繰り返されます。

画像:著者作成|Equation Visuals: Thomas Simonini
開始時、エージェントは環境を探索してQテーブルを更新します。Qテーブルが整うと、エージェントは活用を始め、より良い意思決定を行います。
画像:著者作成
凍った湖の場合、エージェントはゴールに到達するまでの最短経路を学び、穴に落ちるのを避けるようになります。
Q学習 Python チュートリアル
このセクションでは、Gymnasium環境、Pygame、Numpyを使って、Q学習モデルをゼロから構築します。このPythonチュートリアルは、Thomas SimoniniによるNotebookの改変版です。環境とQテーブルの初期化、グリーディ方策の定義、ハイパーパラメータの設定、学習ループと評価の作成と実行、結果の可視化を含みます。
学習ループの作成や実行で問題がある場合は、出力付きのコードソースを確認してください。
セットアップ
仮想ディスプレイのセットアップ
まず、リプレイ動画(Gif)を生成するための依存関係をインストールします。環境をレンダリングしてフレームを記録するために、仮想画面(pyvirtualdisplay)が必要です。
注:%%capture を使うことで、Jupyterセルの出力を抑制しています。
%%capture
!pip install pyglet==1.5.1
!apt install python-opengl
!apt install ffmpeg
!apt install xvfb
!pip3 install pyvirtualdisplay
# Virtual display
from pyvirtualdisplay import Display
virtual_display = Display(visible=0, size=(1400, 900))
virtual_display.start()
依存関係のインストール
続いて、学習ループの作成・実行・評価に役立つ依存関係をインストールします。
- gymnasium:FrozenLake-v1 環境の初期化に使用。
- pygame:FrozenLake-v1 のUIに使用。
- numPy:Qテーブルの作成と操作に使用。
%%capture
!pip install gymnasium
!pip install pygame
!pip install numpy
!pip install imageio imageio_ffmpeg
パッケージのインポート
必要なライブラリをインポートします。
- Imageio はアニメーション作成に使用します。
- tqdm はプログレスバーに使用します。
import numpy as np
import gymnasium as gym
import random
import imageio
from tqdm.notebook import trange
Frozen Lake の Gymnasium 環境
Frozen Lake gymnasium ライブラリを使って、滑らない 4x4 の環境を作成します。
- グリッドは「4x4」と「8x8」の2種類があります。
is_slippery=Trueの場合、氷の上が滑りやすいため、エージェントは意図した方向に動かないことがあります。
環境を初期化したら、環境の分析を行います。
env = gym.make("FrozenLake-v1",map_name="4x4",is_slippery=False)
print("Observation Space", env.observation_space)
print("Sample observation", env.observation_space.sample()) # display a random observation
環境には16個の一意のスペースがあり、ランダムな位置に表示されます。
Observation Space Discrete(16)
Sample observation 15
行動の数を確認し、ランダムな行動を表示してみましょう。
アクション空間:
- 0:左に移動
- 1:下に移動
- 2:右に移動
- 3:上に移動
報酬関数:
- ゴール到達:+1
- 穴に落ちる:0
- 凍った湖上にとどまる:0
print("Action Space Shape", env.action_space.n)
print("Action Space Sample", env.action_space.sample())
Action Space Shape 4
Action Space Sample 1
Qテーブルの作成と初期化
Qテーブルは列が行動、行が状態です。Gymnasium からアクション空間と状態空間を取得し、この情報を使ってQテーブルを作成します。
state_space = env.observation_space.n
print("There are ", state_space, " possible states")
action_space = env.action_space.n
print("There are ", action_space, " possible actions")
There are 16 possible states
There are 4 possible actions
Qテーブルを初期化するために、状態空間と行動空間のサイズを持つ Numpy 配列を作成します。16 × 4 の配列を作ります。
def initialize_q_table(state_space, action_space):
Qtable = np.zeros((state_space, action_space))
return Qtable
Qtable_frozenlake = initialize_q_table(state_space, action_space)
イプシロン・グリーディ方策
前のセクションで、探索と活用のトレードオフを扱うイプシロン・グリーディ戦略について学びました。確率 1 - ɛ で活用し、確率 ɛ で探索します。
epsilon_greedy_policy では以下を行います。
- 0〜1の乱数を生成する。
- 乱数がイプシロンより大きければ活用する。つまり、与えられた状態で最も値が高い行動を取る。
- それ以外は探索(ランダムな行動を取る)。
def epsilon_greedy_policy(Qtable, state, epsilon):
random_int = random.uniform(0,1)
if random_int > epsilon:
action = np.argmax(Qtable[state])
else:
action = env.action_space.sample()
return action
グリーディ方策の定義
Q学習はオフポリシーのアルゴリズムであり、行動を取るための方策と更新に用いる方策が異なることはすでに述べました。
この例では、イプシロン・グリーディ方策が行動方策で、グリーディ方策が更新方策です。
グリーディ方策は、学習後の最終方策にもなります。Qテーブルから状態と行動の値が最大となるものを選択するために使われます。
def greedy_policy(Qtable, state):
action = np.argmax(Qtable[state])
return action
モデルのハイパーパラメータ
これらのハイパーパラメータは学習ループで使用され、適切にチューニングするとより良い結果が得られます。
良い価値近似を学ぶには、エージェントが十分な状態空間を探索する必要があるため、イプシロンは段階的に減衰させる必要があります。減衰率が高すぎると、十分に探索しないまま行き詰まる可能性があります。
- 学習エピソードは10,000、評価は100。
- 学習率は0.7。
- 環境は "FrozenLake-v1" を使用し、1エピソードあたりの最大ステップ数は99。
- ガンマ(割引率)は0.95。
- eval_seed:評価時の環境シード。
- 探索のイプシロン確率は開始時1.0、最小は0.05。
- イプシロン確率の指数減衰率は0.0005。
# Training parameters
n_training_episodes = 10000
learning_rate = 0.7
# Evaluation parameters
n_eval_episodes = 100
# Environment parameters
env_id = "FrozenLake-v1"
max_steps = 99
gamma = 0.95
eval_seed = []
# Exploration parameters
max_epsilon = 1.0
min_epsilon = 0.05
decay_rate = 0.0005
モデルの学習
学習ループでは次のことを行います。
- 学習エピソードのループを作成する。
- 毎エピソードで探索は減らし、活用を増やすために、まずイプシロンを減少させる。
- 環境をリセットする。
- 最大ステップ数のネストされたループを作成する。
- イプシロン・グリーディ方策を使って行動を選択する。
- 行動(At)を取り、期待報酬(Rt+1)と状態(St+1)を観測する。
- 行動(a)を取り、結果の状態(s')と報酬(r)を観測する。
- 式を使ってQ関数を更新する。
done= Trueならエピソードを終了してループを抜ける。- 最後に、現在の状態を新しい状態に更新する。
- すべての学習エピソード完了後、更新されたQテーブルを返す。
def train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable):
for episode in trange(n_training_episodes):
epsilon = min_epsilon + (max_epsilon - min_epsilon)*np.exp(-decay_rate*episode)
# Reset the environment
state = env.reset()
step = 0
done = False
# repeat
for step in range(max_steps):
action = epsilon_greedy_policy(Qtable, state, epsilon)
new_state, reward, done, info = env.step(action)
Qtable[state][action] = Qtable[state][action] + learning_rate * (reward + gamma * np.max(Qtable[new_state]) - Qtable[state][action])
# If done, finish the episode
if done:
break
# Our state is the new state
state = new_state
return Qtable
10,000エピソードの学習は3秒で完了しました。
Qtable_frozenlake = train(n_training_episodes, min_epsilon, max_epsilon, decay_rate, env, max_steps, Qtable_frozenlake)

ご覧のとおり、学習済みのQテーブルには値が入り、エージェントはこれらの値を使って環境を移動し、目標を達成できるようになります。
Qtable_frozenlake
array([[0.73509189, 0.77378094, 0.77378094, 0.73509189],
[0.73509189, 0. , 0.81450625, 0.77378094],
[0.77378094, 0.857375 , 0.77378094, 0.81450625],
[0.81450625, 0. , 0.77378094, 0.77378094],
[0.77378094, 0.81450625, 0. , 0.73509189],
[0. , 0. , 0. , 0. ],
[0. , 0.9025 , 0. , 0.81450625],
[0. , 0. , 0. , 0. ],
[0.81450625, 0. , 0.857375 , 0.77378094],
[0.81450625, 0.9025 , 0.9025 , 0. ],
[0.857375 , 0.95 , 0. , 0.857375 ],
[0. , 0. , 0. , 0. ],
[0. , 0. , 0. , 0. ],
[0. , 0.9025 , 0.95 , 0.857375 ],
[0.9025 , 0.95 , 1. , 0.9025 ],
[0. , 0. , 0. , 0. ]])
評価
evaluate_agent は n_eval_episodes エピソードを実行し、報酬の平均と標準偏差を返します。
- ループ内で、まず評価用のシードがあるかを確認します。なければシードなしで環境をリセットします。
- ネストされたループは max_steps まで実行します。
- エージェントは、Qテーブルを用いて、与えられた状態で将来報酬の期待値が最大となる行動を取ります。
- 報酬を累積する。
- 状態を更新する。
- 終了状態(穴に落ちる、またはゴール達成)の場合、ループを抜ける。
- 結果を配列に追加する。
- 最後に、これらの結果から平均と標準偏差を計算します。
def evaluate_agent(env, max_steps, n_eval_episodes, Q, seed):
episode_rewards = []
for episode in range(n_eval_episodes):
if seed:
state = env.reset(seed=seed[episode])
else:
state = env.reset()
step = 0
done = False
total_rewards_ep = 0
for step in range(max_steps):
# Take the action (index) that have the maximum reward
action = np.argmax(Q[state][:])
new_state, reward, done, info = env.step(action)
total_rewards_ep += reward
if done:
break
state = new_state
episode_rewards.append(total_rewards_ep)
mean_reward = np.mean(episode_rewards)
std_reward = np.std(episode_rewards)
return mean_reward, std_reward
ご覧のとおり、標準偏差ゼロの満点が得られました。これは、100エピソードすべてでエージェントがゴールに到達したことを意味します。
# Evaluate our Agent
mean_reward, std_reward = evaluate_agent(env, max_steps, n_eval_episodes, Qtable_frozenlake, eval_seed)
print(f"Mean_reward={mean_reward:.2f} +/- {std_reward:.2f}")
Mean_reward=1.00 +/- 0.00
結果の可視化
これまでは数値を扱ってきましたが、デモ用に、エージェントが開始からゴールに到達するまでのアニメーションGifを作成します。
- まず、0〜500の乱数をシードにして環境をリセットし、状態を作成します。
- rdb_array を使って環境をレンダリングし、画像配列を作成します。
- その後、
imgをimages配列に追加します。 - ループでは、Qテーブルを使ってステップを踏み、各ステップで画像をレンダリングします。
- 最後に、この配列と imageio を使って、1秒あたり1フレームのGifを作成します。
def record_video(env, Qtable, out_directory, fps=1):
images = []
done = False
state = env.reset(seed=random.randint(0,500))
img = env.render(mode='rgb_array')
images.append(img)
while not done:
# Take the action (index) that have the maximum expected future reward given that state
action = np.argmax(Qtable[state][:])
state, reward, done, info = env.step(action) # We directly put next_state = state for recording logic
img = env.render(mode='rgb_array')
images.append(img)
imageio.mimsave(out_directory, [np.array(img) for i, img in enumerate(images)], fps=fps)
Jupyter Notebookの場合は、IPython.display の Image 関数を使ってGifを表示できます。
video_path="/content/replay.gif"
video_fps=1
record_video(env, Qtable_frozenlake, video_path, video_fps)
from IPython.display import Image
Image('./replay.gif')
これで、結果を同僚やクラスメイトと共有したり、ソーシャルメディアに投稿したりできます。
Q学習に関するよくある質問
Q学習の欠点は何ですか?
Q学習では、とくに初期段階において、学習プロセスがエージェントにとってコストが高くなります。なぜでしょうか。最適方策へ収束させるためには、あらゆる状態と行動の組み合わせを頻繁に訪れる必要があるからです。
なぜQ learningと呼ばれるのですか?
Q学習の「Q」はquality(品質)を表します。将来の報酬を得るうえで、与えられた行動がどれだけ有用かを示し、期待報酬を最大化するための状態と行動の対応関係(マップ)を作るのに用いられます。
なぜQ学習はオフポリシーなのですか?
Q学習では、更新される方策が行動(振る舞い)方策と異なるため、オフポリシーのアルゴリズムと呼ばれます。
Q学習は常に収束しますか?
はい。学習中、アルゴリズムは常に最適方策へ収束します。
なぜDeep Q学習が必要なのですか?
Q学習は、小規模で離散的な環境向けに設計されたシンプルなアルゴリズムです。環境が大規模になると、状態と行動のQテーブルが非常に巨大になり、学習に多大なメモリと計算資源を要します。一方、Deep Q学習はQテーブルをニューラルネットワークに置き換えることで、連続的な行動や状態を含む大規模な環境に対応します。