提示:转载请注明出处,若文章无意侵犯到您的合法权益,请及时与作者联系。
前言
1.1 强化学习的问题定义
1.2 方格世界的经典MDP问题
1.3 环境与Agent对象
二、策略迭代分析
2.1 策略评估
2.2 策略改进
2.3 策略迭代
2.4 值迭代
在之前的学习中我们学习了有限MDP问题的基于模型的DP解法,也就是策略迭代法,但是很多东西不与实践代码相结合,总会觉得没有成就感,那么我们就将之前介绍的算法通过举例并进行代码分析,注意本文并不会给出全部的可实现的代码,只会根据需要截取相应的部分进行讲解,但是本文会将自己参考的原文链接及代码挂出,供有需要的同学学习。
本文参考的资料文章主要来源:强化学习基础篇: 策略迭代 (Policy Iteration)
一、典型的方格世界问题说明
一个Agent与环境不断进行交互,在每一个时间步长t中,环境提供当前状态给Agent,Agent根据这个当前状态做出决策,这时Agent可能存在多个动作可选,Agent按照一定的策略采取动作,之后会得到环境反馈的立即回报,环境此时按照状态转移的概率分布进入了下一个时刻的后继状态,这个后继状态可能是自身也可能是其他状态。
在以上过程中注意存在2个概率分布:
一个是当前状态下要采取哪个动作,即以什么样的策略来决策,随机选择还是统一固定的,本质是一个概率分布问题。
一个是转移到的后继状态将会是什么,即当前状态可以转移到哪些状态,转移到每个状态的概率是多少,本质上就是状态转移的概率分布。
注意:有时还会提到初始状态分布,即初始时刻选择哪一个初始状态的问题。
接下来我们给出一个标准的马尔科夫决策过程(MDP)问题来进行研究。假设我们有一个3 x 3的方格世界(GridWorld)作为环境,
存在一个单元格叫做超级玛丽,它每个时间步长只可以往上、下、左、右四个方向移动,在方格世界中存在一个固定的单元格是宝藏,当超级玛丽找到宝藏则游戏结束,我们的目标是让超级玛丽以最快的速度找到宝藏。
我们将上面的描述进行马尔科夫决策过程(MDP)的描述:
通过分析以上强化学习的基本定义,我们可以归纳出环境和Agent所具备的基本特点:
环境的特点:
Agent的特点:
在本文中我们将会把主要精力集中在研究Agent对象上,特别是它的策略迭代法,其他更多深入研究可参考来源资料中的讲解或者我的实践阶段的代码分析。
直接给出之前的策略评估算法流程:
上述评估算法输入一个策略policy,输出一个存储状态价值的数组v,算法中包含大循环来保证每个状态价值可以不断迭代到响应的精确度,在大循环里面存在一个遍历所有状态的循环、一个遍历所有动作的循环和一个遍历所有后继状态的循环,一共是一个四层嵌套循环结构。
下面是其Python代码实现,这个方法是Agent类中的一个方法,env是其内部的环境类对象的映射。
def policy_evaluation(self, policy):
"""策略评估"""
V = np.zeros(self.env.nS) # 使用一个含有9个0.的数组来存储9个状态的价值
THETA = 0.0001 # 策略评估的精度
delta = float("inf") # 当前迭代的差距量,初始化为正无穷
while delta > THETA:
delta = 0 # 当前迭代的差距量初始化为0
for s in range(self.env.nS):# 遍历环境给出的所有的状态(即0-8)
expected_value = 0 #初始化当前状态价值为0
for action, action_prob in enumerate(policy[s]): # 获取当前状态可采取的所有动作(上下左右)及其采取对应动作的概率
# 遍历采取动作后的所有可能转移的后继状态
# 在当前状态采取当前动作后得到环境反馈的后继状态转移概率,后继状态,立即回报,是否终止
for prob, next_state, reward, done in self.env.P[s][action]:
expected_value += action_prob * prob * (reward + DISCOUNT_FACTOR * V[next_state])#贝尔曼期望方程
delta = max(delta, np.abs(V[s] - expected_value)) # 本次迭代后价值变化的差异
V[s] = expected_value #保存当前状态的价值
return V
以上需要特别说明的是输入的policy是一个9行4列的状态*动作的矩阵,这个矩阵初始状态中第2列都为1,其余都为0,表示向下的动作概率为1,其余动作的概率为0。
另外其中调用的self.env.P[s][a]是一个二维列表,它可以返回指定状态和动作对应的所有后继状态及其被选中的概率、立即回报和是否终结等信息,这个功能由环境类实现,我们这里不做分析。
策略改进这里就是根据状态价值计算出当前动作价值最大的动作:
def next_best_action(self, s, V):
"""下一个最好的动作"""
action_values = np.zeros(env.nA)# 动作价值
for a in range(env.nA):
for prob, next_state, reward, done in self.env.P[s][a]:
action_values[a] += prob * (reward + DISCOUNT_FACTOR * V[next_state])
return np.argmax(action_values), np.max(action_values) #返回最大动作状态的动作索引及其动作价值
下面直接给出策略迭代的算法流程:
相应的代码实现如下:
def optimize(self):
"""策略优化"""
policy = np.tile(np.eye(self.env.nA)[1], (self.env.nS, 1)) #9行4列的矩阵,状态*动作,向下的动作都为1,其余为0
is_stable = False
round_num = 0 #循环次数
while not is_stable:
is_stable = True
print("\nRound Number:" + str(round_num))
round_num += 1
print("Current Policy")
print(np.reshape([env.get_action_name(entry) for entry in [np.argmax(policy[s]) for s in range(self.env.nS)]], self.env.shape))
# 进行策略评估
V = self.policy_evaluation(policy)
print("Expected Value accoridng to Policy Evaluation")
print(np.reshape(V, self.env.shape))
# 遍历所有的状态
for s in range(self.env.nS):
action_by_policy = np.argmax(policy[s]) #获取动作概率最大的动作序号
best_action, best_action_value = self.next_best_action(s, V) # 获取当前状态中动作价值最大的动作序号及其动作价值
# print("\nstate=" + str(s) + " action=" + str(best_action))
policy[s] = np.eye(self.env.nA)[best_action] #替换原先的动作选取策略
if action_by_policy != best_action: #如果当前动作不是最优的就继续迭代
is_stable = False
policy = [np.argmax(policy[s]) for s in range(self.env.nS)] #返回最优策略:含有9个表示最优动作序号的数组
return policy
在上述代码中可以看到,在每一轮策略迭代中都分为策略评估和策略改进两部分。
策略评估计算出每个状态的价值后,策略改进部分再遍历所有的状态,计算每个状态下,动作价值最好的动作,然后按照贪婪的策略,将当前最好的策略替换掉原先的动作策略,指导所有状态的动作策略不再变化时,策略迭代就结束了。
下面直接给出值迭代的算法流程:
相应的代码实现如下:
def optimize(self):
THETA = 0.0001
delta = float("inf")
round_num = 0
while delta > THETA:
delta = 0
print("\nValue Iteration: Round " + str(round_num))
print(np.reshape(self.V, env.shape))
for s in range(env.nS):
best_action, best_action_value = self.next_best_action(s, self.V)
delta = max(delta, np.abs(best_action_value - self.V[s]))
self.V[s] = best_action_value
round_num += 1
policy = np.zeros(env.nS)
for s in range(env.nS):
best_action, best_action_value = self.next_best_action(s, self.V)
policy[s] = best_action
return policy
上述代码中直接使用当前状态下的动作价值最大的动作来代替状态价值,这样子的好处就是可以减少遍历状态空间的次数,但是相应的收敛速度就会变慢。