在圖像中疗疟,我們很難根據(jù)認(rèn)為我理解提取出有效而豐富的特征署鸡。在深度學(xué)習(xí)出現(xiàn)之前如失,我們必須借助SIFT绊诲,HoG等算法提取有良好區(qū)分性的特征,再集合SVM等進(jìn)行圖像識(shí)別褪贵。但是SIFT算法錯(cuò)誤率常年難以突破掂之,卷積神經(jīng)網(wǎng)絡(luò)提取的特征則可以達(dá)到更好的效果。CNN最大特點(diǎn)在于卷積的權(quán)值共享結(jié)構(gòu)脆丁,可以大幅減少神經(jīng)網(wǎng)絡(luò)的參數(shù)量世舰,防止過擬合的同時(shí)又降低了神經(jīng)網(wǎng)絡(luò)模型的復(fù)雜度。
在卷積神經(jīng)網(wǎng)絡(luò)中槽卫,第一各卷積層會(huì)直接接受圖像像素級(jí)的輸入跟压,每一個(gè)卷積操作只處理一小塊圖像,進(jìn)行卷積變化后再傳入后面的網(wǎng)絡(luò)歼培,每一層卷積(或者說濾波器)都可以提取數(shù)據(jù)中最有效的特征震蒋。再進(jìn)行組合抽象形成更高階的特征。
一般的卷積神經(jīng)網(wǎng)絡(luò)由多個(gè)卷積層構(gòu)成躲庄,每個(gè)卷積層通常有如下幾個(gè)操作:
(1)圖像會(huì)通過多個(gè)不同的卷積核的濾波查剖,并加偏置(bias),提取出局部特征噪窘,每一個(gè)卷積核會(huì)映射出一個(gè)新的2D圖像笋庄;
(2)將前面卷積核的濾波輸出結(jié)果,進(jìn)行非線性的激活函數(shù)處理。目前常用ReLu直砂,以前sigmoid最多菌仁;
(3)對(duì)激活函數(shù)的結(jié)果再進(jìn)行池化操作(即降采樣,比如將2x2的圖片降為1x1的圖片)哆键,目前一般是使用最大池化掘托,保留最顯著特征,并提升模型的畸變?nèi)萑棠芰?br>
權(quán)限共享降低模型復(fù)雜度籍嘹,減輕過擬合并且降低計(jì)算量
通過局部鏈接闪盔,可以明顯降低參數(shù)量,但是仍然偏多辱士,但是使用卷積核可以大量降低
卷積的好處是泪掀,不管圖片尺寸如何,我們需要訓(xùn)練的權(quán)重?cái)?shù)量只跟卷積核大小颂碘,卷積核數(shù)量有關(guān)异赫,我們可以使用非常少的參數(shù)快速處理任意大小的圖片
每一層卷積層提取的特征,在后面的層中都會(huì)抽象組合成更高階的特征头岔。而且多層抽象的卷積網(wǎng)絡(luò)表達(dá)能力更強(qiáng)塔拳,效率更高,相比只使用一個(gè)隱含層提取全部高階特征峡竣,反而可以節(jié)省大量的參數(shù)
最后總結(jié)一下靠抑,卷積神經(jīng)網(wǎng)絡(luò)的要點(diǎn)就是局部連接,權(quán)值共享和池化層中的降采樣适掰。
下面使用TensorFlow實(shí)現(xiàn)一個(gè)簡(jiǎn)單的卷積網(wǎng)絡(luò)颂碧,首先載入MNIST數(shù)據(jù)集,并創(chuàng)建默認(rèn)Interactive Session:
from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
sess = tf.InteractiveSession()
tf.InteractiveSession():它能讓你在運(yùn)行圖的時(shí)候类浪,插入一些計(jì)算圖载城,這些計(jì)算圖是由某些操作(operations)構(gòu)成的。這對(duì)于工作在交互式環(huán)境中的人們來說非常便利费就,比如使用IPython诉瓦。
tf.Session():需要在啟動(dòng)session之前構(gòu)建整個(gè)計(jì)算圖,然后啟動(dòng)該計(jì)算圖力细。
意思就是在我們使用tf.InteractiveSession()來構(gòu)建會(huì)話的時(shí)候垦搬,我們可以先構(gòu)建一個(gè)session然后再定義操作(operation),如果我們使用tf.Session()來構(gòu)建會(huì)話我們需要在會(huì)話構(gòu)建之前定義好全部的操作(operation)然后再構(gòu)建會(huì)話艳汽。
接下來創(chuàng)建所需要的權(quán)重和偏置。先定義好初始化函數(shù)以便重復(fù)使用对雪。我們需要給權(quán)重制造一些隨機(jī)噪聲來打破完全對(duì)稱河狐,比如截?cái)嗟恼龖B(tài)分布噪聲,標(biāo)準(zhǔn)差設(shè)為0.1,同時(shí)因?yàn)槲覀兪褂肦eLU馋艺,也給偏置增加一些小的正值(0.1)用來避免死亡節(jié)點(diǎn)(dead neurons):
def weight_variable(shape):
initial = tf.truncated_normal(shape, stddev=0.1)#返回元素服從截?cái)嗾龖B(tài)分布
return tf.Variable(initial)
def bias_variable(shape):
initial = tf.constant(0.1, shape=shape)
return tf.Variable(initial)
下面創(chuàng)建卷積層和池化層函數(shù)栅干。conv2d是TensorFlow的2維卷積函數(shù),x是輸入捐祠,W是卷積參數(shù)碱鳞。例如[5,5,1,32]代表5x5的卷積核尺寸,1個(gè)channel(灰色)踱蛀,32個(gè)卷積核窿给,也就是這個(gè)卷積核會(huì)提取多少類特征,strides代表步長率拒,都是1代表不會(huì)遺漏地劃過每一個(gè)點(diǎn)崩泡。Padding代表邊界處理方式,SAME代表給邊界加上Padding讓卷積的輸出和輸入保持同樣的尺寸猬膨。ksze指滑動(dòng)窗口尺寸:
def conv2d(x, W):
return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], 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')
定義輸入placeholder角撞,x是特征,y_是真實(shí)的label勃痴。因?yàn)榫矸e神經(jīng)網(wǎng)絡(luò)會(huì)利用空間信息谒所,因此需要把一維輸入結(jié)果轉(zhuǎn)化為二維,即1x784到28x28沛申。[-1,28,28,1]中劣领,-1代表樣本數(shù)量不固定,1代表顏色通道數(shù)量污它。tf.reshape是變形函數(shù):
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
x_image = tf.reshape(x, [-1,28,28,1])
接下來我們定義第一個(gè)卷積層剖踊。我們先用前面寫好的函數(shù)進(jìn)行初始化,包括weights和bias衫贬。這里的[5,5,1,32]代表卷積核尺寸為5x5,1個(gè)顏色通道德澈,32個(gè)不同的卷積核。然后使用conv2d函數(shù)進(jìn)行卷積操作進(jìn)行卷積操作固惯,并加上偏置梆造,接下來使用ReLU進(jìn)行非線性處理。最后使用最大池化函數(shù)max_pool-2x2對(duì)卷積輸出結(jié)果進(jìn)行池化操作:
W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)
現(xiàn)在定義第二個(gè)卷積層葬毫,不同的是會(huì)提取64種特征:
W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
然后使用reshape函數(shù)對(duì)第二個(gè)卷積層的輸出tenso進(jìn)行變形镇辉,轉(zhuǎn)成一維向量,然后連接全連接層贴捡,隱含節(jié)點(diǎn)為1024忽肛,并使用ReLU激活函數(shù):
W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])
h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64])
h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
為了減輕過擬合,下面使用一個(gè)Dropout層烂斋,訓(xùn)練時(shí)屹逛,隨機(jī)丟棄一部分節(jié)點(diǎn)的數(shù)據(jù)來減輕過擬合础废,預(yù)測(cè)時(shí)則保留全部數(shù)據(jù)來追求最好的預(yù)測(cè)性能:
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
最后我們將Dropout層的輸出連接一個(gè)Softmax層,得到最后的概率輸出:
W_fc2 = weight_variable([1024, 10])
b_fc2 = bias_variable([10])
y_conv=tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)
我們定義損失函數(shù)為cross entropy罕模,和之前一樣评腺,但是優(yōu)化器使用Adam,并給與一個(gè)比較小的學(xué)習(xí)速率1e-4:
cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y_conv), reduction_indices=[1]))
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
再定義準(zhǔn)確率操作:
correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
下面開始訓(xùn)練淑掌。首先依然是初始化所有參數(shù)蒿讥,設(shè)置訓(xùn)練時(shí)Dropout的keep_prob比率為0.5。然后使用大小為50的mini-batch抛腕,共進(jìn)行2w次訓(xùn)練迭代芋绸,參與訓(xùn)練的樣本數(shù)量總共100w。其中每100次訓(xùn)練兽埃,我們會(huì)對(duì)準(zhǔn)確率進(jìn)行評(píng)測(cè) (評(píng)測(cè)時(shí)keep_prob設(shè)為1)侥钳,用以實(shí)時(shí)監(jiān)測(cè)模型的性能:
tf.global_variables_initializer().run()
for i in range(20000):
batch = mnist.train.next_batch(50)
if i%100 == 0:
train_accuracy = accuracy.eval(feed_dict={
x:batch[0], y_: batch[1], keep_prob: 1.0})
print("step %d, training accuracy %g"%(i, train_accuracy))
train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
全部訓(xùn)練完后,我們?cè)跍y(cè)試集上進(jìn)行全面的測(cè)試柄错,得到整體的分類準(zhǔn)確率:
print("test accuracy %g"%accuracy.eval(feed_dict={
x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
最后跑了很久舷夺。。售貌。應(yīng)該是我超極本的原因吧
然后實(shí)現(xiàn)一個(gè)進(jìn)階的卷機(jī)網(wǎng)絡(luò)
這個(gè)卷積神經(jīng)網(wǎng)絡(luò)中给猾,我們使用了以下技巧:
(1)對(duì)weights進(jìn)行了L2的正則化;
(2)對(duì)圖片進(jìn)行翻轉(zhuǎn)颂跨、隨機(jī)剪裁等數(shù)據(jù)增強(qiáng)敢伸,制造更多的樣本;
(3)在每個(gè)卷積-最大池化層后面使用了LRN層恒削,增強(qiáng)了模型的泛化能力
先載入常用庫和數(shù)據(jù)集的默認(rèn)路徑:
import cifar10,cifar10_input
import tensorflow as tf
import numpy as np
import time
max_steps = 3000
batch_size = 128
data_dir = '/tmp/cifar10_data/cifar-10-batches-bin'
正則化即特征的權(quán)重也會(huì)成為模型的損失函數(shù)的一部分池颈。可以理解為钓丰,為了使用某個(gè)特征躯砰,我們需要付出loss的代價(jià),除非這個(gè)特征非常有效携丁,否則就會(huì)被loss上的增加覆蓋效果(奧卡姆剃刀)琢歇。
def variable_with_weight_loss(shape, stddev, wl):
var = tf.Variable(tf.truncated_normal(shape, stddev=stddev))#截?cái)嗾龖B(tài)分布
if wl is not None:
weight_loss = tf.multiply(tf.nn.l2_loss(var), wl, name='weight_loss')#加入l2loss,相乘
tf.add_to_collection('losses', weight_loss)
return var
然后解壓展開數(shù)據(jù)集到指定位置:
cifar10.maybe_download_and_extract()
產(chǎn)生需要使用的數(shù)據(jù)梦鉴,包括特征及其對(duì)應(yīng)的label李茫,返回已經(jīng)封裝好的Tensor,每次執(zhí)行都會(huì)生成一個(gè)batch_size的數(shù)量的樣本肥橙。但是對(duì)圖片進(jìn)行了增強(qiáng)(課本87頁)魄宏。所以需要耗費(fèi)大量的CPU時(shí)間,因此distorted_input使用了16個(gè)獨(dú)立的線程加速任務(wù):
images_train, labels_train = cifar10_input.distorted_inputs(data_dir=data_dir,
batch_size=batch_size)
再生成測(cè)試數(shù)據(jù)存筏,不需要對(duì)圖片進(jìn)行處理娜庇,不過要剪裁圖片正中間24x24大小的區(qū)塊塔次,并進(jìn)行數(shù)據(jù)標(biāo)準(zhǔn)化操作:
images_test, labels_test = cifar10_input.inputs(eval_data=True,
data_dir=data_dir,
batch_size=batch_size)
輸入特征和label:
image_holder = tf.placeholder(tf.float32, [batch_size, 24, 24, 3])
label_holder = tf.placeholder(tf.int32, [batch_size])
下面創(chuàng)建第一個(gè)卷積層。卷積核大小5x5名秀,顏色通道3,,6個(gè)藕溅。標(biāo)準(zhǔn)差0.05匕得。尺寸和步長不一致,增加數(shù)據(jù)的豐富性巾表。在之后汁掠,使用tf.nn.lrn函數(shù),即LRN對(duì)結(jié)果進(jìn)行處理集币。LRN層模仿了生物神經(jīng)網(wǎng)絡(luò)的“側(cè)抑制”機(jī)制考阱,對(duì)局部神經(jīng)元的活動(dòng)創(chuàng)建競(jìng)爭(zhēng)環(huán)境,使得其中響應(yīng)比較大的值變得相對(duì)更大鞠苟,并抑制其他反饋較小的神經(jīng)元乞榨,增強(qiáng)了模型的泛化能力。LRN對(duì)ReLU這種沒有上限邊界的激活函數(shù)會(huì)比較有用当娱,因?yàn)樗鼤?huì)從附件的多個(gè)卷積核的響應(yīng)(response)中挑選比較大的反饋吃既,但不適用于Sigmoid這種有固定邊界并且能夠抑制過大值的激活函數(shù):
weight1 = variable_with_weight_loss(shape=[5, 5, 3, 64], stddev=5e-2, wl=0.0)#創(chuàng)建卷積核函數(shù)并初始化
kernel1 = tf.nn.conv2d(image_holder, weight1, [1, 1, 1, 1], padding='SAME')
bias1 = tf.Variable(tf.constant(0.0, shape=[64]))
conv1 = tf.nn.relu(tf.nn.bias_add(kernel1, bias1))#與偏置相加
pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='SAME')
norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
現(xiàn)在來創(chuàng)建第二個(gè)卷積層。這里的步驟和第一步很像跨细,區(qū)別如下:輸入通道調(diào)整為64鹦倚;bias初始化為0.1,而不是0冀惭;調(diào)整最大池化層和LRN層的順序:
weight2 = variable_with_weight_loss(shape=[5, 5, 64, 64], stddev=5e-2, wl=0.0)
kernel2 = tf.nn.conv2d(norm1, weight2, [1, 1, 1, 1], padding='SAME')
bias2 = tf.Variable(tf.constant(0.1, shape=[64]))
conv2 = tf.nn.relu(tf.nn.bias_add(kernel2, bias2))
norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75)
pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1],
padding='SAME')
在兩個(gè)卷積層后震叙,將使用一個(gè)全連接層,這里先reshape第二個(gè)卷積層的輸出結(jié)果散休。我們希望這個(gè)全連接層不要過擬合媒楼,所以設(shè)了非0的weight loss值0.04,讓這一層所有參數(shù)都被L2正則所約束:
reshape = tf.reshape(pool2, [batch_size, -1])
dim = reshape.get_shape()[1].value#獲取扁平化后的長度
weight3 = variable_with_weight_loss(shape=[dim, 384], stddev=0.04, wl=0.004)
bias3 = tf.Variable(tf.constant(0.1, shape=[384]))
local3 = tf.nn.relu(tf.matmul(reshape, weight3) + bias3)
接下來的這個(gè)全連接層和前一層很像溃槐,只不過其隱含層節(jié)點(diǎn)數(shù)下降了一半匣砖,其他參數(shù)不變:
weight4 = variable_with_weight_loss(shape=[384, 192], stddev=0.04, wl=0.004)
bias4 = tf.Variable(tf.constant(0.1, shape=[192]))
local4 = tf.nn.relu(tf.matmul(local3, weight4) + bias4)
下面是最后一層,依然先創(chuàng)建這一層的weight昏滴,其正態(tài)分布標(biāo)準(zhǔn)差是上一層隱含節(jié)點(diǎn)數(shù)的倒數(shù)猴鲫,并且不計(jì)入L2正則。因?yàn)閟oftmax主要是為了計(jì)算loss谣殊,因此整合到后面比較合適:
weight5 = variable_with_weight_loss(shape=[192, 10], stddev=1/192.0, wl=0.0)
bias5 = tf.Variable(tf.constant(0.0, shape=[10]))
logits = tf.add(tf.matmul(local4, weight5), bias5)
完成模型的inference部分的構(gòu)建拂共,接下來計(jì)算CNN的loss。這里依然使用cross entropy姻几,需要注意的是我們把softmax的計(jì)算和cross entropy的計(jì)算合在一起了宜狐。使用tf.reduce_mean對(duì)cross entropy計(jì)算均值势告,再使用tf.add_to_collection把cross entropy的loss添加到整體losses的collection中。最后抚恒,全部loss求和:
def loss(logits, labels):
labels = tf.cast(labels, tf.int64)
cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits(
logits=logits, labels=labels, name='cross_entropy_per_example')
cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy')
tf.add_to_collection('losses', cross_entropy_mean)
return tf.add_n(tf.get_collection('losses'), name='total_loss')
接著將logits節(jié)點(diǎn)和label_holder傳入loss函數(shù)獲得最終的loss:
loss = loss(logits, label_holder)
優(yōu)化器:
train_op = tf.train.AdamOptimizer(1e-3).minimize(loss) #0.72
返回分?jǐn)?shù)自高的那一類:
top_k_op = tf.nn.in_top_k(logits, label_holder, 1)
創(chuàng)建默認(rèn)session咱台,初始化全部模型參數(shù):
sess = tf.InteractiveSession()
tf.global_variables_initializer().run()
啟動(dòng)圖片增強(qiáng)線程:
tf.train.start_queue_runners()
下面開始訓(xùn)練過程。每隔10步計(jì)算當(dāng)前l(fā)oss俭驮、每秒能訓(xùn)練的樣本數(shù)量回溺,以及訓(xùn)練一個(gè)batch數(shù)據(jù)所花費(fèi)的時(shí)間:
###
for step in range(max_steps):
start_time = time.time()
image_batch,label_batch = sess.run([images_train,labels_train])
_, loss_value = sess.run([train_op, loss],feed_dict={image_holder: image_batch,
label_holder:label_batch})
duration = time.time() - start_time
if step % 10 == 0:
examples_per_sec = batch_size / duration
sec_per_batch = float(duration)
format_str = ('step %d, loss = %.2f (%.1f examples/sec; %.3f sec/batch)')
print(format_str % (step, loss_value, examples_per_sec, sec_per_batch))
接下來測(cè)試數(shù)據(jù)集。使用固定的batch_size混萝,然后一個(gè)batch一個(gè)batch地輸入測(cè)試數(shù)據(jù):
###
num_examples = 10000
import math
num_iter = int(math.ceil(num_examples / batch_size))
true_count = 0
total_sample_count = num_iter * batch_size
step = 0
while step < num_iter:
image_batch,label_batch = sess.run([images_test,labels_test])
predictions = sess.run([top_k_op],feed_dict={image_holder: image_batch,
label_holder:label_batch})
true_count += np.sum(predictions)
step += 1
最后打印準(zhǔn)確率的評(píng)測(cè)結(jié)果計(jì)算并打印出來:
precision = true_count / total_sample_count
print('precision @ 1 = %.3f' % precision)
最終結(jié)果大致73%遗遵。持續(xù)增加max_step,可以期望準(zhǔn)確率逐漸增加逸嘀。如果max_steps比較大车要,推薦使用學(xué)習(xí)速率衰減(decay)的SGD進(jìn)行訓(xùn)練,這樣訓(xùn)練過程的準(zhǔn)確率會(huì)大致接近86%崭倘。
數(shù)據(jù)增強(qiáng)(Data Augmenation)在外面的訓(xùn)練作用很大翼岁,它可以給單幅畫增加多個(gè)副本,提高圖片的利用率绳姨,防止過擬合登澜。
從本章的例子可以發(fā)現(xiàn),卷積層一般需要和一個(gè)池化層連接飘庄。卷積層最后幾個(gè)全連接層的作用是輸出分類結(jié)果脑蠕,前面的卷積層主要是特征提取工作,直到最后全連接層更復(fù)雜跪削,訓(xùn)練全連接層基本是進(jìn)行一些矩陣運(yùn)算谴仙,而目前卷積層的訓(xùn)練基本依賴于cuDNN的實(shí)現(xiàn)。