Python_强化学习_Q-Learning算法_二维迷宫游戏
发布日期:2021-06-29 19:49:33 浏览次数:4 分类:技术文章

本文共 10154 字,大约阅读时间需要 33 分钟。

1.问题描述

在该项目中,你将使用强化学习算法(本文使用的Q-Learning),实现一个自动走迷宫的机器人。

迷宫

用AI玩二维迷宫

如B站以上视频所示,机器人初始位置在地图左上角。在我们的迷宫中,有墙壁(黑色方块)、元宝(黄色圆块)及终点(绿色方块)。机器人要尽可能避开陷阱,并且拿到元宝后,以最少的步子到达终点。

机器人可执行的动作包括:向左走 L 、向右走 R 、向上走 U 、向下走 D 。

2.强化学习:算法理解

强化学习作为机器学习算法的一种,其模式也是让智能体在“训练”中学到“经验”,以实现给定的任务。但不同于监督学习与非监督学习,在强化学习的框架中,我们更侧重通过智能体与环境的交互来学习。通常在监督学习和非监督学习任务中,智能体往往需要通过给定的训练集,辅之以既定的训练目标(如最小化损失函数),通过给定的学习算法来实现这一目标。然而在强化学习中,智能体则是通过其与环境交互得到的奖励进行学习。这个环境可以是虚拟的(如虚拟的迷宫),也可以是真实的(自动驾驶汽车在真实道路上收集数据)。

在强化学习中有五个核心组成部分,它们分别是:环境(Environment)、智能体(Agent)、状态(State)、动作(Action)和奖励(Reward)。

3 定义动作

接下来,定义机器人是如何选择行动的。这里需要引入增强学习中epsilon greedy的概念。因为在初始阶段, 随机的探索环境, 往往比固定的行为模式要好, 所以这也是累积经验的阶段, 我们希望探索者不会那么贪婪(greedy)。说说我的理解,上图迷宫中,当机器人第一次找到黄金后,如果不控制他的贪婪程度,那么很可能他每次都会直奔去,加入地图中还有第二个黄金,则很有可能被忽略(即缺少对地图的完全搜索)。

  所以epsilon就是用来控制贪婪程度的值。epsilon可以随着探索时间不断提升(越来越贪婪), 不过在这个例子中, 我们就固定成 epsilon = 0.7, 70% 的时间是选择最优策略, 30% 的时间来探索。

完整Python代码如下:

import randomimport timeimport tkinter as tkimport pandas as pd'''此文件实现Q_learning 强化学习算法解决二维迷宫问题一个小球在一个迷宫中,四周都是陷阱,设置一个入口,设置一个出口。小球怎么走到出口,迷宫中间会陷阱。打印出路线。以下提供了二维迷宫问题的一个比较通用的模板,拿到后需要修改的地方非常少。对于任意的二维迷宫的 class Agent,只需修改三个地方:MAZE_R, MAZE_R, rewards,其他的不要动!如下所示: 6*6 的迷宫:-------------------------------------------| 入口 | 陷阱 |      |      |      |      |-------------------------------------------|      | 陷阱 |      |      | 陷阱 |      |-------------------------------------------|      | 陷阱 |      | 陷阱 |      |      |-------------------------------------------|      | 陷阱 |      | 陷阱 |      |      |-------------------------------------------|      | 陷阱 |      | 陷阱 | 元宝  |      |-------------------------------------------|      |      |     | 陷阱 |      | 出口 |-------------------------------------------'''class Maze(tk.Tk):    '''环境类(GUI),主要用于画迷宫和小球'''    UNIT = 40  # 像素    MAZE_R = 6  # grid row    MAZE_C = 6  # grid column    def __init__(self):        super().__init__()        self.title('迷宫')        h = self.MAZE_R * self.UNIT        w = self.MAZE_C * self.UNIT        self.geometry('{0}x{1}'.format(h, w))  # 窗口大小        self.canvas = tk.Canvas(self, bg='white', height=h, width=w)        # 画网格        for c in range(1, self.MAZE_C):            self.canvas.create_line(c * self.UNIT, 0, c * self.UNIT, h)        for r in range(1, self.MAZE_R):            self.canvas.create_line(0, r * self.UNIT, w, r * self.UNIT)        # 画入口        self._draw_rect(0, 0, 'blue')        # 画陷阱        self._draw_rect(1, 0, 'black')  # 在1列、0行处,下同        self._draw_rect(1, 1, 'black')        self._draw_rect(1, 2, 'black')        self._draw_rect(1, 3, 'black')        self._draw_rect(1, 4, 'black')        self._draw_rect(3, 2, 'black')        self._draw_rect(3, 3, 'black')        self._draw_rect(3, 4, 'black')        self._draw_rect(3, 5, 'black')        self._draw_rect(4, 1, 'black')        # 画奖励        self._draw_rect(4, 4, 'yellow')        # 画出口        self._draw_rect(5, 5, 'green')        # 画玩家(保存!!)        self.rect = self._draw_oval(0, 0, 'red')        self.canvas.pack()  # 显示画作!    def _draw_rect(self, x, y, color):        '''画矩形,  x,y表示横,竖第几个格子'''        padding = 5  # 内边距5px,参见CSS        coor = [self.UNIT * x + padding, self.UNIT * y + padding, self.UNIT * (x + 1) - padding,                self.UNIT * (y + 1) - padding]        return self.canvas.create_rectangle(*coor, fill=color)    def _draw_oval(self, x, y, color):        '''画矩形,  x,y表示横,竖第几个格子'''        padding = 6  # 内边距5px,参见CSS        coor = [self.UNIT * x + padding, self.UNIT * y + padding, self.UNIT * (x + 1) - padding,                self.UNIT * (y + 1) - padding]        return self.canvas.create_oval(*coor, fill=color)    def move_agent_to(self, state):        '''移动玩家到新位置,根据传入的状态'''        coor_old = self.canvas.coords(self.rect)  # 形如[5.0, 5.0, 35.0, 35.0](第一个格子左上、右下坐标)        x, y = state % 6, state // 6  # 横竖第几个格子        padding = 5  # 内边距5px,参见CSS        coor_new = [self.UNIT * x + padding, self.UNIT * y + padding, self.UNIT * (x + 1) - padding,                    self.UNIT * (y + 1) - padding]        dx_pixels, dy_pixels = coor_new[0] - coor_old[0], coor_new[1] - coor_old[1]  # 左上角顶点坐标之差        self.canvas.move(self.rect, dx_pixels, dy_pixels)        self.update()  # tkinter内置的update!class Agent(object):    '''个体类'''    MAZE_R = 6  # 迷宫行数    MAZE_C = 6  # 迷宫列数    def __init__(self, alpha=0.1, gamma=0.9):        '''初始化'''        self.states = range(self.MAZE_R * self.MAZE_C)  # 状态集。0~35 共36个状态        self.actions = list('udlr')  # 动作集。上下左右  4个动作 ↑↓←→        self.rewards = [0, -10, 0, 0, 0, 0,                        0, -10, 0, 0, -10, 0,                        0, -10, 0, -10, 0, 0,                        0, -10, 0, -10, 0, 0,                        0, -10, 0, -10, 2, 0,                        0, 0, 0, -10, 0, 10]  # 奖励集。出口奖励10,陷阱奖励-10,元宝奖励2        # 贝尔曼方程的两个参数,alpha = 0.1     # 学习率        # gamma = 0.9     # 奖励递减值        self.alpha = alpha        self.gamma = gamma        # Q表格环境        self.q_table = pd.DataFrame(data=[[0 for _ in self.actions] for _ in self.states],                                    index=self.states,                                    columns=self.actions)    def choose_action(self, state, epsilon=0.8):        '''选择相应的动作。根据当前状态,随机或贪婪,按照参数epsilon'''        # if (random.uniform(0,1) > epsilon) or ((self.q_table.ix[state] == 0).all()):  # 探索        if random.uniform(0, 1) > epsilon:  # 探索,这是非常有必要的,不探索会困在元宝里            action = random.choice(self.get_valid_actions(state))        else:            # action = self.q_table.loc[state].idxmax() # 利用 当有多个最大值时,会锁死第一个!            # 重大改进!然鹅与上面一样            # action = self.q_table.loc[state].filter(items=self.get_valid_actions(state)).idxmax()            # 从q表格中找到相应方向的反馈值            s = self.q_table.loc[state].filter(items=self.get_valid_actions(state))            # 最大的反馈值可能都是1,从里面随机选择一个!            action = random.choice(s[s == s.max()].index)        return action    def get_q_values(self, state):        '''取给定状态state的所有Q value'''        q_values = self.q_table.loc[state, self.get_valid_actions(state)]        return q_values    def update_q_value(self, state, action, next_state_reward, next_state_q_values):        '''更新Q value,根据贝尔曼方程'''        self.q_table.loc[state, action] += self.alpha * (                next_state_reward + self.gamma * next_state_q_values.max() - self.q_table.loc[state, action])    def get_valid_actions(self, state):        '''取当前状态下所有的合法动作'''        valid_actions = set(self.actions)        if state // self.MAZE_C == 0:  # 首行,则 不能向上            valid_actions -= {
'u'} elif state // self.MAZE_C == self.MAZE_R - 1: # 末行,则 不能向下 valid_actions -= {
'd'} if state % self.MAZE_C == 0: # 首列,则 不能向左 valid_actions -= {
'l'} elif state % self.MAZE_C == self.MAZE_C - 1: # 末列,则 不能向右 valid_actions -= {
'r'} return list(valid_actions) def get_next_state(self, state, action): '''对状态执行动作后,得到下一状态''' # u,d,l,r,n = -6,+6,-1,+1,0 if action == 'u' and state // self.MAZE_C != 0: # 除首行外,向上-MAZE_C next_state = state - self.MAZE_C elif action == 'd' and state // self.MAZE_C != self.MAZE_R - 1: # 除末行外,向下+MAZE_C next_state = state + self.MAZE_C elif action == 'l' and state % self.MAZE_C != 0: # 除首列外,向左-1 next_state = state - 1 elif action == 'r' and state % self.MAZE_C != self.MAZE_C - 1: # 除末列外,向右+1 next_state = state + 1 else: next_state = state return next_state def learn(self, env=None, episode=222, epsilon=0.8): '''q-learning算法''' print('Agent is learning...') for i in range(episode): # 将起始位置设为入口位置 current_state = self.states[0] env.move_agent_to(current_state) # 如果没有走到出口就一直执行 while current_state != self.states[-1]: current_action = self.choose_action(current_state, epsilon) # 按一定概率,随机或贪婪地选择 next_state = self.get_next_state(current_state, current_action) next_state_reward = self.rewards[next_state] next_state_q_values = self.get_q_values(next_state) self.update_q_value(current_state, current_action, next_state_reward, next_state_q_values) current_state = next_state env.move_agent_to(current_state) print(i) print('\n學習完畢!') def test_agent(self): '''测试agent是否能在36步之内走出迷宫''' count = 0 current_state = self.states[0] while current_state != self.states[-1]: current_action = self.choose_action(current_state, 1.) # 1., 100%贪婪 next_state = self.get_next_state(current_state, current_action) current_state = next_state count += 1 if count > self.MAZE_R * self.MAZE_C: # 没有在36步之内走出迷宫,则 print('无智能') return False # 无智能 print('有智能') return True # 有智能 def play(self, env=None): '''玩游戏,使用策略''' print('测试agent是否能在36步之内走出迷宫') if not self.test_agent(): # 若尚无智能,则 print("I need to learn before playing this game.") self.learn(env, episode=222, epsilon=0.7) print('Agent is playing...') current_state = self.states[0] env.move_agent_to(current_state) while current_state != self.states[-1]: current_action = self.choose_action(current_state, 1) next_state = self.get_next_state(current_state, current_action) current_state = next_state env.move_agent_to(current_state) time.sleep(0.4) print('\nCongratulations, Agent got it!')if __name__ == '__main__': env = Maze() # 环境 agent = Agent() # 个体(智能体) ''' epsilon = 0.9 # 贪婪度 greedy,0-1,越接近1随机性越小,会导致智能体死在元寶裏面 Q_learning比较“大胆”,此处设置为1,Sarsa就显得比较“谨慎”,设置0.5-0.9都可以 Q_learning采取最大化的策略,即我不管后面的动作怎样,我只选取下一个可能性最大的动作来更新表格。这种训练方法训练智能体,在CliffWalking环境里会比较明显, 它会紧贴着元宝走到终点,因为这种寻路的方法是路径最短的。但是智能体在训练初期相比于Sarsa很容易掉入元宝。 对于Sarsa,需要考虑到下一个动作具体是什么,也即考虑到“长期”的动作的影响。在CliffWalking环境中,智能体会远离元宝动到终点。 因为掉入元宝会得到很坏的回报,由于算法考虑“长期”的回报,所以智能体会尽可能的远离元宝。 设置episode参数会影响算法训练的时间,越大越慢,学得越好 ''' agent.learn(env, episode=333, epsilon=0.7) # 先学习 agent.play(env) # 再玩耍

转载地址:https://data-mining.blog.csdn.net/article/details/114783723 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:python_强化学习算法DQN_玩五子棋游戏
下一篇:AI人年度必看的222页报告!九大要点解读,中国AI论文引用首超美国

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年04月26日 01时16分49秒