第2課: TensorFlow運(yùn)算
注意:運(yùn)算的介紹可能比較枯燥代兵,但是是基礎(chǔ)芭届,之后會(huì)更有趣颇玷。
1. TensorBoard
TensorFlow不僅僅是一個(gè)軟件包去件,它是一個(gè)包括TensorFlow坡椒,TensorBoard和Tensor Serving的套件。為了充分利用TensorFlow尤溜,我們應(yīng)該知道如何將它們結(jié)合起來使用倔叼,所以首先我們來了解TensorBoard。
Tensor是TensorFlow安裝程序自帶的圖形可視化工具宫莱,用Google自己的話說:
“你使用TensorFlow進(jìn)行的例如訓(xùn)練深度神經(jīng)網(wǎng)絡(luò)的計(jì)算可能會(huì)非常復(fù)雜和難懂丈攒。為了更簡單的理解,調(diào)試和優(yōu)化TensorFlow程序授霸,我們提供了一個(gè)名叫TensorBoard的可視化套件巡验。”
TensorBoard配置好后绝葡,大概是這個(gè)樣子:
當(dāng)用戶在一個(gè)激活了TensorBoard的TensorFlow程序上進(jìn)行運(yùn)算時(shí)深碱,這些運(yùn)算都會(huì)被導(dǎo)出到事件日志(event log)文件中。TensorBoard能夠?qū)⑦@些事件日志可視化以便可以深入了解模型的計(jì)算圖和它的運(yùn)行時(shí)行為藏畅。越早和越經(jīng)常的使用TensorBoard會(huì)使TensorFlow上的工作更有趣且更有成效敷硅。
接下來讓我們編寫第一個(gè)TensorFlow程序,然后使用TensorBoard可視化程序的計(jì)算圖愉阎。為了使用TensorBoard進(jìn)行可視化绞蹦,我們需要寫入程序的事件日志。
import tensorflow as tf
a = tf.constant(2)
b = tf.constant(3)
x = tf.add(a, b)
writer = tf.summary.FileWriter([logdir], [graph])
with tf.Session() as sess:
print(sess.run(x))
writer.close()
[graph]是程序的計(jì)算圖榜旦,你可以用tf.get_default_graph()
獲得程序默認(rèn)的計(jì)算圖幽七,也可以用sess.graph
獲得Session處理的計(jì)算圖。(當(dāng)然你需要?jiǎng)?chuàng)建一個(gè)Session)不管怎樣溅呢,確定在創(chuàng)建writer之前你已經(jīng)定義了完整的計(jì)算圖澡屡,不然TensorBoard可視化的結(jié)果將會(huì)不完整。
[logdir]是你希望存儲(chǔ)事件日志的目錄咐旧。
接下來驶鹉,打開終端,先運(yùn)行剛才的Tensorflow程序铣墨,再以剛才寫入的日志目錄作為參數(shù)運(yùn)行TensorBoard室埋。
$ python3 [my_program.py]
$ tensorboard --logdir [logdir] --port [port]
最后打開瀏覽器,輸入http://localhost:[port]/(端口號(hào)自己選擇),你將會(huì)看到TensorBoard頁面姚淆。點(diǎn)擊Graph標(biāo)簽?zāi)憧梢圆榭从?jì)算圖中有3個(gè)節(jié)點(diǎn):2個(gè)常量運(yùn)算和一個(gè)add運(yùn)算孕蝉。
“Const”和“Const_1"代表a和b,節(jié)點(diǎn)”Add“對(duì)應(yīng)x腌逢。我們可以在代碼中給a降淮,b和x命名讓TensorBoard了解這些運(yùn)算的名字。
a = tf.constant(2, name="a")
b = tf.constant(3, name="b")
x = tf.add(a, b, name="add")
現(xiàn)在如果你再次運(yùn)行程序和TensorBoard搏讶,你會(huì)看到:
計(jì)算圖自己定義了運(yùn)算和運(yùn)算間依賴關(guān)系骤肛,只要簡單的點(diǎn)擊節(jié)點(diǎn)就可以查看值和節(jié)點(diǎn)類型。
Note:如果你運(yùn)行了程序很多次窍蓝,在日志目錄會(huì)有多個(gè)事件日志文件。TensorBoard只會(huì)顯示最后一個(gè)計(jì)算圖并且警告有多個(gè)日志文件繁成,如果想避免警告就刪除不需要的日志文件吓笙。
當(dāng)然,TensorBoard能做的遠(yuǎn)遠(yuǎn)不止于可視化計(jì)算圖巾腕,這里我們將介紹它最重要的一些功能面睛。
2. 常量運(yùn)算(Constant op)
創(chuàng)建常量運(yùn)算很直接。
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")
你可以用指定值初始化一個(gè)特定維度的tensor尊搬,就像Numpy一樣叁鉴。
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]]
你可以創(chuàng)建一個(gè)常量序列
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
tf.range(3, 18, 3) ==> [3, 6, 9, 12, 15]
tf.range(3, 1, -0.5) ==> [3, 2.5, 2, 1.5]
tf.range(5) ==> [0, 1, 2, 3, 4]
需要注意的是和Numpy的序列不同,TensorFlow的序列是不能迭代的佛寿。
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.
你也可以創(chuàng)建服從指定分布的隨機(jī)常量幌墓。
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. 數(shù)學(xué)運(yùn)算
TensorFlow的數(shù)學(xué)運(yùn)算符很標(biāo)準(zhǔn),你可以在這里找到完整的列表冀泻。
- TensorFlow大量的除法運(yùn)算
TensorFlow至少支持7種除法運(yùn)算常侣,做著或多或少一樣的事情:tf.div, tf.divide, tf.truediv, tf.floordiv, tf.realdiv, tf.truncateddiv, tf.floor_div
。創(chuàng)建這些運(yùn)算的人一定非常喜歡除法弹渔。胳施。。
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
將多個(gè)tensor相加肢专。
tf.add_n([a, b, b]) => equivalent to a + b + b
- 點(diǎn)積
注意tf.matmul
不是點(diǎn)積舞肆,而是使用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
下面是Python中的運(yùn)算博杖,摘自Fundamentals of DeepLearning椿胯。
4. 數(shù)據(jù)類型
- Python原生類型
TensorFlow兼容Python的原生數(shù)據(jù)類型,例如:boolean欧募,integer压状,float和string等。單獨(dú)的值轉(zhuǎn)換為0維tensor(標(biāo)量,scalar)种冬,列表轉(zhuǎn)換為1維tensor(向量镣丑,vector),列表的列表轉(zhuǎn)換為2維tensor(矩陣娱两,matrix)莺匠,以此類推。
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原生類型
TensorFlow也有自己的原生類型:tf.int32, tf.float32
,還有更令人興奮的類型:tf.bfloat, tf.complex, tf.quint
十兢,完整的類型列表在這里趣竣。
- Numpy數(shù)據(jù)類型
到此為止,你可能已經(jīng)注意到Numpy和TensorFlow數(shù)據(jù)類型的相似性旱物。TensorFlow被設(shè)計(jì)為和Numpy無縫集成遥缕,這個(gè)軟件包已經(jīng)成為數(shù)據(jù)科學(xué)的通用語。
TensorFlow的數(shù)據(jù)類型是基于Numpy的宵呛,實(shí)際上np.int32 == tf.int32
返回True
单匣。你可以將Numpy類型傳給TensorFlow函數(shù)。
tf.ones([2, 2], np.float32) ==> [[1.0 1.0], [1.0 1.0]]
5. 變量
- 創(chuàng)建變量
使用tf.Variable
創(chuàng)建變量宝穗,應(yīng)該注意的是tf.constant
是小寫的而tf.Variable
是大寫的户秤,這是因?yàn)?strong>tf.constant
是一個(gè)運(yùn)算而tf.Variable
是一個(gè)含有多個(gè)運(yùn)算的類。
x = tf.Variable(...)
x.initializer # init
x.value() # read op
x.assign(...) # write op
x.assign_add(...)
# and more
傳統(tǒng)的創(chuàng)建變量方式為
tf.Variable(<initial-value>, name=<optional-name>)
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
來創(chuàng)建變量逮矛,這樣有利于變量的共享鸡号。
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())
- 初始化變量
你必須在使用變量之前初始化它們,否則將會(huì)報(bào)FailedPreconditionError: Attempting to use uninitialized value
的錯(cuò)誤须鼎。想查看所有沒初始化的變量鲸伴,你可以將它們打印出來:
print(session.run(tf.report_uninitialized_variables()))
簡單的將所有變量一次性初始化的方法為:
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
在這種情況下你用tf.Session.run()
獲得的是一個(gè)initializer而不是之前的tensor運(yùn)算。
如果只初始化一部分變量莉兰,你可以使用tf.variables_initializer()
:
with tf.Session() as sess:
sess.run(tf.variables_initializer([a, b]))
你也可以用tf.Variable.initializer()
一個(gè)個(gè)的初始化變量:
with tf.Session() as sess:
sess.run(W.initializer)
還有一種初始化變量方式是從一個(gè)文件讀取挑围,我們會(huì)在后面的課程提到。
- 計(jì)算(Evaluate)變量的值
和tensor類似糖荒,可以用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而不是100蜘矢?W.assign(100)
并沒有給W賦值而是創(chuàng)建了一個(gè)assign運(yùn)算,要想使這個(gè)運(yùn)算生效我們可以在session中運(yùn)行這個(gè)運(yùn)算综看。
W = tf.Variable(10)
assign_op = W.assign(100)
with tf.Session() as sess:
sess.run(assign_op)
print(W.eval()) # >> 100
注意這次我們沒有初始化W品腹,因?yàn)閍ssign為我們做了。實(shí)際上initializer就是一個(gè)assign運(yùn)算红碑,它用初始值來初始化變量舞吭。
# in the source code
self._initializer_op = state_ops.assign(self._variable, self._initial_value, validate_shape=validate_shape).op
為了簡化變量的加減運(yùn)算泡垃,TensorFlow提供了tf.Variable.assign_add()
和tf.Variable.assign_sub()
方法。不同于tf.Variable.assign()
羡鸥,這兩個(gè)方法不會(huì)初始化變量蔑穴,因?yàn)樗鼈円蕾囎兞康某跏贾怠?/p>
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
因?yàn)門ensorFlow的Session們維護(hù)著各自的值,每個(gè)Session擁有變量屬于Session自己的當(dāng)前值惧浴。
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()
當(dāng)你有一個(gè)依賴其它變量的變量時(shí)存和,假設(shè)你聲明了U = W * 2
# W is a random 700 x 10 tensor
W = tf.Variable(tf.truncated_normal([700, 10]))
U = tf.Variable(W * 2)
在這種情況下,你應(yīng)該使用initialized_value()
方法去保證在使用W的值去初始化U之前W已經(jīng)被初始化衷旅。
U = tf.Variable(W.initialized_value() * 2)
6. 交互Session(Interactive Session)
你有時(shí)候會(huì)看到InteractiveSession
代替Session
捐腿,它們之間唯一的不同是InteractiveSession
會(huì)把自己設(shè)置為默認(rèn)的Session,這樣你就可以直接調(diào)用run()
和eval()
方法柿顶。這樣方便了在Shell和IPython Notebook中使用茄袖,但是當(dāng)有多個(gè)Session時(shí)使問題變得復(fù)雜。
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()
方法返回當(dāng)前線程的默認(rèn)Session嘁锯。
7. 控制依賴關(guān)系(Control Dependencies)
有時(shí)候绞佩,我們擁有兩個(gè)或者更多獨(dú)立的運(yùn)算,而我們希望指定哪些運(yùn)算應(yīng)該先運(yùn)行猪钮。這個(gè)情況下,我們可以使用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. 導(dǎo)入數(shù)據(jù)
8.1 傳統(tǒng)的方法: placehoder
和feed_dict
TensorFlow 程序一般由兩個(gè)階段:
- 組裝一個(gè)計(jì)算圖
- 用Session在計(jì)算圖中執(zhí)行運(yùn)算和評(píng)估變量
我們可以在不管計(jì)算所需的數(shù)值的情況下組裝計(jì)算圖烤低,這個(gè)在不知道輸入數(shù)據(jù)的情況下定義函數(shù)是一樣的。
在計(jì)算圖組裝完成后笆载,我們可以用placeholder
將自己的數(shù)據(jù)灌入計(jì)算圖中:
tf.placeholder(dtype, shape=None, name=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))
當(dāng)我們嘗試通過Session計(jì)算c
的值時(shí)扑馁,我們將會(huì)獲得一個(gè)錯(cuò)誤,因?yàn)槲覀冃枰@得a
的值凉驻。我們可以通過feed_dict
來向placeholder
灌數(shù)據(jù)腻要,它是一個(gè)字典。
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.]
這時(shí)我們?cè)俨榭碩ensorBoard涝登,計(jì)算圖如下:
我們可以向placeholder
中多次灌入不同的值雄家。
with tf.Session() as sess:
for a_value in list_of_a_values:
print(sess.run(c, {a: a_value}))
你也可以向不是placeholder
的tensor灌入數(shù)值,所有的tensor都是可以灌值的胀滚,可以用tf.Graph.is_feedable(tensor)
方法查看一個(gè)tensor是否是可以灌值的趟济。
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
feed_dict
對(duì)測(cè)試你的模型非常有用,當(dāng)你有一個(gè)很大的計(jì)算圖但只想測(cè)試其中某一塊時(shí)咽笼,你可以灌入假值來避免在不必要的運(yùn)算上浪費(fèi)時(shí)間顷编。
8.2 新的方法:tf.data
這個(gè)方法需要配合例子來講,所以我們會(huì)在下一課線性和邏輯回歸中涉及剑刑。
9.lazy loading的陷阱
現(xiàn)在TensorFlow中最常見的不是bug的bug叫做“l(fā)azy loading”媳纬。Lazy loading是一種設(shè)計(jì)模式,即在你要使用一個(gè)對(duì)象時(shí)才初始化對(duì)象。在TensorFlow的場(chǎng)景中钮惠,它的含義是在要執(zhí)行一個(gè)運(yùn)算時(shí)才創(chuàng)建這個(gè)運(yùn)算茅糜。例如:
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))
print(tf.get_default_graph().as_graph_def())
writer.close()
現(xiàn)在我們看看TensorBoard:
打印計(jì)算圖的定義:
print(tf.get_default_graph().as_graph_def())
得到如下結(jié)果:
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"
...
}
你可能回想:“這很愚蠢,為什么我要在一個(gè)相同的值上計(jì)算兩次萌腿?”限匣,然后認(rèn)為這是一個(gè)bug。這種情況會(huì)經(jīng)常發(fā)生毁菱,比如在你想在訓(xùn)練集的每個(gè)batch上計(jì)算損失函數(shù)或做預(yù)測(cè)時(shí)米死,如果你不注意,可能會(huì)添加巨量的無用運(yùn)算贮庞。
Note:在翻譯這篇文章時(shí)峦筒,譯者用TensorFlow 1.8版本做了實(shí)驗(yàn),這個(gè)bug應(yīng)該沒有了窗慎。