Tensorflow深度學習-第三天

爲了區分需要計算梯度信息的張量與不需要計算梯度信息的張量,TensorFlow 增加了 一種專門的數據類型來支持梯度信息的記錄:tf.Variable。tf.Variable 類型在普通的張量類 型基礎上添加了 name,trainable 等屬性來支持計算圖的構建。由於梯度運算會消耗大量的 計算資源,而且會自動更新相關參數,對於不需要的優化的張量,如神經網絡的輸入𝑿, 不需要通過 tf.Variable 封裝;相反,對於需要計算梯度並優化的張量,如神經網絡層的𝑾 和𝒃,需要通過 tf.Variable 包裹以便 TensorFlow 跟蹤相關梯度信息。

import tensorflow as tf
a = tf.constant([-1, 0, 1, 2]) # 創建 TF 張量 
aa = tf.Variable(a) # 轉換爲 Variable 類型 
aa.name, aa.trainable # Variable類型張量的屬性
('Variable:0', True)

aa
<tf.Variable 'Variable:0' shape=(4,) dtype=int32, numpy=array([-1,  0,  1,  2], dtype=int32)>

a
<tf.Tensor: id=0, shape=(4,), dtype=int32, numpy=array([-1,  0,  1,  2], dtype=int32)>

其中張量的 name 和 trainable 屬性是 Variable 特有的屬性,name 屬性用於命名計算圖中的 變量,這套命名體系是 TensorFlow 內部維護的,一般不需要用戶關注 name 屬性;trainable 屬性表徵當前張量是否需要被優化,創建 Variable 對象時是默認啓用優化標誌,可以設置 trainable=False 來設置張量不需要優化。

 

正態分佈(Normal Distribution,或 Gaussian Distribution)和均勻分佈(Uniform Distribution)是最常見的分佈之一,創建採樣自這 2 種分佈的張量非常有用,比如在卷積神 經網絡中,卷積核張量𝑾初始化爲正態分佈有利於網絡的訓練;在對抗生成網絡中,隱藏 變量𝒛一般採樣自均勻分佈。

通過 tf.random.normal(shape, mean=0.0, stddev=1.0)可以創建形狀爲 shape,均值爲 mean,標準差爲 stddev 的正態分佈𝒩(mean, stddev2)。例如,創建均值爲 0,標準差爲 1 的正態分佈:

tf.random.normal([2,2])
tf.random.normal([2,2], mean=1,stddev=2)

考慮自然語言處理(Natural Language Processing,簡稱 NLP)中句子的表示,如評價句 子的是否爲正面情緒的情感分類任務網絡,如圖 4.3 所示。爲了能夠方便字符串被神經網 絡處理,一般將單詞通過嵌入層(Embedding Layer)編碼爲固定長度的向量,比如“a”編碼 爲某個長度 3 的向量,那麼 2 個等長(單詞數量爲 5)的句子序列可以表示爲 shape 爲[2,5,3] 的 3 維張量,其中 2 表示句子個數,5 表示單詞數量,3 表示單詞向量的長度。

from tensorflow.keras import datasets, layers, optimizers, Sequential, metrics
# 創建32x32的彩色圖片輸入,個數爲4
x = tf.random.normal([4,32,32,3])
# 創建卷積神經網絡
layer = layers.Conv2D(16,kernel_size=3) 
out = layer(x) # 前向計算
out.shape # 輸出大小Out[48]: TensorShape([4, 30, 30, 16])
#其中卷積核張量也是 4 維張量,可以通過 kernel 成員變量訪問:
#layer.kernel.shape # 訪問卷積核張量
TensorShape([4, 30, 30, 16])

layer.kernel.shape # 訪問卷積核張量
TensorShape([3, 3, 3, 16])

索引與切片:

在 TensorFlow 中,支持基本的[𝑖][𝑗] ⋯標準索引方式,也支持通過逗號分隔索引號的索 引方式。考慮輸入𝑿爲 4 張32 × 32大小的彩色圖片(爲了方便演示,大部分張量都使用隨機 分佈模擬產生,後文同),shape 爲[4,32,32,3],

 

通過start: end: step切片方式可以方便地提取一段數據,其中 start 爲開始讀取位置的索引,end 爲結束讀取位置的索引(不包含 end 位),step 爲採樣步長。

張量的索引與切片方式多種多樣,尤其是切片操作,初學者容易犯迷糊。但本質上切 片操作只有start: end: step這一種基本形式,通過這種基本形式有目的地省略掉默認參數, 從而衍生出多種簡寫方法,這也是很好理解的。它衍生的簡寫形式熟練後一看就能推測出 省略掉的信息,書寫起來也更方便快捷。由於深度學習一般處理的維度數在四維以內,⋯ 操作符完全可以用:符號代替,因此理解了這些就會發現張量切片操作並不複雜。

x=tf.range(96)
<tf.Tensor: id=95, shape=(96,), dtype=int32, numpy=
array([ 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, 30, 31, 32, 33,
       34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
       51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
       68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
       85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95], dtype=int32)>
x=tf.reshape(x,[2,4,4,3])
<tf.Tensor: id=97, shape=(2, 4, 4, 3), dtype=int32, numpy=
array([[[[ 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],
         [30, 31, 32],
         [33, 34, 35]],

        [[36, 37, 38],
         [39, 40, 41],
         [42, 43, 44],
         [45, 46, 47]]],


       [[[48, 49, 50],
         [51, 52, 53],
         [54, 55, 56],
         [57, 58, 59]],

        [[60, 61, 62],
         [63, 64, 65],
         [66, 67, 68],
         [69, 70, 71]],

        [[72, 73, 74],
         [75, 76, 77],
         [78, 79, 80],
         [81, 82, 83]],

        [[84, 85, 86],
         [87, 88, 89],
         [90, 91, 92],
         [93, 94, 95]]]], dtype=int32)>

數據在創建時按着初始的維度順序寫入,改變張量的視圖僅僅是改變了張量的理解方 式,並不需要改變張量的存儲順序,這在一定程度上是從計算效率考慮的,大量數據的寫 入操作會消耗較多的計算資源。由於存儲時數據只有平坦結構,與數據的邏輯結構是分離 的,因此如果新的邏輯結構不需要改變數據的存儲方式,就可以節省大量計算資源,這也 是改變視圖操作的優勢。改變視圖操作在提供便捷性的同時,也會帶來很多邏輯隱患,這 主要的原因是改變視圖操作的默認前提是存儲不需要改變,否則改變視圖操作就是非法 的。

例如,張量𝑨按着初始視圖[𝑏, h, , 𝑐]寫入的內存佈局,我們改變𝑨的理解方式,它可以 有如下多種合法的理解方式:

❑ [𝑏, h ∙ , 𝑐] 張量理解爲𝑏張圖片,h ∙ 個像素點,𝑐個通道
❑ [𝑏, h, ∙ 𝑐] 張量理解爲𝑏張圖片,h行,每行的特徵長度爲 ∙ 𝑐

 ❑ [𝑏, h ∙ ∙ 𝑐] 張量理解爲𝑏張圖片,每張圖片的特徵長度爲h ∙ ∙ 𝑐 上述新視圖的存儲都不需要改變,因此是合法的。

從語法上來說,視圖變換隻需要滿足新視圖的元素總量與存儲區域大小相等即可,即 新視圖的元素數量等於

𝑏∙h∙w ∙𝑐

改變視圖是神經網絡中非常常見的操作,可以通過串聯多個 reshape 操作來實現複雜 邏輯,但是在通過 reshape 改變視圖時,必須始終記住張量的存儲順序,新視圖的維度順 序不能與存儲順序相悖,否則需要通過交換維度操作將存儲順序同步過來。舉個例子,對 於 shape 爲[4,32,32,3]的圖片數據,通過 reshape 操作將 shape 調整爲[4,1024,3],此時視圖 的維度順序爲𝑏 − pixel − 𝑐,張量的存儲順序爲[𝑏, h, , 𝑐]。可以將[4,1024,3]恢復爲

 

[𝑏, h, w, 𝑐] = [4,32,32,3]時,新視圖的維度順序與存儲順序無衝突,可以恢復出無邏輯 問題的數據。

[𝑏, w, h, 𝑐] = [4,32,32,3]時,新視圖的維度順序與存儲順序衝突。

[h ∙w ∙ 𝑐, 𝑏] = [3072,4]時,新視圖的維度順序與存儲順序衝突。

通過上述的一系列連續變換視圖操作時需要意識到,張量的存儲順序始終沒有改變,數據 在內存中仍然是按着初始寫入的順序0,1,2, ⋯ ,95保存的。

改變視圖、增刪維度都不會影響張量的存儲。在實現算法邏輯時,在保持維度順序不 變的條件下,僅僅改變張量的理解方式是不夠的,有時需要直接調整的存儲順序,即交換 維度(Transpose)。通過交換維度操作,改變了張量的存儲順序,同時也改變了張量的視 圖。

交換維度操作是非常常見的,比如在 TensorFlow 中,圖片張量的默認存儲格式是通道 後行格式:[𝑏, h, , 𝑐],但是部分庫的圖片格式是通道先行格式:[𝑏, 𝑐, h, ],因此需要完成 [𝑏, h, , 𝑐]到[𝑏, 𝑐, h, ]維度交換運算,此時若簡單的使用改變視圖函數 reshape,則新視圖 的存儲方式需要改變,因此使用改變視圖函數是不合法的。我們以[𝑏, h, , 𝑐]轉換到
[𝑏, 𝑐, h, ]爲例,介紹如何使用 tf.transpose(x, perm)函數完成維度交換操作,其中參數 perm 表示新維度的順序 List。考慮圖片張量 shape 爲[2,32,32,3],“圖片數量、行、列、通道 數”的維度索引分別爲 0、1、2、3,如果需要交換爲[𝑏, 𝑐, h, ]格式,則新維度的排序爲 “圖片數量、通道數、行、列”,對應的索引號爲[0,3,1,2],因此參數 perm 需設置爲 [0,3,1,2],實現如下:

 

x = tf.random.normal([2,32,32,3])
tf.transpose(x,perm=[0,3,1,2])
<tf.Tensor: id=105, shape=(2, 3, 32, 32), dtype=float32, numpy=
array([[[[-0.39762518,  1.4674902 ,  0.3429396 , ..., -0.18593988,
          -0.9753849 , -1.3450396 ],
         [ 0.9279257 , -1.9623818 ,  0.7048407 , ..., -0.8269033 ,
...

到現在爲止,我們已經介紹瞭如何創建張量、對張量進行索引切片、維度變換和常見

的數學運算等操作。最後我們將利用已經學到的知識去完成三層神經網絡的實現:

out = 𝑅𝑒𝐿𝑈{𝑅𝑒𝐿𝑈{𝑅𝑒𝐿𝑈[𝑿@𝑾1 + 𝒃1]@𝑾2 + 𝒃2}@𝑾 + 𝒃 }

我們採用的數據集是 MNIST 手寫數字圖片集,輸入節點數爲 784,第一層的輸出節點數是256,第二層的輸出節點數是 128,第三層的輸出節點是 10,也就是當前樣本屬於 10 類別的概率。

合併與切割:

a = tf.random.normal([4,35,8]) # 模擬成績冊A 
b = tf.random.normal([6,35,8]) # 模擬成績冊B
c = tf.concat([a,b],axis=0) # 拼接合併成績冊

<tf.Tensor: id=134, shape=(10, 35, 8), dtype=float32, numpy=
array([[[ 0.06554277,  0.70229685,  0.05765281, ...,  0.16478623,
         -1.4000126 ,  1.8207668 ],
        [-2.2485626 , -0.27521434, -0.3096695 , ...,  1.1652282 ,
          0.5162945 ,  0.54561096],
        [ 1.0548744 ,  1.1472257 ,  0.24377841, ...,  0.7914138 ,
...

從語法上來說,拼接合並操作可以在任意的維度上進行,唯一的約束是非合併維度的 長度必須一致。

堆疊

a = tf.random.normal([4,35,8]) # 模擬成績冊A 
b = tf.random.normal([4,35,8]) # 模擬成績冊B
c = tf.stack([a,b],axis=0) # 拼接合併成績冊
<tf.Tensor: id=161, shape=(2, 4, 35, 8), dtype=float32, numpy=
...

 

合併操作的逆過程就是分割,將一個張量分拆爲多個張量。繼續考慮成績冊的例子, 我們得到整個學校的成績冊張量,shape 爲[10,35,8],現在需要將數據在班級維度切割爲 10 個張量,每個張量保存了對應班級的成績冊數據。

通過 tf.split(x, num_or_size_splits, axis)可以完成張量的分割操作,參數意義如下:

  • ❑  x參數:待分割張量。

  • ❑  num_or_size_splits參數:切割方案。當num_or_size_splits爲單個數值時,如10,表 示等長切割爲 10 份;當 num_or_size_splits 爲 List 時,List 的每個元素表示每份的長 度,如[2,4,2,2]表示切割爲 4 份,每份的長度依次是 2、4、2、2。

  • ❑  axis參數:指定分割的維度索引號。

x = tf.random.normal([10,35,8])
result = tf.split(x, num_or_size_splits=10, axis=0)
len(result)
10
<tf.Tensor: id=170, shape=(1, 35, 8), dtype=float32, numpy=
...


x = tf.random.normal([10,35,8])
result = tf.split(x, num_or_size_splits=[3,3,3,1], axis=0)
len(result)
4
result[1]
<tf.Tensor: id=189, shape=(3, 35, 8), dtype=float32, numpy=
...

等分切割的時候,num_or_size_splits必須可以背整除。

向量範數:

向量範數(Vector Norm)是表徵向量“長度”的一種度量方法,它可以推廣到張量上。

在神經網絡中,常用來表示張量的權值大小,梯度大小等。常用的向量範數有:

 ❑ L1範數,定義爲向量𝒙的所有元素絕對值之和

\left \| x \right \|_{1} = \sum _{I}\left | x_{i} \right |

\left \| x \right \|_{1} = \sum _{I}\left | x_{i} \right |

❑ L2範數,定義爲向量𝒙的所有元素的平方和,再開根號

\left \| x \right \|_{2} = \sqrt{\sum _{i}\left | x_{i} \right |^{2}}

\left \| x \right \|_{2} = \sqrt{\sum _{i}\left | x_{i} \right |^{2}}

❑ ∞−範數,定義爲向量𝒙的所有元素絕對值的最大值: 

\left \| x \right \|_{\infty} = max_{i}(\left | x_{i} \right |)

\left \| x \right \|_{\infty} = max_{i}(\left | x_{i} \right |)

對於矩陣和張量,同樣可以利用向量範數的計算公式,等價於將矩陣和張量打平成向量後 計算。

x = tf.ones([2,2])
tf.norm(x,ord=1)
<tf.Tensor: id=224, shape=(), dtype=float32, numpy=4.0>
tf.norm(x,ord=2)
<tf.Tensor: id=229, shape=(), dtype=float32, numpy=2.0>
import numpy as np
tf.norm(x,ord=np.inf) # 計算∞範數
<tf.Tensor: id=233, shape=(), dtype=float32, numpy=1.0>

通過 tf.reduce_max、tf.reduce_min、tf.reduce_mean、tf.reduce_sum 函數可以求解張量在某個維度上的最大、最小、均值、和,也可以求全局最大、最小、均值、和信息。

 

fc = layers.Dense(512, activation=tf.nn.relu)

代碼即可以創建一層全連接層 fc,並指定輸出節點數爲 512,輸入的節點數 在fc(x)計算時自動獲取,並創建內部權值張量𝑾和偏置張量𝒃。我們可以通過類內部的成 員名 kernel 和 bias 來獲取權值張量𝑾和偏置張量𝒃對象:

fc.kernel

實際上,網絡層除了保存了待優化張量列表 trainable_variables,還有部分層包含了不 參與梯度優化的張量,如後續介紹的 Batch Normalization 層,可以通過 non_trainable_variables 成員返回所有不需要優化的參數列表。

在設計全連接網絡時,網絡的結構配置等超參數可以按着經驗法則自由設置,只需要 遵循少量的約束即可。例如,隱藏層 1 的輸入節點數需和數據的實際特徵長度匹配,每層 的輸入層節點數與上一層輸出節點數匹配,輸出層的激活函數和節點數需要根據任務的具 體設定進行設計。總的來說,神經網絡模型的結構設計自由度較大,如圖 6.5 層中每層的 輸出節點數不一定要設計爲[256,128,64,10],可以自由搭配,如[256,256,64,10]或 [512,64,32,10]等都是可行的。至於與哪一組超參數是最優的,這需要很多的領域經驗知識 和大量的實驗嘗試,或者可以通過 AutoML 技術搜索出較優設定。

 

總有事情,今天又雙叒先到這裏。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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