嗶哩嗶哩有帶讀https://www.bilibili.com/video/BV1AJ411i7US/?spm_id_from=333.788.videocard.2
第二章
最優化損失函數
量
連續值預測問題是非常常見的,比如股價的走勢預測、天氣預報中溫 度和濕度等的預測、年齡的預測、交通流量的預測等。對於預測值是連續的實數范圍,或 者屬於某一段連續的實數區間,我們把這種問題稱為回歸(Regression)問題。
第三章
分類
x = 2*tf.convert_to_tensor(x, dtype=tf.float32)/255.-1 # 轉換為浮點張量,並縮放到 -1~1
【把[0,255]像素范圍 歸一化(Normalize)到[0,1. ]區間,再縮放到[−1,1]區間】
y = tf.convert_to_tensor(y, dtype=tf.int32) # 轉換為整形張量
y = tf.one_hot(y, depth=10) # one-hot編碼
train_dataset = tf.data.Dataset.from_tensor_slices((x, y)) # 構建數據集對象
train_dataset = train_dataset.batch(512) # 批量訓練
對於分類問題的誤差計算來說,更常見的是采用交叉熵(Cross Entropy)損失函數,較少采用 回歸問題中介紹的均方差損失函數。
可以通過重復堆疊多次變換來增加其表達能力
# 創建一層網絡,設置輸出節點數為 256,激活函數類型為 ReLU
layers.Dense(256, activation='relu')
第4章 TensorFlow 基礎
aa = tf.constant(1.2) # TF方式創建標量
x.numpy() # 將TF張量的數據導出為numpy數組格式
a = tf.constant('Hello, Deep Learning.') # 創建字符串
a = tf.constant(True) # 創建布爾類型標量 ⚠️TensorFlow 的布爾類型和 Python 語言的布爾類型並不等價,不能通 用
# 創建指定精度的張量 tf.constant(123456789, dtype=tf.int16)
tf.cast(a, tf.double) # 轉換為高精度張量
由於梯度運算會消耗大量的 計算資源,而且會自動更新相關參數,對於不需要的優化的張量,如神經網絡的輸入𝑿, 不需要通過 tf.Variable 封裝;相反,對於需要計算梯度並優化的張量,如神經網絡層的𝑾 和𝒃,需要通過 tf.Variable 包裹以便 TensorFlow 跟蹤相關梯度信息。
aa = tf.Variable(a) # 轉換為 Variable 類型
tf.zeros([]),tf.ones([]) # 創建全0,全1的標量
【線性變換𝒚 = 𝑾𝒙 + 𝒃,將權值矩陣𝑾初始化為全 1 矩陣,偏置 b 初始化為全 0 向量】
tf.zeros_like(a) # 創建一個與a形狀相同,但是全0的新矩陣
tf.ones_like(a) # 創建一個與a形狀相同,但是全1的新矩陣
tf.fill(shape, value)可以創建全為自定義數值 value 的張量,形狀由 shape 參數指定
tf.fill([2,2], 99) # 創建2行2列,元素全為99的矩陣
tf.random.normal([2,2], mean=1,stddev=2) # 創建正態分布的張量
tf.random.uniform([2,2]) # 創建采樣自[0,1)均勻分布的矩陣 Out[35]:
tf.random.uniform([2,2],maxval=10) # 創建采樣自[0,10)均勻分布的矩陣
tf.random.uniform([2,2],maxval=100,dtype=tf.int32)# 創建采樣自[0,100)均勻分布的整型矩陣
tf.range(1,10,delta=2) # 1~10通過 tf.range(start, limit, delta=1)可以創建[start, limit),步長為 delta 的序列,不包含 limit 本身
標量最容易理解,它就是一個簡單的數字,維度數為 0,shape 為 []。標量的一些典型用途是誤差值的表示、各種測量指標的表示,比如准確度(Accuracy, 簡稱 acc),精度(Precision)和召回率(Recall)等。
向量是一種非常常見的數據載體,如在全連接層和卷積神經網絡層中,偏置張量𝒃就 使用向量來表示。
矩陣也是非常常見的張量類型,比如全連接層的批量輸入張量𝑿的形狀為[𝑏, 𝑑in],其 中𝑏表示輸入樣本的個數,即 Batch Size,𝑑in表示輸入特征的長度。
x = tf.random.normal([2,4]) # 2個樣本,特征長度為4的張量
fc.kernel # 查看權值矩陣W
三維張量
三維的張量一個典型應用是表示序列信號,它的格式是𝑿 = [𝑏, sequence len, feature len]
𝑏表示序列信號的數量
sequence len 表示序列信號在時間維度上的采樣點數或步數,
feature len 表示每個點的特征長度。
embedding=layers.Embedding(10000, 100) 通過 layers.Embedding 層將數字編碼 的單詞轉換為長度為 100 個詞向量
out = embedding(x_train) # 將數字編碼的單詞轉換為詞向量
x_train 張量的 shape 為[25000,80],
句子out張量的 shape 變為[25000,80,100],其中 100 表 示每個單詞編碼為長度是 100 的向量。
四維張量在卷積神經網絡中應用非常廣泛,它用於保存特征圖(Feature maps)數據,格式一般定義為[𝑏, h, w, 𝑐]
其中𝑏表示輸入樣本的數量,h/ 分別表示特征圖的高/寬,𝑐表示特征圖的通道數,部分深 度學習框架也會使用[𝑏, 𝑐, h,w ]格式的特征圖張量,例如 PyTorch。
layer = layers.Conv2D(16,kernel_size=3) # 創建卷積神經網絡
out = layer(x) # 前向計算
索引
x = tf.random.normal([4,32,32,3]) # 創建4D張量,輸入𝑿為 4 張32 × 32大小的彩色圖片
x[0] # 程序中的第一的索引號應為0,容易混淆
x[0][1] 取第1張圖片的第2行
x[0][1][2] 取第1張圖片,第2行,第3列的數據
x[2][1][0][1] 取第3張圖片,第2行,第1列的像素,B通道(第2個通道)顏色強度值
x[1,9,2] 取第2張圖片,第10行,第3列的數據
切片
通過start: end: step切片方式可以方便地提取一段數據,其中 start 為開始讀取位置的索引,end 為結束讀取位置的索引(不包含 end 位),step 為采樣步長。
x[1:3] 讀取第 2,3 張圖片
全部省略時即為::,表示從最開始讀取到最末尾,步長為 1,即不跳過任何 元素。如 x[0,::]表示讀取第 1 張圖片的所有行,其中::表示在行維度上讀取所有行,它等價 於 x[0]的寫法
x[0,::] # 讀取第一張圖片
x[ : , 0:28:2 , 0:28:2 , : ] 表示讀取所有圖片、隔行采樣、隔列采樣,、讀取所有通道數據,相當於在圖片的高寬上各 縮放至原來的 50%。
x[8:0:-1] # 從8取到0,逆序,不包含0
【當step = −1時,start: end: −1表 示從 start 開始,逆序讀取至 end 結束(不包含 end),索引號𝑒𝑛𝑑 ≤ 𝑠𝑡𝑎𝑟𝑡。考慮一個 0~9 的 簡單序列向量,逆序取到第 1 號元素,不包含第 1 號】
x[::-1] # 逆序全部元素
x[::-2]# 逆序間隔采樣
x[0, ::-2, ::-2] # 行、列逆序間隔采樣【第一張,行,列】讀取每張圖片的所有通道,其中行按着逆序隔行采樣,列按着逆序隔行采樣
x[:,:,:,1] # 取G通道數據
當切片方式出現⋯符號時,⋯符號左邊 的維度將自動對齊到最左邊,⋯符號右邊的維度將自動對齊到最右邊,此時系統再自動推 斷⋯符號代表的維度數量
x[0:2,...,1:] # 高寬維度全部采集 讀取第1~2張圖片的G/B通道數據
x[2:,...] # 高、寬、通道維度全部采集,等價於x[2:] ⚠️讀取最后2張圖片(此處這樣寫,這是因為一共就4張圖片)
x[...,:2] # 所有樣本,所有高、寬的前2個通道
【本質上切 片操作只有start: end: step這一種基本形式】
維度變換
當現有的數據格式不滿足算 法要求時,需要通過維度變換將數據調整為正確的格式。這就是維度變換的功能。
基本的維度變換操作函數包含了改變視圖 reshape、插入新維度 expand_dims,刪除維 度 squeeze、交換維度 transpose、復制數據 tile 等函數。
x=tf.range(96) # 生成向量
x=tf.reshape(x,[2,4,4,3]) # 改變x的視圖,獲得4D張量,存儲並未改變
x.ndim # 獲取張量的維度數
x.shape # 獲取張量的形狀列表
tf.reshape(x,[2,-1]) 參數−1表示當前軸上長度需要根據張量總元素不變的法則自動推導,從而方便用戶 書寫
tf.reshape(x,[2,4,12])
tf.reshape(x,[2,-1,3]) 存儲順序始終沒有改變
x = tf.random.uniform([28,28],maxval=10,dtype=tf.int32)
x = tf.expand_dims(x,axis=2) # axis=2 表示寬維度后面的一個維度
x = tf.expand_dims(x,axis=0) # 高維度之前插入新維度
tf.expand_dims 的 axis 為正時,表示在當前維度之前插入一個新維度;為 負時,表示當前維度之后插入一個新的維度。
刪除維度
是增加維度的逆操作,與增加維度一樣,刪除維度只能刪除長度為 1 的維 度,也不會改變張量的存儲。
x = tf.squeeze(x, axis=0) # 刪除圖片數量維度
⚠️如果不指定維度參數 axis,即 tf.squeeze(x),那么它會默認刪除所有長度為 1 的維度
建議使用 tf.squeeze()時逐一指定需要刪除的維度參數,防止 TensorFlow 意外刪除
交換 維度(Transpose)。
通過交換維度操作,改變了張量的存儲順序,同時也改變了張量的視 圖。
使用 tf.transpose(x, perm)函數完成維度交換操作,其中參數 perm 表示新維度的順序 List。
x = tf.random.normal([2,32,32,3])
tf.transpose(x,perm=[0,2,1,3]) # 交換維度
維度交換后,張量的存儲順序已經改變,視圖也 隨之改變,后續的所有操作必須基於新的存續順序和視圖進行。相對於改變視圖操作,維 度交換操作的計算代價更高。
復制數據
tf.tile(x, multiples)函數完成數據在指定維度上的復制操作,multiples 分別指 定了每個維度上面的復制倍數,對應位置為 1 表明不復制,為 2 表明新長度為原來長度的 2 倍,即數據復制一份,以此類推。
b = tf.tile(b, multiples=[2,1]) # 樣本維度上復制一份 ⚠️[2,1],其中2表示在 axis=0 維度復制 1 次,1表示在 axis=1 維度不復制。 可以這樣理解[維度0,維度1]和[復制為當前的兩倍,復制為當前一倍(即不復制])
x = tf.tile(x,multiples=[1,2]) # 列維度復制一份
x = tf.tile(x,multiples=[2,1]) # 行維度復制一份
【可以這么看,先看list里面,每一個元素的位置代表着a xi s的位置,數字代表倍數,1則不變】
Broadcasting 稱為廣播機制
和 tf.tile 復 制的最終效果是一樣的
節省了大量計算資源, 建議在運算過程中盡可能地利用 Broadcasting 機制提高計算效率。
先將張量 shape 靠右對齊,然后進行普適性判斷:對於長度為 1 的維 度,默認這個數據普遍適合於當前維度的其他位置;對於不存在的維度,則在增加新維度 后默認當前數據也是普適於新維度的,從而可以擴展為更多維度數、任意長度的張量形 狀。
tf.broadcast_to(A, [2,32,32,3]) # 擴展為4D張量
加、減、乘、除
是最基本的數學運算,分別通過 tf.add, tf.subtract, tf.multiply, tf.divide 函數實現,TensorFlow 已經重載了+、 − 、 ∗ 、/運算符,一般推薦直接使用運算符來完成 加、減、乘、除運算。
整除和余除也是常見的運算之一,分別通過//和%運算符實現。
a//b # 整除運算
a%b # 余除運算
tf.pow(x,3) # 乘方運算
x**2 # 乘方運算符
x**(0.5) # 平方根
對於常見的平方和平方根運算,可以使用 tf.square(x)和 tf.sqrt(x)
x = tf.square(x) # 平方
tf.sqrt(x) # 平方根
通過 tf.pow(a, x)或者**運算符也可以方便地實現指數運算𝑎的𝑥次方
2**x # 指數運算
tf.exp(1.) # 自然指數運算(自然指數e𝑥)
自然對數loge X可以通過 tf.math.log(x)實現
tf.math.log(x) # 對數運算
tf.math.log(x)/tf.math.log(10.) # 換底公式
矩陣相乘運算
通過@運算符可以方便的實現矩陣相乘,還可以通過 tf.matmul(a, b)函數實現。
TensorFlow 中的 矩陣相乘可以使用批量方式,也就是張量𝑨和𝑩的維度數可以大於 2。
當張量𝑨和𝑩維度數大 於 2 時,TensorFlow 會選擇𝑨和𝑩的最后兩個維度進行矩陣相乘,前面所有的維度都視作 Batch 維度。【⚠️❓前面所有的維度都視作 Batch 維度】
𝑨和𝑩能夠矩陣相乘的條件是,𝑨的倒數第一個維度長度(列)和𝑩 的倒數第二個維度長度(行)必須相等。
比如張量 a shape:[4,3,28,32]可以與張量 b shape:[4,3,32,2]進行矩陣相乘 【⚠️注意這里就是看32這個數字的位置上必須相同】
到現在為止,我們已經介紹了如何創建張量、對張量進行索引切片、維度變換和常見 的數學運算等操作。最后我們將利用已經學到的知識去完成三層神經網絡的實現: out = 𝑅𝑒𝐿𝑈{𝑅𝑒𝐿𝑈{𝑅𝑒𝐿𝑈[𝑿@𝑾1 + 𝒃1]@𝑾2 + 𝒃2}@𝑾 + 𝒃 } 我們采用的數據集是 MNIST 手寫數字圖片集,輸入節點數為 784,第一層的輸出節點數是256,第二層的輸出節點數是 128,第三層的輸出節點是 10,也就是當前樣本屬於 10 類別 的概率。 首先創建每個非線性層的𝑾和𝒃張量參數,代碼如下: # 每層的張量都需要被優化,故使用Variable類型,並使用截斷的正太分布初始化權值張量 # 偏置向量初始化為0即可 # 第一層的參數 w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1)) b1 = tf.Variable(tf.zeros([256])) # 第二層的參數 w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1)) b2 = tf.Variable(tf.zeros([128])) # 第三層的參數 w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev=0.1)) b3 = tf.Variable(tf.zeros([10])) 在前向計算時,首先將 shape 為[𝑏, 28,28]的輸入張量的視圖調整為[𝑏, 784],即將每個 圖片的矩陣數據調整為向量特征,這樣才適合於網絡的輸入格式: 接下來完成第一個層的計算,我們這里顯示地進行自動擴展操作: 用同樣的方法完成第二個和第三個非線性函數層的前向計算,輸出層可以不使用 ReLU 激 活函數: 將真實的標注張量𝒚轉變為 One-hot 編碼,並計算與 out 的均方差,代碼如下: # 改變視圖,[b, 28, 28] => [b, 28*28] x = tf.reshape(x, [-1, 28*28]) # 第一層計算,[b, 784]@[784, 256] + [256] => [b, 256] + [256] => [b, 256] + [b, 256] h1 = x@w1 + tf.broadcast_to(b1, [x.shape[0], 256]) h1 = tf.nn.relu(h1) # 通過激活函數 # 第二層計算,[b, 256] => [b, 128] h2 = h1@w2 + b2 h2 = tf.nn.relu(h2) # 輸出層計算,[b, 128] => [b, 10] out = h2@w3 + b3 # 計算網絡輸出與標簽之間的均方差,mse = mean(sum(y-out)^2) # [b, 10] loss = tf.square(y_onehot - out) # 誤差標量,mean: scalar loss = tf.reduce_mean(loss) 上述的前向計算過程都需要包裹在 with tf.GradientTape() as tape 上下文中,使得前向計算時 能夠保存計算圖信息,方便自動求導操作。 通過 tape.gradient()函數求得網絡參數到梯度信息,結果保存在 grads 列表變量中,實 現如下: # 自動梯度,需要求梯度的張量有[w1, b1, w2, b2, w3, b3] grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3]) 並按照 來更新網絡參數: 𝜃′ = 𝜃 − 𝜂 ∙(𝜕L/𝜕𝜃) # 梯度更新,assign_sub 將當前值減去參數值,原地更新 w1.assign_sub(lr * grads[0]) b1.assign_sub(lr * grads[1]) w2.assign_sub(lr * grads[2]) b2.assign_sub(lr * grads[3]) w3.assign_sub(lr * grads[4]) b3.assign_sub(lr * grads[5]) 其中 assign_sub()將自身減去給定的參數值,實現參數的原地(In-place)更新操作。
第5章 TensorFlow 進階
合並
合並是指將多個張量在某個維度上合並為一個張量。
張量的合並可以使用拼接(Concatenate)和堆疊(Stack)操作實現
拼接
拼接操作並不會產生新 的維度,僅在現有的維度上合並,而堆疊會創建新維度。
通過 tf.concat(tensors, axis)函數拼接張量,其中參數 tensors 保存了所有需要合並的張量 List,axis 參數指定需要合並的維度索引。
tf.concat([a,b],axis=0) # 拼接合並成績冊
拼接合並操作可以在任意的維度上進行,唯一的約束是非合並維度的 長度必須一致。【⚠️僅允許合並的兩個向量只有一個維度是不一樣的】
堆疊
如果在合並數據 時,希望創建一個新的維度,則需要使用 tf.stack 操作。【⚠️這里會根據你的設定多出來一個維度】
用 tf.stack(tensors, axis)可以堆疊方式合並多個張量,通過 tensors 列表表示,參數 axis 指定新維度插入的位置,axis 的用法與 tf.expand_dims 的一致,當axis ≥ 0時,在 axis 之前插入;當axis < 0時,在 axis 之后插入新維度。
a = tf.random.normal([35,8])
b = tf.random.normal([35,8])
tf.stack([a,b],axis=0) # 堆疊合並為2個班級,班級維度插入在最前【a,b是兩個向量】
tf.stack([a,b],axis=-1) # 在末尾插入班級維度
tf.stack 也需要滿足張量堆疊合並條件,它需要所有待合並的張量 shape 完全一致才可 合並。
分割
合並操作的逆過程就是分割,將一個張量分拆為多個張量。
通過 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參數:指定分割的維度索引號。 現在我們將總成績冊張量切割為 10 份,代碼如下
result = tf.split(x, num_or_size_splits=10, axis=0)# 等長切割為10份
result = tf.split(x, num_or_size_splits=[4,2,2,2] ,axis=0) # 自定義長度的切割,切割為4份,返回4個張量的列表result
如果希望在某個維度上全部按長度為 1 的方式分割,還可以使用 tf.unstack(x, axis)函數。這種方式是 tf.split 的一種特殊情況,切割長度固定為 1,只需要指定切割維度 的索引號即可。
result = tf.unstack(x,axis=0) # Unstack為長度為1的張量【⚠️指定切割的那個維度會消失】
向量范數
向量范數(Vector Norm)是表征向量“長度”的一種度量方法,它可以推廣到張量上。
在神經網絡中,常用來表示張量的權值大小,梯度大小等。
對於矩陣和張量,同樣可以利用向量范數的計算公式,等價於將矩陣和張量打平成向量后 計算。
在 TensorFlow 中,可以通過 tf.norm(x, ord)求解張量的 L1、L2、∞等范數,其中參數 ord 指定為 1、2 時計算 L1、L2 范數,指定為 np.inf 時計算∞ −范數
tf.norm(x,ord=1) # 計算L1范數
tf.norm(x,ord=2) # 計算L2范數
tf.norm(x,ord=np.inf) # 計算∞范數
最值、均值、和
通過 tf.reduce_max、tf.reduce_min、tf.reduce_mean、tf.reduce_sum 函數可以求解張量
在某個維度上的最大、最小、均值、和,也可以求全局最大、最小、均值、和信息。
tf.reduce_max(x,axis=1) # 統計概率維度上的最大值
tf.reduce_min(x,axis=1) # 統計概率維度上的最小值
tf.reduce_mean(x,axis=1) # 統計概率維度上的均值
當不指定 axis 參數時,tf.reduce_*函數會求解出全局元素的最大、最小、均值、和等 數據
tf.reduce_max(x),tf.reduce_min(x),tf.reduce_mean(x) # 統計全局的最大、最小、均值、和,返回的張量均為標量
計算樣本的平均誤差,此時可以通過 tf.reduce_mean 在樣本數維度上計算均值
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) # 平均誤差,在樣本數維度上取均值
求和函數 tf.reduce_sum(x, axis),它可以求解張量在 axis 軸上所有 特征的和
out = tf.random.normal([4,10])
tf.reduce_sum(out,axis=-1) # 求最后一個維度的和
通過 tf.argmax(x, axis)和 tf.argmin(x, axis)可以求解在 axis 軸上,x 的最大值、最小值所 在的索引號
pred = tf.argmax(out, axis=1) # 選取概率最大的位置 【out是最后的預測輸出值,在axis=1是預測的那個分類的概率在第二列上面】
張量比較
通過 tf.equal(a, b)(或 tf.math.equal(a, b),兩者等價)函數可以比較這 2 個張量是否相等
out = tf.equal(pred,y) # 預測值與真實值比較,返回布爾類型的張量
先將布爾類型轉換為整形張量,即 True 對應 為 1,False 對應為 0,再求和其中 1 的個數,就可以得到比較結果中 True 元素的個數
out = tf.cast(out, dtype=tf.float32) # 布爾型轉int型
correct = tf.reduce_sum(out) # 統計True的個數

重復復制數據會破壞原有的數據結構,並不適合於此處。通常的做 法是,在需要補充長度的數據開始或結束處填充足夠數量的特定數值,這些特定數值一般 代表了無效意義,例如 0,使得填充后的長度滿足系統要求。
通過 tf.pad(x, paddings)函數實現,參數 paddings 是包含了多個
[Left Padding, Right Padding]的嵌套方案 List,
如[[0,0], [2,1], [1,2]]表示第一個維度不填 充,第二個維度左邊(起始處)填充兩個單元,右邊(結束處)填充一個單元,第三個維度左邊 填充一個單元,右邊填充兩個單元。
b = tf.pad(b, [[0,2]]) # 句子末尾填充 2 個 0
x_train = keras.preprocessing.sequence.pad_sequences(x_train,maxlen=max_review_len 【最大句子長度】,truncating='post',padding='post')
x = tf.random.normal([4,28,28,1])
tf.pad(x,[[0,0],[2,2],[2,2],[0,0]]) # 圖片上下、左右各填充 2 個單元【⚠️這里是4不填充;28的前后填充兩個零;28前后填充兩個零;1不填充,第一個是28行,第二個是28列,所以總的效果就是四周填上兩圈零】
復制
tf.tile 函數除了可以對長度為 1 的維度進行復制若干份,還可以對任意長度的維度進行復制 若干份,進行復制時會根據原來的數據次序重復復制。
如 shape 為[4,32,32,3]的數據, 復制方案為 multiples=[2,3,3,1],即通道數據不復制,高和寬方向分別復制 2 份,圖片數再 復制 1 份【這里的2331講的是倍數,2就是變成原來的倆倍,1就相當於不變,3就是變成原來的三遍】
x = tf.random.normal([4,32,32,3])
tf.tile(x,[2,3,3,1]) # 數據復制
數據限幅
考慮怎么實現非線性激活函數 ReLU 的問題。它其實可以通過簡單的數據限幅運算實現,限制元素的范圍𝑥 ∈ [0, +∞)即可
通過 tf.maximum(x, a)實現數據的下限幅,即𝑥 ∈ [𝑎, +∞);可
以通過 tf.minimum(x, a)實現數據的上限幅,即𝑥 ∈ (−∞, 𝑎]
tf.maximum(x,2) # 下限幅到 2
tf.minimum(x,7) # 上限幅到 7
tf.minimum(tf.maximum(x,2),7) # 限幅為 2~7
tf.clip_by_value(x,2,7) # 限幅為 2~7
tf.gather
tf.gather 可以實現根據索引號收集數據的目的。
x = tf.random.uniform([4,35,8],maxval=100,dtype=tf.int32) # 成績冊張量
tf.gather(x,[0,1],axis=0) # 在班級維度收集第 1~2 號班級成績冊【現在需要收集第 1~2 個班級的成績冊,可以給定需要收集班級的索引號:[0,1],並指定班 級的維度 axis=0】
tf.gather(x,[0,3,8,11,12,26],axis=1)# 收集第 1,4,9,12,13,27 號同學成績
tf.gather(x,[2,4],axis=2) # 第 3,5 科目的成績
抽查第[2,3]班級的第[3,4,6,27]號同學的科目 成績,則可以通過組合多個 tf.gather 實現
students=tf.gather(x,[1,2],axis=0) # 收集第 2,3 號班級
tf.gather(students,[2,3,5,26],axis=1) # 收集第 3,4,6,27 號同學
【⚠️ 對於axis的理解,要把握到 層 的感覺,比如axis=0就是代表了整個大的方塊里面第二大的那些方塊的整體,而axis=1就是剛剛那個第二大的里面的方塊,也就是第三大的方塊,現在把方塊替換成集合或者數組就能理解了】
tf.gather_nd
通過 tf.gather_nd 函數,可以通過指定每次采樣點的多維坐標來實現采樣多個點的目 的。
tf.gather_nd(x,[[0,0],[0,1],[1,1],[1,2]]) # 多維坐標采集 采樣第 1 個班級的第 1~2 號學生,第 2 個班級的第 2~3 號學生
【[[0,0],[0,1],[1,1],[1,2]],理解為[[一班第一個],[一班第二個],[二班第二個],[二班第三個]]】
tf.gather_nd(x,[[1,1],[2,2],[3,3]]) 抽查第 2 個班級的第 2 個同學的所有科目,第 3 個班級的 第 3 個同學的所有科目,第 4 個班級的第 4 個同學的所有科目。
tf.gather_nd(x,[[1,1,2],[2,2,3],[3,3,4]])抽出了班級 1 的學生 1 的科目 2、班級 2 的學生 2 的科目 3、班級 3 的學 生 3 的科目 4 的成績,共有 3 個成績數據,結果匯總為一個 shape 為[3]的張量。
tf.boolean_mask
通過給定掩碼(Mask)的方式進行采樣。
tf.boolean_mask(x,mask=[True, False,False,True],axis=0) # 根據掩碼方式采樣班級,給出掩碼和維度索引 采樣第 1 和第 4 個班級的數據
【掩碼的長度必須與對應維度的長度一致,如在班級維度上采樣,則必須對這 4 個班級 是否采樣的掩碼全部指定,掩碼長度為 4】
采樣第 1 個班級的第 1~2 號學生,第 2 個班級的第 2~3 號學生
tf.boolean_mask(x,[[True,True,False],[False,True,True]])
tf.where
通過 tf.where(cond, a, b)操作可以根據 cond 條件的真假從參數𝑨或𝑩中讀取數據
𝑜𝑖 = {𝑎𝑖 cond𝑖為 True
𝑜𝑖 = {𝑏𝑖 cond𝑖為 False
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 中采樣
a 和 b 參數不指定,tf.where 會返回 cond 張量中所有 True 的 元素的索引坐標
tf.where(cond)
x = tf.random.normal([3,3]) # 構造 a
mask=x>0 # 比較操作,等同於 tf.math.greater()
indices=tf.where(mask) # 提取所有大於 0 的元素索引
tf.gather_nd(x,indices) # 提取正數的元素值
【⚠️where得到的是索引,要提取值還是需要用到gather】
也可以直接通過 tf.boolean_mask 獲取所有正數的元 素向量:
tf.boolean_mask(x,mask) # 通過掩碼提取正數的元素值
scatter_nd
通過 tf.scatter_nd(indices, updates, shape)函數可以高效地刷新張量的部分數據,但是這 個函數只能在全 0 的白板張量上面執行刷新操作,因此可能需要結合其它操作來實現現有 張量的數據刷新功能。
【理解為先構建了一塊白板的畫布,然后根據給出的位置把數據填到相應的位置上去】
meshgrid
通過 tf.meshgrid 函數可以方便地生成二維網格的采樣點坐標,方便可視化等應用場合。
x = tf.linspace(-8.,8,100) # 設置 x 軸的采樣點
y = tf.linspace(-8.,8,100) # 設置 y 軸的采樣點
x,y = tf.meshgrid(x,y) # 生成網格點,並內部拆分后返回
z = tf.sqrt(x**2+y**2)
z = tf.sin(z)/z # sinc函數實現
繪圖
import matplotlib from matplotlib import pyplot as plt # 導入3D坐標軸支持 from mpl_toolkits.mplot3d import Axes3D fig = plt.figure() ax = Axes3D(fig) # 設置 3D 坐標軸 # 根據網格點繪制 sinc 函數 3D 曲面 ax.contour3D(x.numpy(), y.numpy(), z.numpy(), 50) plt.show()
經典數據集加載
-
通過 datasets.xxx.load_data()函數即可實現經典數據集的自動加載,其中 xxx 代表具體 的數據集名稱
對於常用的經典數據集,例如:
-
❑ Boston Housing,波士頓房價趨勢數據集,用於回歸模型訓練與測試。
-
❑ CIFAR10/100,真實圖片數據集,用於圖片分類任務。
-
❑ MNIST/Fashion_MNIST,手寫數字圖片數據集,用於圖片分類任務。
-
❑ IMDB,情感分類任務數據集,用於文本分類任務。
通過 Dataset.from_tensor_slices 可以將訓練部分的數據圖片 x 和標簽 y 都轉換成 Dataset 對象
train_db = tf.data.Dataset.from_tensor_slices((x, y)) # 構建 Dataset 對象
將數據轉換成 Dataset 對象后,一般需要再添加一系列的數據集標准處理步驟,如隨機打散、預處理、按批裝載等。
隨機打散
通過 Dataset.shuffle(buffer_size)工具可以設置 Dataset 對象隨機打散數據之間的順序, 防止每次訓練時數據按固定順序產生,從而使得模型嘗試“記憶”住標簽信息
train_db = train_db.shuffle(10000) # 隨機打散樣本,不會打亂樣本與標簽映射關系 buffer_size 參數指定緩沖池的大小,一般設置為一個較大的常數即可。
可以通過db = db. step1(). step2(). step3. ()完成所有的數據處理步驟
批訓練
同時計算多個樣本,我們 把這種訓練方式叫做批訓練,其中一個批中樣本的數量叫做 Batch Size
train_db = train_db.batch(128) # 設置批訓練,batch size 為 128,即一次並行計算 128 個樣本的數據。
預處理
通過提供 map(func)工具函 數,可以非常方便地調用用戶自定義的預處理邏輯
train_db = train_db.map(preprocess) def preprocess(x, y): # 自定義的預處理函數
循環訓練
for step, (x,y) in enumerate(train_db): # 迭代數據集對象,帶 step 參數
或for x,y in train_db: # 迭代數據集對象
每次返回的 x 和 y 對象即為批量樣本和標簽。
當對 train_db 的所有樣本完 成一次迭代后,for 循環終止退出。
⚠️這樣完成一個 Batch 的數據訓練,叫做一個 Step;
⚠️通過 多個 step 來完成整個訓練集的一次迭代,叫做一個 Epoch。
迭代多個 Epoch 才能取得較好地訓練效果【epoches->steps->batch也就是先batchbatch地訓練,一個step一個batch,幾個batch組成幾個step,幾個step組成一個】
for epoch in range(20): # 訓練 Epoch 數
for step, (x,y) in enumerate(train_db): # 迭代 Step 數
# training...
或 也可以通過設置 Dataset 對象,使得數據集對象內部遍歷多次才會退出
train_db = train_db.repeat(20) # 數據集迭代 20 遍才終止
第6章 神經網絡
感知機
𝑧 = 𝑤1 𝑥1 + 𝑤2 𝑥2 + ⋯+ 𝑤𝑛 𝑥𝑛 + 𝑏 其中𝑏稱為感知機的偏置(Bias),一維向量𝒘 = [𝑤1, 𝑤2, ... , 𝑤𝑛]稱為感知機的權值(Weight),𝑧稱為感知機的凈活性值(Net Activation)。
【⚠️我們訓練的尋找最好的w和b,xy反倒是已知的數據】不可導性
全連接層
【此處直接結合着線性代數的矩陣運算來看,(m*n)x(n*k)=(m*k)】
張量方式實現【麻煩死了】
例如,創建輸入𝑿 矩陣為𝑏 = 2個樣本,每個樣本的輸入特征長度為𝑑in = 784,輸出節點數為𝑑out = 256,故 定義權值矩陣𝑾的 shape 為[784,256],並采用正態分布初始化𝑾;偏置向量𝒃的 shape 定義 為[256],在計算完𝑿@𝑾后相加即可,最終全連接層的輸出𝑶的 shape 為[2,256],即 2 個樣 本的特征,每個特征長度為 256,代碼實現如下: # 創建W,b張量 x = tf.random.normal([2,784]) w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1)) b1 = tf.Variable(tf.zeros([256])) o1 = tf.matmul(x,w1) + b1 # 線性變換 o1 = tf.nn.relu(o1) # 激活函數
層方式實現
layers.Dense(units, activation)。 通過 layer.Dense 類,只需要指定輸出節點數 Units 和激活函數類型 activation 即可。
⚠️輸入節點數會根據第一次運算時的輸入 shape 確定,同時根據輸入、輸出節點數 自動創建並初始化權值張量𝑾和偏置張量𝒃,因此在新建類 Dense 實例時,並不會立即創 建權值張量𝑾和偏置張量𝒃,而是需要調用 build 函數或者直接進行一次前向計算,才能完 成網絡參數的創建。
其中 activation 參數指定當前層的激活函數,可以為常見的激活函數或 自定義激活函數,也可以指定為 None,即無激活函數
x = tf.random.normal([4,28*28])
from tensorflow.keras import layers # 導入層模塊
# 創建全連接層,指定輸出節點數和激活函數
fc = layers.Dense(512, activation=tf.nn.relu)
h1 = fc(x) # 通過 fc 類實例完成一次全連接層的計算,返回輸出張量shape=(4, 512)
fc.kernel # 獲取 Dense 類的權值矩陣shape=(784, 512)
fc.bias # 獲取 Dense 類的偏置向量shape=(512,)
【可以看見最后的結果是4*512,所以這里的計算應該是h1=x*kernel+bias⚠️❓𝑿@𝑾 + 𝒃中xk的順序】
fc.trainable_variables# 返回待優化參數列表
fc.variables # 返回所有參數列表
設計全連接網絡時,網絡的結構配置等超參數可以按着經驗法則自由設置,只需要 遵循少量的約束即可。
例如,隱藏層 1 的輸入節點數需和數據的實際特征長度匹配
每層 的輸入層節點數與上一層輸出節點數匹配
輸出層的激活函數和節點數需要根據任務的具 體設定進行設計。
【每層的 輸出節點數不一定要設計為[256,128,64,10],可以自由搭配,如[256,256,64,10]或 [512,64,32,10]等都是可行的。
至於與哪一組超參數是最優的,這需要很多的領域經驗知識 和大量的實驗嘗試,或者可以通過 AutoML 技術搜索出較優設定。】
張量方式實現【麻煩方法】
對於多層神經網絡,以圖 6.5 網絡結構為例,需要分別定義各層的權值矩陣𝑾和偏置 向量𝒃。有多少個全連接層,則需要相應地定義數量相當的𝑾和𝒃,並且每層的參數只能用 於對應的層,不能混淆使用。圖 6.5 的網絡模型實現如下: 輸入:[𝑏, 784] 隱藏層1:[256] 隱藏層2:[128] 隱藏層3:[64] 輸出層:[𝑏, 10] # 隱藏層1張量 w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1)) b1 = tf.Variable(tf.zeros([256])) # 隱藏層2張量 w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1)) b2 = tf.Variable(tf.zeros([128])) # 隱藏層3張量 w3 = tf.Variable(tf.random.truncated_normal([128, 64], stddev=0.1)) b3 = tf.Variable(tf.zeros([64])) # 輸出層張量 w4 = tf.Variable(tf.random.truncated_normal([64, 10], stddev=0.1)) b4 = tf.Variable(tf.zeros([10])) 在計算時,只需要按照網絡層的順序,將上一層的輸出作為當前層的輸入即可,重復 直至最后一層,並將輸出層的輸出作為網絡的輸出,代碼如下: with tf.GradientTape() as tape: # 梯度記錄器 # x: [b, 28*28] # 隱藏層 1 前向計算,[b, 28*28] => [b, 256] h1 = x@w1 + tf.broadcast_to(b1, [x.shape[0], 256]) h1 = tf.nn.relu(h1) # 隱藏層 2 前向計算,[b, 256] => [b, 128] h2 = h1@w2 + b2 h2 = tf.nn.relu(h2) # 隱藏層 3 前向計算,[b, 128] => [b, 64] h3 = h2@w3 + b3 h3 = tf.nn.relu(h3) # 輸出層前向計算,[b, 64] => [b, 10] h4 = h3@w4 + b4 最后一層是否需要添加激活函數通常視具體的任務而定,這里加不加都可以。 在使用 TensorFlow 自動求導功能計算梯度時,需要將前向計算過程放置在 tf.GradientTape()環境中,從而利用 GradientTape 對象的 gradient()方法自動求解參數的梯 度,並利用 optimizers 對象更新參數。
層方式實現
# 導入常用網絡層layers from tensorflow.keras import layers,Sequential fc1 = layers.Dense(256, activation=tf.nn.relu) # 隱藏層1 fc2 = layers.Dense(128, activation=tf.nn.relu) # 隱藏層2 fc3 = layers.Dense(64, activation=tf.nn.relu) # 隱藏層3 fc4 = layers.Dense(10, activation=None) # 輸出層
在前向計算時,依序通過各個網絡層即可,代碼如下
x = tf.random.normal([4,28*28])
h1 = fc1(x) # 通過隱藏層 1 得到輸出 h2 = fc2(h1) # 通過隱藏層 2 得到輸出 h3 = fc3(h2) # 通過隱藏層 3 得到輸出 h4 = fc4(h3) # 通過輸出層得到網絡輸出
通過 Sequential 容器封裝
# 導入Sequential容器 from tensorflow.keras import layers,Sequential # 通過Sequential容器封裝為一個網絡類 model = Sequential([ layers.Dense(256, activation=tf.nn.relu) , # 創建隱藏層 1 layers.Dense(128, activation=tf.nn.relu) , # 創建隱藏層 2
layers.Dense(64, activation=tf.nn.relu) , # 創建隱藏層 3 layers.Dense(10, activation=None) , # 創建輸出層 ]) # 前向計算時只需要調用一次網絡大類對象,即可完成所有層的按序計算: out = model(x) # 前向計算得到輸出
優化目標
前向傳播的最后一步就是完成誤差的計算
L = 𝑔(𝑓 (𝒙),𝒚)
希望通過在訓練集𝔻train上面學習到一組參數𝜃使 得訓練的誤差L最小
𝜃∗ = a⏟rg min 𝑔(𝑓 (𝒙), 𝒚), 𝑥 ∈ 𝔻train 𝜃
上述的最小化優化問題一般采用誤差反向傳播(Backward Propagation,簡稱 BP)算法來求解 網絡參數𝜃的梯度信息,並利用梯度下降(Gradient Descent,簡稱 GD)算法迭代更新參數:
𝜃′ = 𝜃 − 𝜂 ∙ ∇𝜃L 𝜂為學習率。
完成的是特征的維度變換的功能
比如 4 層的 MNIST 手寫數字圖片識別的全連接網絡,它依次完成了784 → 256 → 128 → 64 → 10的特 征降維過程。
原始的特征通常具有較高的維度,包含了很多底層特征及無用信息,通過神 經網絡的層層特征變換,將較高的維度降維到較低的維度,
網絡的參數量是衡量網絡規模的重要指標。那么怎么計算全連接層的參數量呢?
考慮 權值矩陣𝑾,偏置向量𝒃,輸入特征長度為𝑑in,輸出特征長度為𝑑out的網絡層,𝑾的參數 量為𝑑in ∙ 𝑑out,再加上偏置𝒃的參數,
⚠️總參數量為𝑑in ∙ 𝑑out + 𝑑out。
對於多層的全連接神經 網絡,比如784 → 256 → 128 → 64 → 10,總參數量的計算表達式為:
(256 ∙ 784 + 256) +( 128 ∙ 256 + 128) + (64 ∙ 128 + 64 )+( 10 ∙ 64 + 10 )= 242762 約 242K 個參數。
激活函數
平滑可導的,適合於梯度下降算法。
Sigmoid 函數也叫 Logistic 函數
把𝑥 ∈ 𝑅的輸入“壓縮”到𝑥 ∈ (0,1)區間,這個區間的數值在機器學習常用來表示以下意義:
-
❑ 概率分布 (0,1)區間的輸出和概率的分布范圍[0,1]契合,可以通過 Sigmoid 函數將輸出 轉譯為概率輸出
-
❑ 信號強度 一般可以將 0~1 理解為某種信號的強度,如像素的顏色強度,1 代表當前通 道顏色最強,0 代表當前通道無顏色;抑或代表門控值(Gate)的強度,1 代表當前門控 全部開放,0 代表門控關閉
通過 tf.nn.sigmoid 實現 Sigmoid 函
x = tf.linspace(-6.,6.,10)
tf.nn.sigmoid(x) # 通過 Sigmoid 函數
【Sigmoid 函數在輸入值較大或較小時容易出現梯度值接 近於 0 的現象,稱為梯度彌散現象。出現梯度彌散現象時,網絡參數長時間得不到更新, 導致訓練不收斂或停滯不動的現象發生,較深層次的網絡模型中更容易出現梯度彌散現 象。】
ReLU
tf.nn.relu(x) # 通過 ReLU 激活函數
負數全部抑制為 0,正數得以保留。
【除了可以使用函數式接口 tf.nn.relu 實現 ReLU 函數外,還可以像 Dense 層一樣將 ReLU 函數作為一個網絡層添加到網絡中,對應的類為 layers.ReLU()類。一般來說,激活 函數類並不是主要的網絡運算層,不計入網絡的層數。】
【ReLU 函數在𝑥 < 0時導數值恆為 0,也可能會造成梯度彌散現象】
LeakyReLU
通過 tf.nn.leaky_relu 實現 LeakyReLU 函數
tf.nn.leaky_relu(x, alpha=0.1) # 通過 LeakyReLU 激活函數
Tanh
Tanh 函數能夠將𝑥 ∈ 𝑅的輸入“壓縮”到(−1,1)區間= 2 ∙ sigmoid(2𝑥) − 1
tf.nn.tanh(x) # 通過 tanh 激活函數
輸出層設計
普通實數空間
這一類問題比較普遍,像正弦函數曲線預測、年齡的預測、股票走勢的預測等都屬於 整個或者部分連續的實數空間,輸出層可以不加激活函數。
誤差的計算直接基於最后一層 的輸出𝒐和真實值𝒚進行計算,如采用均方差誤差函數度量輸出值𝒐與真實值𝒚之間的距離:L = 𝑔(𝒐,𝒚)其中𝑔代表了某個具體的誤差計算函數,例如 MSE 等。
[0, 1]區間
輸出值屬於[0, 1]區間也比較常見,比如圖片的生成、二分類問題等。
需要在輸出層后添 加某個合適的激活函數𝜎,其中 Sigmoid 函數剛好具有此功能。
[0,1]區間,和為 1
輸出值𝑜𝑖 ∈ [0,1],且所有輸出值之和為 1,這種設定以多分類問題最為常見。
輸出層添加 Softmax 函數實現
不僅可以將輸出值映射到[0,1]區間,還滿足所有的輸出值之和為 1 的特性。
tf.nn.softmax(z) # 通過 Softmax 函數
【在 Softmax 函數的數值計算過程中,容易因輸入值偏大發生數值溢出現象;在計算交 叉熵時,也會出現數值溢出的問題。】
函數式接口為 tf.keras.losses.categorical_crossentropy(y_true, y_pred, from_logits=False),其中 y_true 代表了 One-hot 編碼后的真實標簽,y_pred 表示網絡的預測值,當 from_logits 設置為 True 時, y_pred 表示須為未經過 Softmax 函數的變量 z;當 from_logits 設置為 False 時,y_pred 表示 為經過 Softmax 函數的輸出。
z = tf.random.normal([2,10]) # 構造輸出層的輸出
y_onehot = tf.constant([1,3]) # 構造真實值
y_onehot = tf.one_hot(y_onehot, depth=10) # one-hot編碼
# 輸出層未使用 Softmax 函數,故 from_logits 設置為 True
# 這樣 categorical_crossentropy 函數在計算損失函數前,會先內部調用 Softmax 函數
loss = keras.losses.categorical_crossentropy(y_onehot,z,from_logits=True)
也可以利用 losses.CategoricalCrossentropy(from_logits)類方式同時實 現 Softmax 與交叉熵損失函數的計算,from_logits 參數的設置方式相同。
criteon = keras.losses.CategoricalCrossentropy(from_logits=True)
loss = criteon(y_onehot,z) # 計算損失
[-1, 1]
如果希望輸出值的范圍分布在(−1, 1)區間,可以簡單地使用 tanh 激活函數
tf.tanh(x) # tanh激活函數
誤差計算
在搭建完模型結構后,下一步就是選擇合適的誤差函數來計算誤差。
均方差(Mean Squared Error,簡稱 MSE)誤差函數
把輸出向量和真實向量映射到笛卡爾 坐標系的兩個點上,通過計算這兩個點之間的歐式距離(准確地說是歐式距離的平方)來衡 量兩個向量之間的差距
MSE 誤差函數的值總是大於等於 0,當 MSE 函數達到最小值 0 時,輸出等於真實標簽, 此時神經網絡的參數達到最優狀態。
o = tf.random.normal([2,10]) # 構造網絡輸出
y_onehot = tf.constant([1,3]) # 構造真實值
y_onehot = tf.one_hot(y_onehot, depth=10)
loss = keras.losses.MSE(y_onehot, o) # 計算均方差
【⚠️MSE 函數返回的是每個樣本的均方差,需要在樣本維度上再次平均來獲 得平均樣本的均方差】
loss = tf.reduce_mean(loss) # 計算 batch 均方差
過層方式實現,對應的類為 keras.losses.MeanSquaredError()
criteon = keras.losses.MeanSquaredError() loss = criteon(y_onehot,o) # 計算 batch 均方差
交叉熵誤差函數Cross Entrop
【這里面沒有寫,應該是用那個p ra se開頭的交叉函數的損失函數】
最小化交叉熵損失函數的過程也是最大化正確類別的預測概率的過程。
熵越大,代表不確定性越大,信息量也就越大。
分類問題的 One-hot 編碼的分布就是熵為 0 的典型例子。在 TensorFlow 中 間,我們可以利用 tf.math.log 來計算熵。
卷積神經網絡CNN
用於圖片分類的 AlexNet、VGG、GoogLeNet、ResNet、DenseNet 等,
用於目 標識別的 RCNN、Fast RCNN、Faster RCNN、Mask RCNN、YOLO、SSD 等。
循環神經網絡RNN
RNN 變種LSTM 模型, Seq2Seq 模型,還有 GRU、雙向 RNN
注意力(機制)網絡Transformer
用於機器翻譯的注意力網絡模 型,如 GPT、BERT、GPT-2 等
自注意力(Self- Attention)機制構建的網絡比如基於自注意力機制的 BigGAN 模型
圖卷積神經網絡GCN
類似於社交網 絡、通信網絡、蛋白質分子結構等一系列的不規則空間拓撲結構的數據
GAT,EdgeConv,DeepGCN 等。
利用全連接網絡模型來完成汽車的效能指標 MPG(Mile Per Gallon,每加侖燃油英里數)的預測問題實戰。

# 在線下載汽車效能數據集 dataset_path = keras.utils.get_file("auto-mpg.data", "http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto- mpg.data") # 利用 pandas 讀取數據集,字段有效能(公里數每加侖),氣缸數,排量,馬力,重量 # 加速度,型號年份,產地 column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight', 'Acceleration', 'Model Year', 'Origin'] raw_dataset = pd.read_csv(dataset_path, names=column_names, na_values = "?", comment='\t', sep=" ", skipinitialspace=True) dataset = raw_dataset.copy() # 查看部分數據 dataset.head() 原始表格中的數據可能含有空字段(缺失值)的數據項,需要清除這些記錄項: 清除后,觀察到數據集記錄項減為 392 項。 由於 Origin 字段為類別類型數據,我們將其移除,並轉換為新的 3 個字段:USA、 Europe 和 Japan,分別代表是否來自此產地: dataset.isna().sum() # 統計空白數據 dataset = dataset.dropna() # 刪除空白數據項 dataset.isna().sum() # 再次統計空白數據 # 處理類別型數據,其中 origin 列代表了類別 1,2,3,分布代表產地:美國、歐洲、日本 # 先彈出(刪除並返回)origin 這一列 origin = dataset.pop('Origin') # 根據 origin 列來寫入新的 3 個列 dataset['USA'] = (origin == 1)*1.0 dataset['Europe'] = (origin == 2)*1.0 dataset['Japan'] = (origin == 3)*1.0 dataset.tail() # 查看新表格的后幾項 按着 8:2 的比例切分數據集為訓練集和測試集: 將 MPG 字段移出為標簽數據: 統計訓練集的各個字段數值的均值和標准差,並完成數據的標准化,通過 norm()函數 實現,代碼如下: # 切分為訓練集和測試集 train_dataset = dataset.sample(frac=0.8,random_state=0) test_dataset = dataset.drop(train_dataset.index) # 移動 MPG 油耗效能這一列為真實標簽 Y train_labels = train_dataset.pop('MPG') test_labels = test_dataset.pop('MPG') # 查看訓練集的輸入 X 的統計數據 train_stats = train_dataset.describe() train_stats.pop("MPG") # 僅保留輸入 X train_stats = train_stats.transpose() # 轉置 # 標准化數據 def norm(x): # 減去每個字段的均值,並除以標准差 return (x - train_stats['mean']) / train_stats['std'] normed_train_data = norm(train_dataset) # 標准化訓練集 normed_test_data = norm(test_dataset) # 標准化測試集 打印出訓練集和測試集的大小: 利用切分的訓練集數據構建數據集對象: 我們可以通過簡單地統計數據集中各字段之間的兩兩分布來觀察各個字段對 MPG 的 影響,如圖 6.16 所示。可以大致觀察到,其中汽車排量、重量與 MPG 的關系比較簡單, 隨着排量或重量的增大,汽車的 MPG 降低,能耗增加;氣缸數越小,汽車能做到的最好 MPG 也越高,越可能更節能,這都是是符合我們的生活經驗的。 圖 6.16 特征之間的兩兩分布 6.8.2 創建網絡 考慮到 Auto MPG 數據集規模較小,我們只創建一個 3 層的全連接網絡來完成 MPG 值的預測任務。輸入𝑿的特征共有 9 種,因此第一層的輸入節點數為 9。第一層、第二層的 輸出節點數設計為64和64,由於只有一種預測值,輸出層輸出節點設計為 1。考慮MPG ∈ 𝑅+,因此輸出層的激活函數可以不加,也可以添加 ReLU 激活函數。 我們將網絡實現為一個自定義網絡類,只需要在初始化函數中創建各個子網絡層,並 在前向計算函數 call 中實現自定義網絡類的計算邏輯即可。自定義網絡類繼承自 keras.Model 基類,這也是自定義網絡類的標准寫法,以方便地利用 keras.Model 基類提供 的 trainable_variables、save_weights 等各種便捷功能。網絡模型類實現如下: print(normed_train_data.shape,train_labels.shape) print(normed_test_data.shape, test_labels.shape) (314, 9) (314,) # 訓練集共 314 行,輸入特征長度為 9,標簽用一個標量表示 (78, 9) (78,) # 測試集共 78 行,輸入特征長度為 9,標簽用一個標量表示 train_db = tf.data.Dataset.from_tensor_slices((normed_train_data.values, train_labels.values)) # 構建 Dataset 對象 train_db = train_db.shuffle(100).batch(32) # 隨機打散,批量化 class Network(keras.Model): # 回歸網絡模型 def __init__(self): super(Network, self).__init__() # 創建3個全連接層 self.fc1 = layers.Dense(64, activation='relu') self.fc2 = layers.Dense(64, activation='relu') self.fc3 = layers.Dense(1) def call(self, inputs, training=None, mask=None): # 依次通過 3 個全連接層 x = self.fc1(inputs) x = self.fc2(x) x = self.fc3(x) return x 在完成主網絡模型類的創建后,我們來實例化網絡對象和創建優化器,代碼如下: 接下來實現網絡訓練部分。通過 Epoch 和 Step 組成的雙層循環訓練網絡,共訓練 200 個 Epoch,代碼如下: model = Network() # 創建網絡類實例 # 通過 build 函數完成內部張量的創建,其中 4 為任意設置的 batch 數量,9 為輸入特征長度 model.build(input_shape=(4, 9)) model.summary() # 打印網絡信息 optimizer = tf.keras.optimizers.RMSprop(0.001) # 創建優化器,指定學習率 接下來實現網絡訓練部分。通過 Epoch 和 Step 組成的雙層循環訓練網絡,共訓練 200 個 Epoch for epoch in range(200): # 200個Epoch for step, (x,y) in enumerate(train_db): # 遍歷一次訓練集 # 梯度記錄器,訓練時需要使用它 with tf.GradientTape() as tape: out = model(x) # 通過網絡獲得輸出 loss = tf.reduce_mean(losses.MSE(y, out)) # 計算 MSE mae_loss = tf.reduce_mean(losses.MAE(y, out)) # 計算 MAE if step % 10 == 0: # 間隔性地打印訓練誤差 print(epoch, step, float(loss)) # 計算梯度,並更新 grads = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(grads, model.trainable_variables))
第7章 反向傳播算法
第8章 Keras 高層接口
第9章 過擬合
第10章 卷積神經網絡
第11章 循環神經網絡
【【本章中的cell模式比較復雜,目前只列舉 層模式 】】
序列表示方法
如果要表示𝑏件商品在 1 月到 6 月之間的價格 變化趨勢,可以記為 2 維張量:
(1) (1) (1) (2) (2) (2) (𝑏) (𝑏) (𝑏)
[[𝑥1 ,𝑥2 ,⋯,𝑥6 ],[𝑥1 ,𝑥2 ,⋯,𝑥6 ],⋯,[𝑥1 ,𝑥2 ,⋯,𝑥6 ]]
其中𝑏表示商品的數量,張量 shape 為[𝑏, 6]。
Embedding 層
可以通過 layers.Embedding(𝑁vocab,𝑛)來定義一個 Word Embedding 層,其中𝑁vocab參數指定詞匯數量,𝑛指定單詞向量的長度
x = tf.range(10) # 生成 10 個單詞的數字編碼
x = tf.random.shuffle(x) # 打散
# 創建共 10 個單詞,每個單詞用長度為 4 的向量表示的層
net = layers.Embedding(10, 4)
out = net(x) # 獲取詞向量
預訓練的詞向量
embed_glove = load_embed('glove.6B.50d.txt')# 從預訓練模型中加載詞向量表
net.set_weights([embed_glove])# 直接利用預訓練的詞向量表初始化 Embedding 層
循環神經網絡
在循環神經網絡中,激活函數更多地采用 tanh 函數,並且可 以選擇不使用偏執𝒃來進一步減少參數量。
狀態向量 𝑡可以直接用作輸出,即𝒐𝑡 = 𝑡,也 可以對 𝑡做一個簡單的線性變換𝒐𝑡 = 𝑊 𝑜 𝑡后得到每個時間戳上的網絡輸出𝒐𝑡
SimpleRNN 與 SimpleRNNCell 的 區別在於,帶 Cell 的層僅僅是完成了一個時間戳的前向運算,不帶 Cell 的層一般是基於 Cell 層實現的,它在內部已經完成了多個時間戳的循環運算,
SimpleRNNCell手動參與循環神經網絡內部的計算過程【這里不多做描述了,在11.4.2】
通過 SimpleRNN層高層接口可以非常方便地幫助我們實現此目的
單層循環神經網絡的前向運算
layer = layers.SimpleRNN(64) # 創建狀態向量長度為 64 的 SimpleRNN 層
x = tf.random.normal([4, 80, 100])
out = layer(x) # 和普通卷積網絡一樣,一行代碼即可獲得輸出
out.shape Out[6]: TensorShape([4, 64])
【⚠️默認返回最 后一個時間戳上的輸出。如果希望返回所有時間戳上的輸出列表,可以設置 return_sequences=True 參數】
# 創建 RNN 層時,設置返回所有時間戳上的輸出
layer = layers.SimpleRNN(64,return_sequences=True) out = layer(x) # 前向計算
out # 輸出,⚠️自動進行了 concat 操作返回的輸出張量 shape 為[4,80,64],中間維度的 80 即為時間戳維度。
多層循環神經網絡,我們可以通過堆疊多個 SimpleRNN 實現
net = keras.Sequential([ # 構建 2 層 RNN 網絡
layers.SimpleRNN(64, return_sequences=True), layers.SimpleRNN(64),]) ⚠️# 除最末層外,都需要返回所有時間戳的輸出,用作下一層的輸入
每層都需要上一層在每個時間戳上面的狀態輸出,因此除了最末層以外,所有的 RNN 層 都需要返回每個時間戳上面的狀態輸出,通過設置 return_sequences=True 來實現
RNN 情感分類問題實戰
這里使用經典的 IMDB 影評數據集來完成情感分類任務。IMDB 影評數據集包含了 50000 條用戶評價,評價的標簽分為消極和積極,其中 IMDB 評級<5 的用戶評價標注為 0,即消極;IMDB 評價>=7 的用戶評價標注為 1,即積極。25000 條影評用於訓練集, 25,000 條用於測試集。 通過 Keras 提供的數據集 datasets 工具即可加載 IMDB 數據集,代碼如下: In [8]: batchsz = 128 # 批量大小 total_words = 10000 # 詞匯表大小 N_vocab max_review_len = 80 # 句子最大長度 s,大於的句子部分將截斷,小於的將填充
embedding_len = 100 # 詞向量特征長度 n # 加載 IMDB 數據集,此處的數據采用數字編碼,一個數字代表一個單詞 (x_train, y_train), (x_test, y_test) = keras.datasets.imdb.load_data(num_words=total_words) # 打印輸入的形狀,標簽的形狀 print(x_train.shape, len(x_train[0]), y_train.shape) print(x_test.shape, len(x_test[0]), y_test.shape) Out[8]:
(25000,) 218 (25000,) (25000,) 68 (25000,)
可以看到,x_train 和 x_test 是長度為 25,000 的一維數組,數組的每個元素是不定長 List, 保存了數字編碼的每個句子,例如訓練集的第一個句子共有 218 個單詞,測試集的第一個 句子共有 68 個單詞,每個句子都包含了句子起始標志 ID。
那么每個單詞是如何編碼為數字的呢?我們可以 In [9]: # 數字編碼表 word_index = keras.datasets.imdb.get_word_index() # 打印出編碼表的單詞和對應的數字 for k,v in word_index.items(): print(k,v) 翻轉編碼表,並添加標志位的編碼 ID # 前面4個ID是特殊位 word_index = {k:(v+3) for k,v in word_index.items()} word_index["<PAD>"] = 0 # 填充標志 word_index["<START>"] = 1 # 起始標志 word_index["<UNK>"] = 2 # 未知單詞的標志 word_index["<UNUSED>"] = 3 # 翻轉編碼表 reverse_word_index = dict([(value, key) for (key, value) in word_index.items()]) 對於一個數字編碼的句子,通過如下函數轉換為字符串數據: def decode_review(text): return ' '.join([reverse_word_index.get(i, '?') for i in text])
句子截斷功能可以通過 keras.preprocessing.sequence.pad_sequences()函 數方便實現
# 截斷和填充句子,使得等長,此處長句子保留句子后面的部分,短句子在前面填充 x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=max_review_len) x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=max_review_len) 截斷或填充為相同長度后,通過 Dataset 類包裹成數據集對象,並添加常用的數據集處 理流程,代碼如下:
⚠️ In [12]: # 構建數據集,打散,批量,並丟掉最后一個不夠 batchsz 的 batch db_train = tf.data.Dataset.from_tensor_slices((x_train, y_train)) db_train = db_train.shuffle(1000).batch(batchsz, drop_remainder=True) db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test)) db_test = db_test.batch(batchsz, drop_remainder=True) # 統計數據集屬性 print('x_train shape:', x_train.shape, tf.reduce_max(y_train), tf.reduce_min(y_train)) print('x_test shape:', x_test.shape) Out[12]: x_train shape: (25000, 80) tf.Tensor(1, shape=(), dtype=int64) tf.Tensor(0, shape=(), dtype=int64) x_test shape: (25000, 80) 截斷填充后的句子長度統一為 80,即設定的句子長度閾值。drop_remainder=True 參數設置丟棄掉最后一個 Batch,因為其真實的 Batch Size 可能小於預設的 Batch Siz
自定義的模型類 MyRNN,繼承自 Model 基類,需要新建 Embedding 層,兩個 RNN 層,分類網絡層
class MyRNN(keras.Model): # Cell方式構建多層網絡 def __init__(self, units): super(MyRNN, self).__init__() # [b, 64],構建Cell初始化狀態向量,重復使用
self.state0 = [tf.zeros([batchsz, units])]
self.state1 = [tf.zeros([batchsz, units])]
# 詞向量編碼 [b, 80] => [b, 80, 100] self.embedding = layers.Embedding(total_words, embedding_len, input_length=max_review_len) # 構建 2 個 Cell,使用 dropout 技術防止過擬合 self.rnn_cell0 = layers.SimpleRNNCell(units, dropout=0.5)
self.rnn_cell1 = layers.SimpleRNNCell(units, dropout=0.5)
# 構建分類網絡,用於將 CELL 的輸出特征進行分類,2 分類 # [b, 80, 100] => [b, 64] => [b, 1] self.outlayer = layers.Dense(1) 其中詞向量編碼為長度𝑛 = 100,RNN 的狀態向量長度h = units參數,分類網絡完成 2 分 類任務,故輸出節點設置為 1
前向傳播邏輯如下:輸入序列通過 Embedding 層完成詞向量編碼,循環通過兩個 RNN 層,提取語義特征,取最后一層的最后時間戳的狀態向量輸出送入分類網絡,經過 Sigmoid 激活函數后得到輸出概率
def call(self, inputs, training=None):
x = inputs # [b, 80]
# 獲取詞向量: [b, 80] => [b, 80, 100]
x = self.embedding(x)
# 通過 2 個 RNN CELL,[b, 80, 100] => [b, 64]
state0 = self.state0
state1 = self.state1
for word in tf.unstack(x, axis=1): # word: [b, 100]
out0, state0 = self.rnn_cell0(word, state0, training)
out1, state1 = self.rnn_cell1(out0, state1, training)
# 末層最后一個輸出作為分類網絡的輸入: [b, 64] => [b, 1]
x = self.outlayer(out1, training)
# 通過激活函數,p(y is pos|x)
prob = tf.sigmoid(x)
return prob
使用 Keras 的 Compile&Fit 方式訓練網絡,設置優化器為 Adam 優化 器,學習率為 0.001,誤差函數選用 2 分類的交叉熵損失函數 BinaryCrossentropy,測試指 標采用准確率 def main(): units = 64 # RNN狀態向量長度n epochs = 20 # 訓練 epochs model = MyRNN(units) # 創建模型 # 裝配 model.compile(optimizer = optimizers.Adam(0.001), loss = losses.BinaryCrossentropy(), metrics=['accuracy']) # 訓練和驗證 model.fit(db_train, epochs=epochs, validation_data=db_test) # 測試 model.evaluate(db_test) 網絡固定訓練 20 個 Epoch 后,在測試集上獲得了 80.1%的准確率。
梯度值接近於 0 的現象叫做梯度彌散(Gradient Vanishing),把梯度值遠大於 1 的 現象叫做梯度爆炸(Gradient Exploding)
第12章 自編碼器
第13章 生成對抗網絡
第14章 強化學習
第15章 自定義數據集