tensorflow 之 張量與變量

TensorFlow 與 MXNet、PyTorch 很相似,上手速度很快(相比于 TensorFlow1.x)。

TensorFlow 拆開來看,就是 TensorFlow,即數(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.Tensortf.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.datatf.keras functional models),其中 value 嵌入在 tf.GraphConst 節(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.arraytensor.numpy 方法,您可以將張量轉換為 NumPy 數(shù)組:

張量通常包含浮點型和整型數(shù)據(jù),但是還有許多其他數(shù)據(jù)類型,包括:

  • 復雜的數(shù)值
  • 字符串

tf.Tensor 基類要求張量是“矩形”——也就是說,每個軸上的每一個元素大小相同。但是,張量有可以處理不同形狀的特殊類型。

上面并沒有指定參數(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_tensortf.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.matmultf.reshape)會使用 tf.Tensor 類的參數(shù)。不過,在上面的示例中,您會發(fā)現(xiàn)我們經(jīng)常傳遞形狀類似于張量的 Python 對象。

大部分(但并非全部)運算會在非張量參數(shù)上調用 convert_to_tensor。我們提供了一個轉換注冊表,大多數(shù)對象類(如 NumPy 的 ndarrayTensorShape、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í)行。

有關分布式訓練的詳細信息,請參閱指南。要了解變量的一般使用方法,請參閱關于自動微分的指南。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374