1. 程式人生 > >【TensorFlow-windows】(七) CNN之VGG-net的測試

【TensorFlow-windows】(七) CNN之VGG-net的測試

主要內容:
1.CNN之VGG-net的測試
2.該實現中的函式總結

平臺:
1.windows 10 64位
2.Anaconda3-4.2.0-Windows-x86_64.exe (當時TF還不支援python3.6,又懶得在高版本的anaconda下配置多個Python環境,於是裝了一個3-4.2.0(預設裝python3.5),建議裝anaconda3的最新版本,TF1.2.0版本已經支援python3.6!)
3.TensorFlow1.1.0

牛津大學計算機視覺組(Visual Geometry Group)出產的VGGnet是 ILSVRC 2014年 分類第二名,定位專案第一名,其VGG-16\VGG-19廣泛應用於影象領域!
論文《very deep convolutional networks for large-scale image recognition》
特點就是多個小卷積核(3*3)的堆疊來替代大卷積核(5*5,7*7),減少引數的同時增加了特徵提取的能力。

來看看論文中給了5個模型的基本情況:
這裡寫圖片描述

今天就用TF寫一個VGG-16 ,上圖紅框中的那個,由於硬體問題,這裡和Alex-net一樣,只是做了一個前向和反向的耗時計算。(PS: GTX960m的 GPU(2G視訊記憶體),沒辦法實現 batch_size為32,全連線層1為4096,全連線層2為4096個神經元的VGG網路,因此我修改了網路大小, batch_size為16, 全連線層1和2 都改為2048個神經元)

# -*- coding: utf-8 -*-
"""
Created on Mon Jun 12 16:36:43 2017

@author: ASUS
"""
from datetime import
datetime import math import time import tensorflow as tf # 定義函式對卷積層進行初始化 # input_op : 輸入資料 # name : 該卷積層的名字,用tf.name_scope()來命名 # kh,kw : 分別是卷積核的高和寬 # n_out : 輸出通道數 # dh,dw : 步長的高和寬 # p : 是引數列表,儲存VGG所用到的引數 # 採用xavier方法對卷積核權值進行初始化 def conv_op(input_op, name, kh, kw, n_out, dh, dw, p): n_in = input_op.get_shape()[-1
].value # 獲得輸入影象的通道數 with tf.name_scope(name) as scope: kernel = tf.get_variable(scope+'w', shape = [kh, kw, n_in, n_out], dtype = tf.float32, initializer = tf.contrib.layers.xavier_initializer_conv2d()) # 卷積層計算 conv = tf.nn.conv2d(input_op, kernel, (1, dh, dw, 1), padding = 'SAME') bias_init_val = tf.constant(0.0, shape = [n_out], dtype = tf.float32) biases = tf.Variable(bias_init_val, trainable = True, name = 'b') z = tf.nn.bias_add(conv, biases) activation = tf.nn.relu(z, name = scope) p += [kernel, biases] return activation # 定義函式對全連線層進行初始化 # input_op : 輸入資料 # name : 該全連線層的名字 # n_out : 輸出的通道數 # p : 引數列表 # 初始化方法用 xavier方法 def fc_op(input_op, name, n_out, p): n_in = input_op.get_shape()[-1].value with tf.name_scope(name) as scope: kernel = tf.get_variable(scope+'w', shape = [n_in, n_out], dtype = tf.float32, initializer = tf.contrib.layers.xavier_initializer()) biases = tf.Variable(tf.constant(0.1, shape = [n_out], dtype = tf.float32), name = 'b') activation = tf.nn.relu_layer(input_op, kernel, # ??????????????? biases, name = scope) p += [kernel, biases] return activation # 定義函式 建立 maxpool層 # input_op : 輸入資料 # name : 該卷積層的名字,用tf.name_scope()來命名 # kh,kw : 分別是卷積核的高和寬 # dh,dw : 步長的高和寬 def mpool_op(input_op, name, kh, kw, dh, dw): return tf.nn.max_pool(input_op, ksize = [1,kh,kw,1], strides = [1, dh, dw, 1], padding = 'SAME', name = name) #---------------建立 VGG-16------------------ def inference_op(input_op, keep_prob): p = [] # 第一塊 conv1_1-conv1_2-pool1 conv1_1 = conv_op(input_op, name='conv1_1', kh=3, kw=3, n_out = 64, dh = 1, dw = 1, p = p) conv1_2 = conv_op(conv1_1, name='conv1_2', kh=3, kw=3, n_out = 64, dh = 1, dw = 1, p = p) pool1 = mpool_op(conv1_2, name = 'pool1', kh = 2, kw = 2, dw = 2, dh = 2) # 第二塊 conv2_1-conv2_2-pool2 conv2_1 = conv_op(pool1, name='conv2_1', kh=3, kw=3, n_out = 128, dh = 1, dw = 1, p = p) conv2_2 = conv_op(conv2_1, name='conv2_2', kh=3, kw=3, n_out = 128, dh = 1, dw = 1, p = p) pool2 = mpool_op(conv2_2, name = 'pool2', kh = 2, kw = 2, dw = 2, dh = 2) # 第三塊 conv3_1-conv3_2-conv3_3-pool3 conv3_1 = conv_op(pool2, name='conv3_1', kh=3, kw=3, n_out = 256, dh = 1, dw = 1, p = p) conv3_2 = conv_op(conv3_1, name='conv3_2', kh=3, kw=3, n_out = 256, dh = 1, dw = 1, p = p) conv3_3 = conv_op(conv3_2, name='conv3_3', kh=3, kw=3, n_out = 256, dh = 1, dw = 1, p = p) pool3 = mpool_op(conv3_3, name = 'pool3', kh = 2, kw = 2, dw = 2, dh = 2) # 第四塊 conv4_1-conv4_2-conv4_3-pool4 conv4_1 = conv_op(pool3, name='conv4_1', kh=3, kw=3, n_out = 512, dh = 1, dw = 1, p = p) conv4_2 = conv_op(conv4_1, name='conv4_2', kh=3, kw=3, n_out = 512, dh = 1, dw = 1, p = p) conv4_3 = conv_op(conv4_2, name='conv4_3', kh=3, kw=3, n_out = 512, dh = 1, dw = 1, p = p) pool4 = mpool_op(conv4_3, name = 'pool4', kh = 2, kw = 2, dw = 2, dh = 2) # 第五塊 conv5_1-conv5_2-conv5_3-pool5 conv5_1 = conv_op(pool4, name='conv5_1', kh=3, kw=3, n_out = 512, dh = 1, dw = 1, p = p) conv5_2 = conv_op(conv5_1, name='conv5_2', kh=3, kw=3, n_out = 512, dh = 1, dw = 1, p = p) conv5_3 = conv_op(conv5_2, name='conv5_3', kh=3, kw=3, n_out = 512, dh = 1, dw = 1, p = p) pool5 = mpool_op(conv5_3, name = 'pool5', kh = 2, kw = 2, dw = 2, dh = 2) # 把pool5 ( [7, 7, 512] ) 拉成向量 shp = pool5.get_shape() flattened_shape = shp[1].value * shp[2].value * shp[3].value resh1 = tf.reshape(pool5, [-1, flattened_shape], name = 'resh1') # 全連線層1 添加了 Droput來防止過擬合 fc1 = fc_op(resh1, name = 'fc1', n_out = 2048, p = p) fc1_drop = tf.nn.dropout(fc1, keep_prob, name = 'fc1_drop') # 全連線層2 添加了 Droput來防止過擬合 fc2 = fc_op(fc1_drop, name = 'fc2', n_out = 2048, p = p) fc2_drop = tf.nn.dropout(fc2, keep_prob, name = 'fc2_drop') # 全連線層3 加一個softmax求給類別的概率 fc3 = fc_op(fc2_drop, name = 'fc3', n_out = 1000, p = p) softmax = tf.nn.softmax(fc3) predictions = tf.argmax(softmax, 1) return predictions, softmax, fc3, p # 定義評測函式 def time_tensorflow_run(session, target, feed, info_string): num_steps_burn_in = 10 total_duration = 0.0 total_duration_squared = 0.0 for i in range(num_batches + num_steps_burn_in): start_time = time.time() _ = session.run(target, feed_dict = feed) duration = time.time() - start_time if i >= num_steps_burn_in: if not i % 10: print('%s: step %d, duration = %.3f' % (datetime.now(), i-num_steps_burn_in, duration)) total_duration += duration total_duration_squared += duration * duration mean_dur = total_duration / num_batches var_dur = total_duration_squared / num_batches - mean_dur * mean_dur std_dur = math.sqrt(var_dur) print('%s: %s across %d steps, %.3f +/- %.3f sec / batch' %(datetime.now(), info_string, num_batches, mean_dur, std_dur)) # 定義函式 執行網路 def run_benchmark(): with tf.Graph().as_default(): image_size = 224 images = tf.Variable(tf.random_normal( [batch_size, image_size, image_size, 3], dtype = tf.float32, stddev = 1e-1)) keep_prob = tf.placeholder(tf.float32) predictions, softmax, fc3, p = inference_op(images, keep_prob) # 建立session ,初始化引數 init = tf.global_variables_initializer() sess = tf.Session() sess.run(init) # 評測 forward的耗時 time_tensorflow_run(sess, predictions, {keep_prob: 1.0}, 'Forward') # 評測 forward 和 backward objective = tf.nn.l2_loss(fc3) gard = tf.gradients(objective, p) time_tensorflow_run(sess,gard,predictions, {keep_prob: 0.5}, 'Forward-backward') batch_size = 16 num_batches = 100 run_benchmark()

函式總結(續上篇)

sess = tf.InteractiveSession() 將sess註冊為預設的session
tf.placeholder() , Placeholder是輸入資料的地方,也稱為佔位符,通俗的理解就是給輸入資料(此例中的圖片x)和真實標籤(y_)提供一個入口,或者是存放地。(個人理解,可能不太正確,後期對TF有深入認識的話再回來改~~)
tf.Variable() Variable是用來儲存模型引數,與儲存資料的tensor不同,tensor一旦使用掉就消失
tf.matmul() 矩陣相乘函式
tf.reduce_mean 和tf.reduce_sum 是縮減維度的計算均值,以及縮減維度的求和
tf.argmax() 是尋找tensor中值最大的元素的序號 ,此例中用來判斷類別
tf.cast() 用於資料型別轉換
————————————–我是分割線(一)———————————–
tf.random_uniform 生成均勻分佈的隨機數
tf.train.AdamOptimizer() 建立優化器,優化方法為Adam(adaptive moment estimation,Adam優化方法根據損失函式對每個引數的梯度的一階矩估計和二階矩估計動態調整針對於每個引數的學習速率)
tf.placeholder “佔位符”,只要是對網路的輸入,都需要用這個函式這個進行“初始化”
tf.random_normal 生成正態分佈
tf.add 和 tf.matmul 資料的相加 、相乘
tf.reduce_sum 縮減維度的求和
tf.pow 求冪函式
tf.subtract 資料的相減
tf.global_variables_initializer 定義全域性引數初始化
tf.Session 建立會話.
tf.Variable 建立變數,是用來儲存模型引數的變數。是有別於模型的輸入資料的
tf.train.AdamOptimizer (learning_rate = 0.001) 採用Adam進行優化,學習率為 0.001
————————————–我是分割線(二)———————————–
1. hidden1_drop = tf.nn.dropout(hidden1, keep_prob) 給 hindden1層增加Droput,返回新的層hidden1_drop,keep_prob是 Droput的比例
2. mnist.train.next_batch() 來詳細講講 這個函式。一句話概括就是,打亂樣本順序,然後按順序讀取batch_size 個樣本 進行返回。
具體看程式碼及其註釋,首先要找到函式定義,在tensorflow\contrib\learn\python\learn\datasets 下的mnist.py
————————————–我是分割線(三)———————————–
1. tf.nn.conv2d(x, W, strides = [1, 1, 1, 1], padding =’SAME’)對於這個函式主要理解 strides和padding,首先明確,x是輸入,W是卷積核,並且它們的維數都是4(發現strides裡有4個元素沒,沒錯!就是一一對應的)
先說一下卷積核W也是一個四維張量,各維度表示的資訊是:[filter_height, filter_width, in_channels, out_channels]

輸入x,x是一個四維張量 ,各維度表示的資訊是:[batch, in_height, in_width, in_channels]

strides裡的每個元素就是對應輸入x的四個維度的步長,因為第2,3維是影象的長和寬,所以平時用的strides就在這裡設定,而第1,4維一般不用到,所以是1

padding只有兩種取值方式,一個是 padding=[‘VALID’] 一個是padding=[‘SAME’]
valid:採用丟棄的方式,只要移動一步時,最右邊有超出,則這一步不移動,並且剩餘的進行丟棄。如下圖,圖片長13,卷積核長6,步長是5,當移動一步之後,已經卷積核6-11,再移動一步,已經沒有足夠的畫素點了,所以就不能移動,因此 12,13被丟棄。
same:顧名思義,就是保持輸入的大小不變,方法是在影象邊緣處填充全0的畫素
————————————–我是分割線(四)———————————–
1.tf.nn.l2_loss()是對一個Tensor物件求L2 norm,這裡用來求權值的L2範數
2.tf.add_to_collection(‘losses’, weight_loss), 把weight_loss 加到名為losses的loss中去
3.tf.nn.lrn,做區域性相應歸一化,來看看函式的定義

lrn(input, depth_radius=None, bias=None, alpha=None, beta=None,name=None)

函式有6個輸入,第一個自然是輸入啦,2-5是LRN用到的引數,第一個是depth半徑,bias alpha beta是相應的引數,來看看虛擬碼就清楚了

sqr_sum[a, b, c, d] = sum(input[a, b, c, d - depth_radius : d + depth_radius + 1] ** 2)
output = input / (bias + alpha * sqr_sum) ** beta
2-5的引數是有預設值的,分別是5,1,1,0.5

3.tf.nn.sparse_softmax_cross_entropy_with_logits()就是計算交叉熵的,但是如果是一個batch的,得出的是一個向量,還需要配合tf.reduce_mean()才能計算得出最終的loss(標量)

4.tf.get_collection(‘losses’), 從一個集合中取出全部變數,返回的是一個列表,這裡是把losses的都取出來,在上面把 weight_loss和輸出層的loss都加到losses中了,這個函式就是把losses中的loss取出來

5.tf.add_n:把一個列表的東西都依次加起來 (Adds all input tensors element-wise.) 輸入是一個list (inputs: A list of Tensor objects, each with same shape and type.)
————————————–我是分割線(五)———————————–

1.t.get_shape().as_list() ,t是一個tensor,函式以list的形式返回 tensor的shape
2.tf.name_scope(‘conv1’) as scope ,tf.name_scope是用來命名Variable的,print(parameters)可以看到命名
3. tf.Graph().as_default()能夠在這個上下文裡面覆蓋預設的圖,測試了一下 不用這個也可以,但是用上好點,可能是避免衝突吧~
————————————–我是分割線(六)———————————–
1. tensor.get_shape()[-1].value 獲得 倒數第一個維度上的維數
2. tf.get_variable(scope+’w’,
shape = [kh, kw, n_in, n_out], dtype = tf.float32,
initializer = tf.contrib.layers.xavier_initializer_conv2d())
來看看函式定義:

def get_variable(name,
                 shape=None,
                 dtype=None,
                 initializer=None,
                 regularizer=None,
                 trainable=True,
                 collections=None,
                 caching_device=None,
                 partitioner=None,
                 validate_shape=True,
                 use_resource=None,
                 custom_getter=None):

這下就清楚多了,除了第一個形參,其餘都是帶預設值的關鍵字引數,這樣不根據參形參的順序對引數賦值啦

3.tf.nn.relu_layer(input_op, kernel, biases, name = scope)
這個函式把 卷積操作,加偏置操作,relu操作 整合在一塊兒了,不過這個僅限於全連線層這種一維的層,因為輸入必須是2D的,具體如下:

def relu_layer(x, weights, biases, name=None):
Args:
x: a 2D tensor. Dimensions typically: batch, in_units
weights: a 2D tensor. Dimensions typically: in_units, out_units
biases: a 1D tensor. Dimensions: out_units
name: A name for the operation (optional). If not specified
“nn_relu_layer” is used.
Returns:
A 2-D Tensor computing relu(matmul(x, weights) + biases).
Dimensions typically: batch, out_units.