教程 Tutorials

对于深度学习,该教程会引导您使用MNIST数据集构建不同的手写数字的分类器, 这可以说是神经网络的 "Hello World" 。 对于强化学习,我们将让计算机根据屏幕画面来学习玩乒乓球游戏。 对于自然语言处理。我们从词嵌套(Word Embedding)开始,然后学习递归网络(Recurrent Network)。 此外,TensorLayer的Tutorial包含了所有TensorFlow官方深度学习教程的模块化实现,因此您可以对照TensorFlow深度学习教程来学习 [英文] [极客学院中文翻译] 。 。

注解

若您已经对TensorFlow非常熟悉,阅读 InputLayerDenseLayer 的源代码可让您很好地理解 TensorLayer 是如何工作的。

本文档的中文深度学习教程需要更多贡献者参与,有意者请联系 tensorlayer@gmail.com

《深度学习:一起玩转TensorLayer》

好消息!中文社区推出了 《深度学习:一起玩转TensorLayer》 一书。本书由TensorLayer创始人领衔,TensorLayer主要开发团队倾力打造而成。内容不仅覆盖了人工神经网络的基本知识,如多层感知器、卷积网络、递归网络及增强学习等,还着重讲解了深度学习的一些新的技术,如生成对抗网络、学习方法和实践经验,配有许多应用及产品的实例。读者可从零开始掌握深度学习技术,以及使用TensorLayer实现的各种应用。 本书以通俗易懂的方式讲解深度学习技术,同时配有实现方法教学,面向深度学习初学者、进阶者,以及希望长期从事深度学习研究和产品开发的深度学习的大学生和工程师。

../_images/book_cover.jpeg ../_images/book_description.jpeg

在我们开始之前

本教程假定您在神经网络和TensorFlow方面具有一定的基础。在深度学习方面,这里推荐一些相关的网络资源。

对于人工神经网络更系统的介绍,我们推荐Andrej Karpathy等人所著的 Convolutional Neural Networks for Visual Recognition 、Michael Nielsen 的 Neural Networks and Deep LearningDeeplearning Tutorial

要了解TensorFlow的更多内容,请阅读 TensorFlow Tutorial 。 您不需要学会所有TensorFlow的细节,只需要知道TensorFlow大概是如何工作的,就能够使用TensorLayer。 如果您是深度学习新手,建议您阅读整个教程。

TensorLayer很简单

下面的代码是TensorLayer的一个简单例子,来自 tutorial_mnist_simple.py ,见`所有例子 <http://tensorlayer.readthedocs.io/en/latest/user/example.html>`_ 。 我们提供了很多方便的函数(如: fit()test() ),但如果您想了解更多实现细节,或想成为机器学习领域的专家,我们鼓励 您尽可能地直接使用数据迭代工具箱(tl.iternate)加上TensorFlow的操作(如: sess.run()) 来训练模型,请参考 tutorial_mlp_dropout1.pytutorial_mlp_dropout2.py <https://github.com/tensorlayer/tensorlayer/blob/master/example/tutorial_mlp_dropout2.py>_

import tensorflow as tf
import tensorlayer as tl

sess = tf.InteractiveSession()

# 准备数据
X_train, y_train, X_val, y_val, X_test, y_test = \
                                tl.files.load_mnist_dataset(shape=(-1,784))

# 定义 placeholder
x = tf.placeholder(tf.float32, shape=[None, 784], name='x')
y_ = tf.placeholder(tf.int64, shape=[None, ], name='y_')

# 定义模型
network = tl.layers.InputLayer(x, name='input_layer')
network = tl.layers.DropoutLayer(network, keep=0.8, name='drop1')
network = tl.layers.DenseLayer(network, n_units=800,
                                act = tf.nn.relu, name='relu1')
network = tl.layers.DropoutLayer(network, keep=0.5, name='drop2')
network = tl.layers.DenseLayer(network, n_units=800,
                                act = tf.nn.relu, name='relu2')
network = tl.layers.DropoutLayer(network, keep=0.5, name='drop3')
network = tl.layers.DenseLayer(network, n_units=10,
                                act = tf.identity,
                                name='output_layer')
# 定义损失函数和衡量指标
# tl.cost.cross_entropy 在内部使用 tf.nn.sparse_softmax_cross_entropy_with_logits() 实现 softmax
y = network.outputs
cost = tl.cost.cross_entropy(y, y_, name = 'cost')
correct_prediction = tf.equal(tf.argmax(y, 1), y_)
acc = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
y_op = tf.argmax(tf.nn.softmax(y), 1)

# 定义 optimizer
train_params = network.all_params
train_op = tf.train.AdamOptimizer(learning_rate=0.0001, beta1=0.9, beta2=0.999,
                            epsilon=1e-08, use_locking=False).minimize(cost, var_list=train_params)

# 初始化 session 中的所有参数
tl.layers.initialize_global_variables(sess)

# 列出模型信息
network.print_params()
network.print_layers()

# 训练模型
tl.utils.fit(sess, network, train_op, cost, X_train, y_train, x, y_,
            acc=acc, batch_size=500, n_epoch=500, print_freq=5,
            X_val=X_val, y_val=y_val, eval_train=False)

# 评估模型
tl.utils.test(sess, network, acc, X_test, y_test, x, y_, batch_size=None, cost=cost)

# 把模型保存成 .npz 文件
tl.files.save_npz(network.all_params , name='model.npz')
sess.close()

运行MNIST例子

../_images/mnist.jpeg

在本教程的第一部分,我们仅仅运行TensorLayer官方提供的MNIST例子。 MNIST数据集包含了60000个长宽各28像素的灰白手写数字图片,它通常用于测试小型神经网络图像分类的效果。

我们假设您已经按照 安装 Installation 安装好了TensorLayer。 如果您还没有,请从Github复制一个TensorLayer的目录到本地,进入该文件夹, 然后运行 tutorial_mnist.py 这个例子脚本:

python tutorial_mnist.py

如果所有设置都正确,您将得到下面的结果:

Downloading train-images-idx3-ubyte.gz
Downloading train-labels-idx1-ubyte.gz
Downloading t10k-images-idx3-ubyte.gz
Downloading t10k-labels-idx1-ubyte.gz

X_train.shape (50000, 784)
y_train.shape (50000,)
X_val.shape (10000, 784)
y_val.shape (10000,)
X_test.shape (10000, 784)
y_test.shape (10000,)
X float32   y int64

[TL] InputLayer input_layer (?, 784)
[TL] DropoutLayer drop1: keep: 0.800000
[TL] DenseLayer relu1: 800, relu
[TL] DropoutLayer drop2: keep: 0.500000
[TL] DenseLayer relu2: 800, relu
[TL] DropoutLayer drop3: keep: 0.500000
[TL] DenseLayer output_layer: 10, identity

param 0: (784, 800) (mean: -0.000053, median: -0.000043 std: 0.035558)
param 1: (800,)     (mean: 0.000000,  median: 0.000000  std: 0.000000)
param 2: (800, 800) (mean: 0.000008,  median: 0.000041  std: 0.035371)
param 3: (800,)     (mean: 0.000000,  median: 0.000000  std: 0.000000)
param 4: (800, 10)  (mean: 0.000469,  median: 0.000432  std: 0.049895)
param 5: (10,)      (mean: 0.000000,  median: 0.000000  std: 0.000000)
num of params: 1276810

layer 0: Tensor("dropout/mul_1:0", shape=(?, 784), dtype=float32)
layer 1: Tensor("Relu:0", shape=(?, 800), dtype=float32)
layer 2: Tensor("dropout_1/mul_1:0", shape=(?, 800), dtype=float32)
layer 3: Tensor("Relu_1:0", shape=(?, 800), dtype=float32)
layer 4: Tensor("dropout_2/mul_1:0", shape=(?, 800), dtype=float32)
layer 5: Tensor("add_2:0", shape=(?, 10), dtype=float32)

learning_rate: 0.000100
batch_size: 128

Epoch 1 of 500 took 0.342539s
  train loss: 0.330111
  val loss: 0.298098
  val acc: 0.910700
Epoch 10 of 500 took 0.356471s
  train loss: 0.085225
  val loss: 0.097082
  val acc: 0.971700
Epoch 20 of 500 took 0.352137s
  train loss: 0.040741
  val loss: 0.070149
  val acc: 0.978600
Epoch 30 of 500 took 0.350814s
  train loss: 0.022995
  val loss: 0.060471
  val acc: 0.982800
Epoch 40 of 500 took 0.350996s
  train loss: 0.013713
  val loss: 0.055777
  val acc: 0.983700
...

这个例子脚本允许您从 if__name__=='__main__': 中选择不同的模型进行尝试,包括多层神经网络(Multi-Layer Perceptron), 退出(Dropout),退出连接(DropConnect),堆栈式降噪自编码器(Stacked Denoising Autoencoder)和卷积神经网络(CNN)。

main_test_layers(model='relu')
main_test_denoise_AE(model='relu')
main_test_stacked_denoise_AE(model='relu')
main_test_cnn_layer()

理解MNIST例子

现在就让我们看看这些代码是如何工作的!

序言

您可能会首先注意到,除TensorLayer之外,我们还导入了Numpy和TensorFlow:

import time
import numpy as np
import tensorflow as tf
import tensorlayer as tl

这是因为TensorLayer是建立在TensorFlow上的,TensorLayer设计的初衷是为了简化工作并提供帮助而不是取代TensorFlow。 所以您会需要一起使用TensorLayer和一些常见的TensorFlow代码。

请注意,当使用降噪自编码器(Denoising Autoencoder)时,代码中的 set_keep 被当作用来访问保持概率(Keeping Probabilities)的占位符。

载入数据

下面第一部分的代码首先定义了 load_mnist_dataset() 函数。 其目的是为了自动下载MNIST数据集(如果还未下载),并且返回标准numpy数列通过numpy array的格式。 到这里还没有涉及TensorLayer。

X_train, y_train, X_val, y_val, X_test, y_test = \
                  tl.files.load_mnist_dataset(shape=(-1,784))

X_train.shape(50000,784),可以理解成共有50000张图片并且每张图片有784个数值(像素点)。 Y_train.shape(50000,) ,它是一个和 X_train 长度相同的向量,用于给出每幅图的数字标签,即这些图片所包含的位于0-9之间的10个数字。

另外对于卷积神经网络的例子,MNIST还可以按下面的4D版本来载入:

X_train, y_train, X_val, y_val, X_test, y_test = \
            tl.files.load_mnist_dataset(shape=(-1, 28, 28, 1))

X_train.shape(50000,28,28,1) ,这代表了50000张图片,每张图片有28行和28列。 通道为1是因为它是灰度图像,所以每个像素只能有一个值。

建立模型

来到这里,就轮到TensorLayer来一显身手了!TensorLayer允许您通过创建,堆叠或者合并图层(Layers)来定义任意结构的神经网络。 由于每一层都知道它在一个网络中的直接输入层和(多个)输出接收层,所以通常这是我们唯一要传递给其他代码的内容。

正如上文提到的, tutorial_mnist.py 有四个例子。 首先,我们将定义一个结构固定的多层次感知器(Multi-Layer Perceptron),所有的步骤都会详细的讲解。 然后,我们会实现一个去噪自编码器(Denosing Autoencoding)。 接着,我们要将所有去噪自编码器堆叠起来并对他们进行监督微调(Supervised Fine-tune)。 最后,我们将展示如何去创建一个卷积神经网络(Convolutional Neural Network)。

此外,如果您有兴趣,我们还提供了一个简化版的MNIST例子在 tutorial_mnist_simple.py 中,和一个对于 -10数据集的卷积神经网络(CNN)的例子在 tutorial_cifar10_tfrecord.py 中可供参考。

多层神经网络 (Multi-Layer Perceptron)

第一个脚本 main_test_layers() ,创建了一个具有两个隐藏层,每层800个单元的多层感知器,最后一层是10个单元的Softmax输出层。 它对输入数据采用保留80%数值的Dropout操作,并且对隐藏层使用50%的Dropout操作。这里并不会介绍Dropout原理,感兴趣的朋友可以在网上找到大量相关资料。

为了提供数据给这个网络,TensorFlow可以通过placeholder实现,需如下定义。 在这里 None 是指在编译之后,网络将接受任意批规模(Batch Size)的数据 x 是用来输入 X_train 数据的,而 y_ 是用来输入 y_train 数据的。 如果您想固定Batch Size,您可以把 None 代替为给定数值,这样可以运用TensorFlow一些优化功能,特别是当网络特别大的时候。

x  = tf.placeholder(tf.float32, shape=[None, 784], name='x')
y_ = tf.placeholder(tf.int64, shape=[None, ], name='y_')

在TensorLayer中每个神经网络的基础是一个 InputLayer 实例。它代表了将要提供给网络的输入数据。

network = tl.layers.InputLayer(x, name='input_layer')

在添加第一层隐藏层之前,我们要对输入数据进行Dropout操作。 这里我们通过一个 DropoutLayer 来实现。

network = tl.layers.DropoutLayer(network, keep=0.8, name='drop1')

请注意这里的第一个参数是输入层,第二个参数是保持概率(Keeping probability for the activation value),则数值不被置为零的概率。 现在我们要继续构造第一个800个单位的全连接的隐藏层。 尤其是当要堆叠一个 DenseLayer 时,要特别注意。

network = tl.layers.DenseLayer(network, n_units=800, act = tf.nn.relu, name='relu1')

同样,加一个新层时,我们在原来的 network 之上堆叠出新的 networkn_units 简明地给出了新全连接层的神经元单位数。 act 指定了一个激活函数,这里的激活函数有一部分已经被定义在了 tensorflow.nntensorlayer.activation 中。 我们在这里选择了整流器(Rectifier)作为激活函数。 接着继续添加50%的Dropout层,以及另外800个单元的全链接层(Dense layer),和50%的Dropout层:

network = tl.layers.DropoutLayer(network, keep=0.5, name='drop2')
network = tl.layers.DenseLayer(network, n_units=800, act = tf.nn.relu, name='relu2')
network = tl.layers.DropoutLayer(network, keep=0.5, name='drop3')

最后,我们加入 n_units 等于分类个数的全连接的输出层。注意, cost = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(y, y_)) 在内部实现 Softmax,以提高计算效率,因此最后一层的输出为 identity ,更多细节请参考 tl.cost.cross_entropy()

network = tl.layers.DenseLayer(network,
                              n_units=10,
                              act = tl.act.identity,
                              name='output_layer')

如上所述,因为每一层都被链接到了它的输入层,所以我们只需要在TensorLayer中将输出层接入一个网络:

y = network.outputs
y_op = tf.argmax(tf.nn.softmax(y), 1)
cost = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(y, y_))

在这里,network.outputs 是网络的10个种类的输出概率(以One-hot的形式)。 y_op 是代表类索引的整数输出, cost 是目标和预测标签的交叉熵(Cross Entropy)。

降噪自编码器(Denoising Autoencoder)

自编码器是一种无监督学习(Unsupervisered Learning)模型,可从数据中学习出更好的表达, 目前已经用于逐层贪婪的预训练(Greedy layer-wise pre-train)。 有关Vanilla自编码器,请参考教程 Deeplearning Tutorial

tutorial_mnist.pymain_test_denoise_AE() 实现了50%的腐蚀率(Corrosion Rate)的去噪自编码器。 这个自编码器可以按如下方式定义,这里的使用全链接层来搭建一个自编码器:

network = tl.layers.InputLayer(x, name='input_layer')
network = tl.layers.DropoutLayer(network, keep=0.5, name='denoising1')
network = tl.layers.DenseLayer(network, n_units=200, act=tf.nn.sigmoid, name='sigmoid1')
recon_layer1 = tl.layers.ReconLayer(network,
                                    x_recon=x,
                                    n_units=784,
                                    act=tf.nn.sigmoid,
                                    name='recon_layer1')

训练 DenseLayer ,只需要运行 ReconLayer.pretrain() 即可。 如果要使用去噪自编码器,可以使用 DropoutLayer 作为腐蚀层(Corrosion layer)。

对于Sigmoid型激活函数来说,自编码器可以用KL散度来实现。 而对于整流器(Rectifier)来说,对激活函数输出的L1正则化能使得输出变得稀疏。 所以 ReconLayer 默认只对整流激活函数(ReLU)提供KL散度和交叉熵这两种损失度量,而对Sigmoid型激活函数提供均方误差以及激活输出的L1范数这两种损失度量。 我们建议您修改 ReconLayer 来实现自己的预训练方式。

recon_layer1.pretrain(sess,
                      x=x,
                      X_train=X_train,
                      X_val=X_val,
                      denoise_name='denoising1',
                      n_epoch=200,
                      batch_size=128,
                      print_freq=10,
                      save=True,
                      save_name='w1pre_')

此外,脚本 main_test_stacked_denoise_AE() 展示了如何将多个自编码器堆叠到一个网络,然后进行微调。

卷积神经网络(Convolutional Neural Network)

tutorial_mnist.pymain_test_cnn_layer() 创建了如下的一个卷积网络分类器。

network = tl.layers.Conv2d(network, 32, (5, 5), (1, 1),
        act=tf.nn.relu, padding='SAME', name='cnn1')
network = tl.layers.MaxPool2d(network, (2, 2), (2, 2),
        padding='SAME', name='pool1')
network = tl.layers.Conv2d(network, 64, (5, 5), (1, 1),
        act=tf.nn.relu, padding='SAME', name='cnn2')
network = tl.layers.MaxPool2d(network, (2, 2), (2, 2),
        padding='SAME', name='pool2')

network = tl.layers.FlattenLayer(network, name='flatten')
network = tl.layers.DropoutLayer(network, keep=0.5, name='drop1')
network = tl.layers.DenseLayer(network, 256, act=tf.nn.relu, name='relu1')
network = tl.layers.DropoutLayer(network, keep=0.5, name='drop2')
network = tl.layers.DenseLayer(network, 10, act=tf.identity, name='output')

训练模型

tutorial_mnist.py 脚本的其余部分,在MNIST数据上对于只使用交叉熵的循环训练进行了设置并且运行。

数据集迭代

一个在给定的项目数的最小批规模下的输入特征及其对应的标签的两个Numpy数列依次同步的迭代函数。 更多有关迭代函数的说明,可以在 tensorlayer.iterate 中找到。

tl.iterate.minibatches(inputs, targets, batchsize, shuffle=False)

损失和更新公式

我们继续创建一个在训练中被最小化的损失表达式:

y = network.outputs
y_op = tf.argmax(tf.nn.softmax(y), 1)
cost = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(y, y_))

更多的损失函数或者正则化方法可以在这里定义。比如,如果要在权重矩阵中应用最大范数(Max-norm)方法,您可以添加下列代码。

cost = cost + tl.cost.maxnorm_regularizer(1.0)(network.all_params[0]) +
              tl.cost.maxnorm_regularizer(1.0)(network.all_params[2])

根据要解决的问题,您会需要使用不同的损失函数,更多有关损失函数的说明请见: tensorlayer.cost 除了通过 network.all_params 来获取网络参数,您还可以通过 tl.layers.get_variables_with_name 来通过字符串方式获取指定的参数。

有了模型和定义的损失函数之后,我们就可以创建用于训练网络的更新公式。 接下去,我们将使用TensorFlow的优化器如下:

train_params = network.all_params
train_op = tf.train.AdamOptimizer(learning_rate, beta1=0.9, beta2=0.999,
    epsilon=1e-08, use_locking=False).minimize(cost, var_list=train_params)

为了训练网络,我们需要提供数据和保持概率给 feed_dict

feed_dict = {x: X_train_a, y_: y_train_a}
feed_dict.update( network.all_drop )
sess.run(train_op, feed_dict=feed_dict)

同时为了进行验证和测试,我们这里用了略有不同的方法。 所有的Dropout,DropConnect,腐蚀层(Corrosion Layers)都将被禁用。 tl.utils.dict_to_one 将会设置所有 network.all_drop 值为1。

dp_dict = tl.utils.dict_to_one( network.all_drop )
feed_dict = {x: X_test_a, y_: y_test_a}
feed_dict.update(dp_dict)
err, ac = sess.run([cost, acc], feed_dict=feed_dict)

最后,作为一个额外的监测量,我们需要创建一个分类准确度的公式:

correct_prediction = tf.equal(tf.argmax(y, 1), y_)
acc = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

下一步?

tutorial_cifar10_tfrecord.py 中我们还有更复杂的图像分类的例子。 请阅读代码及注释,以明白如数据增强(Data Augmentation)的重要性,以及什么是局部响应正则化。 此外,您可以尝试着去实现 残差网络(Residual Network)

运行乒乓球例子

在本教程的第二部分,我们将运行一个深度强化学习的例子,它在Karpathy的两篇博客 Deep Reinforcement Learning:Pong from Pixels 有介绍。

python tutorial_atari_pong.py

在运行教程代码之前 您需要安装 OpenAI gym environment ,它提供了大量强化学习常用的游戏环境。 如果一切运行正常,您将得到以下的输出:

[2016-07-12 09:31:59,760] Making new env: Pong-v0
  [TL] InputLayer input_layer (?, 6400)
  [TL] DenseLayer relu1: 200, relu
  [TL] DenseLayer output_layer: 3, identity
  param 0: (6400, 200) (mean: -0.000009  median: -0.000018 std: 0.017393)
  param 1: (200,)      (mean: 0.000000   median: 0.000000  std: 0.000000)
  param 2: (200, 3)    (mean: 0.002239   median: 0.003122  std: 0.096611)
  param 3: (3,)        (mean: 0.000000   median: 0.000000  std: 0.000000)
  num of params: 1280803
  layer 0: Tensor("Relu:0", shape=(?, 200), dtype=float32)
  layer 1: Tensor("add_1:0", shape=(?, 3), dtype=float32)
episode 0: game 0 took 0.17381s, reward: -1.000000
episode 0: game 1 took 0.12629s, reward: 1.000000  !!!!!!!!
episode 0: game 2 took 0.17082s, reward: -1.000000
episode 0: game 3 took 0.08944s, reward: -1.000000
episode 0: game 4 took 0.09446s, reward: -1.000000
episode 0: game 5 took 0.09440s, reward: -1.000000
episode 0: game 6 took 0.32798s, reward: -1.000000
episode 0: game 7 took 0.74437s, reward: -1.000000
episode 0: game 8 took 0.43013s, reward: -1.000000
episode 0: game 9 took 0.42496s, reward: -1.000000
episode 0: game 10 took 0.37128s, reward: -1.000000
episode 0: game 11 took 0.08979s, reward: -1.000000
episode 0: game 12 took 0.09138s, reward: -1.000000
episode 0: game 13 took 0.09142s, reward: -1.000000
episode 0: game 14 took 0.09639s, reward: -1.000000
episode 0: game 15 took 0.09852s, reward: -1.000000
episode 0: game 16 took 0.09984s, reward: -1.000000
episode 0: game 17 took 0.09575s, reward: -1.000000
episode 0: game 18 took 0.09416s, reward: -1.000000
episode 0: game 19 took 0.08674s, reward: -1.000000
episode 0: game 20 took 0.09628s, reward: -1.000000
resetting env. episode reward total was -20.000000. running mean: -20.000000
episode 1: game 0 took 0.09910s, reward: -1.000000
episode 1: game 1 took 0.17056s, reward: -1.000000
episode 1: game 2 took 0.09306s, reward: -1.000000
episode 1: game 3 took 0.09556s, reward: -1.000000
episode 1: game 4 took 0.12520s, reward: 1.000000  !!!!!!!!
episode 1: game 5 took 0.17348s, reward: -1.000000
episode 1: game 6 took 0.09415s, reward: -1.000000

这个例子让神经网络通过游戏画面来学习如何像人类一样打乒乓球。神经网络将于伪AI电脑对战不断地对战,最后学会战胜它。 在经过15000个序列的训练之后,神经网络就可以赢得20%的比赛。 在20000个序列的训练之后,神经网络可以赢得35%的比赛, 我们可以看到计算机学的越来越快,这是因为它有更多的胜利的数据来进行训练。 训练了30000个序列后,神经网络再也不会输了。

render = False
resume = False

如果您想显示游戏过程,那就设置 renderTrue 。 当您再次运行该代码,您可以设置 resumeTrue,那么代码将加载现有的模型并且会基于它继续训练。

../_images/pong_game.jpeg

理解强化学习

乒乓球

要理解强化学习,我们要让电脑学习如何从原始的屏幕输入(像素输入)打乒乓球。 在我们开始之前,我们强烈建议您去浏览一个著名的博客叫做 Deep Reinforcement Learning:pong from Pixels , 这是使用python numpy库和OpenAI gym environment来实现的一个深度强化学习的例子。

python tutorial_atari_pong.py

策略网络(Policy Network)

在深度强化学习中,Policy Network 等同于深度神经网络。 它是我们的选手(或者说“代理人(Agent)“),它的输出告诉我们应该做什么(如:向上移动或向下移动): 在Karpathy的代码中,他只定义了2个动作,向上移动和向下移动,并且仅使用单个Simgoid输出: 为了使我们的教程更具有普遍性,我们使用3个Softmax输出来定义向上移动,向下移动和停止(什么都不做)3个动作。

# observation for training
states_batch_pl = tf.placeholder(tf.float32, shape=[None, D])

network = tl.layers.InputLayer(states_batch_pl, name='input_layer')
network = tl.layers.DenseLayer(network, n_units=H,
                                act = tf.nn.relu, name='relu1')
network = tl.layers.DenseLayer(network, n_units=3,
                        act = tl.activation.identity, name='output_layer')
probs = network.outputs
sampling_prob = tf.nn.softmax(probs)

然后我们的代理人就一直与伪AI对战。它计算不同动作的概率,并且之后会从这个均匀的分布中选取样本(动作)。 因为动作被1,2和3代表,但是Softmax输出应该从0开始,所以我们会把动作索引减去1来作为网络输出。

prob = sess.run(
    sampling_prob,
    feed_dict={states_batch_pl: x}
)
# action. 1: STOP  2: UP  3: DOWN
action = np.random.choice([1,2,3], p=prob.flatten())
...
ys.append(action - 1)

策略逼近(Policy Gradient)

策略梯度下降法是一个end-to-end的算法,它直接学习从状态映射到动作的策略函数。 一个近似最优的策略可以通过最大化预期的奖励来直接学习。 策略函数的参数(例如,在乒乓球例子终使用的策略网络的参数)在预期奖励的近似值的引导下能够被训练和学习。 换句话说,我们可以通过过更新它的参数来逐步调整策略函数,这样它能从给定的状态做出一系列行为来获得更高的奖励。

策略迭代的一个替代算法就是深度Q-learning(DQN)。 他是基于Q-learning,学习一个映射状态和动作到一些值的价值函数的算法(叫Q函数)。 DQN采用了一个深度神经网络来作为Q函数的逼近来代表Q函数。 训练是通过最小化时序差分(Temporal-Difference)误差来实现。 一个名为“再体验(Experience Replay)“的神经生物学的启发式机制通常和DQN一起被使用来帮助提高非线性函数的逼近的稳定性。

您可以阅读以下文档,来得到对强化学习更好的理解:

强化深度学习近些年来最成功的应用就是让模型去学习玩Atari的游戏。 AlphaGO同时也是使用类似的策略逼近方法来训练他们的策略网络而战胜了世界级的专业围棋选手。

数据集迭代

在强化学习中,我们把每场比赛所产生的所有决策来作为一个序列 (up,up,stop,...,down)。在乒乓球游戏中,比赛是在某一方达到21分后结束的,所以一个序列可能包含几十个决策。 然后我们可以设置一个批规模的大小,每一批包含一定数量的序列,基于这个批规模来更新我们的模型。 在本教程中,我们把每批规模设置成10个序列。使用RMSProp训练一个具有200个单元的隐藏层的2层策略网络

损失和更新公式

接着我们创建一个在训练中被最小化的损失公式:

actions_batch_pl = tf.placeholder(tf.int32, shape=[None])
discount_rewards_batch_pl = tf.placeholder(tf.float32, shape=[None])
loss = tl.rein.cross_entropy_reward_loss(probs, actions_batch_pl,
                                              discount_rewards_batch_pl)
...
...
sess.run(
    train_op,
    feed_dict={
        states_batch_pl: epx,
        actions_batch_pl: epy,
        discount_rewards_batch_pl: disR
    }
)

一个batch的损失和一个batch内的策略网络的所有输出,所有的我们做出的动作和相应的被打折的奖励有关 我们首先通过累加被打折的奖励和实际输出和真实动作的交叉熵计算每一个动作的损失。 最后的损失是所有动作的损失的和。

下一步?

上述教程展示了您如何去建立自己的代理人,end-to-end。 虽然它有很合理的品质,但它的默认参数不会给您最好的代理人模型。 这有一些您可以优化的内容。

首先,与传统的MLP模型不同,比起 Playing Atari with Deep Reinforcement Learning 更好的是我们可以使用CNNs来采集屏幕信息

另外这个模型默认参数没有调整,您可以更改学习率,衰退率,或者用不同的方式来初始化您的模型的权重。

最后,您可以尝试不同任务,以及学习其他增强学习算法,请见`Example <http://tensorlayer.readthedocs.io/en/latest/user/example.html>`_ 。

运行Word2Vec例子

在教程的这一部分,我们训练一个词嵌套矩阵,每个词可以通过矩阵中唯一的行向量来表示。 在训练结束时,含义类似的单词会有相识的词向量输出。 在代码的最后,我们通过把单词放到一个2D平面上来可视化,我们可以看到相似的单词会被聚集在一起。

python tutorial_word2vec_basic.py

如果一切设置正确,您最后会得到如下的可视化图。

../_images/tsne.png

理解词嵌套(word embedding)

词嵌套(嵌入)

我们强烈建议您先阅读Colah的博客 Word Representations [中文翻译] , 以理解为什么我们要使用一个向量来表示一个单词。更多Word2vec的细节可以在 Word2vec Parameter Learning Explained 中找到。

基本来说,训练一个嵌套矩阵是一个非监督学习的过程。一个单词使用唯一的ID来表示,而这个ID号就是嵌套矩阵的行号(row index),对应的行向量就是用来表示该单词的,使用向量来表示单词可以更好地表达单词的意思。比如,有4个单词的向量, woman man = queen - king ,这个例子中可以看到,嵌套矩阵中有一个纬度是用来表示性别的。

定义一个Word2vec词嵌套矩阵如下。

# train_inputs is a row vector, a input is an integer id of single word.
# train_labels is a column vector, a label is an integer id of single word.
# valid_dataset is a column vector, a valid set is an integer id of single word.
train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)

# Look up embeddings for inputs.
emb_net = tl.layers.Word2vecEmbeddingInputlayer(
        inputs = train_inputs,
        train_labels = train_labels,
        vocabulary_size = vocabulary_size,
        embedding_size = embedding_size,
        num_sampled = num_sampled,
        nce_loss_args = {},
        E_init = tf.random_uniform_initializer(minval=-1.0, maxval=1.0),
        E_init_args = {},
        nce_W_init = tf.truncated_normal_initializer(
                          stddev=float(1.0/np.sqrt(embedding_size))),
        nce_W_init_args = {},
        nce_b_init = tf.constant_initializer(value=0.0),
        nce_b_init_args = {},
        name ='word2vec_layer',
    )

数据迭代和损失函数

Word2vec使用负采样(Negative sampling)和Skip-gram模型进行训练。 噪音对比估计损失(NCE)会帮助减少损失函数的计算量,加快训练速度。 Skip-Gram 将文本(context)和目标(target)反转,尝试从目标单词预测目标文本单词。 我们使用 tl.nlp.generate_skip_gram_batch 函数来生成训练数据,如下:

# NCE损失函数由 Word2vecEmbeddingInputlayer 提供
cost = emb_net.nce_cost
train_params = emb_net.all_params

train_op = tf.train.AdagradOptimizer(learning_rate, initial_accumulator_value=0.1,
          use_locking=False).minimize(cost, var_list=train_params)

data_index = 0
while (step < num_steps):
  batch_inputs, batch_labels, data_index = tl.nlp.generate_skip_gram_batch(
                data=data, batch_size=batch_size, num_skips=num_skips,
                skip_window=skip_window, data_index=data_index)
  feed_dict = {train_inputs : batch_inputs, train_labels : batch_labels}
  _, loss_val = sess.run([train_op, cost], feed_dict=feed_dict)

加载已训练好的的词嵌套矩阵

在训练嵌套矩阵的最后,我们保存矩阵及其词汇表、单词转ID字典、ID转单词字典。 然后,当下次做实际应用时,可以想下面的代码中那样加载这个已经训练好的矩阵和字典, 参考 tutorial_generate_text.py

vocabulary_size = 50000
embedding_size = 128
model_file_name = "model_word2vec_50k_128"
batch_size = None

print("Load existing embedding matrix and dictionaries")
all_var = tl.files.load_npy_to_any(name=model_file_name+'.npy')
data = all_var['data']; count = all_var['count']
dictionary = all_var['dictionary']
reverse_dictionary = all_var['reverse_dictionary']

tl.nlp.save_vocab(count, name='vocab_'+model_file_name+'.txt')

del all_var, data, count

load_params = tl.files.load_npz(name=model_file_name+'.npz')

x = tf.placeholder(tf.int32, shape=[batch_size])
y_ = tf.placeholder(tf.int32, shape=[batch_size, 1])

emb_net = tl.layers.EmbeddingInputlayer(
                inputs = x,
                vocabulary_size = vocabulary_size,
                embedding_size = embedding_size,
                name ='embedding_layer')

tl.layers.initialize_global_variables(sess)

tl.files.assign_params(sess, [load_params[0]], emb_net)

运行PTB例子

Penn TreeBank(PTB)数据集被用在很多语言建模(Language Modeling)的论文中,包括"Empirical Evaluation and Combination of Advanced Language Modeling Techniques"和 “Recurrent Neural Network Regularization”。该数据集的训练集有929k个单词,验证集有73K个单词,测试集有82k个单词。 在它的词汇表刚好有10k个单词。

PTB例子是为了展示如何用递归神经网络(Recurrent Neural Network)来进行语言建模的。

给一句话 "I am from Imperial College London", 这个模型可以从中学习出如何从“from Imperial College”来预测出“Imperial College London”。也就是说,它根据之前输入的单词序列来预测出下一步输出的单词序列,在刚才的例子中 num_steps (序列长度,sequence length) 为 3。

python tutorial_ptb_lstm.py

该脚本提供三种设置(小,中,大),越大的模型有越好的建模性能,您可以修改下面的代码片段来选择不同的模型设置。

flags.DEFINE_string(
    "model", "small",
    "A type of model. Possible options are: small, medium, large.")

如果您选择小设置,您将会看到:

Epoch: 1 Learning rate: 1.000
0.004 perplexity: 5220.213 speed: 7635 wps
0.104 perplexity: 828.871 speed: 8469 wps
0.204 perplexity: 614.071 speed: 8839 wps
0.304 perplexity: 495.485 speed: 8889 wps
0.404 perplexity: 427.381 speed: 8940 wps
0.504 perplexity: 383.063 speed: 8920 wps
0.604 perplexity: 345.135 speed: 8920 wps
0.703 perplexity: 319.263 speed: 8949 wps
0.803 perplexity: 298.774 speed: 8975 wps
0.903 perplexity: 279.817 speed: 8986 wps
Epoch: 1 Train Perplexity: 265.558
Epoch: 1 Valid Perplexity: 178.436
...
Epoch: 13 Learning rate: 0.004
0.004 perplexity: 56.122 speed: 8594 wps
0.104 perplexity: 40.793 speed: 9186 wps
0.204 perplexity: 44.527 speed: 9117 wps
0.304 perplexity: 42.668 speed: 9214 wps
0.404 perplexity: 41.943 speed: 9269 wps
0.504 perplexity: 41.286 speed: 9271 wps
0.604 perplexity: 39.989 speed: 9244 wps
0.703 perplexity: 39.403 speed: 9236 wps
0.803 perplexity: 38.742 speed: 9229 wps
0.903 perplexity: 37.430 speed: 9240 wps
Epoch: 13 Train Perplexity: 36.643
Epoch: 13 Valid Perplexity: 121.475
Test Perplexity: 116.716

PTB例子证明了递归神经网络能够实现语言建模,但是这个例子并没有做什么实际的事情。 在做具体应用之前,您应该浏览这个例子的代码和下一章 “理解 LSTM” 来学好递归神经网络的基础。 之后,您将学习如何用递归神经网络来生成文本,如何实现语言翻译和问题应答系统。

理解LSTM

递归神经网络 (Recurrent Neural Network)

我们认为Andrey Karpathy的博客 Understand Recurrent Neural Network 是了解递归神经网络最好的材料。 读完这个博客后,Colah的博客 Understand LSTM Network 能帮助您了解LSTM。 我们在这里不介绍更多关于递归神经网络的内容,所以在您继续下面的内容之前,请先阅读我们建议阅读的博客。

../_images/karpathy_rnn.jpeg

图片由Andrey Karpathy提供

同步输入与输出序列 (Synced sequence input and output)

PTB例子中的模型是一个典型的同步输入与输出,Karpathy 把它描述为 “(5) 同步序列输入与输出(例如视频分类中我们希望对每一帧进行标记)。“

模型的构建如下,第一层是词嵌套层(嵌入),把每一个单词转换成对应的词向量,在该例子中没有使用预先训练好的 嵌套矩阵。第二,堆叠两层LSTM,使用Dropout来实现规则化,防止overfitting。 最后,使用全连接层输出一序列的softmax输出。

第一层LSTM的输出形状是 [batch_size, num_steps, hidden_size],这是为了让下一层LSTM可以堆叠在其上面。 第二层LSTM的输出形状是 [batch_size*num_steps, hidden_size],这是为了让输出层(全连接层 Dense)可以堆叠在其上面。 然后计算每个样本的softmax输出,样本总数为 n_examples = batch_size*num_steps。

若想要更进一步理解该PTB教程,您也可以阅读 TensorFlow 官方的PTB教程 ,中文翻译请见极客学院。

network = tl.layers.EmbeddingInputlayer(
            inputs = x,
            vocabulary_size = vocab_size,
            embedding_size = hidden_size,
            E_init = tf.random_uniform_initializer(-init_scale, init_scale),
            name ='embedding_layer')
if is_training:
    network = tl.layers.DropoutLayer(network, keep=keep_prob, name='drop1')
network = tl.layers.RNNLayer(network,
            cell_fn=tf.nn.rnn_cell.BasicLSTMCell,
            cell_init_args={'forget_bias': 0.0},
            n_hidden=hidden_size,
            initializer=tf.random_uniform_initializer(-init_scale, init_scale),
            n_steps=num_steps,
            return_last=False,
            name='basic_lstm_layer1')
lstm1 = network
if is_training:
    network = tl.layers.DropoutLayer(network, keep=keep_prob, name='drop2')
network = tl.layers.RNNLayer(network,
            cell_fn=tf.nn.rnn_cell.BasicLSTMCell,
            cell_init_args={'forget_bias': 0.0},
            n_hidden=hidden_size,
            initializer=tf.random_uniform_initializer(-init_scale, init_scale),
            n_steps=num_steps,
            return_last=False,
            return_seq_2d=True,
            name='basic_lstm_layer2')
lstm2 = network
if is_training:
    network = tl.layers.DropoutLayer(network, keep=keep_prob, name='drop3')
network = tl.layers.DenseLayer(network,
            n_units=vocab_size,
            W_init=tf.random_uniform_initializer(-init_scale, init_scale),
            b_init=tf.random_uniform_initializer(-init_scale, init_scale),
            act = tl.activation.identity, name='output_layer')

数据迭代

batch_size 数值可以被视为并行计算的数量。 如下面的例子所示,第一个 batch 使用 0 到 9 来学习序列信息。 第二个 batch 使用 10 到 19 来学习序列。 所以它忽略了 9 到 10 之间的信息。 只当我们 bath_size 设为 1,它才使用 0 到 20 之间所有的序列信息来学习。

这里的 batch_size 的意思与 MNIST 例子略有不同。 在 MNIST 例子,batch_size 是每次迭代中我们使用的样本数量, 而在 PTB 的例子中,batch_size 是为加快训练速度的并行进程数。

虽然当 batch_size > 1 时有些信息将会被忽略, 但是如果您的数据是足够长的(一个语料库通常有几十亿个字),被忽略的信息不会影响最终的结果。

在PTB教程中,我们设置了 batch_size = 20,所以,我们将整个数据集拆分成 20 段(segment)。 在每一轮(epoch)的开始时,我们有 20 个初始化的 LSTM 状态(State),然后分别对 20 段数据进行迭代学习。

训练数据迭代的例子如下:

train_data = [i for i in range(20)]
for batch in tl.iterate.ptb_iterator(train_data, batch_size=2, num_steps=3):
    x, y = batch
    print(x, '\n',y)
... [[ 0  1  2] <---x                       1st subset/ iteration
...  [10 11 12]]
... [[ 1  2  3] <---y
...  [11 12 13]]
...
... [[ 3  4  5]  <--- 1st batch input       2nd subset/ iteration
...  [13 14 15]] <--- 2nd batch input
... [[ 4  5  6]  <--- 1st batch target
...  [14 15 16]] <--- 2nd batch target
...
... [[ 6  7  8]                             3rd subset/ iteration
...  [16 17 18]]
... [[ 7  8  9]
...  [17 18 19]]

注解

这个例子可以当作词嵌套矩阵的预训练。

损失和更新公式

损失函数是一系列输出cross entropy的均值。

# 更多细节请见 tensorlayer.cost.cross_entropy_seq()
def loss_fn(outputs, targets, batch_size, num_steps):
    # Returns the cost function of Cross-entropy of two sequences, implement
    # softmax internally.
    # outputs : 2D tensor [batch_size*num_steps, n_units of output layer]
    # targets : 2D tensor [batch_size, num_steps], need to be reshaped.
    # n_examples = batch_size * num_steps
    # so
    # cost is the averaged cost of each mini-batch (concurrent process).
    loss = tf.nn.seq2seq.sequence_loss_by_example(
        [outputs],
        [tf.reshape(targets, [-1])],
        [tf.ones([batch_size * num_steps])])
    cost = tf.reduce_sum(loss) / batch_size
    return cost

# Cost for Training
cost = loss_fn(network.outputs, targets, batch_size, num_steps)

在训练时,该例子在若干个epoch之后(由 max_epoch 定义),才开始按比例下降学习率(learning rate),新学习率是前一个epoch的学习率乘以一个下降率(由 lr_decay 定义)。 此外,截断反向传播(truncated backpropagation)截断了

为使学习过程易于处理,通常的做法是将反向传播的梯度在(按时间)展开的步骤上照一个固定长度( num_steps )截断。 通过在一次迭代中的每个时刻上提供长度为 num_steps 的输入和每次迭代完成之后反向传导,这会很容易实现。

# 截断反响传播 Truncated Backpropagation for training
with tf.variable_scope('learning_rate'):
    lr = tf.Variable(0.0, trainable=False)
tvars = tf.trainable_variables()
grads, _ = tf.clip_by_global_norm(tf.gradients(cost, tvars),
                                  max_grad_norm)
optimizer = tf.train.GradientDescentOptimizer(lr)
train_op = optimizer.apply_gradients(zip(grads, tvars))

如果当前epoch值大于 max_epoch ,则把当前学习率乘以 lr_decay 来降低学习率。

new_lr_decay = lr_decay ** max(i - max_epoch, 0.0)
sess.run(tf.assign(lr, learning_rate * new_lr_decay))

在每一个epoch的开始之前,LSTM的状态要被重置为零状态;在每一个迭代之后,LSTM状态都会被改变,所以要把最新的LSTM状态 作为下一个迭代的初始化状态。

# 在每一个epoch之前,把所有LSTM状态设为零状态
state1 = tl.layers.initialize_rnn_state(lstm1.initial_state)
state2 = tl.layers.initialize_rnn_state(lstm2.initial_state)
for step, (x, y) in enumerate(tl.iterate.ptb_iterator(train_data,
                                            batch_size, num_steps)):
    feed_dict = {input_data: x, targets: y,
                lstm1.initial_state: state1,
                lstm2.initial_state: state2,
                }
    # 启用dropout
    feed_dict.update( network.all_drop )
    # 把新的状态作为下一个迭代的初始状态
    _cost, state1, state2, _ = sess.run([cost,
                                    lstm1.final_state,
                                    lstm2.final_state,
                                    train_op],
                                    feed_dict=feed_dict
                                    )
    costs += _cost; iters += num_steps

预测

在训练完模型之后,当我们预测下一个输出时,我们不需要考虑序列长度了,因此 batch_sizenum_steps 都设为 1 。 然后,我们可以一步一步地输出下一个单词,而不是通过一序列的单词来输出一序列的单词。

input_data_test = tf.placeholder(tf.int32, [1, 1])
targets_test = tf.placeholder(tf.int32, [1, 1])
...
network_test, lstm1_test, lstm2_test = inference(input_data_test,
                      is_training=False, num_steps=1, reuse=True)
...
cost_test = loss_fn(network_test.outputs, targets_test, 1, 1)
...
print("Evaluation")
# 测试
# go through the test set step by step, it will take a while.
start_time = time.time()
costs = 0.0; iters = 0
# 与训练时一样,设置所有LSTM状态为零状态
state1 = tl.layers.initialize_rnn_state(lstm1_test.initial_state)
state2 = tl.layers.initialize_rnn_state(lstm2_test.initial_state)
for step, (x, y) in enumerate(tl.iterate.ptb_iterator(test_data,
                                        batch_size=1, num_steps=1)):
    feed_dict = {input_data_test: x, targets_test: y,
                lstm1_test.initial_state: state1,
                lstm2_test.initial_state: state2,
                }
    _cost, state1, state2 = sess.run([cost_test,
                                    lstm1_test.final_state,
                                    lstm2_test.final_state],
                                    feed_dict=feed_dict
                                    )
    costs += _cost; iters += 1
test_perplexity = np.exp(costs / iters)
print("Test Perplexity: %.3f took %.2fs" % (test_perplexity, time.time() - start_time))

下一步?

您已经明白了同步序列输入和序列输出(Synced sequence input and output)。 现在让我们思考下序列输入单一输出的情况(Sequence input and one output), LSTM 也可以学会通过给定一序列输入如 “我来自北京,我会说.." 来输出 一个单词 "中文"。

请仔细阅读并理解 tutorial_generate_text.py 的代码,它讲了如何加载一个已经训练好的词嵌套矩阵, 以及如何给定机器一个文档,让它来学习文字自动生成。

Karpathy的博客: "(3) Sequence input (e.g. sentiment analysis where a given sentence is classified as expressing positive or negative sentiment). "

更多经典教程

您能在例子页面找到包括Seq2seq, 各类对抗学习和增强学习的例子。

翻译对照

Stacked Denosing Autoencoder 堆栈式降噪自编吗器

Word Embedding 词嵌套、词嵌入

Iteration 迭代

Natural Language Processing 自然语言处理

Sparse 稀疏的

Cost function 损失函数

Regularization 规则化、正则化

Tokenization 数字化

Truncated backpropagation 截断反向传播

更多信息

TensorLayer 还能做什么?请继续阅读本文档。

最后,API 参考列表和说明如下:

layers (tensorlayer.layers),

activation (tensorlayer.activation),

natural language processing (tensorlayer.nlp),

reinforcement learning (tensorlayer.rein),

cost expressions and regularizers (tensorlayer.cost),

load and save files (tensorlayer.files),

operating system (tensorlayer.ops),

helper functions (tensorlayer.utils),

visualization (tensorlayer.visualize),

iteration functions (tensorlayer.iterate),

preprocessing functions (tensorlayer.prepro),