深度学习

一 、强化学习

  • 强化学习就是程序或智能体(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值大小选择对 应状态执行的动作,以完成控制。

  • 神经网络的参数:应用监督学习完成

学习流程:

  1. 状态s输入,获得所有动作对应的Q 值Q(s,a);
  2. 选择对应Q值最大的动作 a′ 并执行;
  3. 执行后环境发生改变,并能够获得 环境的奖励r;
  4. 利用奖励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) 进行交互,通过模拟器获得状态,给 出动作,改变模拟器中的状态,获得 模拟器 反馈,依据反馈更新策略的过程

训练过程

训练过程过程主要分为以下三个阶段:

  1. 观察期(OBSERVE):程序与模拟器进行交互,随机给出动作,获取模 拟器中的状态,将状态转移过程存放在D(Replay Memory)中;
  2. 探索期(EXPLORE):程序与模拟器交互的过程中,依据Replay Memory中存储的历史信息更新网络参数,并随训练过程降低随机探索率ε;
  3. 训练期(TRAIN):ε已经很小,不再发生改变,网络参数随着训练过 程不断趋于稳定。

整体框架—观察期

  1. 打开游戏模拟器,不执行跳跃动作,获取游戏的初始状态
  2. 根据ε贪心策略获得一个动作(由于神经网络参数也是随机初始化的,在本阶 段参数也不会进行更新,所以统称为随机动作),并根据迭代次数减小ε的大小
  3. 由模拟器执行选择的动作,能够返回新的状态和反馈奖励
  4. 将上一状态s,动作a,新状态s‘,反馈r组装成(s,a,s’,r)放进 Replay Memory中用作以后的参数更新
  5. 根据新的状态s‘,根据ε贪心策略选择下一步执行的动作,周而复始,直至 迭代次数到达探索期

整体框架—探索期

探索期与观察期的唯一区别在于会根据抽样对网络参数进行更新。

  1. 迭代次数达到一定数目,进入探索期,根据当前状态s,使用ε贪心策略选 择一个动作(可以是随机动作或者由神经网络选择动作),并根据迭代次数减小 ε的值
  2. 由模拟器执行选择的动作,能够返回新的状态和反馈奖励
  3. 将上一状态s,动作a,新状态s‘,反馈r组装成(s,a,s’,r)放进 Replay Memory中用作参数更新
  4. 从Replay Memory中抽取一定量的样本,对神经网络的参数进行更新
  5. 根据新的状态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
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn