飲冰三年-人工智慧-Vue-66 Vue元件化
該篇文件基於kaggle course,通過簡單的理論介紹、程式程式碼、執行圖以及動畫等來幫助大家入門深度學習,既然是入門,所以沒有太多模型推導以及高階技巧相關,都是深度學習中最基礎的內容,希望大家看過之後可以自己動手基於Tensorflow或者Keras搭建一個處理迴歸或者分類問題的簡單的神經網路模型,並通過dropout等手段優化模型結果;
每部分都有對應的練習,練習都是很有針對性的,而且都很有趣,尤其是一些練習中都寫好了動畫的視覺化展示,還是很有心的;
目錄:
- 概述
- 線性模型:單神經元
- 非線性模型:深度神經網路
- 模型訓練:隨機梯度下降
- 驗證模型:過擬合和欠擬合
- 提升效能:Dropout和Batch Normalization
- 分類問題
概述
經過本篇文章,你將搭建自己的深度神經網路,使用Keras和Tensorflow,建立全連線神經網路,在分類和迴歸問題上應用神經網路,通過隨機梯度下降訓練網路、通過dropout等技術提升模型效能;
近些年在AI方面的主要發展都在深度學習,尤其是應用於自然語言處理、影象識別、遊戲AI等領域,深度學習能得到更接近於人類的結果;
深度學習是一種允許大量深度計算為特徵的機器學習方法,深度計算使得深度學習模型可以理解真實世界資料中的複雜和高維的資訊模式,比如這句話的含義是什麼、這張圖中的人在幹嘛等等;
通過這種優勢和靈活性,神經網路成為深度學習的定義模型,神經網路由神經元組成,每個神經元單獨看只是一個簡單的計算單元,神經網路的能力來自於許多神經元之間的複雜的組合模式;
單個神經元
線性單元
只有一個輸入的線性單元對應公式如下:
y=w∗x+by=w∗x+bx
為輸入,神經元連線的權重為w
,w
的更新就是神經網路學習的過程,b
為偏差,它與輸入沒有關係,偏差允許神經元不依賴輸入來修改輸出,y
是神經元的輸出,即公式y=w*x+b
的結果;
線性單元作為模型的例子
神經元通常作為神經網路的一部分,往往也會將一個單獨的神經元模型作為基準模型,單神經元模型是線性模型;
假設我們使用糖分
作為輸入訓練模型,卡路里
作為輸出,假設偏差b
為90,權重w
為2.5,當糖分
為5時,卡路里
為2.5*5+90=102.5
;
多個輸入
當我們期望使用多個輸入而不是一個時,其實就是將多個輸入連線並神經元,計算每個連線權重,並全部加起來得到最終輸出,如下:
上述公式使用了三個輸入,並分別對應各自的連線權重,從輸入維度上看,單個輸入擬合一條直線,兩個輸入你和一個平面,多個輸入擬合的則是超平面;
Keras中使用線性單元
最簡單的建立線性單元模型是通過keras.Sequential
,可以通過dense層來建立上述提到的線性單元模型,對於一個有三個輸入,一個輸出的線性模型,Keras建立方式如下:
from tensorflow import keras
from tensorflow.keras import layers
# Create a network with 1 linear unit
model = keras.Sequential([
layers.Dense(units=1, input_shape=[3])
])
其中units
為1表示該層只有一個輸出,input_shape
為[3]則表示有3個輸入,之所以引數是個列表[],這是因為在影象領域可能需要三維輸入,比如[高度,寬度,通道];
線性單元練習
可以通過這個notebook來進行這部分的練習,裡面包含了如何通過keras搭建線性單元的神經元模型,並通過其weights屬性來檢視模型的連線權重和偏差,最後還有一個未訓練的模型在預測中的表現,可以看到其隨機權重在每次執行結果都不一樣;
深度神經網路
層
典型的神經網路通過層來組織他們的神經元,當我們把線性單元整理到一起時,我們就得到了一個dense層,神經網路通過疊加dense層來將輸入以越來越複雜的方式進行轉換,在一個訓練好的神經網路模型,每一層都會將輸入轉換的更接近結果一點;
啟用函式
啟用函式作用於層的輸出,最常用的是整流函式max(0,x),糾正函式將負部分處理為0,當我們將整流函式應用於一個線性單元時,也就得到了ReLU,而之前的線性公式:
y=w∗x+by=w∗x+b也變成了:
y=max(0,w∗x+b)y=max(0,w∗x+b)可以看到,函式也從線性轉為了非線性,整流函式影象如下:
堆疊dense層
輸出層之前通常有一些隱含層,一般我們不能直接看到他們的輸出(因為他們的輸出並不是最後輸出,而是作為下一層的輸入,因此無法直接看到),注意當處理迴歸問題時,最後一層也就是輸出層是線性單元,也就是沒有應用啟用函式,當我們要處理分類或者其他問題時,仍然需要對應的啟用函式;
通過keras.Sequential
建立多層神經網路方式很簡單,只要從第一層到最後一層依次通過layer
定義即可,第一層獲取輸入,最後一層產生輸出,程式碼如下:
from tensorflow.keras import layers
model = keras.Sequential([
# the hidden ReLU layers
layers.Dense(units=4, activation='relu', input_shape=[2]),
layers.Dense(units=3, activation='relu'),
# the linear output layer
layers.Dense(units=1),
])
其中各個layer
表示各個堆疊的網路層,activation
表示各個層的啟用函式,可以看到最後一層是沒有的,這是因為它處理的是迴歸問題,且最後一層輸出只有一個,而其他層則不一定;
深度神經網路練習
你可以通過這個notebook來進行這部分練習,其中包含如何通過keras.Sequential
搭建3個隱含層1個輸出層的非線性神經網路模型,以及如何使用單獨的啟用層
來代替activation引數,以及ReLU
、eLU
、SeLU
、swish
等各個啟用函式的差異,實驗證明ReLU
適用於大多數場景,因此最適合作為初始啟用函式選擇,下面給出各個接獲函式的影象:
relu:
elu:
selu:
swish:
隨機梯度下降
在之前建立的神經網路模型中,網路中的權重都是隨機指定的,此時的模型還沒有學習到任何東西,這也是第一個練習中每次執行結果都不一樣的原因;
所謂訓練一個神經網路,指的是通過某種方式不斷更新網路中的權重,使得模型通過輸入可以得到期望的輸出,如果可以做到,那麼也說明了這些權重在某種程度上表達了輸入特徵與輸出之間的關係;
訓練模型需要兩個必要元素:
- 損失函式:衡量模型預測結果好壞;
- 優化方法:指導模型如何去修改權重;
損失函式
損失函式用於衡量模型的預測值與真實值之間的差異,不同的問題使用的損失函式一般也是不同的,例如對於迴歸問題,即我們要預測的是數值,一個常用的用於迴歸問題的損失函式為MAE
,即平均絕對誤差,對於每個預測值y_pred
,MAE
計算它與y_true
的差值的絕對值,所有這些絕對值取平均就是MAE的結果,除了MAE,用於迴歸問題的還有很多損失函式,比如MSE
、MASE
、Huber loss
等等,對於模型來說,在訓練過程中,損失函式起到嚮導的作用,最小化損失函式就是模型要解決的問題,以此來指導網路中權重的更新方向;
優化方法 - 隨機梯度下降
通過損失函式我們確定了模型要解決的問題,但是依然需要告知模型如何去解決這個問題,此時就需要一種優化方法,優化方法是一種最小化損失的演算法;
實際上所有應用於深度學習的優化演算法都屬於隨機梯度下降族,它們都是迭代演算法,一步一步的訓練模型,每一步的訓練過程如下:
- 抽樣部分訓練資料,通過模型執行得到預測結果
y_pred
; - 測量這些
y_pred
與y_true
之間的損失函式值; - 通過損失更小的方向來修改權重;
上述過程一遍一遍的執行,直到損失為0或者損失無法再下降為止;
迭代中從訓練集中抽樣的部分稱之為minibatch,或者一般直接叫做batch,每一輪完整的訓練稱之為epoch,epoch的數量決定了模型使用各個資料點的次數;
理想的訓練過程中,權重不斷更新,損失不斷減少,預測值越來越接近於真實值;
學習率和Batch Size
學習率決定了模型在每一個batch上學習到的內容的大小,學習率越小意味著模型需要更多的batch來幫助其學習,學習率和batch size是兩個訓練過程中影響很大的引數,通常也是主要要調的超引數;
可惜的是,對於很多情況下都沒有必要通過非常耗時的超引數調整來獲取最優的結果,Adam是一種不需要設定學習率的隨機梯度下降演算法,它不需要除錯任何引數,或者說它是自調整的,因此它成為一種很好的通用優化方法;
新增損失函式和優化方法
在定義模型後,可以通過模型的compile
方法新增損失函式和優化方法:
model.compile(
optimizer="adam",
loss="mae",
)
例子 - 紅酒品質
資料格式如下,最後一列為預測目標列:
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality |
---|---|---|---|---|---|---|---|---|---|---|---|
10.8 | 0.470 | 0.43 | 2.10 | 0.171 | 27.0 | 66.0 | 0.99820 | 3.17 | 0.76 | 10.8 | 6 |
8.1 | 0.820 | 0.00 | 4.10 | 0.095 | 5.0 | 14.0 | 0.99854 | 3.36 | 0.53 | 9.6 | 5 |
9.1 | 0.290 | 0.33 | 2.05 | 0.063 | 13.0 | 27.0 | 0.99516 | 3.26 | 0.84 | 11.7 | 7 |
10.2 | 0.645 | 0.36 | 1.80 | 0.053 | 5.0 | 14.0 | 0.99820 | 3.17 | 0.42 | 10.0 | 6 |
可以看到,除了最後一列總有11列作為輸入,神經網路搭建程式碼如下:
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(512, activation='relu', input_shape=[11]),
layers.Dense(512, activation='relu'),
layers.Dense(512, activation='relu'),
layers.Dense(1),
])
看到網路由3個隱含層和1個輸出層組成,其中隱含層的units均為512,表示每個隱含層輸出都有512個,第一層負責接受輸入,最後一層輸出結果;
定義完了網路結構,下面需要設定訓練需要使用的損失函式和優化方法:
model.compile(
optimizer='adam',
loss='mae',
)
任務為迴歸預測,損失函式選擇平均絕對誤差,優化器使用adam;
訓練前的準備已經就緒,下面需要告訴模型訓練使用的batch數量、迭代次數等資訊:
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=256,
epochs=10,
)
對於訓練過程中的loss進行視覺化後可以更好的觀察模型的整個迭代過程:
import pandas as pd
# convert the training history to a dataframe
history_df = pd.DataFrame(history.history)
# use Pandas native plot method
history_df['loss'].plot();
可以看到,在迭代次數達到6次時,後續的迭代中loss的下降不明顯,甚至還有變大的情況出行,一般來說這說明迭代次數足夠了;
模型訓練練習
這部分練習可以通過這個notebook,其中包含了完整的神經網路模型,從定義到設定其損失和優化方法,再到最後的訓練過程,並通過很有趣的動畫方式展示了在不同的學習率、batch size、樣本數量等情況下的模型迭代過程,對於理解各個引數的作用非常有幫助哦,這裡展示其中一組引數下的訓練過程:
過擬合和欠擬合
過擬合和欠擬合是機器學習中繞不開的兩個問題,通常我們可以使用學習曲線來觀察模型迭代表現並判斷其當前屬於過擬合還是欠擬合,通常來說過擬合指的是模型過於複雜,將資料中的噪聲部分也擬合了,因此使得模型在真實資料上的表現明顯差於在訓練集的表現,而欠擬合則指的是模型在訓練集上都沒有達到足夠好的效果,可能是因為模型太簡單,也可能是因為資料量太大;
容量
容量指的是模型可以學習到的資料模式的複雜度大小,或者說容量越大的模型,越能深入的理解資料,對於神經網路來說,可以通過增加其寬度和高度來擴大其模型容量;
所謂增大網路寬度指的是增加已有層中的神經元個數,而增大高度指的是增加新的層,一般來說使用同樣的神經元個數,增加高度帶來的容量增益要大於增加寬度,簡單理解如下:
假設當前網路有兩層,每一層都有3個神經元,則其組合為3*3=9,此時我們要增加2個神經元:
如果是用於增加寬度,每層增加一個神經元變為4個,則有4*4=16;
如果是用於增加高度,增加一個單獨的層,有2個神經元,則有3*3*2=18;
因此都是使用了兩個神經元,從結果上看是高度的收益更大,當然這個只是一種直觀理解,實際的解釋要比這個複雜的多;
提前停止訓練
對於模型訓練過程,尤其是基於真實資料的訓練過程,很多時候是無法完全收斂的,而我們需要保證訓練一定可以結束而不是無限執行下去的,因此可以通過Early Stopping來控制其迭代在滿足某些條件下提前結束;
增加Early Stopping
keras
通過callback的方式新增Early Stopping,所謂callback指的是在每次epoch後執行的內容,用於判斷是否應該終止訓練過程:
from tensorflow.keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(
min_delta=0.001, # minimium amount of change to count as an improvement
patience=20, # how many epochs to wait before stopping
restore_best_weights=True,
)
上述程式碼的含義是,如果連續20次迭代,每次的loss下降都不足0.001,那麼訓練終止,反正目前為止表現最好的權重資料;
例子 - 使用Early Stopping訓練模型
還是之前的紅酒例子,資料格式如下:
fixed acidity | volatile acidity | citric acid | residual sugar | chlorides | free sulfur dioxide | total sulfur dioxide | density | pH | sulphates | alcohol | quality |
---|---|---|---|---|---|---|---|---|---|---|---|
10.8 | 0.470 | 0.43 | 2.10 | 0.171 | 27.0 | 66.0 | 0.99820 | 3.17 | 0.76 | 10.8 | 6 |
8.1 | 0.820 | 0.00 | 4.10 | 0.095 | 5.0 | 14.0 | 0.99854 | 3.36 | 0.53 | 9.6 | 5 |
9.1 | 0.290 | 0.33 | 2.05 | 0.063 | 13.0 | 27.0 | 0.99516 | 3.26 | 0.84 | 11.7 | 7 |
10.2 | 0.645 | 0.36 | 1.80 | 0.053 | 5.0 | 14.0 | 0.99820 | 3.17 | 0.42 | 10.0 | 6 |
模型定義、指定loss和優化器、指定Early Stopping程式碼如下:
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(
min_delta=0.001, # minimium amount of change to count as an improvement
patience=20, # how many epochs to wait before stopping
restore_best_weights=True,
)
model = keras.Sequential([
layers.Dense(512, activation='relu', input_shape=[11]),
layers.Dense(512, activation='relu'),
layers.Dense(512, activation='relu'),
layers.Dense(1),
])
model.compile(
optimizer='adam',
loss='mae',
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=256,
epochs=500,
callbacks=[early_stopping],
verbose=0, # turn off training log
)
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot();
print("Minimum validation loss: {}".format(history_df['val_loss'].min()))
以上,通過fit
方法的callbacks
引數將Early Stopping作為一個callback新增到了迭代過程中,用於控制訓練的提前結束,執行圖如下:
結合程式碼和上圖可以看到,雖然我們設定了epoch為500,但是在迭代不到70次時就終止了,這就是Early Stopping在起作用,一定程度上可以避免不必要的訓練過程,減少訓練時間;
過擬合和欠擬合的練習
這部分練習可以通過這個notebook完成,這裡有通過訓練簡單線性模型和複雜神經網路模型等,並通過學習曲線來觀察模型的擬合情況,並通過新增Early Stopping來控制過擬合情況;
Dropout和Batch Normalization
實際的神經網路結構中往往包含更多的層,不僅僅是dense層,比如啟用層、Dropout層等等,有些類似dense層,定義神經元的連線,而有些則是用於預處理和轉換等;
Dropout
Dropout層有助於糾正過擬合問題,在每次訓練迭代中,隨機的去掉網路層中的一部分輸入單元,使得模型難以從訓練資料學習到錯誤的模式,取而代之的是模型會搜尋更普遍適用的模式,也就是具有更好的魯棒性的模式,藉此解決過擬合問題;
可以把Dropout看作是一種整合方法,與隨機森林類似,Dropout的隨機抽取類似隨機森林的行抽取和列抽取,二者的目的都是解決原始模型的過擬合問題,思路是一樣的;
增加Dropout
在keras
中,Drouput作為層使用,作用於其下的一層,通過引數rate
指定隨機取出的比例:
keras.Sequential([
# ...
layer.Dropout(rate=0.3), # apply 30% dropout to the next layer
layer.Dense(16),
# ...
])
Batch Normalization
模型在迭代過程中,權重的更新主要由loss
和optimater
決定,假設我們的輸入特徵的量綱不一致,比如有的特徵範圍從0到1,有的特徵是從-100到+100,那麼在優化器計算過程中就會產生差異很大的結果,並使得訓練過程很不穩定,體現就是學習曲線的波動嚴重;
一個小栗子:比如我們要預測房價,目前有兩個屬性,一個是面積,範圍是10到200,另一個是距離火車站距離,範圍是100到100000,如果不進行量綱統一,可以遇見的是在計算過程中由於火車站距離值更大,因此會影響對結果的預測,或者說這個範圍一定程度上參與了原來權重該起到的作用;
Batch Normalization類似SKLearn裡的StandardScaler和MinMaxScaler的作用,用於將輸入特徵的量綱統一,避免因為量綱不同導致對於預測結果影響的權重差異;
增加Batch Normalization
可以用在某一層之後:
layers.Dense(16, activation='relu'),
layers.BatchNormalization(),
也可以用在某一層和它的啟用層之間:
layers.Dense(16),
layers.BatchNormalization(),
layers.Activation('relu'),
例子 - 使用Dropout和Batch Normalization
繼續紅酒例子,在每一個隱含層後都先加一個Dropout過濾一部分輸入解決過擬合,再應用Batch Normalization優化不穩定情況:
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(1024, activation='relu', input_shape=[11]),
layers.Dropout(0.3),
layers.BatchNormalization(),
layers.Dense(1024, activation='relu'),
layers.Dropout(0.3),
layers.BatchNormalization(),
layers.Dense(1024, activation='relu'),
layers.Dropout(0.3),
layers.BatchNormalization(),
layers.Dense(1),
])
訓練過程不使用Early Stopping:
model.compile(
optimizer='adam',
loss='mae',
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=256,
epochs=100,
verbose=0,
)
# Show the learning curves
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot();
學習曲線如下:
可以看到,首先雖然沒有Early Stopping,但是過擬合問題不明顯,其次在迭代20次之後不穩定的情況基本消失了,說明Dropout和Batch Normalization都起到了各自的作用;
Dropout和Batch Normalization練習
這部分練習在這個notebook裡,其中分別使用兩個資料集,對比其上應用Dropout與不應用,應用Batch Normalization與不應用在學習曲線上的差異,可以很直觀的看到二者起到的作用;
下面是應用Batch Normalization後的學習曲線,要知道在不應用的情況下曲線都無法繪製出來:
分類問題
之前處理的都是迴歸問題,處理分類問題的區別只有以下兩點:
- 損失函式:分類與迴歸在損失函式應用上不同,比如MAE和準確率;
- 輸出層輸出型別:也就是網路結構最後一層輸出的內容,之前都是數值,如果是二分類問題,則應該是0/1;
Sigmoid函式
Sigmoid函式同樣作為啟用函式,它可以將實數輸出對映到0到1之間,也就是通常的概率範圍,而不管是準確率還是交叉熵等都可以利用概率來計算得到;
Sigmoid函式影象如下,上一個使用它的地方是邏輯迴歸,同樣是將線性迴歸的結果對映到0和1之間:
例子 - 二分類
資料格式如下:
V1 | V2 | V3 | V4 | V5 | V6 | V7 | V8 | V9 | V10 | ... | V26 | V27 | V28 | V29 | V30 | V31 | V32 | V33 | V34 | Class |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0.99539 | -0.05889 | 0.85243 | 0.02306 | 0.83398 | -0.37708 | 1.00000 | 0.03760 | ... | -0.51171 | 0.41078 | -0.46168 | 0.21266 | -0.34090 | 0.42267 | -0.54487 | 0.18641 | -0.45300 | good |
1 | 0 | 1.00000 | -0.18829 | 0.93035 | -0.36156 | -0.10868 | -0.93597 | 1.00000 | -0.04549 | ... | -0.26569 | -0.20468 | -0.18401 | -0.19040 | -0.11593 | -0.16626 | -0.06288 | -0.13738 | -0.02447 | bad |
1 | 0 | 1.00000 | -0.03365 | 1.00000 | 0.00485 | 1.00000 | -0.12062 | 0.88965 | 0.01198 | ... | -0.40220 | 0.58984 | -0.22145 | 0.43100 | -0.17365 | 0.60436 | -0.24180 | 0.56045 | -0.38238 | good |
1 | 0 | 1.00000 | -0.45161 | 1.00000 | 1.00000 | 0.71216 | -1.00000 | 0.00000 | 0.00000 | ... | 0.90695 | 0.51613 | 1.00000 | 1.00000 | -0.20099 | 0.25682 | 1.00000 | -0.32382 | 1.00000 | bad |
1 | 0 | 1.00000 | -0.02401 | 0.94140 | 0.06531 | 0.92106 | -0.23255 | 0.77152 | -0.16399 | ... | -0.65158 | 0.13290 | -0.53206 | 0.02431 | -0.62197 | -0.05707 | -0.59573 | -0.04608 | -0.65697 | good |
像之前處理迴歸問題一樣定義模型,區別在於最後一層的啟用函式選擇sigmoid
用於輸出概率:
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(4, activation='relu', input_shape=[33]),
layers.Dense(4, activation='relu'),
layers.Dense(1, activation='sigmoid'),
])
新增交叉熵和準確率到模型中,繼續使用adam
,他在分類問題上表現依然很好:
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['binary_accuracy'],
)
使用Early Stopping控制訓練過程:
early_stopping = keras.callbacks.EarlyStopping(
patience=10,
min_delta=0.001,
restore_best_weights=True,
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=512,
epochs=1000,
callbacks=[early_stopping],
verbose=0, # hide the output because we have so many epochs
)
分別觀察其交叉熵和準確率的變化情況:
history_df = pd.DataFrame(history.history)
# Start the plot at epoch 5
history_df.loc[5:, ['loss', 'val_loss']].plot()
history_df.loc[5:, ['binary_accuracy', 'val_binary_accuracy']].plot()
print(("Best Validation Loss: {:0.4f}" +\
"\nBest Validation Accuracy: {:0.4f}")\
.format(history_df['val_loss'].min(),
history_df['val_binary_accuracy'].max()))
交叉熵:
準確率:
分類練習
這部分練習在這個notebook,很完整的一個分類模型搭建過程,從基於結構圖建立神經網路結構到新增loss和優化器,使用Early Stopping等都有,包括對於結果是否過擬合和欠擬合的討論等,可以通過這個notebook再次練習下整個深度學習流程,麻雀雖小,五臟俱全;
交叉熵:
準確率:
最後
對於深度學習還有很多很多可以學習的內容,本篇文章以最簡單的方式對其中各個基礎模組進行介紹,並結合程式碼和執行結果圖等進行說明,希望看完能夠在腦海中形成對於深度學習的一個感性認識;
最後的最後
歡迎大佬們關注我的公眾號:尼莫的AI小站,新開的公眾號,後續會不定期更新有關機器學習、深度學習、資料處理分析、遊戲的內容;
出處:https://www.cnblogs.com/helongBlog/p/13816768.html