認識卷積神經網絡中的卷積與圖像濾波

卷積神經網絡是各種深度神經網絡中應用很廣泛的一種,在視覺的很多問題上都取得了較好的效果,另外,它在自然語言處理,計算機圖形學等領域也有成功的應用。

LeNet 是當前各種深度卷積神經網絡的鼻祖,它是Yann LeCun於1989年首次提出“卷積”,也是最早推動深度學習領域發展的卷積神經網絡之一,多次成功迭代之後被命名爲 LeNet5,當時 LeNet 框架主要用於字符識別任務。

在早期傳統的神經網絡中,例如BP神經網絡,人們通常依賴專家經驗設計輸入特徵向量。在過去幾十年的經驗來看,通過人工找的特徵並不一定有效,其中早期人臉識別算法普遍採用了人工特徵與分類器結合的思路,人工特徵的巔峯之作是出自CVPR 2013年MSRA的"Blessing of Dimisionality: High Dimensional Feature and Its Efficient Compression for Face Verification",一篇關於如何使用高維度特徵在人臉驗證中的文章。分類器有成熟的方案,如神經網絡,支持向量機,貝葉斯等。看起來很有前景,但是我們的專家有限,計算能力也不足以支持高維特徵計算需求。

我們先做個簡單直觀方案,假如任何特徵都是從圖像中提取的,把整幅圖像作爲特徵來訓練神經網絡,單說這數據量非常驚人!對於這種情況,我們還得做降低維數處理,於是,卷積概念被引入。

自2012年,深度學習在ILSVRC-2012大放異彩以後,卷積神經網絡在圖像分類中能力得到展現和應用,通過學習得到的卷積核明顯優於人工設計的特徵與分類器結合的方案。在人臉識別的研究者利用卷積神經網絡(CNN)對海量的人臉圖片進行學習,然後對輸入圖像提取出對區分不同人的臉有用的特徵向量,替代人工設計的特徵。

DeepFace是CVPR2014上由Facebook提出的方法,是深度卷積神經網絡在人臉識別領域的奠基之作,使用的是經典的交叉熵損失函數(Softmax)進行問題優化,最後通過特徵嵌入(Feature Embedding)得到固定長度的人臉特徵向量。DeepFace在LFW上取得了97.35%的準確率,已經接近了人類的水平。相比於1997年那篇基於卷積神經網絡的40個人400張圖的數據規模,Facebook蒐集了4000個人400萬張圖片進行模型訓練,也許我們能得出一個結論:大數據人工智能取得了成功!

1.卷積基本原理

在泛函分析中,卷積是一種函數的定義。它是通過兩個函數f和g生成第三個函數的一種數學算子,表徵函數f與g經過翻轉和平移的重疊部分的面積。

卷積的數學定義是這樣的:
在這裏插入圖片描述
在計算機視覺領域,卷積核、濾波器通常爲較小尺寸的矩陣,比如3×3、5×5等,數字圖像是相對較大尺寸的2維張量,圖像卷積運算與相關運算的關係如下圖所示。其中F爲濾波器,x爲輸入圖像,O輸出爲結果。
在這裏插入圖片描述
在這裏插入圖片描述
離散後的卷積計算公式如下:
在這裏插入圖片描述

從左到右,逐行掃描,對應位置相乘累加形成如下圖右側的結果。
在這裏插入圖片描述
按照離散後的卷積計算公式,我們使用程序代碼實踐卷積濾波理論和感受濾波效果。在這裏插入圖片描述

import skimage.color
import skimage.io
import numpy
import numpy as np
import matplotlib
import sys

def conv_(img, conv_filter):
    filter_size = conv_filter.shape[0]

    result = numpy.zeros((img.shape))
    padding_size = 1
    #在輸入圖像中循環應用卷積操作.

    #濾波圖像行的取值範圍
    for row in range(0, img.shape[0]-filter_size + padding_size):
        for col in range(0, img.shape[1]-filter_size + padding_size):
            #按濾波器尺寸獲取篩選區域.
            curr_region = img[row:row+filter_size, col:col+filter_size]
            #篩選區域與濾波器相乘.
            curr_result = curr_region * conv_filter
            conv_sum = numpy.sum(curr_result) #相乘後求和
            result[row + padding_size, col + padding_size] = conv_sum #卷積層特徵映射中求和的保存
            
    #剪裁結果矩陣.
    final_result = result[padding_size:result.shape[0]-padding_size,padding_size:result.shape[1]-padding_size]
    return final_result

def conv(img, conv_filter):
    if len(img.shape) > 2 or len(conv_filter.shape) > 3: # 檢查圖像通道數與濾波器通道數是否一致.
        if img.shape[-1] != conv_filter.shape[-1]:
            print("Error: Number of channels in both image and filter must match.")
            sys.exit()
    if conv_filter.shape[1] != conv_filter.shape[2]: # Check if filter dimensions are equal.
        print('Error: Filter must be a square matrix. I.e. number of rows and columns must match.')
        sys.exit()
    if conv_filter.shape[1]%2==0: # Check if filter diemnsions are odd.
        print('Error: Filter must have an odd size. I.e. number of rows and columns must be odd.')
        sys.exit()

    # 初始化輸出特徵圖爲0,大小尺寸與輸入圖形一致
    feature_maps = numpy.zeros((img.shape[0]-conv_filter.shape[1]+1, 
                                img.shape[1]-conv_filter.shape[1]+1, 
                                conv_filter.shape[0]))

    # 使用濾波器進行卷積操作.
    for filter_num in range(conv_filter.shape[0]):
        print("Filter ", filter_num + 1)
        curr_filter = conv_filter[filter_num, :] # getting a filter from the bank.
        """ 
        Checking if there are mutliple channels for the single filter.
        If so, then each channel will convolve the image.
        The result of all convolutions are summed to return a single feature map.
        """
        if len(curr_filter.shape) > 2:
            conv_map = conv_(img[:, :, 0], curr_filter[:, :, 0]) 
            for ch_num in range(1, curr_filter.shape[-1]): 
                conv_map = conv_map + conv_(img[:, :, ch_num], curr_filter[:, :, ch_num])
        else: # There is just a single channel in the filter.
            conv_map = conv_(img, curr_filter)
        feature_maps[:, :, filter_num] = conv_map
    return feature_maps 
    
img = skimage.io.imread("timg1.jpg")
img = skimage.color.rgb2gray(img)

l1_filter = numpy.zeros((2,3,3))  #定義卷積核
l1_filter[0, :, :] = numpy.array([[[-1, 0, 1], 
                                   [-1, 0, 1], 
                                   [-1, 0, 1]]])
l1_filter[1, :, :] = numpy.array([[[1,   1,  1], 
                                   [0,   0,  0], 
                                   [-1, -1, -1]]])

l1_feature_map = conv(img, l1_filter)

# 畫出結果圖
fig0, ax0 = matplotlib.pyplot.subplots(nrows=1, ncols=1)
ax0.imshow(img).set_cmap("gray")
ax0.set_title("Input Image")
matplotlib.pyplot.savefig("in_img.png", bbox_inches="tight")
matplotlib.pyplot.close(fig0)

fig1, ax1 = matplotlib.pyplot.subplots(nrows=1, ncols=2)
ax1[0].imshow(l1_feature_map[:, :, 0]).set_cmap("gray")
ax1[0].set_title("Map1")

ax1[1].imshow(l1_feature_map[:, :, 1]).set_cmap("gray")
ax1[1].set_title("Map2")

matplotlib.pyplot.savefig("Out_Map.png", bbox_inches="tight")
matplotlib.pyplot.close(fig1)matplotlib.pyplot.close(fig1)

經程序濾波處理的圖像,效果如下圖所示,抓出輪廓特徵。
在這裏插入圖片描述

2. 卷積核

卷積核就是在圖像處理時,對於給定輸入圖像,對輸入圖像中一塊區域的像素進行加權處理後,成爲輸出圖像中的對應像素,其中權值由一個函數定義,這個函數稱爲卷積核。
卷積核一般也叫成濾波器,其設計一般有以下幾個原則:

卷積核形狀一般是奇數的。如3×3,5×5,7×7,這樣能保證一定會有個中心點(像數是最小單元,不存在子像數概念),主要是從像素編碼的角度看,奇數似乎正好強調了此像素點,而偶數就會引起平衡或者抵消,消除此點的特點。3×3 是最小的能夠捕獲像素八鄰域信息的尺寸。
卷積核的各個元素值一般相加等於1,這樣做的原因是保證原圖像經過卷積核的作用亮度保持不變(但該原則不是必須)。
在圖9-3 卷積計算過程示意圖中,6×7輸入二維矩陣,經過卷積核濾波處理輸出結果是4×5矩陣,四周邊緣爲padding,如果要使卷積操作後,圖像大小不變,通常是四周補上“0”。
在這裏插入圖片描述
卷積操作會使圖像變小,爲了圖像卷積後大小不變,需要填充0。在tensorflow中,如果padding=‘same’,按前面實踐效果,很容易讓人誤解,卷積後矩陣的大小是不變的,而實際大小公式如下:
設輸入圖像高爲Hin,寬爲Win;卷積核高爲HF,寬爲WF,輸出結果高位Hout,寬爲Wout,stride爲步長。
在這裏插入圖片描述
當stride=1時,padding = ‘same’卷積操作後圖片大小不變,需要填充0,;padding=‘valid’情況下,圖片大小變爲Hout,Wout,結果向上取整。
當stride不爲1時,Wout=Win/stride, Hout=Hin/stride (結果向上取整),對圖片卷積後還是會變小。
卷積核使用多大的合適呢?目前業界的經驗是3×3,就有很好的效果和性能。

關於卷積核,早期觀點是卷積核越大,AlexNet中用到了一些非常大的卷積核,比如11×11、5×5卷積核,receptive field(感受野)越大,看到的圖片信息越多,因此獲得的特徵越好。但是大的卷積核會導致計算量的暴增,不利於模型深度的增加,計算性能也會降低。於是在VGG(最早使用)、Inception網絡中,利用2個3×3卷積核的組合比1個5×5卷積核的效果更佳,同時參數量(3×3×2+1 與 5×5×1+1對比)被降低,因此後來3×3卷積核被廣泛應用在各種模型中。

3.卷積與圖像

對圖像的濾波處理就是對圖像應用一個小小的卷積核,突顯我們所需要的圖像特徵,那這個小小的卷積核到底有哪些魔法,能刻畫圖像特徵。接下來我們一起來領略下一些簡單但不簡單的卷積核的魔法。
如下代碼是基於Tensorflow卷積函數tf.nn.conv2d,使用不同卷積核濾波圖像。

import tensorflow as tf
import skimage.color
import skimage.io
import numpy as np
import matplotlib

img = skimage.io.imread("timg1.jpg")
img = skimage.color.rgb2gray(img)

img1 = img #img.flatten() #轉爲一維數組

l1_filter1 = np.array([[-1, 0, 1], 
                       [-1, 0, 1], 
                       [-1, 0, 1]])

l1_filter1 = l1_filter1.astype("float64")
x_image = tf.reshape(img1, [1,420,300,1])
W_filter = tf.reshape(l1_filter1, [3,3,1,1])

input_tensor = tf.Variable(x_image,  name='input')
input_weight = tf.Variable(W_filter, dtype="float64",  name='weight')

op = tf.nn.conv2d(input_tensor,input_weight, strides=[1, 1, 1, 1], padding='SAME')

init = tf.initialize_all_variables() 
with tf.Session() as sess:
    sess.run(init)
    feature_map = sess.run(op)

# 畫出結果圖
fig0, ax0 = matplotlib.pyplot.subplots(nrows=1, ncols=1)
ax0.imshow(img).set_cmap("gray")
ax0.set_title("Input Image")
matplotlib.pyplot.savefig("in_img1.png", bbox_inches="tight")
matplotlib.pyplot.close(fig0)

fig1, ax1 = matplotlib.pyplot.subplots(nrows=1, ncols=1)
ax1.imshow(feature_map[0,:, :, 0]).set_cmap("gray")
ax1.set_title("Map1")
matplotlib.pyplot.savefig("Out_Map1.png", bbox_inches="tight")
matplotlib.pyplot.close(fig1)

注:其中,關於Tensorflow的卷積,conv2d中要求如下:
input的四個維度是[batch, in_height, in_width, in_channels],
filter的四個維度是[filter_height, filter_width, in_channels, out_channels]。

使用不同的卷積核,濾波後的圖像效果樣例如下所示。
在這裏插入圖片描述

在這裏插入圖片描述

參考:
《使用Python科學計算包搭建CNN算法實踐(1)》 CSDN博客 肖永威 2018.05
《TensorFlow CNN卷積神經網絡實現工況圖分類識別(一)》 CSDN博客 肖永威 2019.03

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