首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【DL笔记9】搭建CNN哪家强?TensorFlow,Keras谁在行?

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

作者头像
beyondGuo
发布2018-10-25 14:49:47
8150
发布2018-10-25 14:49:47
举报
文章被收录于专栏:SimpleAISimpleAISimpleAI

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

从【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。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-09-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 SimpleAI 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、使用TensorFlow框架
    • 1.引入基本的包和数据集:
      • 2.设置CNN的结构
        • 3.开始训练
        • 二、使用Keras框架
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档