摘要:本文主要和大家分享如何使用Tensorflow从头开始构建和训练卷积神经网络。这样就可以将这个知识作为一个构建块来创造有趣的深度学习应用程序了。
0. 简介
在过去,我写的主要都是“传统类”的机器学习文章,如朴素贝叶斯分类、逻辑回归和Perceptron算法。在过去的一年中,我一直在研究深度学习技术,因此,我想和大家分享一下如何使用Tensorflow从头开始构建和训练卷积神经网络。这样,我们以后就可以将这个知识作为一个构建块来创造有趣的深度学习应用程序了。
为此,你需要安装Tensorflow,你还应该对Python编程和卷积神经网络背后的理论有一个基本的了解。安装完Tensorflow之后,你可以在不依赖GPU的情况下运行一个较小的神经网络,但对于更深层次的神经网络,就需要用到GPU的计算能力了。
在互联网上有很多解释卷积神经网络工作原理方面的网站和课程,其中有一些还是很不错的,图文并茂、易于理解。我在这里就不再解释相同的东西,所以在开始阅读下文之前,请提前了解卷积神经网络的工作原理。例如:
1. 什么是卷积层,卷积层的过滤器是什么?
2. 什么是激活层(ReLu层(应用最广泛的)、S型激活或tanh)?
3. 什么是池层(最大池/平均池),什么是dropout?
4. 随机梯度下降的工作原理是什么?
本文内容如下:
1. Tensorflow基础
(1)1.1 常数和变量
(2)1.2 Tensorflow中的图和会话
(3)1.3 占位符和feed_dicts
2. Tensorflow中的神经网络
(1)2.1 介绍
(2)2.2 数据加载
(3)2.3 创建一个简单的一层神经网络
(4)2.4 Tensorflow的多个方面
(5)2.5 创建LeNet5卷积神经网络
(6)2.6 影响层输出大小的参数
(7)2.7 调整LeNet5架构
(8)2.8 学习速率和优化器的影响
3. Tensorflow中的深度神经网络
(1)3.1 AlexNet
(2)3.2 VGG Net-16
(3)3.3 AlexNet性能
4. 结语
1. Tensorflow 基础
在这里,我将向以前从未使用过Tensorflow的人做一个简单的介绍。如果你想要立即开始构建神经网络,或者已经熟悉Tensorflow,可以直接跳到第2节。
1.1 常量与变量
Tensorflow中最基本的单元是常量、变量和占位符。
tf.constant()和tf.Variable()之间的区别很清楚;一个常量有着恒定不变的值,一旦设置了它,它的值不能被改变。而变量的值可以在设置完成后改变,但变量的数据类型和形状无法改变。
除了tf.zeros()和tf.ones()能够创建一个初始值为0或1的张量之外,还有一个tf.random_normal()函数,它能够创建一个包含多个随机值的张量,这些随机值是从正态分布中随机抽取的(默认的分布均值为0.0,标准差为1.0)。
另外还有一个tf.truncated_normal()函数,它创建了一个包含从截断的正态分布中随机抽取的值的张量,其中下上限是标准偏差的两倍。
有了这些知识,我们就可以创建用于神经网络的权重矩阵和偏差向量了。
1.2 Tensorflow 中的图与会话
在Tensorflow中,所有不同的变量以及对这些变量的操作都保存在图(Graph)中。在构建了一个包含针对模型的所有计算步骤的图之后,就可以在会话(Session)中运行这个图了。会话可以跨CPU和GPU分配所有的计算。
1.3 占位符 与 feed_dicts
我们已经看到了用于创建常量和变量的各种形式。Tensorflow中也有占位符,它不需要初始值,仅用于分配必要的内存空间。 在一个会话中,这些占位符可以通过*feed_dict*填入(外部)数据。
以下是占位符的使用示例。
2. Tensorflow 中的神经网络
2.1 简介
包含神经网络的图(如上图所示)应包含以下步骤:
1. 输入数据集:训练数据集和标签、测试数据集和标签(以及验证数据集和标签)。 测试和验证数据集可以放在tf.constant()中。而训练数据集被放在tf.placeholder()中,这样它可以在训练期间分批输入(随机梯度下降)。
2. 神经网络**模型**及其所有的层。这可以是一个简单的完全连接的神经网络,仅由一层组成,或者由5、9、16层组成的更复杂的神经网络。
3. 权重矩阵和**偏差矢量**以适当的形状进行定义和初始化。(每层一个权重矩阵和偏差矢量)
4. 损失值:模型可以输出分对数矢量(估计的训练标签),并通过将分对数与实际标签进行比较,计算出损失值(具有交叉熵函数的softmax)。损失值表示估计训练标签与实际训练标签的接近程度,并用于更新权重值。
5. 优化器:它用于将计算得到的损失值来更新反向传播算法中的权重和偏差。
2.2 数据加载
下面我们来加载用于训练和测试神经网络的数据集。为此,我们要下载MNIST和CIFAR-10数据集。 MNIST数据集包含了6万个手写数字图像,其中每个图像大小为28 x 28 x 1(灰度)。 CIFAR-10数据集也包含了6万个图像(3个通道),大小为32 x 32 x 3,包含10个不同的物体(飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车)。 由于两个数据集中都有10个不同的对象,所以这两个数据集都包含10个标签。
首先,我们来定义一些方便载入数据和格式化数据的方法。
这些方法可用于对标签进行独热码编码、将数据加载到随机数组中、扁平化矩阵(因为完全连接的网络需要一个扁平矩阵作为输入):
在我们定义了这些必要的函数之后,我们就可以这样加载MNIST和CIFAR-10数据集了:
你可以从Yann LeCun的网站下载MNIST数据集。下载并解压缩之后,可以使用python-mnist 工具来加载数据。
2.3 创建一个简单的一层神经网络
神经网络最简单的形式是一层线性全连接神经网络(FCNN, Fully Connected Neural Network)。 在数学上它由一个矩阵乘法组成。
最好是在Tensorflow中从这样一个简单的NN开始,然后再去研究更复杂的神经网络。 当我们研究那些更复杂的神经网络的时候,只是图的模型(步骤2)和权重(步骤3)发生了改变,其他步骤仍然保持不变。
我们可以按照如下代码制作一层FCNN:
在图中,我们加载数据,定义权重矩阵和模型,从分对数矢量中计算损失值,并将其传递给优化器,该优化器将更新迭代“num_steps”次数的权重。
在上述完全连接的NN中,我们使用了梯度下降优化器来优化权重。然而,有很多不同的优化器可用于Tensorflow。 最常用的优化器有GradientDescentOptimizer、AdamOptimizer和AdaGradOptimizer,所以如果你正在构建一个CNN的话,我建议你试试这些。
Sebastian Ruder有一篇不错的博文介绍了不同优化器之间的区别,通过这篇文章,你可以更详细地了解它们。
2.4 Tensorflow的几个方面
Tensorflow包含许多层,这意味着可以通过不同的抽象级别来完成相同的操作。这里有一个简单的例子,操作
logits = tf.matmul(tf_train_dataset, weights) + biases,
也可以这样来实现
logits = tf.nn.xw_plus_b(train_dataset, weights, biases)。
这是layers API中最明显的一层,它是一个具有高度抽象性的层,可以很容易地创建由许多不同层组成的神经网络。例如,conv_2d()或fully_connected()函数用于创建卷积和完全连接的层。通过这些函数,可以将层数、过滤器的大小或深度、激活函数的类型等指定为参数。然后,权重矩阵和偏置矩阵会自动创建,一起创建的还有激活函数和丢弃正则化层(dropout regularization laye)。
例如,通过使用 层API,下面这些代码:
可以替换为
可以看到,我们不需要定义权重、偏差或激活函数。尤其是在你建立一个具有很多层的神经网络的时候,这样可以保持代码的清晰和整洁。
然而,如果你刚刚接触Tensorflow的话,学习如何构建不同种类的神经网络并不合适,因为tflearn做了所有的工作。
因此,我们不会在本文中使用层API,但是一旦你完全理解了如何在Tensorflow中构建神经网络,我还是建议你使用它。
2.5 创建 LeNet5 卷积神经网络
下面我们将开始构建更多层的神经网络。例如LeNet5卷积神经网络。
LeNet5 CNN架构最早是在1998年由Yann Lecun(见论文)提出的。它是最早的CNN之一,专门用于对手写数字进行分类。尽管它在由大小为28 x 28的灰度图像组成的MNIST数据集上运行良好,但是如果用于其他包含更多图片、更大分辨率以及更多类别的数据集时,它的性能会低很多。对于这些较大的数据集,更深的ConvNets(如AlexNet、VGGNet或ResNet)会表现得更好。
但由于LeNet5架构仅由5个层构成,因此,学习如何构建CNN是一个很好的起点。
Lenet5架构如下图所示:
我们可以看到,它由5个层组成:
1. 第1层:卷积层,包含S型激活函数,然后是平均池层。
2. 第2层:卷积层,包含S型激活函数,然后是平均池层。
3. 第3层:一个完全连接的网络(S型激活)
4. 第4层:一个完全连接的网络(S型激活)
5. 第5层:输出层
这意味着我们需要创建5个权重和偏差矩阵,我们的模型将由12行代码组成(5个层 + 2个池 + 4个激活函数 + 1个扁平层)。
由于这个还是有一些代码量的,因此最好在图之外的一个单独函数中定义这些代码。
由于变量和模型是单独定义的,我们可以稍稍调整一下图,以便让它使用这些权重和模型,而不是以前的完全连接的NN:
我们可以看到,LeNet5架构在MNIST数据集上的表现比简单的完全连接的NN更好。
2.6 影响层输出大小的参数
一般来说,神经网络的层数越多越好。我们可以添加更多的层、修改激活函数和池层,修改学习速率,以看看每个步骤是如何影响性能的。由于i层的输入是i-1层的输出,我们需要知道不同的参数是如何影响i-1层的输出大小的。
要了解这一点,可以看看conv2d()函数。
它有四个参数:
1. 输入图像,维度为[batch size, image_width, image_height, image_depth]的4D张量
2. 权重矩阵,维度为[filter_size, filter_size, image_depth, filter_depth]的4D张量
3. 每个维度的步幅数。
4. 填充(='SAME'/'VALID')
这四个参数决定了输出图像的大小。
前两个参数分别是包含一批输入图像的4D张量和包含卷积滤波器权重的4D张量。
第三个参数是卷积的步幅,即卷积滤波器在四维的每一个维度中应该跳过多少个位置。这四个维度中的第一个维度表示图像批次中的图像编号,由于我们不想跳过任何图像,因此始终为1。最后一个维度表示图像深度(不是色彩的通道数;灰度为1,RGB为3),由于我们不想跳过任何颜色通道,所以这个也总是为1。第二和第三维度表示X和Y方向上的步幅(图像宽度和高度)。如果要应用步幅,则这些是过滤器应跳过的位置的维度。因此,对于步幅为1,我们必须将步幅参数设置为[1, 1, 1, 1],如果我们希望步幅为2,则将其设置为[1,2,2,1]。以此类推。
最后一个参数表示Tensorflow是否应该对图像用零进行填充,以确保对于步幅为1的输出尺寸不会改变。如果 padding = 'SAME',则图像用零填充(并且输出大小不会改变),如果 padding = 'VALID',则不填充。
下面我们可以看到通过图像(大小为28 x 28)扫描的卷积滤波器(滤波器大小为5 x 5)的两个示例。
在左侧,填充参数设置为“SAME”,图像用零填充,最后4行/列包含在输出图像中。
在右侧,填充参数设置为“VALID”,图像不用零填充,最后4行/列不包括在输出图像中。
我们可以看到,如果没有用零填充,则不包括最后四个单元格,因为卷积滤波器已经到达(非零填充)图像的末尾。这意味着,对于28 x 28的输入大小,输出大小变为24 x 24 。如果 padding = 'SAME',则输出大小为28 x 28。
如果在扫描图像时记下过滤器在图像上的位置(为简单起见,只有X方向),那么这一点就变得更加清晰了。如果步幅为1,则X位置为0-5、1-6、2-7,等等。如果步幅为2,则X位置为0-5、2-7、4-9,等等。
如果图像大小为28 x 28,滤镜大小为5 x 5,并且步长1到4,那么我们可以得到下面这个表:
可以看到,对于步幅为1,零填充输出图像大小为28 x 28。如果非零填充,则输出图像大小变为24 x 24。对于步幅为2的过滤器,这几个数字分别为 14 x 14 和 12 x 12,对于步幅为3的过滤器,分别为 10 x 10 和 8 x 8。以此类推。
对于任意一个步幅S,滤波器尺寸K,图像尺寸W和填充尺寸P,输出尺寸将为
如果在Tensorflow中 padding = “SAME”,则分子加起来恒等于1,输出大小仅由步幅S决定。
2.7 调整 LeNet5 的架构
在原始论文中,LeNet5架构使用了S形激活函数和平均池。 然而,现在,使用relu激活函数则更为常见。 所以,我们来稍稍修改一下LeNet5 CNN,看看是否能够提高准确性。我们将称之为类LeNet5架构:
主要区别是我们使用了relu激活函数而不是S形激活函数。
除了激活函数,我们还可以改变使用的优化器,看看不同的优化器对精度的影响。
2.8 学习速率和优化器的影响
让我们来看看这些CNN在MNIST和CIFAR-10数据集上的表现。
在上面的图中,测试集的精度是迭代次数的函数。左侧为一层完全连接的NN,中间为LeNet5 NN,右侧为类LeNet5 NN。
可以看到,LeNet5 CNN在MNIST数据集上表现得非常好。这并不是一个大惊喜,因为它专门就是为分类手写数字而设计的。MNIST数据集很小,并没有太大的挑战性,所以即使是一个完全连接的网络也表现的很好。
然而,在CIFAR-10数据集上,LeNet5 NN的性能显着下降,精度下降到了40%左右。
为了提高精度,我们可以通过应用正则化或学习速率衰减来改变优化器,或者微调神经网络。
可以看到,AdagradOptimizer、AdamOptimizer和RMSPropOptimizer的性能比GradientDescentOptimizer更好。这些都是自适应优化器,其性能通常比GradientDescentOptimizer更好,但需要更多的计算能力。
通过L2正则化或指数速率衰减,我们可能会得到更搞的准确性,但是要获得更好的结果,我们需要进一步研究。
3. Tensorflow 中的深度神经网络
到目前为止,我们已经看到了LeNet5 CNN架构。 LeNet5包含两个卷积层,紧接着的是完全连接的层,因此可以称为浅层神经网络。那时候(1998年),GPU还没有被用来进行计算,而且CPU的功能也没有那么强大,所以,在当时,两个卷积层已经算是相当具有创新意义了。
后来,很多其他类型的卷积神经网络被设计出来,你可以在这里查看详细信息。
比如,由Alex Krizhevsky开发的非常有名的AlexNet 架构(2012年),7层的ZF Net (2013),以及16层的 VGGNet (2014)。
在2015年,Google发布了一个包含初始模块的22层的CNN(GoogLeNet),而微软亚洲研究院构建了一个152层的CNN,被称为ResNet。
现在,根据我们目前已经学到的知识,我们来看一下如何在Tensorflow中创建AlexNet和VGGNet16架构。
3.1 AlexNet
虽然LeNet5是第一个ConvNet,但它被认为是一个浅层神经网络。它在由大小为28 x 28的灰度图像组成的MNIST数据集上运行良好,但是当我们尝试分类更大、分辨率更好、类别更多的图像时,性能就会下降。
第一个深度CNN于2012年推出,称为AlexNet,其创始人为Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton。与最近的架构相比,AlexNet可以算是简单的了,但在当时它确实非常成功。它以令人难以置信的15.4%的测试错误率赢得了ImageNet比赛(亚军的误差为26.2%),并在全球深度学习和人工智能领域掀起了一场革命。
它包括5个卷积层、3个最大池化层、3个完全连接层和2个丢弃层。整体架构如下所示:
1. 第0层:大小为224 x 224 x 3的输入图像
2. 第1层:具有96个滤波器(filter_depth_1 = 96)的卷积层,大小为11×11(filter_size_1 = 11),步长为4。它包含ReLU激活函数。 紧接着的是最大池化层和本地响应归一化层。
3. 第2层:具有大小为5 x 5(filter_size_2 = 5)的256个滤波器(filter_depth_2 = 256)且步幅为1的卷积层。它包含ReLU激活函数。 紧接着的还是最大池化层和本地响应归一化层。
4. 第3层:具有384个滤波器的卷积层(filter_depth_3 = 384),尺寸为3×3(filter_size_3 = 3),步幅为1。它包含ReLU激活函数
5. 第4层:与第3层相同。
6. 第5层:具有大小为3×3(filter_size_4 = 3)的256个滤波器(filter_depth_4 = 256)且步幅为1的卷积层。它包含ReLU激活函数
7. 第6-8层:这些卷积层之后是完全连接层,每个层具有4096个神经元。在原始论文中,他们对1000个类别的数据集进行分类,但是我们将使用具有17个不同类别(的花卉)的oxford17数据集。
请注意,由于这些数据集中的图像太小,因此无法在MNIST或CIFAR-10数据集上使用此CNN(或其他的深度CNN)。正如我们以前看到的,一个池化层(或一个步幅为2的卷积层)将图像大小减小了2倍。 AlexNet具有3个最大池化层和一个步长为4的卷积层。这意味着原始图像尺寸会缩小2^5。 MNIST数据集中的图像将简单地缩小到尺寸小于0。
因此,我们需要加载具有较大图像的数据集,最好是224 x 224 x 3(如原始文件所示)。 17个类别的花卉数据集,又名oxflower17数据集是最理想的,因为它包含了这个大小的图像:
让我们试着在AlexNet中创建权重矩阵和不同的层。正如我们之前看到的,我们需要跟层数一样多的权重矩阵和偏差矢量,并且每个权重矩阵的大小应该与其所属层的过滤器的大小相对应。
现在我们可以修改CNN模型来使用AlexNet模型的权重和层次来对图像进行分类。
3.2 VGG Net-16
VGG Net于2014年由牛津大学的Karen Simonyan和Andrew Zisserman创建出来。 它包含了更多的层(16-19层),但是每一层的设计更为简单;所有卷积层都具有3×3以及步长为3的过滤器,并且所有最大池化层的步长都为2。
所以它是一个更深的CNN,但更简单。
它存在不同的配置,16层或19层。 这两种不同配置之间的区别是在第2,第3和第4最大池化层之后对3或4个卷积层的使用(见下文)。
配置为16层(配置D)的结果似乎更好,所以我们试着在Tensorflow中创建它。
3.3 AlexNet 性能
作为比较,看一下对包含了较大图片的oxflower17数据集的LeNet5 CNN性能:
4. 结语
相关代码可以在我的GitHub库中获得,因此可以随意在自己的数据集上使用它。
在深度学习的世界中还有更多的知识可以去探索:循环神经网络、基于区域的CNN、GAN、加强学习等等。在未来的博客文章中,我将构建这些类型的神经网络,并基于我们已经学到的知识构建更有意思的应用程序。
文章原标题《Building Convolutional Neural Networks with Tensorflow》,作者:Ahmet Taspinar,译者:夏天,审校:主题曲。