Q-learning、DQN 都是 基于价值(value-based)的方法,其中 Q-learning 是处理有限状态的算法,而 DQN 可以用来解决连续状态的问题。虽然DQN能够解决连续的状态的问题,但是却无法处理连续动作空间的动作决策。而在实际应用中,很多场景的动作都是连续的,如自动驾驶中,需要决策车辆的速度、加速度、油门刹车的角度等,在游戏AI中,需要决策角色的移动和攻击的方向、距离、预判敌人的位置等。
基于策略(policy-based)的策略梯度(Policy Gradiant)方法不需要学习值函数,它直接构造当前状态
s
s
s、神经网络参数
θ
\theta
θ到动作
a
a
a的策略
π
θ
\pi_{\theta}
πθ,输出当前状态下执行各种动作的概率值:
π
θ
(
s
,
a
)
=
P
[
a
∣
s
,
θ
]
\pi_{\theta}(s,a)=\Bbb P[a|s,\theta]
πθ(s,a)=P[a∣s,θ]
策略梯度方法执行的是非确定性策略。
非确定性策略:在每种状态下,执行的动作是随机的,可以按照概率值选择动作(如Softmax输出执行每个动作的概率)。
确定性策略:在某种状态下,要执行的动作是唯一且确定的。
5.1 策略梯度优化
基于Policy的强化学习是一个最优化问题,给定策略 π θ ( s , a ) \pi_{\theta}(s,a) πθ(s,a)以及神经网络参数 θ \theta θ,找到最优的 θ \theta θ使得目标函数 J ( θ ) J(\theta) J(θ)最大化。
目标函数 J ( θ ) J(\theta) J(θ)的构造
在回合制的环境中,存在起始和终止状态,需要计算从起始状态
s
1
s_1
s1起,执行策略
π
θ
\pi_{\theta}
πθ所得的累积回报的数学期望,表示为:
J
1
(
θ
)
=
E
[
∑
t
=
1
+
∞
γ
t
r
t
∣
s
0
,
π
θ
]
J_1(\theta)=\Bbb E[\sum^{+\infty}_{t=1}\gamma^tr_t|s_0,\pi_{\theta}]
J1(θ)=E[t=1∑+∞γtrt∣s0,πθ]
其中,折扣因子
γ
∈
[
0
,
1
]
\gamma\in[0,1]
γ∈[0,1],
r
t
r_t
rt为立即回报。
而在现实中应用较多持续运行的环境中,并不存在起始和终止状态,因此利用平均奖励目标函数计算各个时刻回报值的均值,表示为:
J
a
v
g
(
θ
)
=
∑
s
d
π
θ
(
s
)
∑
a
π
θ
(
s
,
a
)
R
s
a
J_{\rm{avg}}(\theta)=\sum_sd^{\pi_{\theta}}(s)\sum_a\pi_{\theta}(s,a)R^a_s
Javg(θ)=s∑dπθ(s)a∑πθ(s,a)Rsa
其中,
d
π
θ
(
s
)
d^{\pi_{\theta}}(s)
dπθ(s)表示状态的平稳分布,也即状态
s
s
s的出现概率;
R
s
a
R^a_s
Rsa表示在状态
s
s
s下执行动作
a
a
a时的立即回报的数学期望;
∑
a
π
θ
(
s
,
a
)
R
s
a
\sum_a\pi_{\theta}(s,a)R^a_s
∑aπθ(s,a)Rsa表示在状态
s
s
s下,执行所有动作时的立即回报的均值。
优化神经网络参数 θ \theta θ
梯度下降法
给定神经网络映射函数 ϕ θ \phi_{\theta} ϕθ, θ \theta θ是神经网络参数, x x x是一个训练样本, y y y是对应的样本标签,给定损失函数 L ( ⋅ ) \mathcal L(\cdot) L(⋅),一个常规的优化目标为:
min L ( ϕ θ ( x ) , y ) \min\mathcal L(\phi_{\theta}(x),y) minL(ϕθ(x),y)
ϕ θ ( x ) \phi_{\theta}(x) ϕθ(x)为预测值, y y y为目标值。梯度下降的目标是最小化预测值和目标值之间的差距。
梯度: g = ∇ θ L ( ϕ θ ( x ) , y ) g=\nabla_{\theta}\mathcal L(\phi_{\theta}(x),y) g=∇θL(ϕθ(x),y)更新量: Δ θ = η ⋅ g \Delta\theta=\eta\cdot g Δθ=η⋅g
更新参数:
θ ← { θ − η ⋅ g 梯度下降法 θ + η ⋅ g 梯度上升法 \theta\larr \begin{cases} \theta-\eta\cdot g&梯度下降法\\ \theta+\eta\cdot g&梯度上升法 \end{cases} θ←{θ−η⋅gθ+η⋅g梯度下降法梯度上升法
在传统深度学习的训练中,最优目标 y y y已知,目的是最小化神经网络的输出 y ^ \hat y y^和目标输出 y y y之间的差距(即损失)。而强化学习的训练中,虽然环境能给出立即回报 r r r,但最优目标 y y y(即动作 a a a)是未知的,它需要最大化目标(奖赏)函数 J ( θ ) J(\theta) J(θ)。因此策略梯度法就需要通过梯度上升法来搜索目标函数 J ( θ ) J(\theta) J(θ)中局部最大值所对应的神经网络参数 θ \theta θ。
计算策略梯度
定义策略梯度:
g
=
∇
θ
J
(
θ
)
=
(
∂
J
(
θ
)
∂
θ
1
⋮
∂
J
(
θ
)
∂
θ
n
)
g=\nabla_{\theta}J(\theta)= \begin{pmatrix} {\partial J(\theta)\over\partial\theta_1}\\ \vdots\\ {\partial J(\theta)\over\partial\theta_n} \end{pmatrix}
g=∇θJ(θ)=
∂θ1∂J(θ)⋮∂θn∂J(θ)
其中
J
(
θ
)
J(\theta)
J(θ)使用平均奖励目标函数
J
a
v
g
(
θ
)
=
∑
s
d
π
θ
(
s
)
∑
a
π
θ
(
s
,
a
)
R
s
a
J_{\rm{avg}}(\theta)=\sum_sd^{\pi_{\theta}}(s)\sum_a\pi_{\theta}(s,a)R^a_s
Javg(θ)=∑sdπθ(s)∑aπθ(s,a)Rsa,对于策略
π
θ
\pi_{\theta}
πθ:
-
当 π θ \pi_{\theta} πθ不可微时,利用有限差分法(一种求解微分方程数值解的近似方法,只要原理是对微分方程中的微分项直接进行差分近似),即:
∂ J ( θ ) ∂ θ k ≈ J ( θ + ϵ u k ) − J ( θ ) ϵ {\partial J(\theta)\over\partial\theta_k}\approx{J(\theta+\epsilon u_k)-J(\theta)\over\epsilon} ∂θk∂J(θ)≈ϵJ(θ+ϵuk)−J(θ)
式中 u k u_k uk表示为独热向量,仅第k维为1, ϵ \epsilon ϵ为加入的噪音。该方法简单但是有噪且低效,对任意策略 π θ \pi_{\theta} πθ都有效。 -
当 π θ \pi_{\theta} πθ可微时,利用似然比(Likeinhood Ratios),以上面的平均奖励目标函数 J a v g ( θ ) J_{\rm avg}(\theta) Javg(θ)为例,它的梯度为:
∇ θ J a v g ( θ ) = E π θ [ ∇ θ log π θ ( s , a ) Q π θ ( s , a ) ] \nabla_{\theta}J_{\rm avg}(\theta)=\Bbb E_{\pi_{\theta}}[\nabla_{\theta}\log\pi_{\theta}(s,a)Q^{\pi_{\theta}}(s,a)] ∇θJavg(θ)=Eπθ[∇θlogπθ(s,a)Qπθ(s,a)]
上式即为策略梯度定理(Policy Gradient Theorem)。其中, ∇ θ log π θ ( s , a ) \nabla_{\theta}\log\pi_{\theta}(s,a) ∇θlogπθ(s,a)是score function,它指示 θ \theta θ向哪个方向调整能够获得较好的效果; r r r代表reward,在单步MDP中,算法执行一个时间步即停止,此时立即回报为 r = R s a r=R^a_s r=Rsa。推导
∇ θ J a v g ( θ ) = ∑ s ∈ S d ( s ) ∑ a ∈ A ∇ θ π θ ( s , a ) R s a \nabla_{\theta}J_{\rm avg}(\theta)=\sum_{s\in S}d(s)\sum_{a\in A}\nabla_{\theta}\pi_{\theta}(s,a)R^a_s\\ ∇θJavg(θ)=s∈S∑d(s)a∈A∑∇θπθ(s,a)Rsa利用恒等式 ∇ f = f ∇ log f \nabla f=f\nabla\log f ∇f=f∇logf进行转换:
∇ θ π θ ( s , a ) = π θ ( s , a ) ∇ θ π θ ( s , a ) π θ ( s , a ) = π θ ( s , a ) ∇ θ log π θ ( s , a ) \begin{align} \nabla_{\theta}\pi_{\theta}(s,a)&=\pi_{\theta}(s,a){\nabla_{\theta}\pi_{\theta}(s,a)\over\pi_{\theta}(s,a)}\\ &=\pi_{\theta}(s,a)\nabla_{\theta}\log\pi_{\theta}(s,a) \end{align} ∇θπθ(s,a)=πθ(s,a)πθ(s,a)∇θπθ(s,a)=πθ(s,a)∇θlogπθ(s,a)
将该式带入上面式子,得:
∇ θ J a v g ( θ ) = ∑ s ∈ S d ( s ) ∑ a ∈ A π θ ( s , a ) ∇ θ log π θ ( s , a ) R s a = E π θ [ ∇ θ log π θ ( s , a ) r ] \begin{align} \nabla_{\theta}J_{\rm avg}(\theta)&=\sum_{s\in S}d(s)\sum_{a\in A}\pi_{\theta}(s,a)\nabla_{\theta}\log\pi_{\theta}(s,a)R^a_s\\ &=\Bbb E_{\pi_{\theta}}[\nabla_{\theta}\log\pi_{\theta}(s,a)r] \end{align} ∇θJavg(θ)=s∈S∑d(s)a∈A∑πθ(s,a)∇θlogπθ(s,a)Rsa=Eπθ[∇θlogπθ(s,a)r]使用长期价值 Q π θ ( s , a ) Q^{\pi_{\theta}}(s,a) Qπθ(s,a)替代瞬时奖励 r r r,最后得:
∇ θ J a v g ( θ ) = E π θ [ ∇ θ log π θ ( s , a ) Q π θ ( s , a ) ] \nabla_{\theta}J_{\rm avg}(\theta)=\Bbb E_{\pi_{\theta}}[\nabla_{\theta}\log\pi_{\theta}(s,a)Q^{\pi_{\theta}}(s,a)] ∇θJavg(θ)=Eπθ[∇θlogπθ(s,a)Qπθ(s,a)]
蒙特卡洛策略梯度算法
计算策略梯度的公式中的
Q
π
θ
(
s
,
a
)
Q^{\pi_{\theta}}(s,a)
Qπθ(s,a)可以采用蒙特卡洛方法来估计,该方法即REINFORCE算法,它的策略梯度为:
∇
θ
J
a
v
g
(
θ
)
=
E
π
θ
[
∑
t
=
0
T
(
∑
t
′
=
t
T
γ
t
′
−
t
r
t
′
)
∇
θ
log
π
θ
(
s
t
,
a
t
)
]
\nabla_{\theta}J_{\rm avg}(\theta)=\Bbb E_{\pi_{\theta}}[\sum^T_{t=0}(\sum^T_{t^{\prime}=t}\gamma^{t^{\prime}-t}r_{t^{\prime}})\nabla_{\theta}\log\pi_{\theta}(s_t,a_t)]
∇θJavg(θ)=Eπθ[t=0∑T(t′=t∑Tγt′−trt′)∇θlogπθ(st,at)]
其中,
T
T
T是和环境交互的最大步数。
5.2 REINFORCE算法
算法流程:
- 初始化策略参数 θ \theta θ
-
f
o
r
for
for序列
e
=
1
→
E
d
o
e=1\rarr E\space do
e=1→E do:
- 用当前策略 π θ \pi_{\theta} πθ采样轨迹 { s 1 , a 1 , r 1 , s 2 , a 2 , r 2 , … , s T , a T , r T } \{s_1,a_1,r_1,s_2,a_2,r_2,\dots,s_T,a_T,r_T\} {s1,a1,r1,s2,a2,r2,…,sT,aT,rT}
- 计算当前轨迹每个时刻 t t t往后的回报 ∑ t ′ = t T γ t ′ − t r t ′ \sum^T_{t^{\prime}=t}\gamma^{t^{\prime}-t}r_{t^{\prime}} ∑t′=tTγt′−trt′,记为 ψ t \psi_t ψt
- 对 θ \theta θ进行更新: θ = θ + α ∑ t T ψ t ∇ θ log π θ ( a t ∣ s t ) \theta=\theta+\alpha\sum^T_t\psi_t\nabla_{\theta}\log\pi_{\theta}(a_t|s_t) θ=θ+α∑tTψt∇θlogπθ(at∣st)
代码实现
定义策略网络
class PolicyNet(torch.nn.Module):
def __init__(self, state_dim, hidden_dim, action_dim):
super(PolicyNet, self).__init__()
self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
self.fc2 = torch.nn.Linear(hidden_dim, action_dim)
def forward(self, x):
x = F.relu(self.fc1(x))
return F.softmax(self.fc2(x), dim=1)
REINFORCE算法
class REINFORCE:
def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma, device):
self.policy_net = PolicyNet(state_dim, hidden_dim, action_dim).to(device)
self.optimizer = torch.optim.Adam(self.policy_net.parameters(), learning_rate)
self.gamma = gamma
self.device = device
def take_action(self, state): # 根据动作概率分布随机采样
state = torch.tensor([state], dtype=torch.float).to(self.device)
probs = self.policy_net(state)
action_dist = torch.distributions.Categorical(probs)
action = action_dist.sample()
return action.item()
def update(self, transition_dict):
reward_list = transition_dict['rewards']
state_list = transition_dict['states']
action_list = transition_dict['actions']
G = 0
self.optimizer.zero_grad()
for i in reversed(range(len(reward_list))): # 从最后一步开始
reward = reward_list[i]
state = torch.tensor([state_list[i]], dtype=torch.float).to(self.device)
action = torch.tensor([action_list[i]]).view(-1, 1).to(self.device)
log_prob = torch.log(self.policy_net(state).gather(1, action))
G = self.gamma * G + reward
loss = -log_prob * G # 每一步损失函数
loss.backward()
self.optimizer.step() # 梯度下降
实现
learning_rate = 1e-3
num_episodes = 1000
hidden_dim = 128
gamma = 0.98
device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
env_name = "CartPole-v0"
env = gym.make(env_name)
env.seed(0)
torch.manual_seed(0)
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = REINFORCE(state_dim, hidden_dim, action_dim, learning_rate, gamma, device)
return_list = []
for i in range(10):
with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:
for i_episode in range(int(num_episodes / 10)):
episode_return = 0
transition_dict = {
'states': [],
'actions': [],
'next_states': [],
'rewards': [],
'dones': []
}
state = env.reset()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ = env.step(action)
transition_dict['states'].append(state)
transition_dict['actions'].append(action)
transition_dict['next_states'].append(next_state)
transition_dict['rewards'].append(reward)
transition_dict['dones'].append(done)
state = next_state
episode_return += reward
return_list.append(episode_return)
agent.update(transition_dict)
if (i_episode + 1) % 10 == 0:
pbar.set_postfix({
'episode':
'%d' % (num_episodes / 10 * i + i_episode + 1),
'return':
'%.3f' % np.mean(return_list[-10:])
})
pbar.update(1)
Iteration 0: 100%|██████████| 100/100 [00:04<00:00, 24.69it/s, episode=100, return=42.400]
Iteration 1: 100%|██████████| 100/100 [00:07<00:00, 13.01it/s, episode=200, return=47.700]
Iteration 2: 100%|██████████| 100/100 [00:14<00:00, 7.04it/s, episode=300, return=129.700]
Iteration 3: 100%|██████████| 100/100 [00:22<00:00, 4.50it/s, episode=400, return=167.600]
Iteration 4: 100%|██████████| 100/100 [00:25<00:00, 3.91it/s, episode=500, return=197.200]
Iteration 5: 100%|██████████| 100/100 [00:26<00:00, 3.83it/s, episode=600, return=180.100]
Iteration 6: 100%|██████████| 100/100 [00:26<00:00, 3.84it/s, episode=700, return=194.400]
Iteration 7: 100%|██████████| 100/100 [00:26<00:00, 3.76it/s, episode=800, return=186.600]
Iteration 8: 100%|██████████| 100/100 [00:22<00:00, 4.44it/s, episode=900, return=151.500]
Iteration 9: 100%|██████████| 100/100 [00:26<00:00, 3.78it/s, episode=1000, return=192.800]
def moving_average(a, window_size):
cumulative_sum = np.cumsum(np.insert(a, 0, 0))
middle = (cumulative_sum[window_size:] - cumulative_sum[:-window_size]) / window_size
r = np.arange(1, window_size - 1, 2)
begin = np.cumsum(a[:window_size - 1])[::2] / r
end = (np.cumsum(a[:-window_size:-1])[::2] / r)[::-1]
return np.concatenate((begin, middle, end))
mv_return = moving_average(return_list, 9)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('REINFORCE on {}'.format(env_name))
plt.show()