Keras基本用法

  • 2019 年 12 月 20 日
  • 筆記

Keras是目前使用最為廣泛的深度學習工具之一,它的底層可以支持TensorFlow、MXNet、CNTK和Theano。如今,Keras更是被直接引入了TensorFlow的核心代碼庫,成為TensorFlow官網提供的高層封裝之一。下面首先介紹最基本的Keras API,斌哥給出一個簡單的樣例,然後介紹如何使用Keras定義更加複雜的模型以及如何將Keras和原生態TensorFlow結合起來。

1、Keras基本用法

和TFLearn API類似,Keras API也對模型定義、損失函數、訓練過程等進行了封裝,而且封裝之後的整個訓練過程和TFLearn是基本一致的,可以分為數據處理、模型定義和模型訓練三個部分。使用原生態的Keras API需要先安裝Keras包,安裝的方法如下:

pip install keras

以下代碼展示了如何使用原生態Keras在MNIST數據集上實現LeNet-5模型。

# -*- coding: utf-8 -*-    import keras  from keras.datasets import mnist  from keras.models import Sequential  from keras.layers import Dense, Flatten, Conv2D, MaxPooling2D    num_class = 10    im_rows, im_cols = 28, 28    # 通過Keras封裝好的API加載MNIST數據。其中trainX就是一個60000x28x28的數組,trainY是每一張圖片對應的數字。  (trainX, trainY), (testX, testY) = mnist.load_data()    # 因為不同的底層(TensorFlow或者MXNet)對輸入的要求基本不一樣,所以這裡需要根據圖像編碼的格式要求來設置輸入層的格式。  if K.image_data_format() == 'channels_first':     trainX = trainX.reshape(trainX.shape[0], 1, img_rows, img_cols)     testX = testX.reshape(testX.shape[0], 1, img_rows, img_cols)     # 因為MNIST中的圖片是黑白的,所以第一維的取值為1,     input_shape = (1, img_rows, img_cols)  else:     trainX = trainX.reshape(trainX.shape[0], img_rows, img_cols, 1)     testX = testX.reshape(testX.shape[0], img_rows, img_cols, 1)     input_shapes = (img_rows, img_cols, 1)  # 將圖像像素轉化為0到1之間的實數。  trainX = trainX.astype('float32')  testX = testX.astype('float32')  trainX /= 255.0  testX  /= 255.0    # 將標準答案轉化為需要的格式(one-hot編碼)。  trainY = keras.utils.to_categorical(trainY, num_classes)  testY = keras.utils.to_categories(testY, num_classes)    # 使用Keras API定義模型。  model = Sequential()    # 一層深度為32,濾波器大小為5x5的卷積層。  model.add(MaxPooling2D(pool_size=(2,2)))  # 一層深度為64,濾波器大小為5x5的卷積層。  model.add(Conv2D(64, (5,5), activation='relu'))  # 一層濾波器大小為2x2的最大池化層。  model.add(MaxPooling2D(pool_size=(2,2)))  # 將卷積層的輸出拉直後作為下面全連接層的輸入。  model.add(Flatten())  # 全連接層,有500個節點。  model.add(Flatten())  # 全連接層,有500個節點。  model.add(Dense(500, activate='relu'))  # 全連接層,得到最後的輸出  model.add(Dense(num_classes, activate='softmax'))    # 定義損失函數、優化函數和測評方法。  model.compile(loss=losses.categorical_crossentropy,                optimazer = keras.optimizer.SGD()                metric = ['accuracy'])  # 類似TFLearn中的訓練過程,給出訓練數據、Batch大小、訓練論事和驗證數據,Keras可以自動完成模型訓練過程。  model.fit(trainX, trainY, batch_size=128, epochs=20, validation_data=(testX, testY))  #在測試數據上計算準確率。  score = model.evaluate(testX, testY)  print('Test loss:',score[0])  print('Test accuracy', score[1])

從以上代碼中可以看出使用Keras API訓練模型可以先定義一個Sequential類,然後在Sequential實例中通過add函數添加網絡層。Keras把卷積層、池化層、RNN結構(LSTM、GRU),全連接層等常用的神經網絡結構都做了封裝,可以很方便地實現深層神經網絡。在神經網絡結構定義好之後,Sequential實例可以通過compile函數,指定優化函數、損失函數以及訓練過程中需要監控等指標。Keras對優化函數、損失函數以及監控指標都有封裝,同時也支持使用自定義的方式,在Keras的API文檔中有詳細的介紹,這裡不再贅述。最後在網絡結構、損失函數和優化函數都定義好之後,Sequential實例可以通過fit函數來訓練模型。類似TFLearn中的fit函數,Keras的fit函數只需給出訓練數據,batch大小和訓練輪數,Keras就可以自動完成模型訓練的整個過程。

除了能夠很方便地處理圖像問題,Keras對訓練神經網絡的支持也是非常出色的。有了Keras APA,循環神經網絡的訓練體系也可以通過簡單的一句命令完成。以下代碼給出了如何通過Keras實現自然語言感情分類問題。使用循環網絡判斷語言的感情(比如在以下例子中需要判斷一個評價是好評還是差評)和自然語言建模問題類似,唯一的區別在於除了最後一個時間點的輸出是有意義的,其他時間點的輸出都可以忽略,下圖展示了使用循環網絡處理感情分析問題的模型結構。

# -*- coding: utf-8 -*-    from keras.preprocessing import sequence  from keras.models import Sequential  from keras.layers import Dense, Embedding  from keras.layers import LSTM  from keras.datasets import imdb    # 最多使用的單詞數。  max_features = 20000  # 循環神經網絡的截斷長度。  maxlen = 80  batch_size = 32  # 加載數據並將單詞轉化為ID, max_features給出了最多使用的單詞數。和自然語言模型類似,會將出現頻率 # 較低的單詞替換為統一的ID,通過Keras封裝的API生成25000條訓練數據和25000條測試數據,每一條數據可以  # 擺看成一段話,並且每段話都有一個好評或者差評的標籤。  (trainX, trainY), (testX, testY) = imdb.load_data(num_words=max_features)  print(len(trainX), 'train sequences')  print(len(trainY), 'test_sequences')    # 在自然語言處理中,每一段話的長度都是不一樣的,但循環神經網絡的循環長度是固定的,所以這裡需要首先  # 將所有段落統一成固定長度。對於長度不夠的段落,要使用默認值0來填充,對於超過長度  # 的段落則直接忽略掉超過的部分。  trainX = sequence.pad_sequences(trainX, maxlen = maxlen)  testX  = sequence.pad_sequences(testX, maxlen=mexlen)    # 輸出統一長度之後的數據維度:  # ('x_train shape:', (25000, 80))  # ('x_test shape:', (25000, 80))  print('trainX shape:', trainX.shape)  print('testX shape:', trainX.shape)    # 再完成數據預處理之後的模型結構  model = Sequential()  # 構建embedding層。128代表了embedding層的向量維度。  model.add(Embedding(max_features, 128))  # 構建LSTM層。  model.add(LSTM(128,dropout=0.2))  # 構建最後的全連接層。注意在上面構建LSTM層時只會得到最後一個節點輸出,  # 如果需要輸出每個時間點的結果,那麼可以將return_sequence參數設置為true。  model.add(Dense(1, activation='sigmoid'))    # 與MNIST樣例類似地指定損失函數,優化函數和評測指標。  model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])  # 與MNIST樣例類似地指定訓練數據,訓練輪數,batch大小以及驗證數據。  model.fitt(trainX, trainY, batch_size=batch_size, epochs=15, validation_data=(testX, testY))    # 在測試數據上評測模型  score = model.evaluate(testX, testY, batch_size=batch_size)  print('Test loss:', score[0])  print('Test accuracy:',score[1])

以上兩個樣例針對Keras的基本用法做了詳細的介紹。雖然Keras的封裝,很多經典的神經網絡結構能很快地被實現,不過要實現一些更加靈活的網絡結構、損失函數或者數據輸入方法,就需要對Keras的高級用法有更多的了解。

2、Keras高級用法

上面樣例中最重要的封裝就是Sequential類,所有的神經網絡定義和訓練都是通過Sequential實例來實現的。然而從這個類的名稱可以看出,它只支持順序模型的定義。類似Inception這樣的模型結構,通過Sequential類就不容易直接實現了。為了支持更加靈活的模型定義方法,Keras支持以返回值的形式定義網絡層結構。以下代碼展示了如何使用這種方式定義模型。

# -*- coding:utf-8 -*-    import keras  from keras.datasets import mnist  from keras.layers import Input, Dense  from keras.models import Model    # 使用1中介紹的類似方法生成trainingX、trainingY、testX、testY,唯一的  # 不同是這裡只使用了全連接層,所以不需要將輸入整理成三維矩陣。    ...    # 定義輸入,這裡指定的維度不用考慮batch大小。  inputs = Input(shape=(784,))  # 定義一層全連接,該層有500隱藏節點,使用ReLU激活函數,這一層的輸入為inputs。  x = Dense(500, activate='relu')(inputs)  # 定義輸出層。注意因為keras封裝需要指定softmax作為激活函數。  predictions = Dense(10, activate='softmax')(x)    # 通過Model類創建模型,和Sequential類不同的是Model類在初始化的時候需要指定模型的輸入和輸出。  model = Model(inputs=inputs, outputs=predictions)    # 與1中類似的方法定義損失函數、優化函數和評測方法。  model.complie(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.SGD(),metrics=['accuracy'])    # 與1中類似的方法訓練模型。  model.fit(trainX, trainY, batch_size=128, epoches=20, validation_data=(testX, testY))

通過這樣的方式,Keras就可以實現類似Inception這樣大的模型結構。以下代碼展示了如何通過Keras實現Inception結構。

from keras.layers import Conv2D, MaxPooling2D, Input    # 定義輸入圖像尺寸  input_img = Input(shape=(256,256,3))    # 定義第一個分支。  tower_1 = Conv2D(64,(1,1),padding='same',activation='relu')(input_img)  tower_1 = COnv2D(64,(3,3),padding='same',activation='relu')(tower_1)    # 定義第二個分支。與順序模型不同,第二個分支的輸入使用的是input_img,而不是第一個分支的輸出。  tower_2 = Conv2D(64,(1,1),padding='same',activation='relu')(input_img)  tower_2 = Conv2D(65,(5,5),padding='same',activation='relu')(tower_2)    # 定義第三個分支。類似地,第三個分支的輸入也是input_img,  tower_3 = MaxPooling2D((3,3),strides=(1,1),padding='same')(input_img)  tower_3 = Conv2D(64, (1,1), padding='same', activation='relu')(tower_3)    # 將第三個分支通過concatenate的方式拼接在一起  output = keras.layers.concatenate([tower_1, tower_2, tower_3], axis = 1)

除了可以支持順序模型,Keras也可以支持有多個輸入或者輸出的模型。以下代碼實現了下圖所示的網絡結構。

# -*- coding: utf-8 -*-    import keras  from tflearn,layers.core import fully_connected  from keras.datasets import mnist  from keras.layers import Input, Dense  from keras.models import Model    # 類似1的方式生成trainX, trainY, testX, testY  # 定義兩個輸入,一個輸入為原始的圖片信息,另一個輸入為正確答案。  input1 = Input(shape=(784,), name="input1")  input2 = Input(shape=(10,), name="input2")    # 定義一個只有一個隱藏節點的全連接網絡。  x = Dense(1, activation='relu')(input1)  # 定義只使用了一個隱藏節點的網絡結構的輸出層。  output1 = Desnse(10, activation='softmax',name="output1")(x)    # 將一個隱藏節點的輸出和正確答案拼接在一起,這個將作為第二個輸出層的輸入。  y = keras.layers.concatenate([x, input2])  # 定義第二個輸出層。  input3 = Dense(10, activation='softmax', name = "output2")(y)    # 定義一個有多個輸入和多個輸出的模型。這裡只需要將所有的輸入和輸出給出即可。  model = Model(inputs=[inputs1, inputs2], outputs = [output1, output2])    # 定義損失函數、優化函數和測評方法。若多個輸出的損失函數相同,可以只指定一個損失函數。  # 如果多個輸出的損失函數不同,則可以通過一個列表或一個字典來指定每一個輸出的損失函數。  # 比如可以使用:  # loss = {'output1':binary_crossentropy,'output2':binary_crossentropy}  # 求為不同的輸出指定不同的損失函數。類似地,Keras也支持為不同輸出產生的損失指定權重,  # 這可以通過loss_weights參數來完成。在下面的定義中,輸出output1的權重為1,output2的  # 權重為0.1,所以這個模型會更加偏向於優化的第一個輸出。    model.compile(loss=keras.losses.categorical_crossentropy, optimizer=keras.optimizers.SGD(), loss_weights = [1,0.1], metrics = ['accuracy'])    # 模型訓練過程。因為有兩個輸入和輸出,所以這裡提供的數據也需要有兩個輸入和兩個期待的正確答案輸出。  # 通過列表的方式提供數據時,Keras會假設數據給出的順序和定義Model類時輸入會給出的順序是對應的。為 # 了避免順序不一致導致的問題,推薦使用字典的形式給出:  #  model.fit(  #          {'input1':trainX, 'input2':trainY}  #          {'output1':trainX, 'output2':trainY}  #          ...)    model.fit([trainX, trainY], [trainY, trainY], batch_size=128, epochs=20, validation_data=([testX, testY], [testX, testY]))

從以上輸出可以看出Keras在訓練過程中會展示每個輸出層的loss和accuracy。因為輸出層output1隻使用了一個維度為1的隱藏點,所以正確率只有29.85%。雖然輸出層output2使用了正確答案作為輸入,但是因為在損失函數中權重較低(只有0.1),所以它的收斂速度較慢,在20個epoch時準確率也只有92.1%。如果將兩個輸出層的損失權重設為一樣,那麼輸出層output1在20個epoch時的準確率將只有27%,而輸出層output2的準確率可以達到99.9%。雖然通過返回值的方式已經可以實現大部分的神經網絡模型,然而Keras API還存在兩大問題。第一,原生態Keras API對訓練數據的處理流程支持得不太好,基本上需要一次性將數據全部全部加載到內存。第二,原生態Keras API無法支持分佈式訓練。為了解決這兩個問題,Keras提供了一種與原生態TensorFlow結合地更加緊密的方式。以下代碼顯示了如何將Keras和原生態TensorFlow API聯合起來解決MNIST問題。

# -*- coding: utf-8 -*-    import tensorflow as tf  from tensorflow.example.tutorials.mnist import input_data    mnist_data=input_data.read_data_sets('/path/to/MNIST_data', one_hot=True)    # 通過TensorFlow中的placeholder定義輸入。類似的,Keras封裝數據的網絡層結構也可以支持隊列輸入。  # 這樣可以有效避免一次性加載所有數據的問題。    x = tf.placeholder(tf.float32, shape=(None, 764))  y_ = tf.placeholder(tf.float32, shape=(None, 10))      # 直接使用TensorFlow中提供的Keras API定義網絡層結構。  net = tf.keras.layers.Dense(500, activate='relu')(x)  y = tf.keras.layers.Dense(10, activation='softmax')(net)    # 定義損失函數和優化方法。注意這裡可以混用Keras的API和原生態TensorFlow的API。  loss = tf.reduce_mean(tf.keras.losses.categorical_crossentropy(y_, y))  train_step = tf.train.GradientDescentOptimizer(0.5).minimize(loss)    # 定義正確的預測率作為指標。  acc_value = tf.reduce_mean(tf.keras.metric.categorical_accuracy(y_, y))    # 使用原生態TensorFlow的方式訓練模型。這樣就可以有效的實現分佈式。  with tf.Session() as sess:     tf.global_variables_initializer().run()     for i in range(10000):         xs, ys = mnist_data.train.next_batch(100)         _, loss_value = sess.run([train_step, loss], feed_dict={x:xs, y_:ys})           if i % 1000 == 0:            print("After %d training step(s), loss on training batch is " "%g." % (i,  loss_value))     print acc_value.eval(feed_dict={x:mnist_data.test.images, y_: mnist_data.test.labels})