Python 您所在的位置:网站首页 一个走格子的游戏 Python

Python

2024-06-10 15:35| 来源: 网络整理| 查看: 265

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 random import time import tkinter as tk import 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) # 再玩耍


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有