深度学习
一 、强化学习
- 强化学习就是程序或智能体(agent)通过与环境不断地进行交互学习一 个从环境到动作的映射,学习的目标就是使累计回报最大化。
- 强化学习是一种试错学习,因其在各种状态(环境)下需要尽量尝试所 有可以选择的动作,通过环境给出的反馈(即奖励)来判断动作的优劣, 最终获得环境和最优动作的映射关系(即策略)。
1.1 马尔可夫决策过程(MDP)
马尔可夫决策过程(Markov Decision Process) 通常 用来描述一个强化学习问题 智能体agent根据当前对环境的观察采取动作 获得环境的反馈,并使环境发生改变的循环过 程。
MDP基本元素
s∈S:有限状态state集合,s表示某个特定状态; a∈A:有限动作action集合,a表示某个特定动作;
T(S, a, S’)~Pr(s’|s,a): 状态转移模型, 根据当前状态s和动作a预 测下一个状态s,这里的Pr表示从s采取行动a转移到s’的概率;
R(s,a):表示agent采取某个动作后的即时奖励,它还有 R(s,a,s’), R(s) 等表现形式;
Policy π(s)→a: 根据当前state来产生action,可表现为a=π(s)或 π(a|s) = P(a|s),后者表示某种状态下执行某个动作的概率。
值函数
状态值函数V表示执行策略π能得到的累计折扣奖励:
Vπ(s) = E[R(s0,a0)+γR(s1,a1)+γ2R(s2,a2)+γ3R(s3,a3)+...|s=s0]
整理之后可得: 略
状态动作值函数Q(s,a)表示在状态s下执行动作a能得到的累计折扣奖励:
Qπ(s,a) = E[R(s0,a0)+γR(s1,a1)+γ2R(s2,a2)+γ3R(s3,a3)+...|s=s0,a=a0]
整理之后可得:
略
最优值函数:
略
最优控制
在得到最优值函数之后,可以通过值函数的值得到状态s时应该采取的动作a:
略
1.2 蒙特卡洛强化学习
- 在现实的强化学习任务中,环境的转移概率、奖励函数往往很难得知, 甚至很难得知环境中有多少状态。若学习算法不再依赖于环境建模, 则称为免模型学习,蒙特卡洛强化学习就是其中的一种。
- 蒙特卡洛强化学习使用多次采样,然后求取平均累计奖赏作为期望累 计奖赏的近似。 蒙特卡洛强化学习:直接对状态动作值函数Q(s,a)进行估计,每采 样一条轨迹,就根据轨迹中的所有“状态-动作”利用下面的公式对来对 值函数进行更新。
每次采样更新完所有的“状态-动作”对所对应的Q(s,a),就需要 更新采样策略π。但由于策略可能是确定性的,即一个状态对应一个动作, 多次采样可能获得相同的采样轨迹,因此需要借助ε贪心策略:
1.3 Q-learning算法
蒙特卡洛强化学习算法需要采样一个完整的轨迹来更新值函数,效率较 低,此外该算法没有充分利用强化学习任务的序贯决策结构。
- Q-learning算法结合了动态规划与蒙特卡洛方法的思想,使得学习更加 高效。 算法略
二、深度强化学习(DRL)
- 传统强化学习:真实环境中的状态数目过多,求解困难。
- 深度强化学习:将深度学习和强化学习结合在一起,通过深度神经网络 直接学习环境(或观察)与状态动作值函数Q(s,a)之间的映射关系,简 化问题的求解。
Deep Q Network(DQN):是将神经网络(neural network) 和Q- learning结合,利用神经网络近似模拟函数Q(s,a),输入是问题的状态 (e.g.,图形),输出是每个动作a对应的Q值,然后依据Q值大小选择对 应状态执行的动作,以完成控制。
- 神经网络的参数:应用监督学习完成
学习流程:
- 状态s输入,获得所有动作对应的Q 值Q(s,a);
- 选择对应Q值最大的动作 a′ 并执行;
- 执行后环境发生改变,并能够获得 环境的奖励r;
- 利用奖励r更新Q(s, a′)–强化学习 利用新的Q(s, a′)更新网络参数—监 督学习
三、自主学习Flappy Bird游戏
2013年,Deep Mind团队在NIPS上发表《Playing Atari with Deep Reinforcement Learning》一文,在该文中首次提出Deep Reinforcement Learning一词,并且提出DQN(Deep Q-Network)算法,实现了从纯图像输 入完全通过学习来玩Atari游戏。
Flappy Bird:是由来自越南的独 立游戏开发者开发的一款游戏。在游戏中,玩家需要点击屏幕控制小鸟跳 跃,跨越由各种不同长度水管组成的 障碍。
目标:使用深度强化学习方法自主学习Flappy Bird游戏策略,达到甚至 超过人类玩家的水平。
技术路线:Deep Q-Network 使用工具:tensorflow + pygame + cv2
程序与模拟器交互
训练过程也就是神经网络(agent) 不断与游戏模拟器(Environment) 进行交互,通过模拟器获得状态,给 出动作,改变模拟器中的状态,获得 模拟器 反馈,依据反馈更新策略的过程
训练过程
训练过程过程主要分为以下三个阶段:
- 观察期(OBSERVE):程序与模拟器进行交互,随机给出动作,获取模 拟器中的状态,将状态转移过程存放在D(Replay Memory)中;
- 探索期(EXPLORE):程序与模拟器交互的过程中,依据Replay Memory中存储的历史信息更新网络参数,并随训练过程降低随机探索率ε;
- 训练期(TRAIN):ε已经很小,不再发生改变,网络参数随着训练过 程不断趋于稳定。
整体框架—观察期
- 打开游戏模拟器,不执行跳跃动作,获取游戏的初始状态
- 根据ε贪心策略获得一个动作(由于神经网络参数也是随机初始化的,在本阶 段参数也不会进行更新,所以统称为随机动作),并根据迭代次数减小ε的大小
- 由模拟器执行选择的动作,能够返回新的状态和反馈奖励
- 将上一状态s,动作a,新状态s‘,反馈r组装成(s,a,s’,r)放进 Replay Memory中用作以后的参数更新
- 根据新的状态s‘,根据ε贪心策略选择下一步执行的动作,周而复始,直至 迭代次数到达探索期
整体框架—探索期
探索期与观察期的唯一区别在于会根据抽样对网络参数进行更新。
- 迭代次数达到一定数目,进入探索期,根据当前状态s,使用ε贪心策略选 择一个动作(可以是随机动作或者由神经网络选择动作),并根据迭代次数减小 ε的值
- 由模拟器执行选择的动作,能够返回新的状态和反馈奖励
- 将上一状态s,动作a,新状态s‘,反馈r组装成(s,a,s’,r)放进 Replay Memory中用作参数更新
- 从Replay Memory中抽取一定量的样本,对神经网络的参数进行更新
- 根据新的状态s‘,根据ε贪心策略选择下一步执行的动作,周而复始,直 至迭代次数到达训练期
整体框架—训练期
迭代次数达到一定数目,进入训练期,本阶段跟探索期的过程相同,只是在迭代过 程中不再修改ε的值
模拟器
游戏模拟器:使用Python的Pygame模块完成的Flappy Bird游戏程序,为了配合训练过程,在原有的游戏程序基 础上进行了修改。参考以下网址查看游戏源码:
链接:https://github.com/sourabhv/FlapPyBird
图示通过模拟器获取游戏的画面。
- 训练过程中使用连续4帧图像作为一个状态s,用于神经网络的输入
深度神经网络-CNN
DQN:用卷积神经网络对游戏画面 进行特征提取,这个步骤可以理解为对状态的提取。
- 卷积神经网络(CNN):右侧展示卷 积操作。
卷积核:这里的卷积核指的就是 移动中3*3大小的矩阵
卷积操作:使用卷积核与数据进 行对应位置的乘积并加和,不断移动 卷积核生成卷积后的特征
CNN-池化操作
池化操作:对卷积的结果进行操 作。最常用的是最大池化操作,即从
卷积结果中挑出最大值,如选择一个 2*2大小的池化窗口(操作如图示)
卷积神经网络
卷积神经网络:把Image矩阵中的 每个元素当做一个神经元,那么卷积 核就相当于输入神经元和输出神经元 之间的链接权重,由此构建而成的网 络被称作卷积神经网络。
Flappy Bird-深度神经网络
对采集的4张原始图像进行预处理,得到80804大小的矩阵;
- 使用32个884大小步长4的卷积核对以上矩阵进行卷积,得到 202032大小的矩阵;注:在tensorflow中使用4维向量表示卷 积核[输入通道数,高度,宽度,输出通道数],对应于上面的 [4,8,8,32],可以理解为32个884大小的卷积核;
- 对以上矩阵进行不重叠的池化操作,池化窗口为22大小,步长 为2,得到1010*32大小的矩阵;
- 使用64个4432大小步长为2的卷积核对以上矩阵进行卷积, 得到5564的矩阵;
- 使用64个3364大小步长为1的卷积核对以上矩阵进行卷积, 得到5564的矩阵;
通过获得输入s,神经网络就能够:
- 输出Q(s,a1)和Q(s,a2)比较两个值的大小,就能够评判采用动作 a1和a2的优劣,从而选择要采取的动作
- 在选择并执行完采用的动作后,模拟器会更新状态并返回回报值, 然后将这个状态转移过程存储进D,进行采样更新网络参数。
四、相关的库
4.1 tensorflow库
TensorFlow是谷歌2015年开源的一个人工智能学习系统。主要目 的是方便研究人员开展机器学习和深度神经网络方面的研究,目前 这个系统更具有通用性,也可广泛用于其他计算领域。 *Tensorflow 支持多种前端语言,包括Python(Python也是 tensorflow支持最好的前端语言),因此一般大家利用python实 现对tensorflow的调用。
tensorflow基本使用
理解TensorFlow:
- 使用图(graph)来表示计算任务;
- 在被称之为会话(Session)的上下文(context)中执行图;
- 使用tensor(张量)表示数据;
- 通过变量(Variable)维护状态;
- 使用feed和fetch可以为任意的操作(arbitrary operation)赋值或者从其中获取数据。
TensorFlow是一个编程系统,使用图来表示计算任务。图中的节点被 称作op(Operation),op可以获得0个或多个tensor,产生0个或多 个tensor。每个tensor是一个类型化的多维数组。例如:可以将一 组图像集表示成一个四维的浮点数组,四个维度分别是[batch, height,weight,channels]。
- 图(graph)描述了计算的过程。为了进行计算,图必须在会话中启 动,会话负责将图中的op分发到CPU或GPU上进行计算,然后将产生的 tensor返回。在Python中,tensor就是numpy.ndarray对象。
TensorFlow程序通常被组织成两个阶段:构建阶段和执行阶段。
- 构建阶段:op的执行顺序被描述成一个图;
- 执行阶段:使用会话执行图中的op。
- 例如:通常在构建阶段创建一个图来表示神经网络,在执行阶段反 复执行图中的op训练神经网络。
实例1:
>>> import tensorflow as tf #导入tensorflow库
>>> mat1 = tf.constant([[3., 3.]]) #创建一个1*2的矩阵
>>> mat2 = tf.constant([[2.],[2.]]) #创建一个2*1的矩阵
>>> product = tf.matmul(mat1, mat2) #创建op执行两个矩阵的乘法
>>> sess = tf.Session() >>> res = sess.run(product) >>> print(res)
[[ 12.]]
>>> sess.close()
交互式会话(InteractiveSession): 为了方便使用Ipython之类的Python交互环境,可以使用交互式会 话(InteractiveSession)来代替Session,使用类似Tensor.run() 和Operation.eval()来代替Session.run(),避免使用一个变量来持 有会话。
实例2:
>>> import tensorflow as tf #导入tensorflow库 >>>sess=tf.InteractiveSession() #创建交互式会话 >>> a = tf.Variable([1.0, 2.0]) #创建变量数组
>>> b = tf.constant([3.0, 4.0]) #创建常量数组
>>> sess.run(tf.global_variables_initializer() #变量初始化 >>> res = tf.add(a, b) #创建加法操作
>>> print(res.eval()) #执行操作并输出结果
[4. 5.]
Feed操作:
前面的例子中,数据均以变量或常量的形式进行存储。Tensorflow 还提供了Feed机制,该机制可以临时替代图中任意操作中的tensor。最 常见的用例是使用tf.placeholder()创建占位符,相当于是作为图中的 输入,然后使用Feed机制向图中占位符提供数据进行计算,具体使用方 法见接下来的样例。
实例3:
>>> import tensorflow as tf #导入tensorflow库 >>>sess=tf.InteractiveSession() #创建交互式会话
>>> input1 = tf.placeholder(tf.float32) #创建占位符
>>> input2 = tf.placeholder(tf.float32) #创建占位符
>>> res = tf.mul(input1, input2) #创建乘法操作
>>> res.eval(feed_dict={inp
4.2 OpenCV库
OpenCV是一个开源的跨平台的计算机视觉库,实现了大量的图像 处理和计算机视觉方面的通用算法。
- 本实验采用opencv对采集的游戏画面进行预处理。
PyGame库 Pygame是一个跨平台的模块,专为电子游戏设计。
- Pygame相当于是一款游戏引擎,用户无需编写大量的基础模块, 而只需完成游戏逻辑本身就可以了。
- 本实验游戏模拟器采用Pygame实现。
安装
pip install tensorflow
pip install opencv_python-3.2.0-cp35-cp35m-win_amd64.whl
install pygame
代码如下:
#!/usr/bin/env python
from __future__ import print_function
#1. 建立工程,导入相关工具包:
import tensorflow as tf
import cv2
import sys
sys.path.append("game/")
import wrapped_flappy_bird as game
import random
import numpy as np
from collections import deque
# 2.设置超参数:
GAME = 'bird'
ACTIONS = 2
GAMMA = 0.99
OBSERVE = 10000.
EXPLORE = 3000000.
FINAL_EPSILON = 0.0001
INITIAL_EPSILON = 0.0001
REPLAY_MEMORY = 50000
BATCH = 32
FRAME_PER_ACTION = 1
# 3. 创建深度神经网络:
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev = 0.01)
return tf.Variable(initial)
#首先定义一个函数,该函数用于生成形状为shape的张量(高纬数组) #张量中的初始化数值服从正太分布,且方差为0.01
def bias_variable(shape):
initial = tf.constant(0.01, shape = shape)
return tf.Variable(initial)
# 4.
def conv2d(x, W, stride):
return tf.nn.conv2d(x, W, strides = [1, stride, stride, 1], padding = "SAME")
#定义卷积操作,实现卷积核W在数据x上卷积操作 #strides为卷积核的移动步长,padding为卷积的一种模式,参数为same 表示滑动范围超过边界时,
def max_pool_2x2(x):
return tf.nn.max_pool(x, ksize = [1, 2, 2, 1], strides = [1, 2, 2, 1], padding = "SAME")
#定义池化函数,此程序中通过调用max_pool执行最大池化操作,大小为 2*2, stride步长为2.
def createNetwork():
#定义深度神经网络的参数和偏置
#第一个卷积层
W_conv1 = weight_variable([8, 8, 4, 32])
b_conv1 = bias_variable([32])
#第二个卷积层
W_conv2 = weight_variable([4, 4, 32, 64])
b_conv2 = bias_variable([64])
#第三个卷积层
W_conv3 = weight_variable([3, 3, 64, 64])
b_conv3 = bias_variable([64])
#第一个全连接层
W_fc1 = weight_variable([1600, 512])
b_fc1 = bias_variable([512])
#第二个全连接层
W_fc2 = weight_variable([512, ACTIONS])
b_fc2 = bias_variable([ACTIONS])
# 输入层,placeholder用于占位, 可用作网络的输入
s = tf.placeholder("float", [None, 80, 80, 4])
# 隐藏层 对各层进行连接
h_conv1 = tf.nn.relu(conv2d(s, W_conv1, 4) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2, 2) + b_conv2)
h_conv3 = tf.nn.relu(conv2d(h_conv2, W_conv3, 1) + b_conv3)
h_conv3_flat = tf.reshape(h_conv3, [-1, 1600])
h_fc1 = tf.nn.relu(tf.matmul(h_conv3_flat, W_fc1) + b_fc1)
# 输出层
readout = tf.matmul(h_fc1, W_fc2) + b_fc2
return s, readout, h_fc1
def trainNetwork(s, readout, h_fc1, sess):
# 定义损失函数
a = tf.placeholder("float", [None, ACTIONS])
y = tf.placeholder("float", [None])
readout_action = tf.reduce_sum(tf.multiply(readout, a),
reduction_indices=1)
cost = tf.reduce_mean(tf.square(y - readout_action))
train_step = tf.train.AdamOptimizer(1e-6).minimize(cost)
# 开启游戏模拟器,会打开一个模拟器的窗口,实时显示游戏的信息
game_state = game.GameState()
# 创建双端队列用于存放replay memory
D = deque()
# 获取游戏的初始状态,设置动作为不执行跳跃,并将初始状态修改成80*80*4大小
do_nothing = np.zeros(ACTIONS)
do_nothing[0] = 1
x_t, r_0, terminal = game_state.frame_step(do_nothing)
x_t = cv2.cvtColor(cv2.resize(x_t, (80, 80)), cv2.COLOR_BGR2GRAY)
ret, x_t = cv2.threshold(x_t,1,255,cv2.THRESH_BINARY)
s_t = np.stack((x_t, x_t, x_t, x_t), axis=2)
# 用于加载或保存网络参数
saver = tf.train.Saver()
sess.run(tf.initialize_all_variables())
checkpoint = tf.train.get_checkpoint_state("saved_networks")
if checkpoint and checkpoint.model_checkpoint_path:
saver.restore(sess, checkpoint.model_checkpoint_path)
print("Successfully loaded:", checkpoint.model_checkpoint_path)
else:
print("Could not find old network weights")
# 开始训练
epsilon = INITIAL_EPSILON
t = 0
while "flappy bird" != "angry bird":
# 使用epsilon贪心策略选择一个动作
readout_t = readout.eval(feed_dict={s : [s_t]})[0]
a_t = np.zeros([ACTIONS])
action_index = 0
if t % FRAME_PER_ACTION == 0:
# 执行一个随机动作
if random.random() <= epsilon: print("----------Random Action----------") action_index = random.randrange(ACTIONS) a_t[random.randrange(ACTIONS)] = 1 # 由神经网络计算的Q(s,a)值选择对应的动作 else: action_index = np.argmax(readout_t) a_t[action_index] = 1 else: a_t[0] = 1 # 不执行跳跃动作 # 随游戏的进行,不断降低epsilon,减少随机动作 if epsilon > FINAL_EPSILON and t > OBSERVE:
epsilon -= (INITIAL_EPSILON - FINAL_EPSILON) / EXPLORE
# 执行选择的动作,并获得下一状态及回报
x_t1_colored, r_t, terminal = game_state.frame_step(a_t)
x_t1 = cv2.cvtColor(cv2.resize(x_t1_colored, (80, 80)),
cv2.COLOR_BGR2GRAY)
ret, x_t1 = cv2.threshold(x_t1, 1, 255, cv2.THRESH_BINARY)
x_t1 = np.reshape(x_t1, (80, 80, 1))
s_t1 = np.append(x_t1, s_t[:, :, :3], axis=2)
# 将状态转移过程存储到D中,用于更新参数时采样
D.append((s_t, a_t, r_t, s_t1, terminal))
if len(D) > REPLAY_MEMORY:
D.popleft()
# 过了观察期,才会进行网络参数的更新
if t > OBSERVE:
# 从D中随机采样,用于参数更新
minibatch = random.sample(D, BATCH)
# 分别将当前状态、采取的动作、获得的回报、下一状态分组存放
s_j_batch = [d[0] for d in minibatch]
a_batch = [d[1] for d in minibatch]
r_batch = [d[2] for d in minibatch]
s_j1_batch = [d[3] for d in minibatch]
#计算Q(s,a)的新值
y_batch = []
readout_j1_batch = readout.eval(feed_dict = {s : s_j1_batch})
for i in range(0, len(minibatch)):
terminal = minibatch[i][4]
# 如果游戏结束,则只有反馈值
if terminal:
y_batch.append(r_batch[i])
else:
y_batch.append(r_batch[i] +
GAMMA * np.max(readout_j1_batch[i]))
# 使用梯度下降更新网络参数
train_step.run(feed_dict = {
y : y_batch,
a : a_batch,
s : s_j_batch}
)
# 状态发生改变,用于下次循环
s_t = s_t1
t += 1
# 每进行10000次迭代,保留一下网络参数
if t % 10000 == 0:
saver.save(sess, 'saved_networks/' + GAME + '-dqn', global_step=t)
# 打印游戏信息
state = ""
if t <= OBSERVE: state = "observe" elif t > OBSERVE and t <= OBSERVE + EXPLORE:
state = "explore"
else:
state = "train"
print("TIMESTEP", t, "/ STATE", state, \
"/ EPSILON", epsilon, "/ ACTION", action_index, "/ REWARD", r_t, \
"/ Q_MAX %e" % np.max(readout_t))
def playGame():
sess = tf.InteractiveSession()
s, readout, h_fc1 = createNetwork()
trainNetwork(s, readout, h_fc1, sess)
if __name__ == "__main__":
playGame()
训练参数加载
参数训练完成之后,修改程序中的超参数INITIAL_EPSILON=0,即不使 用随机动作,直接由神经网络输出动作。
- saved_networks文件夹下,保存了最近几次检查点保留的网络参数,只 需使用tf.train.Saver()加载参数就可以使用了。 修改checkpoint配置文件, 加载保存的网络参数,默认加载最后一次 保存的参数。修改完成,运行程序即可。 结果展示: t=25万 2-3次 t=100万 24次 t=210万 死不掉 t=280万 死不掉 以上为不同迭代次数下,训练的神经网络能达到的游戏水平
参考资料:
北京理工大学 礼欣 www.python123.org
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn