這幾天在做一個【python入門語法】和【Django全棧開發】的互動課程,就把AI算法實現系列文章落下了…
我感覺我再補充幾個算法和數學原理文章,AI的系列課程也可以開始做了,希望大家多多捧場!🎉
今天補充一個,經典的CNN結構!
目標:提高速度
說到神經網路,我們已經學了四關了,相信大家對於【網絡結構】的重要性肯定了解了。
不同的神經網絡的作用只有一個:加快運算速度!
深層神經網絡和淺層神經網絡相比,在速度上就有所突破,但是對於更龐大的數據,比如說圖片數據、視頻數據來說,這種突破帶來的優化微乎其微。
對於圖片而言,比如說一個分辨率200x200的三通道(RGB)圖片,他的數據就有200x200x3,再如果有10000條數據,那麼他的數據量就有 200x200x3x10000 之多!(各類算法的複雜度具體計算方法,請看鏈接文章【DNN/CNN/RNN複雜度評估】)
深度學習的深度沒有固定的定義,2006年Hinton解決了局部最優解問題,將隱含層發展到7層,這達到了深度學習上所說的真正深度。不同問題的解決所需要的隱含層數自然也是不相同的,一般語音識別4層就可以,而圖像識別20層屢見不鮮。但隨着層數的增加,又出現了參數爆炸增長的問題。假設輸入的圖片是1K*1K的圖片,隱含層就有1M個節點,會有10^12個權重需要調節,這將容易導致過度擬合和局部最優解問題的出現。爲了解決上述問題,提出了CNN。
面對如此龐大的數據,我們直接用DNN(深度神經網絡)雖然可以達成效果,但是,計算速度始終難以突破,那麼我們就要想辦法去【降低參與計算的參數】
面對圖像問題,我們進行了一系列思考,DNN是模擬了人的【神經元網絡】,相當於大腦的功能,那麼既然我們面對圖像的是看圖,那麼我們是不是可以參考視覺的大腦皮層結構呢??
他來了他來了,他帶着算法走來了!
Hubel 和Wiesel1968年做的實驗是很有影響力的,具體的就不說了,有興趣的同學可以看這個科學史,反正它發現,視覺皮層對於信息的處理,做了一個很NB的操作:【特徵提取】,行話叫做 【過濾】 !
通俗解釋吧,就是進行了以下操作:
- 不看單個像素信息,而是直接看一片
- 這個一片不是全部圖像,而是【盲人摸象】一般由無數個小片段組成
- 每個小片段我們抽象一個平均結果,就是知道他大概是個啥,比如說知道這裏是豎線,這裏是一大片黃色等等…這些抽象後的大概結果組成了我們新的圖片,給下一層神經
- 這一層受到了上面的指令,就繼續這樣拆分,找出最大特徵和匹配,形成結果再給下一層
- 最後一層收到的結果就是是我們提取的特徵了!然後在大腦中匹配一些列可能的選項,可能是xxx的概率分別是多少,然後給出判斷。
總之,我們通過視覺認識事物的方式,就是通過幾次*幾層的【盲人摸象】來搞定的。
再解釋一邊:想象一下,我們現在是個【盲人皇帝】,命令一大堆的工人給我們當眼睛,他們每個人首先描述一小塊的最重要特徵告訴我,我們就很快得出結論,至少要比按像素要快很多…
然後我們這個工人描述特徵的層級再多一點,由【淺層】到【深層】來加快我們的判斷,這個方法就叫做 「卷積神經網絡」
每個小工人就是「卷積神經元」,每一批工人處理出來結果給下一批,就是不同層的神經網絡。
卷積+池化就是工人提取特徵的方式:
- 卷積——提取特徵
卷積層的運算過程如下圖,用一個卷積核掃完整張圖片:
解釋一下,下面藍色是輸入的圖,綠色是卷積之後的圖,外側的虛線補充的是人工添加的數據,目的是掃描的全一點。
這樣,我們就得到了一個綠色的卷積輸出結果!
但是還沒結束,因爲它的數據量還沒有變化,我們需要讓自己【近視眼】一下,將一大片的卷積結果變成一個!這樣的【特徵降維】的操作就是池化
我們上面的,就是一個2*2池化Max矩陣,就是選擇這裏最大值輸出(簡單粗暴但有用),連續操作之後就變成了下圖
然後把這個結果繼續傳給下一層的卷積+池化…和DNN一個操作。最後根據結果的Label進行匹配【最大似然結果】,輸出結果~
提問:淺層和深層神經網絡的優化是在優化 每個神經元的權重值 ,那麼在CNN中我們要優化什麼呢???
思考…
.
.
.
…
.
.
…
.
.
…
.
.
好的,公佈答案,是…卷積核中的權重!
這就要深入到具體的卷積計算了,先來一段天書,不過你之後要慢慢會看哦卷積,是我們學習高等數學之後,新接觸的一種運算,因爲涉及到積分、級數,所以看起來覺得很複雜。
卷積運算
我們稱 (f*g)(n) 爲 f,g 的卷積
其連續的定義爲:
其離散的定義爲:
這兩個式子有一個共同的特徵:
這個特徵有什麼意義?
如果遍歷這些直線,就好比,把毛巾沿着角捲起來:
只看數學符號,卷積是抽象的,不好理解的,但是,我們可以通過現實中的意義,來習慣卷積這種運算,正如我們小學的時候,學習加減乘除需要各種蘋果、糖果來幫助我們習慣一樣。
以上卷積運算摘自參考文章卷積運算 - Bill Yuan - 博客園,文章內講解更詳細,相關卷積運算理解請點擊查看。
但是我們看到一個關鍵信息:卷積操作是【乘操作】!a爲輸入,f爲卷積,c爲輸出示例:
經過計算得到的新圖像,那麼我們的【卷積動圖加數據】就是這樣的:
回憶我們神經網絡的知識,我們的構造函數中w,是不是和這裏的卷積核驚人相似!而每個卷積核中都具有一些列參數,我們需要調整這個識別的參數讓我們的特徵提取更加順利,更加傾向於我們的預判結果(訓練)。
換句話說,神經網絡中的全聯接參數優化,不變成了一個個卷積核的參數了,我們需要優化的參數大幅度減少!
CNN最大的利用了圖像的局部信息。圖像中有固有的局部模式(比如輪廓、邊界,人的眼睛、鼻子、嘴等)可以利用,顯然應該將圖像處理中的概念和神經網絡技術相結合,對於CNN來說,並不是所有上下層神經元都能直接相連,而是通過“卷積核”作爲中介。
同一個卷積核在所有圖像內是共享的,圖像通過卷積操作後仍然保留原先的位置關係。卷積神經網絡隱含層,通過一個例子簡單說明卷積神經網絡的結構。假設m-1=1是輸入層,我們需要識別一幅彩色圖像,這幅圖像具有四個通道ARGB(透明度和紅綠藍,對應了四幅相同大小的圖像),假設卷積核大小爲100100,共使用100個卷積核w1到w100(從直覺來看,每個卷積核應該學習到不同的結構特徵)。
用w1在ARGB圖像上進行卷積操作,可以得到隱含層的第一幅圖像;這幅隱含層圖像左上角第一個像素是四幅輸入圖像左上角100100區域內像素的加權求和,以此類推。
同理,算上其他卷積核,隱含層對應100幅“圖像”。每幅圖像對是對原始圖像中不同特徵的響應。按照這樣的結構繼續傳遞下去。
CNN中還有max-pooling等操作進一步提高魯棒性。
綜上,我們就是通過訓練,要得到一個合理的卷積核是我們的目的(參數共享),優化器我們還是選擇梯度下降算法即可
參數共享是指在一個模型的多個函數中使用相同的參數,它是卷積運算帶來的固有屬性。
在全連接中,計算每層的輸出時,權重矩陣中的元素只作用於某一個輸入元素一次;
而在卷積神經網絡中,卷積核中的每一個元素將作用於每一個局部輸入的特定位置上。根據參數共享的思想,我們只需要學習一組參數集合,而不需要針對每一個位置的每一個參數來進行優化學習,從而大大降低了模型的存儲需求。
卷積神經網絡
卷積神經網絡(Convolutional Neural Networks, CNN)是一類包含卷積計算且具有深度結構的前饋神經網絡(Feedforward Neural Networks),是深度學習(deep learning)的代表算法之一 。由於卷積神經網絡能夠進行平移不變分類(shift-invariant classification),因此也被稱爲“平移不變人工神經網絡(Shift-Invariant Artificial Neural Networks, SIANN)” 。
《百度百科》
典型的卷積神經網絡由3部分構成:
- 卷積層
- 池化層
- 全連接層
卷積層負責提取圖像中的局部特徵;池化層用來大幅降低參數量級(降維);全連接層類似傳統神經網絡的部分,用來輸出想要的結果。
那麼只要滿足上面的條件,網絡搭建起來就可以用【優化器】去迭代就好啦~
本文中將手把手帶你實現一個超超超簡單的LENET-5卷積神經網絡:
模型爲: 輸入 - 第一層卷積 - 第一層池化 - 第二層卷積 - 第二層池化 - 第三層卷積 - 第一層全連接 - Drop層 - 第二層全連接輸出
LeNet-5共有7層,不包含輸入,每層都包含可訓練參數;每個層有多個Feature Map,每個FeatureMap通過一種卷積濾波器提取輸入的一種特徵,然後每個FeatureMap有多個神經元。
想要了解具體結構可以點擊,LeNet-5結構講解來查看。
學習的還是我們之前的mnist訓練集。
代碼實現
選擇架構:tensorflow會使我們的工作變得簡單很多。
我們首先理清思路:我們需要構建模型,之後訓練模型並將訓練好的神經網絡保存下來。然後就可以調用我們的網絡來執行自己的識別操作。
- 建立模型-訓練模型-保存模型
- 使用模型測試
首先我們來構建模型函數:
#!/usr/bin/python
# -*- coding: utf-8 -*
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import tensorflow as tf
FLAGS = None
def deepnn(x):
with tf.name_scope('reshape'):
x_image = tf.reshape(x, [-1, 28, 28, 1])
#第一層卷積層,卷積核爲5*5,生成6個feature maps.
with tf.name_scope('conv1'):
W_conv1 = weight_variable([5, 5, 1, 6])
b_conv1 = bias_variable([6])
h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) #激活函數採用relu
# 第一層池化層,下采樣2.
with tf.name_scope('pool1'):
h_pool1 = max_pool_2x2(h_conv1)
# 第二層卷積層,卷積核爲5*5,生成16個feature maps
with tf.name_scope('conv2'):
W_conv2 = weight_variable([5, 5, 6, 16])
b_conv2 = bias_variable([16])
h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)#激活函數採用relu
# 第二層池化層,下采樣2.
with tf.name_scope('pool2'):
h_pool2 = max_pool_2x2(h_conv2)
with tf.name_scope('conv3'):
W_conv3 = weight_variable([5, 5, 16, 120])
b_conv3 = bias_variable([120])
h_conv3 = tf.nn.relu(conv2d(h_pool2, W_conv3) + b_conv3)#激活函數採用relu
# #第一層全連接層,將7x7x16個feature maps與120個features全連接
# with tf.name_scope('fc1'):
# W_fc1 = weight_variable([7 * 7 * 16, 120])
# b_fc1 = bias_variable([120])
#
# h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*16])
# h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
# 第二層全連接層,將120個7 * 7 * feature maps與84個features全連接
with tf.name_scope('fc2'):
W_fc2 = weight_variable([7 * 7 * 120, 84])
b_fc2 = bias_variable([84])
h_pool3_flat = tf.reshape(h_conv3, [-1, 7 * 7 * 120])
h_fc2 = tf.nn.relu(tf.matmul(h_pool3_flat, W_fc2) + b_fc2)
#dropout層,訓練時候隨機讓一半的隱含層節點權重不工作,防止過擬合
with tf.name_scope('dropout'):
keep_prob = tf.placeholder(tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc2, keep_prob)
# 第三層全連接層,84個features和10個features全連接
with tf.name_scope('fc3'):
W_fc3 = weight_variable([84, 10])
b_fc3 = bias_variable([10])
y_conv = tf.matmul(h_fc1_drop, W_fc3) + b_fc3
return y_conv, keep_prob
#卷積
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')
#權重
def weight_variable(shape):
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)
上面的代碼就是把哥哥層都鏈接好了即可,不熟悉的也是你tensorflow不熟悉罷了。
之後構建訓練主函數:
- 導入數據
- 初始化輸入輸出
- 構建網絡模型
- 構建損失函數
- 構建優化器
- 初始化訓練場,並開啓記錄
- 開始訓練,記錄模型
- 保存,記錄地址並打印
另外,我們引入accuracy來記錄準確率的記錄與運算,運算方法是利用包中的測試集
#!/train_minist_model.py
# -*- coding: utf-8 -*
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import sys
import tempfile
from tensorflow.examples.tutorials.mnist import input_data
import matplotlib.pyplot as plt
import tensorflow as tf
import mnist_model
FLAGS = None
def main(_):
mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)
#輸入變量,mnist圖片大小爲28*28
x = tf.placeholder(tf.float32, [None, 784])
#輸出變量,數字是1-10
y_ = tf.placeholder(tf.float32, [None, 10])
# 構建網絡,輸入—>第一層卷積—>第一層池化—>第二層卷積—>第二層池化—>第一層全連接—>第二層全連接
y_conv, keep_prob = mnist_model.deepnn(x)
#第一步對網絡最後一層的輸出做一個softmax,第二步將softmax輸出和實際樣本做一個交叉熵
#cross_entropy返回的是向量
with tf.name_scope('loss'):
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_,
logits=y_conv)
#求cross_entropy向量的平均值得到交叉熵
cross_entropy = tf.reduce_mean(cross_entropy)
#AdamOptimizer是Adam優化算法:一個尋找全局最優點的優化算法,引入二次方梯度校驗
with tf.name_scope('adam_optimizer'):
train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
#記錄在測試集上的精確度
with tf.name_scope('accuracy'):
correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1)) #測試集判斷
correct_prediction = tf.cast(correct_prediction, tf.float32) #數據準換
accuracy = tf.reduce_mean(correct_prediction) #均值代表準確率
#將訓練的網絡保存下來
saver = tf.train.Saver() #初始化
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
writer = tf.summary.FileWriter("./log/", sess.graph) #記錄器開始
cost_accum2 = []
for i in range(1000):
batch = mnist.train.next_batch(50)
if i % 10 == 0:
train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0})#輸入是字典,表示tensorflow被feed的值
print('step %d, training accuracy %g' % (i, train_accuracy))
cost_accum2.append(train_accuracy) # 記錄準確率
train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
test_accuracy = 0
for i in range(200):
batch = mnist.test.next_batch(50)
test_accuracy += accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0}) / 200;
print('test accuracy %g' % test_accuracy)
save_path = saver.save(sess,"mnist_cnn_model.ckpt") #保存路徑g
print('save_path : ',save_path)
writer.close() # 關閉記錄器
draw(cost_accum2)
#繪製
def draw(cost_accum2):
#plt.plot(range(len(cost_accum)), cost_accum, 'r')
plt.plot(range(len(cost_accum2)), cost_accum2, 'b')
plt.title('Accuracy of myCNN')
plt.xlabel('step/10')
plt.ylabel('accuracy')
print('show')
plt.show()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--data_dir', type=str,
default='./',
help='Directory for storing input data')
FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
這一步我們就完成了我們訓練與保存!我們的準確率曲線繪製如下:
可以看出雖然訓練了1000次,但是還是有潛力可以挖掘的!此時的準確率已達到0.9342
那麼我們怎麼使用我們保存的模型呢?這個說實話不是學習重點,保存代碼,直接用就行了🤷♂️
#!/usr/bin/python
# -*- coding: utf-8 -*
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import tensorflow as tf
import mnist_model
from PIL import Image, ImageFilter
def load_data(argv):
grayimage = Image.open(argv).convert('L')
width = float(grayimage.size[0])
height = float(grayimage.size[1])
newImage = Image.new('L', (28, 28), (255))
if width > height:
nheight = int(round((20.0/width*height),0))
if (nheigth == 0):
nheigth = 1
img = grayimage.resize((20,nheight), Image.ANTIALIAS).filter(ImageFilter.SHARPEN)
wtop = int(round(((28 - nheight)/2),0))
newImage.paste(img, (4, wtop))
else:
nwidth = int(round((20.0/height*width),0))
if (nwidth == 0):
nwidth = 1
img = grayimage.resize((nwidth,20), Image.ANTIALIAS).filter(ImageFilter.SHARPEN)
wleft = int(round(((28 - nwidth)/2),0))
newImage.paste(img, (wleft, 4))
tv = list(newImage.getdata())
tva = [ (255-x)*1.0/255.0 for x in tv]
return tva
def main(argv):
imvalue = load_data(argv)
x = tf.placeholder(tf.float32, [None, 784])
y_ = tf.placeholder(tf.float32, [None, 10])
y_conv, keep_prob = mnist_model.deepnn(x)
y_predict = tf.nn.softmax(y_conv)
init_op = tf.global_variables_initializer()
saver = tf.train.Saver()
with tf.Session() as sess:
sess.run(init_op)
saver.restore(sess, "mnist_cnn_model.ckpt")
prediction=tf.argmax(y_predict,1)
predint = prediction.eval(feed_dict={x: [imvalue],keep_prob: 1.0}, session=sess)
print (predint[0])
if __name__ == "__main__":
# main(sys.argv[1])
main('4_my.png')
好了,到此我們卷積神經網絡的入門就結束了,下面我們要學習的是RNN結構。
而你在卷積上的優化更多是針對你擁有的數據集,來優化你的模型結構,注意魯棒性的優化和避免過度擬合。