TF入門02-TensorFlow Ops

本文的主要內容安排如下:

  • 基本的操作
  • 張量類型
  • 導入數據
  • lazy loading

我們首先介紹一下TensorBoard的使用,然後介紹TensorFlow的基本ops,之後介紹張量的數據類型,最後介紹一下如何將自己的輸入導入模型。

1. TensorBoard

TensorBoard是TensorFlow的一個可視化工具,可以用於對TensorFlow模型的調試和優化。TensorBoard的外觀大致如下:

當用戶在TensorBoard激活的TensorFlow程序中執行某些操作時,這些操作將導出到事件日誌文件中。 TensorBoard能夠將這些事件文件轉換爲可視化文件,從而可以深入瞭解模型的結構及其運行時的行爲。

讓我們從一個小例子中,看看TensorBoard如何使用。下面的代碼是不使用TensorBoard的計算a+b的例子:

import tensorflow as tf

a = tf.constant(2)
b = tf.constant(3)
x = tf.add(a, b)
with tf.Session() as sess:
    print(sess.run(x))

爲了使用TensorBoard對程序進行可視化,我們需要將模型寫入日誌文件中。而日誌文件的寫入,需要依賴於一個filewriter,其生命代碼如下:

writer = tf.summary.FileWriter([logdir], [graph])

[graph]指當前模型的運算圖。可以使用tf.get_default_graph()獲取模型的默認運算圖,或者使用sess.graph獲取當前會話處理的運算圖;後者要求sess會話已經被聲明。值得注意的是,FileWriter的聲明需要放在在運算圖定義完成之後,否則TensorBoard對模型的結構顯示不完整。

[logdir]表明日誌文件的存儲位置。可以將[logdir]命名爲’./graphs’或者’./graphs/lecture02’.

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) # if you prefer creating your writer using session's graph
    print(sess.run(x))
writer.close()

模型運行之後會將日誌文件寫入並存儲到指定位置。接下來,到命令行窗口,運行如下代碼:

$ python3 [my_program.py]
$ tensorboard --logdir="./graphs" --port 6006

如果運行報錯:OSError:[Errno 22] Invalid argument,解決方法爲:clickME

運行成果後,在瀏覽器中打開網址:http://localhost:6006就可以看到TensorBoard頁面了。你可以看到運算圖中有3個結點:2個constants和一個Add op。

image-20200523165359951

其中Const、Const_1分別對應結點a和結點b,Add對應x。我們也可以自己對結點命名:

a = tf.constant(2, name="a")
b = tf.constant(3, name="b")
x = tf.add(a, b, name="add")

再次運行打開TensorBoard後,我們可以得到:

image-20200523165606967

運算圖定義了ops以及它們的依賴關係。我們可以通過點擊結點來確定結點的值以及結點類型。

在瞭解TensorBoard之後,我們來看看TensorFlow中的各種op。

2. Constant op

TensorFlow中創建常量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")

你也可以創建特定維度、填充特定值的常量,具體操作和使用Numpy類似。

# 創建任意維度,內部元素全是0的constant常量
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]]
# 創建和input_tensor維度相同,全是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]]
# 創建任意維度,值全是1的常量
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]]
#創建和input_tensor維度相同,值全是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]]

和Numpy類似,TF也可以創建constants序列,創建方法如下:

tf.lin_space(start, stop, num, name=None)
# 生成start到stop的封閉線性空間,總的個數爲num;包含start和stop在內
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')
# 生成start到limit的子序列,包含start在內,不包含limit,delta控制步長

# 'start' is 3, 'limit' is 18, 'delta' is 3
tf.range(3, 18, 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]

特別注意的是,和Numpy對象不同,tensor是不可以迭代的(iterable),所以下列操作是非法的:

for _ in tf.linspace(0.0, 10.0, 4): # TypeError
for _ in tf.range(4): # TypeError

可以使用隨機方法生成常量constant,使用的函數如下:

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. Math op與數學運算相關的ops

TensorFlow中包含各種各樣的數學ops,如加法tf.add, tf.add_n等。

image-20200523210413730

TF常見ops如下:

image-20200523172030757

4. 數據類型

4.1 Python 原生類型

TF可以使用Python的原生數據類型,如Boolean、數值型(整數、浮點數)、字符串。單個值可以被轉換成0-d張量(標量)、列表可以被轉換爲1-d張量(向量)、二級列表可以被轉換爲2-d張量(矩陣)。

# 實數
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

4.2 TF 原生類型

TF包含的數據類型如下:

4.3 Numpy數據類型與TF類型

TF在設計時要求能無縫集成Numpy,TF的數據類型基於Numpy對應的類型。事實上、np.int32 == tf.int32結果爲真。我們可以將numpy類型傳送到TF ops中。

tf.ones([2,2], np.float32)

在TF中,numpy數組用於表示tensor的值。在tf.Session.run()中,如果fetches是tensor,返回值將是一個numpy數組。

a = tf.ones([2,3], np.int32)
sess = tf.Session()
print(a) ==> Tensor("ones:0", shape=(2, 3), dtype=int32)
print(type(a)) ==> <class 'tensorflow.python.framework.ops.Tensor'>
print(sess.run(a)) ==>[[1 1 1],[1 1 1]]
print(type(sess.run(a))) ==> <class 'numpy.ndarray'>

雖然,TF能支持Numpy數據類型,但我們建議儘可能地使用TF的數據類型,因爲:

  • 如果使用Numpy類型,必須引用numpy工具包
  • 最重要的是,Numpy不兼容GPU

5. Variable變量

常量和變量之間的區別:

  • 常量,顧名思義,是固定不變的。在模型訓練過程中,我們希望模型的權重參數能不斷優化,因此常量不適用於這種場景
  • 常量的值作爲graph定義的一部分被存儲和序列化,每次graph加載時,常量的值都需要複製一份;變量是分開存儲的,可能放在單獨的參數服務器上。

因爲常量的值將作爲graph定義的一部分被存儲和序列化,如果運算圖中常量過多,就會導致graph的加載成本加大。這一點,我們可以通過打印graph的定義來確定,具體可以通過as_graph_def()函數來實現:

my_const = tf.constant([1.0, 2.0], name="my_const")
with tf.Session()as sess:# 運算圖中只有一個常量my_const
    print(sess.graph.as_graph_def())

image-20200523181816188

5.1 Variable的兩種創建方法

tf.Variable()tf.get_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 value
# 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]]))

兩種方法都可以創建tf.Variable對象,我們推薦使用第二種方法tf.get_variable(),這種方法是對第一種方法的封裝,可以更容易地進行共享。

爲什麼tf.constant是小寫字母開頭,tf.Variable爲大寫字母呢?因爲tf.constant只是一個op,而tf.Variable是一個類,內部包含多個op:

x = tf.Variable(...)
x.initializer # init
x.value() # read op
x.assign(...) # write op
x.assign_add(...)
# and more

5.2 Variable初始化

Variable在使用之前必須先初始化。如果嘗試使用一個未初始化的Variable會提示FailedPreconditionError: Attempting to use uninitialized value. 我們可以獲取未被初始化的Variable列表:

print(sess.run(tf.report_uninitialized_variable()))

初始化所有Variables最簡單的方法是使用:

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

這裏,我們fetches是一個變量初始化op,而不是一個獲取tensor的op。

如果只想初始化一部分Variables,我們可以將variable變量送到tf.variables_initializer()來實現:

with tf.Session() as sess:
    # 只初始化a,b
    sess.run(tf.variables_initializer([a, b]))

我們也可以將各個Variable的初始化工作分離,每個變量使用tf.Variable.initializer來初始化:

with tf.Session() as sess:
    sess.run(a.initializer)

我們還可以從文件中加載值來完成Variable的初始化。

5.3 打印、輸出Variable的值

# V聲明
V = tf.get_variable("normal_matrix", shape=(784, 10),
 initializer=tf.truncated_normal_initializer())

with tf.Session() as sess:
    # V初始化
    sess.run(tf.global_variables_initializer())
    # V打印、輸出
    print(sess.run(V))

也可以使用tf.Variable.eval()來打印:

# W 聲明
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))

tf.Variable.eval() 只是run的一個快捷方式等價於tf.get_default_session().run(t).

5.4 爲變量賦值assign

可以使用tf.Variable.assign()對variable進行賦值。

W = tf.Variable(10)
W.assign(100) # op
with tf.Session() as sess:
    sess.run(W.initializer)
    print(W.eval()) # >> 10

值得注意的是**tf.Variable.assign()**是一個op,爲了讓賦值操作起效,我們需要在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函數會完成Variable的初始化。其實,變量的初始化op就是一個assign op,它將初始值賦值給變量對象.

# in the source code
self._initializer_op = state_ops.assign(self._variable, self._initial_value, validate_shape=validate_shape).op

如果assign op反覆調用,效果將會疊加。

a = tf.get_variable('scalar', initializer=tf.constant(2))
a_times_two = a.assign(a * 2)
with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    sess.run(a_times_two) # >> 4
    sess.run(a_times_two) # >> 8
    sess.run(a_times_two) # >> 16

如果只想簡單地對變量進行增減操作,可以使用tf.Variable.assign_add()tf.Variable.assign_sub()方法。和tf.Variable.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

TF中每個Session維持一份Variable,相互之間互不干擾。

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. Interactive Session

Interactive Session和Session的區別在於InteractiveSession創建後,會自動設置爲默認session,相當於執行了session.as_default_session。因此在調用run()eval()方法的時候不需要顯式的調用session。

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。

7. Importing Data 導入數據

7.1 placeholders和feed_dict

定義placeholders,可以使用tf.placeholder(dtype, shape=None, name=None).

其中,shape可以是None,表示不指定shape,shape具體根據輸入來確定。但是我們並不推薦這種方式,因爲shape的不確定,會給模型debug增加難度。

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

運行上述代碼會拋出異常:InvalidArgumentError: a doesn’t an actual value表示a的值不知道。因爲我們沒有對placeholder進行賦值。我們可以使用feed_dict來完成這項操作:feed_dict是一個字典,其中鍵爲placeholder的對象名字(不是字符串),值爲傳送的值。

a = tf.placeholder(tf.float32, shape=[3])
b = tf.constant([5, 5, 5], tf.float32)
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’
    # 輸出爲 >> [6, 7, 8]

placeholder也是有效的ops,tf.placeholer返回的也是一個tf.Tensor對象。

除了向placeholder進行值傳送,我們也可以向非placeholder的tensor對象進行feed。確定tensor是否是可feed的,我們可以使用下面函數來確定:

tf.Graph.is_feedable(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

這種操作在測試的時候特別有用.假如一個graph太大,我們只想測試圖的一個部分,就可以用這種方法提供值,節省不必要的計算時間。

7.2 tf.data

後續結合例子進行講解。

8. Lazy loading懶加載

TensorFlow的一個常見的non-bug bugs(不報異常的異常)就是懶加載。懶加載指的是直到加載對象時纔對它進行聲明/初始化的編程模式(推遲聲明和初始化)。在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()

使用lazy loadding方式的代碼:

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()

這種方式看起來更爲簡潔,減少了z的創建語句。讓我們看看兩種運算圖的定義有什麼不同,使用語句

print(tf.get_default_graph().as_graph_def())

前一種方法內部只有一個add結點,後者會產生Add_1到Add_10共10個結點。如果循環10萬次,會導致graph定義的極速膨脹。

我們應該避免使用Lazy Loading,方法是:

  • 將op的定義和運行分別開來。
  • 使用Python的property保證function僅在第一次調用時加載

9. References

CS 20: Tensorflow for Deep Learning Research Operations


歡迎加入我的公衆號,一起學習成長

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章