1. 程式人生 > >使用tensorflow實現CNN

使用tensorflow實現CNN

前言

好久沒有更新部落格,在之前的博文用程式碼一步一步完成了手寫數字識別,但是在工業應用中不需要這麼複雜的實現。我們造車再也不需要自己造輪子。Tensorflow作為當今最流行的機器學習框架,也是Google的親兒子,對其學習也是有必要性。當然tensorflow也出來很久了,在寫本文的時候tensorflow已經是1.8版本。這篇文章沒有什麼理論知識,因為理論知識早在前面的文章說過了,也用程式碼實現了一個一個神經元搭建起來的卷積神經網路。本文就是使用tensorflow對一個 CNN 網路的實現,程式碼思路清晰,可供後續學習參考。

模型功能

這個Model資料來源於大名鼎鼎的Mnist資料集,相信搞機器學習的同學對這個資料集已經不感冒。這個資料集裡面有55000張28*28的手寫數字(黑白)以及對應的標籤。這個資料集資料結構簡單,而且有一定資料量,是研究機器學習在影象方面各種分類演算法的基礎。這個資料集也被tensorflow收錄在自己的原始碼中。所以獲取這個資料集相對來說也比較簡單。只需要一條命令即可。

from tensorflow.examples.tutorials.mnist import input_data
mnist=input_data.read_data_sets('mnist_data',one_hot=True)#引數一:檔案目錄。引數二:是否為one_hot向量

這裡one_hot為True其實就是說每一條資料對應的標籤是一個向量。因為是手寫數字,數字只有0,1,2,3,4,5,6,7,8,9
相對應的one-hot就是
0: [1,0,0,0,0,0,0,0,0,0]
1: [0,1,0,0,0,0,0,0,0,0]
2: [0,0,1,0,0,0,0,0,0,0]
3: [0,0,0,1,0,0,0,0,0,0]
4: [0,0,0,0,1,0,0,0,0,0]
5: [0,0,0,0,0,1,0,0,0,0]
6: [0,0,0,0,0,0,1,0,0,0]
7: [0,0,0,0,0,0,0,1,0,0]
8: [0,0,0,0,0,0,0,0,1,0]
9: [0,0,0,0,0,0,0,0,0,1]

這裡不詳細解釋one-hot是什麼意思,如果不懂的看客需自行百度。

網路結構

image.png
我們會一層一層的實現圖上的網路結構。這裡做一個簡單的說明。

第一層是輸入層
因為輸入是一個28*28的黑白影象。所以在二維空間上面是28*28。一個畫素點相當於一個神經元。那麼輸入層的維度就是三維[28,28,1]

第二層為一個卷積層
卷積層的尺寸是5*5,也就是window是5*5。深度為32。也就是說filter的個數是32個。相當於32個window掃描影象。那麼影象就會變成28*28*32的影象。(卷積原理自行復習)

第三層是一個池化層。
這裡使用的是max pooling。在之前也提到過,池化有點像另一種方式的卷積。也是一個window對影象進行掃描。這裡的window的尺寸是2*2。因為池化的原理經過池化之後影象的尺寸會變小但是厚度不會發生變化,輸出變成了14*14*32。(池化原理自行復習)

第四層是一個卷積層
與第二層一樣不做贅述。

第五層是一個池化層
與第三層一樣不做贅述。

第六層是一個全連線層
全連線層的unit的個數為1024
在第五層和第六層之間做了一個flat(平坦化)操作,也就是說把7*7*64的資料壓成[7*7*64,1]。也就是3136個
神經元與1024個神經元進行全連結。

第七層還是一個全連線層
因為標籤一個one-hot向量,我們的輸出應該也是一個1*1*10的向量。這裡的unit的個數就是10個。1024個神經元與10個神經元進行全連結。

實現

使用tensorflow搭建神經網路就跟我們小時候搭積木是一樣的,不需要關注過多邏輯實現的問題。關注點應該轉移到模型的設計上面來,這也是tensorflow這麼火的原因。廢話不多說我們開始搭積木。
這裡要知道使用tensorflow是先“畫”出網路結構,之後再進行資料的輸入。所以在搭建神經網路的時候不需要關注資料的輸入和輸出的邏輯。就好比如C\C++這類的非動態語言在真正寫業務邏輯之前就要把要用到的變數先定義出來。tensorflow就是先定義出網路結構。

預備

定義輸入、輸出

input_x=tf.placeholder(tf.float32,[None,28*28])/255.
#輸出是一個one hot的向量
output_y=tf.placeholder(tf.int32,[None,10])

#None means tensor 的第一維度可以是任意維度
#/255. 做均一化。因為影象畫素值是(0~255)如果用原始資料計算出來的東西會很大。

輸入層

由圖上可以知道輸入是一張張28*28的影象。那麼單個輸入的維度就是三維[28,28,1] 那麼輸入層其實就是把資料reshape一下形狀。

#輸入層 [28*28*1]
input_x_images=tf.reshape(input_x,[-1,28,28,1])

有人肯定會問為什麼是4維的,是因為影象的個數我們不知道。那麼第一個維度就用-1。python會幫我們算出來第一個維度的數值。

卷積層1

卷積層的卷積核的size是5*5,卷積的深度是32。
這裡使用的是tensorflow的 layers.conv2d
這裡解釋一下幾個常用的引數(詳情見tensorflow官網的文件)

#conv1 5*5*32
#layers.conv2d parameters
#inputs 輸入,是一個張量
#filters 卷積核個數,也就是卷積層的厚度
#kernel_size 卷積核的尺寸
#strides: 掃描步長
#padding: 邊邊補0 valid不需要補0,same需要補0,為了保證輸入輸出的尺寸一致,補多少不需要知道
#activation: 啟用函式
conv1=tf.layers.conv2d(
    inputs=input_x_images,
    filters=32,
    kernel_size=[5,5],
    strides=1,
    padding='same',
    activation=tf.nn.relu
)

在搭建網路結構的時候我們需要額外注意輸入輸出的尺寸和維度,因為這是上下層緊密相連的。
這裡使用padding=’same’的好處就是tensorflow會幫我們在卷積核補0,不需要我們關注補幾圈0。就可以保證輸入在二維是28*28。輸出也是28*28。因為卷積層的深度是32,那麼輸出就是[28,28,32] (注意:這裡是對單張圖片而言的)。真正的維度是四維
[?,28,28,32] ?代表不知道具體數值,需要根據輸入,tensorflow幫我們計算。我們也可以在程式碼執行的時候打印出來conv1的detail.

Tensor("conv2d/Relu:0", shape=(?, 28, 28, 32), dtype=float32)

池化層1

#tf.layers.max_pooling2d
#inputs 輸入,張量必須要有四個維度
#pool_size: 過濾器的尺寸

pool1=tf.layers.max_pooling2d(
    inputs=conv1,
    pool_size=[2,2],
    strides=2
)

經過池化操作之後。資料維度變成了
[?,14,14,32]

卷積層2&池化層2

換湯不換藥緊接著還是卷積層和池化層。

#conv2 5*5*64
conv2=tf.layers.conv2d(
    inputs=pool1,
    filters=64,
    kernel_size=[5,5],
    strides=1,
    padding='same',
    activation=tf.nn.relu
)

#輸出變成了 [?,14,14,64]

#pool2 2*2
pool2=tf.layers.max_pooling2d(
    inputs=conv2,
    pool_size=[2,2],
    strides=2
)

#輸出變成了[?,7,7,64]

flat(平坦化)

#flat(平坦化)
flat=tf.reshape(pool2,[-1,7*7*64])
#形狀變成了[?,3136]

全連線層

#densely-connected layers 全連線層 1024
#tf.layers.dense
#inputs: 張量
#units: 神經元的個數
#activation: 啟用函式
dense=tf.layers.dense(
    inputs=flat,
    units=1024,
    activation=tf.nn.relu
)

dropout

#dropout
#tf.layers.dropout
#inputs 張量
#rate 丟棄率
#training 是否是在訓練的時候丟棄
dropout=tf.layers.dropout(
    inputs=dense,
    rate=0.5,
)

輸出層

#輸出層,不用啟用函式(本質就是一個全連線層)
logits=tf.layers.dense(
    inputs=dropout,
    units=10
)
#輸出形狀[?,10]

OP

網路結構我們搭出來了,就要開始寫各種op(操作)。我們已經知道神經網路最重要的就是前向傳播和反向傳遞。
在tensorflow裡我們不需要進行復雜的求導然後進行梯度更新,這些tensorflow統統幫我們搞定。

損失

#計算誤差 cross entropy(交叉熵),再用Softmax計算百分比的概率
#tf.losses.softmax_cross_entropy
#onehot_labels: 標籤值
#logits: 神經網路的輸出值
loss=tf.losses.softmax_cross_entropy(onehot_labels=output_y,
                                     logits=logits)

這裡的loss其實也是一個張量。

訓練OP

這裡使用的Adam優化器,也可以使用GD優化器。

#計算誤差 cross entropy(交叉熵),再用Softmax計算百分比的概率
#tf.losses.softmax_cross_entropy
#onehot_labels: 標籤值
#logits: 神經網路的輸出值
loss=tf.losses.softmax_cross_entropy(onehot_labels=output_y,
                                     logits=logits)

準確率OP

#精度。計算預測值和實際標籤的匹配程度
#tf.metrics.accuracy
#labels:真實標籤
#predictions: 預測值
#Return: (accuracy,update_op)accuracy 是一個張量準確率,update_op 是一個op可以求出精度。
#這兩個都是區域性變數
accuracy_op=tf.metrics.accuracy(
    labels=tf.argmax(output_y,axis=1),
    predictions=tf.argmax(logits,axis=1)
)[1] #為什麼是1 是因為,我們這裡不是要準確率這個數字。而是要得到一個op

資料傳輸

上面我們已經把圖和OP都建立了。現在只需要用資料把這些連線起來就可以了。資料傳輸分兩步。

圖的初始化

#建立會話
sess=tf.Session()
#初始化變數
#group 把很多個操作弄成一個組
#初始化變數,全域性,和區域性
init=tf.group(tf.global_variables_initializer(),
              tf.local_variables_initializer())
sess.run(init)

資料流動

for i in range(20000):
    batch=mnist.train.next_batch(50) #從Train(訓練)資料集中取‘下一個’樣本
    train_loss,train_op_=sess.run([loss,train_op],{input_x:batch[0],output_y:batch[1]})
    if i%100==0:
        test_accuracy=sess.run(accuracy_op,{input_x:test_x,output_y:test_y})
        print("Step=%d, Train loss=%.4f,[Test accuracy=%.2f]"%(i,train_loss,test_accuracy))

#測試: 列印20個預測值和真實值 對
test_output=sess.run(logits,{input_x:test_x[:20]})
inferenced_y=np.argmax(test_output,1)
print(inferenced_y,'Inferenced numbers')#推測的數字
print(np.argmax(test_y[:20],1),'Real numbers')
sess.close()

全部程式碼

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 5/21/2018 11:21 AM
# @Author  : SkullFang
# @Contact : [email protected]
# @File    : CNN_demo1.py
# @Software: PyCharm

import numpy as np
import tensorflow as tf

#download mnist datasets
#55000 * 28 * 28 55000image
from tensorflow.examples.tutorials.mnist import input_data

mnist=input_data.read_data_sets('mnist_data',one_hot=True)#引數一:檔案目錄。引數二:是否為one_hot向量

#one_hot is encoding format
#None means tensor 的第一維度可以是任意維度
#/255. 做均一化
input_x=tf.placeholder(tf.float32,[None,28*28])/255.
#輸出是一個one hot的向量
output_y=tf.placeholder(tf.int32,[None,10])

#輸入層 [28*28*1]
input_x_images=tf.reshape(input_x,[-1,28,28,1])
#從(Test)資料集中選取3000個手寫數字的圖片和對應標籤

test_x=mnist.test.images[:3000] #image
test_y=mnist.test.labels[:3000] #label



#隱藏層
#conv1 5*5*32
#layers.conv2d parameters
#inputs 輸入,是一個張量
#filters 卷積核個數,也就是卷積層的厚度
#kernel_size 卷積核的尺寸
#strides: 掃描步長
#padding: 邊邊補0 valid不需要補0,same需要補0,為了保證輸入輸出的尺寸一致,補多少不需要知道
#activation: 啟用函式
conv1=tf.layers.conv2d(
    inputs=input_x_images,
    filters=32,
    kernel_size=[5,5],
    strides=1,
    padding='same',
    activation=tf.nn.relu
)
print(conv1)

#輸出變成了 [28*28*32]

#pooling layer1 2*2
#tf.layers.max_pooling2d
#inputs 輸入,張量必須要有四個維度
#pool_size: 過濾器的尺寸

pool1=tf.layers.max_pooling2d(
    inputs=conv1,
    pool_size=[2,2],
    strides=2
)
print(pool1)
#輸出變成了[?,14,14,32]

#conv2 5*5*64
conv2=tf.layers.conv2d(
    inputs=pool1,
    filters=64,
    kernel_size=[5,5],
    strides=1,
    padding='same',
    activation=tf.nn.relu
)

#輸出變成了  [?,14,14,64]

#pool2 2*2
pool2=tf.layers.max_pooling2d(
    inputs=conv2,
    pool_size=[2,2],
    strides=2
)

#輸出變成了[?,7,7,64]

#flat(平坦化)
flat=tf.reshape(pool2,[-1,7*7*64])


#形狀變成了[?,3136]

#densely-connected layers 全連線層 1024
#tf.layers.dense
#inputs: 張量
#units: 神經元的個數
#activation: 啟用函式
dense=tf.layers.dense(
    inputs=flat,
    units=1024,
    activation=tf.nn.relu
)

#輸出變成了[?,1024]
print(dense)

#dropout
#tf.layers.dropout
#inputs 張量
#rate 丟棄率
#training 是否是在訓練的時候丟棄
dropout=tf.layers.dropout(
    inputs=dense,
    rate=0.5,
)
print(dropout)

#輸出層,不用啟用函式(本質就是一個全連線層)
logits=tf.layers.dense(
    inputs=dropout,
    units=10
)
#輸出形狀[?,10]
print(logits)

#計算誤差 cross entropy(交叉熵),再用Softmax計算百分比的概率
#tf.losses.softmax_cross_entropy
#onehot_labels: 標籤值
#logits: 神經網路的輸出值
loss=tf.losses.softmax_cross_entropy(onehot_labels=output_y,
                                     logits=logits)
# 用Adam 優化器來最小化誤差,學習率0.001 類似梯度下降
print(loss)
train_op=tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)


#精度。計算預測值和實際標籤的匹配程度
#tf.metrics.accuracy
#labels:真實標籤
#predictions: 預測值
#Return: (accuracy,update_op)accuracy 是一個張量準確率,update_op 是一個op可以求出精度。
#這兩個都是區域性變數
accuracy_op=tf.metrics.accuracy(
    labels=tf.argmax(output_y,axis=1),
    predictions=tf.argmax(logits,axis=1)
)[1] #為什麼是1 是因為,我們這裡不是要準確率這個數字。而是要得到一個op

#建立會話
sess=tf.Session()
#初始化變數
#group 把很多個操作弄成一個組
#初始化變數,全域性,和區域性
init=tf.group(tf.global_variables_initializer(),
              tf.local_variables_initializer())
sess.run(init)

for i in range(20000):
    batch=mnist.train.next_batch(50) #從Train(訓練)資料集中取‘下一個’樣本
    train_loss,train_op_=sess.run([loss,train_op],{input_x:batch[0],output_y:batch[1]})
    if i%100==0:
        test_accuracy=sess.run(accuracy_op,{input_x:test_x,output_y:test_y})
        print("Step=%d, Train loss=%.4f,[Test accuracy=%.2f]"%(i,train_loss,test_accuracy))

#測試: 列印20個預測值和真實值 對
test_output=sess.run(logits,{input_x:test_x[:20]})
inferenced_y=np.argmax(test_output,1)
print(inferenced_y,'Inferenced numbers')#推測的數字
print(np.argmax(test_y[:20],1),'Real numbers')
sess.close()