上一節(jié)我們介紹了graph、tensor和session,這一節(jié)主要介紹operation。主要內(nèi)容有:
- TensorBoard的基本用法
- Basic operations
- Tensor types
- Importing data
- Lazy loading
1- Visualize it with TensorBoard
TensorFlow提供了可視化graph的工具TensorBoard,之前我們看到的graph示意圖就是TensorBoard生成的,下面介紹其使用方法。
1.1- 產(chǎn)生event文件
如果要使用TensorBoard,需要先產(chǎn)生graph的event文件,可以通過(guò)tf.summary.FileWriter,示例代碼如下:
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:
print(sess.run(x))
writer.close() # close the writer when you’re done using it
在graph定義結(jié)束后,session運(yùn)行之前,通過(guò)tf.summary.FileWriter將graph輸出到event文件。注意,event文件的產(chǎn)生,和session是否運(yùn)行無(wú)關(guān),它只和graph有關(guān),比如下面的代碼:
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())
writer.close()
1.2- 運(yùn)行tensorboard命令
tf.summary.FileWriter生成event文件后,然后使用tensorboard命令去讀取:
tensorboard --logdir="./graphs"
tensorboard命令是隨TensorFlow安裝自帶的,上面的命令會(huì)在默認(rèn)的6006端口啟動(dòng)了一個(gè)HTTP服務(wù),讀取./graphs目錄下的event文件。瀏覽器打開(kāi)http://localhost:6006 , 即可看到TensorBoard的界面,類(lèi)似如下:

關(guān)于TensorBoard更多內(nèi)容,請(qǐng)參考官網(wǎng):https://www.tensorflow.org/guide/graph_viz
1.3- 命名
在TensorBoard上,可以看到每個(gè)節(jié)點(diǎn)都有一個(gè)名字,這個(gè)名字可以在代碼里定義,如果沒(méi)有定義,一般會(huì)被自動(dòng)命名,比如,下面的代碼,節(jié)點(diǎn)會(huì)根據(jù)其節(jié)點(diǎn)類(lèi)型+序號(hào)自動(dòng)命名:
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())
writer.close()

我們可以在代碼里通過(guò)name參數(shù),指定每個(gè)node的名字:
import tensorflow as tf
a = tf.constant(2, name='a')
b = tf.constant(3, name='b')
x = tf.add(a, b, name='add')
writer = tf.summary.FileWriter('./graphs', tf.get_default_graph())
with tf.Session() as sess:
print(sess.run(x)) # >> 5

最后說(shuō)明一下,上面只是介紹了TensorBoard的可視化功能,但其功能遠(yuǎn)不僅如此,它將是我們常用的工具。
2- Constants, Variables, Ops
這里介紹的constants和Variable,本質(zhì)上都是Operation(簡(jiǎn)稱(chēng)為Ops),Operation在graph里表現(xiàn)為一個(gè)節(jié)點(diǎn)。
2.1- Constants
graph中使用tf.constant定義常量,常量不會(huì)被改變。tf.constant方法簽名如下:
tf.constant(
value,
dtype=None,
shape=None,
name='Const',
verify_shape=False
)
舉例:
a = tf.constant([2, 2], name='a')
b = tf.constant([[0, 1], [2, 3]], name='b')
tf.constant定義了一個(gè)返回固定值的operation。有有點(diǎn)繞的地方是:tf.constant()方法的返回值還是tf.Tensor,可以理解為operation是在tf.constant()的內(nèi)部定義了,但返回的是operation的輸出,即tf.Tensor,我們通過(guò)TensorFlow的開(kāi)源代碼可以大概窺探:

2.1.1- 快捷方法生成常見(jiàn)constant
除了上面標(biāo)準(zhǔn)的定義constant的方法,對(duì)于一些常見(jiàn)的constant(比如比如全零,全一),與NumPy類(lèi)似,tf也提供了一些同名方法,快速生成某類(lèi)tensor,
tf.zeros(shape, dtype=tf.float32, name=None)
tf.zeros([2, 3], tf.int32) ==> [[0, 0, 0], [0, 0, 0]]
tf.zeros_like(input_tensor, dtype=None, name=None, optimize=True)
# input_tensor is [[0, 1], [2, 3], [4, 5]]
tf.zeros_like(input_tensor) ==> [[0, 0], [0, 0], [0, 0]]
類(lèi)似的全一:
tf.ones(shape, dtype=tf.float32, name=None)
tf.ones_like(input_tensor, dtype=None, name=None, optimize=True)
或者填充某一個(gè)值的tensor
tf.fill(dims, value, name=None)
tf.fill([2, 3], 8) ==> [[8, 8, 8], [8, 8, 8]]
2.1.2- constant序列
下面介紹幾種生成constant序列的方法,與NumPy類(lèi)似。
tf.line_space生成start到stop的封閉線(xiàn)性空間,總的個(gè)數(shù)為num。start和stop必須包含在內(nèi)。
方法簽名:
tf.lin_space(start, stop, num, name=None)
舉例:
tf.lin_space(10.0, 13.0, 4) ==> [10. 11. 12. 13.]
tf.range生成start到limit的序列,start必須包含在內(nèi),limit一定不包含,不一定包含在內(nèi)。delta控制了步長(zhǎng)。
方法簽名:
tf.range(limit, delta=1, dtype=None, name='range')
tf.range(start, limit, delta=1, dtype=None, name='range')
舉例:
tf.range(3, 18, 3) ==> [3 6 9 12 15]
tf.range(5) ==> [0 1 2 3 4]
lin_sapce 和range有什么區(qū)別?
- lin_space,嚴(yán)格準(zhǔn)確的是start和stop,以及生成數(shù)量,每個(gè)數(shù)字之間并不一定嚴(yán)格步長(zhǎng)相等。
- range,嚴(yán)格準(zhǔn)確的是start和步長(zhǎng),limit是上限。
特別注意,tensor是不可迭代的(iterable),所以如下操作是非法的:
for _ in tf.range(4): # TypeError
2.1.3- 與隨機(jī)數(shù)相關(guān)的方法:
下面是幾個(gè)常見(jiàn)的生成隨機(jī)數(shù)的方法:
tf.random_normal #(正態(tài)分布)
tf.truncated_normal
tf.random_uniform
tf.random_shuffle
tf.random_crop
tf.multinomial
tf.random_gamma
其中:
tf.truncated_normal很常見(jiàn),它會(huì)剔除正太分布中超過(guò)2個(gè)標(biāo)準(zhǔn)差的隨機(jī)值。
tf.random_shuffle,會(huì)將傳入的tensor按第0個(gè)維度進(jìn)行隨機(jī)重排(shuffle)
另外可以設(shè)置隨機(jī)數(shù)種子,讓所有隨機(jī)數(shù)變得固定。
tf.set_random_seed(seed)
2.1.4- broadcasting
Tensor可以像NumPy一樣broadcasting,比如下面的element-wise乘法:
import tensorflow as tf
a = tf.constant([2, 2], name='a')
b = tf.constant([[0, 1], [2, 3]], name='b')
x = tf.multiply(a, b, name='mul')
with tf.Session() as sess:
print(sess.run(x))
# >> [[0 2]
# [4 6]]
2.1.5- verfiy_shape
關(guān)于verify_shape,默認(rèn)是不校驗(yàn)value的shape和參數(shù)shape必須匹配,如果value的shape不一致的話(huà),會(huì)按shape指定的維度,廣播為一致,比如:tf.constant(2, shape=[2,2]) 相當(dāng)于tf.constant([[2,2],[2,2]], shape=[2,2])
2.2- Operations
常見(jiàn)的operation:

值得注意都是,從上表看出tf.Variable屬于一個(gè)operation
其中的算數(shù)運(yùn)算如下,和NumPy非常類(lèi)似:

更多數(shù)學(xué)操作,請(qǐng)參考:https://www.tensorflow.org/api_guides/python/math_ops
關(guān)于除法,需要特別注意,下面是幾個(gè)舉例:
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]]
總體來(lái)說(shuō),tf.div是TensorFlow風(fēng)格的除法,而tf.divide對(duì)應(yīng)Python風(fēng)格的除法。
2.3- TensorFlow Data Types
2.3.1- python原生數(shù)據(jù)類(lèi)型
首先,TensorFlow使用Python原生數(shù)據(jù)類(lèi)型: boolean, numeric (int, float), strings
單個(gè)值,會(huì)被轉(zhuǎn)換為0-d的tensor,list會(huì)被轉(zhuǎn)換為1-d的tensor,含有l(wèi)ist的list,會(huì)被轉(zhuǎn)換為2-d tensor,以此類(lèi)推。
t_0 = 19 # scalars are treated like 0-d tensors
tf.zeros_like(t_0) # ==> 0
tf.ones_like(t_0) # ==> 1
t_1 = [b"apple", b"peach", b"grape"] # 1-d arrays are treated like 1-d tensors
tf.zeros_like(t_1) # ==> [b'' b'' b'']
tf.ones_like(t_1) # ==> TypeError: Expected string, got 1 of type 'int' instead.
t_2 = [[True, False, False],
[False, False, True],
[False, True, False]] # 2-d arrays are treated like 2-d tensors
tf.zeros_like(t_2) # ==> 3x3 tensor, all elements are False
tf.ones_like(t_2) # ==> 3x3 tensor, all elements are True
2.3.2- TensorFlow數(shù)據(jù)類(lèi)型
TensorFlow提供了如下數(shù)據(jù)類(lèi)型:
-
tf.float16: 16-bit half-precision floating-point. -
tf.float32: 32-bit single-precision floating-point. -
tf.float64: 64-bit double-precision floating-point. -
tf.bfloat16: 16-bit truncated floating-point. -
tf.complex64: 64-bit single-precision complex. -
tf.complex128: 128-bit double-precision complex. -
tf.int8: 8-bit signed integer. -
tf.uint8: 8-bit unsigned integer. -
tf.uint16: 16-bit unsigned integer. -
tf.uint32: 32-bit unsigned integer. -
tf.uint64: 64-bit unsigned integer. -
tf.int16: 16-bit signed integer. -
tf.int32: 32-bit signed integer. -
tf.int64: 64-bit signed integer. -
tf.bool: Boolean. -
tf.string: String. -
tf.qint8: Quantized 8-bit signed integer. -
tf.quint8: Quantized 8-bit unsigned integer. -
tf.qint16: Quantized 16-bit signed integer. -
tf.quint16: Quantized 16-bit unsigned integer. -
tf.qint32: Quantized 32-bit signed integer. -
tf.resource: Handle to a mutable resource. -
tf.variant: Values of arbitrary types.
2.3.3- 關(guān)于TF數(shù)據(jù)類(lèi)型和NumPy數(shù)據(jù)類(lèi)型
tf定義的數(shù)據(jù)類(lèi)型,幾乎是和NumPy對(duì)應(yīng)的。因此兩者甚至可以無(wú)縫集成:
甚至兩個(gè)類(lèi)型直接判斷相等,返回的是true
tf.int32 == np.int32 # ? True
傳入給Operation的參數(shù),指定數(shù)據(jù)類(lèi)型,用NumPy類(lèi)型也是可以的。
tf.ones([2, 2], np.float32) # ? [[1.0 1.0], [1.0 1.0]]
而且,在TensorFlow中,就是用NumPy的ndarray來(lái)表示Tensor value的,對(duì)于tf.Session.run(fetches),如果fetches是Tensor,則返回的是NumPy ndarray。
sess = tf.Session()
a = tf.zeros([2, 3], np.int32)
print(type(a)) # ? <class'tensorflow.python.framework.ops.Tensor'>
a = sess.run(a)
print(type(a)) # ? <class 'numpy.ndarray'>
雖然如此,還是建議盡可能的使用TF的數(shù)據(jù)類(lèi)型,原因如下:
- 使用Python原生類(lèi)型,TensorFlow 必須引用numpy類(lèi)型
- 最重要的是:NumPy不兼容GPU
2.4- 使用常量的注意
常量的值將作為graph定義的一部分被存儲(chǔ)和序列化,如果常量過(guò)多,將使graph的加載成本太大。這一點(diǎn),可以通過(guò)as_graph_def()證明:
my_const = tf.constant([1.0, 2.0], name="my_const")
with tf.Session() as sess:
print(sess.graph.as_graph_def())

關(guān)于常量的使用,有兩點(diǎn)指導(dǎo)意見(jiàn):
- 僅對(duì)基本數(shù)據(jù)類(lèi)型使用constant
- 對(duì)于需要更多內(nèi)存的數(shù)據(jù),使用Variable或reader
疑惑:用Variable有用嗎?不是也要提供initializ嗎?也會(huì)在模型內(nèi)存儲(chǔ)初始值啊。
解答:constant的value存儲(chǔ)在graph的定義中,因此只要graph在哪里加載一次,constant就要復(fù)制一份。而Variable分開(kāi)存儲(chǔ),而且可能放在單獨(dú)的參數(shù)服務(wù)器上。
2.5- Variables
2.5.1- 兩種創(chuàng)建Variable的方法:
# create variables with tf.Variable
s = tf.Variable(2, name="scalar") # with scalar value
m = tf.Variable([[0, 1], [2, 3]], name="matrix") # with list vlue
W = tf.Variable(tf.zeros([784,10])) # with tensor
# create variables with tf.get_variable
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())
兩種方法創(chuàng)建的都是tf.Variable對(duì)象,雖然tf.Variable()看起來(lái)更簡(jiǎn)潔,但這是一種老式的調(diào)用方法,并不推薦使用。tf.get_variable是對(duì)tf.Variable的包裝,可以更容易的共享。
為什么tf.constant是小寫(xiě)開(kāi)頭,而tf.Variable要大寫(xiě)開(kāi)頭?這是因?yàn)閠f.constant只是一個(gè)op,而tf.Variable是一個(gè)class,內(nèi)部包含了多個(gè)op:
x = tf.Variable(...)
x.initializer # init op
x.value() # read op
x.assign(...) # write op
x.assign_add(...)
# and more
通過(guò)TensorBoard可以看出,一個(gè)Variable是一個(gè)子圖:

2.5.2- Variable初始化
Variable在使用時(shí)必須初始化,否則會(huì)報(bào)錯(cuò):
# create variables with tf.get_variable
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())
with tf.Session() as sess:
print(sess.run(W)) >> FailedPreconditionError: Attempting to use uninitialized value Variable
最簡(jiǎn)單的方法是執(zhí)行tf.global_variables_initializer()這個(gè)op,它會(huì)對(duì)graph中的所有Variable初始化
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
也可以只初始化一部分變量tf.variables_initializer()
sess.run(tf.variables_initializer([a, b]))
或直接調(diào)用一個(gè)Variable自帶的initializer
W = tf.Variable(tf.zeros([784,10]))
with tf.Session() as sess:
sess.run(W.initializer)
還有一個(gè)輸出所有為初始化的Variable列表的技巧:
print(session.run(tf.report_uninitialized_variables()))
2.5.3- Eval() a variable
# W is a random 700 x 10 variable object
W = tf.Variable(tf.truncated_normal([700, 10]))
with tf.Session() as sess:
sess.run(W.initializer)
print(W.eval()) # Similar to print(sess.run(W))
t.eval() 只是run的一個(gè)快捷方式: tf.get_default_session().run(t).
2.5.4- tf.Variable.assign()
assgin相當(dāng)于對(duì)變量的賦值語(yǔ)句,需要注意assigin()也是一個(gè)op,因此要run或eval(),比如下面的assgin就沒(méi)有起作用:
W = tf.Variable(10)
W.assign(100)
with tf.Session() as sess:
sess.run(W.initializer)
print(W.eval()) # >> 10
應(yīng)該這樣:
W = tf.Variable(10)
assign_op = W.assign(100)
with tf.Session() as sess:
sess.run(W.initializer)
sess.run(assign_op)
print(W.eval()) # >> 100
另外,如果已經(jīng)有assign了,則Variable的initializer可以不用調(diào)用,initializer本質(zhì)上也是一個(gè)assign。
assign語(yǔ)句反復(fù)運(yùn)行,效果累加。
# create a variable whose original value is 2
my_var = tf.Variable(2, name="my_var")
# assign a * 2 to a and call that op a_times_two
my_var_times_two = my_var.assign(2 * my_var)
with tf.Session() as sess:
sess.run(my_var.initializer)
sess.run(my_var_times_two) # >> the value of my_var now is 4
sess.run(my_var_times_two) # >> the value of my_var now is 8
sess.run(my_var_times_two) # >> the value of my_var now is 16
2.5.5- assign_add() and assign_sub()
my_var = tf.Variable(10)
With tf.Session() as sess:
sess.run(my_var.initializer)
# increment by 10
sess.run(my_var.assign_add(10)) # >> 20
# decrement by 2
sess.run(my_var.assign_sub(2)) # >> 18
2.5.6- 每個(gè)session維護(hù)一份Variable的拷貝
可以看到在兩個(gè)session內(nèi),同一個(gè)Variable對(duì)象的當(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()
2.6- Session與InteractiveSession
有時(shí)你會(huì)看到InteractiveSession。與Session的唯一區(qū)別是,InteractiveSession創(chuàng)建后,會(huì)自動(dòng)設(shè)置為默認(rèn)session,相當(dāng)于執(zhí)行了session.as_default_session。因此在調(diào)用run()和eval()方法的時(shí)候不需要顯式的調(diào)用session。InteractiveSession在命令行環(huán)境或jupyer notebook上很常用。
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()
另外,在with tf.Sessoin as sess語(yǔ)句內(nèi)部,也相當(dāng)于直接設(shè)置了default session。
tf.get_default_session()方法可以用來(lái)獲取當(dāng)前線(xiàn)程的默認(rèn)session。
2.7- Importing Data
2.7.1- palceholder
之前我們談到,TF程序分為兩步:
- 組裝一個(gè)graph
- 使用session執(zhí)行g(shù)raph上的操作
組裝graph,并不需要知道要參與計(jì)算的具體值,這就像定義函數(shù)不需要知道函數(shù)的形參的具體值。
組裝完graph后,我們(或者我們的客戶(hù)端代碼)可以在他們將要執(zhí)行計(jì)算的時(shí)候提供它們自己的數(shù)據(jù)。
定義placeholder的方法:tf.placeholder(dtype, shape=None, name=None)。
其中shape可以是None,即不明確指定shape,根據(jù)最終輸入的數(shù)據(jù)決定。雖然如此,還是推薦盡可能的準(zhǔn)確定義shape,至少是一部分shape,比如shape=(None, 3)
這里有一個(gè)小小的疑問(wèn),Variable和placeholder有什么區(qū)別呢?
- Variable是變量,可以在graph里不斷的被修改,而placeholder不行。
- 在機(jī)器學(xué)習(xí)模型里,Variable通常是需要學(xué)習(xí)的權(quán)重,而placeholder通常是訓(xùn)練數(shù)據(jù)。
- Variable使用initializer初始化,而placeholder在run的時(shí)候通過(guò)fee_dict賦值。
- 活著說(shuō),Variable類(lèi)比于函數(shù)內(nèi)定義的變量,而placeholder相當(dāng)于函數(shù)方法簽名上的形參。
2.7.1- feed_dict
如果使用了placeholder,就要在運(yùn)行的時(shí)候傳入實(shí)際值,否則報(bào)錯(cuò):
tf.placeholder(dtype, shape=None, name=None)
# create a placeholder for a vector of 3 elements, type tf.float32
a = tf.placeholder(tf.float32, shape=[3])
b = tf.constant([5, 5, 5], tf.float32)
# use the placeholder as you would a constant or a variable
c = a + b # short for tf.add(a, b)
with tf.Session() as sess:
print(sess.run(c)) # >> InvalidArgumentError: a doesn’t an actual value
需要通過(guò)feed_dict參數(shù)對(duì)placeholder設(shè)值:
# create a placeholder for a vector of 3 elements, type tf.float32
a = tf.placeholder(tf.float32, shape=[3])
b = tf.constant([5, 5, 5], tf.float32)
# use the placeholder as you would a constant or a variable
c = a + b # short for tf.add(a, b)
with tf.Session() as sess:
print(sess.run(c, feed_dict={a: [1, 2, 3]})) # the tensor a is the key, not the string ‘a(chǎn)’
# >> [6, 7, 8]
特別注意feed_dict的key就是placeholder對(duì)象,而不是字符串。placeholder也是有效的ops,tf.placeholer返回的也是一個(gè)tf.Tensor對(duì)象。
2.7.2- feed多次數(shù)據(jù)
比如下面的操作,通過(guò)一個(gè)循環(huán),反復(fù)feed不同的數(shù)據(jù):
with tf.Session() as sess:
for a_value in list_of_values_for_a:
print(sess.run(c, {a: a_value}))
這種做法不僅正確,而且很常見(jiàn),機(jī)器學(xué)習(xí)算法中,定義一個(gè)訓(xùn)練op,然后不斷feed不同的訓(xùn)練數(shù)據(jù)進(jìn)行訓(xùn)練。雖然place_holder一直在傳入,但里面的參數(shù)通過(guò)Variable一直在迭代。
2.7.3- is_feedable
事實(shí)上,feed_dict不僅可以feed的是placeholder,還可以feed任何可feed的tensor! placeholder只是一種方法表示必須被feed。
或者我們可以通過(guò)is_feedable判斷是否可以被feed:
tf.Graph.is_feedable(tensor)
# True if and only if tensor is feedable.
這種操作在測(cè)試的時(shí)候特別有用,當(dāng)一個(gè)graph太大,我們只想測(cè)試圖的一個(gè)部分,就可以用這種方法提供虛假值,節(jié)省不必要的計(jì)算時(shí)間。
# create operations, tensors, etc (using the default graph)
a = tf.add(2, 5)
b = tf.multiply(a, 3)
with tf.Session() as sess:
# compute the value of b given a is 15
sess.run(b, feed_dict={a: 15}) # >> 45
2.7.4- tf.data
placeholder是一種簡(jiǎn)單、老舊的方式,更好的辦法是td.data,下一章我們會(huì)通過(guò)linear和logistic regression為例介紹。
2.8- lazy loading
lazy loading是一種常見(jiàn)的錯(cuò)誤。
比如下面的做法是正常的loading

下面這個(gè)做法是lazy loading

雖然兩段程序的運(yùn)行結(jié)果看似一樣,后者好像還省了一行代碼,但后者不斷的在循環(huán)內(nèi)創(chuàng)建了多個(gè)add節(jié)點(diǎn),造成graph的定義膨脹(想象一下循環(huán)的是100萬(wàn)次,則代價(jià)非常大)。
程序運(yùn)行后,我們查看兩個(gè)graph的定義,可以發(fā)現(xiàn),前者只有一個(gè)add節(jié)點(diǎn):
node {
name: "Add"
op: "Add"
input: "x/read"
input: "y/read"
attr {
key: "T"
value {
type: DT_INT32
}
}
}
后者會(huì)生成Add_1到Add_10一共10個(gè)add節(jié)點(diǎn):
node {
name: "Add_1"
op: "Add"
...
}
...
node {
name: "Add_10"
op: "Add"
...
}
我們應(yīng)該避免lazy loading,方法是:
- 將op的定義和運(yùn)行區(qū)分開(kāi)來(lái)。
- 使用Python的property保證function僅在第一次調(diào)用時(shí)加載。比如下面的做法:

lazy loading的更多內(nèi)容,可參考:https://danijar.com/structuring-your-tensorflow-models/