Keras TensorFlow教程:如何從零開發一個複雜深度學習模型

做者:chen_h
微信號 & QQ:862251340
微信公衆號:coderpai
簡書地址:https://www.jianshu.com/p/205...python


Keras 是提供一些高可用的 Python API ,能幫助你快速的構建和訓練本身的深度學習模型,它的後端是 TensorFlow 或者 Theano 。本文假設你已經熟悉了 TensorFlow 和卷積神經網絡,若是,你尚未熟悉,那麼能夠先看看這個10分鐘入門 TensorFlow 教程卷積神經網絡教程,而後再回來閱讀這個文章。git

在這個教程中,咱們將學習如下幾個方面:github

  1. 爲何選擇 Keras?爲何 Keras 被認爲是深度學習的將來?
  2. 在Ubuntu上面一步一步安裝Keras。
  3. Keras TensorFlow教程:Keras基礎知識。
  4. 瞭解 Keras 序列模型
    4.1 實際例子講解線性迴歸問題
  5. 使用 Keras 保存和回覆預訓練的模型
  6. Keras API
    6.1 使用Keras API開發VGG卷積神經網絡
    6.2 使用Keras API構建並運行SqueezeNet卷積神經網絡

1. 爲何選擇Keras?

Keras 是 Google 的一位工程師 François Chollet 開發的一個框架,能夠幫助你在 Theano 上面進行快速原型開發。後來,這被擴展爲 TensorFlow 也能夠做爲後端。而且最近,TensorFlow決定將其做爲 contrib 文件中的一部分進行提供。算法

Keras 被認爲是構建神經網絡的將來,如下是一些它流行的緣由:json

  1. 輕量級和快速開發:Keras 的目的是在消除樣板代碼。幾行 Keras 代碼就能比原生的 TensorFlow 代碼實現更多的功能。你也能夠很輕鬆的實現 CNN 和 RNN,而且讓它們運行在 CPU 或者 GPU 上面。
  2. 框架的「贏者」:Keras 是一個API,運行在別的深度學習框架上面。這個框架能夠是 TensorFlow 或者 Theano。Microsoft 也計劃讓 CNTK 做爲 Keras 的一個後端。目前,神經網絡框架世界是很是分散的,而且發展很是快。具體,你能夠看看 Karpathy 的這個推文:

想象一下,咱們每一年都要去學習一個新的框架,這是多麼的痛苦。到目前爲止,TensorFlow 彷佛成爲了一種潮流,而且愈來愈多的框架開始爲 Keras 提供支持,它可能會成爲一種標準。ubuntu

目前,Keras 是成長最快的一種深度學習框架。由於可使用不一樣的深度學習框架做爲後端,這也使得它成爲了流行的一個很大的緣由。你能夠設想這樣一個場景,若是你閱讀到了一篇頗有趣的論文,而且你想在你本身的數據集上面測試這個模型。讓咱們再次假設,你對TensorFlow 很是熟悉,可是對Theano瞭解的很是少。那麼,你必須使用TensorFlow 對這個論文進行復現,可是這個週期是很是長的。可是,若是如今代碼是採用Keras寫的,那麼你只要將後端修改成TensorFlow就可使用代碼了。這將是對社區發展的一個巨大的推進做用。後端

2. 怎麼安裝Keras,而且把TensorFlow做爲後端

a) 依賴安裝

安裝 h5py,用於模型的保存和載入:數組

pip install h5py

還有一些依賴包也要安裝。微信

pip install numpy scipy
pip install pillow

若是你尚未安裝TensorFlow,那麼你能夠按照這個教程先去安裝TensorFlow。一旦,你安裝完成了 TensorFlow,你只須要使用 pip 很容易的安裝 Keras。網絡

sudo pip install keras

使用如下命令來查看 Keras 版本。

>>> import keras
Using TensorFlow backend.
>>> keras.__version__
'2.0.4'

一旦,Keras 被安裝完成,你須要去修改後端文件,也就是去肯定,你須要 TensorFlow 做爲後端,仍是 Theano 做爲後端,修改的配置文件位於 ~/.keras/keras.json 。具體配置以下:

{
    "floatx": "float32",
    "epsilon": 1e-07,
    "backend": "tensorflow",
    "image_data_format": "channels_last"
}

請注意,參數 image_data_format channels_last ,也就是說這個後端是 TensorFlow。由於,在TensorFlow中圖像的存儲方式是[height, width, channels],可是在Theano中是徹底不一樣的,也就是 [channels, height, width]。所以,若是你沒有正確的設置這個參數,那麼你模型的中間結果將是很是奇怪的。對於Theano來講,這個參數就是channels_first

那麼,至此你已經準備好了,使用Keras來構建模型,而且把TensorFlow做爲後端。

3. Keras基礎知識

在Keras中主要的數據結構是 model ,該結構定義了一個完整的圖。你能夠向已經存在的圖中加入任何的網絡結構。

import keras

Keras 有兩種不一樣的建模方式:

  1. Sequential models:這種方法用於實現一些簡單的模型。你只須要向一些存在的模型中添加層就好了。
  2. Functional API:Keras的API是很是強大的,你能夠利用這些API來構造更加複雜的模型,好比多輸出模型,有向無環圖等等。

在本文的下一節中,咱們將學習Keras的Sequential models 和 Functional API的理論和實例。

4. Keras Sequential models

在這一部分中,我未來介紹Keras Sequential models的理論。我將快速的解釋它是如何工做的,還會利用具體代碼來解釋。以後,咱們將解決一個簡單的線性迴歸問題,你能夠在閱讀的同時運行代碼,來加深印象。

如下代碼是如何開始導入和構建序列模型。

from keras.models import Sequential
models = Sequential()

接下來咱們能夠向模型中添加 Dense(full connected layer),Activation,Conv2D,MaxPooling2D函數。

from keras.layers import Dense, Activation, Conv2D, MaxPooling2D, Flatten, Dropout
model.add(Conv2D(64, (3,3), activation='relu', input_shape = (100,100,32)))
# This ads a Convolutional layer with 64 filters of size 3 * 3 to the graph

如下是如何將一些最流行的圖層添加到網絡中。我已經在卷積神經網絡教程中寫了不少關於圖層的描述。

1. 卷積層

這裏咱們使用一個卷積層,64個卷積核,維度是3*3的,以後採用 relu 激活函數進行激活,輸入數據的維度是 100*100*32。注意,若是是第一個卷積層,那麼必須加上輸入數據的維度,後面幾個這個參數能夠省略。

model.add(Conv2D(64, (3,3), activation='relu', input_shape = (100,100,32)))

2. MaxPooling 層

指定圖層的類型,而且指定赤的大小,而後自動完成赤化操做,酷斃了!

model.add(MaxPooling2D(pool_size=(2,2)))

3. 全鏈接層

這個層在 Keras 中稱爲被稱之爲 Dense 層,咱們只須要設置輸出層的維度,而後Keras就會幫助咱們自動完成了。

model.add(Dense(256, activation='relu'))

4. Dropout

model.add(Dropout(0.5))

5. 扁平層

model.add(Flatten())

數據輸入

網絡的第一層須要讀入訓練數據。所以咱們須要去制定輸入數據的維度。所以,input_shape參數被用於制定輸入數據的維度大小。

model.add(Conv2D(32, (3,3), activation='relu', input_shape=(224, 224, 3)))

在這個例子中,數據輸入的第一層是一個卷積層,輸入數據的大小是 224*224*3

以上操做就幫助你利用序列模型構建了一個模型。接下來,讓咱們學習最重要的一個部分。一旦你指定了一個網絡架構,你還須要指定優化器和損失函數。咱們在Keras中使用compile函數來達到這個功能。好比,在下面的代碼中,咱們使用 rmsprop 來做爲優化器,binary_crossentropy 來做爲損失函數值。

model.compile(loss='binary_crossentropy', optimizer='rmsprop')

若是你想要使用隨機梯度降低,那麼你須要選擇合適的初始值和超參數:

from keras.optimizers import SGD
sgd = SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='categorical_crossentropy', optimizer=sgd)

如今,咱們已經構建完了模型。接下來,讓咱們向模型中輸入數據,在Keras中是經過 fit 函數來實現的。你也能夠在該函數中指定 batch_sizeepochs 來訓練。

model.fit(x_train, y_train, batch_size = 32, epochs = 10, validation_data(x_val, y_val))

最後,咱們使用 evaluate 函數來測試模型的性能。

score = model.evaluate(x_test, y_test, batch_size = 32)

這些就是使用序列模型在Keras中構建神經網絡的具體操做步驟。如今,咱們來構建一個簡單的線性迴歸模型。

4.1 實際例子講解線性迴歸問題

問題陳述

在線性迴歸問題中,你能夠獲得不少的數據點,而後你須要使用一條直線去擬合這些離散點。在這個例子中,咱們建立了100個離散點,而後用一條直線去擬合它們。

a) 建立訓練數據

TrainX 的數據範圍是 -1 到 1,TrainY 與 TrainX 的關係是3倍,而且咱們加入了一些噪聲點。

import keras
from keras.models import Sequential
from keras.layers import Dense
import numpy as np
 
trX = np.linspace(-1, 1, 101)
trY = 3 * trX + np.random.randn(*trX.shape) * 0.33

b) 構建模型

首先咱們須要構建一個序列模型。咱們須要的只是一個簡單的連接,所以咱們只須要使用一個 Dense 層就夠了,而後用線性函數進行激活。

model = Sequential()
model.add(Dense(input_dim=1, output_dim=1, init='uniform', activation='linear'))

下面的代碼將設置輸入數據 x,權重 w 和偏置項 b。然咱們來看看具體的初始化工做。以下:

weights = model.layers[0].get_weights()
w_init = weights[0][0][0]
b_init = weights[1][0]
print('Linear regression model is initialized with weights w: %.2f, b: %.2f' % (w_init, b_init)) 
## Linear regression model is initialized with weight w: -0.03, b: 0.00

如今,咱們能夠l利用本身構造的數據 trXtrY 來訓練這個線性模型,其中 trYtrX 的3倍。所以,權重 w 的值應該是 3。

咱們使用簡單的梯度降低來做爲優化器,均方偏差(MSE)做爲損失值。以下:

model.compile(optimizer='sgd', loss='mse')

最後,咱們使用 fit 函數來輸入數據。

model.fit(trX, trY, nb_epoch=200, verbose=1)

在通過訓練以後,咱們再次打印權重:

weights = model.layers[0].get_weights()
w_final = weights[0][0][0]
b_final = weights[1][0]
print('Linear regression model is trained to have weight w: %.2f, b: %.2f' % (w_final, b_final))
     
##Linear regression model is trained to have weight w: 2.94, b: 0.08

正如你所看到的,在運行 200 輪以後,如今權重很是接近於 3。你能夠將運行的輪數修改成區間 [100, 300] 之間,而後觀察輸出結構有什麼變化。如今,你已經學會了利用不多的代碼來構建一個線性迴歸模型,若是要構建一個相同的模型,在 TensorFlow 中須要用到更多的代碼。

5. 使用 Keras 保存和回覆預訓練的模型

HDF5 二進制格式

一旦你利用Keras完成了訓練,你能夠將你的網絡保存在HDF5裏面。固然,你須要先安裝 h5py。HDF5 格式很是適合存儲大量的數字收,並從 numpy 處理這些數據。好比,咱們能夠輕鬆的將存儲在磁盤上的多TB數據集進行切片,就好像他們是真正的 numpy 數組同樣。你還能夠將多個數據集存儲在單個文件中,遍歷他們或者查看 .shape.dtype 屬性。

若是你須要信心,那麼告訴你,NASA也在使用 HDF5 進行數據存儲。h5py 是python對HDF5 C API 的封裝。幾乎你能夠用C在HDF5上面進行的任何操做均可以用python在h5py上面操做。

保存權重

若是你要保存訓練好的權重,那麼你能夠直接使用 save_weights 函數。

model.save_weights("my_model.h5")

載入預訓練權重

若是你想要載入之前訓練好的模型,那麼你可使用 load_weights 函數。

model.load_weights('my_model_weights.h5')

6. Keras API

若是對於簡單的模型和問題,那麼序列模型是很是好的方式。可是若是你要構建一個現實世界中複雜的網絡,那麼你就須要知道一些功能性的API,在不少流行的神經網絡中,咱們都有一個最小的網絡結構,完整的模型是根據這些最小的模型進行疊加完成的。這些基礎的API可讓你一層一層的構建模型。所以,你只須要不多的代碼就能夠來構建一個完整的複雜神經網絡。

讓咱們來看看它是如何工做的。首先,你須要導入一些包。

from keras.models import Model

如今,你須要去指定輸入數據,而不是在順序模型中,在最後的 fit 函數中輸入數據。這是序列模型和這些功能性的API之間最顯著的區別之一。咱們使用 input() 函數來申明一個 1*28*28 的張量。

from keras.layers import Input
## First, define the vision modules

digit_input = Input(shape=(1, 28, 28))

如今,讓咱們來利用API設計一個卷積層,咱們須要指定要在在哪一個層使用卷積網絡,具體代碼這樣操做:

x = Conv2D(64, (3, 3))(digit_input)
x = Conv2D(64, (3, 3))(x)
x = MaxPooling2D((2, 2))(x)
out = Flatten()(x)

最後,咱們對於指定的輸入和輸出數據來構建一個模型。

vision_model = Model(digit_input, out)

固然,咱們還須要指定損失函數,優化器等等。但這些和咱們在序列模型中的操做同樣,你可使用 fit 函數和 compile 函數來進行操做。

接下來,讓咱們來構建一個vgg-16模型,這是一個很大很「老」的模型,可是因爲它的簡潔性,它是一個很好的學習模型。

6.1 使用Keras API開發VGG卷積神經網絡

VGG:

VGG卷積神經網絡是牛津大學在2014年提出來的模型。當這個模型被提出時,因爲它的簡潔性和實用性,立刻成爲了當時最流行的卷積神經網絡模型。它在圖像分類和目標檢測任務中都表現出很是好的結果。在2014年的ILSVRC比賽中,VGG 在Top-5中取得了92.3%的正確率。 該模型有一些變種,其中最受歡迎的固然是 vgg-16,這是一個擁有16層的模型。你能夠看到它須要維度是 224*224*3 的輸入數據。

*Vgg 16 architecture*

讓咱們來寫一個獨立的函數來完整實現這個模型。

img_input = Input(shape=input_shape)
# Block 1
x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(img_input)
x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)

# Block 2
x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x)
x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)

# Block 3
x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x)
x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x)
x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x)

# Block 4
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x)
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x)
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block4_pool')(x)

# Block 5
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x)
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x)
x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x)
x = MaxPooling2D((2, 2), strides=(2, 2), name='block5_pool')(x)

x = Flatten(name='flatten')(x)
x = Dense(4096, activation='relu', name='fc1')(x)
x = Dense(4096, activation='relu', name='fc2')(x)
x = Dense(classes, activation='softmax', name='predictions')(x)

咱們能夠將這個完整的模型,命名爲 vgg16.py。

在這個例子中,咱們來運行 imageNet 數據集中的某一些數據來進行測試。具體代碼以下:

model = applications.VGG16(weights='imagenet')
img = image.load_img('cat.jpeg', target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
preds = model.predict(x)
for results in decode_predictions(preds):
    for result in results:
        print('Probability %0.2f%% => [%s]' % (100*result[2], result[1]))

正如你在圖中看到的,模型會對圖片中的物體進行一個識別預測。

咱們經過API構建了一個VGG模型,可是因爲VGG是一個很簡單的模型,因此並無徹底將API的能力開發出來。接下來,咱們經過構建一個 SqueezeNet模型,來展現API的真正能力。

6.2 使用Keras API構建並運行SqueezeNet卷積神經網絡

SequeezeNet 是一個很是了不得的網絡架構,它的顯著點不在於對正確性有多少的提升,而是減小了計算量。當SequeezeNet的正確性和AlexNet接近時,可是ImageNet上面的預訓練模型的存儲量小於5 MB,這對於在現實世界中使用CNN是很是有利的。SqueezeNet模型引入了一個 Fire模型,它由交替的 Squeeze 和 Expand 模塊組成。

*SqueezeNet fire module*

如今,咱們對 fire 模型進行屢次複製,從而來構建完整的網絡模型,具體以下:

爲了去構建這個網絡,咱們將利用API的功能首先來構建一個單獨的 fire 模塊。

# Squeeze part of fire module with 1 * 1 convolutions, followed by Relu
x = Convolution2D(squeeze, (1, 1), padding='valid', name='fire2/squeeze1x1')(x)
x = Activation('relu', name='fire2/relu_squeeze1x1')(x)

#Expand part has two portions, left uses 1 * 1 convolutions and is called expand1x1 
left = Convolution2D(expand, (1, 1), padding='valid', name='fire2/expand1x1')(x)
left = Activation('relu', name='fire2/relu_expand1x1')(left)

#Right part uses 3 * 3 convolutions and is called expand3x3, both of these are follow#ed by Relu layer, Note that both receive x as input as designed. 
right = Convolution2D(expand, (3, 3), padding='same', name='fire2/expand3x3')(x)
right = Activation('relu', name='fire2/relu_expand3x3')(right)

# Final output of Fire Module is concatenation of left and right. 
x = concatenate([left, right], axis=3, name='fire2/concat')

爲了重用這些代碼,咱們能夠將它們轉換成一個函數:

sq1x1 = "squeeze1x1"
exp1x1 = "expand1x1"
exp3x3 = "expand3x3"
relu = "relu_"
WEIGHTS_PATH = "https://github.com/rcmalli/keras-squeezenet/releases/download/v1.0/squeezenet_weights_tf_dim_ordering_tf_kernels.h5"

模塊化處理

sq1x1 = "squeeze1x1"
exp1x1 = "expand1x1"
exp3x3 = "expand3x3"
relu = "relu_"

def fire_module(x, fire_id, squeeze=16, expand=64):
   s_id = 'fire' + str(fire_id) + '/'
   x = Convolution2D(squeeze, (1, 1), padding='valid', name=s_id + sq1x1)(x)
   x = Activation('relu', name=s_id + relu + sq1x1)(x)

   left = Convolution2D(expand, (1, 1), padding='valid', name=s_id + exp1x1)(x)
   left = Activation('relu', name=s_id + relu + exp1x1)(left)

   right = Convolution2D(expand, (3, 3), padding='same', name=s_id + exp3x3)(x)
   right = Activation('relu', name=s_id + relu + exp3x3)(right)

   x = concatenate([left, right], axis=3, name=s_id + 'concat')
return x

如今,咱們能夠利用咱們構建好的單獨的 fire 模塊,來構建完整的模型。

x = Convolution2D(64, (3, 3), strides=(2, 2), padding='valid', name='conv1')(img_input)
x = Activation('relu', name='relu_conv1')(x)
x = MaxPooling2D(pool_size=(3, 3), strides=(2, 2), name='pool1')(x)

x = fire_module(x, fire_id=2, squeeze=16, expand=64)
x = fire_module(x, fire_id=3, squeeze=16, expand=64)
x = MaxPooling2D(pool_size=(3, 3), strides=(2, 2), name='pool3')(x)

x = fire_module(x, fire_id=4, squeeze=32, expand=128)
x = fire_module(x, fire_id=5, squeeze=32, expand=128)
x = MaxPooling2D(pool_size=(3, 3), strides=(2, 2), name='pool5')(x)

x = fire_module(x, fire_id=6, squeeze=48, expand=192)
x = fire_module(x, fire_id=7, squeeze=48, expand=192)
x = fire_module(x, fire_id=8, squeeze=64, expand=256)
x = fire_module(x, fire_id=9, squeeze=64, expand=256)
x = Dropout(0.5, name='drop9')(x)

x = Convolution2D(classes, (1, 1), padding='valid', name='conv10')(x)
x = Activation('relu', name='relu_conv10')(x)
x = GlobalAveragePooling2D()(x)
out = Activation('softmax', name='loss')(x)

model = Model(inputs, out, name='squeezenet')

完整的網絡模型咱們放置在 squeezenet.py 文件裏。咱們應該先下載 imageNet 預訓練模型,而後在咱們本身的數據集上面進行訓練和測試。下面的代碼就是實現了這個功能:

import numpy as np
from keras_squeezenet import SqueezeNet
from keras.applications.imagenet_utils import preprocess_input, decode_predictions
from keras.preprocessing import image

model = SqueezeNet()

img = image.load_img('pexels-photo-280207.jpeg', target_size=(227, 227))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)

preds = model.predict(x)
all_results = decode_predictions(preds)
for results in all_results:
  for result in results:
    print('Probability %0.2f%% => [%s]' % (100*result[2], result[1]))

對於相同的一幅圖預測,咱們能夠獲得以下的預測機率。

至此,咱們的Keras TensorFlow教程就結束了。但願能夠幫到你 :-)


做者:chen_h
微信號 & QQ:862251340
簡書地址:https://www.jianshu.com/p/205...

CoderPai 是一個專一於算法實戰的平臺,從基礎的算法到人工智能算法都有設計。若是你對算法實戰感興趣,請快快關注咱們吧。加入AI實戰微信羣,AI實戰QQ羣,ACM算法微信羣,ACM算法QQ羣。長按或者掃描以下二維碼,關注 「CoderPai」 微信號(coderpai)

圖片描述

圖片描述

相關文章
相關標籤/搜索