本博客是根據斯坦福大學的一門 tensorflow 課程的課件整理。這是課程地址 CS 20: Tensorflow for Deep Learning Research。
1 TensorBoard 使用簡介
tensorboard
可以用來使你創建的圖可視化,比如你創建了一個複雜的神經網絡,複雜到自己都不太理解,這時候就可以用tensorboard
來看一看你的圖,讓你更好的去訓練、調試或者優化。我們首先給出使用的方法,
import tensorflow as tf a = tf.constant(2) b = tf.constant(3) x = tf.add(a, b) writer = tf.summary.FileWriter('./graphs',tf.get_default_graph()) with tf.Session() as sess: # writer = tf.summary.FileWriter('./graphs', sess.graph) print(sess.run(x)) writer.close()
如果你還記得上一節課寫的程序,那你應該能注意到這裏多了兩行程序。想要使用
tensorboard
,就得先將你定義的圖寫到日誌文件裏,用法是:writer = tf.summary.FileWriter([logdir], [graph])
其中的
[logdir]
是你日誌文件的目錄。需要注意的一點是,在windows
系統上,你的目錄路徑要麼是類似./graphs
這樣在日誌文件的上一層。要麼擁有更長的路徑,這時候你需要用雙斜槓分開路徑,比如E:\\TensorBoard\\logfile
.[graph]
是你想要查看的圖,一般是你當前正在使用的。你可以使用tf.get_default_graph()
或者sess.graph
作爲參數(當然你得先創建一個sess
才能這麼寫)。注意:上述的語句必須得寫在你定義好圖之後,執行計算之前。
接下來,運行的程序,並啓動
tensorboard
,在命令行輸入:python [filename.py] tensorboard --logdir="./graphs" --port 6006
[filename.py]
是你自己程序的名字,logdir=
後面也要改成你自己的目錄,port
是可選的參數,tensorboard
默認的端口就是6006
.然後根據屏幕上的提示,在瀏覽器輸入地址,就可以看到剛纔創建的圖了。
你還可以自定義上面節點的名字,將代碼中的幾行改成如下:
a = tf.constant(2, name="a") b = tf.constant(3, name="b") x = tf.add(a, b, name="add")
再運行
tensorboard
以後就會變成:注意:如果你重複運行程序,目錄下會生成多個日誌文件,需要自己手動清理一下。
現在你已經知道了怎麼使用
tensorboard
,還有很多其他功能需要你自己去探索。
2 Constant 常量
可以直接創建一個常量,我們之前已經試過很多次了:
tf.constant(value, dtype=None, shape=None, name='Const', verify_shape=False) # constant of 1d tensor (vector) a = tf.constant([2, 2], name="vector") # constant of 2x2 tensor (matrix) b = tf.constant([[0, 1], [2, 3]], name="matrix")
可以創建一個特定維數的張量(tensor),然後填入特定的值:
tf.zeros(shape, dtype=tf.float32, name=None) # create a tensor of shape and all elements are zeros tf.zeros([2, 3], tf.int32) ==> [[0, 0, 0], [0, 0, 0]]
tf.zeros_like(input_tensor, dtype=None, name=None, optimize=True) # create a tensor of shape and type (unless type is specified) as the input_tensor but all elements are zeros. # input_tensor [[0, 1], [2, 3], [4, 5]] tf.zeros_like(input_tensor) ==> [[0, 0], [0, 0], [0, 0]]
tf.ones(shape, dtype=tf.float32, name=None) # create a tensor of shape and all elements are ones tf.ones([2, 3], tf.int32) ==> [[1, 1, 1], [1, 1, 1]]
tf.ones_like(input_tensor, dtype=None, name=None, optimize=True) # create a tensor of shape and type (unless type is specified) as the input_tensor but all elements are ones. # input_tensor is [[0, 1], [2, 3], [4, 5]] tf.ones_like(input_tensor) ==> [[1, 1], [1, 1], [1, 1]]
tf.fill(dims, value, name=None) # create a tensor filled with a scalar value. tf.fill([2, 3], 8) ==> [[8, 8, 8], [8, 8, 8]]
可以創建序列常量:
tf.lin_space(start, stop, num, name=None) # create a sequence of num evenly-spaced values are generated beginning at start. If num > 1, the values in the sequence increase by (stop - start) / (num - 1), so that the last one is exactly stop. # comparable to but slightly different from numpy.linspace tf.lin_space(10.0, 13.0, 4, name="linspace") ==> [10.0 11.0 12.0 13.0]
tf.range([start], limit=None, delta=1, dtype=None, name='range') # create a sequence of numbers that begins at start and extends by increments of delta up to but not including limit # slight different from range in Python # 'start' is 3, 'limit' is 18, 'delta' is 3 tf.range(start, limit, delta) ==> [3, 6, 9, 12, 15] # 'start' is 3, 'limit' is 1, 'delta' is -0.5 tf.range(start, limit, delta) ==> [3, 2.5, 2, 1.5] # 'limit' is 5 tf.range(limit) ==> [0, 1, 2, 3, 4]
不像
python
中的range
,TensorFlow
中的range
並不能用來迭代。for _ in np.linspace(0, 10, 4): # OK for _ in tf.linspace(0.0, 10.0, 4): # TypeError: 'Tensor' object is not iterable. for _ in range(4): # OK for _ in tf.range(4): # TypeError: 'Tensor' object is not iterable.
也可以創建特定分佈的隨機常量,具體查閱 官方文檔:
tf.random_normal tf.truncated_normal tf.random_uniform tf.random_shuffle tf.random_crop tf.multinomial tf.random_gamma tf.set_random_seed
3 數學運算
tensorflow
中的運算都很直觀,直接查詢官方文檔就可以。接下去就講幾個比較棘手的點。除法:
tensorflow
中有 7 種不同的除法運算,所有的運算都差不多但是又有一些小差異。a = tf.constant([2, 2], name='a') b = tf.constant([[0, 1], [2, 3]], name='b') with tf.Session() as sess: print(sess.run(tf.div(b, a))) ⇒ [[0 0] [1 1]] print(sess.run(tf.divide(b, a))) ⇒ [[0. 0.5] [1. 1.5]] print(sess.run(tf.truediv(b, a))) ⇒ [[0. 0.5] [1. 1.5]] print(sess.run(tf.floordiv(b, a))) ⇒ [[0 0] [1 1]] print(sess.run(tf.realdiv(b, a))) ⇒ # Error: only works for real values print(sess.run(tf.truncatediv(b, a))) ⇒ [[0 0] [1 1]] print(sess.run(tf.floor_div(b, a))) ⇒ [[0 0] [1 1]]
tf.add_n()
,可以對多個張量進行相加:tf.add_n([a, b, b]) => equivalent to a + b + b
點乘:使用
tf.tensordot()
進行點乘:a = tf.constant([10, 20], name='a') b = tf.constant([2, 3], name='b') with tf.Session() as sess: print(sess.run(tf.multiply(a, b))) ⇒ [20 60] # element-wise multiplication print(sess.run(tf.tensordot(a, b, 1))) ⇒ 80
4 數據類型
TensorFlow
接受Python
原生類型,例如Python
布爾值,數值(整數,浮點數)和字符串。 單個值將被轉換爲 0維張量(或標量),列表將被轉換爲 1維張量(向量),列表的列表 將被轉換爲 2 維張量。t_0 = 19 # Treated as a 0-d tensor, or "scalar" tf.zeros_like(t_0) # ==> 0 tf.ones_like(t_0) # ==> 1 t_1 = [b"apple", b"peach", b"grape"] # treated as a 1-d tensor, or "vector" tf.zeros_like(t_1) # ==> [b'' b'' b''] tf.ones_like(t_1) # ==> TypeError t_2 = [[True, False, False], [False, False, True], [False, True, False]] # treated as a 2-d tensor, or "matrix" tf.zeros_like(t_2) # ==> 3x3 tensor, all elements are False tf.ones_like(t_2) # ==> 3x3 tensor, all elements are True
下面是
TensorFlow
的完整數據類型列表:如果你使用過
NumPy
,你可能已經發現TensorFlow
與NumPy
有很多相似的。沒錯,TensorFlow
就是設計成可以跟NumPy
無縫銜接的。其實很多TensorFlow
的數據類型和NumPy
的是一樣的,可以直接傳入NumPy
的數據類型。np.int32 == tf.int32 ==> True tf.ones([2, 2], np.float32) ==> [[1.0 1.0], [1.0 1.0]]
在
tf.Session.run()
中, 如果請求的對象是一個張量,那麼輸出將是一個NumPy
數組。雖然大多數時候,
TensorFlow
和NumPy
的數據類型可以交替使用,但是因爲還是會存在一些小問題,所以我們還是應該儘可能的使用TensorFlow
的類型。
5 Variables 變量
常量很好玩,但是我們現在需要學習真正有用的東西:變量。常量和變量的區別:
- 常量是不可變的,但是你在訓練模型的過程中需要更新參數。
- 常量的值存儲在圖中,並且會複製到加載圖的地方。而變量的值是單獨存儲的,比如說存在變量服務器上。
上述第2點意味着,當你的常量佔用很大的空間時,加載圖的時候就會多花費一些時間。
創建變量: 需要創建一個類
tf.Variable
的實例。老的方法是這樣子:s = tf.Variable(2, name="scalar") m = tf.Variable([[0, 1], [2, 3]], name="matrix") W = tf.Variable(tf.zeros([784,10]))
但是
TensorFlow
不建議使用這種方法,而是建議我們使用tf.get_variable
.tf.get_variable( name, shape=None, dtype=None, initializer=None, regularizer=None, trainable=True, collections=None, caching_device=None, partitioner=None, validate_shape=True, use_resource=None, custom_getter=None, constraint=None )
s = tf.get_variable("scalar", initializer=tf.constant(2)) m = tf.get_variable("matrix", initializer=tf.constant([[0, 1], [2, 3]])) W = tf.get_variable("big_matrix", shape=(784, 10), initializer=tf.zeros_initializer())
如果使用
tf.constant
作爲初始值,那就不用提供shape
參數。初始化參數: 使用參數之前需要進行初始化。不然會得到一個錯誤
FailedPreconditionError: Attempting to use uninitialized value
。使用下面這條語句可以打印出所有沒有被初始化的參數:
print(session.run(tf.report_uninitialized_variables()))
最簡單的初始化方法就是初始化所有參數:
with tf.Session() as sess: sess.run(tf.global_variables_initializer())
也可以使用列表初始化部分參數:
with tf.Session() as sess: sess.run(tf.variables_initializer([a, b]))
也可以僅初始化某個參數:
with tf.Session() as sess: sess.run(W.initializer)
計算變量的值: 同樣,我們必須在一個
session
裏才能計算變量的值:# V is a 784 x 10 variable of random values V = tf.get_variable("normal_matrix", shape=(784, 10), initializer=tf.truncated_normal_initializer()) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print(sess.run(V))
也可以通過
tf.Variable.eval()
得到變量值:with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print(V.eval())
給變量賦值: 使用
tf.Variable.assign()
:W = tf.Variable(10) W.assign(100) with tf.Session() as sess: sess.run(W.initializer) print(W.eval()) # >> 10
爲什麼會輸出 10 呢?因爲
assign
也是一個運算,需要在session
中才能計算出來。W = tf.Variable(10) assign_op = W.assign(100) with tf.Session() as sess: sess.run(assign_op) print(W.eval()) # >> 100
注意這裏我們沒有對
W
進行初始化,因爲assign
爲我們做了初始化。實際上,初始化操作就是一種assign
操作。自增和自減:
W = tf.Variable(10) with tf.Session() as sess: sess.run(W.initializer) print(sess.run(W.assign_add(10))) # >> 20 print(sess.run(W.assign_sub(2))) # >> 18
這裏需要對變量進行初始化,因爲自增和自減依賴於一個當前值。
每個
session
都是單獨存儲比變量的,所以每個session
對一張圖中的變量可以擁有自己的當前值。W = tf.Variable(10) sess1 = tf.Session() sess2 = tf.Session() sess1.run(W.initializer) sess2.run(W.initializer) print(sess1.run(W.assign_add(10))) # >> 20 print(sess2.run(W.assign_sub(2))) # >> 8 print(sess1.run(W.assign_add(100))) # >> 120 print(sess2.run(W.assign_sub(50))) # >> -42 sess1.close() sess2.close()
6 InteractiveSession
與普通
session
唯一的不同就是,InteractiveSession
會使自己成爲默認會話,因此可以在不顯示使用會話的情況下使用run()
或者eval()
。 這在交互式shell
和IPython
筆記本中很方便,因爲它避免了必須傳遞顯式會話對象來運行ops
。 但是,當您有多個會話運行時,這會很複雜。sess = tf.InteractiveSession() a = tf.constant(5.0) b = tf.constant(6.0) c = a * b print(c.eval()) # we can use 'c.eval()' without explicitly stating a session sess.close()
tf.get_default_session()
返回當前線程的默認會話。 返回的Session
將是輸入Session
或Session.as_default()
上下文的最內層會話。
7 Control Dependencies 控制依賴
有時候,我們有兩個或更多的獨立操作,我們想指定哪個操作應該先運行。 在這種情況下,我們使用
tf.Graph.control_dependencies([control_inputs])
。# your graph g have 5 ops: a, b, c, d, e with g.control_dependencies([a, b, c]): # `d` and `e` will only run after `a`, `b`, and `c` have executed. d = ... e = …
8 Importing Data 導入數據
8.1 老方法:placeholders 和 feed_dict
我們寫一個
TensorFlow
程序,一般是兩個步驟:- 定義一張圖;
- 使用
Session
去執行計算。
在定義圖的時候,我們可能根本不知道我們要計算的值是多少,拿我們熟悉的函數舉個例子:
其中的 是未知數,或者說 佔位符(placeholder) 。使用了佔位符後,你可以在你想要計算的時候再提供所需的數據。
使用方法如下:
tf.placeholder(dtype, shape=None, name=None)
參數的意義都很明確,唯一一點需要注意的是,當使用
shape=None
時表示接受任意形狀的參數。對於一些接受特定形狀的函數來說,使用shape=None
會使程序出錯,所以爲了不會引發一些奇怪的問題,應該儘量詳細的定義佔位符的形狀。對於如下程序:
a = tf.placeholder(tf.float32, shape=[3]) # a is placeholder for a vector of 3 elements b = tf.constant([5, 5, 5], tf.float32) c = a + b # use the placeholder as you would any tensor with tf.Session() as sess: print(sess.run(c))
運行的時候會出錯,因爲我們沒有爲佔位符
a
賦值,這時候需要用到feed_dict
,這是一個字典,鍵是佔位符,值是要賦給佔位符的數據。with tf.Session() as sess: # compute the value of c given the value of a is [1, 2, 3] print(sess.run(c, {a: [1, 2, 3]})) # [6. 7. 8.]
我們也可以通過迭代來連續賦值:
with tf.Session() as sess: for a_value in list_of_a_values: print(sess.run(c, {a: a_value}))
feed_dict
不僅可以用在佔位符上,我們通過:tf.Graph.is_feedable(tensor)
來判斷是否可以使用
feed_dict
。如果可以,就能:a = tf.add(2, 5) b = tf.multiply(a, 3) with tf.Session() as sess: print(sess.run(b)) # >> 21 # compute the value of b given the value of a is 15 print(sess.run(b, feed_dict={a: 15})) # >> 45
這個功能可以用在測試你的模型上。當你想要測試一張很大的圖的其中的一部分,你可以提供“假”數據給模型,這樣子
TensorFlow
就不會浪費時間去做一些無用的計算。
8.2 新方法:tf.data
- 這個方法留到下節課我們通過一個例子講解。
9 The trap of lazy loading 延遲加載的陷阱
延遲加載指的是推遲聲明/初始化一個對象直到加載它時爲止的編程模式。在
TensorFlow
中,它意味着你推遲創建一個op
,直到你需要計算它。這是一個正常加載的例子:
x = tf.Variable(10, name='x') y = tf.Variable(20, name='y') z = tf.add(x, y) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) writer = tf.summary.FileWriter('graphs/normal_loading', sess.graph) for _ in range(10): sess.run(z) writer.close()
這是一個爲了省一行代碼自作聰明的例子:
x = tf.Variable(10, name='x') y = tf.Variable(20, name='y') with tf.Session() as sess: sess.run(tf.global_variables_initializer()) writer = tf.summary.FileWriter('graphs/lazy_loading', sess.graph) for _ in range(10): sess.run(tf.add(x, y)) writer.close()
如果在
tensorboard
裏看是這樣的:上方的是正常加載,下方的是延遲加載。看起來好像沒有什麼區別。
我們再通過下面這條語句查看一下圖的定義:
print(tf.get_default_graph().as_graph_def())
這是正常加載的:
node { name: "Add" op: "Add" input: "x/read" input: "y/read" attr { key: "T" value { type: DT_INT32 } } }
這是延遲加載的:
node { name: "Add_1" op: "Add" input: "x_1/read" input: "y_1/read" attr { key: "T" value { type: DT_INT32 } } } … … … node { name: "Add_10" op: "Add" ... }
我們發現程序創建了 10 個
Add
節點!以上就是我們要避免延遲加載的原因,因爲我們可能會在不經意間產生許多無用的節點,這會使你的圖佔用空間變得巨大,以及加載緩慢等等許多壞處。