TensorFlow 與 MXNet、PyTorch 很相似,上手速度很快(相比于 TensorFlow1.x)。
TensorFlow 拆開來看,就是 Tensor
與 Flow
,即數(shù)據(jù)流程圖,也是有向無環(huán)圖(DAG)。在 TensorFlow 中以 Tensor
(張量)表示數(shù)據(jù),即 DAG 的(實線)邊,而虛線的邊用來表示流程的控制關系。DAG 的節(jié)點表示數(shù)學操作符(即數(shù)學運算)。
1 張量 tf.Tensor
張量是由 tf.Tensor
對象表示的具有統(tǒng)一類型(稱為 dtype
)的多維數(shù)組。您可以在 tf.dtypes.DType
中查看所有支持的 dtypes
。就像 Python 數(shù)值和字符串一樣,所有張量都是不可變的:永遠無法更新張量的內(nèi)容,只能創(chuàng)建新的張量。
編寫 TensorFlow 程序時,主要操縱和傳遞的對象是 tf.Tensor
。tf.Tensor
具有以下屬性:
- 單一數(shù)據(jù)類型 (比如,float32,int32或字符串)
- a shape
TensorFlow 支持 eager 執(zhí)行和 graph 執(zhí)行。eager 執(zhí)行時,將立即執(zhí)行運算。在 graph 中執(zhí)行時,構造一個計算圖為以后運算。TensorFlow 默認 eager 執(zhí)行。在以下示例中,立即計算矩陣乘法結果。其中 tf.constant
是張量的一種。
# 使用 Tensor 計算一些值
c = tf.constant([[1.0, 2.0], [3.0, 4.0]]) # 常量
d = tf.constant([[1.0, 1.0], [0.0, 1.0]])
e = tf.matmul(c, d) # 矩陣乘法
print(e)
輸出:
tf.Tensor(
[[1. 3.]
[3. 7.]], shape=(2, 2), dtype=float32)
請注意,在 eager 執(zhí)行過程中,您可能會發(fā)現(xiàn)您的 Tensors
實際上是類型為“EagerTensor”。這是內(nèi)部細節(jié),但確實可以給您訪問有用的函數(shù) numpy()
:
在 TensorFlow 中,tf.function
是定義 graph 執(zhí)行的常用方法。張量的 shape
(即張量的 rank 和每個維度的大小)可能并不總是完全已知的。在 tf.function
定義中,shape 可能僅是部分已知的。
如果也可以完全知道其輸入的形狀,則大多數(shù)操作都會生成形狀已知的張量,但是在某些情況下,只有在執(zhí)行時才能找到張量的形狀。
有許多專用的張量:參見 tf.constant
, tf.sparse.SparseTensor
, 和 tf.RaggedTensor
。
1.1 常量 tf.constant
def constant(value, dtype=None, shape=None, name="Const"):
從 tensor-like 的對象創(chuàng)建恒定張量,即常量。
注意:所有 eager 的 tf.Tensor
值都是不可變的(與 tf.Variable
相反)。從 tf.constant
返回的值沒有特別常數(shù)。此功能與 tf.convert_to_tensor
基本上沒有區(qū)別。名稱 tf.constant
來自符號API(例如 tf.data
或 tf.keras
functional models),其中 value
嵌入在 tf.Graph
的 Const
節(jié)點中。tf.constant
可用于斷言該值可以通過這種方式嵌入。
我們來創(chuàng)建一些基本張量。
下面是一個“標量”(或稱“0 秩”張量)。標量包含單個值,但沒有“軸”。
rank_0_tensor = tf.constant(4)
print(rank_0_tensor)
輸出:
tf.Tensor(4, shape=(), dtype=int32)
“向量”(或稱“1 秩”張量)就像一個值的列表。向量有 1 個軸:
rank_1_tensor = tf.constant([2.0, 3.0, 4.0])
print(rank_1_tensor)
輸出:
tf.Tensor([2. 3. 4.], shape=(3,), dtype=float32)
“矩陣”(或稱“2 秩”張量)有 2 個軸:
rank_2_tensor = tf.constant([[1, 2],
[3, 4],
[5, 6]], dtype=tf.float16)
print(rank_2_tensor)
輸出:
tf.Tensor(
[[1. 2.]
[3. 4.]
[5. 6.]], shape=(3, 2), dtype=float16)
張量的軸可能更多,下面是一個包含 3 個軸的張量:
rank_3_tensor = tf.constant([
[[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]],
[[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19]],
[[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]],])
print(rank_3_tensor)
輸出:
tf.Tensor(
[[[ 0 1 2 3 4]
[ 5 6 7 8 9]]
[[10 11 12 13 14]
[15 16 17 18 19]]
[[20 21 22 23 24]
[25 26 27 28 29]]], shape=(3, 2, 5), dtype=int32)
對于包含 2 個以上的軸的張量,您可以通過多種方式將其可視化。
通過使用 np.array
或 tensor.numpy
方法,您可以將張量轉換為 NumPy 數(shù)組:
張量通常包含浮點型和整型數(shù)據(jù),但是還有許多其他數(shù)據(jù)類型,包括:
- 復雜的數(shù)值
- 字符串
tf.Tensor
基類要求張量是“矩形”——也就是說,每個軸上的每一個元素大小相同。但是,張量有可以處理不同形狀的特殊類型。
- 不規(guī)則張量(參閱 RaggedTensor)
- 稀疏張量(參閱 SparseTensor)
上面并沒有指定參數(shù) dtype
,而從 value
的類型中自動推斷出類型。
>>> # Constant 1-D Tensor from a python list.
>>> tf.constant([1, 2, 3, 4, 5, 6])
<tf.Tensor: shape=(6,), dtype=int32,
numpy=array([1, 2, 3, 4, 5, 6], dtype=int32)>
>>> # Or a numpy array
>>> a = np.array([[1, 2, 3], [4, 5, 6]])
>>> tf.constant(a)
<tf.Tensor: shape=(2, 3), dtype=int64, numpy=
array([[1, 2, 3],
[4, 5, 6]])>
如果指定了 dtype
,則將結果張量值強制轉換為請求的 dtype
。
>>> tf.constant([1, 2, 3, 4, 5, 6], dtype=tf.float64)
<tf.Tensor: shape=(6,), dtype=float64,
numpy=array([1., 2., 3., 4., 5., 6.])>
如果設置了 shape
,則將 value
調整為匹配的形狀。標量被擴展以填充 shape
:
>>> tf.constant(0, shape=(2, 3))
<tf.Tensor: shape=(2, 3), dtype=int32, numpy=
array([[0, 0, 0],
[0, 0, 0]], dtype=int32)>
>>> tf.constant([1, 2, 3, 4, 5, 6], shape=[2, 3])
<tf.Tensor: shape=(2, 3), dtype=int32, numpy=
array([[1, 2, 3],
[4, 5, 6]], dtype=int32)>
如果將 eager 張量作為 value
傳遞,tf.constant
無效,它甚至會傳輸梯度:
v = tf.Variable([0.0])
with tf.GradientTape() as g:
loss = tf.constant(v + v)
g.gradient(loss, v).numpy()
輸出:
array([2.], dtype=float32)
但是,由于 tf.constant
將值嵌入到 tf.Graph
中,因此對于符號張量(symbolic tensors)而言失敗:
Related Ops:
-
tf.convert_to_tensor
與tf.constant
類似,而不同于:- 無
shape
參數(shù) - Symbolic tensors 被允許傳遞
- 無
>>> i = tf.keras.layers.Input(shape=[None, None])
>>> t = tf.convert_to_tensor(i)
1.2 張量的簡單運算
我們可以對張量執(zhí)行基本數(shù)學運算,包括加法、逐元素乘法和矩陣乘法運算。
a = tf.constant([[1, 2],
[3, 4]])
b = tf.constant([[1, 1],
[1, 1]]) # Could have also said `tf.ones([2,2])`
tf.print(tf.add(a, b), "\n") # 矩陣加法
tf.print(tf.multiply(a, b), "\n") # 逐元素乘法
tf.print(tf.matmul(a, b), "\n") # 矩陣乘法
輸出:
[[2 3]
[4 5]]
[[1 2]
[3 4]]
[[3 3]
[7 7]]
其中,tf.print
用于打印信息。
TensorFlow 也重載了運算符:
tf.print(a + b, "\n") # element-wise addition
tf.print(a * b, "\n") # element-wise multiplication
tf.print(a @ b, "\n") # matrix multiplication```
輸出:
[[2 3]
[4 5]]
[[1 2]
[3 4]]
[[3 3]
[7 7]]
張量支持其他各種運算 (op) 。
c = tf.constant([[4.0, 5.0], [10.0, 1.0]])
# Find the largest value
tf.print(tf.reduce_max(c), '\n')
# Find the index of the largest value
tf.print(tf.argmax(c), '\n')
# Compute the softmax
tf.print(tf.nn.softmax(c), '\n')
輸出:
10
[1 0]
[[0.268941432 0.731058598]
[0.999876618 0.00012339458]]
1.3 形狀簡介
張量有形狀。下面是幾個相關術語:
- 形狀:張量的每個維度的長度(元素數(shù)量)。
- 秩:張量的維度數(shù)量。標量的秩為 0,向量的秩為 1,矩陣的秩為 2。
軸或維度:張量的一個特殊維度。 - 大小:張量的總項數(shù),即乘積形狀向量
注:雖然您可能會看到“二維張量”之類的表述,但 2 秩張量通常并不是用來描述二維空間。
張量和 tf.TensorShape
對象提供了方便的屬性來訪問:
雖然通常用索引來指代軸,但是您始終要記住每個軸的含義。軸一般按照從全局到局部的順序進行排序:首先是批次軸,隨后是空間維度,最后是每個位置的特征。這樣,在內(nèi)存中,特征向量就會位于連續(xù)的區(qū)域。
tf.size(x).numpy()
與 len(x)
等效。
1.4 操作形狀
通過重構可以改變張量的形狀。重構的速度很快,資源消耗很低,因為不需要復制底層數(shù)據(jù)。
x = tf.constant([[1], [2], [3]])
reshaped = tf.reshape(x, [1, 3])
print(x.shape)
print(reshaped.shape)
輸出:
(3, 1)
(1, 3)
數(shù)據(jù)在內(nèi)存中的布局保持不變,同時使用請求的形狀創(chuàng)建一個指向同一數(shù)據(jù)的新張量。TensorFlow 采用 C 樣式的“行優(yōu)先”內(nèi)存訪問順序,即最右側的索引值遞增對應于內(nèi)存中的單步位移。
如果您展平張量,則可以看到它在內(nèi)存中的排列順序。
一般來說,tf.reshape
唯一合理的用途是用于合并或拆分相鄰軸(或添加/移除 1
)。
對于 3x2x5 張量,重構為 (3x2)x5 或 3x(2x5) 都合理,因為切片不會混淆:
重構可以處理總元素個數(shù)相同的任何新形狀,但是如果不遵從軸的順序,則不會發(fā)揮任何作用。
利用 tf.reshape
無法實現(xiàn)軸的交換,要交換軸,您需要使用 tf.transpose
。
您可能會遇到非完全指定的形狀。要么是形狀包含 None
維度(維度的長度未知),要么是形狀為 None
(張量的秩未知)。
除了 tf.RaggedTensor 外,這種情況只會在 TensorFlow 的符號化計算圖構建 API 環(huán)境中出現(xiàn):
1.5 DTypes 詳解
使用 Tensor.dtype
屬性可以檢查 tf.Tensor
的數(shù)據(jù)類型。
從 Python 對象創(chuàng)建 tf.Tensor
時,您可以選擇指定數(shù)據(jù)類型。
如果不指定,TensorFlow 會選擇一個可以表示您的數(shù)據(jù)的數(shù)據(jù)類型。TensorFlow 將 Python 整數(shù)轉換為 tf.int32
,將 Python 浮點數(shù)轉換為 tf.float32
。另外,當轉換為數(shù)組時,TensorFlow 會采用與 NumPy 相同的規(guī)則。
數(shù)據(jù)類型可以相互轉換。
1.6 廣播
廣播是從 NumPy 中的等效功能借用的一個概念。簡而言之,在一定條件下,對一組張量執(zhí)行組合運算時,為了適應大張量,會對小張量進行“擴展”。
最簡單和最常見的例子是嘗試將張量與標量相乘或相加。在這種情況下會對標量進行廣播,使其變成與其他參數(shù)相同的形狀。
同樣,可以擴展大小為 1 的維度,使其符合其他參數(shù)。在同一個計算中可以同時擴展兩個參數(shù)。
在本例中,一個 3x1 的矩陣與一個 1x4 進行元素級乘法運算,從而產(chǎn)生一個 3x4 的矩陣。注意前導 1 是可選的:y 的形狀是 [4]。
下面是不使用廣播的同一運算:
在大多數(shù)情況下,廣播的時間和空間效率更高,因為廣播運算不會在內(nèi)存中具體化擴展的張量。
使用 tf.broadcast_to
可以了解廣播的運算方式。
與數(shù)學運算不同,比方說,broadcast_to
并不會節(jié)省內(nèi)存。在這個例子中,張量已經(jīng)具體化。
這可能會變得更復雜。Jake VanderPlas 的《Python 數(shù)據(jù)科學手冊》一書中的這一節(jié)介紹了更多廣播技巧(同樣使用 NumPy)。
1.7 tf.convert_to_tensor
大部分運算(如 tf.matmul
和 tf.reshape
)會使用 tf.Tensor
類的參數(shù)。不過,在上面的示例中,您會發(fā)現(xiàn)我們經(jīng)常傳遞形狀類似于張量的 Python 對象。
大部分(但并非全部)運算會在非張量參數(shù)上調用 convert_to_tensor
。我們提供了一個轉換注冊表,大多數(shù)對象類(如 NumPy 的 ndarray
、TensorShape
、Python 列表和 tf.Variable
)都可以自動轉換。
有關更多詳細信息,請參閱 tf.register_tensor_conversion_function
。如果您有自己的類型,則可能希望自動轉換為張量。
1.8 不規(guī)則張量
如果張量的某個軸上的元素個數(shù)可變,則稱為“不規(guī)則”張量。對于不規(guī)則數(shù)據(jù),請使用 tf.ragged.RaggedTensor
。
例如,下面的例子無法用規(guī)則張量表示:
應使用 tf.ragged.constant
來創(chuàng)建 tf.RaggedTensor
:
tf.RaggedTensor
的形狀包含未知維度:
1.9 字符串張量
tf.string
是一種 dtype
,也就是說,在張量中,我們可以用字符串(可變長度字節(jié)數(shù)組)來表示數(shù)據(jù)。
字符串是原子類型,無法像 Python 字符串一樣編制索引。字符串的長度并不是張量的一個維度。有關操作字符串的函數(shù),請參閱 tf.strings
。
下面是一個標量字符串張量:
下面是一個字符串向量:
在上面的打印輸出中,b
前綴表示 tf.string
dtype 不是 Unicode 字符串,而是字節(jié)字符串。有關在 TensorFlow 如何使用 Unicode 文本的詳細信息,請參閱 Unicode 教程。
如果傳遞 Unicode 字符,則會使用 utf-8 編碼。
在 tf.strings
中可以找到用于操作字符串的一些基本函數(shù),包括 tf.strings.split
。
以及 tf.string.to_number
:
雖然不能使用 tf.cast
將字符串張量轉換為數(shù)值,但是可以先將其轉換為字節(jié),然后轉換為數(shù)值。
tf.string
dtype 可用于 TensorFlow 中的所有原始字節(jié)數(shù)據(jù)。tf.io
模塊包含在數(shù)據(jù)與字節(jié)類型之間進行相互轉換的函數(shù),包括解碼圖像和解析 csv 的函數(shù)。
1.10 稀疏張量
在某些情況下,數(shù)據(jù)很稀疏,比如說在一個非常寬的嵌入空間中。為了高效存儲稀疏數(shù)據(jù),TensorFlow 支持 tf.sparse.SparseTensor
和相關運算。
2 變量
TensorFlow 變量是用于表示程序處理的共享持久狀態(tài)的推薦方法。本指南介紹在 TensorFlow 中如何創(chuàng)建、更新和管理 tf.Variable
的實例。
變量通過 tf.Variable
類進行創(chuàng)建和跟蹤。tf.Variable
表示張量,對它執(zhí)行運算可以改變其值。利用特定運算可以讀取和修改此張量的值。更高級的庫(如 tf.keras
)使用 tf.Variable
來存儲模型參數(shù)。
2.1 設置
本筆記本討論變量布局。如果要查看變量位于哪一個設備上,請取消注釋這一行代碼。
import tensorflow as tf
# Uncomment to see where your variables get placed (see below)
# tf.debugging.set_log_device_placement(True)
2.2 創(chuàng)建變量
要創(chuàng)建變量,請?zhí)峁┮粋€初始值。tf.Variable
與初始值的 dtype
相同。
my_tensor = tf.constant([[1.0, 2.0], [3.0, 4.0]])
my_variable = tf.Variable(my_tensor)
# Variables can be all kinds of types, just like tensors
bool_variable = tf.Variable([False, False, False, True])
complex_variable = tf.Variable([5 + 4j, 6 + 1j])
變量與張量的定義方式和操作行為都十分相似,實際上,它們都是 tf.Tensor
支持的一種數(shù)據(jù)結構。與張量類似,變量也有 dtype
和形狀,并且可以導出至 NumPy。
大部分張量運算在變量上也可以按預期運行,不過變量無法重構形狀。
如上所述,變量由張量提供支持。您可以使用 tf.Variable.assign
重新分配張量。調用 assign
(通常)不會分配新張量,而會重用現(xiàn)有張量的內(nèi)存。
如果在運算中像使用張量一樣使用變量,那么通常會對支持張量執(zhí)行運算。
從現(xiàn)有變量創(chuàng)建新變量會復制支持張量。兩個變量不能共享同一內(nèi)存空間。
2.3 生命周期、命名和監(jiān)視
在基于 Python 的 TensorFlow 中,tf.Variable
實例與其他 Python 對象的生命周期相同。如果沒有對變量的引用,則會自動將其解除分配。
為了便于跟蹤和調試,您還可以為變量命名。兩個變量可以使用相同的名稱。
保存和加載模型時會保留變量名。默認情況下,模型中的變量會自動獲得唯一變量名,所以除非您希望自行命名,否則不必多此一舉。
雖然變量對微分很重要,但某些變量不需要進行微分。在創(chuàng)建時,通過將 trainable 設置為 False 可以關閉梯度。例如,訓練計步器就是一個不需要梯度的變量。
step_counter = tf.Variable(1, trainable=False)
2.4 放置變量和張量
為了提高性能,TensorFlow 會嘗試將張量和變量放在與其 dtype
兼容的最快設備上。這意味著如果有 GPU,那么大部分變量都會放置在 GPU 上。
不過,我們可以重寫變量的位置。在以下代碼段中,即使存在可用的 GPU,我們也可以將一個浮點張量和一個變量放置在 CPU 上。通過打開設備分配日志記錄(參閱設置),可以查看變量的所在位置。
注:雖然可以手動放置變量,但使用分布策略是一種可優(yōu)化計算的更便捷且可擴展的方式。
如果在有 GPU 和沒有 GPU 的不同后端上運行此筆記本,則會看到不同的記錄。請注意,必須在會話開始時打開設備布局記錄。
您可以將變量或張量的位置設置在一個設備上,然后在另一個設備上執(zhí)行計算。但這樣會產(chǎn)生延遲,因為需要在兩個設備之間復制數(shù)據(jù)。
不過,如果您有多個 GPU 工作進程,但希望變量只有一個副本,則可以這樣做。
注:由于 tf.config.set_soft_device_placement
默認處于打開狀態(tài),所以,即使在沒有 GPU 的設備上運行此代碼,它也會運行,只不過乘法步驟在 CPU 上執(zhí)行。