前言
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 等多種算法。
1 @keras_export('keras.Model', 'keras.models.Model') 2 class Model(base_layer.Layer, version_utils.ModelVersionSelector): 3 def compile(self, optimizer='rmsprop', loss=None, 4 metrics=None,loss_weights=None, 5 weighted_metrics=None, run_eagerly=None, 6 steps_per_execution=None, **kwargs):
- optimizer:綁定優化器,用於綁定訓練時所使用的優化器,一般有 SGD、AdaGrad、Adam、RMSProp 等。
- loss:綁定損失函數,有上述所介紹過的 15 種 損失函數可選擇。
- metrics:綁定用於評估當前訓練模型的性能參數,例如 ['acc' , 'losses'] 正確率等,損失函數。評價函數和損失函數相似,只不過評價函數的結果只用於監測數據而不會用於訓練過程中。
- loss_weights:參數類型 列表或字典,例如(0.7,0.3)。模型優化過程中,將損失函數最小化,是指對兩個損失值求和之后最小化,那么這個loss_weights就是給這兩個輸出的損失值加權用的。
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 函數,簡單分類也可用 Sigmoid 邏輯回歸,在輸出層的回歸計算可使用恆等函數,即對輸入信息不作任何修改直接輸出。
五、多層感知機 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 多層感知機
前面介紹過 tensorflow 2.0 已經融入 keras 庫,因此可以直接使用層 layer 的概念,先建立一個 model,然后通過 model.add(layer) 方法,加入每層的配置。完成層設置后,調用 model.compile(optimizer, loss, metrics) 可綁定損失函數和計算方法。最后用 model.fit() 進行訓練,分批的數據量和重復訓練次數都可以直接通過參數設置。
1 @keras_export('keras.Model', 'keras.models.Model') 2 class Model(base_layer.Layer, version_utils.ModelVersionSelector): 3 def fit(self, x=None,y=None,batch_size=None, epochs=1, 4 verbose='auto',callbacks=None, validation_split=0., 5 validation_data=None, shuffle=True, class_weight=None, 6 sample_weight=None,initial_epoch=0, steps_per_epoch=None, 7 validation_steps=None,validation_batch_size=None, 8 validation_freq=1,max_queue_size=10, 9 workers=1, use_multiprocessing=False):
參數說明
- x:輸入數據。如果模型只有一個輸入,那么x的類型是 array,如果模型有多個輸入,那么x的類型應當為list,list的元素是對應於各個輸入的 array
- y:輸入數據的驗證標簽,array
- batch_size:整數,指定進行梯度下降時每個batch包含的樣本數。訓練時一個batch的樣本會被計算一次梯度下降,使目標函數優化一步。
- epochs:整數,訓練終止時的epoch值,訓練將在達到該epoch值時停止,當沒有設置initial_epoch時,它就是訓練的總輪數,否則訓練的總輪數為epochs - inital_epoch
- verbose:日志顯示,0為不在標准輸出流輸出日志信息,1為輸出進度條記錄,2為每個epoch輸出一行記錄
- callbacks:list,其中的元素是keras.callbacks.Callback的對象。這個list中的回調函數將會在訓練過程中的適當時機被調用,參考回調函數
- validation_split:0~1之間的浮點數,用來指定訓練集的一定比例數據作為驗證集。驗證集將不參與訓練,並在每個epoch結束后測試的模型的指標,如損失函數、精確度等。注意,validation_split的划分在shuffle之前,因此如果你的數據本身是有序的,需要先手工打亂再指定validation_split,否則可能會出現驗證集樣本不均勻。
- validation_data:形式為(X,y)的 tuple,是指定的驗證數量集。設定此參數后將覆蓋 validation_spilt。
- shuffle:布爾值或字符串,一般為布爾值,表示是否在訓練過程中隨機打亂輸入樣本的順序。若為字符串“batch”,則是用來處理HDF5數據的特殊情況,它將在batch內部將數據打亂。
- class_weight:字典,將不同的類別映射為不同的權值,該參數用來在訓練過程中調整損失函數(只能用於訓練)
- sample_weight:權值的numpy array,用於在訓練時調整損失函數(僅用於訓練)。可以傳遞一個1D的與樣本等長的向量用於對樣本進行1對1的加權,或者在面對時序數據時,傳遞一個的形式為(samples,sequence_length)的矩陣來為每個時間步上的樣本賦不同的權。這種情況下請確定在編譯模型時添加了sample_weight_mode=’temporal’。
- initial_epoch: 從該參數指定的epoch開始訓練,在繼續之前的訓練時有用。
- validation_steps:驗證數據的批次數
- validation_batch_size:每批次驗證數據的數量
相比起 tensorflow 1.x 可讀性更強,而且能支持多平台運行,它去除了占位符的概念,開發時無受到 session 的約束,而是直接通過函數來調用。
還是以 mnist 為例子,通過 5 層的訓練,神經元數目從 784 逐層下降 200、100、60、30、10,最后通過 softmax 函數輸出。通過 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) 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
原創作品,轉載時請注明作者及出處