机器学习之Grid World的Monte Carlo算法解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tomatomas/article/details/77170101

同样是来自于Github开源项目的代码,这次尝试分析其Grid World的Monte Carlo算法。
Github地址:https://github.com/rlcode/reinforcement-learning/tree/master/1-grid-world/3-monte-carlo

Grid World

Grid World是该开源项目用于检验算法的一个测试环境,是由n*n个方块组成的棋盘,棋盘上放置红色方块为起点,绿色菱形为障碍,蓝色圆形为终点,红色方块可被控制上下左右移动。算法的目标就是将红色方块以最短的距离(不碰到障碍物)达到蓝色圆形处。其示意图如下:
这里写图片描述

Monte Carlo

以下引用自百度百科:

蒙特卡罗方法于20世纪40年代美国在第二次世界大战中研制原子弹的“曼哈顿计划”计划的成员S.M.乌拉姆和J.冯·诺伊曼首先提出。数学家冯·诺伊曼用驰名世界的赌城—摩纳哥的Monte Carlo—来命名这种方法,为它蒙上了一层神秘色彩。在这之前,蒙特卡罗方法就已经存在。1777年,法国数学家布丰(Georges Louis Leclere de Buffon,1707—1788)提出用投针实验的方法求圆周率π。这被认为是蒙特卡罗方法的起源。
蒙特·卡罗方法(Monte Carlo method),也称统计模拟方法,是二十世纪四十年代中期由于科学技术的发展和电子计算机的发明,而被提出的一种以概率统计理论为指导的一类非常重要的数值计算方法。是指使用随机数(或更常见的伪随机数)来解决很多计算问题的方法。与它对应的是确定性算法。蒙特·卡罗方法在金融工程学,宏观经济学,计算物理学(如粒子输运计算、量子热力学计算、空气动力学计算)等领域应用广泛。

算法实现

本例子的实现代码由两个文件组成:environment.py和mc_agent.py。其中environment.py主要是进行图形绘制及模拟环境反馈,由于此处我们重点分析的是Monte Carlo算法,所以我们主要还是看mc_agent.py文件内的代码。

入口代码

if __name__ == "__main__":
    env = Env()
    agent = MCAgent(actions=list(range(env.n_actions)))

    for episode in range(1000):
        state = env.reset()
        action = agent.get_action(state)

        while True:
            env.render()

            # forward to next state. reward is number and done is boolean
            next_state, reward, done = env.step(action)
            agent.save_sample(next_state, reward, done)

            # get next action
            action = agent.get_action(next_state)

            # at the end of each episode, update the q function table
            if done:
                print("episode : ", episode)
                agent.update()
                agent.samples.clear()
                break

以上即是入口代码,我们可以看到它有两层循环,内层无限循环当done变量为true的时候退出循环,通过environment.py的代码我们可以知道当红色方块到达蓝色圆形时down返回true,结束一个游戏循环。外层循环1000次,即总共会进行1000次游戏训练。
代码最开始创建了两个对象env和agent,分别代表游戏环境和红色方块。然后进入第一层循环,先是获取了一个action:

action = agent.get_action(state)

action是怎么来的呢?我们看看get_action的代码:

# get action for the state according to the q function table
    # agent pick action of epsilon-greedy policy
    def get_action(self, state):
        if np.random.rand() < self.epsilon:
            # take random action
            action = np.random.choice(self.actions)
        else:
            # take action according to the q function table
            next_state = self.possible_next_state(state)
            action = self.arg_max(next_state)
        return int(action)

get_action有一定的几率返回一个随机的action,这个几率是epsilon,在这里epsilon的值一直是0.1。然后就很明显是选取一个可能受益最大的action了。我们跟进看看possible_next_state及arg_max函数:

# compute arg_max if multiple candidates exit, pick one randomly
    @staticmethod
    def arg_max(next_state):
        max_index_list = []
        max_value = next_state[0]
        for index, value in enumerate(next_state):
            if value > max_value:
                max_index_list.clear()
                max_value = value
                max_index_list.append(index)
            elif value == max_value:
                max_index_list.append(index)
        return random.choice(max_index_list)

    # get the possible next states
    def possible_next_state(self, state):
        col, row = state
        next_state = [0.0] * 4

        if row != 0:
            next_state[0] = self.value_table[str([col, row - 1])]
        else:
            next_state[0] = self.value_table[str(state)]
        if row != self.height - 1:
            next_state[1] = self.value_table[str([col, row + 1])]
        else:
            next_state[1] = self.value_table[str(state)]
        if col != 0:
            next_state[2] = self.value_table[str([col - 1, row])]
        else:
            next_state[2] = self.value_table[str(state)]
        if col != self.width - 1:
            next_state[3] = self.value_table[str([col + 1, row])]
        else:
            next_state[3] = self.value_table[str(state)]

        return next_state

很明显,possible_next_state函数将红色方块当前位置上下左右位置的value_table值作为一个数组返回(考虑边界),arg_max函数则在possible_next_state的返回值中挑选最大值的index返回,这个值直接转化为action左右get_action方法的返回值。这就像我们平时生活一样,最正确的做法不是把所有时间都用在学习上,而是抽出一定的时间去瞎混,那我们下一步是不是该放松下了,嘿嘿~别傻了,我们继续。前面说到value_table,代码中其初始赋值是defaultdict(float),即所有引用都返回0.0。故在value_table的值发生改变前,get_action获得的值都是随机的。我们又回到入口代码处:

next_state, reward, done = env.step(action)

向env发出一个action之后得到next_state、reward、done3个变量,然后调用agent的save_sample函数,仅仅是将3个变量存储起来:

def save_sample(self, state, reward, done):
        self.samples.append([state, reward, done])

然后获取下一个action,如此循环下去直到done为true,既红色方块到达蓝色圆形处。然后agent的update方法被调用:

    # for every episode, agent updates q function of visited states
    def update(self):
        G_t = 0
        visit_state = []
        for reward in reversed(self.samples):
            state = str(reward[0])
            if state not in visit_state:
                visit_state.append(state)
                G_t = self.discount_factor * (reward[1] + G_t)
                value = self.value_table[state]
                self.value_table[state] = (value +
                                           self.learning_rate * (G_t - value))

update方法更新了value_table的内容,前面我们说到,影响get_action方法结果的主要因素就是value_table的内容,value_table决定了红色方块的行走策略,由此可见update方法是整个实现的核心。其算法还是比较好看懂的,但意思并不是很好理解。我觉得其核心内容是衰减因子discount_factor和学习率learning_rate,通过这两个参数和其他变量的计算去逼近正确的概率,从而逼近最优解。

总结

本人才疏学浅,也是刚接触机器学习算法,希望通过分析总结来增长技能,如果有写得不对不好的地方,望不吝指正,谢谢!

猜你喜欢

转载自blog.csdn.net/tomatomas/article/details/77170101
今日推荐