本文是 Python 系列的第十三篇,也是深度學(xué)習(xí)框架的第一篇 - Keras。
深度學(xué)習(xí)之 Keras
深度學(xué)習(xí)之 TensorFlow
深度學(xué)習(xí)之 PyTorch
深度學(xué)習(xí)之 MXnet
Keras 是一個(gè)高級(jí)的 (high-level) 深度學(xué)習(xí)框架,作者是 Fran?ois Chollet。Keras 可以以兩種方法運(yùn)行:
以 TensorFlow, CNTK, 或者 Theano 作為后端 (backend) 運(yùn)行
在 TensorFlow 里面直接運(yùn)行 tf.keras
我們用的是 TensorFlow 下面的 Keras,不過在本貼不會(huì)涉及任何關(guān)于 TensorFlow 的內(nèi)容,只單單講解 tf.keras 下面的內(nèi)容。首先引入 tensorflow 和 keras。
import tensorflow as tf
import tensorflow.keras as keras
Keras 是深度學(xué)習(xí)框架,里面有各種深度學(xué)習(xí)模型,介紹它之前讓我們先回憶下它的好兄弟 - 機(jī)器學(xué)習(xí)框架 Scikit-Learn。
在 Scikit-Learn 里完整的一套流程如下:
數(shù)據(jù)是不可缺少的,Scikit-Learn 里面也有不少自帶數(shù)據(jù)集。大家應(yīng)該還記得 Scikit-Learn 里面的三大核心 API 吧:估計(jì)器(estimator),預(yù)測(cè)器(predictor)和轉(zhuǎn)換器(transformer)。叢上圖看估計(jì)器用來構(gòu)建模型和擬合模型,而預(yù)測(cè)器用來評(píng)估模型。而轉(zhuǎn)換器一般用來做數(shù)據(jù)預(yù)處理得到干凈的 X_train
和 y_train
。
除了數(shù)據(jù)和模型,要完成一個(gè)任務(wù)還需定義損失函數(shù)(loss function)和指定算法(algorithm),它們都隱藏在 Scikit-Learn 的具體模型中,比如
LinearRegression
模型用的是 mean_square_error 損失函數(shù),用梯度下降算法
LogisticRegression
模型用的是 cross_entropy 損失函數(shù),用梯度下降算法
損失函數(shù)和算法都會(huì)在 Keras 里面都會(huì)顯性定義出來,帶著上面 Scikit-Learn 的圖,讓我們來看看 Keras 的高層流程。
說白了,Keras 里面的模型都是神經(jīng)網(wǎng)絡(luò),而神經(jīng)網(wǎng)絡(luò)都是一層一層(layer by layer)疊加起來的,在Keras 里完整的一套流程如下:
總共分五步:
引入數(shù)據(jù):和 Scikit-Learn 操作一樣
用 numpy 數(shù)據(jù)
引用自帶數(shù)據(jù)
構(gòu)建模型:用 Keras 構(gòu)建模型就類似把每層當(dāng)積木連起來稱為一個(gè)網(wǎng)絡(luò), 連接的方法有三種:
序列式(sequential)
函數(shù)式(functional)
子類化(subclassing)
編譯模型:這是 Scikit-Learn 里面沒有的,顯性定義出損失函數(shù)(loss)、優(yōu)化方法(optimizer)和監(jiān)控指標(biāo)(metrics)。
擬合模型:和 Scikit-Learn 里的估計(jì)器類似,但可以額外設(shè)定 epoch 數(shù)量、是否包含驗(yàn)證集、設(shè)定調(diào)用函數(shù)里面的指標(biāo),等等。
評(píng)估模型:和 Scikit-Learn 里的預(yù)測(cè)器類似。
本帖目錄如下(由于內(nèi)容太多,上帖只包括第一章關(guān)于 Keras 最基本的知識(shí),下帖再講怎么用 Keras 做一些有趣的事情):
第一章 - Keras 簡(jiǎn)介
1.1 Keras 數(shù)據(jù)
1.2 Keras 里的神經(jīng)網(wǎng)絡(luò)
1.3 構(gòu)建模型
1.4 編譯模型
1.5 擬合模型
1.6 評(píng)估模型
1.7 保存模型
第二章 - 用 Keras 畫畫
第三章 - 用 Keras 寫作
第四章 - 用 Keras 作曲
總結(jié)
1.1
Keras 數(shù)據(jù)
不像 TensorFlow, PyTorch 和 MXNet 有自己特有的數(shù)據(jù)格式
Tensorflow 用 tf.Tensor
MXNet 用 ndarray
PyTorch里用 torch.tensor
Keras 的數(shù)據(jù)格式就是 numpy array。
機(jī)器學(xué)習(xí) (深度學(xué)習(xí)) 中用到的數(shù)據(jù),包括結(jié)構(gòu)性數(shù)據(jù) (數(shù)據(jù)表) 和非結(jié)構(gòu)性數(shù)據(jù) (序列、圖片、視屏) 都是張量,總結(jié)如下:
數(shù)據(jù)表-2D 形狀 = (樣本數(shù),特征數(shù))
序列類-3D 形狀 = (樣本數(shù),步長(zhǎng),特征數(shù))
圖像類-4D 形狀 = (樣本數(shù),寬,高,通道數(shù))
視屏類-5D 形狀 = (樣本數(shù),幀數(shù),寬,高,通道數(shù))
機(jī)器學(xué)習(xí),尤其深度學(xué)習(xí),需要大量的數(shù)據(jù),因此樣本數(shù)肯定占一個(gè)維度,慣例我們把它稱為維度 1。這樣機(jī)器學(xué)習(xí)要處理的張量至少?gòu)?2 維開始。
2 維張量就是矩陣,也叫數(shù)據(jù)表,一般用 csv 存儲(chǔ)。
這套房屋 21,000 個(gè)數(shù)據(jù)包括其價(jià)格 (y),平方英尺,臥室數(shù),樓層,日期,翻新年份等等 21 欄。該數(shù)據(jù)形狀為 (21000, 21)。傳統(tǒng)機(jī)器學(xué)習(xí)的線性回歸可以來預(yù)測(cè)房?jī)r(jià)。
2 維張量的數(shù)據(jù)表示圖如下:
推特 (twitter) 的每條推文 (tweet) 規(guī)定只能發(fā) 280 個(gè)字符。在編碼推文時(shí),將 280 個(gè)字符的序列用獨(dú)熱編碼 (one-hot encoding) 到包含 128 個(gè)字符的 ASCII 表,如下所示。
這樣,每條推文都可以編碼為 2 維張量形狀 (280, 128),比如一條 tweet 是 'I love python :)',這句話映射到 ASCII 表變成:
如果收集到 1 百萬條推文,那么整個(gè)數(shù)據(jù)集的形狀為 (1000000, 280, 128)。傳統(tǒng)機(jī)器學(xué)習(xí)的對(duì)率回歸可以來做情感分析。
3 維張量的數(shù)據(jù)表示圖如下:
圖像通常具有 3 個(gè)維度:寬度,高度和顏色通道。雖然是黑白圖像 (如 MNIST 數(shù)字) 只有一個(gè)顏色通道,按照慣例,我們還是把它當(dāng)成 4 維,即顏色通道只有一維。
一組黑白照片可存成形狀為 (樣本數(shù),寬,高,1) 的 4 維張量
一組彩色照片可存成形狀為 (樣本數(shù),寬,高,3) 的 4 維張量
通常 0 代表黑色,255 代表白色。
4 維張量的數(shù)據(jù)表示圖如下:
視頻可以被分解成一幅幅幀 (frame)。
每幅幀就是彩色圖像,可以存儲(chǔ)在形狀是 (寬度,高度,通道) 的 3D 張量中
視屏 (一個(gè)序列的幀) 可以存儲(chǔ)在形狀是 (幀數(shù),寬度,高度,通道) 的 4D 張量中
一批不同的視頻可以存儲(chǔ)在形狀是 (樣本數(shù),幀數(shù),寬度,高度,通道) 的 5D 張量中
下面一個(gè) 9:42 秒的 1280 x 720 油管視屏 (哈登三分絕殺勇士),被分解成 40 個(gè)樣本數(shù)據(jù),每個(gè)樣本包括 240 幀。這樣的視頻剪輯將存儲(chǔ)在形狀為 (40, 240, 1280, 720, 3) 的張量中。
5 維張量的數(shù)據(jù)表示圖如下:
對(duì)于以上用 numpy 自定義的各種維度的數(shù)據(jù)集 (X, y),用 Scikit-Learn 的子包 model_selection 里的 train_test_split 函數(shù),代碼如下:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test
= train_test_split( X, y, test_size=0.2 )
和 Scikit-Learn 一樣,Keras 本身也自帶數(shù)據(jù)集,從其官網(wǎng)中收集到 7 套。
想了解數(shù)據(jù)的具體描述,去 https://keras.io/datasets/ 鏈接。
想引進(jìn)并劃分它們可用以下代碼:
from keras.datasets import data
(x_train, y_train), (x_test, y_test)
= data.load_data()
這里 data 指的就是上面七套數(shù)據(jù)的統(tǒng)稱,比如
data = boston_housing
data = cifar10
data = cifar100
data = imbd
data = reuters
data = mnist
data = fashion_mnist
或者直接寫 keras.dataset. + <tab> 來選取數(shù)據(jù),展示如下
在本節(jié)后面介紹構(gòu)建模型的三種方式時(shí),我們用 fashion_mnist 數(shù)據(jù)來說明。Fashion-MNIST
是一個(gè)替代 MNIST 手寫數(shù)字集的圖像數(shù)據(jù)集。它是由Zalando(一家德國(guó)的時(shí)尚科技公司)旗下的研究部門提供。
Fashion-MNIST 的大小、格式和訓(xùn)練集/測(cè)試集劃分與原始的 MNIST 完全一致。60000/10000 的訓(xùn)練測(cè)試數(shù)據(jù)劃分,28x28 的灰度圖片。
打印它們的形狀確認(rèn)一下。
(x_train, y_train),(x_test, y_test) = data.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
print( x_train.shape )
print( x_test.shape )
print( y_train.shape )
print( y_test.shape )
(60000, 28, 28)
(10000, 28, 28)
(60000,)
(10000,)
每個(gè)訓(xùn)練和測(cè)試樣本都按照以下類別(總共有 10 類標(biāo)簽)進(jìn)行了標(biāo)注:
我們來驗(yàn)證一下標(biāo)簽和圖片是不是一一對(duì)應(yīng)。
class_names = [ 'T-shirt/top', 'Trouser', 'Pullover',
'Dress', 'Coat', 'Sandal', 'Shirt',
'Sneaker', 'Bag', 'Ankle boot' ]
class_names[y_train[0]]
'Ankle boot'
第一張圖片的標(biāo)簽是踝靴(Ankle boot),用 matplotlib.pyplot 里的 imshow 函數(shù)來把圖片展示出來。
import matplotlib.pyplot as plt
plt.imshow(x_train[0])
是踝靴!
1.2
Keras 里的神經(jīng)網(wǎng)絡(luò)
組成神經(jīng)網(wǎng)絡(luò)的四個(gè)方面:
層(layers)和模型(models)
輸入(input)和輸出(output)
損失函數(shù)(loss)
優(yōu)化器(optimizer)
多個(gè)層鏈接在一起組成了模型,將輸入數(shù)據(jù)映射為預(yù)測(cè)值。然后損失函數(shù)將這些預(yù)測(cè)值輸出,并與目標(biāo)進(jìn)行比較,得到損失值,用于衡量網(wǎng)絡(luò)預(yù)測(cè)值與預(yù)期結(jié)果的匹配程度。優(yōu)化器使用這個(gè)損失值來更新網(wǎng)絡(luò)的權(quán)重。
下圖給出模型、層、輸入、輸出、損失函數(shù)和優(yōu)化器之間的關(guān)系:
神經(jīng)網(wǎng)絡(luò)里面的基本數(shù)據(jù)結(jié)構(gòu)是層,而 Keras 里 layers 也是最基本的模塊。
不同數(shù)據(jù)格式或不同數(shù)據(jù)處理類型需要用到不同的層,比如
形狀為 (樣本數(shù),特征數(shù)) 的 2D 數(shù)據(jù)用全連接層,對(duì)應(yīng) Keras 里面的 Dense
形狀為 (樣本數(shù),步長(zhǎng),特征數(shù)) 的 3D 序列數(shù)據(jù)用循環(huán)層,對(duì)應(yīng) Keras 里面的 RNN, GRU 或 LSTM
形狀為 (樣本數(shù),寬,高,通道數(shù)) 的 4D 圖像數(shù)據(jù)用二維卷積層,對(duì)應(yīng) Keras 里面的 Conv2D
等等。。。
深度學(xué)習(xí)模型是層構(gòu)成的有向無環(huán)圖。最常見的例子就是層的線性堆疊,將單一輸入映射為單一輸出(single input to single output)。
此外,神經(jīng)網(wǎng)絡(luò)還有更復(fù)雜的結(jié)構(gòu),比如
用數(shù)值型和類別型的數(shù)據(jù)(下圖左邊),和圖像數(shù)據(jù)(下圖右邊)一起來預(yù)測(cè)房?jī)r(jià)。
根據(jù)圖像數(shù)據(jù)來識(shí)別物體(下圖左分支)和顏色(下圖右分支)。
等等。。。
在 Keras 里將層連成模型確定網(wǎng)絡(luò)架構(gòu)后,你還需要選擇以下兩個(gè)參數(shù),選擇損失函數(shù)和設(shè)定優(yōu)化器。
在訓(xùn)練過程中需要將最小化損失函數(shù),這它是衡量當(dāng)前任務(wù)是否已成功完成的標(biāo)準(zhǔn)。
對(duì)于分類、回歸、序列預(yù)測(cè)等常見問題,你可以遵循一些簡(jiǎn)單的指導(dǎo)原則來選擇正確的損失函數(shù)。
對(duì)于二分類問題,用二元交叉熵(binary crossentropy)損失函數(shù)
對(duì)于多分類問題,用分類交叉熵(categorical crossentropy)損失函數(shù)
對(duì)于回歸問題,用均方誤差(mean-squared error)損失函數(shù)
對(duì)于序列學(xué)習(xí)問題,用聯(lián)結(jié)主義時(shí)序分類(CTC,connectionist temporal classification)損失函數(shù)
有時(shí)在面對(duì)真正全新的問題時(shí),你還需要自主的設(shè)計(jì)損失函數(shù),但這個(gè)超出本帖的范圍了,以后再講。
優(yōu)化器決定如何基于損失函數(shù)對(duì)網(wǎng)絡(luò)進(jìn)行更新。它執(zhí)行的是隨機(jī)梯度下降(stochastic gradient descent,SGD)方法或其變體,目前 Keras 優(yōu)化器包括
Adagrad
Adadelta
RMSprop
Adam
AdaMax
Nadam
AMSGrad
具體每個(gè)方法就不細(xì)講了,對(duì)算法感興趣的讀者可參考鏈接 http://ruder.io/optimizing-gradient-descent/.
借用 Ruder 大神上面文章里的兩幅動(dòng)圖對(duì)比各種優(yōu)化算法的表現(xiàn),圖一對(duì)比他們?cè)诎包c(diǎn)(saddle point)處的收斂到最優(yōu)值的速度,SGD 沒有收斂,圖二從損失函數(shù)等值線(contour)看收斂速度,SGD 最慢。
1.3
構(gòu)建模型
本節(jié)分別用序列式、函數(shù)書和子類化,配著 Fashion-MNIST 數(shù)據(jù)集構(gòu)建模型,注意為了便于說明 Keras 語法特征,我故意只構(gòu)建個(gè)簡(jiǎn)單模型,可能不實(shí)際,比如分類 Fashion-MNIST,用卷積效果網(wǎng)絡(luò)好些,干嘛只用全連接網(wǎng)絡(luò)舉例?
一切只是便于解說基本核心概念。這些基本點(diǎn)弄清楚了,構(gòu)建復(fù)雜模型和構(gòu)建簡(jiǎn)單模型沒任何區(qū)別。
序列式(sequential)建模有兩種方式。
用全連接網(wǎng)絡(luò)(fully-connected neural network, FCNN)來建模,代碼吐下:
首先用 Sequential() 創(chuàng)建一個(gè)空模型 ,這個(gè)沒辦法,硬著記住吧。
接下來就像搭積木一樣,用 add() 函數(shù)一層層加 layers,這個(gè)操作用代碼寫出來很自然,但是 layers 有很多種,這里用了兩種:
Flatten:顧名思義,就是通過「Flatten 層」把高維數(shù)據(jù)打平成低維數(shù)據(jù),做的就是下圖的事。
Dense:顧名思義,就是通過「Dense 層」把前一層每一個(gè)神經(jīng)元和后一層神經(jīng)元(除了偏置)兩兩相連,如下圖:
層的大方向弄清楚后,讓我們看看里面的參數(shù)
Flatten( input_shape=[28,28] )
Dense( 100, activation='relu' )
Dense( 10, activation='softmax' )
每個(gè)層的第一個(gè)參數(shù)都是設(shè)定該層輸入數(shù)據(jù)的維度。比如
Flatten 層接受形狀 28 × 28 的二維數(shù)據(jù)
第一個(gè) Dense 層接受形狀 100 的一維數(shù)據(jù)
第二個(gè) Dense 層接受形狀 10 的一維數(shù)據(jù)
在 Keras 里不需要設(shè)定該層輸出數(shù)據(jù)的維度,為什么呢?很簡(jiǎn)單,下一層的輸入數(shù)據(jù)維度 = 該層的輸出數(shù)據(jù)維度!Keras 會(huì)自動(dòng)幫你連起來,那么
Flatten 層接受形狀 28 × 28 的二維數(shù)據(jù),輸出形狀 100 的一維數(shù)據(jù)
第一個(gè) Dense 層接受形狀 100 的一維數(shù)據(jù),輸出形狀 10 的一維數(shù)據(jù)
第二個(gè) Dense 層接受形狀 10 的一維數(shù)據(jù),輸出形狀 10 的一維數(shù)據(jù)
每個(gè)層(除了 Flatten 層)的第二個(gè)參數(shù)設(shè)定了激活函數(shù)的方式,比如
第一個(gè) Dense 層用 relu,防止梯度消失
第二個(gè) Dense 層用 softmax,因?yàn)?Fashion-MNIST 是個(gè)多分類問題
當(dāng)構(gòu)建完模型,我們可以打印出它的層的信息(用 model.layers)和概要信息(用 model.summary())。
model.layers
整個(gè)模型有三層,按順序它們的類別分別是 Flatten, Dense 和 Dense。
model.summary()
該模型自動(dòng)被命名 sequential_8,接著一張表分別描述每層的名稱類型(layer (type))、輸出形狀(Output Shape)和參數(shù)個(gè)數(shù)(Param #)。我們一層層來看
Flatten 層被命名為 flatten_7
輸出形狀是 (None, 784),784 好理解,就是 28×28 打平之后的維度,這個(gè) None 其實(shí)是樣本數(shù),更嚴(yán)謹(jǐn)?shù)闹v是一批 (batch) 里面的樣本數(shù)。為了代碼簡(jiǎn)潔,這個(gè)「0 維」的樣本數(shù)在建模時(shí)通常不需要顯性寫出來。
參數(shù)個(gè)數(shù)為 0,因?yàn)榇蚱街皇侵厮軘?shù)組,不需要任何參數(shù)來完成重塑動(dòng)作。
第一個(gè) Dense 層被命名為 dense_5
輸出形狀是 (None, 100),好理解。
參數(shù)個(gè)數(shù)為 78500,為什么不是 784×100 = 78400 呢?別忘了偏置項(xiàng)(bias)哦,(784+1)×100 = 78500。
第二個(gè) Dense 層被命名為 dense_6
輸出形狀是 (None, 10),好理解。
參數(shù)個(gè)數(shù)為 1010,考慮偏置項(xiàng),(100+1)×10 = 1010。
最下面還列出總參數(shù)量 79510,可訓(xùn)練參數(shù)量 79510,不可訓(xùn)練參數(shù)量 0。為什么還有參數(shù)不需要訓(xùn)練呢?你想想遷移學(xué)習(xí),把借過來的網(wǎng)絡(luò)鎖住開始的 n 層,只訓(xùn)練最后 1- 2 層,那前面 n 層的參數(shù)可不就不參與訓(xùn)練嗎?
再回顧一下代碼。
如果你還是覺得上面代碼太多,我們還可以做進(jìn)一步精簡(jiǎn),事先引用 Sequential, Flattern 和 Dense。
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten
from tensorflow.keras.layers import Dense
這樣每次就不用重復(fù)寫 keras.models 和 keras.layers 了,下面代碼是不是簡(jiǎn)潔多了。
事無巨細(xì)把最簡(jiǎn)單的序列式建模講完,大家是不是覺得 Keras 很簡(jiǎn)單呢?
讓我們?cè)倩仡櫼幌?model.layers。
model.layers
仔細(xì)看看輸出數(shù)據(jù)的格式,是個(gè)列表,那么有沒有一種方法用列表而不用 model.add() 來構(gòu)建模型么?有,代碼如下:
model.summary()
同樣的模型結(jié)果(輸入形狀和參數(shù)個(gè)數(shù),名稱不一樣),但是又省掉幾個(gè) model.add() 的字節(jié)了,代碼看起來又簡(jiǎn)潔些。
我們可以用 model.layers[n].name 來獲取第 n 層的名稱。
model.layers[1].name
'dense_11'
也可以用 get_weights() 來獲取每層的權(quán)重矩陣 W 和偏置向量 b。
weights, biases = model.layers[1].get_weights()
weights
biases
當(dāng)模型還沒訓(xùn)練時(shí),W 是隨機(jī)初始化,而 b 是零初始化。最后檢查一下它們的形狀。
print( weights.shape )
print( biases.shape )
(784, 100)
(100,)
一張圖總結(jié)「序列式建?!埂?/p>
上面的序列式只適用于線性堆疊層的神經(jīng)網(wǎng)絡(luò),但這種假設(shè)過于死板,有些網(wǎng)絡(luò)
需要多個(gè)輸入
需要多個(gè)輸出
在層與層之間具有內(nèi)部分支
這使得網(wǎng)絡(luò)看起來像是層構(gòu)成的圖(graph),而不是層的線性堆疊,這是需要更加通用和靈活建模方式,函數(shù)式(functional)建模。
本小節(jié)還是用上面序列式的簡(jiǎn)單例子來說明函數(shù)式建模,目的只是闡明函數(shù)式建模的核心要點(diǎn),更加實(shí)際的案例放在之后幾章。
首先引入必要的模塊,和序列式建模比,注意 Input 和 Model 是個(gè)新東西。
from tensorflow.keras.layers import Input, Flatten, Dense
from tensorflow.keras.models import Model
代碼如下。
函數(shù)式建模只用記住一句話:把層當(dāng)做函數(shù)用。有了這句在心,代碼秒看懂。
第二行,把 Flatten() 當(dāng)成函數(shù) f,化簡(jiǎn)不就是 x = f(input)
第三行,把 Dense(100, activation='relu') 當(dāng)成函數(shù) g,化簡(jiǎn)不就是 x = g(x)
第三行,把 Dense(10, activation='softmax') 當(dāng)成函數(shù) h,化簡(jiǎn)不就是 output = h(x)
這樣一層層(函數(shù)接著函數(shù))把 input 傳遞到 output,最后再用 Model() 將他倆建立關(guān)系。
看看模型概要。
model.summary()
概要包含的內(nèi)容和序列式建模產(chǎn)生的一眼,除了多了一個(gè) InputLayer。
序列式構(gòu)建的模型都可以用函數(shù)式來完成,反之不行,如果在兩者選一,建議只用函數(shù)式來構(gòu)建模型。
一張圖對(duì)比「函數(shù)式建?!购汀感蛄惺浇!?。
序列式和函數(shù)式都是聲明式編程(declarative programming),它描述目標(biāo)的性質(zhì),讓計(jì)算機(jī)明白目標(biāo),而非流程。
具體來說,它們都是聲明哪些層應(yīng)該按什么順序來添加,層與層以什么樣的方式連接,所有聲明完成之后再給模型喂數(shù)據(jù)開始訓(xùn)練。這種方法有好有快。
好處:模型很容易保存、復(fù)制和分享,模型結(jié)構(gòu)也容易展示和分析,因此調(diào)試起來比較容易。
壞處:是個(gè)靜態(tài)模型,很多情況模型有循環(huán)(loops)和條件分支(conditional branching)。這是我們更需要命令式編程(imperative programming)了。
子類化(subclassing)建模登場(chǎng)了。
首先引入必要的模塊
from tensorflow.keras.layers import Flatten, Dense
from tensorflow.keras.models import Model
Model 是個(gè)類別,而子類化就是創(chuàng)建 Model 的子類,起名為 SomeModel。
該類別里有一個(gè)構(gòu)造函數(shù) __init__() 和一個(gè) call() 函數(shù):
構(gòu)造函數(shù)負(fù)責(zé)創(chuàng)建不同的層,在本例中創(chuàng)建了一個(gè)隱藏層 self.hidden 和一個(gè)輸出層 self.main_output。
call() 函數(shù)負(fù)責(zé)各種計(jì)算,注意到該函數(shù)有個(gè)參數(shù)是 input。
咋一看子類化和函數(shù)式非常像,但有個(gè)細(xì)微差別,構(gòu)造函數(shù)里面只有各種層,沒有 input,而做計(jì)算的地方全部在 call() 里進(jìn)行。這樣就把創(chuàng)建層和計(jì)算兩者完全分開。
在 call() 你可以盡情發(fā)揮想象:用各種 for, if, 甚至低層的 Tensorflow 里面的操作。研究員比較喜歡用子類化構(gòu)建模型,他們可以嘗試不同的點(diǎn)子。
1.4
編譯模型
當(dāng)構(gòu)建模型完畢,接著需要編譯(compile)模型,需要設(shè)定三點(diǎn):
根據(jù)要解決的任務(wù)來選擇損失函數(shù)
選取理想的優(yōu)化器
選取想監(jiān)控的指標(biāo)
代碼如下:
常見問題類型的最后一層激活和損失函數(shù),可供選擇:
二分類問題:最后一層激活函數(shù)是 sigmoid,損失函數(shù)是 binary_crossentropy
多分類問題:最后一層激活函數(shù)是 softmax,損失函數(shù)是 categorical_crossentropy
多標(biāo)簽問題:最后一層激活函數(shù)是 sigmoid,損失函數(shù)是 binary_crossentropy
回歸問題:最后一層無激活函數(shù)是,損失函數(shù)是 mse
Fashion_MNIST 是一個(gè)十分類問題,因此損失函數(shù)是 categorical_crossentropy。
大多數(shù)情況下,使用 adam 和 rmsprop 及其默認(rèn)的學(xué)習(xí)率是穩(wěn)妥的。本例中選擇的是 adam。
除了通過名稱來調(diào)用優(yōu)化器 model.compile('名稱'),我們還可以通過實(shí)例化對(duì)象來調(diào)用優(yōu)化器 model.compile('優(yōu)化器')。選取幾個(gè)對(duì)比如下:
名稱:SGD
對(duì)象:SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False)
名稱:RMSprop
對(duì)象:RMSprop(lr=0.001, rho=0.9, epsilon=None, decay=0.0)
名稱:Adagrad
對(duì)象:Adagrad(lr=0.01, epsilon=None, decay=0.0)
名稱:Adam
對(duì)象:
Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=None, decay=0.0, amsgrad=False)
這些優(yōu)化器對(duì)象都在 keras.optimizer 命名空間下。使用優(yōu)化器對(duì)象來編譯模型的好處是可以調(diào)節(jié)里面的超參數(shù)比如學(xué)習(xí)率 lr,使用名稱則來編譯模型只能采用優(yōu)化器的默認(rèn)參數(shù),比如用 Adam 里面的學(xué)習(xí)率 0.001。
指標(biāo)和損失函數(shù)一樣,都可以通過用名稱和實(shí)例化對(duì)象來調(diào)用,在本例中的指標(biāo)是精度,那么可寫成
名稱:metrics = ['acc']
對(duì)象:metrics = [metrics.categorical_accuracy])
指標(biāo)不會(huì)用于訓(xùn)練過程,只是讓我們監(jiān)控模型訓(xùn)練時(shí)的表現(xiàn),常見的指標(biāo)如下:
除了 Keras 自帶指標(biāo),我們還可以自定指標(biāo),下列的 mean_pred 就是自定義指標(biāo)(該指標(biāo)計(jì)算預(yù)測(cè)的平均值)。
def mean_pred(y_true, y_pred):
return K.mean(y_pred)
model.compile(optimizer='sgd',
loss='binary_crossentropy',
metrics=['acc', mean_pred])
1.5
擬合模型
和 Scikit-Learn 一樣,Keras 里也用 model.fit() 函數(shù);和 Scikit-Learn 不一樣,Keras 會(huì)設(shè)置要遍歷訓(xùn)練數(shù)據(jù)多少遍,即 epochs,先用 20 遍。
發(fā)現(xiàn) loss 逐漸減少,acc 逐漸提高,這么個(gè)簡(jiǎn)單的單層全連接神經(jīng)網(wǎng)絡(luò)在 Fashion_MNIST 上精度做到 92.82% 也可以了。
如果項(xiàng)目只要求精度達(dá)到 90% 即可,那么我們不用浪費(fèi)資源把程序跑到底。這是用調(diào)用函數(shù)(callback)來控制,代碼如下:
回調(diào)函數(shù)是一個(gè)函數(shù)的合集,會(huì)在訓(xùn)練的階段中所使用。你可以使用回調(diào)函數(shù)來查看訓(xùn)練模型的內(nèi)在狀態(tài)和統(tǒng)計(jì)。你可以傳遞一個(gè)列表的回調(diào)函數(shù)(作為 callbacks 關(guān)鍵字參數(shù))到 Sequential 或 Model 類型的 .fit() 方法。在訓(xùn)練時(shí),相應(yīng)的回調(diào)函數(shù)的方法就會(huì)被在各自的階段被調(diào)用。
在本例中,我們定義的是 on_epoch_end(),在每期結(jié)束式,一旦精度超過 90%,模型就停止訓(xùn)練。
最常見的回調(diào)函數(shù)是
此外而且具體情況,我們可以自定義
on_train_begin()
on_train_end()
on_epoch_begin()
on_epoch_end()
on_batch_begin()
on_batch_end()
定義完 callbacks,我們只用把它當(dāng)做參數(shù)傳到 model.fit() 里。
在 Epoch = 8 時(shí),訓(xùn)練精度達(dá)到 90.17%,停止訓(xùn)練。
1.6
預(yù)測(cè)模型
Keras 預(yù)測(cè)模型和 Scikit-Learn 里一樣,都用是 model.predict()。
prob = model.predict( x_test[0:1] )
prob
在測(cè)試集上第一張圖上做預(yù)測(cè),輸出是一個(gè)數(shù)組,里面 10 個(gè)數(shù)值代表每個(gè)類別預(yù)測(cè)的概率??瓷先ナ堑?10 類(索引為 9)概率最大。用 argmax 驗(yàn)證一下果然是的,而且把真正標(biāo)簽打印出來也吻合,第一張圖預(yù)測(cè)對(duì)了。
import numpy as np
print( np.argmax(prob) )
print( y_test[0] )
9
9
前面講了 Fashion_MNIST 第 10 類是踝靴(Ankle boot),畫出來看看。
plt.imshow(x_test[0])
最后用 model.evaluate() 來看看模型在所有測(cè)試集上的表現(xiàn)。
model.evaluate( x_test, y_test )
訓(xùn)練精度 90.17% 但是測(cè)試精度 87.73%,有過擬合的征兆。這是需要用驗(yàn)證集了。
我們將原來訓(xùn)練集前 5000 個(gè)當(dāng)驗(yàn)證集,剩下了當(dāng)訓(xùn)練集。
這時(shí)來用回調(diào)函數(shù)關(guān)注驗(yàn)證精度 val_acc,一旦超過 90% 就停止訓(xùn)練。
代碼基本和上面一樣,唯一區(qū)別是把 (x_valid, y_valid) 傳到 model.fit() 中。
但是驗(yàn)證精度適中沒有超過 90%,模型從頭訓(xùn)練到完。
難道是我們的單層全連接模型太簡(jiǎn)單?現(xiàn)在數(shù)據(jù)集可不是 MNIST 而是 Fashion_MNIST 啊,服裝的特征還是數(shù)字的特征要豐富多了吧,再怎么樣也要弄到卷積神經(jīng)網(wǎng)絡(luò)吧。
首先引進(jìn)二維卷積層 Conv2D 和二維最大池化層 MaxPooling2D。在全連接層前我們放了兩組 Conv2D + MaxPooling2D。
效果一下子出來了,訓(xùn)練精度 98.71% 但是驗(yàn)證精度只有 91.36%,明顯的過擬合。畫個(gè)圖看的更明顯。
怎么辦?用 Dropout 試試?
代碼和上面一摸一樣,在第一個(gè)全連接層前加一個(gè) Dropout 層(高亮強(qiáng)調(diào)出)。
雖然訓(xùn)練精度降到 93.89% 但是驗(yàn)證精度提高到 92.26%,Dropout 有效地抑制了過擬合。繼續(xù)上圖。
1.7
保存模型
花費(fèi)很長(zhǎng)時(shí)間辛苦訓(xùn)練的模型不保存下次再?gòu)念^開始訓(xùn)練太傻了。
對(duì)于用序列式和函數(shù)式構(gòu)建的模型可以用 model.save() 來保存:
model.save('my_keras_model.h5')
加載可用 models 命名空間里面的 load_model() 函數(shù):
model = keras.models.load_model('my_keras_model.h5')
用子類化構(gòu)建的模型不能用上面的 save 和 load 來保存和加載,它對(duì)應(yīng)的方式是
save_weights()
load_weights()
雖然沒能保存模型所有的東西,但是保存了最重要的參數(shù),這就夠了。
一篇 12000 字的長(zhǎng)文才講清了 Keras 的基本內(nèi)容,一開始本想一貼寫完,結(jié)果太低估 Keras 和太高估自己了,啥也別說了,敬請(qǐng)期待 Keras(下)!
Stay Tuned!
機(jī)器學(xué)習(xí)、金融工程、量化投資的干貨營(yíng);快樂硬核的終生學(xué)習(xí)者。
聯(lián)系客服