前言
在上一篇中,我簡單介紹了一下Tensorflow
以及在本機及阿里雲的PAI
平台上跑通第一個示例的步驟。在本篇中我將稍微講解一下幾個基本概念以及Tensorflow
的基礎語法。
本文代碼都是基於API版本r1.4
。本文中本地開發環境為Pycharm
,在文中不再贅述。
名詞解釋
核心概念
和很多開發語言設計一樣,Tensorflow
提供了多個級別的客戶端API,其中最底層叫Tensorflow Core
,使用這一層API可以完全控制Tensorflow
,但是使用難度上也相對較大。在Tensorflow Core
之上創建的更高級別的API,對開發者更友好,更易於使用、學習起來也更簡單。
Tensorflow
中該數據的核心單位是張量(Tensor)
,張量
就是將一組基礎數值,組織成形態(Shape)
為一個任意維度的數組,張量
的階(Rank)
就是維度的數量。概念還是挺拗口的,舉個例子就非常明了了:
[1., 2., 3.] # Rank=1, Shape=[3]
[[1., 2., 3.], [4., 5., 6.]] # Rank=2; Shape=[2, 3]: 代表第一層數組里包含2個子數組,每個子數組里包含3個值
[[[1., 2., 3.]], [[7., 8., 9.]]] # Rank=3; Shape=[2, 1, 3] : 代表第一層數組里包含2個數組,每個子數組里又包含1個子數組,子數組里包含3個元素
Tensorflow
其實就是針對張量
的計算圖,計算圖中的每個節點(Node)
之間是有向連接的,看起來像張量
的流動圖(即從輸入開始,流過一系列的節點,最終輸出結果),Tensorflow
也由此得名。官方原話是:
What is a Data Flow Graph?
Data flow graphs describe mathematical computation with a directed graph of nodes & edges. Nodes typically implement mathematical operations, but can also represent endpoints to feed in data, push out results, or read/write persistent variables. Edges describe the input/output relationships between nodes. These data edges carry dynamically-sized multidimensional data arrays, or tensors. The flow of tensors through the graph is where TensorFlow gets its name. Nodes are assigned to computational devices and execute asynchronously and in parallel once all the tensors on their incoming edges becomes available.
基礎語法
Tensorflow Core
編程,有點像畫設計稿(構建流圖)
->按圖施工(執行流圖)
這樣的過程,執行流圖必須使用tf.run()
方法。計算圖中的節點
將接受0-N個張量
作為輸入值並產生一個輸出值。在我的理解中,節點
可以分為數值型
和運算型
兩種。
數值型
Constant
常量是一種沒有輸入,只有一個輸出值的節點,常量在定義的時候就將其值存儲在Tensorflow
內部了,一旦定義則無法修改其值。
示例代碼:
# 定義常量c1,並將其數值類型定義為tf.float32,默認值為1.0
c1 = tf.constant(1., dtype=tf.float32)
# 定義常量c2,並將其數值類型定義為tf.float32,默認值為2.0
c2 = tf.constant(2., dtype=tf.float32)
# 執行流圖: c1 + c2
with tf.Session() as sess:
print(sess.run(tf.add(c1, c2)))
Placeholder
占位符也是數值型節點的一種定義方式,占位符是一種Promise
,就是承諾在執行tf.run()
的時候一定會在參數feed_dict
中提供其值。相比常量,占位符更像是一種參數,使用起來更靈活。
用占位符改寫上面的示例代碼如下:
# 定義占位值p1,並將其數值類型定義為tf.float32
p1 = tf.placeholder(tf.float32)
# 定義占位值p2,並將其數值類型定義為tf.float32
p2 = tf.placeholder(tf.float32)
# 執行流圖: p1 + p2
with tf.Session() as sess:
# 既然承諾過,因此在run的時候必須提供p1,p2的值,否則代碼將報錯
print(sess.run(tf.add(p1, p2), {p1: 1., p2: 2.}))
Variable
比起占位符,變量就更靈活了,可以隨時賦值,這樣就可以將某些節點的輸出值賦值到指定的變量中,以便后續節點使用。這種模式在機器學習中是非常必要的,因為機器學習就是一個調參的過程,在運行的時候就希望能隨時改變某些值以達到預期。
變量在使用的時候需要注意的是,在執行tf.run()
方法之前,必須將變量進行初始化,初始化語句是:
init = tf.global_variables_initializer()
sess.run(init)
依舊是上述代碼用變量改寫:
# 定義變量v1,並將其數值類型定義為tf.float32,默認值為1.0
v1 = tf.Variable(1., tf.float32)
# 定義變量v2,並將其數值類型定義為tf.float32,默認值為1.0
v2 = tf.Variable(2., tf.float32)
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
print(sess.run(tf.add(v1, v2)))
運算型
其實上面的代碼中已經用到了加法tf.add()
方法,減法是tf.subtract()
,乘法是tf.multiply()
,除法是tf.divide()
等等。所有的方法可以在官方API文檔中找到:https://www.tensorflow.org/api_docs/python/tf,這里就不贅述了。
TensorBoard
這里再簡單介紹下Tensorflow
自帶的非常強大的可視化工具TensorBoard
,TensorBoard
完全可以單獨寫一篇博文,本文先拋磚引玉,主要是為了直觀的展示上述代碼產生的圖。
最簡單的TensorBoard
的使用方法如下:
# 保存計算圖
with tf.summary.FileWriter(logdir='logs', graph=tf.get_default_graph()) as writer:
writer.flush()
執行上述代碼之后,Tensorflow
會將生成圖所需的數據序列化到本地文件中,我指定了生成到當前同級目錄logs
中,生成成功之后,可以在PyCharm
的控制台(使用快捷鍵ALT+F12可調出)中輸入:
tensorboard --logdir=logs
等待幾秒鍾之后,控制台輸出類似於如下內容則表示TensorBoard
已經啟動成功:
TensorBoard 0.4.0rc3 at http://localhost:6006 (Press CTRL+C to quit)
在本地瀏覽器(推薦使用Chrome)地址欄中,輸入http://localhost:6006
打開TensorBoard
,大致效果如下:
復雜點的示例——線性模型
真正的機器學習過程中,我們當然是不知道變量的,我們真正的目的就是去習得
這些變量,以達到模型能夠盡可能准確預測樣本的期望,也就是所謂的損失(loss)
最小化。Tensorflow
提供了優化器(optimizers)
來做這個工作。最簡單的優化器
算法叫梯度下降
,這是在線性模型中最常用的一種優化算法。優化器
底層會調用Tensorflow Core
中的tf.gradients
方法來實現梯度下降
。
如上圖所示,假設現在已知4個藍色的點(1,0),(2,-1),(3,-2),(4,-3),我們需要推導出代表紅色直線的系數W
和b
(公式為y = Wx + b
),當然這個例子很簡單,用肉眼看一下就知道W=-1
,b=1
,用Tensorflow
實現的完整代碼如下:
import tensorflow as tf
# y = Wx + b, 初始化的時候隨便定義一個初始值
W = tf.Variable([.3], dtype=tf.float32)
b = tf.Variable([-.3], dtype=tf.float32)
# 輸入值 x, 定義為占位符, 便於在學習過程中換成不同的值
x = tf.placeholder(tf.float32)
# 定義線性模型
linear_model = W*x + b
# 輸出值 y, 定義為占位符, 便於在學習過程中換成不同的值
y = tf.placeholder(tf.float32)
# 損失loss,線性模型中以歐式距離來衡量損失值
loss = tf.reduce_sum(tf.square(linear_model - y))
# 定義優化器optimizer
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)
# 4個藍色點的訓練數據,分解成x和y的數組為
x_train = [1, 2, 3, 4]
y_train = [0, -1, -2, -3]
# 初始化Session
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
# 循環1000次,訓練模型
for i in range(1000):
sess.run(train, {x: x_train, y: y_train})
# 評估准確率
curr_W, curr_b, curr_loss = sess.run([W, b, loss], {x: x_train, y: y_train})
print("W: %s b: %s loss: %s"%(curr_W, curr_b, curr_loss))
# 保存計算圖
with tf.summary.FileWriter(logdir='logs_linear_regression', graph=tf.get_default_graph()) as writer:
writer.flush()
我本機的輸出結果為:
W: [-0.9999969] b: [ 0.99999082] loss: 5.69997e-11
W
的值無限接近-1,b
的值無限接近1,而loss
無限接近0,這個就是我們設計的函數y=-x+1
。
在TensorBoard
中查看結果如圖所示:
這個圖就看起來就比較像這么回事了。
在阿里雲PAI上運行
本系列教程我盡量在阿里雲的PAI
平台上也運行一次,雖然目前公測階段還是有很多問題,但是也是讓很多人對機器學習變得觸手可及的一種非常好的方案。
上一篇中,我用web版的OSS管理工具上傳了源代碼文件,本用例將使用OSS Browser
客戶端上傳和管理文件,下載地址在阿里雲后台如下位置:
下載客戶端的同時,可以開通阿里雲的Access Key
(用來登錄OSS Browser
),開通位置如下:
開通之后,在管理界面看到如下內容:
打開並解壓縮剛才下載的OSS Browser
,雙擊打開oss-browser.exe
文件,使用剛才開通的Access Key
登錄:
我依舊在上一篇相同的目錄oss://danielfu-oss-tf-test/tensorflowtest/
下,創建了一個放summary
文件夾,並上傳了代碼文件tensorflow-demo2.py
:
在阿里雲上使用Tensorflow
需要將上述的demo示例代碼進行少量的改造,格式基本也都是固定的,改造完之后的完整代碼如下:
# 指定文件的編碼格式,這個不加在PAI里運行會報錯
#!/usr/bin/python
# -*-coding:utf-8 -*-
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import sys
import argparse
import tensorflow as tf
# 定義FLAGS用來傳遞全局參數
FLAGS = None
def main(_):
# y = Wx + b, 初始化的時候隨便定義一個初始值
W = tf.Variable([.3], dtype=tf.float32)
b = tf.Variable([-.3], dtype=tf.float32)
# 輸入值 x, 定義為占位符, 便於在學習過程中換成不同的值
x = tf.placeholder(tf.float32)
# 定義線性模型
linear_model = tf.multiply(W, x) + b
# 輸出值 y, 定義為占位符, 便於在學習過程中換成不同的值
y = tf.placeholder(tf.float32)
# 損失loss,線性模型中以歐式距離來衡量損失值
loss = tf.reduce_sum(tf.square(linear_model - y))
# 定義優化器optimizer
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)
# 4個藍色點的訓練數據,分解成x和y的數組為
x_train = [1, 2, 3, 4]
y_train = [0, -1, -2, -3]
# 初始化Session
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
# 循環1000次,訓練模型
for i in range(1000):
sess.run(train, {x: x_train, y: y_train})
# 評估准確率
curr_W, curr_b, curr_loss = sess.run([W, b, loss], {x: x_train, y: y_train})
print("W: %s b: %s loss: %s" % (curr_W, curr_b, curr_loss))
# 保存計算圖
with tf.summary.FileWriter(FLAGS.summaryDir + 'train', sess.graph) as writer:
writer.flush()
# 在運行main程序的時候,將參數傳入執行代碼中
# 本例中就指定了summaryDir參數
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--summaryDir', type=str, default='',
help='Summaries log directory')
FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main)
在PAI
中,下圖中1
的位置指定為tensorflow-demo2.py
文件,2
的位置指定為summary
目錄,然后點擊3
處的按鈕:
可能是PAI的BUG,該示例在執行的時候,輸出結果永遠是報錯,但是在OSS中,summary
文件也已經成功生成,而且如果點擊查看Tensorblaord
按鈕,其實是可以啟動TensorBoard
的:
如上圖所示,可以成功運行PAI
端的TensorBoard
(URL是阿里雲的,不是本機localhost的)。而且生成的圖和本地運行生成的圖也是一模一樣的(廢話)。
參考文檔
官方文檔:https://www.tensorflow.org/get_started/get_started