前言
AI 人工智慧包含了機器學習與深度學習,在前幾篇文章曾經介紹過機器學習的基礎知識,包括了監督學習和無監督學習,有興趣的朋友可以閱讀《 Python 機器學習實戰 》。
而深度學習開始只是機器學習的一分支領域,它更強調從連續的層中進行學習,這種層級結構中的每一層代表不同程式的抽象,層級越高,抽象程度越大。這些層主要通過神經網路的模型學習得到的,最大的模型會有上百層之多。而最簡單的神經網路分為輸入層,中間層(中間層往往會包含多個隱藏層),輸出層。
下面幾篇文章將分別從前饋神經網路 FNN、卷積神經網路 CNN、迴圈神經網路 RNN、編碼器等領域進行詳細介紹。
目錄
一、深度學習簡介
1.1 深度學習的起源
AI 人工智慧包含了機器學習,而深度學習本屬於機器學習的一個分支,它結合了生物神經學的原理,以多層學習模型,將資料層層細化提煉,最後完成輸出。如今深度學習已經廣泛應用於影象識別、人臉識別、語音識別、搜尋引擎、自動駕駛等多個領域。
深度學習強調從連續的層中進行學習,每個層都是通過神經網路的模型來學習得到,神經網路的結構逐層疊加,最後通過輸出層完成輸出。深度學習的原理本來源於生物神經學,由於受到外界刺激神經元細胞傳送訊號,從其他神經元接收,多層疊加後形成樹狀結構,當到某個閾值後轉換成啟用狀態。而深度學習的結構與此類似,原始資料輸入後會經過多個隱藏層,每個隱藏層都會存在若干個神經元,最後通過輸出層完成輸出,層越深所提煉到純度越高。
1.2 深度學習的工作原理
打個比方在輸入層有 n 個引數 X1,X2,.....,Xn,每個引數的取值權重都儲存在W1,W2,....,Wn,偏置量都保存於 h1,h2,...,hn,其求和結點值為公式
當結點值超過某個值時就會引發啟用函式 Φ(*) 把資料輸出到下一層,周而復始直到輸出層。而深度學習的意義在於通過千萬個監督資料不斷重複學習,從而調整權重與偏置量,使其與監督資料匹配。完成學習後,即使有末經測試的新資料輸入,系統也可根據原有的基礎進行判斷,不斷地完善。
區別於傳統的資料學習,深度學習的不同在於: 1. 深度學習強調了模型結構的深度,它最少的深度有3層,就是輸入層,隱藏層,輸出層,而通常會有5層、6層,甚至10多層的隱藏層; 2. 深度明確了特徵學習的重要性,通過逐層特徵變換,將樣本在原空間的特徵表示變換到一個新特徵空間,從而使特徵純度越來越高,讓分類或預測更容易。
1.3 Tensorflow 2.0 簡介
用於實現深度學習的框架有很多,常用的有 Tensorflow、Theano、CNTK 等,而在 Tensorflow 1.x 前 Keras 是用 Python 開發的模型庫,能兼顧三大平臺,在不同的後端執行。當 Tensorflow 2.0 出現後,已經把 Keras 定義為訓練模型的一個 API 規範,把它包含到 Tensorflow 2.0 的一個庫裡,此後使用 Keras 時無需再額外下載 Keras 包。Eager 執行模型也是 Tensorflow 2.0 的一個重要特徵,它可以跟 Keras 結合使用,形成一個高效能的流水線執行模式。另外 Tensorflow 2.0 使用函式代替了原來的 session 會話模式,把設計重心放在 Eager 執行模型上。以後在設計模型時無需要再考慮全域性變數,佔位符 placeholder 等繁瑣的細節。Tensorflow 2.0 的應用將在下面幾篇文章中一一介紹,敬請留意。
二、損失函式
上一節介紹到,深度學習是通過大量的監督資料進行訓練,計算損失函式的最小值從而得出最符合現狀的權重與偏移量的。損失函式的公式有很多,最常用的就是均方誤差和交叉熵誤差兩種,關於損失函式的原理在《 Python機器學習實戰 —— 監督學習 》中已經深入講解過,在這章中主要從實用層面進行介紹。
在 Tensoflow 2.0 中可通過 model.compile (optimizer , loss , metrics) 方法繫結損失函式和計算梯度的方法,loss 引數可繫結 tensorflow.keras.losses 中多種已定義的損失函式,常用的 loss 損失函式有下面幾種:(當中 yi 為真實值 yi^為預測值)
2.1 mean_squared_error 均方誤差
1 def mean_squared_error(y_true, y_pred):
2 return K.mean(K.square(y_pred - y_true), axis=-1)
均方誤差是最常用的損失函式,一般用於迴歸計算
2.2 mean_absolute_error 平均絕對誤差
1 def mean_absolute_error(y_true, y_pred):
2 return K.mean(K.abs(y_pred - y_true), axis=-1)
平均絕對誤差與均方誤差類似,一般用於迴歸計算,只不過均方誤差公式使用的是平方和的平均值,而平均絕對誤差則是用絕對值的平均值
2.3 mean_absolute_percentage_error 平均絕對值百分比誤差
1 def mean_absolute_percentage_error(y_true, y_pred):
2 diff = K.abs((y_true - y_pred) / K.clip(K.abs(y_true),
3 K.epsilon(),
4 None))
5 return 100. * K.mean(diff, axis=-1)
平均絕對值百分比誤差則是用真實值與預測值的差值比例進行計算的,通常會乘以百分比進行計算,一般用於迴歸計算。這是銷量預測最常用的指標,在實際的線上線下銷量預測中有著非常重要的評估意義
2.4 mean_squared_logarithmic_error 對數方差
1 def mean_squared_logarithmic_error(y_true, y_pred):
2 first_log = K.log(K.clip(y_pred, K.epsilon(), None) + 1.)
3 second_log = K.log(K.clip(y_true, K.epsilon(), None) + 1.)
4 return K.mean(K.square(first_log - second_log), axis=-1)
對數方差計算了一個對應平方對數(二次)誤差或損失的預估值風險度量,一般用於迴歸計算。當目標具有指數增長的趨勢時, 該指標最適合使用, 例如人口數量, 跨年度商品的平均銷售額等。
2.5 categorical_crossentropy 多類交叉熵
1 def categorical_crossentropy(y_true, y_pred):
2 return K.categorical_crossentropy(y_true, y_pred)
交叉熵主要用於分類演算法,當使用交叉熵損失函式時,目標值應該是分類格式 (nb_samples, nb_classes) (即如果有10個類,輸出資料格式應為 [-1,10],每個樣本的目標值 nb_class 應該是一個10維的向量 ,這個向量除了表示類別的那個索引為1,其他均為0,類似於 [ [0,0,0,0,1,0,0,0,0,0],[0,1,0,0,0,0,0,0,0,0] , [...] ... ] 陣列)。 為了將 整數目標值轉換為分類目標值,可以使用 Keras 實用函式 to_categorical(int_labels, num_classes=None)
2.6 sparse_categorical_crossentropy 稀疏交叉熵
1 def sparse_categorical_crossentropy(y_true, y_pred, from_logits=False, axis=-1):
2 y_pred = ops.convert_to_tensor_v2_with_dispatch(y_pred)
3 y_true = math_ops.cast(y_true, y_pred.dtype)
4 return backend.sparse_categorical_crossentropy(
5 y_true, y_pred, from_logits=from_logits, axis=axis)
SCCE 稀疏多分類交叉熵與 CCE 多分類交叉熵的實現方式相類似,主要用於分類演算法,只是目標值輸出值格式略有不同,CEE以向量作為輸出,而SCEE 則直接轉化為索引值進行輸出。例如同一組測試資料如果有10個分類CEE 的輸出方式是 [-1,10],類似於 [ [0,0,0,0,1,0,0,0,0,0],[0,1,0,0,0,0,0,0,0,0] , [...] ... ] 陣列)。而SCCE的輸出方式將會是[-1,1],即類似 [ [1],[3],[4],[9] ..... ] 型別的陣列。
2.7 binary_crossentropy 二進位制交叉熵
def binary_crossentropy(y_true, y_pred, from_logits=False, label_smoothing=0):
y_pred = ops.convert_to_tensor_v2_with_dispatch(y_pred)
y_true = math_ops.cast(y_true, y_pred.dtype)
label_smoothing = ops.convert_to_tensor_v2_with_dispatch(
label_smoothing, dtype=backend.floatx()) def _smooth_labels():
return y_true * (1.0 - label_smoothing) + 0.5 * label_smoothing y_true = smart_cond.smart_cond(label_smoothing, _smooth_labels,
lambda: y_true)
return backend.mean(
backend.binary_crossentropy(
y_true, y_pred, from_logits=from_logits), axis=-1)
CCE 和 SCCE 主要用於多分類, 而 BCE 更適用於二分類,由於CCE 需要輸出 n_class 個通道而 BCE 只需要輸出一條通道,所以同一組測試資料往往 BCE 的執行效率會更高。需要注意的是,如果使用BCE損失函式,則節點的輸出應介於(0-1)之間,這意味著你必須在最終輸出中使用sigmoid啟用函式。
2.8 hinge 合頁
1 def hinge(y_true, y_pred):
2 return K.mean(K.maximum(1. - y_true * y_pred, 0.), axis=-1)
它通常用於 "maximum-margin" 二分類任務中,如 SVM 支援向量機。由公式可以看出,使用 hinge 損失函式會使( yi * yi^)>1 的樣本損失皆為0,由此帶來了稀疏解,使得 svm 僅通過少量的支援向量就能確定最終超平面。關於 SVM 支援向量機模型在 《 Python 機器學習實戰 —— 監督學習(下)》中有詳細介紹,有興趣的朋友可以開啟連結。
2.9 squared_hinge 平方合頁
1 def squared_hinge(y_true, y_pred):
2 y_pred = ops.convert_to_tensor_v2_with_dispatch(y_pred)
3 y_true = math_ops.cast(y_true, y_pred.dtype)
4 y_true = _maybe_convert_labels(y_true)
5 return backend.mean(
6 math_ops.square(math_ops.maximum(1. - y_true * y_pred, 0.)), axis=-1)
squared_hinge 平方合頁損失函式與 hinge 類似,只有取最大值時加上平方值,與常規 hinge 合頁損失函式相比,平方合頁損失函式對離群值的懲罰更嚴厲,一般多於二分類計算。
2.10 categorical_hinge 多類合頁
1 def categorical_hinge(y_true, y_pred):
2 y_pred = ops.convert_to_tensor(y_pred)
3 y_true = math_ops.cast(y_true, y_pred.dtype)
4 pos = math_ops.reduce_sum(y_true * y_pred, axis=-1)
5 neg = math_ops.reduce_max((1. - y_true) * y_pred, axis=-1)
6 return math_ops.maximum(0., neg - pos + 1.)
categorical_hinge 更多用於多分類形式
2.11 log_cosh
1 def log_cosh(y_true, y_pred):
2 y_pred = ops.convert_to_tensor_v2_with_dispatch(y_pred)
3 y_true = math_ops.cast(y_true, y_pred.dtype)
4
5 def _logcosh(x):
6 return x + math_ops.softplus(-2. * x) - math_ops.cast(
7 math_ops.log(2.), x.dtype)
8
9 return backend.mean(_logcosh(y_pred - y_true), axis=-1)
log_cosh 適用於迴歸,且比L2更平滑。它的計算方式是預測誤差的雙曲餘弦的對數,對於較小的 x , log ( cosh(x))近似等於 x2 / 2 。對於大的 x,近似於 |x| - log2 。這表示 'logcosh' 與均方誤差演算法大致相同,但是不會受到偶發性錯誤預測的強烈影響。
2.12 huber
1 def huber(y_true, y_pred, delta=1.0):
2 y_pred = math_ops.cast(y_pred, dtype=backend.floatx())
3 y_true = math_ops.cast(y_true, dtype=backend.floatx())
4 delta = math_ops.cast(delta, dtype=backend.floatx())
5 error = math_ops.subtract(y_pred, y_true)
6 abs_error = math_ops.abs(error)
7 half = ops.convert_to_tensor_v2_with_dispatch(0.5, dtype=abs_error.dtype)
8 return backend.mean( array_ops.where_v2(abs_error <= delta,
9 half * math_ops.square(error),
10 delta * abs_error - half * math_ops.square(delta)),
11 axis=-1)
Huber 適用於迴歸, 它是平滑的平均絕對誤差,優點是能增強平方誤差損失函式對離群點的魯棒性。當預測偏差小於 δ(delta)時,它採用平方誤差,當預測偏差大於 δ 時,採用的絕對值誤差,誤差降到多小才變為平方誤差由超引數δ。相比於均方誤差,Huber 降低了對離群點的懲罰程度,所以 Huber 是一種常用的魯棒的迴歸損失函式。也就是說當Huber 損失在 [0-δ,0+δ] 之間時,等價為MSE,而在 [-∞,δ] 和 [δ,+∞] 時相當於 MAE。這裡超引數 δ(delta)的選擇非常重要,因為這決定了對異常點的定義。當殘差大於 δ(delta),應當採用 L1(對較大的異常值不那麼敏感)來最小化,而殘差小於超引數,則用 L2 來最小化。
2.13 poisson 泊松損失函式
1 def poisson(y_true, y_pred):
2 y_pred = ops.convert_to_tensor_v2_with_dispatch(y_pred)
3 y_true = math_ops.cast(y_true, y_pred.dtype)
4 return backend.mean(
5 y_pred - y_true * math_ops.log(y_pred + backend.epsilon()), axis=-1)
poisson 用於迴歸演算法,一般用於計算事件發性的概率
2.14 cosine_similarity 餘旋相似度
1 def cosine_similarity(y_true, y_pred, axis=-1):
2 y_true = nn.l2_normalize(y_true, axis=axis)
3 y_pred = nn.l2_normalize(y_pred, axis=axis)
4 return -math_ops.reduce_sum(y_true * y_pred, axis=axis)
預測值與真實標籤的餘弦距離平均值的相反數,它是一個介於-1和1之間的數字。當它是負數時在-1和0之間,0表示正交,越接近-1 表示相似性越大,值越接近1表示不同性越大,這使得它在設定中可用作損失函式。如果' y_true '或' y_pred '是一個零向量,餘弦無論預測的接近程度如何,則相似度都為 0,而與預測值和目標值之間的接近程度無關。
2.15 kl_divergence 散度
1 def kl_divergence(y_true, y_pred):
2 y_pred = ops.convert_to_tensor_v2_with_dispatch(y_pred)
3 y_true = math_ops.cast(y_true, y_pred.dtype)
4 y_true = backend.clip(y_true, backend.epsilon(), 1)
5 y_pred = backend.clip(y_pred, backend.epsilon(), 1)
6 return math_ops.reduce_sum(y_true * math_ops.log(y_true / y_pred), axis=-1)
用於分類計算,通過衡量預測值概率分佈到真值概率分佈的相似度差異,在運動捕捉裡面可以衡量未新增標籤的運動與已新增標籤的運動,進而進行運動的分類。
三、Optimizer 優化器
由於輸入資料的特徵很多,所以讓計算損失函式變成很複雜,因此人們想出通過巧妙地利用梯度來計算損失函式最小值,而最常用的計算梯度方法就有梯度下降法和反向傳播法。 通過 model.compile (optimizer , loss , metrics) 中的 optimizer 優化器可繫結 SGD、AdaGrad、Adam、RMSProp 等多種演算法。
3.1 SGD 隨機梯度下降法
1 class SGD(optimizer_v2.OptimizerV2):
2 def __init__(self,
3 learning_rate=0.01,
4 momentum=0.0,
5 nesterov=False,
6 name="SGD",
7 **kwargs):
引數說明
- learning_rate: float 型別,預設值為0.01,表示學習率
- momentum: float 型別,預設值為0,表示動量,大於等於0時可以抑制振盪
- nesterov: bool 型別,預設值為 False,是否啟動 momentum 引數
- name: str型別,預設為 ‘SGD’,演算法名稱
梯度下降法的實現原理在 《 Python 機器學習實戰 —— 監督學習(上)》已經詳細介紹過,就是利用在微積分原理,對多元函式的輸入引數求偏導數,把求得的各個引數的導數以向量的形式寫出來就是梯度。梯度下降是迭代法的一種,通過不斷小幅修改輸入引數,求得切線斜率接近無限於0時的資料點(即鞍點)。通過多次遞迴方式,切線斜率就是無限接近於0,此就是資料點的最小值 。用此方法求得損失函式最小值,當損失函式到達最小值時,權重 w 和偏置量 h 則最符合資料集的特徵。在無約束問題時,梯度下降是最常採用的方法之一。在使用 SGD 時,最重要的是選擇合適的學習率 learning rate 比較困難 ,學習率太低會收斂緩慢,學習率過高會使收斂時的波動過大。
而梯度下降法也存在一定問題,因為所有引數都是用同樣的 learning rate,在複雜的資料集情況下,損失函式就猶如一個小山丘,有多處小坑其梯度(偏導數)都為0,此時會有多個區域性最小值(local minimum)和一個是全域性最小值(global minimum。到達區域性最小值時梯度為0,如果 learning rate 太小,無論移向哪一方,其梯度都會增加,運算就會停止在坑中。因此,區域性最小值就會被誤認為全域性最小值。
遇到此情況可嘗試調節學習率 learning rate 或新增動量 momentum 加速收斂,但並不解決所有問題。
若想要加速收斂速度,可以嘗試新增動量 momentum ,使用 momentum 的演算法思想是:引數更新時在一定程度上保留之前更新的方向,同時又利用當前batch的梯度微調最終的更新方向,簡言之就是通過積累之前的動量來加速當前的梯度。
在梯度方向改變時,momentum 能夠降低引數更新速度,從而減少震盪;在梯度方向相同時,momentum可以加速引數更新, 從而增加衝過小坡的可能性。總而言之,momentum 能夠加速SGD收斂,抑制震盪。
3.2 AdaGrad 演算法
1 class Adagrad(optimizer_v2.OptimizerV2):
2 def __init__(self,
3 learning_rate=0.001,
4 initial_accumulator_value=0.1,
5 epsilon=1e-7,
6 name='Adagrad',
7 **kwargs):
引數說明
- learning_rate: float 型別,預設值為0.001,表示學習率
- initial_accumulator_value:float 型別,預設值為 0.1 ,累加器的初始值
- epsilon:float 型別,預設值為 1e-7,為的是避免計算時出現分母為 0 的特殊情況。
- name: str型別,預設為 ‘Adagrad’,演算法名稱
AdaGrad 也稱自適應梯度法,它是 SGD 的一個優化演算法,由 John Duchi 等人在 2011 年於 Adaptive Subgradient Methods for Online Learning and Stochastic Optimization 提出。相比起 SGD 它可以不斷地自動調整學習率,當初期梯度矩陣平方的累積較小時,學習率相對比較快,到後期梯度矩陣平方的累積較大時,學習率會相對降低。
AdaGrad 的原理大概就是累計每一次梯度的矩陣平方,接著讓學習率除以它的開方。這個的作用是為了不斷地改變學習率。在前期,梯度累計平方和比較小,也就是 r 相對較小,這樣就能夠放大梯度對權重的影響力; 隨著迭代次數增多,梯度累計矩陣平方和也越來越大,即 r 也相對較大,梯度對權重的影響力變得越來越小。
假設訓練集中包含有 n 個樣本 xi ,其對應的輸出值為 yi,權重為 ωi ,學習率為 ϵ,則針對損失函式 L 的梯度 g 的計算公式如下:
r 為梯度矩陣平方的累積變數,初始值由 initial_accumulator_value 引數確定,預設為 0.1
計算權重的更新值 Δ ω,其中 δ 為小常數即引數 epsilon,預設值為 1e-7,為的是避免出現分母為 0 的特殊情況。而 ϵ 為學習率,預設值為0.001。由於分母越大值越小,這反映在圖上就是在初始階段,累積梯度矩陣平方值 r 較小,因此剛開發訓練時變化會較快。但隨著 r 值累計越來越大,變化會越來慢。但有個壞處就是有可能導致累計梯度矩陣平方 r 增速過大,權重的變化過早減小,學習率過早降低的情況。
最後更新權重值
3.3 RMSProp 演算法
1 class RMSprop(optimizer_v2.OptimizerV2):
2 def __init__(self,
3 learning_rate=0.001,
4 rho=0.9,
5 momentum=0.0,
6 epsilon=1e-7,
7 centered=False,
8 name="RMSprop",
9 **kwargs):
- learning_rate: float 型別,預設值為0.001,表示學習率
- rho : float 型別 ,預設值0.9,衰減速率, 即是等式中的 ρ。
- epsilon : float 型別,預設值為 1e-7,為的是避免計算時出現分母為 0 的特殊情況。
- momentum :float型別,預設值 0.0,即方程中的動量係數 α 。
- centered:bool型別,預設值為False。如果為True,則通過梯度的估計方差,對梯度進行歸一化;如果False,則由未centered的第二個moment歸一化。將此設定為True有助於模型訓練,但會消耗額外計算和記憶體資源。預設為False。
- name:str型別,預設為 ‘RMSprop’,演算法名稱
RMSProp 演算法是 AdaGrad 演算法的改量版,上面說過AdaGrad最大的問題在於當累計梯度矩陣平方 r 早期增速過大時,會導致權重的變化過早減小,學習率過早降低的情況。為解決這個問題,RMSProp 加入了衰減速率引數 rho ,使梯度矩陣平方的累積變數 r 值增加的速度更加平穩,從而避免權重的變化過早減小,學習率過早降低的情況。
假設訓練集中包含有 n 個樣本 xi ,其對應的輸出值為 yi,權重為 ωi ,學習率為 ϵ,則針對損失函式 L 的梯度 g 的計算公式如下:
r 為梯度矩陣平方的累積變數,它的計算方式與AdaGrad 略有不同,在原有基礎上加入衰減速率 ρ,預設值為 0.9,r 的變化率會受到衰減率 ρ 設定的影響。
計算權重的更新值 Δ ω,其中 δ 為小常數即引數 epsilon,預設值為 1e-7,為的是避免出現分母為 0 的特殊情況。而 ϵ 為學習率,預設值為0.001。與 AdaGrad相似,在累積梯度矩陣平方值 r 較小,因此剛開發訓練時變化會較快。但隨著 r 值累計越來越大,變化會越來慢。然而不同在於,由於 r 的取值加入了衰減速率 ρ 的控制,所以其減速現象明顯得以抑制,避免了權重的變化過早減小,學習率過早降低的情況。若要調整權重更新值 Δ ω 的佔比,也可設定動量係數 α。
由於動量係數 α (即引數 momentum )預設值為 0,所以預設情況下權重更新值 Δ ω 與 AdaGrad 類似。
最後更新權重值
3.4 Adam 演算法
1 class Adam(optimizer_v2.OptimizerV2):
2 def __init__(self,
3 learning_rate=0.001,
4 beta_1=0.9,
5 beta_2=0.999,
6 epsilon=1e-7,
7 amsgrad=False,
8 name='Adam',
9 **kwargs):
- learning_rate: float 型別,預設值為0.001,表示學習率
- beta_1:float 型別,預設值為 0.9 ,指定一階矩估計的指數衰減率 ρ1
- beta_2:float 型別,預設值為 0.999 , 指定二階矩估計的指數衰減率 ρ2
- epsilon:float 型別,預設值為 1e-7,為的是避免計算時出現分母為 0 的特殊情況。
- amsgrad: bool 型別,預設值為 False,是否應用該演算法的 AMSGrad 變體
- name: str型別,預設為 ‘Adam’,演算法名稱
Adam 相當於把 AdaGrad 演算法融合了動量 momentum 的概念,整合出來的新演算法,由 Diederik Kingma 等人 2014 年在 A Method for Stochastic Optimization .arXiv:1412.6980 提出。實現偏置校正是 Adam 的特徵,這使 Adam 演算法的運算效率更高。它分別指定了一階矩估計的指數衰減率 ρ1 和二階矩估計的指數衰減率 ρ2,隨著訓練集的迴圈會不斷更新一階矩偏差 s^ 和 二階矩偏差 r^,從而計算出計算權重的更新值 Δ ω 。
假設訓練集中包含有 n 個樣本 xi ,其對應的輸出值為 yi,權重為 ωi ,學習率為 ϵ,則針對損失函式 L 的梯度 g 的計算公式如下:
Adam 把 RMSProp 中的梯度矩陣平方的累積變數的演算法轉換成計算一階矩偏差 s^ 和 二階矩偏差 r^,最後用它們的比值 s ^ / sqrt( r ^ ) 得出結果 。這樣做梯度經過偏置校正,每一次迭代學習率都有一個固定範圍,因此學習流程更加平穩。s 與 r 的初始值均為 0 ,指數衰減率 ρ1、ρ2 的初始值預設為 0.9 和 0.999 。每次迴圈,系統都會根據累積變數 s、r 修正偏差值 s^、 r^。
更新一階矩累積變數
更新二階矩估計累積變數
跟隨訓練,迴圈修正一階矩的偏差值 s^
跟隨訓練,迴圈修正二階矩的偏差值 r^
計算權重的更新值 Δ ω,其中 δ 為小常數即引數 epsilon,預設值為 1e-7,為的是避免出現分母為 0 的特殊情況。而 ϵ 為學習率,預設值為0.001。
最後更新權重值
四、啟用函式
在第一節曾經介紹到,每個神經元要傳播到下一層時,都需要通過啟用函式,我們可以把這看成是輸入訊號的加權和轉化為輸出訊號的的一個過程,它為神經元提供了模擬非線性資料集所必需的非線性特徵。正因為大部分神經元之間的資料都存在非線性關係,所以啟用函式都非線性的,要不然隱藏層就會失去其意義。最常見的啟用函式有階躍啟用函式、Sigmoid 啟用函式、ReLU啟用函式、Tanh 啟用函式、Softmax啟用函式等,下面將一一介紹。
4.1 階躍啟用函式
這是最簡單的一種啟用方式,它以0為臨界時,當輸入值 input 小於0 時返回 0,大於 0 時返回 1。由於它的值呈階梯式變化,因為被稱為階躍啟用函式(也稱閾值啟用函式)。但是這簡單方法的缺點也是非常明顯的。 首先它是不連續且不光滑的,這就導致在反向傳播時這一層很難學習。 其次階躍函式有著 “非黑即白” 的特性,所以一般只適用於簡單的二分類非線性啟用。由於它在 x=0 時不具有連貫性,因此不適合用於梯度下降的資料訓練中。
1 def func(x):
2 return np.array(x>0)
3
4 x=np.linspace(-5,5,50)
5 y=func(x)
6 plt.xlabel('input')
7 plt.ylabel('output')
8 plt.title('Step Function Activation Func')
9 plt.plot(x,y)
10 plt.show()
4.2 Sigmoid 啟用函式
sigmoid 的輸出函式由 f(x)=1/(1+exp(-x)) 確定,在tensorflow 中可以通過 tf.sigmoid 呼叫,由於形狀很像 S,因此命名為 sigmoid 。相比起階躍啟用函式,sigmoid 在0~1之間值過渡顯得更平滑,但在兩個邊緣值梯度都無窮接近於 0,這非常容易造成 “梯度消失”,因此為優化訓練增加了難度。此外 sigmoid 函式輸出值在 0 到 1 之間,其均值為0.5,不符合神經網路內數值期望為 0 的設想。一般情況 sigmoid 用於隱藏層的啟用或輸出層的迴歸。
1 def func(x):
2 return 1/(1+np.exp(-x))
3
4 x=np.linspace(-5,5,50)
5 y=func(x)
6 plt.xlabel('input')
7 plt.ylabel('output')
8 plt.title('Sigmoid Activation Func')
9 plt.plot(x,y)
10 plt.show()
4.3 Tanh 啟用函式
tanh 雙曲正切啟用函式公式由 f(x)=(1-exp(-2x)) / (1+exp(-2x)) 確定,在tensorflow 中可以通過 tf.tanh 呼叫。它相當於 sigmoid 的改良版,其輸入值從 -1 到 1 之間均值為0,這正解決了 sigmoid 均值為0.5 的缺陷,並且它的切線比 sigmoid 更陡峭。然而 tanh 的兩個邊緣值梯度也是無窮接近於 0, 並不能解決 sigmoid “梯度消失” 的問題。一般情況 tanh 會用於隱藏層的啟用或輸出層的迴歸。
1 def func(x):
2 return (1-np.exp(-2*x))/(1+np.exp(-2*x))
3
4 x=np.linspace(-5,5,50)
5 y=func(x)
6 plt.xlabel('input')
7 plt.ylabel('output')
8 plt.title('Tanh Activation Func')
9 plt.plot(x,y)
10 plt.show()
4.4 ReLU啟用函式
ReLU 啟用函式是分段線性函式,它能在多層啟用時捕獲非線性特徵,在tensorflow 中可以通過 tf.nn.relu 呼叫。在輸入為正數的時候,其輸出值為無窮大,彌補了sigmoid函式以及tanh函式的梯度消失問題。而且 ReLU 函式只有線性關係,因此不管是前向傳播還是反向傳播,計算速度都比 sigmod 和 tanh 要快。ReLU 最大的問題在於當輸入值小於0 時,梯度一直為0,因此產生梯度消失問題。儘管如此,ReLU 也是最常用的啟用函式之一,常用於隱藏層的啟用和輸出層的迴歸。
1 def func(x):
2 return np.maximum(0,x)
3
4 x=np.linspace(-5,5,50)
5 y=func(x)
6 plt.xlabel('input')
7 plt.ylabel('output')
8 plt.title('ReLU Activation Func')
9 plt.plot(x,y)
10 plt.show()
4.5 Leaky ReLU 啟用函式
Leaky ReLU 啟用函式是 ReLU 的改良版,在 tensorflow 中可以通過 tf.nn.leaky_relu 呼叫。它是為了改善 ReLU 輸入值小於0 時,梯度一直為 0 的問題而設計的。在 Leaky ReLU 中當輸入值小於0時,輸出值將為 ax,因此不會造成神經元失效。當神經元輸出值有可能出現小於0的情況,就會使用 Leaky ReLU 輸出,常用於隱藏層的啟用和輸出層的迴歸。
1 def func(x,a=0.01):
2 return np.maximum(a*x,x)
3
4 x=np.linspace(-5,5,50)
5 y=func(x)
6 plt.xlabel('input')
7 plt.ylabel('output')
8 plt.title('Leaky ReLU Activation Func')
9 plt.plot(x,y)
10 plt.show()
4.6 Softmax 函式
Softmax 的計算公式如下,在tensorflow中可直接通過 tf.nn.softmax 呼叫,一般用作輸出層的分類啟用函式,它表示每個分類的輸出概率,所有輸出概率合共為 1。
1 def func(x):
2 return np.exp(x)/np.sum(np.exp(x))
3
4 x=np.linspace(-5,5,50)
5 y=func(x)
6 plt.xlabel('input')
7 plt.ylabel('output')
8 plt.title('Softmax Activation Func')
9 plt.plot(x,y)
10 plt.show()
到此歸納總結一下階躍啟用函式、Sigmoid 啟用函式、Tanh 啟用函式、ReLU啟用函式、Leaky ReLU 啟用函式、Softmax 函式和恆等輸出的應用場景。一般階躍啟用函式只用於簡單的二分類輸出,Sigmoid 和 Tanh 啟用函式用於隱藏層的輸出或輸出層的迴歸,但兩者皆會存在輸出梯度消失的風險。ReLU 和 Leaky ReLU 啟用函式可用於隱藏層的輸出或輸出層的迴歸,它能消除梯度消失的風險,而且效能高。當輸出值為正值時可使用 ReLU,當輸出值存在負值的可能時使用 Leaky ReLU 。而在輸出層的分類計算可使用 Softmax 函式,在輸出層的迴歸計算也可使用恆等函式,即對輸入資訊不作任何修改直接輸出。
五、多層感知機 MLP
多層感知機 MLP,它以層為組織搭建:至少包含一個一個輸入層,一個或多個隱藏層,一個輸出層。資料通過輸入層輸入,通過多個隱藏層傳遞,通過輸出層輸出,過程中層與層之間沒有資訊反饋,因此被稱為前饋神經網路 FNN。當資料傳到輸出層時,系統會把輸出資料與正確資料進行對比,把梯度反饋同時更新權重,如此反覆迴圈,最後把誤差減到最小。
5.1 三層分類感知機
下面從最簡單的分類感知機開始介紹,用 mnist 資料進行測試,當中只包含一個輸入層,一個隱藏層,一個輸出層 。為了方便講解,第一個例子先用 tensorflow 1.x 版本進行講解,先用佔位符設定輸入資料X,y。由於 mnist 的影象是28*28 畫素,0~9 的數字,這個感知機的目的就是把 784 特徵的資料分成 0~9 的10類,所以隱藏層引數形狀應為 [784,10],h 為 [10] 。通過公式 y=w*x+h 進行計算,最後 softmax 分類函式輸出。由於是分類計算,所以損失函式選用了常用的交叉熵損失函式,演算法使用 Adam 演算法,把學習率設定為 0.3 。輸入測試資料,分多批每批 500 個進行訓練,每隔200個輸出一次正確率,如此迴圈訓練10次。可見10次後,測試資料的準確率已經達到 91%,最後檢視測試資料的準確率,也將近有 90%。
這就是最簡單的三層感知機 ,可見其結構原理比較簡單,然後使用 tensorflow 1.x 的方法略顯繁瑣。下面介紹一下多層感知機,使用 tensorflow 2.x 進行編寫,可讀性會更高。
def test():
tf.disable_eager_execution()
X=tf.placeholder(tf.float32,[None,784])
y=tf.placeholder(tf.float32,[None,10])
# 隱藏層引數w0,h0
w0=tf.Variable(tf.random_normal([784,10],stddev=0.1))
h0=tf.Variable(tf.random_normal([10],stddev=0.1))
# 計算 logits
logits=tf.matmul(X,w0)+h0
# 計算輸出值 y_
y_=tf.nn.softmax(logits)
# 交叉熵損失函式
cross_entropy=tf.nn.softmax_cross_entropy_with_logits(logits=logits,labels=y)
cross_entropy=tf.reduce_mean(cross_entropy)
# Adam 演算法,學習率為 0.3
train_step=tf.train.AdamOptimizer(0.3).minimize(cross_entropy)
# 計算準確率
correct=tf.equal(tf.argmax(y_,1),tf.argmax(y,1))
accuray=tf.reduce_mean(tf.cast(correct,tf.float32))
# 計入測試資料
(X_train,y_train),(X_test,y_test)=datasets.mnist.load_data()
with tf.Session() as session:
session.run(tf.global_variables_initializer())
#訓練10次
for epoch in range(10):
#分批處理訓練資料,每批500個數據
start=0
n=int(len(X_train)/500)
print('---------------epoch'+str(epoch)+'---------------')
for index in range(n):
end = start + 500
batch_X,batch_y=X_train[start:end],y_train[start:end]
batch_X=batch_X.reshape(500,784)
batch_y=keras.utils.to_categorical(batch_y)
#分批訓練
train_,cross,acc=session.run([train_step,cross_entropy,accuray]
,feed_dict={X:batch_X,y:batch_y})
if index%200==0:
# 每隔200個輸出準確率
print(' accuray:'+str(acc*100))
start+=500
#處理測試資料輸出準確率
X_test=X_test.reshape(-1,784)
y_test=keras.utils.to_categorical(y_test)
accuray=session.run(accuray,feed_dict={X:X_test,y:y_test})
print('--------------test data-------------\n accuray:'+str(accuray*100))
執行結果
5.2 多層感知機
還是以 mnist 為例子,通過 5 層的訓練,神經元數目從 784 逐層下降 200、100、60、30、10,最後通過 softmax 函式輸出。前面介紹過 tensorflow 2.0 已經融入 keras 庫,因此可以直接使用層 layer 的概念,先建立一個 model,然後通過 model.add(layer) 方法,加入每層的配置。完成層設定後,呼叫 model.compile(optimizer, loss, metrics) 可繫結損失函式和計算方法。最後用 model.fit() 進行訓練,分批的資料量和重複訓練次數都可以直接通過引數設定。相比起 tensorflow 1.x 可讀性更強,而且能支援多平臺執行,它去除了佔位符的概念,開發時無受到 session 的約束,而是直接通過函式來呼叫。通過 5 層的神經元,正確率可提升到將近 93%。
1 def getModel():
2 # 神經元數目從 784 逐層下降 200、100、60、30、10,最後通過 softmax 函式輸出
3 model=keras.models.Sequential()
4 model.add(layers.Flatten(input_shape=(28,28)))
5 model.add(layers.Dense(units=200,activation='relu'))
6 model.add(layers.Dense(units=100,activation='relu'))
7 model.add(layers.Dense(units=60,activation='relu'))
8 model.add(layers.Dense(units=30,activation='relu'))
9 model.add(layers.Dense(10,activation='softmax'))
10 return model
11
12 def test():
13 # 獲取資料集
14 (X_train,y_train),(X_test,y_test)=keras.datasets.mnist.load_data()
15 X_train,y_train=tf.convert_to_tensor(X_train,tf.float32) , tf.convert_to_tensor(y_train,tf.float32)
16 # 建立 model
17 model=getModel()
18 # 使用 SGD 梯度下降法,學習率為 0.003
19 # 使用交叉熵演算法
20 model.compile(optimizer=optimizers.SGD(0.003),
21 loss=losses.sparse_categorical_crossentropy,
22 metrics=['accuracy'])
23 # 繫結 tensorboard 對日誌資料進行監測
24 callback=keras.callbacks.TensorBoard(log_dir='logs', histogram_freq=1, embeddings_freq=1)
25 # 重複訓練30次,每 500 個作為一批
26 model.fit(X_train,y_train,epochs=30,batch_size=500,callbacks=callback)
27 # 輸出測試資料準確率
28 X_test, y_test = tf.convert_to_tensor(X_test, tf.float32), tf.convert_to_tensor(y_test, tf.float32)
29 print('\n-----test data------')
30 model.fit(X_test,y_test)
執行結果
通過 keras.callbacks.TensorBoard(log_dir='日誌路徑')直接繫結日誌目錄,訓練時繫結回撥函式即可將檢測資料寫入日誌 model.fit (x=測試資料, y=正確輸出結果,batch_size=分批執行時每批數量, epochs=訓練重複次數, callbacks=繫結回撥),最後通過命令 “ tensorboard --logdir=日誌路徑 “ 即可在瀏覽器 http://localhost:6006/ 上檢視日誌。
5.3 多層感知機迴歸測試
以波士頓房價作為測試資料集,嘗試使用多層感知機對未來房價進行預測,看一下測試結果如何。首先建好 Model,測試資料集有13個特徵,把神經元擴充套件到 20、50 個。由於是迴歸計算,所以輸出層使用 sigmoid 所以輸出值只需要一列。由於 boston 資料集中有多列資料,大小不一,所以在輸入可以前先利用 MinMaxScaler 進行歸一化處理。注意測試資料集中 y_train 和 y_test只有一列,所以在歸一化處理前先要先利用 y_train[:,np.newaxis] 或其他方式進行行列調換,不然系統將報錯。然後使用 Adam 演算法,huber 損失函式進行10次訓練。
完成訓練後,對比測試資料與原資料的值。可見經過15次訓練後,損失率已到達0.003 以下,測試值與真實值已經相當接近。
1 def getModel():
2 # 神經元從13到20、50,輸出層使用sigmoid啟用函式
3 model=keras.models.Sequential()
4 model.add(layers.Flatten())
5 model.add(layers.Dense(units=20,activation='relu'))
6 model.add(layers.Dense(units=50,activation='relu'))
7 model.add(layers.Dense(units=1,activation='sigmoid'))
8 return model
9
10 def test():
11 # Boston房價測試資料集
12 (X_train,y_train),(X_test,y_test)=keras.datasets\
13 .boston_housing.load_data()
14 # 資料多列大於1,所以先把資料進行歸一化處理
15 scale = MinMaxScaler()
16 X_train = scale.fit_transform(X_train)
17 X_test = scale.fit_transform(X_test)
18 y_train=y_train[:,np.newaxis]
19 y_train=scale.fit_transform(y_train)
20 y_test=y_test[:,np.newaxis]
21 y_test=scale.fit_transform(y_test)
22 # 生成 Model
23 model=getModel()
24 # 使用Adam演算法,學習率為0.003,huber損失函式
25 model.compile(optimizer=optimizers.Adam(0.003)
26 ,loss=losses.huber,metrics=['accuracy'])
27 # 回撥生成日誌記錄
28 callback=keras.callbacks.TensorBoard(log_dir='logs'
29 , histogram_freq=1, embeddings_freq=1)
30 # 訓練資料訓練
31 model.fit(X_train,y_train,10,epochs=15,callbacks=callback)
32 # 計算測試輸出資料
33 y_hat=model.predict(X_test)
34 # 畫圖物件測試輸出資料與真實輸出資料差別
35 x=np.linspace(0,1,len(y_hat))
36 # 把單列資料變形返回單行資料
37 y1=y_hat.flatten()
38 y2=y_test.flatten()
39 # 畫出對比圖
40 plt.scatter(x,y1,marker='^',s=60)
41 plt.scatter(x,y2,marker='*',s=60)
42 plt.title('Boston_Housing Test vs Actual')
43 plt.legend(['test data','actual data'])
44 plt.show()
執行結果
對比圖
損失函式圖
六、利用 Dropout 進行正則化
6.1 回顧 L1/L2 正則化處理
過擬合就是說模型在訓練資料上的效果遠遠好於在測試集上的效能,引數越多,模型越複雜,而越複雜的模型越容易過擬合。記得在《Python 機器學習實戰 —— 監督學習(上)》的第四節曾經介紹過通過正則化處理過擬合問題,常用的處理方式方式 L1/ L2 兩種:
- L1 正則化則是以累加絕對值來計算懲罰項,因此使用 L1 會讓 W(i) 元素產生不同量的偏移,使某些元素為0,從而產生稀疏性,提取最有效的特徵進行計算。
- L2 正則化則是使用累加 W 平方值計算懲罰項,使用 L2 時 W(i) 的權重都不會為0,而是對每個元素進行不同比例的放縮。 此時可以考慮正則化,通過設定正則項前面的 hyper parameter,來權衡損失函式和正則項,減小引數規模,達到模型簡化的目的,從而使模型具有更好的泛化能力。
6.2 Dropout 優化處理
而在 MLP 中也提供了 dropout 對過擬合的資料進行正則化處理,它的處理方式是在學習階段,設定丟失神經元的概率,當一個神經元被丟棄時,它的輸出值被設為0。由於神經元在每次新的訓練中被隨機丟棄,所以每個訓練階段其丟失的神經元都不相同。在面對複雜的資料集時,很多時候 dropout 會跟 L2 正則化同時使用以降低過擬合情況。
下面的例子以 mnist 資料集為例子,經過五層的訓練,每層訓練都加入 5% 的丟失率進行正則化處理。反覆訓練 30 次後,測試資料的準確率依然達到 90%,可見 dropout 對避免過擬合是有一定的效果。
1 def getModel():
2 # 神經元數目從 784 逐層下降 200、100、60、30、10,最後通過 softmax 函式輸出
3 model=keras.models.Sequential()
4 model.add(layers.Flatten(input_shape=(28,28)))
5 model.add(layers.Dense(units=200,activation='relu'))
6 model.add(layers.Dropout(rate=0.05))
7 model.add(layers.Dense(units=100,activation='relu'))
8 model.add(layers.Dropout(rate=0.05))
9 model.add(layers.Dense(units=60,activation='relu'))
10 model.add(layers.Dropout(rate=0.05))
11 model.add(layers.Dense(units=30,activation='relu'))
12 model.add(layers.Dropout(rate=0.05))
13 model.add(layers.Dense(units=10,activation='softmax'))
14 return model
15
16 def test():
17 # 獲取資料集
18 (X_train,y_train),(X_test,y_test)=keras.datasets.mnist.load_data()
19 X_train,y_train=tf.convert_to_tensor(X_train,tf.float32) , tf.convert_to_tensor(y_train,tf.float32)
20 # 建立 model
21 model=getModel()
22 # 使用 SGD 梯度下降法,學習率為 0.003
23 # 使用交叉熵演算法
24 model.compile(optimizer=optimizers.SGD(0.003),
25 loss=losses.sparse_categorical_crossentropy,
26 metrics=['accuracy'])
27 # 繫結 tensorboard 對日誌資料進行監測
28 callback=keras.callbacks.TensorBoard(log_dir='logs', histogram_freq=1, embeddings_freq=1)
29 # 重複訓練50次,每 500 個作為一批
30 model.fit(X_train,y_train,epochs=30,batch_size=500,callbacks=callback)
31 # 輸出測試資料準確率
32 X_test, y_test = tf.convert_to_tensor(X_test, tf.float32), tf.convert_to_tensor(y_test, tf.float32)
33 print('\n-----test data------')
34 model.fit(X_test,y_test)
執行結果
本篇總結
本文主要介紹了MSE、MAE、CEE 、Hinge、Huber 等 15 個常用損失函式的計算方式和使用場景,分析 SGD、AdaGrad、Adam、RMSProp 4類優化器的公式原理,對階躍啟用函式、Sigmoid 啟用函式、ReLU啟用函式、Leaky ReLU 啟用函式、Tanh 啟用函式、Softmax啟用函式等進行講解。
多層感知器 MLP 是深度學習的基礎,本文通過分類、迴歸的使用例子對 MLP 的使用進行介紹。最後,講解了如何使用 dropout 正則化對複雜型別的資料集進行優化處理。
希望本篇文章對相關的開發人員有所幫助,由於時間倉促,錯漏之處敬請點評。
後面的文章將開始對 CNN 卷積神經網路和 RNN 迴圈神經網路進行介紹,敬請留意!
對 .Python 開發有興趣的朋友歡迎加入QQ群:790518786 共同探討 !
對 JAVA 開發有興趣的朋友歡迎加入QQ群:174850571 共同探討!
對 .NET 開發有興趣的朋友歡迎加入QQ群:162338858共同探討 !
AI人工智慧相關文章
作者:風塵浪子
https://www.cnblogs.com/leslies2/p/15184548.html
原創作品,轉載時請註明作者及出處