tensorflow程式設計入門筆記之一
基本上每一個語言或者工具都有自己的“hello world” demo,那麼學習它們一般都會從這個“hello world”開始。今天我們就來看看tensorflow的“hello world”(非官網)。
在開始編寫“hello world”之前我們先看看tensorflow的程式設計模型。
一. tensorflow程式設計模型簡介
這部分的一個很好的教程是官網上的Basic Usage,講解的還是很清晰的。
Tensorflow中的計算可以表示為一個有向圖(directed graph),或稱計算圖(computation graph),其中每一個運算操作將作為一個節點(node),節點與節點之間的連線成為邊(edge),而在計算圖的邊中流動(flow)的資料被稱為張量(tensor),所以形象的看整個操作就好像資料(tensor)在計算圖(computation graphy)中沿著邊(edge)流過(flow)一個個節點(node),這就是tensorflow名字的由來的。
計算圖中的每個節點可以有任意多個輸入和任意多個輸出,每個節點描述了一種運算操作(operation, op),節點可以算作運算操作的例項化(instance)。計算圖描述了資料的計算流程,它也負責維護和更新狀態,使用者可以對計算圖的分支進行條件控制或迴圈操作。使用者可以使用pyton、C++、Go、Java等語言設計計算圖。tensorflow通過計算圖將所有的運算操作全部執行在python外面,比如通過c++執行在cpu或通過cuda執行在gpu 上,所以實際上python只是一種介面,真正的核心計算過程還是在底層採用c++或cuda在cpu或gpu上執行。
一個 TensorFlow圖描述了計算的過程. 為了進行計算, 圖必須在會話(session)裡被啟動. 會話將圖的op分發到諸如CPU或GPU之的備上, 同時提供執行op的方法. 這些方法執行後, 將產生的tensor返回. 在Python語言中, 返回的tensor是numpy ndarray物件; 在C和C++語言中, 返回的tensor是tensorflow::Tensor例項。
從上面的描述中我們可以看到,tensorflow的幾個比較重要的概念:tensor, computation graphy, node, session。正如前面所說,整個操作就好像資料(tensor)在計算圖(computation graphy)中沿著邊(edge)流過(flow)一個個節點(node),然後通過會話(session)啟動計算。所以簡單來說,要完成這整個過程,我們需要的東西是要定義資料、計算圖和計算圖上的節點,以及啟動計算的會話。所以在實際使用中我們要做的大部分工作應該就是定義這些內容了。
二. tensorflow基本使用
正如官方教程裡所說:
To use TensorFlow you need to understand how TensorFlow:
- Represents computations as graphs.
- Executes graphs in the context of Sessions.
- Represents data as tensors.
- Maintains state with Variables.
- Uses feeds and fetches to get data into and out of arbitrary operations.
我們只有理解了這些概念,明白它們分別是做什麼的,才能掌握tensorflow的使用方法。下面簡單介紹下這些概念及使用。
- 計算圖(computation graphy)
計算圖是由一個個節點和連線各個節點的邊組成,因此要定義一個計算圖,只需要定義好各個節點以及節點的輸入輸出(對應計算圖的邊)。節點代表各種操作,如加法、乘法、卷積運算等等,輸入輸出主要是各種資料(tensor)。下面是一個簡單的計算圖定義方法示例(來自官網):
import tensorflow as tf
# Create a Constant op that produces a 1x2 matrix. The op is
# added as a node to the default graph.
#
# The value returned by the constructor represents the output
# of the Constant op.
matrix1 = tf.constant([[3., 3.]])
# Create another Constant that produces a 2x1 matrix.
matrix2 = tf.constant([[2.],[2.]])
# Create a Matmul op that takes 'matrix1' and 'matrix2' as inputs.
# The returned value, 'product', represents the result of the matrix
# multiplication.
product = tf.matmul(matrix1, matrix2)
當然,我們也可以新增更多更復雜的操作(operation)的節點(node)到計算圖(computation graphy)中,如果增加一些卷積網路節點、全連線網路節點等等就可以組建一個神經網路計算圖了。
- 節點(node)
計算圖中的每個節點可以有任意多個輸入和任意多個輸出,每個節點描述了一種運算操作(operation, op),節點可以算作運算操作的例項化(instance)。一種運算操作代表了一種型別的抽象運算,比如矩陣乘法貨響亮加法。tensorflow內建了很多種運算操作,如下表所示:
型別 | 示例 |
---|---|
標量運算 | Add、Sub、Mul、Div、Exp、Log、Greater、Less、Equal |
向量運算 | Concat、Slice、Splot、Constant、Rank、Shape、Shuffle |
矩陣運算 | Matmul、MatrixInverse、MatrixDeterminant |
帶狀態的運算 | Variable、Assign、AssignAdd |
神經網路元件 | SoftMax、Sigmoid、ReLU、Convolution2D、MaxPooling |
儲存、恢復 | Save、Restore |
佇列及同步運算 | Enqueue、Dequeue、MutexAcquire、MutexRelease |
控制流 | Merge、Switch、Enter、Leave、NextIteration |
在tensorflow中,也可以通過註冊機制加入新的運算操作或者運算核,這和torch上的註冊機制類似。
- 會話(session)
正如我們前面所說,計算圖裡描述的計算並沒有真正執行,只是進行了定義和描述,要實際執行我們就需要在會話(session)裡被啟動. 這時session才會將計算圖上的節點操作op分發到諸如CPU或GPU之類的裝置上, 同時提供執行op的方法. 這些方法執行後,將產生的tensor返回.
要啟動計算圖,我們收下需要定義一個session物件:
sess = tf.Session()
啟動操作,最簡單的就是呼叫函式run:
result = sess.run(product)
tensorflow還支援分散式session,將計算圖佈置到多個機器上進行計算。由於我這邊不具備該環境,就不介紹這部分內容了。
另外tensorflow還支援互動環境下采用InteractiveSession
定義一個互動session,然後所有的操作都預設在該session上執行,可以直接呼叫Tensor.eval()
和Operation.run()
兩個方法,如:
# Enter an interactive TensorFlow Session.
import tensorflow as tf
sess = tf.InteractiveSession()
x = tf.Variable([1.0, 2.0])
a = tf.constant([3.0, 3.0])
# Initialize 'x' using the run() method of its initializer op.
x.initializer.run()
# Add an op to subtract 'a' from 'x'. Run it and print the result
sub = tf.sub(x, a)
print(sub.eval())
# ==> [-2. -1.]
# Close the Session when we're done.
sess.close()
- 資料(tensor)
TensorFlow程式使用tensor資料結構來代表所有的資料, 計算圖中的節點間傳遞的資料都是tensor. 你可以把TensorFlow tensor看作是一個n維的陣列或列表. 一個 tensor包含一個靜態型別rank, 和一個shape。 - 變數(Variable)
在tensorflow裡有一類資料比較特殊,那就是我們需要在整個計算圖執行過程中需要儲存的狀態。比如我們在進行神經網路訓練時要時刻儲存並更新的網路引數,這時我們就需要用到Varibale來儲存這些引數。其實,我們在前面的示例中已經用到了變數的定義了,它的定義關鍵字為Variable
,如上面的x = tf.Variable([1.0, 2.0])
。 - feed & fetch
我們都知道,進行機器學習或者神經網路訓練時,都需要大量的訓練資料。細心的朋友可能注意到,我們前面一直沒講到訓練資料怎麼定義,怎麼輸入到網路裡。實際上,tensorflow提供了一個feed機制來將tensor直接放置到計算圖的任意節點操作上去。“feed”這個詞用的很形象啊,就像我們在上課學習時,老師拿課本里的各種例子、習題往我們腦子裡喂。那麼,這個利用這個feed機制我們就可以把訓練資料“喂”到計算圖的輸入中去。一般我們採用placeholder來指定一個feed操作,這個placeholder就像是一個容器一樣來接收訓練資料,然後在最終進行計算時只需要用placehoder裡的資料替換計算圖的輸入量就可以了。一個簡單的例子:
input1 = tf.placeholder(tf.float32)
input2 = tf.placeholder(tf.float32)
output = tf.mul(input1, input2)
with tf.Session() as sess:
print(sess.run([output], feed_dict={input1:[7.], input2:[2.]}))
採用兩個placeholder操作來定義兩個輸入,在後面的see.run()裡採用feed_dict替換成真正的訓練資料,feed_dict裡的才是真正的資料。一般情況,placeholder和feed_dict是搭配使用的。
fetch,正如其字面意思,就是取回資料的意思。我們將計算圖部署到session上進行計算後,需要將計算結果取回,這就是一個fetch。下面是取回多個tensor的例子:
input1 = tf.constant(3.0)
input2 = tf.constant(2.0)
input3 = tf.constant(5.0)
intermed = tf.add(input2, input3)
mul = tf.mul(input1, intermed)
with tf.Session() as sess:
result = sess.run([mul, intermed])
print result
上面就是tensorflow程式設計模型的一些基本概念和內容。通過上面的介紹,我們可以用一句話來總結tensorflow的一個工作流程:
那麼我們也可以簡單總結出tensorflow程式設計的一個基本步驟:
- 定義資料
- 定義計算圖與變數
- 定義會話
- 進行計算
三. 用tensorflow搭建神經網路“hello world”
按照我們上一節介紹的tensorflow程式設計的基本步驟,我們來搭建我們的第一個神經網路——基於mnist資料集的手寫數字識別,即基於圖片的10分類問題。
此部分可以參考官網教程MNIST For ML Beginners。
MNIST是一個簡單的機器視覺資料集,如下圖所示,它有幾萬張28×28畫素的手寫數字組成,這些圖片只包含灰度資訊,我們的任務就是對這些手寫數字進行分類,轉成0~9一共10類。
1.定義資料
在神經網路裡我們需要定義的資料就是輸入訓練/測試資料,而變數用來儲存網路模型裡的各種引數。如:
# 輸入資料(包括訓練資料和測試資料)
x = tf.placeholder( tf.float32, [None, 784] )
y_ = tf.placeholder( tf.float32, [None, 10] )
這裡我們把圖片的2828個畫素展開成一維列向量(2828-784)
2.定義計算圖與變數
對於神經網路來說,涉及到的操作主要有三部分:網路模型定義,損失函式定義、訓練/優化方法定義。那麼我們的計算圖基本也由這三部分的定義組成。(當然還可能包括其它部分,如輸入資料初始化操作,網路引數初始化等等,這裡我們不討論)
- 網路模型定義
這裡我們定義一個最簡單的單層全連線網路,計算公式為:y=Wx+b
,然後利用softmax來計算預測概率,預測概率最大的對應預測的分類。我需要定義兩個變數來儲存網路引數W
和b
的狀態。
W = tf.Variable( tf.zeros([784,10]) )
b = tf.Variable( tf.zeros([10]) )
y = tf.nn.softmax( tf.matmul(x,W) + b )
- 損失函式定義
採用cross-entropy作為損失函式,它的公式為:$H_{y'}\left(y\right)=-\underset{i}{{\textstyle \sum}}y'{i}\log\left(y{i}\right)$。(才發現簡書竟然不支援Latex,尷尬。。。)
cross_entropy = tf.reduce_mean( -tf.reduce_sum( y_*tf.log(y), reduction_indices=[1] ) )
- 訓練/優化方法定義
神經網路常採用SGD(Stochastic Gradient Descent)進行網路的優化訓練。tensorflow會自動根據前面定義的計算圖進行forward和backward計算並更新引數。
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy)
3.定義會話
按照前面的方法,定義一個session即可。但是還要記住對所有的變數進行全域性初始化。
sess = tf.InteractiveSession()
tf.global_variables_initializer().run() #由於是InteractiveSession可以直接run
或者
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run()
4.進行計算
對於神經網路來說,就是要開始迭代進行訓練和評估,降低損失函式。
# training
for i in range(10000):
batch_xs, batch_ys = mnist.train.next_batch(100)
train_step.run( {x:batch_xs, y_:batch_ys} ) #InteractiveSession
# sess.run(train_step, feed_dict={x:batch_xs, y_:batch_ys}) #非InteractiveSession
# eval
correct_prediction = tf.equal( tf.argmax(y,1), tf.argmax(y_,1) )
accuracy = tf.reduce_mean( tf.cast(correct_prediction, tf.float32) )
print(accuracy.eval( {x:mnist.test.images, y_:mnist.test.labels} )) #InteractiveSession
print(sess.run(accuracy, feed_dict={x:mnist.test.images, y_:mnist.test.labels})#非InteractiveSession
以上就是整個神經網路的搭建過程。這裡只採用的單層全連線網路,但是準確率依然達到了92%左右,如果我們採用卷積神經網路等更復雜的網路,可以將準確率提高到99%。
以上只是搭建一個神經網路的基本框架,當然實際中還是資料預處理、引數初始化、超引數設定等問題,這些就需要在實際使用過程中慢慢學習了。
以下是該網路的全部程式碼:
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] )
y_ = tf.placeholder( tf.float32, [None, 10] )
W = tf.Variable( tf.zeros([784,10]) )
b = tf.Variable( tf.zeros([10]) )
y = tf.nn.softmax( tf.matmul(x,W) + b )
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)
session = tf.InteractiveSession()
tf.global_variables_initializer().run()
for i in range(1000):
batch_xs, batch_ys = mnist.train.next_batch(100)
train_step.run( {x:batch_xs, y_:batch_ys} )
# print(i)
correct_prediction = tf.equal( tf.argmax(y,1), tf.argmax(y_,1) )
accuracy = tf.reduce_mean( tf.cast(correct_prediction, tf.float32) )
print(accuracy.eval( {x:mnist.test.images, y_:mnist.test.labels} ))