使用TensorFlow實現手寫識別(Softmax)

weixin_33968104發表於2017-03-13

準備工作

由於將TensorFlow安裝到了Conda的tensorflow環境,雖然可以用Jupyter notebook開啟,但是沒有提示,寫程式碼不方便,所以使用PyCharm進行編寫。設定如下:

  • 如果是新建專案,在選擇使用python的地址的地方,找到anaconda目錄,點選envs ----> tensorflow -----> bin -----> python2.7(我的是2.7)
  • 如果已經建立了專案,但是沒有用該環境下的python,就進入專案的設定裡,找到project interpreter這一項,做同上面相同的事。

MNIST資料集簡介

該資料集是機器學習入門級別的資料集,也是tensorflow在教程中使用的資料集。包含手寫數字圖片以及圖片的標籤(標籤告訴我們圖片中是數字幾)。資料集分為三部分:mnist.train(訓練資料),包含55000個資料點;mnist.test(測試資料),包含10000個資料點;mnist.validation(驗證資料),包含5000個資料點。

開始之前

先將下面程式碼拷貝到pycharm中,試試能否執行(不必懂,主要先試執行,測試環境是否有問題)。

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

x = tf.placeholder(tf.float32, [None, 784])

W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

y = tf.nn.softmax(tf.matmul(x, W) + b)

# Training
y_ = tf.placeholder(tf.float32, [None, 10])

cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))

train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)

# run
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

# evaluate
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

分析模型

每個圖片的大小都由28*28=784個畫素點組成,可以抽象成一個784維的向量,每一位的值代表畫素強度。下圖橫座標代表圖片索引,縱座標代表每個畫素值。

4964755-3327319917c8e022.png
索引-畫素圖

每個圖片有一個標籤,代表它是數字幾,那麼下圖的橫座標代表圖片索引,縱座標代表數字幾。

4964755-43d6ad719072e5f0.png
索引-標籤圖

我們給每個數字的每個畫素都標一個權重,如果是這個數字該有的部分,則為正圈中,如果是不該有的部分,則標為負權重,如下圖,藍色代表正權重,紅色代表負權重。

4964755-f54e4a26fd026033.png
數字權重圖

我們再給每個畫素新增一些偏差b,則可以得到下面公式

4964755-81b5f20b4c445c38.png
線性函式公式

再用softmax函式將evidence轉化成我們想要的一系列可能性y

4964755-271f014317bcb864.png
softmax函式

為啥這麼做呢,假設輸入一個手寫圖片,那麼畫素點值大的地方肯定是那個數字,把我們剛才設定的所有權重都和這個圖的畫素點做一下上面公式的運算,就能得出10個圖,最後再看這些圖,假設這個圖是1,那麼它和1的權重圖運算後是1的部分就會變更大更黑,是1的可能性也就越大,我們用softmax函式的目的就是給出沒種運算後的可能性,根據one-hot編碼讓我們知道它是數字幾。

用圖表示會更形象:

4964755-26a954e8187e8c77.png
softmax regression
4964755-e572cde3f076cd6b.png
softmax寫成公式圖
4964755-2f3a55ee096e4493.png
向量化

開始

  • 新建python檔案,匯入tensorflow,輸入下面兩行,可以自動下載並讀取mnist資料集:
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data", one_hot=True)

寫完這裡有個疑問,如果我想讀取別的資料集怎麼辦?追蹤input方法,發現一大堆讀取的程式碼,為了不偏離主線,先放過,過後查詢。

還有一個發現,括號裡的one_hot=True沒有空格,加了空格反而會顯示:unexpected spaces around keyword/parameter equals,所以看來和java不同,鍵值之間的等號不能加空格。

還有,import語句一定寫在最上面,雖然這是常識,不過由於教程裡面是先講的自動下載資料集,然後講的import tensorflow,所以我還是試了試如果把import放下面咋辦,果然報錯了。

  • 設定佔位符x
x = tf.placeholder(tf.float32, [None, 784])

tensorflow是需要我們先繪製一個dataflow graph,這個graph我理解其實就是設定變數、引數、用的啟用函式、損失函式等等,把公式先寫好,未知數用一個佔位符先佔著,這裡的x就是上圖中的[x1,x2,x3,x4],None代表該維度可以任意長。

  • 設定變數
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))

W的第一個維度代表784個畫素,第二個維度代表10個類,初始化成0。
b的維度代表10個類,初始化成0。

  • 設定softmax求出結果
y = tf.nn.softmax(tf.matmul(x, W) + b)
  • 訓練
    在tensorflow裡,我們也先用佔位符來表示預期結果
y_ = = tf.placeholder(tf.float32, [None, 10])

為了訓練我們的模型,通常會定義它怎麼樣才算一個好模型,在機器學習裡,我們通過比對模型輸出和預期值的差異,成為損失函式或者代價函式,差異越小越好,“交叉熵”是經常使用的損失函式,公式如下:


4964755-adc9235b19ac4ddf.png
交叉熵公式

y是預期分佈,y'是真實分佈。
用tensorflow實現交叉熵函式:

cross_entropy = tf.reduce_mean(-tf.reduce_sum(y_ * tf.log(y), reduction_indices=[1]))

因為這種寫法得到的分佈值不平穩,所以常用 tf.nn.softmax_cross_entropy_with_logits函式來得到平穩的結果
到這裡,tensorflow知道了整個graph,它會自動通過反向傳播演算法找到讓損失函式最小的變數值,它還提供了一些優化演算法來幫助讓損失函式最小。如梯度下降法:

train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
  • run tensorflow
    • 初始化變數
init = tf.global_variables_initializer()

這裡只是定義了,還沒有執行初始化。

  • 建立session,執行初始化
sess = tf.Session()
sess.run(init)
  • 訓練
for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})

執行1000次,每次取樣100個x和y,放在batch裡,替換佔位符(相當於賦值給佔位符)。

  • 衡量模型
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(y_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

tf.argmax(y, 1)第二個引數代表維度,correct_prediction給了我們一個布林列表;
accuracy一句將布林集合轉換為數字集合,比如[True, True, False, False, False],轉換為[1, 1, 0, 0, 0],正確率為0.4,對所有正確率求平均數;
最後一句,執行精度計算,得到精度。

遇到的問題

  • 對他的dimension表示疑惑,可能受其他語言影響,認為[1,2,3,...,784]是一個一維陣列,[1][2]才是2維陣列,但是它說了個784-dimensional vector,一下子把我搞蒙了,不過後來似乎轉過彎了,[1,2,3,...,784]就是一個784維的向量,不是要在空間畫出來,只是這樣表示而已,[784,10]代表784列10行的一個二維矩陣。

  • 在執行後,發出警告,說The TensorFlow library wasn't compiled to use SSE4.1/SSE4.2 /AVX/AVX2/FMA instructions, but these are available on your machine and could speed up CPU computations.
    那麼怎麼使用這些提高CPU計算速度呢,到StackOverFlow上查了一下,說是最好從sources編譯它,應該就是說安裝的時候從sources安裝,但是我是通過anaconda安裝的,等實在忍不下去這個速度了再從sources編譯吧,就先不在這裡耗時了,畢竟我花了兩天才安裝好它……

後續將補充的知識

今天的主要目的是跑通並理解整個程式,所以沒有深究所有演算法的原理,接下來的幾天將進行下面知識補充:

  • softmax regression
  • Python陣列問題
  • 交叉熵
  • 啟用函式
  • 反向傳播演算法

相關文章