前邊的章節介紹了什么是Tensorflow,本節將帶大家真正走進Tensorflow的世界,學習Tensorflow一些基本的操作及使用方法。同時也歡迎大家關注我們的網站和系列教程:http://www.tensorflownews.com/,學習更多的機器學習、深度學習的知識!
Tensorflow是一種計算圖模型,即用圖的形式來表示運算過程的一種模型。Tensorflow程序一般分為圖的構建和圖的執行兩個階段。圖的構建階段也稱為圖的定義階段,該過程會在圖模型中定義所需的運算,每次運算的的結果以及原始的輸入數據都可稱為一個節點(operation ,縮寫為op)。我們通過以下程序來說明圖的構建過程:程序2-1:

程序2-1定義了圖的構建過程,“import tensorflow as tf”,是在python中導入tensorflow模塊,並另起名為“tf”;接着定義了兩個常量op,m1和m2,均為1*2的矩陣;最后將m1和m2的值作為輸入創建一個矩陣加法op,並輸出最后的結果result。
我們分析最終的輸出結果可知,其並沒有輸出矩陣相加的結果,而是輸出了一個包含三個屬性的Tensor(Tensor的概念我們會在下一節中詳細講解,這里就不再贅述)。
以上過程便是圖模型的構建階段:只在圖中定義所需要的運算,而沒有去執行運算。我們可以用圖2-1來表示:

圖2-1 圖的構建階段
第二個階段為圖的執行階段,也就是在會話(session)中執行圖模型中定義好的運算。我們通過程序2-2來解釋圖的執行階段:
程序2-2:

程序2-2描述了圖的執行過程,首先通過“tf.session()”啟動默認圖模型,再調用run()方法啟動、運行圖模型,傳入上述參數result,執行矩陣的加法,並打印出相加的結果,最后在任務完成時,要記得調用close()方法,關閉會話。
除了上述的session寫法外,我們更建議大家,把session寫成如程序2-4所示“with”代碼塊的形式,這樣就無需顯示的調用close釋放資源,而是自動地關閉會話。
程序2-3:

此外,我們還可以利用CPU或GPU等計算資源分布式執行圖的運算過程。一般我們無需顯示的指定計算資源,Tensorflow可以自動地進行識別,如果檢測到我們的GPU環境,會優先的利用GPU環境執行我們的程序。但如果我們的計算機中有多於一個可用的GPU,這就需要我們手動的指派GPU去執行特定的op。如下程序2-4所示,Tensorflow中使用with...device語句來指定GPU或CPU資源執行操作。
程序2-4:

上述程序中的“tf.device(“/gpu:2”)”是指定了第二個GPU資源來運行下面的op。依次類推,我們還可以通過“/gpu:3”、“/gpu:4”、“/gpu:5”...來指定第N個GPU執行操作。
關於GPU的具體使用方法,我們會在下面的章節結合案例的形式具體描述。
Tensorflow中還提供了默認會話的機制,如程序2-5所示,我們通過調用函數as_default()生成默認會話。
程序2-5:

我們可以看到程序2-5和程序2-2有相同的輸出結果。我們在啟動默認會話后,可以通過調用eval()函數,直接輸出變量的內容。
有時,我們需要在Jupyter或IPython等python交互式環境開發。Tensorflow為了滿足用戶的這一需求,提供了一種專門針對交互式環境開發的方法InteractiveSession(),具體用法如程序2-6所示:
程序2-6:

程序2-6就是交互式環境中經常會使用的InteractiveSession()方法,其創建sess對象后,可以直接輸出運算結果。
綜上所述,我們介紹了Tensorflow的核心概念——計算圖模型,以及定義圖模型和運行圖模型的幾種方式。接下來,我們思考一個問題,為什么Tensorflow要使用圖模型?圖模型有什么優勢呢?
首先,圖模型的最大好處是節約系統開銷,提高資源的利用率,可以更加高效的進行運算。因為我們在圖的執行階段,只需要運行我們需要的op,這樣就大大的提高了資源的利用率;其次,這種結構有利於我們提取中間某些節點的結果,方便以后利用中間的節點去進行其它運算;還有就是這種結構對分布式運算更加友好,運算的過程可以分配給多個CPU或是GPU同時進行,提高運算效率;最后,因為圖模型把運算分解成了很多個子環節,所以這種結構也讓我們的求導變得更加方便。
2.3.2 Tensor介紹
Tensor(張量)是Tensorflow中最重要的數據結構,用來表示Tensorflow程序中的所有數據。Tensor本是廣泛應用在物理、數學領域中的一個物理量。那么在Tensorflow中該如何理解Tensor的概念呢?
實際上,我們可以把Tensor理解成N維矩陣(N維數組)。其中零維張量表示的是一個標量,也就是一個數;一維張量表示的是一個向量,也可以看作是一個一維數組;二維張量表示的是一個矩陣;同理,N維張量也就是N維矩陣。
在計算圖模型中,操作間所傳遞的數據都可以看做是Tensor。那Tensor的結構到底是怎樣的呢?我們可以通過程序2-7更深入的了解一下Tensor。
程序2-7:

程序2-7的輸出結果表明:構建圖的運算過程輸出的結果是一個Tensor,且其主要由三個屬性構成:Name、Shape和Type。Name代表的是張量的名字,也是張量的唯一標識符,我們可以在每個op上添加name屬性來對節點進行命名,Name的值表示的是該張量來自於第幾個輸出結果(編號從0開始),上例中的“mul_3:0”說明是第一個結果的輸出。Shape代表的是張量的維度,上例中shape的輸出結果(1,1)說明該張量result是一個二維數組,且每個維度數組的長度是1。最后一個屬性表示的是張量的類型,每個張量都會有唯一的類型,常見的張量類型如圖2-2所示。

圖2-2 常用的張量類型
我們需要注意的是要保證參與運算的張量類型相一致,否則會出現類型不匹配的錯誤。如程序2-8所示,當參與運算的張量類型不同時,Tensorflow會報類型不匹配的錯誤:程序2-8:

正如程序的報錯所示:m1是int32的數據類型,而m2是float32的數據類型,兩者的數據類型不匹配,所以發生了錯誤。所以我們在實際編程時,一定注意參與運算的張量數據類型要相同。
2.3.3 常量、變量及占位符
Tensorflow中對常量的初始化,不管是對數值、向量還是對矩陣的初始化,都是通過調用constant()函數實現的。因為constant()函數在Tensorflow中的使用非常頻繁,經常被用於構建圖模型中常量的定義,所以接下來,我們通過程序2-9了解一下constant()的相關屬性:
程序2-9:

如程序2-9所示,函數constant有五個參數,分別為value,name,dtype,shape和verify_shape。其中value為必選參數,其它均為可選參數。Value為常量的具體值,可以是一個數字,一維向量或是多維矩陣。Name是常量的名字,用於區別其它常量。Dtype是常量的類型,具體類型可參見圖2-2。Shape是指常量的維度,我們可以自行定義常量的維度。
verify_shape是驗證shape是否正確,默認值為關閉狀態(False)。也就是說當該參數true狀態時,就會檢測我們所寫的參數shape是否與value的真實shape一致,若不一致就會報TypeError錯誤。如:上例中的實際shape為(2,0),若我們將參數中的shape屬性改為(2,1),程序就會報如下錯誤:
TypeError: Expected Tensor's shape: (2, 1), got (2,).
Tensorflow還提供了一些常見常量的初始化,如:tf.zeros、tf.ones、tf.fill、tf.linspace、tf.range等,均可以快速初始化一些常量。例如:我們想要快速初始化N維全0的矩陣,我們可以利用tf.zeros進行初始化,如程序2-10所示:
程序2-10:

程序2-10向我們展示了tf.zeros和tf.zeros_like的用法。其它常見常量的具體初始化用法可以參考Tensorflow官方手冊:https://www.tensorflow.org/api_guides/python/constant_op。
此外,Tensorflow還可以生成一些隨機的張量,方便快速初始化一些隨機值。如:tf.random_normal()、tf.truncated_normal()、tf.random_uniform()、tf.random_shuffle()等。如程序2-11所示,我們以tf.random_normal()為例,來看一下隨機張量的具體用法:
程序2-11:

隨機張量random_normal()有shape、mean、stddev、dtype、seed、name六個屬性。 shape是指張量的形狀,如上述程序是生成一個2行3列的tensor;mean是指正態分布的均值;stddev是指正太分布的標准差;dtype是指生成tensor的數據類型;seed是分發創建的一個隨機種子;而name是給生成的隨機張量命名。
Tensorflow中的其它隨機張量的具體使用方法和屬性介紹,可以參見Tensorflow官方手冊:https://www.tensorflow.org/api_guides/python/constant_op。這里將不在一一贅述。
除了常量constant(),變量variable()也是在Tensorflow中經常會被用到的函數。變量的作用是保存和更新參數。執行圖模型時,一定要對變量進行初始化,經過初始化后的變量才能拿來使用。變量的使用包括創建、初始化、保存、加載等操作。首先,我們通過程序2-12了解一下變量是如何被創建的:
程序2-12:

程序2-12展示了創建變量的多種方式。我們可以把函數variable()理解為構造函數,構造函數的使用需要初始值,而這個初始值是一個任何形狀、類型的Tensor。也就是說,我們
既可以通過創建數字變量、一維向量、二維矩陣初始化Tensor,也可以使用常量或是隨機常量初始化Tensor,來完成變量的創建。
當我們完成了變量的創建,接下來,我們要對變量進行初始化。變量在使用前一定要進行初始化,且變量的初始化必須在模型的其它操作運行之前完成。通常,變量的初始化有三種方式,如程序2-13所示:
程序2-13:

程序2-13說明了初始化變量的三種方式:初始化全部變量、初始化變量的子集以及初始化單個變量。首先,global_variables_initializer()方法是不管全局有多少個變量,全部進行初始化,是最簡單也是最常用的一種方式;variables_initializer()是初始化變量的子集,相比於全部初始化化的方式更加節約內存;Variable()是初始化單個變量,函數的參數便是要初始化的變量內容。通過上述的三種方式,我們便可以實現變量的初始化,放心的使用變量了。
我們經常在訓練模型后,希望保存訓練的結果,以便下次再使用或是方便日后查看,這時就用到了Tensorflow變量的保存。變量的保存是通過tf.train.Saver()方法創建一個Saver管理器,來保存計算圖模型中的所有變量。具體代碼如程序2-14所示:
程序2-14:

我們要注意,我們的存儲文件save.ckpt是一個二進制文件,Saver存儲器提供了向該二進制文件保存變量和恢復變量的方法。保存變量的方法就是程序中的save()方法,保存的內容是從變量名到tensor值的映射關系。完成該存儲操作后,會在對應目錄下生成如圖2-3所示的文件:

圖2-3 保存變量生成的相應文件
Saver提供了一個內置的計數器自動為checkpoint文件編號。這就支持訓練模型在任意步驟多次保存。此外,還可以通過global_step參數自行對保存文件進行編號,例如:global_step=2,則保存變量的文件夾為model.ckpt-2。那如何才能恢復變量呢?首先,我們要知道一定要用和保存變量相同的Saver對象來恢復變量。其次,不需要事先對變量進行初始化。具體代碼如程序2-15所示:
程序2-15:

本程序示例中,我們要注意:變量的獲取是通過restore()方法,該方法有兩個參數,分別是session和獲取變量文件的位置。我們還可以通過latest_checkpoint()方法,獲取到該目錄下最近一次保存的模型。
以上就是對變量創建、初始化、保存、加載等操作的介紹。此外,還有一些與變量相關的重要函數,如:eval()等。
認識了常量和變量,Tensorflow中還有一個非常重要的常用函數——placeholder。placeholder是一個數據初始化的容器,它與變量最大的不同在於placeholder定義的是一個模板,這樣我們就可以session運行階段,利用feed_dict的字典結構給placeholder填充具體的內容,而無需每次都提前定義好變量的值,大大提高了代碼的利用率。Placeholder的具體用法如程序2-16所示:
程序序2-16:

程序2-16演示了placeholder占位符的使用過程。Placeholder()方法有dtype,shape和name三個參數構成。dtype是必填參數,代表傳入value的數據類型;shape是選填參數,代表傳入value的維度;name也是選填參數,代表傳入value的名字。我們可以把這三個參數看作為形參,在使用時傳入具體的常量值。這也是placeholder不同於常量的地方,它不可以直接拿來使用,而是需要用戶傳遞常數值。
最后,Tensorflow中還有一個重要的概念——fetch。Fetch的含義是指可以在一個會話中同時運行多個op。這就方便我們在實際的建模過程中,輸出一些中間的op,取回多個tensor。Fetch的具體用法如程序2-17所示:
程序2-17:

程序2-17展示了fetch的用法,即我們利用session的run()方法同時取回多個tensor值,方便我們查看運行過程中每一步op的輸出結果。
程序2-18:

小結:本節旨在讓大家學會Tensorflow的基礎知識,為后邊實戰的章節打下基礎。主要講了Tensorflow的核心——計算圖模型,如何定義圖模型和計算圖模型;還介紹了Tensor的概念,以及Tensorflow中的常量、變量、占位符、feed等知識點。大家都掌握了嗎?