CNN卷積神經網絡實現-人臉性別識別模型-可視化各層卷積特徵

本文主要是實現了根據人臉識別性別的卷積神經網絡,並對卷積過程中的提取特徵進行了可視化.

Github地址:https://github.com/chenlinzho...

在這裏插入圖片描述

在這裏插入圖片描述

卷積神經網絡

  • 卷積神經網絡最早是爲了解決圖像識別的問題,現在也用在時間序列數據和文本數據處理當中,卷積神經網絡對於數據特徵的提取不用額外進行,在對網絡的訓練的過程當中,網絡會自動提取主要的特徵.
  • 卷積神經網絡直接用原始圖像的全部像素作爲輸入,但是內部爲非全連接結構.因爲圖像數據在空間上是有組織結構的,每一個像素在空間上和周圍的像素是有關係的,和相距很遠的像素基本上是沒什麼聯繫的,每個神經元只需要接受局部的像素作爲輸入,再將局部信息彙總就能得到全局信息. 權值共享和池化兩個操作使網絡模型的參數大幅的減少,提高了模型的訓練效率.

卷積神經網絡主要特點

  • 權值共享: 在卷積層中可以有多個卷積核,每個卷積核與原始圖像進行卷積運算後會映射出一個新的2D圖像,新圖像的每個像素都來自同一個卷積核.這就是權值共享.
  • 池化: 降採樣,對卷積(濾波)後,經過激活函數處理後的圖像,保留像素塊中灰度值最高的像素點(保留最主要的特徵),比如進行 2X2的最大池化,把一個2x2的像素塊降爲1x1的像素塊.

卷積網絡的訓練數據(112*92*3圖形)

從data目錄讀取數據,famale存放女性圖片,male存放男性圖片

def read_img(list,flag=0):
    for i in range(len(list)-1):
         if os.path.isfile(list[i]):
             images.append(cv2.imread(list[i]).flatten())
             labels.append(flag)

read_img(get_img_list('male'),[0,1])
read_img(get_img_list('female'),[1,0])

images = np.array(images)
labels = np.array(labels)

重新打亂

permutation = np.random.permutation(labels.shape[0])
all_images = images[permutation,:]
all_labels = labels[permutation,:]

訓練集與測試集比例 8:2

train_total = all_images.shape[0]
train_nums= int(all_images.shape[0]*0.8)
test_nums = all_images.shape[0]-train_nums

#訓練集
images = all_images[0:train_nums,:]
labels = all_labels[0:train_nums,:]

#測試集
test_images = all_images[train_nums:train_total,:]
test_labels = all_labels[train_nums:train_total,:]

訓練參數

train_epochs=3000                # 訓練輪數
batch_size= random.randint(6,18) # 每次訓練數據,隨機
drop_prob = 0.4                  # 正則化,丟棄比例
learning_rate=0.00001            # 學習效率

網絡結構

在這裏插入圖片描述

輸入層爲輸入的灰度圖像尺寸:  -1 x 112 x 92 x 3 
第一個卷積層,卷積核的大小,深度和數量 (3, 3, 3, 16)
池化後的特徵張量尺寸:       -1 x 56 x 46 x 16
第二個卷積層,卷積核的大小,深度和數量 (3, 3, 16, 32)
池化後的特徵張量尺寸:       -1 x 28 x 23 x 32
第三個卷積層,卷積核的大小,深度和數量 (3, 3, 32, 64)
池化後的特徵張量尺寸:       -1 x 14 x 12 x 64
全連接第一層權重矩陣:         10752 x 512
全連接第二層權重矩陣:         512 x 128
輸出層與全連接隱藏層之間:     128 x 2

輔助函數

# 權重初始化(卷積核初始化)
# tf.truncated_normal()不同於tf.random_normal(),返回的值中不會偏離均值兩倍的標準差
# 參數shpae爲一個列表對象,例如[5, 5, 1, 32]對應
# 5,5 表示卷積核的大小, 1代表通道channel,對彩色圖片做卷積是3,單色灰度爲1
# 最後一個數字32,卷積核的個數,(也就是卷基層提取的特徵數量)

def weight_init(shape):
    weight = tf.truncated_normal(shape,stddev=0.1,dtype=tf.float32)
    return tf.Variable(weight)

#偏執初始化
def bias_init(shape):
    bias = tf.random_normal(shape,dtype=tf.float32)
    return tf.Variable(bias)

#全連接矩陣初始化
def fch_init(layer1,layer2,const=1):
    min = -const * (6.0 / (layer1 + layer2));
    max = -min;
    weight = tf.random_uniform([layer1, layer2], minval=min, maxval=max, dtype=tf.float32)
    return tf.Variable(weight)
    
# 源碼的位置在tensorflow/python/ops下nn_impl.py和nn_ops.py
# 這個函數接收兩個參數,x 是圖像的像素, w 是卷積核
# x 張量的維度[batch, height, width, channels]
# w 卷積核的維度[height, width, channels, channels_multiplier]
# tf.nn.conv2d()是一個二維卷積函數,
# stirdes 是卷積核移動的步長,4個1表示,在x張量維度的四個參數上移動步長
# padding 參數'SAME',表示對原始輸入像素進行填充,卷積後映射的2D圖像與原圖大小相等
# 填充,是指在原圖像素值矩陣周圍填充0像素點
# 如果不進行填充,假設 原圖爲 32x32 的圖像,卷積和大小爲 5x5 ,卷積後映射圖像大小 爲 28x28
def conv2d(images,weight):
    return tf.nn.conv2d(images,weight,strides=[1,1,1,1],padding='SAME')


    

Padding

#池化
卷積核在提取特徵時的動作成爲padding,它有兩種方式:SAME和VALID。卷積核的移動步長不一定能夠整除圖片像素的寬度,所以在有些圖片的邊框位置有些像素不能被卷積。這種不越過邊緣的取樣就叫做 valid padding,卷積後的圖像面積小於原圖像。爲了讓卷積核覆蓋到所有的像素,可以對邊緣位置進行0像素填充,然後在進行卷積。這種越過邊緣的取樣是 same padding。如過移動步長爲1,那麼得到和原圖一樣大小的圖像。如果步長很大,超過了卷積核長度,那麼same padding,得到的特徵圖也會小於原來的圖像。
def max_pool2x2(images,tname):
    return tf.nn.max_pool(images,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME',name=tname)

#images_input 爲輸入的圖片,labels_input爲輸入的標籤
images_input = tf.placeholder(tf.float32,[None,112*92*3],name='input_images')
labels_input = tf.placeholder(tf.float32,[None,2],name='input_labels')
#把圖像轉換爲112*92*3的形狀
x_input = tf.reshape(images_input,[-1,112,92,3])

訓練

第一層卷積+池化

# 卷積核3*3*3 16個     第一層卷積
w1 = weight_init([3,3,3,16])
b1 = bias_init([16])
conv_1 = conv2d(x_input,w1)+b1
relu_1 = tf.nn.relu(conv_1,name='relu_1')
max_pool_1 = max_pool2x2(relu_1,'max_pool_1')

第二層卷積+池化

# 卷積核3*3*16  32個  第二層卷積
w2 = weight_init([3,3,16,32])
b2 = bias_init([32])
conv_2 = conv2d(max_pool_1,w2) + b2
relu_2 = tf.nn.relu(conv_2,name='relu_2')
max_pool_2 = max_pool2x2(relu_2,'max_pool_2')

第三層卷積+池化

w3 = weight_init([3,3,32,64])
b3 = bias_init([64])
conv_3 = conv2d(max_pool_2,w3)+b3
relu_3 = tf.nn.relu(conv_3,name='relu_3')
max_pool_3 = max_pool2x2(relu_3,'max_pool_3')

全連接第一層

#把第三層的卷積結果平鋪成一維向量
f_input = tf.reshape(max_pool_3,[-1,14*12*64])

#全連接第一層 31*31*32,512
f_w1= fch_init(14*12*64,512)
f_b1 = bias_init([512])
f_r1 = tf.matmul(f_input,f_w1) + f_b1

#激活函數,relu隨機丟掉一些權重提供泛華能力
f_relu_r1 = tf.nn.relu(f_r1)

# 爲了防止網絡出現過擬合的情況,對全連接隱藏層進行 Dropout(正則化)處理,在訓練過程中隨機的丟棄部分
# 節點的數據來防止過擬合.Dropout同把節點數據設置爲0來丟棄一些特徵值,僅在訓練過程中,
# 預測的時候,仍使用全數據特徵
# 傳入丟棄節點數據的比例
f_dropout_r1 = tf.nn.dropout(f_relu_r1,drop_prob)

全連接第二層

f_w2 = fch_init(512,128)
f_b2 = bias_init([128])
f_r2 = tf.matmul(f_dropout_r1,f_w2) + f_b2
f_relu_r2 = tf.nn.relu(f_r2)
f_dropout_r2 = tf.nn.dropout(f_relu_r2,drop_prob)

全連接輸出層

f_w3 = fch_init(128,2)
f_b3 = bias_init([2])
f_r3 = tf.matmul(f_dropout_r2,f_w3) + f_b3
最後輸出結果,可能是這樣的[[0.0001,0.99999] ,那個位置的結果大就屬於哪個分類
f_softmax = tf.nn.softmax(f_r3,name='f_softmax')

損失函數

#交叉熵代價函數
cross_entry =  tf.reduce_mean(tf.reduce_sum(-labels_input*tf.log(f_softmax)))
#優化器,自動執行梯度下降算法
optimizer  = tf.train.AdamOptimizer(learning_rate).minimize(cross_entry)

計算準確率&損失

arg1 = tf.argmax(labels_input,1)
arg2 = tf.argmax(f_softmax,1)
#每個樣本的預測結果是一個(1,2)的vector
cos = tf.equal(arg1,arg2)
# tf.cast把bool值轉換爲浮點數
acc = tf.reduce_mean(tf.cast(cos,dtype=tf.float32))

啓動會話開始訓練

init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
Cost = []
Accuracy=[]
for i in range(train_epochs):
    idx=random.randint(0,len(train_data.images)-20)
    batch= random.randint(6,18)
    train_input = train_data.images[idx:(idx+batch)]
    train_labels = train_data.labels[idx:(idx+batch)]
    result,acc1,cross_entry_r,cos1,f_softmax1,relu_1_r= sess.run([optimizer,acc,cross_entry,cos,f_softmax,relu_1],feed_dict={images_input:train_input,labels_input:train_labels})
    print acc1
    Cost.append(cross_entry_r)
    Accuracy.append(acc1)

# 代價函數曲線
fig1,ax1 = plt.subplots(figsize=(10,7))
plt.plot(Cost)
ax1.set_xlabel('Epochs')
ax1.set_ylabel('Cost')
plt.title('Cross Loss')
plt.grid()
plt.show()

# 準確率曲線
fig7,ax7 = plt.subplots(figsize=(10,7))
plt.plot(Accuracy)
ax7.set_xlabel('Epochs')
ax7.set_ylabel('Accuracy Rate')
plt.title('Train Accuracy Rate')
plt.grid()
plt.show()

測試集驗證

#測試
arg2_r = sess.run(arg2,feed_dict={images_input:train_data.test_images,labels_input:train_data.test_labels})
arg1_r = sess.run(arg1,feed_dict={images_input:train_data.test_images,labels_input:train_data.test_labels})
#使用混淆矩陣,打印報告
print (classification_report(arg1_r, arg2_r))

驗證通過,保存模型

#保存模型
saver = tf.train.Saver()
saver.save(sess, './model/my-gender-v1.0')

使用已訓練好的模型參考:gender_model_use.py

結果: 迭代3000次,模型的準確率達到93%
在這裏插入圖片描述

訓練交叉熵代價

在這裏插入圖片描述

訓練的準確率
在這裏插入圖片描述

訓練數據中的一個樣本
在這裏插入圖片描述

第一層卷積提取的特徵
在這裏插入圖片描述

2x2池化後特徵
在這裏插入圖片描述

第二層卷積提取的特徵
在這裏插入圖片描述

2x2池化後特徵
在這裏插入圖片描述

第三層卷積提取的特徵
在這裏插入圖片描述

2x2池化後特徵
在這裏插入圖片描述

參考

https://blog.csdn.net/u014281...

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