※本コラムは、以前に個人ブログとして公開していた内容を、加筆・再構成のうえ掲載しております。技術的な内容は執筆当時のものであり、現在とは異なる場合がございます。
こんにちは。Anagraftの伊藤です。
Googleの子会社であるDeepMindにより開発された、人工知能を搭載したコンピューター囲碁プログラムAlphaGo(アルファ碁)の活躍により、深層学習や強化学習の注目度はさらに増しています。
今回は、このDeepMindが考案した深層強化学習(Deep Q-Network: DQN)の実装例をご紹介いたします。それにあたり、まずは基本的な強化学習(Q-Learning)と強化学習用オープンプラットフォームOpenAI Gymをご紹介し、その後DQNへの発展を見ていきます。また最後に、DQN以降に拡張された深層強化学習についても簡単に触れたいと思います。
目次
強化学習(Reinforcement learning)は、ある環境内におけるエージェントが、現在の状態を観測し、取るべき行動を決定する問題を扱う、機械学習の一種の手法です。機械学習の書籍では、機械学習の種類として「教師なし学習」と「教師あり学習」に加えて、3つ目に「強化学習」がよく挙げられています。
ある環境内に置かれたエージェントは、環境の状態ごとに施策(行動を決定するルール)に基づいて行動を決定して、次の状態に遷移して報酬を受け取ります。これを繰り返して、より多くの累積報酬を得られるような施策を学習する方法が強化学習です。
強化学習にもさまざまな種類がありますが、今回は、有限マルコフ決定過程な環境において、より多い累積報酬を得る施策として、行動価値関数Qを用いた学習方法を実装します。
エージェントが各状態において選択できる行動の中で、最も行動価値関数の値が高い行動を選択するように学習する方法をQ学習と言います。学習は、エージェントが行動を選択し、遷移した状態とその時に得られた報酬を用いて、その時の行動価値関数Qの値を更新することを繰り返すことで行います。
例えば、エージェントの学習率が \( \alpha \)、割引率が \( \gamma \) であるとして、状態 \( s_t \) のエージェントが行動 \( a_t \) を選択し、報酬 \( r \) を得て、状態が \( s_{t+1} \) に遷移したとすると、このときの行動価値関数Qの値 \( Q(s_t, a_t) \) を次の式で更新します。
\( Q_{s_t,a_t} \leftarrow Q_{s_t,a_t} + \alpha(r_{t+1} + \gamma \max_{a_{t+1}} Q(s_{t+1},a_{t+1}) – Q(s_t,a_t)) \)
学習率は、エージェントが行動の価値をどのくらいの割合で学習するかを表すパラメータであり、割引率は、将来もらえる報酬を現在の価値としてどのくらいの割合とするかを表すパラメータになります。
行動の選択は、今回は、基本的には行動価値関数の価値が高い行動を選びつつ、一定確率 \( \varepsilon \) でランダムに行動を選ぶように行動を選択させる、\( \varepsilon \)-greedy法という手法を用います。他にもボルツマン分布に従う確率で行動を選択する方法などもあるようです。
前述の通り、このような強化学習を実行するには、強化学習で解く問題(環境)をプログラム上で用意しなければならないことになります。そういった強化学習用の環境を提供するプラットフォームとして、OpenAI Gymというものがあります。
OpenAI Gymは環境(行動空間、状態空間、報酬)のみを提供してくれるライブラリです。環境の種類はさまざまなものが含まれています。これらに対して自身で強化学習アルゴリズムを実装して環境に試してみることで、強化学習アルゴリズムの評価を行うことができます。
それでは実際に、OpenAI Gymで提供されている環境をQ学習で解いてみる例をご紹介します。今回はToy textの「FrozenLake-v0」という環境にしました。
簡単に環境の説明をすると、4×4のマスを想定し、Sがエージェントの現在地、Fが道、Hが穴(落ちるとゲームオーバーでマイナス報酬を得る)、Gがゴール(ここに来て初めてプラス報酬を得る)で、Gに向かうようエージェントを行動(移動)させるといった、経路探索をする環境です。
PythonでQ-learningにより解いてみるコード例が以下になります。
env = gym.make("FrozenLake-v0")
n_obs = env.observation_space.n
n_act = env.action_space.n
q = np.zeros([n_obs, n_act])
epoch_cnt = 20000
max_steps = 200
epsilon = 0.001
gamma = 0.9
alpha = 0.9
rewards = np.zeros(epoch_cnt)
for epoch in tqdm(range(epoch_cnt)):
pobs = env.reset()
done = False
for step in range(max_steps):
pact = np.argmax(q[pobs, :])
pact = np.random.choice(np.where(q[pobs, :] == q[pobs, pact])[0])
if np.random.rand() <= epsilon:
pact = env.action_space.sample()
obs, reward, done, _ = env.step(pact)
if not done:
q[pobs, pact] += alpha * (reward - q[pobs, pact] + gamma * np.max(q[obs, :]))
else:
q[pobs, pact] += alpha * (reward - q[pobs, pact])
pobs = obs
rewards[epoch] = reward
if done:
break
rates = np.average(rewards.reshape([epoch_cnt//1000, 1000]), axis = 1)
plt.plot(rates)
plt.show()
最初はすぐに穴に落ちたりして報酬が低く、ゴールにたどり着けていませんが、エピソード数を重ねるごとにだんだんとゴールするようになって報酬を得るように学習していることが分かります。
Q-learningでは前述の通り、\( Q_{s_t,a_t} \) をより良いものに更新していき、このテーブルの値を使って行動を選択しているわけですが、例えば、状態数や行動数がとても大きい数であったり、あるいは連続的であるなどの場合には、組み合わせの数が非常に多くなってしまい、計算させるのが困難なものになってしまいます。
そこで、DQNではこういった場合に対処するため、Qをテーブルではなく、関数(ニューラルネットワークないし深層学習)で近似するということを考えます。ただし、ここで近似するネットワークは、状態x行動を入力とするのではなく、状態を入力して行動のQ値を出力するネットワークを構成します。
Q-learningと同様に、\( Q(s_t, a_t) \) を \( r + \gamma \max_{a_{t+1}} Q(s_{t+1}, a_{t+1}) \) に近づけたいと考えるので、後者を教師信号targetとして、現在のQとの誤差関数 \( L(s,a) \) を使ってネットワークを学習させます。
\( \text{target} = r + \gamma \max_{a_{t+1}} Q(s_{t+1}, a_{t+1}) \)
\( L(s,a) = \frac{1}{2} (\text{target} - Q(s,a))^2 \)
またDQNでは、以上の学習アルゴリズムの仕組みに加えて、下記のような工夫を加えることで、ネットワークを効率的に学習させています。
それでは、実際に先ほどのFrozenLakeの環境に対してDQNでアプローチをしてみるコード例を記します。もちろん、FrozenLakeはDQNを使うほどの環境ではないのですが、実装の勉強のため簡単な問題でまずは見てみます。
ニューラルネットワークの実装にはChainerを使用しました。実装が以下の通りです。
import copy
import time
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import chainer
import chainer.links as L
import chainer.functions as F
import gym
# NNクラス定義
class NN(chainer.Chain):
def __init__(self):
super(NN, self).__init__(
xc = L.Linear(16, 100),
ch = L.Linear(100, 100),
hy = L.Linear(100, 4)
)
def __call__(self, x):
x = chainer.Variable(x)
h = F.relu(self.xc(x))
h = F.relu(self.ch(h))
y = F.relu(self.hy(h))
return y
def reset(self):
self.zerograds()
# 状態変換関数定義
def convert(obs):
tmp = np.zeros(16)
tmp[obs] = 1
obs = np.array(tmp, dtype="float32")
return obs
# 環境
env = gym.make("FrozenLake-v0")
# モデル
Q = NN() # 近似Q関数
Q_ast = copy.deepcopy(Q)
optimizer = chainer.optimizers.Adam()
optimizer.setup(Q)
# 定数
EPOCH_NUM = 1000
MEMORY_SIZE = 1000
BATCH_SIZE = 100
EPSILON = 1
EPSILON_DECREASE = 0.005
EPSILON_MIN = 0.01
START_REDUCE_EPSILON = 1000
TRAIN_FREQ = 10
UPDATE_TARGET_Q_FREQ = 20
GAMMA = 0.99
total_step = 0
memory = []
total_rewards = []
total_losses = []
# 学習開始
print("\t".join(["epoch", "EPSILON", "reward", "total_step", "loss", "elapsed_time"]))
start = time.time()
for epoch in range(EPOCH_NUM):
pobs = env.reset()
pobs = convert(pobs)
done = False
total_reward = 0
total_loss = 0
while not done:
# 行動選択
pact = env.action_space.sample()
if np.random.rand() > EPSILON:
pact = Q(pobs.reshape((1, 16)))
pact = np.argmax(pact.data)
# 行動
obs, reward, done, _ = env.step(pact)
obs = convert(obs)
# メモリに蓄積
memory.append((pobs, pact, reward, obs, done))
if len(memory) > MEMORY_SIZE:
memory.pop(0)
# 学習
if len(memory) == MEMORY_SIZE:
if total_step % TRAIN_FREQ == 0:
np.random.shuffle(memory)
memory_idx = range(len(memory))
for i in memory_idx[::BATCH_SIZE]:
batch = np.array(memory[i:i+BATCH_SIZE])
pobss = np.array(batch[:,0].tolist(), dtype="float32").reshape((BATCH_SIZE, 16))
pacts = np.array(batch[:,1].tolist(), dtype="int32")
rewards = np.array(batch[:,2].tolist(), dtype="int32")
obss = np.array(batch[:,3].tolist(), dtype="float32").reshape((BATCH_SIZE, 16))
dones = np.array(batch[:,4].tolist(), dtype="bool")
# set y
q = Q(pobss)
maxq = list(map(np.max, Q_ast(obss).data))
target = copy.deepcopy(q.data)
for j in range(BATCH_SIZE):
target[j, pacts[j]] = rewards[j]+GAMMA*maxq[j]*(not dones[j])
# Perform a gradient descent step
Q.reset()
loss = F.mean_squared_error(q, chainer.Variable(target))
total_loss += loss.data
loss.backward()
optimizer.update()
# Q関数の更新
if total_step % UPDATE_TARGET_Q_FREQ == 0:
Q_ast = copy.deepcopy(Q)
# εの減少
if EPSILON > EPSILON_MIN:
if total_step > START_REDUCE_EPSILON:
EPSILON -= EPSILON_DECREASE
# 次の行動へ
total_reward += reward
total_step += 1
pobs = obs
total_rewards.append(total_reward)
total_losses.append(total_loss)
if (epoch+1) % 100 == 0:
elapsed_time = time.time()-start
r = sum(total_rewards[((epoch+1)-10):(epoch+1)])
l = sum(total_losses[((epoch+1)-10):(epoch+1)])
print("\t".join(map(str,[epoch+1, EPSILON, r, total_step, l, str(elapsed_time)+"[sec]"])))
start = time.time()
plt.figure(figsize=(10,5))
resize = (len(total_rewards)//10, 10)
tmp = np.array(total_rewards, dtype="float32").reshape(resize)
tmp = np.average(tmp, axis=1)
plt.plot(tmp)
plt.show()
無事に学習をしました。Q-learningの時と同様に、この環境についてはトータル報酬はせいぜい0.6程度までなのかもしれません。
この章では、やはり前章のような離散的な状態を返す簡単な問題ではなく、もう少し難しめの連続的な状態を表す環境にも適用してみようと思います。
FrozenLakeは迷路探索のような問題でしたので、状態が「プレイヤーはどこにいるのか」といった離散的な値で表すことができました。今回扱う環境はCartPoleというゲームになります。強化学習の界隈では古典的によく使われてきた環境のようで、OpenAI Gymにも環境が用意されています。
Open AI Gymによる説明とゲームの様子が下記になります。
CartPole-v0
A pole is attached by an un-actuated joint to a cart, which moves along a frictionless track. The system is controlled by applying a force of +1 or -1 to the cart. The pendulum starts upright, and the goal is to prevent it from falling over. A reward of +1 is provided for every timestep that the pole remains upright. The episode ends when the pole is more than 15 degrees from vertical, or the cart moves more than 2.4 units from the center.
Open AI Gym
つまり、台車の上に棒(ポール)を立て、その棒が倒れてしまわないように、台車を右左に動かして、バランスよく立て続けるといったゲームです。よく小学生の男子が箒を手のひらに乗せてバランスを保ち続けるといった遊びをやることがあるかと思いますが、あれと同じようなものです。
ゲームのルールについてまとめると下記のようになります。
実際にOpenAI Gymから環境を読み込んで、ランダムに行動を取り続けてみたときの各値が下記のようになります。
env = gym.make("CartPole-v0")
print("observation space num: ", env.observation_space.shape[0])
print("action space num: ", env.action_space.n)
print("-"*50)
pobs = env.reset()
done = False
while not done:
act = env.action_space.sample()
obs, reward, done, _ = env.step(act)
print(pobs, act, reward, obs, done)
pobs = obsそれでは先ほどと同様に、DQNを使って学習させてみます。今度は深層学習のライブラリはPyTorchを使ってみました。実装のコードは以下の通りです。
import copy
import time
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
import gym
from gym import wrappers
# 環境
MONITOR = False
env = gym.make("CartPole-v0")
if MONITOR:
env = wrappers.Monitor(env, "./result", force=True)
obs_num = env.observation_space.shape[0]
acts_num = env.action_space.n
HIDDEN_SIZE = 100
class NN(nn.Module):
def __init__(self):
super(NN, self).__init__()
self.fc1 = nn.Linear(obs_num, HIDDEN_SIZE)
self.fc2 = nn.Linear(HIDDEN_SIZE, HIDDEN_SIZE)
self.fc3 = nn.Linear(HIDDEN_SIZE, HIDDEN_SIZE)
self.fc4 = nn.Linear(HIDDEN_SIZE, acts_num)
def __call__(self, x):
h = F.relu(self.fc1(x))
h = F.relu(self.fc2(h))
h = F.relu(self.fc3(h))
y = F.relu(self.fc4(h))
return y
# 定数
EPOCH_NUM = 3000
STEP_MAX = 200
MEMORY_SIZE = 200
BATCH_SIZE = 50
EPSILON = 1.0
EPSILON_DECREASE = 0.001
EPSILON_MIN = 0.1
START_REDUCE_EPSILON = 200
TRAIN_FREQ = 10
UPDATE_TARGET_Q_FREQ = 20
GAMMA = 0.97
LOG_FREQ = 1000
# モデル
Q = NN()
Q_ast = copy.deepcopy(Q)
optimizer = optim.RMSprop(Q.parameters(), lr=0.00015, alpha=0.95, eps=0.01)
total_step = 0
memory = []
total_rewards = []
# 学習開始
print("\t".join(["epoch", "epsilon", "reward", "total_step", "elapsed_time"]))
start = time.time()
for epoch in range(EPOCH_NUM):
pobs = env.reset()
step = 0
done = False
total_reward = 0
while not done and step < STEP_MAX:
if MONITOR:
env.render()
# 行動選択
pact = env.action_space.sample()
# ε-greedy法
if np.random.rand() > EPSILON:
# 最適な行動を予測
pobs_ = np.array(pobs, dtype="float32").reshape((1, obs_num))
pobs_ = Variable(torch.from_numpy(pobs_))
pact = Q(pobs_)
maxs, indices = torch.max(pact.data, 1)
pact = indices.numpy()[0]
# 行動
obs, reward, done, _ = env.step(pact)
if done:
reward = -1
# メモリに蓄積
memory.append((pobs, pact, reward, obs, done))
if len(memory) > MEMORY_SIZE:
memory.pop(0)
# 学習
if len(memory) == MEMORY_SIZE:
# 経験リプレイ
if total_step % TRAIN_FREQ == 0:
memory_ = np.random.permutation(memory)
memory_idx = range(len(memory_))
for i in memory_idx[::BATCH_SIZE]:
batch = np.array(memory_[i:i+BATCH_SIZE])
pobss = np.array(batch[:,0].tolist(), dtype="float32").reshape((BATCH_SIZE, obs_num))
pacts = np.array(batch[:,1].tolist(), dtype="int32")
rewards = np.array(batch[:,2].tolist(), dtype="int32")
obss = np.array(batch[:,3].tolist(), dtype="float32").reshape((BATCH_SIZE, obs_num))
dones = np.array(batch[:,4].tolist(), dtype="bool")
# set y
pobss_ = Variable(torch.from_numpy(pobss))
q = Q(pobss_)
obss_ = Variable(torch.from_numpy(obss))
maxs, indices = torch.max(Q_ast(obss_).data, 1)
maxq = maxs.numpy()
target = copy.deepcopy(q.data.numpy())
for j in range(BATCH_SIZE):
target[j, pacts[j]] = rewards[j]+GAMMA*maxq[j]*(not dones[j])
# Perform a gradient descent step
optimizer.zero_grad()
loss = nn.MSELoss()(q, Variable(torch.from_numpy(target)))
loss.backward()
optimizer.step()
# Q関数の更新
if total_step % UPDATE_TARGET_Q_FREQ == 0:
Q_ast = copy.deepcopy(Q)
# εの減少
if EPSILON > EPSILON_MIN and total_step > START_REDUCE_EPSILON:
EPSILON -= EPSILON_DECREASE
# 次の行動へ
total_reward += reward
step += 1
total_step += 1
pobs = obs
total_rewards.append(total_reward)
if (epoch+1) % LOG_FREQ == 0:
r = sum(total_rewards[((epoch+1)-LOG_FREQ):(epoch+1)])/LOG_FREQ
elapsed_time = time.time()-start
print("\t".join(map(str,[epoch+1, EPSILON, r, total_step, str(elapsed_time)+"[sec]"])))
start = time.time()
if MONITOR:
env.render(close=True)
得られる累積報酬がエポックを重ねるごとに増えていっていることが確認できます。200ステップまでしか行動しませんので、この環境での最高累積報酬は200です。
また、この環境では以下の通り、動画として保存することができます。以下はエポックがまだ最初の方のプレイ動画です。まだ学習が進んでいなく、すぐに棒が傾いてしまってゲームがすぐ終了(そして動画もすぐ終了)する状態です。
次に以下が、学習が進んだ状態のプレイ動画になります。台車を小刻みに動かしながらバランスをとって、棒を立て続けられるようになっています。
このようにして学習できていることが確認できました。実は前章のFrozenLakeのときよりも、ハイパーパラメータの調整に苦労しました。DQNに関しては、勾配法のアルゴリズムの選択も含め、論文などで良いハイパーパラメータ値についての研究がされています。最初はなかなかうまく学習しませんでしたが、アルゴリズムやハイパーパラメータをそのような研究を参考に設定し直すことで、うまく学習するようになりました。この辺りの試行錯誤をする必要があることを念頭に置いておいた方がよいでしょう。
上記のDQNが登場したのが2013年頃のことで、その後、こういった深層強化学習の分野の研究も盛んに行われるようになってから、さまざまなDQNの改良が登場しています。最近DQNの周辺の研究の中からいくつか簡単に触れてみたいと思います。
Double DQN
論文: Deep reinforcement learning with double q-learning
DQNでは、TD誤差計算時に目標値に大きすぎる値が設定されると、前の状態を過大評価してしまうという問題がありました。そこで、2つのQ関数を混ぜて学習をさせることで、TD誤差の計算の安定化を図ったものがDouble DQNです。具体的には、DQNにおける教師信号出力用のネットワークを \( Q_{\theta^{-1}} \)、そのコピーを \( Q_\theta \) とすると、\( s_{t+1} \) でとるべき行動を \( Q_\theta \) で決定し、その評価値を \( Q_{\theta^{-1}} \) で出力して \( Q_\theta \) を更新します。
Dueling Double DQN
論文: Dueling Network Architectures for Deep Reinforcement Learning
ここまでの方法では、Q関数において、1回の更新で1つの行動に対する価値しか更新ができません。そこでこの研究では、Q関数を状態価値関数 \( V(s) \) とAdvantage(行動優位)関数 \( A(s,a) \) に分解して学習させます。
\( Q_\theta(s_t, a_t) = V_{\theta,\beta} + \left( A_{\theta,\alpha}(s_t, a_t) + \frac{1}{|A|} \sum A_{\theta,\alpha}(s_t, a_{t+1}) \right) \)
こうすることで、\( V \) については毎回更新ができるので、TD誤差の計算の伝播が早くなるようです。単純に線形和で分解してしまうと、状態価値関数、Advantage関数がともに誤差を含めて学習してしまう恐れがあるため、これを防ぐために \( \frac{1}{|A|} \sum A_{\theta,\alpha}(s_t, a_{t+1}) \) を加えるということをするようです。
Prioritized Experience Replay
論文: Prioritized Experience Replay
DQNでは経験からランダムに選んで学習してきているので、より学習に役立つ経験を優先して学習させるようにします。具体的には、経験サンプルの重要性 \( p_i \) を、TD誤差の絶対値 \( |\delta_i| \)(パラメータの更新幅とみなせる)を用いて表し、確率とした上で経験サンプリングをします。
\( P(i) = \frac{p_i^\alpha}{\sum_k p_k^\alpha}, \quad p_i = |\delta_i| + \varepsilon \)
こうすることで、TD誤差の大きい経験を優先して学習させられるようになります。
Gorila DQN
論文: Massively Parallel Methods for Deep Reinforcement Learning
「GOogle ReInforcement Learning Architecture」を略して「Gorila」です。こちらは、これまでの学習アルゴリズムの改善とは少し違い、学習を並列分散させるというものです。Memory(経験)を集めるActorと、誤差計算を行うLearnerに分けて、並列処理をすることで、DQNよりも高速に学習できたことが報告されています。
A3C
論文: Asynchronous Methods for Deep Reinforcement Learning
Google DeepMindにより、2016年に発表された論文です。Asynchronous Advantage Actor-Critic(A3C)は、これまでのDQNの学習アルゴリズムとは大きく変わっており、こちらも並列処理をさせてGPUを使ったDQNよりも高速に学習させているという点と、これまでベースとしていた強化学習手法であるQ-Learningとは違い、Actor-Criticを採用しています。
主な特徴は以下の2点です。
Asynchronous
CPUのマルチスレッドで同時に複数のエージェントを並列で走らせ、パラメータを非同期に更新します。つまり、以下のプロセスを繰り返します。
この時にパラメータだけでなく、最適化アルゴリズムRMSpropの変数についてもグローバルに共有するようです。
Advantage Actor-Critic
Actor-Criticは、政策 \( \pi \) と、状態価値関数 \( V \) をそれぞれ独立に推定しながら学習を行う強化学習手法です。それぞれ独立に行うことにより、行動が連続的な場合でも学習させやすいといったメリットがあります。
ただし、状態価値関数は、報酬 \( r_{t+1} \) と次の状態価値 \( V(s_{t+1}) \) を使って更新されます。したがって、次の状態価値が正しくなければ、現在の状態価値も正しく推定されないといった問題も起こります。そこで、Actor-Criticによって時系列のまま学習できる性質を生かし、kステップ先までの報酬を考慮した推定値(Advantage)を使います。これにより現在の状態価値が、より確からしい推定値となり、学習が早く進むようです。
以上、これらの工夫によって、これまでのDQNよりも学習速度が大幅に向上することが報告されています。
また、A3Cは、実は深層強化学習のライブラリとして公開されているChainerRLにすでに実装されています。単純に学習を試したいということであれば、こちらを試してみるのもよいかもしれません。
今回は、強化学習(Q-learning)と深層強化学習(DQN)の実装方法とその発展手法についてご紹介しました。
Q-learningは価値ベースの手法であり、DQNは深層学習を用いてQ-learningを発展させた手法です。DQNは高次元・複雑な問題にも対応できるため、幅広い応用が期待されます。
また、DQNの発展手法として、Double DQNやDueling DQN、Prioritized Experience Replay、Gorila DQN、A3Cなどの手法についてもご紹介しました。強化学習は実用的な問題への適用が増えている分野であり、本記事を参考にしながらさまざまな応用にも挑戦していただければ幸いです。
本記事は執筆当時の技術環境に基づいた内容です。以下に、2026年現在の状況について補足いたします。
Chainerについて
Chainerは2019年12月にPreferred Networksにより開発終了が発表されました。後継としてPyTorchへの移行が推奨されています。本記事中のChainerによる実装は当時の記録として残しておりますが、現在同様の実装を行う場合はPyTorchの使用を推奨いたします。
OpenAI Gym(Gymnasium)について
OpenAI GymはOpenAIによるメンテナンスが終了し、後継プロジェクトとしてFarama Foundationの「Gymnasium」が開発されています。APIの基本構造は同様ですが、gym.make()の戻り値やstep()の戻り値の形式が若干変更されています。
深層強化学習の発展
2026年現在、強化学習の分野はさらに大きく発展しています。PPO(Proximal Policy Optimization)やSAC(Soft Actor-Critic)が実用的な手法として広く採用されているほか、大規模言語モデルの学習にRLHF(Reinforcement Learning from Human Feedback)が活用されるなど、強化学習の応用領域はさらに拡大しています。
主要なライブラリ
2026年現在の主要な強化学習ライブラリとして、Stable Baselines3、CleanRL、RLlibなどが広く利用されています。本記事で使用したChainerRLは開発が終了しておりますので、現在はこれらのライブラリの利用を推奨いたします。