【超簡明】通俗解釋CNN-卷積-圖片識別-TF實現-數據可視化【人工智能學習】

這幾天在做一個【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的操作:【特徵提取】,行話叫做 【過濾】

通俗解釋吧,就是進行了以下操作:

  1. 不看單個像素信息,而是直接看一片
  2. 這個一片不是全部圖像,而是【盲人摸象】一般由無數個小片段組成
  3. 每個小片段我們抽象一個平均結果,就是知道他大概是個啥,比如說知道這裏是豎線,這裏是一大片黃色等等…這些抽象後的大概結果組成了我們新的圖片,給下一層神經
  4. 這一層受到了上面的指令,就繼續這樣拆分,找出最大特徵和匹配,形成結果再給下一層
  5. 最後一層收到的結果就是是我們提取的特徵了!然後在大腦中匹配一些列可能的選項,可能是xxx的概率分別是多少,然後給出判斷。

在這裏插入圖片描述
總之,我們通過視覺認識事物的方式,就是通過幾次*幾層的【盲人摸象】來搞定的。

再解釋一邊:想象一下,我們現在是個【盲人皇帝】,命令一大堆的工人給我們當眼睛,他們每個人首先描述一小塊的最重要特徵告訴我,我們就很快得出結論,至少要比按像素要快很多…
然後我們這個工人描述特徵的層級再多一點,由【淺層】到【深層】來加快我們的判斷,這個方法就叫做 「卷積神經網絡」

每個小工人就是「卷積神經元」,每一批工人處理出來結果給下一批,就是不同層的神經網絡。

卷積+池化就是工人提取特徵的方式:

  1. 卷積——提取特徵
    卷積層的運算過程如下圖,用一個卷積核掃完整張圖片:
    在這裏插入圖片描述
    解釋一下,下面藍色是輸入的圖,綠色是卷積之後的圖,外側的虛線補充的是人工添加的數據,目的是掃描的全一點。
    這樣,我們就得到了一個綠色的卷積輸出結果!

但是還沒結束,因爲它的數據量還沒有變化,我們需要讓自己【近視眼】一下,將一大片的卷積結果變成一個!這樣的【特徵降維】的操作就是池化
在這裏插入圖片描述
我們上面的,就是一個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圖像上進行卷積操作,可以得到隱含層的第一幅圖像;這幅隱含層圖像左上角第一個像素是四幅輸入圖像左上角100
100區域內像素的加權求和,以此類推。
同理,算上其他卷積核,隱含層對應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會使我們的工作變得簡單很多。
我們首先理清思路:我們需要構建模型,之後訓練模型並將訓練好的神經網絡保存下來。然後就可以調用我們的網絡來執行自己的識別操作。

  1. 建立模型-訓練模型-保存模型
  2. 使用模型測試

首先我們來構建模型函數:

#!/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不熟悉罷了。
之後構建訓練主函數:

  1. 導入數據
  2. 初始化輸入輸出
  3. 構建網絡模型
  4. 構建損失函數
  5. 構建優化器
  6. 初始化訓練場,並開啓記錄
  7. 開始訓練,記錄模型
  8. 保存,記錄地址並打印

另外,我們引入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結構。
而你在卷積上的優化更多是針對你擁有的數據集,來優化你的模型結構,注意魯棒性的優化和避免過度擬合。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章