前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >万字详述 | 全开源:python写小游戏+AI强化学习与传统DFS/BFS控制分别实现

万字详述 | 全开源:python写小游戏+AI强化学习与传统DFS/BFS控制分别实现

作者头像
Piper蛋窝
发布2020-11-19 11:04:09
1.3K0
发布2020-11-19 11:04:09
举报
文章被收录于专栏:Piper蛋窝

简介:本周的强化学习我们来到实践部分。我以我在 GitHub 上开源的项目 PiperLiu / Amazing-Brick-DFS-and-DRL 为对象,从零开始与各位朋友分享:如何用 python 写一个小游戏如何匹配传统的深度优先搜索算法来控制如何匹配传统的广度优先搜索算法来控制如何匹配深度强化学习算法来控制强化学习的优势在哪里 。无论你是零基础还是有项目经验,我都希望能给你带来收获。

去年在 B 站看到大佬 UP The CW[1] 的视频:用AI在手游中作弊!内藏干货:神经网络、深度/强化学习讲解[2],当时觉得很有趣;但代码部分没有开源,于是我便想着复现一下这位 UP 的作品,仅作为学习之用。

我的复现

深度学习先驱 Yann LeCun 将强化学习比作“蛋糕上的樱桃”,因为强化学习能获得的数据实在是太少了。 一般的强化学习,必须不断与环境交互,获得数据。 (好比网游中,只能通过刷野怪来获得经验值升级)

因此,遇到一个新问题时: 监督学习 会思考如何 给大量数据打标签 ,而 强化学习 会开始着手 写仿真/写游戏

我们今天来自动控制一个“躲避障碍”的小游戏,全部由 python 实现,我的项目地址为:

•https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL

我希望本文带给你的收获有二: 如何写一个简单的小游戏,并且提供控制程序的接口; 强化学习控制与深度优先搜索有什么区别,优劣的对比。 本文结构为:

•游戏实现思路•什么是深度优先搜索DFS?用其控制小游戏•什么是广度优先搜索BFS?用其控制小游戏•深度强化学习控制小游戏•强化学习与传统控制的优劣比较

游戏实现思路

动作类游戏的思路:逐帧计算、逐帧渲染

我用手玩这个小游戏,重力很大,按键后左右加速度也很大,很不好控制(我刻意将参数设置得如此反人类,为了增加游戏难度)

这个游戏共涉及三个 .py 脚本:

代码语言:javascript
复制
amazing_brick / amazing_brick_utils.py              / wrapped_amazing_brick.pykeyboard_play.py

从玩家角度看,该游戏是动态的;但实际上,由于我没有使用已有物理引擎/游戏引擎,我是 基于每一帧对游戏进行设计、并迭代画面的。

keyboard_play.py 在操作时,游戏类实体: game_state.frame_step(action) 处于一个无限循环中:

•每执行一次 game_state.frame_step(action)game_state 会判断位移、是否碰撞、是否得分,并绘制这一帧,并显示;•默认收到的动作 action=1 ,即什么也不干;•玩家按下按钮,将改变下一帧 action 的赋值。

1. 整体思路

如图,在游戏中需要绘制在屏幕上的,一共有三种实体:

•玩家(黑色方块);•方块障碍物;•中间留有空隙的长条障碍物。

基于这三个实体,我们主要需要考虑以下五个事件:

•简易的物理引擎,考虑重力、阻力与加速度;•当玩家上升时,屏幕要随之上升;•检测得分,当玩家穿过间隙时,得分加一;•检测碰撞,当玩家碰到障碍物或撞墙时,游戏结束;•新建随机障碍物。

下面我将展开分别讲解上述事件的实现。

2. 简易的物理引擎

简易物理引擎是最简单的部分,我们为玩家(黑色方块)声明几个变量,作为定位的依据,我这里选择的是左上点 (x, y)

此外,玩家还应该具有速度变量。在 2D 空间里,速度是一个矢量(有大小,有方向),为了方便计算,我用横轴坐标方向的速度值表示 (velX, velY) ,即:单位时间内的 X 、 Y 轴位移量来表示速度。

此外,还有加速度系统。为玩家声明四个变量,分布表示重力加速度、横向空气阻力带来的加速度、按下按钮后带来的横向加速度、按下按钮后带来的纵向加速度: gravity, dragForce, AccX, AccY

因此,我们就能很轻松地实现符合物理公式的运动系统:

•首先根据加速度计算速度;•接下来根据速度计算玩家应该处于什么位置。

game/amazing_brick_utils.py[3] :

代码语言:javascript
复制
class Play:    def __init__(self):        self.x = ...        self.y = ...        self.x_= ...        self.y_= ...        # 如果你觉得游戏太难的话,可以改变这些物理参数        self.gravity = 0.35        self.dragForce = 0.01        self.velX = 0        self.velY = 0        self.AccX = 4.5        self.AccY = 2.5    def lFlap(self):        # 按下左边按钮时,玩家获得一个向左上的力        # 因此速度发生改变        self.velX -= self.AccX        self.velY -= (self.AccY - self.gravity)    def rFlap(self):        # 按下右边按钮时,玩家获得一个向右上的力        # 因此速度发生改变        self.velX += self.AccX        self.velY -= (self.AccY - self.gravity)    def noneDo(self):        # 没有按按钮        # 玩家因为横向空气阻力而减缓横向速度        # 此外,还因为重力向下加速        if self.velX > 0:            self.velX -= self.dragForce        elif self.velX < 0:            self.velX += self.dragForce        self.velY += self.gravity

在 game/wrapped_amazing_brick.py[4] 中,我在每帧的迭代代码中,添加了下述代码,用来根据当前速度,确定玩家的新位置:

代码语言:javascript
复制
class GameState:    def __init__(self, ifRender=True, fps=30):        ...    def frame_step(self, action):        ...        if action == 0:            self.player.noneDo()        elif action == 1:            self.player.lFlap()        elif action == 2:            self.player.rFlap()        ...        # player's movement        self.player.x += self.player.velX        self.player.x_ += self.player.velX        self.player.y += self.player.velY        self.player.y_ += self.player.velY

3. 屏幕上升机制

有两个思路:

•第一个是,让所有障碍物在每帧下移固定距离,从而造成“玩家在上升”的假象;•另一个是,建立一个“摄像头”,摄像头本身有一个坐标,摄像头随着玩家的上升而上升。无论是障碍物还是玩家,都有两套坐标,一套是真实的、绝对的坐标,另一套是相对于“摄像头”的坐标。我们计算碰撞时,基于前者即真实的坐标;绘图时,基于后者即相对于“摄像头”的坐标。

我采用了第二个思路。这样做的好处是,无需每时每刻对所有障碍物的坐标进行更新,且让镜头的移动更加灵活。

我在 game/wrapped_amazing_brick.py[5] 中将这个“摄像头”实现了:

代码语言:javascript
复制
class ScreenCamera:    def __init__(self):        self.x = 0        self.y = 0        self.width = CONST['SCREEN_WIDTH']        self.height = CONST['SCREEN_HEIGHT']        self.x_ = self.x + self.width        self.y_ = self.y + self.height    def __call__(self, obj: Box):        # output the obj's (x, y) on screen        x_c = obj.x - self.x        y_c = obj.y - self.y        # 每个实体:玩家、障碍物都有一套相对坐标,即 x_c, y_c        # obj.set_camera(x_c, y_c) 将其在屏幕上的新位置告诉它        # 绘图时,就根据其 x_c, y_c 来将其绘制在屏幕上        obj.set_camera(x_c, y_c)        return obj    def move(self, obj: Player):        # 如果玩家此时在屏幕上的坐标将高于屏幕的 1/2        # 镜头上移        # 即不允许玩家跑到屏幕上半部分去        self(obj)        if obj.y_c < self.height / 2:            self.y -= (self.height / 2 - obj.y_c)        else:            pass

值得注意的是,pygame中的坐标系是右下为正方向的。

如图,因为相机的移动,我们的玩家一直处于屏幕中央。

4. 检测得分

在 game/wrapped_amazing_brick.py[6] 中,我在每帧的迭代代码中,添加了下述代码,用来检测得分:

代码语言:javascript
复制
class GameState:    def __init__(self, ifRender=True, fps=30):        ...    def frame_step(self, action):        ...        # check for score        playerMidPos = self.s_c(self.player).y_c + self.player.height / 2        for ind, pipe in enumerate(self.pipes):            if ind % 2 == 1:                continue            self.s_c(pipe)            # 判断 Y 轴是否处于间隙中央            if pipe.y_c <= playerMidPos <= pipe.y_c + pipe.height:                if not pipe.scored:                    self.score += 1                    # 不能在一个间隙中得两次分                    pipe.scored = True                    # reward 用于强化学习                    reward = 1

只要在Y轴方向经过了间隙中央,则得分。

5. 检测碰撞

以下情况视为碰撞发生,游戏结束:

•碰到障碍物;•碰到边缘镜头。

其中,“碰到障碍物”用实际坐标计算:

•对于两个物体,取其中心点;•当满足如下图片两个条件时,视为碰撞。

碰到边缘镜头则用相对坐标判断。

6. 新建障碍物

因为每次碰撞都要遍历所有障碍物,因此当障碍物淡出屏幕后,就要将障碍物从内存中删除,以确保程序不会越来越卡顿。

我使用两个列表保存所有已有障碍物:

代码语言:javascript
复制
class GameState:    def __init__(self, ifRender=True, fps=30):        ...        self.pipes = []        self.blocks = []    def frame_step(self, action):        ...        # 判断是否新增障碍物        low_pipe = self.pipes[0]        if self.s_c(low_pipe).y_c >= self.s_c.height - low_pipe.width \                and len(self.pipes) < 6:            # 满足条件,新增障碍物            self._getRandomPipe()        # 如果条形障碍物超出屏幕,则删除        if self.s_c(low_pipe).y_c >= self.s_c.height \                and len(self.pipes) > 4:            self.pipes.pop(0)            self.pipes.pop(0)        # 如果块状障碍物超出屏幕,则删除        for block in self.blocks:            self.s_c(block)            x_flag = - CONST['BLOCK_WIDTH'] <= block.x_c <= self.s_c.width            y_flag = block.y_c >= self.s_c.height

此外,还需新增障碍物。这里我使用随机数生成。

代码语言:javascript
复制
class GameState:    ...    def _getRandomPipe(self, init=False):        if self.score % 5 == 4:            self.color_ind = (self.color_ind + 1) % 5        gap_left_topXs = list(range(100, 190, 20))        if init:            index = random.randint(0, len(gap_left_topXs)-1)            x = gap_left_topXs[index]            y = CONST['SCREEN_HEIGHT'] / 2 - CONST['PIPE_WIDTH'] / 2            first_pipes = pipes(x, y, self.color_ind)            self.pipes.append(first_pipes[0])            self.pipes.append(first_pipes[1])            self._addBlocks()        index = random.randint(0, len(gap_left_topXs)-1)        x = self.s_c.x + gap_left_topXs[index]        y = self.pipes[-1].y - CONST['SCREEN_HEIGHT'] / 2        pipe = pipes(x, y, self.color_ind)        self.pipes.append(pipe[0])        self.pipes.append(pipe[1])        self._addBlocks()    def _addBlocks(self):        x = (self.pipes[-2].x_ + self.pipes[-1].x) / 2        y = (self.pipes[-2].y + self.pipes[-2].y_) / 2        for i in range(2, 0, -1):            y_block = y + i * CONST['BLOCK_SPACE']            x_block = x + np.random.normal() * CONST['PIPE_GAPSIZE'] / 2.5            block = Block(x_block, y_block, self.color_ind)            self.blocks.append(block)

深度优先搜索 DFS

何为深度优先搜索 DFS

如上图,我们将使用“深度优先搜索”的方法,来控制黑色方块自动闯关。

所谓“深度优先搜索”,即:

•搜索: 精准预测下一步操作后 ,黑色方块将到达什么位置;并再次精准预测在这个位置进行操作后,黑色方块将到达什么位置... 直到触发终止条件,即找到最终得分的路径 ;•深度优先:假设黑色方块有两个动作可以选择:A与B,那么 黑色方块做出“选择A后应该到达的位置”的预测后,继续接着这条路径预测 ,而非去预测在初始状态下“选择B后应该到达的位置”。具体原理如下图。

图片生成自:https://visualgo.net/zh/dfsbfs

如何用 DFS 匹配我们的小游戏

在我写的小游戏中,我们的小方块时刻面临 三个选项

•给自己一个左上的力;•给自己一个右上的力;•什么也不做,这一时刻任由自己受重力牵制而掉落。

因此,我们每层也就有三个结点,如下图:

但是因为 算法本身的时间复杂度过大 ,我们可以不考虑“什么也不做”这一动作。否则,将如下图,需要搜索的结点过多,导致程序运行过慢或内存溢出。

如果不考虑“什么也不做”这一动作,每层的父结点就只有两个子结点, 大大减少需要遍历的空间。

使用递归的实现

我使用递归来实现 DFS 算法,我大概描述一下这个过程。数据结构不够硬的同学,应该静下心来读读我的源码、或者其他经典的 DFS 教程、或者刷刷 LeetCode 。

我的源码见:https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL/blob/master/dfs_play.py

代码语言:javascript
复制
final_s_a_list = []def dfs_forward(root_state, show=False):    # 最后需要返回的就是这个(状态、动作)列表    global final_s_a_list    final_s_a_list = []    # 在内部定义 dfs ,用于递归    # 在递归过程中,修改 final_s_a_list 的值    # 总是保留目前最优解    def dfs(state, s_a_list):        global final_s_a_list        # a trick        # 每次结点的排列都不一样        # 这样搜索速度更快        # 能更快地找到可行解        if len(s_a_list) % 2 == 1:            ACTIONS_tmp = (2, 1)        else:            ACTIONS_tmp = (1, 2)        for action in ACTIONS_tmp:            if len(final_s_a_list) > 0:                break            new_state = move_forward(state, action)            new_s_a_list = s_a_list.copy()            new_s_a_list.append((new_state, action))            if check_crash(new_state):                if show:                    # 绘图部分                    pygame.draw.rect(SCREEN, (255, 0, 0), \                            (new_state['x'] - game_state.s_c.x, new_state['y'] - game_state.s_c.y, game_state.player.width, game_state.player.height))                    pygame.display.update()                del new_state                del new_s_a_list            else:                if show:                    # 绘图部分                    pygame.draw.rect(SCREEN, (100, 100, 100), \                            (new_state['x'] - game_state.s_c.x, new_state['y'] - game_state.s_c.y, game_state.player.width, game_state.player.height))                    pygame.display.update()                if check_for_score(new_state):                    if show:                        # 绘图部分                        pygame.draw.rect(SCREEN, (0, 0, 255), \                                (new_state['x'] - game_state.s_c.x, new_state['y'] - game_state.s_c.y, game_state.player.width, game_state.player.height))                        pygame.display.update()                    final_s_a_list = new_s_a_list                    break                dfs(new_state, new_s_a_list)    # 开始递归    dfs(root_state, [])    return final_s_a_list

我这里 DFS 算法效果较好:

代码语言:javascript
复制
python dfs_play.py

输入参数 --display 可以查看寻路过程:

代码语言:javascript
复制
python dfs_play.py --display

广度优先搜索 BFS

何为广度优先搜索 DFS

如上图,我们将使用“广度优先搜索”的方法,来控制黑色方块自动闯关。

所谓“广度优先搜索”,即:

•搜索:精准预测一步操作后,黑色方块将到达什么位置;并再次精准预测在这个位置进行操作后,黑色方块将到达什么位置...直到触发终止条件,即找到最终得分的路径;•广度优先:假设黑色方块有两个动作可以选择:A与B,那么黑色方块做出“选择A后应该到达的位置”的预测后,不继续接着这条路径预测;而是去预测在初始状态下“选择B后应该到达的位置”。具体原理如下图。

图片生成自:https://visualgo.net/zh/dfsbfs

为了更好地了解 BFS 的特性,你可以用 DFS(深度优先搜索) 进行对比:

如何用 BFS 匹配我们的小游戏

同样,在小游戏中,我们的小方块时刻面临 三个选项

•给自己一个左上的力;•给自己一个右上的力;•什么也不做,这一时刻任由自己受重力牵制而掉落。

因此,我们每层也就有三个结点,如下图:

但是同样,因为算法本身的时间复杂度过大, 我们可以不考虑“什么也不做”这一动作。这样,每层的父结点就只有两个子结点,大大减少需要遍历的空间。 否则,需要搜索的结点过多,导致程序运行过慢或内存溢出。

使用队列的实现

我使用队列来实现 BFS 算法,我大概描述一下这个过程。数据结构不够硬的同学,应该静下心来读读我的源码、或者其他经典的 BFS 教程、或者刷刷 LeetCode 。

我的源码见:https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL/bfs_play.py

代码语言:javascript
复制
Node = namedtuple("Node", ['sta' , 'act', 'father'])game_state = GameState(True)# 为了避免搜索空间过大# 这里调高了游戏的力学参数game_state.player.velMaxY = 20game_state.player.AccY = 5ACTIONS = (0, 1, 2)def bfs_forward(root_state, show=False):    # 保存结点的队列    q = Queue()    for action in ACTIONS:        node = Node(root_state.copy(), action, None)        q.put(node)    while True:        # 如果队列为空        # 则说明所有可行结点已经遍历        if q.empty():            break        father_node = q.get()        father_state = father_node.sta        if check_for_score(father_state):            # 如果得分            # 说明可行路径已经找到            # 跳出循环            if show:                pygame.draw.rect(SCREEN, (0, 0, 255), \                        (father_state['x'] - game_state.s_c.x, father_state['y'] - game_state.s_c.y, game_state.player.width, game_state.player.height))                pygame.display.update()            break        # 只考虑动作 1 和 2        for action in ACTIONS[1:]:            # father_state = move_forward(father_state, ACTIONS[0])            new_state = move_forward(father_state, action)            if check_crash(new_state):                if show:                    pygame.draw.rect(SCREEN, (255, 0, 0), \                            (new_state['x'] - game_state.s_c.x, new_state['y'] - game_state.s_c.y, game_state.player.width, game_state.player.height))                    pygame.display.update()            else:                if show:                    pygame.draw.rect(SCREEN, (100, 100, 100), \                            (new_state['x'] - game_state.s_c.x, new_state['y'] - game_state.s_c.y, game_state.player.width, game_state.player.height))                    pygame.display.update()                node = Node(new_state, action, father_node)                q.put(node)    return father_node

我这里 BFS 算法效果不好:

代码语言:javascript
复制
python bfs_play.py

输入参数 --display 可以查看寻路过程:

代码语言:javascript
复制
python bfs_play.py --display

深度强化学习控制

强化学习:实时决策的技术

强化学习是一种“学习”,这意味着可以 从两个角度 理解, 学习应用

•在“学习”时,我们需要将数据“喂”给神经网络(或者其他映射结构),其本身根据新老数据,基于一个较为复杂的数学关系,进行参数迭代。 你可以将其理解为,输入数据,参数改变,导致映射结构更加强大准确。•而“应用”则非常好理解,对于神经网络(或者其他映射结构),我们输入某个/些值,则获得其输出的值。 比如强化学习中,我们输入环境信息,得到当前做什么动作最优。

对于我们的小游戏,则是根据当前环境信息(小黑点位置,障碍物位置等),实时输出此使做什么动作:向左发力向右发力不发力三选一。

基于 thu-ml/tianshou 强化学习控制

在项目中,涉及的 .py 文件有:

代码语言:javascript
复制
DQN_train/gym_warpper.pyDQN_train/dqn_train3.pyDQN_train/dqn_render3.py

requirements

代码语言:javascript
复制
tianshoupytorch > 1.40gym

继续训练与测试

在本项目地址中,你可以使用如下文件对我训练的模型进行测试,或者继续训练。

继续训练该模型:

代码语言:javascript
复制
python DQN_train/dqn_train3.py

我已经训练了 40 次(每次5个epoch),输入上述命令,你将开始第 41 次训练,如果不使用任务管理器强制停止,计算机将一直训练下去,并自动保存每一代的权重。

查看效果:

代码语言:javascript
复制
python DQN_train/dqn_render3.py 3

注意参数 3 ,输入 3 代表使用训练 3 次后的权重。

效果如图:

训练不够充分,强化学习智能体没有找到最优解(避开障碍,过关得分;只找到了次优解,在下面苟着命,不得分,但也不会因为碰到障碍物受伤)

我保留了该模型的所有历史权重。你还可以输入参数:1-40,查看历代神经网络的表现。如果你继续训练了模型,你可以输入更大的参数,如 41 。

输入 10 则代表使用训练 10 次后的权重:

代码语言:javascript
复制
python DQN_train/dqn_render3.py 25

效果如图:

输入 30 则代表使用训练 30 次后的权重:

代码语言:javascript
复制
python DQN_train/dqn_render3.py 30

效果如图:

封装交互环境

事件

奖励

动作后碰撞障碍物、墙壁

-1

动作后无事发生

0.0001

动作后得分

1

在第一层滞留过久(超过500步)

-10

可以看出,我将动作后无事发生的奖励从 0.1 降低到了 -1 ,是为了:

•突出动作后得分这项的奖励;•如此,智能体第一次得分后,会很“欣喜地发现”上升一层的快乐远远大于在第一层苟命的快乐。

此外,如果智能体在第一层滞留过久,也是会受到 -10 的惩罚的:

•这是为了告诉智能体,在第一层过久是不被鼓励的;•因为状态是链式的,因此最后的惩罚会回溯分配到之前的“苟命”策略上。

封装代码在 gym_wrapper.py[7] 中,使用类 AmazingBrickEnv3

强化学习机制与神经网络的构建

我之前常识过将 2 帧的数据输入到线性层中,效果并不理想。我进一步帮助机器提取了信息,并且预处理了数据

•不再将巨大的 2 帧数据输入到网络中;•取而代之的是,当前状态的速度向量(velx, vely)(共2个数);•再加上玩家xy坐标左障碍物右上顶点xy坐标右障碍物左上顶点xy坐标4个障碍方块的左上顶点的xy坐标(共14个数);•如此,输入层只有 16 个神经元即可,且每 1 帧做一次决策。

我还放慢了 epsilon (探索概率)的收敛速度,让智能体更多地去探索动作,不局限在局部最优解中。

此外,我对输入数据进行了归一化处理比如,玩家的坐标 x, y 分别除以了屏幕的 宽、高。从结果和训练所需的代数更少来看,我认为这对于机器学习有极大的帮助。

线性神经网络的构建
代码语言:javascript
复制
class Net(nn.Module):    def __init__(self):        super().__init__()        self.fc1 = nn.Linear(16, 128)        self.fc2 = nn.Linear(128, 256)        self.fc3 = nn.Linear(256, 128)        self.fc4 = nn.Linear(128, 3)    def forward(self, obs, state=None, info={}):        if not isinstance(obs, torch.Tensor):            obs = torch.tensor(obs, dtype=torch.float)        x = F.relu(self.fc1(obs))        x = F.relu(self.fc2(x))        x = F.relu(self.fc3(x))        x = self.fc4(x)        return x, state

如上,共四层线性网络。

记录训练的微型框架

为了保存训练好的权重,且在需要时可以暂停并继续训练,我新建了一个.json文件用于保存训练数据。

代码语言:javascript
复制
dqn2_path = osp.join(path, 'DQN_train/dqn_weights/')if __name__ == '__main__':    try:        with open(dqn3_path + 'dqn3_log.json', 'r') as f:            jlist = json.load(f)        log_dict = jlist[-1]        round = log_dict['round']        policy.load_state_dict(torch.load(dqn3_path + 'dqn3round_' + str(int(round)) + '.pth'))        del jlist    except FileNotFoundError as identifier:        print('\n\nWe shall train a bright new net.\n')        # 第一次训练时,新建一个 .json 文件        # 声明一个列表        # 以后每次写入 json 文件,向列表新增一个字典对象        with open(dqn3_path + 'dqn3_log.json', 'a+') as f:            f.write('[]')            round = 0    while True:        round += 1        print('\n\nround:{}\n\n'.format(round))        result = ts.trainer.offpolicy_trainer(            policy, train_collector, test_collector,            max_epoch=max_epoch, step_per_epoch=step_per_epoch,            collect_per_step=collect_per_step,            episode_per_test=30, batch_size=64,            # 如下,每新一轮训练才更新 epsilon            train_fn=lambda e: policy.set_eps(0.1 / round),            test_fn=lambda e: policy.set_eps(0.05 / round), writer=None)        print(f'Finished training! Use {result["duration"]}')        torch.save(policy.state_dict(), dqn3_path + 'dqn3round_' + str(int(round)) + '.pth')        policy.load_state_dict(torch.load(dqn3_path + 'dqn3round_' + str(int(round)) + '.pth'))        log_dict = {}        log_dict['round'] = round        log_dict['last_train_time'] = datetime.datetime.now().strftime('%y-%m-%d %I:%M:%S %p %a')        log_dict['best_reward'] = result['best_reward']        with open(dqn3_path + 'dqn3_log.json', 'r') as f:            """dqn3_log.json should be inited as []"""            jlist = json.load(f)        jlist.append(log_dict)        with open(dqn3_path + 'dqn3_log.json', 'w') as f:            json.dump(jlist, f)        del jlist

DQN

代码语言:javascript
复制
import os.path as ospimport sysdirname = osp.dirname(__file__)path = osp.join(dirname, '..')sys.path.append(path)from amazing_brick.game.wrapped_amazing_brick import GameStatefrom amazing_brick.game.amazing_brick_utils import CONSTfrom DQN_train.gym_wrapper import AmazingBrickEnv3import tianshou as tsimport torch, numpy as npfrom torch import nnimport torch.nn.functional as Fimport jsonimport datetimetrain_env = AmazingBrickEnv3()test_env = AmazingBrickEnv3()state_shape = 16action_shape = 1net = Net()optim = torch.optim.Adam(net.parameters(), lr=1e-3)'''args for rl'''estimation_step = 3max_epoch = 5step_per_epoch = 300collect_per_step = 50policy = ts.policy.DQNPolicy(net, optim,    discount_factor=0.9, estimation_step=estimation_step,    use_target_network=True, target_update_freq=320)train_collector = ts.data.Collector(policy, train_env, ts.data.ReplayBuffer(size=2000))test_collector = ts.data.Collector(policy, test_env)

采用这种方式获得了不错的效果,在第 40 代训练后(共 40 * 5 * 300 = 6000 个 step),智能体已经能走 10 层左右。

相信继续的迭代会获得更好的成绩。

思考:强化学习与传统控制

首先明确一个概念,在这个案例中, 深度优先搜索 DFS 与广度优先搜索 BFS 作弊了。

DFS/BFS 哪里作弊了

开始的时候,时间被暂停了?

以如上是 DFS 的一个实例,你有没有发现, DFS 只有 把接下来的所有步骤都算好了 ,才能 开始行动 。这就有两点要求:

•DFS / BFS 这种搜索算法 所处的环境必须是稳定的 ,即其计算出的下一步、下下一步到哪里等,都必须是准确的,如果环境中有风吹草动这种不可预测因素,则无法进行“搜索”•DFS / BFS 这种搜索算法 必须暂停时间 ,如上图中,其不可以一遍掉落一遍计算路径(因为掉落对其来说是环境的改变,环境改变了,就要重新搜索、计算)

但也正是因为这种“局限性”,一个问题一旦能用 DFS / BFS 求解,求出来的解一定是最优解。比如这里,尽管 DFS 空间、时间消耗很大,但是其可以 “一丁点失误都没有”

强化学习魅力在于“无需作弊”

强化学习则不需要 “作弊” ,在这里,我们完全可以把强化学习理解为一个人:

•人类无法精准地计算出接下来小黑点会到达什么位置,强化学习也是•但是人类有“感觉”这种东西,练习多了,人类就能大概感觉出来: 这种情况下,按左键;在那种情况下,按右键。 强化学习也是如此,只根据当前的情况,做出当前的决策, 但是其考虑到了长远的总收益

训练10个epoch,强化学习稍微会玩了一点

最后一点闲言

我在前文提到过“传统控制算法”,实际上,DFS、BFS只是一种数据结构搜索算法,并算不上 “传统控制算法”

“传统控制算法” 当然也不需要 “作弊” ,其贯穿的数学原理 + 算法控制 + 硬件实现可谓 巧妙惊人 。父母都是理工名校工科专业出身,我本人从小也向往“制造机器人”,虽然没有学习自动控制相关专业(而是学了管理),但是会将其作为兴趣钻研下去,尽管目前尚无精力。

目前看来,传统控制算法依然在大部分场景下优于强化学习。未来有机会,我会与各位分享经典的 PID 控制与其他学习心得。欢迎关注我,此外,请点击“在看”支持我。

引用链接

[1] The CW: https://space.bilibili.com/13081489 [2] 用AI在手游中作弊!内藏干货:神经网络、深度/强化学习讲解: https://www.bilibili.com/video/BV1Ft411E79Y [3] game/amazing_brick_utils.py: https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL/amazing_brick/game/amazing_brick_utils.py [4] game/wrapped_amazing_brick.py: https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL/amazing_brick/game/wrapped_amazing_brick.py [5] game/wrapped_amazing_brick.py: https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL/amazing_brick/game/wrapped_amazing_brick.py [6] game/wrapped_amazing_brick.py: https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL/amazing_brick/game/wrapped_amazing_brick.py [7] gym_wrapper.py: https://github.com/PiperLiu/Amazing-Brick-DFS-and-DRL/blob/master/DQN_train/gym_wrapper.py

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Piper蛋窝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 游戏实现思路
    • 动作类游戏的思路:逐帧计算、逐帧渲染
      • 1. 整体思路
        • 2. 简易的物理引擎
          • 3. 屏幕上升机制
            • 4. 检测得分
              • 5. 检测碰撞
                • 6. 新建障碍物
                • 深度优先搜索 DFS
                  • 何为深度优先搜索 DFS
                    • 如何用 DFS 匹配我们的小游戏
                      • 使用递归的实现
                      • 广度优先搜索 BFS
                        • 何为广度优先搜索 DFS
                          • 如何用 BFS 匹配我们的小游戏
                            • 使用队列的实现
                            • 深度强化学习控制
                              • 强化学习:实时决策的技术
                                • 基于 thu-ml/tianshou 强化学习控制
                                  • requirements
                                    • 继续训练与测试
                                      • 封装交互环境
                                        • 强化学习机制与神经网络的构建
                                          • 线性神经网络的构建
                                      • 记录训练的微型框架
                                      • DQN
                                      • 思考:强化学习与传统控制
                                        • DFS/BFS 哪里作弊了
                                          • 强化学习魅力在于“无需作弊”
                                            • 最后一点闲言
                                              • 引用链接
                                          领券
                                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档