干货 | Python人工智能在贪吃蛇游戏中的应用探索(上)
文案&代码 白宇啸
排版&审校 邓发珩
前言
一个月前,人工智能对我来说都是很陌生的,更不用说神经网络、强化学习、DQN等名词了。疫情期间,经过在家努力学习,我对这些概念越来越清晰了,也越来越喜欢上了它们。
下面,我想写一些收获,希望能给同样想在这方面学习的小伙伴一点启发,也欢迎大家指教,一起进步哦。今天的内容主要有以下几方面:
- 什么是神经网络
- tensorflow的安装和开发环境的配置
- 强化学习Q_Learning
- 深度神经网络DQN
- 利用DQN开发的贪吃蛇程序
后续我将分三篇来进行该主题的分享,让我们开始吧!
什么是神经网络
神经网络原本指的是生物神经网络,人工智能兴起后,产生了人工神经网络(artificial neural network,缩写ANN)。人的神经接受信息以后,通过轴突传至末梢,转化成一种人可接受的信息。
而ANN是指由大量的处理单元(神经元) 互相连接而形成的复杂网络结构,是对人脑组织结构和运行机制的某种抽象、简化和模拟,以数学模型模拟神经元活动,是基于模仿大脑神经网络结构和功能而建立的一种信息处理系统。
生物神经网络图神经网络主要由:输入层、隐藏层、输出层构成。如图,最左边的一层称为输入层,位于这一层的神经元称为输入神经元。最右边的输出层包含了输出神经元。中间的层被称为隐藏层。隐藏层就是既不是输入也不是输出的层次,一个神经网络可以有一个或多个隐藏层。
神经网络构成网络中的输入和输出层一般都被设计的很简单。网络输入层的每个神经元代表了一个特征,输出层个数代表了分类标签的个数。而隐藏层的设计比较复杂,隐藏层作用很大,就其本身而言,每一层都可以视为一个单独的机器学习算法。
每个隐藏层神经元/输出层神经元的值(激活值),都是由上一层神经元,经过加权求和与非线性变换而得到的。上游层的输出被用作输入,它的输出被传递到下一层,然后下一层使用该输出作为输入,依此类推。
此行为意味着,当堆叠各种层和创建深度神经网络时,系统会学习数据的中间表示,以帮助下游层更有效地完成其工作。现在,神经网络的研究人员已经开发了隐藏层的许多的最优设计规则,帮助我们决定如何权衡网络的隐藏层数和训练网络所需的时间。
总结:
建立神经网络的方法:建立M个隐藏层,按顺序建立输入层跟隐藏层的联结,最后建立隐藏层跟输出层的联结。为每个隐藏层的每个节点选择激活函数。求解每个联结的权重和每个节点自带的bias值。
下面重点描述下神经网络中的概念。
结构
:结构指定了网络中的变量和它们的拓扑关系。例如,神经网络中的变量可以是神经元连接的权重和神经元的激励值。激活函数(激励函数)与权重
权重
:通俗来讲就是各个变量在计算中所占比重。举个简单的例子,大学的每一门科目都有相应的学分,这个学分意味着在加权时所占的比重,我们假设哲学占5学分,数学3学分,就有得到值A。在神经元中,这个值会被带入激活函数进一步处理。
此处还涉及到偏值b,其大概相当于一次函数的截距,我们通过b来适当控制值的范围。常见激活函数如下:
激活函数损失函数
:如下公式,此处为得到的输出值,则是期望值,当函数值趋于零,就得到了理想的输出值(不一定是最好的)。从数学的角度来讲,我们写出的损失函数,在输出值趋于期望时,函数值要尽可能快的趋于零,如果在绝对值外添加次方,即可达到这一效果。
根据损失函数的大小,我们以此来调整权重和偏值,寻找最优解。
学习规则
:指定了网络中的权重如何随着时间推进而调整。一般情况下,学习规则依赖于神经元的激励值。它也可能依赖于监督者提供的目标值和当前权重的值。
tensorflow的安装与配置
TensorFlow是谷歌研发的第二代人工智能学习系统,其命名来源于本身的运行原理。Tensor(张量)意味着N维数组,Flow(流)意味着基于数据流图的计算,TensorFlow为张量从流图的一端流动到另一端计算过程。TensorFlow是将复杂的数据结构传输至人工智能神经网中进行分析和处理过程的系统。
TensorFlow可被用于语音识别或图像识别等多项机器深度学习领域。是一个开源的、基于 Python 的机器学习框架。下面我们先讲述如何配置tensorflow的开发环境。
首先强调一点,在python环境下安装tensorflow,必须做到版本的匹配。如果你的电脑上装了多个python版本,则很容易因为版本问题造成安装的失败。最好卸载不需要的python,使用Anacoda自带的python。
下载安装Anacoda
输入网址https://www.anaconda.com/distribution/,如图所示,选择python3.7版本下载:
下载安装后,点击开始,找到Anacoda3文件,选择Anacoda Prompt,输入python,会显示python版本,即安装成功。
或者在cmd中输入pip list
,如图,即安装成功。
建立tensorflow虚拟环境
Step 1
: 建立Anacoda(以下简称ana)与tensorflow、python 的关系,输入conda create --name tensorflow python=3.7
Step 2
: 输入y,即开始建立虚拟环境。
这里注意版本问题,自己需要的python版本是哪个版本,这里需要写明确(即conda create --name tensorflow python= ??? )。一个ana在同一时间只能支持一个版本,每个版本对应的tensorflow 的版本不同。查询版本,即在ana prompt 中输入python。如果已经知道对应的tensorflow版本,可以直接conda create—name tensorflow版本,
Step 3
: 安装tensorflow,在ana环境下,进入tensorflow的虚拟环境,输入conda activate。
注意一定要进入tensorflow 虚拟环境安装,输入conda install tensorflow 版本(我安装用的2.1.0),如图,即安装成功
输入deactivate,退出虚拟环境。
Step 4
: 安装完毕,可以在cmd的python状态下输入import tensorflow as tf来测试是否安装成功。
安装pycharm
Step 1
: 下载安装Pycharm,进入官网:
http://www.jetbrains.com/pycharm/download/#section=windows
如图所示,推荐下载免费使用的社区版:
Step 2
: 配置pycharm 环境,进入pycharm,点击左上角File,找到Setting,点击Project 中的Project Interpreter,点击设置按钮,找到已安装包的路径,创建环境。创建完毕后,可以找到python库和tensorflow库。(图中很多为私人配置,不一样不要紧)
Step 3
: 安装opencv
方法一:Opencv在cmd环境中安装,输入pip install opencv-python,默认使用国外源文件,速度比较慢,能够成功,但大概率在下载过程中因为网络原因或者其他原因中断,如图
方法二:在opencv的官网上下载好安装文件包再进行安装:
https://www.lfd.uci.edu/~gohlke/pythonlibs/
注意,要对应自己的python 和tensorflow版本,还有自己安装的电脑的位数来选择合适的安装包,方法是在出错的那句话中找到文件名,按照这个文件名去找安装包。
进入网站后要疯狂往下拉,在很下面。
安装合适opencv文件后,在cmd环境下输入pip install 路径(\opencv_python-*.whl)
写命令代码时指明安装包的路径。安装完成后,在python环境下,输入import cv2,即可检验。
另外有很多文献建议使用国内镜像网站安装opencv ,这里不建议,因为失败了很多次。
Step 4
: 安装loguru,输入pip install loguru
Tips:其实能顺利安装 tensorflow 并且在Python中正常使用不是件很容易的事情。特别指出的是,能正确导入tensorflow并不一定能正常使用,如果遇到同样的问题,请再次认真的检查你的python版本,tensorflow版本 和python中的环境配置。实在找不出原因,那就重新开始吧!我用了一周的时间解决安装中遇到的各种问题,步步惊心哦!
利用tensorflow建立神经网络(用后面贪吃蛇神经网络的模型为例)
Step 1
: 导入tensorflow
import tensorflow as tf
from tensorflow.keras import layers
Step 2
: 用模型堆叠构建模型
我们使用的最多的是层的堆叠,即tf.keras.Sequential模型,如下:
self.model = tf.keras.Sequential([
tf.keras.layers.Dense(units=32, input_dim=self.input_shape,
activation=tf.nn.relu), #输入层
tf.keras.layers.Dense(units=16, activation=tf.nn.relu),
tf.keras.layers.Dense(units=8, activation=tf.nn.relu),
tf.keras.layers.Dense(units=8, activation=tf.nn.relu),
tf.keras.layers.Dense(units=16, activation=tf.nn.relu),
tf.keras.layers.Dense(units=32, activation=tf.nn.relu),#隐藏层
tf.keras.layers.Dense(units=self.output_size, activation=tf.keras.activations.linear) #输出层
])
# activation 激活函数
下面图形是用激活函数
Step 3
: 编译网络神经模型
self.model.compile(optimizer = tf.keras.optimizers.Adam(self.lr), loss='mse',metrics=['accuracy'])
#loss 损失函数
Step 4
: 神经网络预测
self.model.predict(state, batch_size)
# batch_size批次数据 整形
Step 5
: 神经网络训练
model.fit(states, action_values, batch_size=batch_size, verbose=0, epochs=4)
强化学习Q_Learning
Q_learning是一个基于值的强化学习算法,利用 Q 函数寻找最优的「动作—选择」策略。强化学习是机器学习的一个分支,是指在某个环境下,一个个体通过和环境的互动,而不断改进他行为的方法。
最常见的强化学习的例子就是我们经常玩的游戏,比如贪吃蛇游戏,在这个游戏中,
输入的内容是:
状态(States)
=环境,贪吃蛇的蛇头的位置,食物的位置;
动作(Actions)
=任何可以执行的操作,上下左右的移动;
奖励(Rewards)
=每个动作得到的奖励,吃掉食物得到的分数,蛇死掉扣掉的分数等;
输出的内容:
方案(Policy)
=在当前状态下,应该选哪个行动。它是一个状态到一个行动的函数。(S,A,R)是用户输入的,P是函数生成的。
以上4个元素通过tuple方法定义结构,tuple(S,A,R,P) 构成了强化学习系统。
Q_learning 的目的就是最大化Q函数的值(给定一个状态和动作时的未来奖励期望),贪吃蛇走怎么样的路线,才能得到最高的分数。
对于在每个状态下的每个动作产生的结果(得到的分数),我们可以用下面的表
这里,我们引入一个路径规划的概念: Bellman condition, 这个概念的中心思想是说:如果从最佳选择的路径的末端截除一小部分,余下的路径仍然是最佳路径。举例:有个最优路径经过了ABCDE五个点,那么BCDE路径肯定是最优的。所以,我们想获得最优的路径,只需要获得每个分割成的更短路径的最优解。上图是最简单的Q_table的例子, Q-table是Q-learning的核心。它是一个表格,每一列代表一个动作,每一行表示一个状态。则每个格子的值就是此状态下采取此动作获得的最大长期奖励期望。通过此,就可以知道每一步的最佳动作是什么。
将这个概念用到寻找最佳路径上称作temporal difference learning。
为便于计算,将Q-Table表示为Bellman递推等式,拆分为当前回报和未来最大回报的和,即,其中表示状态在行为作用下的下一状态,而为状态后所有可能的行为,为价值累积过程中的打折系数,决定了未来回报相对于当前回报的重要程度。
在训练过程中,初始为0,训练中每行动一次,通过Bellman等式计算,优化目标是使得Agent根据Q函数执行动作能获得训练过程中的最大价值回报,即与的差异最小。
我们的训练步骤是:首先我们设置一个探索速率「epsilon」,它的值在0和1之间。一开始时候我们将它设定为1。它处于最大值,因为我们不知道 Q-table 中任何的值,所以我们需要走出随机的行动。随着训练次数的增加,我们将会进行借助前面训练得到的经验,于是我们逐渐减小「epsilon」值,做到探索和贪心的平衡。
选择了动作Action后, 并且观察输出的状态和奖励。接着我们使用Bellman方程去更新,:
Q_learning 训练过程如上图所示,推荐给大家我看到的两张有趣的图,可以更清楚的理解Q_Learning.
Deep Q-Learning深度学习
在前面介绍中,我们用矩阵来表示,但是在现实情况下,这个只是个理想状态,因为状态实在是太多。使用表格的方式根本存不下,那么怎么处理遇到的上面的问题呢?
对于如同贪吃蛇或者更复杂的场景这种情况,需要输入的state包含的信息会很多(高维),而输出的内容比较少(低维,比如上下左右)。这种情况又怎么处理呢?
Q-learning无法解决的这些问题,而被与神经网络结合的DQN完美的解决了。DQN和Q_learning相比,还有突出的几个改进:
1) DQN使用了卷积神经网络来逼近行为值函数
什么是价值函数近似呢?说起来很简单,就是如果用一个函数来表示Q(s,a)。理论上对于任意的(s,a)我们都可以由公式求出它的值函数。但是当state或action的个数过多时,分别去求每一个值函数会很慢。因此我们用函数近似的方式去估计值函数,这样,对于未在Q_Table中出现的state action也可以估计值函数。
值函数网络与贪心策略之间的联系是这样的:首先环境会给出一个state,根据值函数网络得到关于这个state的所有Q(s,a),然后利用贪心策略选择action并做出决策,环境接收到此action后会给出一个奖励Rew及下一个state。这是一个step,此时我们根据Rew去更新值函数网络的参数,接着进入下一个step。如此循环下去。
最优化一个损失函数loss function,也就是标签和网络输出的偏差,目标是让损失函数最小化。然后我们用Q_table处理巨量的有标签数据,然后通过反向传播使用梯度下降的方法来更新神经网络的参数。
2) DQN 设计了memory储存经验,并利用经验回放训练强化学习过程
由于在强化学习中,我们得到的观测数据是有顺序的,用这样的不独立数据使整个网络局限于一小块状态区域,用它们去更新神经网络的参数有很大的局限性,为了得到独立的数据, 用一个Memory来存储经历过的数据,每次更新参数的时候从Memory中随机抽取一部分的数据来用于更新,这样打破数据间的关联。
3) DQN的探索
在开始训练的时候,所有的参数都是随机的,有最高Q值的Action也是随机的,这是神经网络刚开始的探索过程。但是随着训练次数的增加,随着Q值的收敛,选择的Action会趋于一致。这时候,我们选择一个合适的概率,使一部分Action不按照最大Q值行动,也就是寻找一个好奇心和贪婪心之间的平衡。这个概率一般是从开始训练时的1逐步减少到0.1。也就是说开始训练拥有最大的好奇心,然后逐步向利用经验侧重。
4) 引入了一个target Q网络
为了解决这个问题, DQN在原来的Q网络的基础上又引入了一个target Q网络,即用来计算target的网络。它和Q网络结构一样,初始的权重也一样,只是Q网络每次迭代都会更新,而target Q网络是每隔一段时间才会更新。
下图是DQN的基本架构
DQN的基本架构DQN的基本算法流程:
- 首先初始化Memory,定义它的容量为D;
- 初始化本地神经网络和目标神经网络,随机生成权重,本地神经网络和目标神经网络的权重相同;
- 循环遍历训练次数episode =1, 2, …, M;
- 初始化环境变量;
- 循环遍历step =1,2,…, T:
- 用贪心策略生成action
- 执行action 计算得到的分数,获取next state;
- 分action后是terminal和不是terminal两种情况计算reward;
- 对模型权重使用梯度下降法进行更新;
- 每经过N步,对目标模型进行更新;
- 将相应内容存入memory中(state, action, reward, next state)
利用DQN开发的贪吃蛇程序
说明:为了更快地学习和验证DQN在贪吃蛇程序中的应用,我借鉴了齐浩洋学长的源代码。为了程序的呈现效果,我把部分源程序(训练部分)重新组合了一下,并将原来的程序里的各个模块进行了一下整合。
此处,先介绍贪吃蛇训练的过程,完整的程序在后续推文中进行介绍。
贪吃蛇训练的过程(DQN实现方法)
** 注:在本例中每个批次取出的数据
self.BATCH_SIZE=64
For.. to 迭代次数
#环境初始化
# ...
# 蛇,食物位置;界面大小,边界位置
# while 贪吃蛇 处于活的状态 每次循环是走一个step.
# memory 中如果有足够的样本,则随机取出批次量的数据。
if self.memory.__len__() > self.BATCH_SIZE:
# experiences = random.sample(self.memory, k=self.BATCH_SIZE)
#提取反馈信息 取出的每个变量为长度为64的数组,18是state组合的环境因素个数.
states(当前状态 tensor[64,18]), actions(动作 tensor[64,4]), rewards(分数), next_states(下一状态, tensor[64,18]), dones(是否活着) = zip(*experiences)
#设置本地模型和目标模型(解决参数不收敛的问题)
#使用本地模型估计下一个动作 target 为tensor(64,4)
target = self.qnetwork_local.predict(states, self.BATCH_SIZE)
#使用目标模型估计下一个动作。
target_val = self.qnetwork_target.predict(
next_states, self.BATCH_SIZE)
target_next = self.qnetwork_local.predict(
next_states, self.BATCH_SIZE)
#Double DQN需要从中取出有最大值的下一步做为action
max_action_values = np.argmax(target_next, axis=1)
#计算target 的奖励分数(或许样本的最大分数)
for i in range(self.BATCH_SIZE):
if dones[i]:
target[i][actions[i]] = rewards[i]
else:
target[i][actions[i]]= rewards[i] + self.GAMMA
target_val[i][max_action_values[i]]
# 训练模型 对本地模型权重进行更新
self.qnetwork_local.train(
states, target, batch_size=self.BATCH_SIZE)
# 每训练UPDATE_EVERY次更新目标模型的权重 目标模型不是每次都进行更新,保持参数的收敛。
if self.t == self.UPDATE_EVERY:
self.update_target_weights()
self.t = 0
else:
self.t += 1
#按照训练选择下一步 (代码3,随机走出随机的动作,使样本内容更加全面)
state = state.reshape((1,)+state.shape)
action_values = self.qnetwork_local.predict(state)
if random.random() > epsilon:
#选择最好的行动
action = np.argmax(action_values)
else:
#选择随机的行动
action = random.randint(0, self.nA-1)
#在环境中走下一步,并且判断贪吃蛇是否触发死的条件
......
if 死掉
break;
#将经验存入memory
self.memory.add(state, action, reward, next_state, done)
Endfor
系统设置参数,循环进行上述训练,从环境初始到贪吃蛇死掉.为一个过程。系统训练这个过程多次,训练结果放入****.h文件中。
下图为贪吃蛇训练部分的程序运行展示:
上述就是人工智能贪吃蛇的基本入门知识,希望对大家有所帮助,后续,我还会进一步完整的分析代码,方便大家理解。