【DL笔记9】搭建CNN哪家强?TensorFlow,Keras谁在行?

▶深度学习敲门砖-系列笔记

从【DL笔记1】到【DL笔记N】,是我学习深度学习一路上的点点滴滴的记录,是从Coursera网课、各大博客、论文的学习以及自己的实践中总结而来。从基本的概念、原理、公式,到用生动形象的例子去理解,到动手做实验去感知,到著名案例的学习,到用所学来实现自己的小而有趣的想法......我相信,一路看下来,我们可以感受到深度学习的无穷的乐趣,并有兴趣和激情继续钻研学习。 正所谓 Learning by teaching,写下一篇篇笔记的同时,我也收获了更多深刻的体会,希望大家可以和我一同进步,共同享受AI无穷的乐趣。


本篇文章我们会使用两种框架(TensorFlow和Keras,虽然Keras从某种意义上是TF的一种高层API)来实现一个简单的CNN,来对我们之前的MNIST手写数字进行识别。还记得上一次我们用TF实现了一个简单的三层神经网络,最终测试集准确率达到95%的水平。今天,我们期望达到99%以上的准确率!

在开始前,我们先确定一下我们的网络结构:

我们也不用弄很复杂的,毕竟这个手写数字识别实际上很容易了,所以我们设计两层卷积(后接池化层),然后一个全连接层,最后用Softmax输出即可。

这里需要说的一点是:

卷积层、池化层,处理的输入都是三维的(长、宽、通道),但是全连接层处理的输入却是一维的,因此我们需要在FC层之前对输入数据进行“压扁”处理,把每一维的每一个单元全部排列成一条直线!

在下面的代码中,我们可以看到在不同的框架中是怎么操作的。

一、使用TensorFlow框架

1.引入基本的包和数据集:

import tensorflow as tf
sess = tf.InteractiveSession()
import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
X_train,Y_train = mnist.train.images,mnist.train.labels
X_test,Y_test = mnist.test.images,mnist.test.labels
print(X_train.shape)
print(Y_train.shape)
print(X_test.shape)
print(Y_test.shape)
## 输出,看看数据的形状:
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
(55000, 784)
(55000, 10)
(10000, 784)
(10000, 10)

这里需要多说一句的就是这个InteractiveSession。

还记得我们上次使用TF的时候,是用sess=tf.Session()或者with tf.Session() as sess:的方法来启动session。

他们两者有什么区别呢: InteractiveSession()多用于交互式的环境中,如IPython Notebooks,比Session()更加方便灵活。 在前面我们知道,我们所有的计算,都必须在:

with tf.Session as sess:
    ... ...

中进行,如果出界了,就会报错,说“当前session中没有这个操作/张量”这种话。

但是在InteractiveSession()中,我们只要创建了,就会全局默认是这个session,我们可以随时添加各种操作,用Tensor.eval()Operation.run()来随机进行求值和计算。

2.设置CNN的结构

为了方便,我们不是直接给变量赋值,而是写一些通用的函数:

对于weights,我们在前面的文章【】中提到过,需要随机初始化参数,不能直接用0来初始化,否则可能导致无法训练。因此我们这里采用truncated_normal方法,normal分布就是我们熟悉的正态分布,truncated_normal就是将正太分布的两端的过大过小的值去掉了,使得我们的训练更容易。

对于bias,它怎么初始化就无所谓了,我们简单起见,就直接用0初始化了。

def weights(shape):
    initial = tf.truncated_normal(shape,stddev=0.1)    return tf.Variable(initial)

def bias(shape):
    initial = tf.zeros(shape)    return tf.Variable(initial)

然后一些定义层的函数:

定义卷积层、池化层的时候有很多超参数要设置,但是很多参数可能是一样的,所以我们写一个函数会比较方便。

参数的格式有必要在这里说一说,因为这个很容易搞混淆:

对于卷积层:

  • 输入(inputs/X):[batch, height, width, channels],就是[样本量,高,宽,通道数];
  • 权重(filter/W):[filter_height, filter_width, in_channels, out_channels],这个就不解释了吧,英语都懂哈;
  • 步长(strides):一开始我很奇怪为什么会是四维的数组,查看文档才只有,原来是对应于input每一维的步长。而我们一般只关注对宽、高的步长,所以假如我们希望步长是2,那么stride就是[1,2,2,1];
  • 填白(padding):这个是我们之前讲过的,如果不填白,就设为VALID,如果要填白使得卷积后大小不变,那么就用SAME.

对于池化层:

  • 输入、步长跟上面一样;
  • ksize:指的是kernel-size,就是在pooling的时候的窗口,也是一个四维数组,对应于输入的每一维。跟strides一样,我们一般只关心中间两个维度,比如我们希望一个2×2的窗口,就设为[1,2,2,1]. (这里有一个经验:当stride为2,窗口也为2的时候,这个Maxpool实际上把前面的输入图像的长宽各缩小了一半!)
  • 这里的padding和卷积层的padding有所不同!这里的padding是指,当窗口的大小和步长设置不能正好覆盖原图时,需不需要填白使得可以正好覆盖。一般我们都选SAME,表示要填白。
def conv(X,W):
    return tf.nn.conv2d(X,W,strides=[1,1,1,1],padding='SAME')  def max_pool(X):
    return tf.nn.max_pool(X,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')

好了,准备工作都做好了,我们可以开始正式搭建网络结构了!

先为X,Y定义一个占位符,后面运行的时候再注入数据:

X= tf.placeholder(dtype=tf.float32,shape=[None,784])

## 由于我们的卷积层处理的是四维的输入,所以我们这里需要对数据进行变形:

X_image = tf.reshape(X,[-1,28,28,1]) # -1代表这一维度可以变化
Y = tf.placeholder(dtype=tf.float32,shape=[None,10])

我们就来两个卷积层(filter为5×5,每层后面都接一个池化层),后接一个全连接层吧:

## 第一个卷积层:

W1 = weights([5,5,1,32])
b1 = bias([32])
A1 = tf.nn.relu(conv(X_image,W1)+b1,name='relu1')
A1_pool = max_pool(A1)

## 第二个卷积层:
W2 = weights([5,5,32,64])
b2 = bias([64])
A2 = tf.nn.relu(conv(A1_pool,W2)+b2,name='relu2')
A2_pool = max_pool(A2)

## 全连接层:
## 前面经过两个CONV,将图片的通道数变成了64个,
## 另外经过两个POOL,将原来28×28的图片,缩小成了7×7,
## 因此,压扁后,会有7*7*64个单元:
W3 = weights([7*7*64,128])
b3 = bias([128])
## 对数据进行压扁处理:
A2_flatten = tf.reshape(A2_pool,[-1,7*7*64])
A3= tf.nn.relu(tf.matmul(A2_flatten,W3)+b3,name='relu3')

## 输出层(Softmax):

W4 = weights([128,10])
b4 = bias([10])
Y_pred = tf.nn.softmax(tf.matmul(A3,W4)+b4)

定义损失和优化器:

loss = -tf.reduce_sum(Y*tf.log(Y_pred))
train_step = tf.train.AdamOptimizer(0.001).minimize(loss)

3.开始训练

## 先要初始化所有的变量:
sess.run(tf.global_variables_initializer())
## 然后训练它个几千次:(用CPU计算,用时大概20分钟)
costs = []
for i in range(3000):
    X_batch,Y_batch = mnist.train.next_batch(batch_size=64)
    _,batch_cost = sess.run([train_step,loss],feed_dict={X:X_batch,Y:Y_batch})    if i%100 == 0:
        print("Batch%d cost:"%i,batch_cost)
        costs.append(batch_cost)
print("Training finished!")
## 部分输出:
Batch0 cost: 0.16677594
Batch100 cost: 0.052068923
Batch200 cost: 0.5979577
Batch300 cost: 0.049106397
Batch400 cost: 0.047060404
Batch500 cost: 2.0360851
Batch600 cost: 3.3168547
Batch700 cost: 0.11393449
Batch800 cost: 0.06208247
Batch900 cost: 0.035165284
Training finished!
## 计算测试集准确率:
correct_prediction = tf.equal(tf.argmax(Y_pred,1),tf.argmax(Y,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction,'float'))
accuracy.eval(feed_dict={X:X_test,Y:Y_test})
## 得到测试集准确率:
0.9927

经过几千次的迭代,准确率已经达到了99%以上!CNN果真效果不错!

上面,我们已经用TensorFlow搭建了一个4层的卷积神经网络(2个卷积层,两个全连接层,注意,最后的Softmax层实际上也是一个全连接层)

接下来,我们用Keras来搭建一个一模一样的模型,来对比一下二者在实现上的差异。

二、使用Keras框架

import keras
from keras.models import Sequential
from keras.layers import Conv2D,MaxPooling2D,Dense,Flatten

下面搭建网络结构:

model = Sequential()
# 第一个卷积层(后接池化层):
model.add(Conv2D(32,kernel_size=(5,5),padding='same',activation='relu',input_shape=(28,28,1)))
model.add(MaxPooling2D(pool_size=(2,2),padding='same'))

# 第二个卷积层(后接池化层):
model.add(Conv2D(64,kernel_size=(5,5),padding='same',activation='relu'))
model.add(MaxPooling2D(pool_size=(2,2),padding='same'))

# 将上面的结果扁平化,然后接全连接层:
model.add(Flatten())
model.add(Dense(128,activation='relu'))

#最后一个Softmax输出:
model.add(Dense(10,activation='softmax'))

编译模型,设定优化器和损失:

model.compile(optimizer='Adam',loss='categorical_crossentropy',metrics=['accuracy'])

开始训练:

这里我们先将输入数据变形,跟前面TensorFlow中的一样:

X_train_image = X_train.reshape(X_train.shape[0],28,28,1)
X_test_image = X_test.reshape(X_test.shape[0],28,28,1)
# 开始训练:
model.fit(X_train_image,Y_train,epochs=6,batch_size=64)

这里的epoch代表迭代的次数,注意,这里为什么我只写了6次!?

前面用TensorFlow的时候,不是迭代了几千次吗?

细心的读者会注意到,用TensorFlow的时候,我们使用的MNIST数据集自带的一个取mini-batch的方法,每次迭代只选取55000个样本中的64个来训练,因此虽然迭代了3000多次,但实际上也就是3000次的更新。

但是在这里,我们传入的是整个数据集,设置batch为64,也就是一个epoch里面,我们会把原数据集分成一个个64大小的数据包丢进去训练,一个epoch就会更新55000/64次,因此,虽然epoch=6,但是实际上参数也是更新了几千次。

## 训练过程输出(部分):

Epoch 1/6
55000/55000 [==============================] - 138s 3ms/step - loss: 0.0093 - acc: 0.9968
Epoch 2/6
55000/55000 [==============================] - 140s 3ms/step - loss: 0.0064 - acc: 0.9979
Epoch 3/6
55000/55000 [==============================] - 141s 3ms/step - loss: 0.0052 - acc: 0.9982
Epoch 4/6
55000/55000 [==============================] - 141s 3ms/step - loss: 0.0049 - acc: 0.9984
Epoch 5/6
55000/55000 [==============================] - 140s 3ms/step - loss: 0.0060 - acc: 0.9981
Epoch 6/6
55000/55000 [==============================] - 141s 3ms/step - loss: 0.0034 - acc: 0.9991
## 查看测试集准确率:
result = model.evaluate(X_test_image,Y_test)
print("Test accuracy:",result[1])
## 得到结果:
10000/10000 [==============================] - 10s 969us/step
Test accuracy: 0.9926

测试集准确率达到99.26%!与前面TensorFlow的训练结果基本一致。


对比与总结:

可以看到,在Keras里面搭建网络结构是如此的简单直白,直接往上堆就行了,不用考虑输入数据的维度,而是自动进行转换。在用TensorFlow的时候,我们需要手动计算一下,在经过每一层后,通道数、长宽都是变成了多少,并据此设置后面的参数,但是在Keras里面,我们只用关心我的结构到底应该怎么设计,不用关心数据的各维度是怎么变化的,毕竟结构确定了,数据的变化是唯一确定的。因此,在这一点上,我是十分喜欢Keras的。

另外,Keras的模型的编译也十分地简单,只要清楚相关的深度学习概念,损失函数都不用我们去写公式,而是直接选择,公式都是内置的。

我觉得最让人舒服的一点是,keras里面基本不用考虑session的问题,这就避免了很多我们调bug的时间,而这本来不重要。我们搭建好模型就编译,编译好模型就训练,一气呵成,而且keras在训练中,内置了日志打印,所以我们很容易看清楚训练的过程怎样。

上面这么一说,好像Keras什么都好。但其实这就像是C++和Python的关系一样,大家都知道Python好学容易上手,很多复杂的东西已经封装成接口我们直接调用就可以了。但是真正到了复杂的问题,碰到python没有封装好的功能的时候,我们只能回到底层的C++去寻求答案了。TensorFlow也一样,虽然有些晦涩难懂,什么session、graph、op搞得人晕头转向,但是它有许多允许我们DIY的地方,而Keras作为一个由TensorFlow作为支撑而构建的框架,必然会有很多功能不具备,也会有诸多限制。

所以如果日常搭建一下模型、复现一下模型,我觉得keras挺好的,省去了我们很多麻烦。如果有更高级的目标,例如学术上的创新,建议还是耐心地学一学TensorFlow。

原文发布于微信公众号 - SimpleAI(SimpleAI_1)

原文发表时间:2018-09-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏鸿的学习笔记

写给开发者的机器学习指南(六)

在本节中,我们为您介绍一组在实际环境中的机器学习算法。 这些例子的想法是让你开始使用机器学习算法,而不深入解释底层算法。我们只专注于这些算法的特征方面,如何验证...

902
来自专栏京东技术

【解题报告】看雪·京东2018CTF—京东AI CTF大挑战特别题

近日,京东安全联合安全界的黄埔军校看雪论坛举办了一次线上CTF大赛,近3w人参赛。参与解答“京东AI CTF大挑战特别题”的同学有1435人,最终解出题目的只有...

1512
来自专栏数据科学与人工智能

【Python环境】无监督学习之KMeans

k-means,这一种算法是非监督模型,也就是说一开始我可以不用告诉它类别,让他们自己去分类。那么怎么去分类呢?假设我们首先将它映射到欧式空间 ? 可以直观...

3138
来自专栏机器之心

教程 | TensorFlow从基础到实战:一步步教你创建交通标志分类神经网络

选自DataCamp 作者:Karlijn Willems 机器之心编译 参与:Panda TensorFlow 已经成为了现在最流行的深度学习框架,相信很多对...

5286
来自专栏数据小魔方

随机数函数

今天给大家分享几种常用的随机数函数! ▼ 在excel中生成随机数虽然不是很频繁的需求,但是简单了解几个随机数生成方式,偶尔还是很有帮助的。因为我们时常需要使用...

2864
来自专栏量化投资与机器学习

【致敬周杰伦】基于TensorFlow让机器生成周董的歌词(附源码)

? 周杰伦 深深地 影响了我们 一代人 这句话 不足为过 前言 今日推文将介绍如何使用TensorFlow一步步来搭建一个序列建模的应用——机器创作歌词,训练...

1.7K5
来自专栏机器学习算法原理与实践

用gensim学习word2vec

    在word2vec原理篇中,我们对word2vec的两种模型CBOW和Skip-Gram,以及两种解法Hierarchical Softmax和Nega...

2613
来自专栏大数据挖掘DT机器学习

使用sklearn进行数据挖掘

目录 1 使用sklearn进行数据挖掘   1.1 数据挖掘的步骤   1.2 数据初貌   1.3 关键技术 2 并行处理   2...

3724
来自专栏AI研习社

如何在 Keras 中从零开始开发一个神经机器翻译系统?

机器翻译是一项具有挑战性的任务,包含一些使用高度复杂的语言知识开发的大型统计模型。 神经机器翻译的工作原理是——利用深层神经网络来解决机器翻译问题。 在本教程...

37412
来自专栏决胜机器学习

机器学习(十八) ——SVM实战

机器学习(十八)——SVM实战 (原创内容,转载请注明来源,谢谢) 一、概述 本篇主要用python来实现SVM算法,并用SVM算法进行预测分类结果。对于SM...

3715

扫码关注云+社区

领取腾讯云代金券