本篇筆記包含張量的合並與分割,范數統計,張量填充,限幅等操作。
1.合並與分割
合並
張量的合並可以使用拼接(Concatenate)和堆疊(Stack)操作實現,拼接並不會產生新的維度,而堆疊會創建新維度。選擇使用拼接還是堆疊操作來合並張量,取決於具體的場景是否需要創建新維度。
拼接 在 TensorFlow 中,可以通過 tf.concat(tensors, axis),其中 tensors 保存了所有需要合並的張量 List,axis 指定需要合並的維度。合並操作可以在任意的維度上進行,唯一的約束是非合並維度的長度必須一致。
a = tf.random.normal([4,35,8]) # 模擬成績冊 A
b = tf.random.normal([6,35,8]) # 模擬成績冊 B
tf.concat([a,b],axis=0) # 合並成績冊
Out[1]:
<tf.Tensor: id=13, shape=(10, 35, 8), dtype=float32, numpy=...>
堆疊 如果在合並數據時,希望創建一個新的維度,則需要使用 tf.stack 操作。使用 tf.stack(tensors, axis)可以合並多個張量 tensors,其中 axis 指定插入新維度的位置,axis 的用法與 tf.expand_dims 的一致,當axis ≥ 0時在 axis 之前插入;當axis < 0時,在 axis 之后插入新維度。axis 參數對應的插入位置設置如圖:

a = tf.random.normal([35,8])
b = tf.random.normal([35,8])
tf.stack([a,b],axis=0)
Out[4]:
<tf.Tensor: id=55, shape=(2, 35, 8), dtype=float32, numpy=...>
tf.stack 也需要滿足張量堆疊合並條件,它需要所有合並的張量 shape 完全一致才可合並。
分割
通過 tf.split(x, axis, num_or_size_splits)可以完成張量的分割操作,其中x代表待分割張量;axis代表分割的維度索引號;num_or_size_splits代表切割方案。當 num_or_size_splits 為單個數值時,如 10,表示切割為 10 份;當 num_or_size_splits 為 List 時,每個元素表示每份的長度,如[2,4,2,2]表示切割為 4 份,每份的長度分別為 2,4,2,2
x = tf.random.normal([10,35,8])
# 等長切割
result = tf.split(x,axis=0,num_or_size_splits=10)
result[0]
Out[9]: <tf.Tensor: id=136, shape=(1, 35, 8), dtype=float32, numpy=...>
切割后的shape 為[1,35,8],保留了維度,這一點需要注意。
特別地,如果希望在某個維度上全部按長度為 1 的方式分割,還可以直接使用 tf.unstack(x,axis)。這種方式是 tf.split 的一種特殊情況,切割長度固定為 1,只需要指定切割維度即可。
x = tf.random.normal([10,35,8])
result = tf.unstack(x,axis=0) # Unstack 為長度為 1
result[0]
Out[12]: <tf.Tensor: id=166, shape=(35, 8), dtype=float32, numpy=...>
可以看到,通過 tf.unstack 切割后,shape 變為[35,8],即班級維度消失了,這也是與 tf.split區別之處。
2.數據統計
在神經網絡的計算過程中,經常需要統計數據的各種屬性,如最大值,均值,范數等等。由於張量通常 shape 較大,直接觀察數據很難獲得有用信息,通過觀察這些張量統計信息可以較輕松地推測張量數值的分布。
向量范數
向量范數(Vector norm)是表征向量“長度”的一種度量方法,對於矩陣、張量,同樣可以利用向量范數的計算公式,等價於將矩陣、張量打平成向量后計算。在神經網絡中,常用來表示張量的權值大小,梯度大小等。常用的向量范數有:
- L1 范數,定義為向量𝒙的所有元素絕對值之和

- L2 范數,定義為向量𝒙的所有元素的平方和,再開根號

- ∞ −范數,定義為向量𝒙的所有元素絕對值的最大值

在 TensorFlow 中,可以通過 tf.norm(x, ord)求解張量的 L1, L2, ∞等范數,其中參數 ord指定為 1,2 時計算 L1, L2 范數,指定為 np.inf 時計算∞ −范數
最大最小值、均值、和
通過 tf.reduce_max, tf.reduce_min, tf.reduce_mean, tf.reduce_sum 可以求解張量在某個維度上的最大、最小、均值、和,也可以求全局最大、最小、均值、和信息。
tf.reduce_max(x,axis=1) # 統計概率維度上的最大值
當不指定 axis 參數時,tf.reduce_*函數會求解出全局元素的最大、最小、均值、和
tf.reduce_max(x)
out = tf.random.normal([4,10]) # 網絡預測輸出
y = tf.constant([1,2,2,0]) # 真實標簽
y = tf.one_hot(y,depth=10) # one-hot 編碼
loss = keras.losses.mse(y,out) # 計算每個樣本的誤差
loss = tf.reduce_mean(loss) # 平均誤差
loss
Out[20]:
<tf.Tensor: id=241, shape=(), dtype=float32, numpy=1.1921183>
與均值函數相似的是求和函數 tf.reduce_sum(x,axis),它可以求解張量在 axis 軸上所有特征的和
通過 tf.argmax(x, axis),tf.argmin(x, axis)可以求解在 axis 軸上,x 的最大值、最小值所在的索引號
3.張量比較
通過 tf.equal(a, b)(或 tf.math.equal(a, b))函數可以比較這 2個張量是否相等。tf.equal()函數返回布爾型的張量比較結果,只需要統計張量中 True 元素的個數,即可知道預測正確的個數。tf.cast(out, dtype=tf.float32) # 布爾型轉 int 型

4.填充與復制
填充
對於圖片數據的高和寬、序列信號的長度,維度長度可能各不相同。為了方便網絡的並行計算,需要將不同長度的數據擴張為相同長度,通常的做法是,在需要補充長度的信號開始或結束處填充足夠數量的特定數值,如 0,使得填充后的長度滿足系統要求。那么這種
操作就叫做填充(Padding)。
填充操作可以通過 tf.pad(x, paddings)函數實現,paddings 是包含了多個[Left Padding ,Right Padding]的嵌套方案 List,如 [ [ 0 , 0 ] , [ 2 , 1 ] , [ 1 , 2 ] ]表示第一個維度不填充,第二個維度左邊(起始處)填充兩個單元,右邊(結束處)填充一個單元,第三個維度左邊填充
一個單元,右邊填充兩個單元。
復制
通過 tf.tile 函數可以在任意維度將數據重復復制多份,如 shape 為[4,32,32,3]的數據,復制方案 multiples=[2,3,3,1],即通道數據不復制,高寬方向分別復制 2 份,圖片數再復1 份
5.數據限幅
在 TensorFlow 中,可以通過 tf.maximum(x, a)實現數據的下限幅:𝑦 ∈ [a,+∞);可以通過 tf.minimum(x, a)實現數據的上限幅:𝑦 ∈ (−∞,a]
ReLU 函數可以實現為:
def relu(x):
return tf.maximum(x,0.) # 下限幅為 0 即可(原書表述為return tf.minimum(x,0.)有誤 )
通過組合 tf.maximum(x, a)和 tf.minimum(x, b)可以實現同時對數據的上下邊界限幅:𝑦 ∈ [a,𝑏]:
tf.minimum(tf.maximum(x,a),b) # 限幅為 a~b
更方便地,我們可以使用 tf.clip_by_value 實現上下限幅,tf.clip_by_value(x,2,7) # 限幅為 2~7
6.高級操作
(1)tf.gather
tf.gather 可以實現根據索引號收集數據的目的。
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32)
tf.gather(x,[0,1],axis=0)
Out[38]:<tf.Tensor: id=83, shape=(2, 35, 8), dtype=int32, numpy=...>
tf.gather 非常適合索引沒有規則的場合,其中索引號可以亂序排列,此時收集的數據也是對應順序
(2)tf.gather_nd
通過 tf.gather_nd,可以通過指定每次采樣的坐標來實現采樣多個點的目的。一般地,在使用 tf.gather_nd 采樣多個樣本時,如果希望采樣第 i 號班級,第 j 個學生,第 k 門科目的成績,則可以表達為[...,[𝑗,𝑘,𝑙],...],外層的括號長度為采樣樣本的個數,內層列表包含了每個采樣點的索引坐標
(3)tf.boolean_mask
通過 tf.boolean_mask(x, mask, axis)可以在 axis 軸上根據 mask 方案進行采樣
tf.boolean_mask(x,mask=[True, False,False,True],axis=0)
tf.boolean_mask 既可以實現了 tf.gather 方式的一維掩碼采樣,又可以實現 tf.gather_nd 方式的多維掩碼采樣,與mask的維度有關
(4)tf.where
通過 tf.where(cond, a, b)操作可以根據 cond 條件的真假從 a 或 b 中讀取數據,其中 i 為張量的索引,返回張量大小與 a,b 張量一致,當對應位置中cond 𝑖 為 True,o 𝑖 位置從a 𝑖 中復制數據;當對應位置中cond 𝑖 為False,o 𝑖 位置從b 𝑖 中復制數據。
In [53]: a = tf.ones([3,3]) # 構造 a 為全 1 b = tf.zeros([3,3]) # 構造 b 為全 0 # 構造采樣條件 cond = tf.constant([[True,False,False],[False,True,False],[True,True,False]]) tf.where(cond,a,b) # 根據條件從 a,b 中采樣 Out[53]:<tf.Tensor: id=384, shape=(3, 3), dtype=float32, numpy= array([[1., 0., 0.], [0., 1., 0.], [1., 1., 0.]], dtype=float32)>
當 a=b=None 即 a,b 參數不指定時,tf.where 會返回 cond 張量中所有 True 的元素的索引坐標
In [55]:tf.where(cond) # 獲取 cond 中為 True 的元素索引 Out[55]:<tf.Tensor: id=387, shape=(4, 2), dtype=int64, numpy= array([[0, 0], [1, 1], [2, 0], [2, 1]], dtype=int64)>
(5)scatter_nd
通過 tf.scatter_nd(indices, updates, shape)可以高效地刷新張量的部分數據,但是只能在全 0 張量的白板上面刷新,因此可能需要結合其他操作來實現現有張量的數據刷新功能白板的形狀表示為 shape 參數,需要刷新的數據索引為 indices,新數據為 updates,其中每個需要刷新的數據對應在白板中的位置,根據 indices 給出的索引位置將 updates 中新的數據依次寫入白板中,並返回更新后的白板張量。
In [61]: # 構造需要刷新數據的位置 indices = tf.constant([[4], [3], [1], [7]]) # 構造需要寫入的數據 updates = tf.constant([4.4, 3.3, 1.1, 7.7]) # 在長度為 8 的全 0 向量上根據 indices 寫入 updates tf.scatter_nd(indices, updates, [8]) Out[61]:<tf.Tensor: id=467, shape=(8,), dtype=float32, numpy=array([0. , 1.1, 0. , 3.3, 4.4, 0. , 0. , 7.7], dtype=float32)>
(6)meshgrid
通過 tf.meshgrid 可以方便地生成二維網格采樣點坐標,方便可視化等應用場合。tf.meshgrid 會返回在 axis=2 維度切割后的 2 個張量 a,b,其中張量 a 包含了所有點的 x坐標,b 包含了所有點的 y 坐標,shape 都為[100,100]
x = tf.linspace(-8.,8,100) # 設置 x 坐標的間隔 y = tf.linspace(-8.,8,100) # 設置 y 坐標的間隔 x,y = tf.meshgrid(x,y) # 生成網格點,並拆分后返回
