目录
1. 前言
2. 数学原理
3. 实现
3.1 Planner类
3.2 ValueIterationPlanner类
4. 运行结果及分析
1. 前言
在强化学习中,根据是否依赖于(环境的)模型,可以分为有模型(model-based)学习和无模型(model-free)学习。根据行动的决策基准,可以分为基于价值的学习(value-based)和基于策略学习(policy-based)。
在基于价值的学习中,根据状态值函数(state-value function)值或者动作值函数的值,进行行动决策。总是选择从当前状态出发可能到达的下一个状态中价值最大的那个状态所对应的行动。
这里的关键在于状态(或者动作)值函数的估计。在基于模型(model-based)的方法中,如果已知迁移函数(T represent transit or transfer)和奖励函数(两者可以合并为Pr(s',r|s,a))的话,可以基于动态规划的方法进行状态值函数的求解。对状态值函数的精确求解有比较严格的条件限制,更有实际应用意义的是对值函数进行近似求解,也称价值近似(value approximation)。价值近似可以通过价值迭代(value iteration)的方式进行。在上一篇(强化学习笔记:基于价值的学习方法之价值估计(python实现))中讨论了基于贝尔曼方程的状态值函数的基本求解方法(求严格解)。但是,这种方法计算量巨大,运行时间随递归搜索深度指数级上升。
本文(以及接下来的文章)描述价值估计或者近似,通常称为价值迭代(value iteration)的原理和实现。
2. 数学原理
首先,如上一篇所述,在基于价值的学习方法中,价值函数可以简化如下:
(1)
迭代计算的关键要点是:
(a) 对所有各状态值可以随机初始化(而不像上一篇中那样总是需要从最后的确定性的终局状态开始回溯)
(b) 重复执行式(1)所代表的计算,每一轮遍历所有的状态进行更新计算,称为一个iteration。
(c) 每一iteration的计算是基于上一次iteration后所得到的结果
价值迭代的数学表达式如下所示:
(2)
式(2)与式(1)相比仅有细微的区别,等式左边和右边的V分别追加了下表{i} 和 {i+1},用于表示iteration序号。正是这小小的一点修改带来了魔法式的效果。
3. 实现
(其实是参考文献【2】的代码的摘抄学习。。。但是基于我的理解追加了解说)
3.1 Planner类
class Planner():
def __init__(self, env):
self.env = env
self.log = []
self.V_grid = []
def initialize(self):
self.env.reset()
self.log = []
def plan(self, gamma=0.9, threshold=0.0001):
raise Exception("Planner have to implements plan method.")
def transitions_at(self, state, action):
transition_probs = self.env.transit_func(state, action)
for next_state in transition_probs:
prob = transition_probs[next_state]
reward, _ = self.env.reward_func(next_state)
yield prob, next_state, reward
def dict_to_grid(self, state_reward_dict):
grid = []
for i in range(self.env.row_length):
row = [0] * self.env.column_length
grid.append(row)
for s in state_reward_dict:
grid[s.row][s.column] = state_reward_dict[s]
return grid
def print_value_grid(self):
for i in range(len(self.V_grid)):
for j in range(len(self.V_grid[0])):
print('{0:6.3f}'.format(self.V_grid[i][j]), end=' ' )
print('')
如上所示,首先实现了一个Planner类(但是这个类名以及plan()方法名都感觉并不是很合适)作为基类,后面价值迭代和策略迭代都将继承该类进行实现。
plan()方法是价值迭代和策略迭代都需要实现的,这里只是给出一个接口定义,具体实现交给子类去实现。如果子类没有实现的话,会报告错误。这里涉及面向对象编程中的覆盖(override)的概念,点到为止。
transitions_at()方法实现迁移函数。为了节约内存使用,采用yield将其实现为一个生成器,这样在使用时是按需生成,而不是一次性地将所有的状态转移数据全部生成,对于大型问题,状态数非常之多时这个非常重要。
dict_to_grid()和print_value_grid()只是两个helper函数,用于数据格式转换和打印,不多解释。
3.2 ValueIterationPlanner类
class ValueIterationPlanner(Planner):
def __init__(self, env):
super().__init__(env)
def plan(self, gamma=0.9, threshold=0.0001):
self.initialize()
actions = self.env.actions
V = {}
for s in self.env.states:
# Initialize each state's expected reward.
V[s] = 0
while True:
delta = 0
self.log.append(self.dict_to_grid(V))
for s in V:
if not self.env.can_action_at(s):
continue
expected_rewards = []
for a in actions:
r = 0
for prob, next_state, reward in self.transitions_at(s, a):
r += prob * (reward + gamma * V[next_state])
expected_rewards.append(r)
max_reward = max(expected_rewards)
delta = max(delta, abs(max_reward - V[s]))
V[s] = max_reward
if delta < threshold:
break
self.V_grid = self.dict_to_grid(V)
其中plan()方法实现了上一节式(2)所表示的迭代运行过程。
变量V一开始初始化为全0。
每次迭代对所有的状态值更新后,与上一轮结束时的值进行比较求所有各状态值得变化的绝对值。如果该变化绝对值小于预设门限threshold则认为已经收敛,停止迭代。
4. 运行结果及分析
测试代码如下所示:
if __name__ == "__main__":
# Create grid environment
grid = [
[0, 0, 0, 1],
[0, 9, 0, -1],
[0, 0, 0, 0]
]
env = Environment(grid)
valueIterPlanner = ValueIterationPlanner(env)
valueIterPlanner.plan(0.9,0.001)
valueIterPlanner.print_value_grid()
运算结果如下:
0.610 0.766 0.928 0.000
0.487 0.000 0.585 0.000
0.374 0.327 0.428 0.189
首先,运行时间相比上一篇的基本实现几乎可以忽略不计。
其次,上一篇的基本实现中,递归深度为7和8时grid[2][0]的结果分别为0.339和0.488,与上面的0.374并不吻合,这是为什么呢?
最后,如上所示,grid[0][3], grid[1][1], 和grid[1][3]的值均为0,这是符合定义的。因为grid[1][1]不能进入,另外两个是导致回合结束的点,它们的未来预期奖励都应该为0。但是,这里牵出了一个问题。比如说,当前agent处于grid[0][2]的话,由于grid[0][3]就是reward cell,所以最优行动显然是应该向右移动。但是根据以上结果,以及基于价值的方法中的行动决策机制,agent应该是要向左移动!这个困惑了我很久。。。这里似乎存在状态值的定义与基于价值的方法的行动决策机制之间的矛盾。其本质问题是什么呢?
完整代码参见:reinforcement-learning/value_eval at main · chenxy3791/reinforcement-learning (github.com)
参考文献:
【1】Sutton, et, al, Introduction to reinforcement learning (2020)
【2】久保隆宏著,用Python动手学习强化学习