1. 程式人生 > >利用Tensorflow構建CNN影象多分類模型及影象引數、資料維度變化情況例項分析

利用Tensorflow構建CNN影象多分類模型及影象引數、資料維度變化情況例項分析

本文以CIFAR-10為資料集,基於Tensorflow介紹了CNN(卷積神經網路)影象分類模型的構建過程,著重分析了在建模過程中卷積層、池化層、扁平化層、全連線層、輸出層的運算機理,以及經過運算後圖像尺寸、資料維度等引數的變化情況。

CIFAR-10資料集介紹

CIFAR-10資料集由60000張彩色圖片構成,其中包括50000張訓練集圖片、10000張測試集圖片,每張圖片的shape為(32,32,3),即圖片尺寸為32*32,通道數為3;所有圖片可以被分為10類,包括:

  • 飛機
  • 汽車
  • 鳥類
  • 鹿
  • 青蛙
  • 船隻
  • 卡車

官網截圖如下所示:
CIFAR-10分類截圖

利用Tensorflow構建CNN影象多分類模型

TensorFlow™ 是一個使用資料流圖進行數值計算的開放原始碼軟體庫,其API可在Python、C++、Java、Go、Swift (Early Release)語言中呼叫,其中Python API是目前最完整且易用的。TensorFlow擅長訓練深度神經網路,被認定為是神經網路中最好用的庫之一。通過使用TensorFlow我們可以快速入門神經網路, 大大降低了深度學習(也就是深度神經網路)的開發成本和開發難度。
Tensorflow使用資料流圖進行數值計算,圖中的節點代表數學運算,圖中的邊代表在這些節點之間傳遞的多維陣列(張量)。在使用其構建模型時,先搭建好流圖結構——類似於鋪好管道,然後再載入資料——向管道中注水,讓資料在各節點計算、沿各管道流動;資料在流圖中計算、傳遞時採用多維陣列(張量)的形式,因此在Tensorflow中參與計算的均為陣列資料型別。
本文使用Tensorflow構建簡單的CNN影象多分類模型,其由3個卷積(含啟用函式)與池化層、1個扁平層、3個全連線層、1個輸出層構成,示意圖如下所示:
這裡寫圖片描述

輸入(Input)層

影象資料經過標準化(normalize)、ont-hot編碼等預處理後形成的4D張量,本文輸入層張量shape為[batch, height, width, channels]。

卷積(Convolution)層

卷積層與池化層的作用在於:
1. invariance(不變性),這種不變性包括translation(平移),rotation(旋轉),scale(尺度);
2. 保留主要的特徵同時減少引數(降維,效果類似PCA)和計算量,防止過擬合,提高模型泛化能力。

卷積層計算的過程如下圖所示:
卷積計算示意圖
最左側為輸入層影象(加padding後的shape:(7,7,3)),中部為2個卷積核W0、W1及與之相對應的Bias b0、b1,最右側為輸入影象與核w0、w1進行卷積計算後的結果——前者對應於上部圖、後者對應於下部圖,卷積計算得到的結果即為feature map。每個卷積核與上層影象進行卷積運算後均會得到1個feature map。對於3通道影象,參考本圖,卷積運算的過程如下:
1. 輸入影象的每個通道分別從左上角開始,每次取與卷積核大小相同的部分(稱之為patch),與卷積核對應部分分別相乘後再相加(內積運算);如R通道與W0的最上部核(0核)對應元素相乘再相加得到0,G通道與w0的1核做內積運算得到2,B通道與w0的2核做內積運算後得到0;
2. 內積運算得到的結果相加,再加上w0的bias值(b0),得到feature map左上的元素值;即0+2+0+1=3;
3. 按照指定步長移動卷積核,直至輸入影象被整個覆蓋,得到最終的feature map;本圖中步長(stride)為(2,2)。

這裡是動圖
這裡寫圖片描述

Tensorflow實現卷積運算的函式為:

tf.nn.conv2d(
    input,
    filter,
    strides,
    padding,
    use_cudnn_on_gpu=True,
    data_format='NHWC',
    dilations=[1, 1, 1, 1],
    name=None
)

引數說明如下:

  • input:需要做卷積的輸入影象,它要求是一個Tensor,shape為[batch, in_height, in_width, in_channels],具體含義是[訓練時一個batch的圖片數量, 圖片高度, 圖片寬度, 影象通道數],注意這是一個4維的Tensor,要求型別為float32和float64其中之一;
  • filter:相當於CNN中的卷積核,它要求是一個Tensor,shape為[filter_height, filter_width, in_channels, out_channels],具體含義是[卷積核的高度,卷積核的寬度,影象通道數,卷積核個數],要求型別與引數input相同,filter的通道數要求與input的in_channels一致,有一個地方需要注意,第三維in_channels,就是引數input的第四維;
  • strides:卷積時在影象每一維的步長,這是一個一維的向量,長度4,strides[0]=strides[3]=1;
  • padding:string型別的量,只能是”SAME”、”VALID”其中之一,這個值決定了不同的卷積方式;卷積運算時卷積核滑動過程中當輸入影象(input)的in_height、in_width不是filter的filter_height、filter_width的整數倍時,”SAME”方式是在input周圍填充0以將其in_height、in_width補充為filter相應部分的整數倍,”VALID”方式將input的多餘部分丟棄,詳細介紹請參看這裡
  • use_cudnn_on_gpu:bool型別,是否使用cudnn加速,預設為true;
  • data_format:指定輸入資料、輸出資料的格式,取值為”NHWC”、”NCHW”,前者對應於 [batch, height, width, channels],後者對應於 [batch, channels, height, width],預設為’NHWC’。
  • dilations:一個可選的ints列表;預設為[1,1,1,1]。長度為4的一維張量,每個input維度的膨脹係數; 如果設定為k> 1,則每個該維度上的過濾器元素之間會有k-1個跳過的單元格; 維度順序由data_format的值決定; 在Dilations中批次和深度尺寸必須為1;
  • name:為該操作指定名稱;
  • 返回值:Tensor,也就是我們常說的feature map。

輸入影象經過卷積運算後,其高度、寬度變為——
‘SAME’ 型別的padding,其輸出的height和width計算如下:
out_height = ceil(float(in_height) / float(strides[1])) ceil:向上取整
out_width = ceil(float(in_width) / float(strides[2]))

‘VALID’型別的padding, 其輸出的height和width計算如下:
out_height = ceil(float(in_height – filter_height + 1) / float(strides[1]))
out_width = ceil(float(in_width – filter_width + 1) / float(strides[2]))

驗證該函式的文章,請看這裡

啟用函式(Activation Function)

池化層、全連線層等通過線性運算構建的是線性模型,該模型的表達能力有限;啟用函式能夠加入非線性因素,從而使模型的表達能力得到極大提升。常用的啟用函式有Tanh函式 、ReLU函式 、Leaky ReLU函式 、Maxout函式 等,本文使用ReLU函式作為啟用函式。
Tensorflow中的relu啟用函式為:

tf.nn.relu(
    features,
    name=None
)

引數說明如下:

  • features: 張量,必須為以下型別中的一種: float32, float64, int32, uint8, int16, int8, int64, bfloat16, uint16, half, uint32, uint64;
  • name: 為該操作指定名稱;
  • Returns: 張量。

池化(Pooling)層

池化層與卷積層的作用、計算方法類似,也有池化filter、步長、填充方式等引數,所不同的是,池化計算比較簡單,常取filter覆蓋部分的最大值、或者均值,分別稱之為最大池化、平均池化,最大池化的示意圖如下所示:
這裡寫圖片描述
最大池化的filter shape為(2,2), 步長也為(2,2),每個filter滑動覆蓋處取最大值,得到右側所示的結果。
Tensorflow實現最大池化運算的函式為:

tf.nn.max_pool(
    value,
    ksize,
    strides,
    padding,
    data_format='NHWC',
    name=None
)

引數說明如下:

  • value:輸入4D張量, 其格式由data_format指定;
  • ksize:含有4個元素的1D張量,指定池化核的尺寸;
  • strides:含有4個元素的1D int 張量,指定池化時在影象每一維的步長,strides[0]=strides[3]=1;
  • padding: 邊界填充方式,string型別的量,只能是”SAME”、”VALID”其中之一;
  • data_format:資料格式,string型別的量, 只能是’NHWC’、 ‘NCHW’ 、’NCHW_VECT_C’ 其中之一;
  • name:為該操作指定名稱;
  • 返回值: Tensor,也就是我們常說的feature map。

上層feature map經池化運算後,其batch、channels均不發生變化,只有height、width會發生變化,輸出height、width計算方式同卷積層。

扁平化(Flatten)層

將上一層得到的全部feature map拉直成列向量,作為全連線網路的輸入。拉直方式為height*width*channels。
Tensorflow實現扁平化層運算的函式為:

tf.contrib.layers.flatten(
    inputs,
    outputs_collections=None,
    scope=None
)

引數說明如下:

  • inputs:形如 [batch_size, …]的張量,注意張量的第1個元素必須為batch_size;
  • outputs_collections:新增到輸出的集合;
  • scope:name_scope的可選範圍。
  • 返回值:形如[batch_size, k]的扁平化張量。

影象分類模型訓練完成後,本層返回張量的shape(主要是k)就確定了,從而限定了模型預測時輸入影象的尺寸——因為在模型確定的情況下,卷積層、池化層等結構、數量均不再改變,預測影象與訓練影象尺寸一致時,經卷積、池化等運算後,扁平化輸出shape與模型一致;預測影象與訓練影象尺寸不一致時,經卷積、池化等運算後,扁平化輸出的shape與模型不一致,從而無法繼續運算。這也是遷移學習時,預訓練模型均約定輸入影象height、width的原因。

從該層開始,分類模型的網路結構類似於“多層前饋神經網路”。

全連線(Fully Connected)層

全連線層在整個卷積神經網路中起到“分類器”的作用、也是模型表示能力的“防火牆”。全連線層的運算形如X*W+b,X*W為矩陣乘法,其中X為輸入2維陣列(shape為[batch_size, k]),W為權重2維陣列(shape為[k,out_k]),b為偏置,W的第2個元素(out_k)決定了全連線層運算後輸出的2維陣列形狀(shape為[batch_size, out_k])。
由於全連線層引數冗餘(僅全連線層引數就可佔整個網路引數80%左右),需要使用tf.nn.dropout來隨機丟棄一些節點,或者使用一些效能優異的網路模型如ResNet和GoogLeNet等來取代全連線層融合學到的深度特徵。

Tensorflow中實現全連線層的函式為:

tf.layers.dense(
    inputs,
    units,
    activation=None,
    use_bias=True,
    kernel_initializer=None,
    bias_initializer=tf.zeros_initializer(),
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    trainable=True,
    name=None,
    reuse=None
)

引數說明如下:

  • inputs: Tensor input.
  • units: Integer or Long, dimensionality of the output space.
  • activation: Activation function (callable). Set it to None to maintain a linear activation.
  • use_bias: Boolean, whether the layer uses a bias.
  • kernel_initializer: Initializer function for the weight matrix. If None (default), weights are initialized using the default initializer used by tf.get_variable.
  • bias_initializer: Initializer function for the bias.
  • kernel_regularizer: Regularizer function for the weight matrix.
  • bias_regularizer: Regularizer function for the bias.
  • activity_regularizer: Regularizer function for the output.
  • kernel_constraint: An optional projection function to be applied to the kernel after being updated by an Optimizer (e.g. used to implement norm constraints or value constraints for layer weights). The function must take as input the unprojected variable and must return the projected variable (which must have the same shape). Constraints are not safe to use when doing asynchronous distributed training.
  • bias_constraint: An optional projection function to be applied to the bias after being updated by an Optimizer.
  • trainable: Boolean, if True also add variables to the graph collection GraphKeys.TRAINABLE_VARIABLES (see tf.Variable).
  • name: String, the name of the layer.
  • reuse: Boolean, whether to reuse the weights of a previous layer by the same name.
  • Returns:Output tensor the same shape as inputs except the last dimension is of size units.

Tensorflow中的Dropout函式為:

tf.nn.dropout(
    x,
    keep_prob,
    noise_shape=None,
    seed=None,
    name=None
)

引數說明:

  • x: A floating point tensor.
  • keep_prob: A scalar Tensor with the same type as x. The probability that each element is kept.
  • noise_shape: A 1-D Tensor of type int32, representing the shape for randomly generated keep/drop flags.
  • seed: A Python integer. Used to create random seeds. See tf.set_random_seed for behavior.
  • name: A name for this operation (optional).
  • Returns: A Tensor of the same shape of x.

輸出(Output)層

輸出層運算與全連線層類似,只是在設定運算引數時輸出節點數量需與分類標記數量相一致,並且在運算完成後再使用tf.nn.softmax函式,得到測試影象屬於各分類的概率,該所有概率值之和為1。
Tensorflow中的tf.nn.softmax函式如下所示:

tf.nn.softmax(
    logits,
    axis=None,
    name=None,
    dim=None
)

引數說明如下:

  • logits: A non-empty Tensor. Must be one of the following types: half, float32, float64.
  • axis: The dimension softmax would be performed on. The default is -1 which indicates the last dimension.
  • name: A name for the operation (optional).
  • dim: Deprecated alias for axis.
  • Returns: A Tensor. Has the same type and shape as logits.

訓練得到分類模型

按照輸入層、卷積層與池化層(共3層)、扁平化層、全連線層(共3層)、輸出層的順序搭建好模型,以交叉熵均值作為cost,以Adam優化演算法尋找全域性最優點,以cost、Accuracy(分類準確率)作為評估模型的指標,各層引數設定如下圖所示:
這裡寫圖片描述

補充說明如下:

  • batch為訓練時每批輸入的影象數量,視訓練環境的硬體配置而定,本文設定為512
  • 卷積核height,width:(3,3)
  • 卷積步長:(1,1,1,1)
  • 卷積 padding:SAME
  • 啟用函式:tf.nn.relu
  • 最大池化height,width:(2,2)
  • 最大池化步長:(1,2,2,1)
  • 池化 padding:SAME
  • Dropout時保留節點的比例:0.5
  • Epochs(訓練輪數):30

訓練構建好的CNN影象分類模型時,單張圖片用3D陣列表示,多張圖片用4D陣列表示,經過卷積、啟用函式、池化、扁平化、全連線、Dropout等運算,其batch、height、width、channels、feature map數量、節點數量、資料shape有的會發生變化,有的不會發生變化,如下表所示:

運算名稱 batch 影象height 影象width 影象channels feature map數量 節點數量 資料shape
卷積 不改變 會改變 會改變 (首次卷積後變成1) 會改變 - 4D張量
啟用函式 不改變 不改變 不改變 - 不改變 - 4D張量
池化 不改變 會改變 會改變 - 不改變 - 4D張量
扁平化 不改變 不改變 不改變 - 不改變 不改變 2D張量
全連線 不改變 - - - - 會改變 2D張量
Dropout 不改變 - - - - 會改變 2D張量

使用CIFAR-10訓練集進行訓練,在訓練集和測試集上的準確率均為0.73,如下圖所示:
這裡寫圖片描述

這裡寫圖片描述

小結

本文以CIFAR-10資料集為例,使用Tensorflow構建了簡單的CNN影象分類模型,該模型包含輸入層、卷積與池化層、扁平化層、全連線層、輸出層,這些層均是構建CNN分類模型必要且重要的層;訓練後分類模型的準確率雖然不夠高,但本文重在分析各層運算時影象資料shape的變化及其它尺寸資料的改變情況。