一、機器如何識圖
先給大家出個腦筋急轉(zhuǎn)彎:在白紙上畫出一個大熊貓,一共需要幾種顏色的畫筆?——大家應(yīng)該都知道,只需要一種黑色的畫筆,只需要將大熊貓黑色的地方涂上黑色,一個大熊貓的圖像就可以展現(xiàn)出來。
我們畫大熊貓的方式,其實和媽媽們的十字繡很接近——在給定的格子里,繡上不同的顏色,最后就可以展現(xiàn)出一幅特定的“圖片”。而機器識圖的方式正好和繡十字繡的方式相反,現(xiàn)在有了一幅圖片,機器通過識別圖片中每個格子(像素點)上的顏色,將每個格子里的顏色都用數(shù)字類型存儲,得到一張很大的數(shù)字矩陣,圖片信息也就存儲在這張數(shù)字矩陣中。
上圖中每一個格子代表一個像素點,像素點里的數(shù)字代表顏色碼,顏色碼范圍是[0,255],(各式各樣的顏色都是由紅、綠、藍三色組成,每個顏色都是0~255之間數(shù)字)
我們在得到的一張大數(shù)字矩陣的基礎(chǔ)上開展卷積神經(jīng)網(wǎng)絡(luò)識別工作: 機器識圖的過程:機器識別圖像并不是一下子將一個復(fù)雜的圖片完整識別出來,而是將一個完整的圖片分割成許多個小部分,把每個小部分里具有的特征提取出來(也就是識別每個小部分),再將這些小部分具有的特征匯總到一起,就可以完成機器識別圖像的過程了
二、卷積神經(jīng)網(wǎng)絡(luò)原理介紹
用CNN卷積神經(jīng)網(wǎng)絡(luò)識別圖片,一般需要的步驟有:
-
卷積層初步提取特征
-
池化層提取主要特征
-
全連接層將各部分特征匯總
-
產(chǎn)生分類器,進行預(yù)測識別
1、卷積層工作原理
卷積層的作用:就是提取圖片每個小部分里具有的特征
假定我們有一個尺寸為6*6 的圖像,每一個像素點里都存儲著圖像的信息。我們再定義一個卷積核(相當(dāng)于權(quán)重),用來從圖像中提取一定的特征。卷積核與數(shù)字矩陣對應(yīng)位相乘再相加,得到卷積層輸出結(jié)果。
(429 = 18*1+54*0+51*1+55*0+121*1+75*0+35*1+24*0+204*1)
卷積核的取值在沒有以往學(xué)習(xí)的經(jīng)驗下,可由函數(shù)隨機生成,再逐步訓(xùn)練調(diào)整
當(dāng)所有的像素點都至少被覆蓋一次后,就可以產(chǎn)生一個卷積層的輸出(下圖的步長為1)
機器一開始并不知道要識別的部分具有哪些特征,是通過與不同的卷積核相作用得到的輸出值,相互比較來判斷哪一個卷積核最能表現(xiàn)該圖片的特征——比如我們要識別圖像中的某種特征(比如曲線),也就是說,這個卷積核要對這種曲線有很高的輸出值,對其他形狀(比如三角形)則輸出較低。卷積層輸出值越高,就說明匹配程度越高,越能表現(xiàn)該圖片的特征。
卷積層具體工作過程:
比如我們設(shè)計的一個卷積核如下左,想要識別出來的曲線如下右:
現(xiàn)在我們用上面的卷積核,來識別這個簡化版的圖片——一只漫畫老鼠
當(dāng)機器識別到老鼠的屁股的時候,卷積核與真實區(qū)域數(shù)字矩陣作用后,輸出較大:6600
而用同一個卷積核,來識別老鼠的耳朵的時候,輸出則很?。?
我們就可以認為:現(xiàn)有的這個卷積核保存著曲線的特征,匹配識別出來了老鼠的屁股是曲線的。我們則還需要其他特征的卷積核,來匹配識別出來老鼠的其他部分。卷積層的作用其實就是通過不斷的改變卷積核,來確定能初步表征圖片特征的有用的卷積核是哪些,再得到與相應(yīng)的卷積核相乘后的輸出矩陣
2、池化層工作原理
池化層的輸入就是卷積層輸出的原數(shù)據(jù)與相應(yīng)的卷積核相乘后的輸出矩陣
池化層的目的:
最常見的兩種池化層的形式:
舉例說明兩種池化方式:(池化步長為2,選取過的區(qū)域,下一次就不再選取)
在4*4的數(shù)字矩陣?yán)?,以步長2*2選取區(qū)域,比如上左將區(qū)域[1,2,3,4]中最大的值4池化輸出;上右將區(qū)域[1,2,3,4]中平均值5/2池化輸出
3、全連接層工作原理
卷積層和池化層的工作就是提取特征,并減少原始圖像帶來的參數(shù)。然而,為了生成最終的輸出,我們需要應(yīng)用全連接層來生成一個等于我們需要的類的數(shù)量的分類器。
全連接層的工作原理和之前的神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)很類似,我們需要把池化層輸出的張量重新切割成一些向量,乘上權(quán)重矩陣,加上偏置值,然后對其使用ReLU激活函數(shù),用梯度下降法優(yōu)化參數(shù)既可。
三、卷積神經(jīng)網(wǎng)絡(luò)代碼解析
1、數(shù)據(jù)集的讀取,以及數(shù)據(jù)預(yù)定義
from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets('MNIST_data', one_hot=True) sess = tf.InteractiveSession() #預(yù)定義輸入值X、輸出真實值Y placeholder為占位符 x = tf.placeholder(tf.float32, shape=[None, 784]) y_ = tf.placeholder(tf.float32, shape=[None, 10]) keep_prob = tf.placeholder(tf.float32) x_image = tf.reshape(x, [-1,28,28,1])
-
MNIST是Google的很經(jīng)典的一個做圖像識別的數(shù)據(jù)集,圖片大小是28*28的,需要先下載才能使用。
-
x、y_現(xiàn)在都是用占位符表示,當(dāng)程序運行到一定指令,向x、y_傳入具體的值后,就可以代入進行計算了
-
shape=[None, 784]是數(shù)據(jù)維度大小——因為MNIST數(shù)據(jù)集中每一張圖片大小都是28*28的,計算時候是將28*28的二維數(shù)據(jù)轉(zhuǎn)換成一個一維的、長度為784的新向量。None表示其值大小不定,意即選中的x、y_的數(shù)量暫時不定
-
keep_prob 是改變參與計算的神經(jīng)元個數(shù)的值。(下有詳細說明)
2、權(quán)重、偏置值函數(shù)
def weight_variable(shape): # truncated_normal:選取位于正態(tài)分布均值=0.1附近的隨機值 initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial)
truncated_normal()函數(shù):選取位于正態(tài)分布均值=0.1附近的隨機值
3、卷積函數(shù)、池化函數(shù)定義
# stride = [1,水平移動步長,豎直移動步長,1] return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') # stride = [1,水平移動步長,豎直移動步長,1] return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
-
輸入x是圖片信息矩陣,W是卷積核的值
-
卷積層conv2d()函數(shù)里strides參數(shù)要求第一個、最后一個參數(shù)必須是1;
-
第二個參數(shù)表示:卷積核每次向右移動的步長
-
第三個參數(shù)表示:卷積核每次向下移動的步長
-
在上面卷積層的工作原理中,有展示strides=[1, 1, 1, 1]的動態(tài)圖, 下面展示strides=[1, 2, 2, 1]時的情況:可以看到高亮的區(qū)域每次向右移動兩格,向下移動兩格
可以得到:當(dāng)我們的卷積層步長值越大,得到的輸出圖像的規(guī)格就會越小。為了使得到的圖像的規(guī)格和原圖像保持一樣的大,在輸入圖像四周填充足夠多的 0 邊界就可以解決這個問題,這時padding的參數(shù)就為“SAME”(利用邊界保留了更多信息,并且也保留了圖像的原大小)下圖:
padding的另一個可選參數(shù)為“VALID”,和“SAME”不同的是:不用0來填充邊界,這時得到的圖像的規(guī)格就會小于原圖像。新圖像尺寸大小 = 原數(shù)據(jù)尺寸大小-卷積核尺寸大小+1(一般我們選用的padding都為“SAME”)
池化函數(shù)用簡單傳統(tǒng)的2x2大小的模板做max pooling,池化步長為2,選過的區(qū)域下次不再選取
4、第一次卷積+池化
#卷積層1網(wǎng)絡(luò)結(jié)構(gòu)定義 #卷積核1:patch=5×5;in size 1;out size 32;激活函數(shù)reLU非線性處理 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) #output size 28*28*32 h_pool1 = max_pool_2x2(h_conv1) #output size 14*14*32#卷積層2網(wǎng)絡(luò)結(jié)構(gòu)定義
-
圖片集是黑白單色,x_image 中的圖片尺寸參數(shù)最后一個 = 1,彩色 = 3
-
這里的卷積核大小是5*5的,輸入的通道數(shù)是1,輸出的通道數(shù)是32
-
卷積核的值這里就相當(dāng)于權(quán)重值,用隨機數(shù)列生成的方式得到
-
由于MNIST數(shù)據(jù)集圖片大小都是28*28,且是黑白單色,所以準(zhǔn)確的圖片尺寸大小是28*28*1(1表示圖片只有一個色層,彩色圖片都是3個色層——RGB),所以經(jīng)過第一次卷積后,輸出的通道數(shù)由1變成32,圖片尺寸變?yōu)椋?8*28*32(相當(dāng)于拉伸了高)
-
再經(jīng)過第一次池化(池化步長是2),圖片尺寸為14*14*32
5、第二次卷積+池化
#卷積核2:patch=5×5;in size 32;out size 64;激活函數(shù)reLU非線性處理 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) #output size 14*14*64 h_pool2 = max_pool_2x2(h_conv2) #output size 7 *7 *64
-
這里的卷積核大小也是5*5的,第二次輸入的通道數(shù)是32,輸出的通道數(shù)是64
-
第一次卷積+池化輸出的圖片大小是14*14*32,經(jīng)過第二次卷積后圖片尺寸變?yōu)椋?4*14*64
-
再經(jīng)過第二次池化(池化步長是2),最后輸出的圖片尺寸為7*7*64
6、全連接層1、全連接層2
W_fc1 = weight_variable([7*7*64,1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64]) #[n_samples,7,7,64]->>[n_samples,7*7*64] h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 減少計算量dropout W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) prediction = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
-
全連接層的輸入就是第二次池化后的輸出,尺寸是7*7*64,全連接層1有1024個神經(jīng)元
-
tf.reshape(a,newshape)函數(shù),當(dāng)newshape = -1時,函數(shù)會根據(jù)已有的維度計算出數(shù)組的另外shape屬性值
-
keep_prob 是為了減小過擬合現(xiàn)象。每次只讓部分神經(jīng)元參與工作使權(quán)重得到調(diào)整。只有當(dāng)keep_prob = 1時,才是所有的神經(jīng)元都參與工作
-
全連接層2有10個神經(jīng)元,相當(dāng)于生成的分類器
-
經(jīng)過全連接層1、2,得到的預(yù)測值存入prediction 中
7、梯度下降法優(yōu)化、求準(zhǔn)確率
#二次代價函數(shù):預(yù)測值與真實值的誤差 loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=prediction)) #梯度下降法:數(shù)據(jù)太龐大,選用AdamOptimizer優(yōu)化器 train_step = tf.train.AdamOptimizer(1e-4).minimize(loss) correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
-
由于數(shù)據(jù)集太龐大,這里采用的優(yōu)化器是AdamOptimizer,學(xué)習(xí)率是1e-4
-
tf.argmax(prediction,1)返回的是對于任一輸入x預(yù)測到的標(biāo)簽值,tf.argmax(y_,1)代表正確的標(biāo)簽值
-
correct_prediction 這里是返回一個布爾數(shù)組。為了計算我們分類的準(zhǔn)確率,我們將布爾值轉(zhuǎn)換為浮點數(shù)來代表對與錯,然后取平均值。例如:[True, False, True, True]變?yōu)閇1,0,1,1],計算出準(zhǔn)確率就為0.75
8、其他說明、保存參數(shù)
batch = mnist.train.next_batch(50) train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0}) print("step", i, "training accuracy", train_accuracy) train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) saver.save(sess, './model.ckpt') print("test accuracy %g"%accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
-
batch 是來源于MNIST數(shù)據(jù)集,一個批次包含50條數(shù)據(jù)
-
feed_dict=({x: batch[0], y_: batch[1], keep_prob: 0.5}語句:是將batch[0],batch[1]代表的值傳入x,y_;
-
keep_prob = 0.5 只有一半的神經(jīng)元參與工作
-
當(dāng)完成訓(xùn)練時,程序會保存學(xué)習(xí)到的參數(shù),不用下次再訓(xùn)練
-
特別提醒:運行非常占內(nèi)存,而且運行到最后保存參數(shù)時,有可能卡死電腦
-
特別提醒:運行非常占內(nèi)存,而且運行到最后保存參數(shù)時,有可能卡死電腦
-
特別提醒:運行非常占內(nèi)存,而且運行到最后保存參數(shù)時,有可能卡死電腦
四、源碼及效果展示
from tensorflow.examples.tutorials.mnist import input_data os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' def weight_variable(shape): # truncated_normal:選取位于正態(tài)分布均值=0.1附近的隨機值 initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) # stride = [1,水平移動步長,豎直移動步長,1] return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') # stride = [1,水平移動步長,豎直移動步長,1] return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') mnist = input_data.read_data_sets('MNIST_data', one_hot=True) sess = tf.InteractiveSession() #預(yù)定義輸入值X、輸出真實值Y placeholder為占位符 x = tf.placeholder(tf.float32, shape=[None, 784]) y_ = tf.placeholder(tf.float32, shape=[None, 10]) keep_prob = tf.placeholder(tf.float32) x_image = tf.reshape(x, [-1,28,28,1]) #print(x_image.shape) #[n_samples,28,28,1] #卷積層1網(wǎng)絡(luò)結(jié)構(gòu)定義 #卷積核1:patch=5×5;in size 1;out size 32;激活函數(shù)reLU非線性處理 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) #output size 28*28*32 h_pool1 = max_pool_2x2(h_conv1) #output size 14*14*32#卷積層2網(wǎng)絡(luò)結(jié)構(gòu)定義 #卷積核2:patch=5×5;in size 32;out size 64;激活函數(shù)reLU非線性處理 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) #output size 14*14*64 h_pool2 = max_pool_2x2(h_conv2) #output size 7 *7 *64 W_fc1 = weight_variable([7*7*64,1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64]) #[n_samples,7,7,64]->>[n_samples,7*7*64] h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 減少計算量dropout W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) prediction = tf.matmul(h_fc1_drop, W_fc2) + b_fc2 # prediction = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) #二次代價函數(shù):預(yù)測值與真實值的誤差 loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=prediction)) #梯度下降法:數(shù)據(jù)太龐大,選用AdamOptimizer優(yōu)化器 train_step = tf.train.AdamOptimizer(1e-4).minimize(loss) correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) saver = tf.train.Saver() # defaults to saving all variables sess.run(tf.global_variables_initializer()) batch = mnist.train.next_batch(50) train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0}) print("step", i, "training accuracy", train_accuracy) train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) saver.save(sess, './model.ckpt')
效果展示如下:
訓(xùn)練700次時候,成功率已經(jīng)到達98%,越往后學(xué)習(xí),準(zhǔn)確率越高,但是 特別提醒:運行非常占內(nèi)存,而且運行到最后保存參數(shù)時,有可能卡死電腦 特別提醒:運行非常占內(nèi)存,而且運行到最后保存參數(shù)時,有可能卡死電腦 特別提醒:運行非常占內(nèi)存,而且運行到最后保存參數(shù)時,有可能卡死電腦
結(jié)束語:自己也是通過學(xué)習(xí)前輩們的講解,自己慢慢摸索的,學(xué)習(xí)就是自我填坑的過程,希望我們都能堅持下來,也希望這篇能幫到你,朋友。
經(jīng)過博友的反應(yīng),現(xiàn)已增加測試代碼
from tensorflow.examples.tutorials.mnist import input_data os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' def weight_variable(shape): # truncated_normal:選取位于正態(tài)分布均值=0.1附近的隨機值 initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) # stride = [1,水平移動步長,豎直移動步長,1] return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') # stride = [1,水平移動步長,豎直移動步長,1] return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') mnist = input_data.read_data_sets('MNIST_data', one_hot=True) sess = tf.InteractiveSession() #預(yù)定義輸入值X、輸出真實值Y placeholder為占位符 x = tf.placeholder(tf.float32, shape=[None, 784]) y_ = tf.placeholder(tf.float32, shape=[None, 10]) keep_prob = tf.placeholder(tf.float32) x_image = tf.reshape(x, [-1,28,28,1]) #print(x_image.shape) #[n_samples,28,28,1] #卷積層1網(wǎng)絡(luò)結(jié)構(gòu)定義 #卷積核1:patch=5×5;in size 1;out size 32;激活函數(shù)reLU非線性處理 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) #output size 28*28*32 h_pool1 = max_pool_2x2(h_conv1) #output size 14*14*32#卷積層2網(wǎng)絡(luò)結(jié)構(gòu)定義 #卷積核2:patch=5×5;in size 32;out size 64;激活函數(shù)reLU非線性處理 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) #output size 14*14*64 h_pool2 = max_pool_2x2(h_conv2) #output size 7 *7 *64 W_fc1 = weight_variable([7*7*64,1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1,7*7*64]) #[n_samples,7,7,64]->>[n_samples,7*7*64] h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 減少計算量dropout W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) prediction = tf.matmul(h_fc1_drop, W_fc2) + b_fc2 correct_prediction = tf.equal(tf.argmax(prediction,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) saver = tf.train.Saver() # defaults to saving all variables sess.run(tf.global_variables_initializer()) saver.restore(sess, './model.ckpt') # 和之前保存的文件名一致 print("test accuracy %g"% sess.run(accuracy,feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})) print("test accuracy %g"% accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
測試準(zhǔn)確率高達96.6%
|