使用50行Python代码从零开始实现一个AI平衡小游戏


Posted in Python onNovember 21, 2018

使用50行Python代码从零开始实现一个AI平衡小游戏 

集智导读:

本文会为大家展示机器学习专家 Mike Shi 如何用 50 行 Python 代码创建一个 AI,使用增强学习技术,玩耍一个保持杆子平衡的小游戏。所用环境为标准的 OpenAI Gym,只使用 Numpy 来创建 agent。

各位看官好,我(作者 Mike Shi——译者注)将在本文教大家如何用 50 行 Python 代码,教会 AI 玩一个简单的平衡游戏。我们会用到标准的 OpenAI Gym 作为测试环境,仅用 Numpy 创建我们的 AI,别的不用。

这个小游戏就是经典的 Cart Pole 任务,它是 OpenAI Gym 中一个经典的传统增强学习任务。游戏玩法如下方动图所示,就是尽力保持这根杆子始终竖直向上。杆子由于重力原因,会出现倾斜,到了一定程度就会倒下,AI 的任务就是在此时向左或向右移动杆子,不让它倒下。这就跟我们在手指尖上树立一支铅笔玩“金鸡独立”一样,只不过我们这里是个一维的简单游戏(但是还是很有挑战性的)。

你可能好奇最终实现怎样的结果,可以在repl.it 上查看 demo:

 https:// repl.it/@MikeShi42/Cart Pole 

使用50行Python代码从零开始实现一个AI平衡小游戏

增强学习速览

如果这是你第一次接触机器学习或增强学习,别担心,我下面介绍一些基础知识,这样你就可以了解本文使用的术语了:)。如果已经熟悉了,大可跳过这部分,直接看看编写 AI 的部分。

增强学习(RL)是一个研究领域:教 agent(我们的算法/机器)执行某些任务/动作,但明确告诉它该怎样做。把它想象成一个婴儿,以随机的方式伸腿,如果宝宝偶然间走运站立起来,我们会给它一个糖果作为奖励。同样,Agent 的目标就是在其生命周期内得到最多的奖励,而且我们会根据是否和要完成的任务相符来决定奖励的类型。对于婴儿站立的例子,站立时奖励 1,否则为0。

增强学习 agent 的一个著名例子是 AlphaGo,其中的 agent 已经学会了如何玩围棋以最大化其奖励(赢得游戏)。在本教程中,我们将创建一个 agent,或者说 AI,可以向左或向右移动小车,让杆子保持平衡。

状态

状态是目前游戏的样子。我们通常处理游戏的多种数字表示。在乒乓球比赛中,它可能是每个球拍的垂直位置和 x,y 坐标和球的速度。在我们这个游戏中,我们的状态由 4 个数字组成:底部小车的位置,小车的速度,杆的位置(以角度表示)和杆的角速度。这 4 个数字都是给定的数组(或向量)。这个很重要,理解状态是一个数字数组意味着我们可以对它进行一些数学运算来决定我们根据状态采取什么行动。

使用50行Python代码从零开始实现一个AI平衡小游戏

策略

策略是一种函数,其输入是游戏的状态(例如棋盘的位置,或小车和杆的位置),输出 agent应该在该位置采取的动作(例如,将小车向左边移动)。在 agent 采取我们选择的操作后,游戏将使用下一个状态进行更新,我们会再次将其纳入策略以做出决策。这种情况一直持续到游戏结束。策略非常重要,也是我们一直追求的,因为代表了 agent 背后的决策能力。

点积

两个数组(向量)之间的点积简单地将第一个数组的每个元素乘以第二个数组的对应元素,并将它们全部加在一起。假设我们想找到数组 A 和 B 的点积,只需计算是 A [0] * B [0] + A [1] * B [1] ......我们将使用这种运算将状态(一个数组)乘以另一个数组(我们的策略)。

创建我们的策略

为了完成这个推车平衡游戏,我们希望让我们的 agent(或者说 AI)学习策略赢得比赛或获得最大奖励。

对于我们今天要开发的 agent,我们将策略表示为 4 个数字的数组,分别代表状态的各个部分的“重要性”(小车位置,杆子的位置等)然后我们会计算状态和策略数组的点积,得到一个数字。根据数字是正数还是负数,我们将向左或向右推动小车。

如果这听起来有点抽象,那么我们选择一个具体的例子,看看会发生什么。

假设小车在游戏中居中并且静止,杆子向右倾斜且可能倒向右边。它看起来像这样:

使用50行Python代码从零开始实现一个AI平衡小游戏

相关状态可能如下所示:

使用50行Python代码从零开始实现一个AI平衡小游戏

那么状态数组将是 [0,0,0.2,0.05]。

从直觉上,我们要把小车推向右边,将支杆拉直。我从训练中得到了一个很好的策略,其策略数据如下:[ - 0.116,0.332,0.207 0.352]。我们快速计算一下,看看该策略会输出怎样的动作。

这里,我们将状态数组 [0,0,0.2,0.05] 和上述策略数组结合计算点积。如果数字是正数,我们将车推向右边,如果数字是负数,我们向左推。

使用50行Python代码从零开始实现一个AI平衡小游戏

结果为正,意味着策略会向右推动小车,符合我们的预期。

现在比较明显了,我们需要 4 个像上面这样的神奇数字来帮我们解决问题。那么我们该如何获得这些数字?如果我们只是随机挑选它们会怎样?AI 的效果会怎样?我们来一起看代码!

启动你的编辑器!

首先在repl.it 上打开一个 Python 实例。Repl.it 能让我们快速启动大量不同编程环境的云实例,并在任何地方都能访问的强大云 IDE 中编辑代码!

使用50行Python代码从零开始实现一个AI平衡小游戏

安装软件包

我们首先安装这个项目所需的两个软件包:numpy 帮助进行数值计算;OpenAI Gym 作为我们代理的模拟器。

使用50行Python代码从零开始实现一个AI平衡小游戏

只需在编辑器左侧的包搜索工具中输入 gym 和 numpy,然后单击加号按钮即可安装包。

创建基础框架

我们首先将我们刚刚安装的两个依赖项导入到main.py 脚本中,并设置一个新的 gym 环境:

import gymimport numpy as npenv = gym.make('CartPole-v1')

接下来,我们定义一个名为“play”的函数,为该函数提供一个环境和一个策略数组,在环境中计算策略数组并返回分数,以及每个时步的游戏快照(用于观察)。我们将使用分数来判断策略的效果以及查看每个时步的游戏快照来判断策略的表现。这样我们就可以测试不同的策略,看看它们在游戏中的表现如何!

首先我们理解函数的定义,然后将游戏重置为开始状态。

def play(env, policy): observation = env.reset()

接下来,我们将初始化一些变量以跟踪游戏是否已经结束,包括策略的总分以及游戏中每个步骤的快照(供观察)。

done = False score = 0 observations = []

现在我们多次运行游戏,直到 gym 告诉我们游戏已经完成。

for _ in range(5000): 
observations += [observation.tolist()] 
# 记录用于正则化的观察值,并回放 
 if done:
 # 如果模拟在最后一次迭代中结束,则退出循环  
 break  
# 根据策略矩阵选择一种行为 
outcome = np.dot(policy, observation) 
 action = 1 if outcome > 0 else 0 
 # 创建行为,记录反馈 
observation, reward, done, info = env.step(action) 
 score += reward 
return score, observations

上面的大部分代码主要是玩游戏的过程以及记录的结果。实际上,我们的策略代码只需要两行:

outcome = np.dot(policy, observation) 
action = 1 if outcome > 0 else 0

我们在这里所做的只是策略数组和状态数组之间的点积运算,就像我们之前在具体例子中所示的那样。然后我们根据结果是正还是负,选择 1 或 0(左或右)的动作。

到目前为止,我们的main.py 应如下所示:

import gymimport numpy as npenv = gym.make('CartPole-v1')def play(env, policy): 
 observation = env.reset() 
done = False score = 0 
observations = [] for _ in range(5000): 
 observations += [observation.tolist()] 
# 如果模拟在最后一次迭代中结束,则退出循环  
if done: # 如果模拟在最后一次迭代中结束,则退出循环  break  
# 根据策略矩阵选择一种行为 
outcome = np.dot(policy, observation) 
action = 1 if outcome > 0 else 0  
# 创建行为,记录反馈 
observation, reward, done, info = env.step(action) 
score += reward 
return score, observations

现在,我们开始玩游戏,寻找我们的最佳策略!

第一局游戏

由于我们有了能够玩游戏的函数,并且能告诉我们的策略有多好,那么下面就创建一些策略,看看它们的效果怎样。

如果我们首先只想尝试随机策略呢?能达到怎样的效果?我们使用 numpy 来生成我们的策略,它是一个 4 元素数组或 1x4 矩阵。它会选择 0 到 1 之间的 4 个数字作为我们的策略。

policy = np.random.rand(1,4)

根据该策略和我们上面创建的环境,我们可以用它们来玩游戏,获得一个分数。

score, observations = play(env, policy)print('Policy Score', score)

点击运行,执行我们的脚本,然后会输出我们的策略得分:

使用50行Python代码从零开始实现一个AI平衡小游戏

游戏的最大得分是 500 分,你的策略有可能达不到这个水平。如果达到了,恭喜你!绝对是你的大日子!只是看一个数字并没有特别大的意义。如果能看到我们的 agent 是如何玩游戏的,那就太好了,下一步我们就会设置它!

查看我们的agent

要查看我们的 agent,我们会使用 Flask 设置一个轻量级服务器,以便我们可以在浏览器中查看代理的性能。Flask 是一个轻量级的 Python HTTP 服务器框架,可以为我们的 HTML UI 和数据伺服。这部分我就一笔带过了,因为渲染和 HTTP 服务器背后的细节对训练我们的 agent 并不重要。

我们首先将 Flask 安装为 Python 包,就像我们在前面安装 gym 和 Numpy 一样。

使用50行Python代码从零开始实现一个AI平衡小游戏 

接着,在我们脚本的底部,我们将创建一个 Flask 服务器。它将在端点 / data 上显示游戏的每一帧的记录,并在/上托管UI。

from flask import Flaskimport jsonapp = Flask(__name__, static_folder='.')@app.route("/data")def data(): 
 return json.dumps(observations)@app.route('/')def root(): 
 return app.send_static_file('./index.html')
 app.run(host='0.0.0.0', port='3000')

另外,我们需要添加两个文件。一个是项目的空白 Python 文件。这是repl.it 如何检测 repl 是处于 eval 模式还是项目模式的专用术语。只需使用新文件按钮添加空白 Python 脚本即可。

之后我们还想创建一个用于渲染 UI 的 index.html。这里不再深入讲解,只需将此 index.html 上传到你的repl.it 项目即可。

现在你应该有一个如下所示的项目目录:

使用50行Python代码从零开始实现一个AI平衡小游戏

现在有了这两个新文件,当我们运行 repl 时,它应该能演示我们的策略。有了这个,我们尝试找到最佳策略!

使用50行Python代码从零开始实现一个AI平衡小游戏

策略搜索

在我们的第一局游戏中,我们只是随机选择了一个策略,但是如果我们选择了一批策略,并且只保留那个表现最好的策略呢?

我们回到发布策略的部分,这次不是仅生成一个,而是编写一个循环来生成多个策略,并跟踪每个策略的执行情况,最终仅保存最佳策略。

首先我们创建一个名为 max 的元组,它将存储我们迄今为止看到的最佳策略的得分,观察值和策略数组。

max = (0, [], [])

接着我们会生成和评估 10 个策略,并将最优策略保存在 max 中。

for _ in range(10): 
 policy = np.random.rand(1,4) 
 score, observations = play(env, policy) 
if score > max[0]: 
max = (score, observations, policy)print('Max Score', max[0])

我们还要让 /data 端点返回最优策略的回放。

该端点:

@app.route("/data")def data():return json.dumps(observations)

应该改为:

@app.route("/data")def data():return json.dumps(max[1])

你的main.py 应该如下所示:

import gymimport numpy as npenv = gym.make('CartPole-v1')def play(env, policy): 
 observation = env.reset() done = False score = 0 
 observations = [] for _ in range(5000): 
observations += [observation.tolist()]  
 if done:  
break  
outcome = np.dot(policy, observation) 
 action = 1 if outcome > 0 else 0 
 observation, reward, done, info = env.step(action) score += reward return score, observationsmax = (0, [], [])for _ in range(10): policy = np.random.rand(1,4) score, observations = play(env, policy) if score > max[0]: 
max = (score, observations, policy)print('Max Score', max[0])from flask import Flaskimport jsonapp = Flask(__name__, static_folder='.')@app.route("/data")def data(): return json.dumps(max[1])@app.route('/')def root(): 
 return app.send_static_file('./index.html') 
app.run(host='0.0.0.0', port='3000')

如果我们现在运行 repl,应该会得到最多为 500 分的分数,如果没有达到这个结果,那就再运行 repl 一遍。另外我们可以看到策略几乎完美地让推车上的杆子保持平衡。

不是那么快

不过实际上或许没有这么好,因为我们在第一部分稍微有一点作弊。首先,我们只是在 0 到 1 的范围内随机创建了策略数组。这恰好可行,但是如果我们修改一下运算符,就会看到 agent 出现灾难性的失败。你自己可以试试将 action = 1 if outcome > 0 else 0 改成 action = 1 if outcome < 0 else 0

但是效果仍然不稳定,因为如果我们恰好选择少于而不是大于 0,我们永远找不到最优的策略。为了解决这个问题,我们实际上应该生成对负数同样适用的策略。虽然这为我们的工作增加了难度,但我们再也不必通过将我们的特定算法拟合特定游戏来“作弊”了。不然,如果我们试图在 OpenAIgym 以外的其他环境中运行算法时,算法肯定会失败。

要做到这一点,我们不再使用 policy = np.random.rand(1,4),而是改为 policy = np.random.rand(1,4) - 0.5。这样我们策略中的每个数字都在 -0.5 到 0.5 之间,而不是 0 到 1。但是因为这样难度更高,我们还想搜索更多的策略。在上面的 for 循环中,不是迭代 10 个策略,而是通过让代码改为读取 for _ in range(100): 来尝试 100 个策略。此外也鼓励大家尝试首先只迭代 10 个策略,看看现在用负数来获得好的策略的难度如何。

现在我们的main.py 应该如下所示:

import gym
import numpy as np
env = gym.make('CartPole-v1')
def play(env, policy):
 observation = env.reset()
 done = False
 score = 0
 observations = []
 for _ in range(5000):
  observations += [observation.tolist()] 
  if done: 
   break
  outcome = np.dot(policy, observation)
  action = 1 if outcome > 0 else 0
  observation, reward, done, info = env.step(action)
  score += reward
 return score, observations
max = (0, [], [])
# 修改接下来两行!
for _ in range(100):
 policy = np.random.rand(1,4) - 0.5
 score, observations = play(env, policy)
 if score > max[0]:
  max = (score, observations, policy)
print('Max Score', max[0])
from flask import Flask
import json
app = Flask(__name__, static_folder='.')
@app.route("/data")
def data():
  return json.dumps(max[1])
@app.route('/')
def root():
  return app.send_static_file('./index.html')
app.run(host='0.0.0.0', port='3000')

如果现在运行 repl,无论我们使用的值是否大于或小于 0,我们仍然可以为游戏找到一个好的策略。

但是等等,这还没完!即使我们的策略可以运行一次就达到最高分 500,但每次都能做到吗?当我们生成 100 个策略,并选择出在单一运行中表现最佳的策略时,该策略可能只是走运而已,甚至它可能是一个非常糟糕的策略,只是恰好运行效果很好。这是因为游戏本身具有随机性因素(起始位置每次都不同),因此策略可能只适用于一个起始位置,换成其他起始位置就不行了。

因此,为了解决这个问题,我们需要评估策略在多次试验中的表现。现在,我们使用之前找到的最优策略,看看它在 100 次试验中的表现如何。

scores = []for _ in range(100): score, _ = play(env, max[2])
 scores += [score] print('Average Score (100 trials)', np.mean(scores))

这里我们将该策略运行 100次,并且每次都记录它的得分。然后我们使用 numpy 计算平均分数并将其打印到我们的终端。没有严格的已发布的“已解决”定义,但它应该只有少数几个点。你可能会注意到最好的政策实际上可能实际上是低于平均水平。但是,我会把解决方案留给你决定!

当然,对于何为“最优”并没有严格的定义,但是至少比最高分 500 来说不应太差。你可能注意到最优策略有时是低于平均水平的,但是最终的最优策略如何,还是要靠大家根据自己的实际情况来定夺。

结语

恭喜!至此我们成功创建了一个 AI,能够很好地玩耍这个简单的平衡游戏。不过,仍然有很多需要改进的地方:

  • 找到一个“真正的”最优策略(每局游戏都能表现良好)
  • 减少我们寻找最优策略的搜索次数
  • 研究怎样找到正确的策略,而不是随机选择它们
  • 尝试其它开发环境

以上所述是小编给大家介绍的使用50行Python代码从零开始实现一个AI平衡小游戏,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
Python批量修改文件后缀的方法
Jan 26 Python
python验证码识别的实例详解
Sep 09 Python
Python 调用Java实例详解
Jun 02 Python
windows下python安装paramiko模块和pycrypto模块(简单三步)
Jul 06 Python
解决Python2.7读写文件中的中文乱码问题
Apr 12 Python
谈谈Python中的while循环语句
Mar 10 Python
python搜索包的路径的实现方法
Jul 19 Python
PyCharm中代码字体大小调整方法
Jul 29 Python
Python爬虫爬取Bilibili弹幕过程解析
Oct 10 Python
使用Python求解带约束的最优化问题详解
Feb 11 Python
Python Scrapy多页数据爬取实现过程解析
Jun 12 Python
python主要用于哪些方向
Jul 05 Python
pygame游戏之旅 调用按钮实现游戏开始功能
Nov 21 #Python
pygame游戏之旅 按钮上添加文字的方法
Nov 21 #Python
Face++ API实现手势识别系统设计
Nov 21 #Python
详解django自定义中间件处理
Nov 21 #Python
pygame游戏之旅 添加游戏界面按键图形
Nov 20 #Python
pygame游戏之旅 添加游戏介绍
Nov 20 #Python
pygame游戏之旅 计算游戏中躲过的障碍数量
Nov 20 #Python
You might like
PHP封装curl的调用接口及常用函数详解
2018/05/31 PHP
ie7+背景透明文字不透明超级简单的实现方法
2014/01/17 Javascript
js重写alert控件(适合学习js的新手朋友)
2014/08/24 Javascript
DOM基础教程之事件对象
2015/01/20 Javascript
在javascript中随机数 math random如何生成指定范围数值的随机数
2015/10/21 Javascript
jQuery formValidator表单验证
2016/01/07 Javascript
JavaScript字符串常用的方法
2016/03/10 Javascript
knockoutjs动态加载外部的file作为component中的template数据源的实现方法
2016/09/01 Javascript
Javascrip实现文字跳动特效
2016/11/27 Javascript
使用travis-ci如何持续部署node.js应用详解
2017/07/30 Javascript
详解webpack4之splitchunksPlugin代码包分拆
2018/12/04 Javascript
解决node.js含有%百分号时发送get请求时浏览器地址自动编码的问题
2019/11/20 Javascript
Vue中Table组件行内右键菜单实现方法(基于 vue + AntDesign)
2019/11/21 Javascript
基于vue.js仿淘宝收货地址并设置默认地址的案例分析
2020/08/20 Javascript
JavaScript async/await原理及实例解析
2020/12/02 Javascript
vant时间控件使用方法详解
2020/12/24 Javascript
js正则表达式简单校验方法
2021/01/03 Javascript
[52:40]完美世界DOTA2联赛PWL S2 Magma vs GXR 第一场 11.29
2020/12/02 DOTA
测试、预发布后用python检测网页是否有日常链接
2014/06/03 Python
python网络编程实例简析
2014/09/26 Python
分享一个常用的Python模拟登陆类
2015/03/29 Python
Python遍历目录的4种方法实例介绍
2015/04/13 Python
python脚本设置系统时间的两种方法
2016/02/21 Python
Pandas读取MySQL数据到DataFrame的方法
2018/07/25 Python
英国可持续奢侈品包包品牌:Elvis & Kresse
2018/08/05 全球购物
美国围栏公司:Walpole Outdoors
2019/11/19 全球购物
教师党员公开承诺书
2014/03/25 职场文书
竞选体育委员演讲稿
2014/04/26 职场文书
师德师风事迹材料
2014/12/20 职场文书
小学教师工作总结2015
2015/04/07 职场文书
2019年最新借条范本!
2019/07/08 职场文书
哪类餐饮行业,最适合在高校创业?
2019/08/19 职场文书
浅谈vue2的$refs在vue3组合式API中的替代方法
2021/04/18 Vue.js
解决Golang中ResponseWriter的一个坑
2021/04/27 Golang
JVM钩子函数的使用场景详解
2021/08/23 Java/Android
使用Redis实现点赞取消点赞的详细代码
2022/03/20 Redis