在很多时候,我们无法得知模型信息,比如前几节的蛇棋中,我们不知道棋盘梯子的信息和骰子的信息,用数学化的方法来说,就是我们用于决策的智能体不知道状态转移概率 P P P。和有模型问题使用 V V V计算的方法相比,我们需要保存并更新 Q Q Q,很容易波动不收敛,因此需要的迭代次数一般需要更多。
首先介绍蒙特卡洛法,这是一种策略迭代方法。
当我们无法得知 P P P的时候,一个直观的想法就是使用大量的采样去进行估计。
为了方便,我们把策略迭代算法中策略评估的公式进行一下拆解:
Q = P ( R + γ V ) Q=P(R+\gamma V) Q=P(R+γV)
V = Π Q V=\Pi Q V=ΠQ
Q Q Q无法使用迭代的方法计算得到,因此使用蒙特卡洛法,采样得到大量的样本序列:
s 0 , a 0 , r 0 , s 1 1 , a 1 1 , r 1 1 . . . {s_0,a_0,r_0,s^1_1,a^1_1,r^1_1...} s0,a0,r0,s11,a11,r11...
s 0 , a 0 , r 0 , s 1 2 , a 1 2 , r 1 2 . . . {s_0,a_0,r_0,s^2_1,a^2_1,r^2_1...} s0,a0,r0,s12,a12,r12...
s 0 , a 0 , r 0 , s 1 3 , a 1 3 , r 1 3 . . . {s_0,a_0,r_0,s^3_1,a^3_1,r^3_1...} s0,a0,r0,s13,a13,r13...
…
s 0 , a 0 , r 0 , s 1 N , a 1 N , r 1 N . . . {s_0,a_0,r_0,s^N_1,a^N_1,r^N_1...} s0,a0,r0,s1N,a1N,r1N...
状态行动集( s 0 s_0 s0, a 0 a_0 a0)的值函数可以近似为: Q = 1 N ( r 0 + γ r 1 1 + γ 2 r 2 1 . . . + r 0 + γ r 1 2 + γ 2 r 2 2 . . . + r 0 + γ r 1 N + γ 2 r 2 N + . . . ) Q=\frac{1}{N}(r_0+\gamma r^1_1+\gamma ^2r^1_2...+r_0+\gamma r^2_1+\gamma^2 r^2_2...+r_0+\gamma r^N_1+\gamma^2 r^N_2+...) Q=N1(r0+γr11+γ2r21...+r0+γr12+γ2r22...+r0+γr1N+γ2r2N+...)。再根据策略 Π \Pi Π就可以计算出 V V V了。
具体计算时,我们是使用更新的方法计算 Q Q Q,而不是等遍历完再取均值。令 q i = r 0 + γ r 1 i + γ 2 r 2 i . . . q^i = r_0+\gamma r^i_1+\gamma ^2r^i_2... qi=r0+γr1i+γ2r2i...
则更新公式为: Q i = Q i − 1 + 1 i ( q i − Q i − 1 ) Q^i=Q^{i-1}+\frac{1}{i}(q^i-Q^{i-1}) Qi=Qi−1+i1(qi−Qi−1)
可以证明: Q N = 1 N ( q 1 + . . . + q N ) Q^N=\frac{1}{N}(q^1+...+q^N) QN=N1(q1+...+qN)
更新公式和梯度下降法非常相像。
在使用蒙特卡洛法采样的时候,我们有很多前进的方法。一方面,我们要保证行动 a a a的最优性,这样才能准确估计出 v v v;另一方面,我们要保证行动的随机性,这样我们才能访问到更多状态,避免陷入局部最优。
一种常见的策略是在采样的时候加入一定的随机性,比如 ϵ \epsilon ϵ-greedy策略,即以一定的概率采取随机行动,其他情况采取最优策略行动。
加入我们在行动的过程中,又碰到了 ( s 0 , a 0 ) (s_0,a_0) (s0,a0)怎么办?一种方法是将其加入 Q Q Q的计算之中,称为every visit方法;另一种方法是不加入,称为first visit方法。
在有限状态的序贯决策问题中,不同时间段访问同样状态的 V V V值可能会差很大。这种情况下建议使用first visit方法,减少 q q q值的方差,加快收敛速度。
class MonteCarlo(object):
def __init__(self, epsilon=0.0):
self.epsilon = epsilon
# 一轮蒙特卡洛采样
def monte_carlo_eval(self, agent, env):
state = env.reset()
episode = []
while True:
ac = agent.play(state, self.epsilon)
next_state, reward, terminate, _ = env.step(ac)
episode.append((state, ac, reward))
state = next_state
if terminate:
break
value = []
return_val = 0
# 逆向计算q值
for item in reversed(episode):
return_val = return_val * agent.gamma + item[2]
value.append((item[0], item[1], return_val))
# 进行更新
for item in reversed(value):
if agent.value_n[item[0]][item[1]] == 0: # 这里是first visit方法;every visit把这句判断删除即可
agent.value_n[item[0]][item[1]] += 1 # 访问次数
agent.value_q[item[0]][item[1]] += (item[2] - \
agent.value_q[item[0]][item[1]]) / \
agent.value_n[item[0]][item[1]]
def policy_improve(self, agent):
new_policy = np.zeros_like(agent.pi)
for i in range(1, agent.s_len):
new_policy[i] = np.argmax(agent.value_q[i,:])
if np.all(np.equal(new_policy, agent.pi)):
return False
else:
agent.pi = new_policy
return True
# monte carlo法
def monte_carlo_opt(self, agent, env):
for i in range(10): # 进行10轮迭代
for j in range(100): # 每次采样100轮
self.monte_carlo_eval(agent, env)
self.policy_improve(agent)
对比测试结果如下:
ladders info:
{14: 65, 18: 48, 41: 68, 30: 45, 68: 41, 48: 18, 49: 89, 71: 79, 65: 14, 70: 18, 73: 41, 45: 30, 89: 49, 79: 71}
Iter 3 rounds converge
Timer PolicyIter COST:0.2772650718688965
return_pi=64
Timer MonteCarlo COST:0.22463107109069824
return_pi=30
MonteCarlo法的效果一般,主要是因为前几轮迭代的值方差很大,而我们对迭代次数进行了限制,因此较难收敛。下一节将介绍可以减小方差的时序差分法。