使用Keras函数式API进行深度学习
序贯(sequential)API允许您为大多数问题逐层堆栈创建模型。虽然说对很多的应用来说, 这样的一个手法很简单也解决了很多深度学习网络结构的构建,但是它也有限制 – 它不允许你创建模型有共享层或有多个输入或输出的网络。
Keras中的函数式(functional)API是创建网络模型的另一种方式,它提供了更多的灵活性,包括创建更复杂的模型。
在这个文章中,您将了解如何使用Keras中更灵活的函数式(functional)API来定义深度学习模型。
完成这个文章的相关范例, 您将知道:
* Sequential和FunctionalAPI之间的区别。
* 如何使用功能性(functional)API定义简单的多层感知器(MLP),卷积神经网络(CNN)和递归神经网络(RNN)模型。
* 如何用共享层和多个输入输出来定义更复杂的模型。
这个是工作环境:
# 这个Jupyter Notebook的环境
import platform
import tensorflow
import keras
print("Platform: {}".format(platform.platform()))
print("Tensorflow version: {}".format(tensorflow.__version__))
print("Keras version: {}".format(keras.__version__))
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
from IPython.display import Image
1. Keras 序貫模型 (Sequential Models)
from keras.models import Sequential
from keras.layers import Dense
# 构建模型
model = Sequential([Dense(2, input_shape=(1,)), Dense(1)])
当然我们也可以一层一层分段添加上去
from keras.models import Sequential
from keras.layers import Dense
# 构建模型
model = Sequential()
model.add(Dense(2, input_shape=(1,)))
model.add(Dense(1))
2.Keras 函数式(functional)API构建模型
Keras函数式(functional)API为构建网络模型提供了更为灵活的方式。
它允许您定义多个输入或输出模型以及共享图层的模型。除此之外,它允许您定义动态(ad-hoc)的非周期性(acyclic)网络图。
模型是通过创建层的实例(layerinstances)并将它们直接相互连接成对来定义的,然后定义一个模型(model)来指定那些层是要作为这个模型的输入和输出。
让我们依次看看Keras功能(functional)API的三个独特特性:
2.1 定义输入
与Sequential模型不同,您必须创建独立的Input层对象的instance并定义输入数据张量的维度形状(tensor shape)。
输入层采用一个张量形状参数(tensorshape),它是一个tuple,用于宣吿输入张量的维度。
例如: 我们要把MNIST的每张图像(28×28)打平成一个一维(784)的张量做为一个多层感知器(MLP)的Input
from keras.layers import Input
mnist_input = Input(shape=(784,))
2.2 连接不同的网络层
模型中的神经层是成对连接的,就像是一个乐高积木一样有一面是凸一面是凹, 一个神经层的输出会接到另一个神经层的输入。
这是通过在定义每个新神经层时指定输入的来源来完成的。使用括号表示法,以便在创建图层之后,指定作为输入的神经层。
我们用一个简短的例子来说明这一点。我们可以像上面那样创建输入层,然后创建一个隐藏层作为密集层,它接收来自输入层的输入。
from keras.layers import Input
from keras.layers import Dense
mnist_input = Input(shape=(784,))
hidden = Dense(512)(mnist_input)
正是这种逐层连接的方式赋予功能性(functional)API灵活性。您可以看到开始一些动态的神经网络是多么容易。
2.3 创建模型
在创建所有模型图层并将它们连接在一起之后,您必须定义一个模型(Model)对象的instance。
与SequentialAPI一样,这个模型是您可以用于总结(summarize),拟合(fit),评估(evaluate)和预测(predict)。
Keras提供了一个Model类别,您可以使用它从创建的图层创建模型的instance。它会要求您只指定整个模型的第一个输入层和最后一个的输出层。例如:
from keras.layers import Input
from keras.layers import Dense
from keras.models import Model
mnist_input = Input(shape=(784,))
hidden = Dense(512)(mnist_input)
model = Model(inputs=mnist_input, outputs=hidden)
现在我们已经知道了Keras函数式API的所有关键部分,让我们通过定义一系列不同的模型来开展工作。
每个范例都是可以执行的,并打印网络结构及产生网络图表。我建议你为自己的模型做这个事情,以明确你所定义的是什么样的网络结构。
我希望这些范例能够在将来使用函数式API定义自己的模型时为您提供模板。
3.标准网络模型
在开始使用函数式API时,最好先看一些标准的神经网络模型是如何定义的。在本节中,我们将着眼于定义一个简单的多层感知器(MLP),卷积神经网络(CNN)和递归神经网络(RNN)。这些范例将为以后了解更复杂的网络构建提供基础。
3.1 多层感知器(Multilayer Perceptron)
用pydot和graphviz时总是出现错误,按照这个方法顺利解决。
Error description:
OSError: `pydot` failed to call GraphViz.Please install GraphViz (https://www.graphviz.org/) and ensure that its executables are in the $PATH.
Environment: Anaconda, Python 3.6.7, Keras 2.2.4
Solution:
conda install python-graphviz
conda install pydot-ng pydot
if not work
also do:
1 . Download and install graphviz-2.38.msi from
https://graphviz.gitlab.io/_pages/Download/Download_windows.html
Double-click to install graphviz-2.38.msi using default path
2 . Set the path variable
(a) Control Panel > System and Security > System > Advanced System Settings > Environment Variables > Path > Edit
(b) add ‘C:\Program Files (x86)\Graphviz2.38\bin’
3. Restart python coding environment
eferences:
https://www.experts-exchange.com/questions/29106033/Pydot-having-problems-with-GraphViz.html
https://github.com/ContinuumIO/anaconda-issues/issues/1666
让我们来定义了一个多类别分类(multi-class classification)的多层感知器(MLP)模型。该模型有784个输入,3个隐藏层,512,216和128个隐藏神经元,输出层有10个输出。在每个隐藏层中使用relu
激活函数,并且在输出层中使用softmax
激活函数进行多类别分类。
# 多層感知器(MLP)模型
from keras.models import Model
from keras.layers import Input, Dense
from keras.utils import plot_model
mnist_input = Input(shape=(784,), name='input')
hidden1 = Dense(512, activation='relu', name='hidden1')(mnist_input)
hidden2 = Dense(216, activation='relu', name='hidden2')(hidden1)
hidden3 = Dense(128, activation='relu', name='hidden3')(hidden2)
output = Dense(10, activation='softmax', name='output')(hidden3)
model = Model(inputs=mnist_input, outputs=output)
# 打印網絡結構
model.summary()
# 產生網絡拓撲圖
plot_model(model, to_file='multilayer_perceptron_graph.png')
# 秀出網絡拓撲圖
Image('multilayer_perceptron_graph.png')
3.2 卷积神经网络(CNN)
我们将定义一个用于图像分类的卷积神经网络(convolutional neural network)。
该模型接收灰阶的28×28图像作为输入,然后有一个作为特征提取器的两个卷积和池化层的序列,
然后是一个完全连接层来解释特征,并且具有用于10类预测的softmax
激活的输出层。
# 卷積神經網絡(CNN)
from keras.models import Model
from keras.layers import Input, Dense
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPool2D
from keras.utils import plot_model
mnist_input = Input(shape=(28, 28, 1), name='input')
conv1 = Conv2D(128, kernel_size=4, activation='relu', name='conv1')(mnist_input)
pool1 = MaxPool2D(pool_size=(2, 2), name='pool1')(conv1)
conv2 = Conv2D(64, kernel_size=4, activation='relu', name='conv2')(pool1)
pool2 = MaxPool2D(pool_size=(2, 2), name='pool2')(conv2)
hidden1 = Dense(64, activation='relu', name='hidden1')(pool2)
output = Dense(10, activation='softmax', name='output')(hidden1)
model = Model(inputs=mnist_input, outputs=output)
# 打印網絡結構
model.summary()
# 產生網絡拓撲圖
plot_model(model, to_file='convolutional_neural_network.png')
# 秀出網絡拓撲圖
Image('convolutional_neural_network.png')
3.3 递归神经网络(RNN)
我们将定义一个长期短期记忆(LSTM)递归神经网络用于图像分类。该模型预期一个特征的784个时间步骤作为输入。该模型具有单个LSTM隐藏层以从序列中提取特征, 接着是完全连接的层来解释LSTM输出,接着是用于进行10类别预测的输出层。
# 遞歸神經網絡(RNN)
from keras.models import Model
from keras.layers import Input, Dense
from keras.layers.recurrent import LSTM
from keras.utils import plot_model
mnist_input = Input(shape=(784, 1), name='input') # 把每一個像素想成是一序列有前後關係的time_steps
lstm1 = LSTM(128, name='lstm1')(mnist_input)
hidden1 = Dense(128, activation='relu', name='hidden1')(lstm1)
output = Dense(10, activation='softmax', name='output')(hidden1)
model = Model(inputs=mnist_input, outputs=output)
# 打印網絡結構
model.summary()
# 產生網絡拓撲圖
plot_model(model, to_file='recurrent_neural_network.png')
# 秀出網絡拓撲圖
Image('recurrent_neural_network.png')
4.共享层模型
多个神经层可以共享一个神经层的输出来当成输入。
例如,一个输入可能可以有多个不同的特征提取层,或者多个神经层用于解释特征提取层的输出。
我们来看这两个例子。
4.1 共享输入层 (Shared Input Layer)
我们定义具有不同大小的内核的多个卷积层来解释图像输入。
该模型使用28×28像素的灰阶图像。有两个CNN特征提取子模型共享这个输入;第一个具有4的内核大小和第二个8的内核大小。
这些特征提取子模型的输出被平坦化(flatten)为向量(vector),并且被串连成一个长向量, 然后被传递到完全连接的层以用于在最终输出层之前进行10类别预测。
# 共享輸入層
from keras.models import Model
from keras.layers import Input, Dense, Flatten
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPool2D
from keras.layers.merge import concatenate
from keras.utils import plot_model
# 輸入層
mnist_input = Input(shape=(28, 28, 1), name='input')
# 第一個特徵提取層
conv1 = Conv2D(32, kernel_size=4, activation='relu', name='conv1')(mnist_input) # <-- 看這裡
pool1 = MaxPool2D(pool_size=(2, 2), name='pool1')(conv1)
flat1 = Flatten()(pool1)
# 第二個特徵提取層
conv2 = Conv2D(16, kernel_size=8, activation='relu', name='conv2')(mnist_input) # <-- 看這裡
pool2 = MaxPool2D(pool_size=(2, 2), name='pool2')(conv2)
flat2 = Flatten()(pool2)
# 把兩個特徵提取層的結果併起來
merge = concatenate([flat1, flat2])
# 進行全連結層
hidden1 = Dense(64, activation='relu', name='hidden1')(merge)
# 輸出層
output = Dense(10, activation='softmax', name='output')(hidden1)
# 以Model來組合整個網絡
model = Model(inputs=mnist_input, outputs=output)
# 打印網絡結構
model.summary()
# plot graph
plot_model(model, to_file='shared_input_layer.png')
# 秀出網絡拓撲圖
Image('shared_input_layer.png')
4.2 共享特征提取层 (Shared Feature Extraction Layer)
我们将使用两个并行子模型来解释用于序列分类的LSTM特征提取器的输出。
该模型的输入是1个特征的784个时间步长。具有10个存储单元的LSTM层解释这个序列。第一种解释模型是浅层单连通层,第二层是深层3层模型。两个解释模型的输出连接成一个长向量,传递给用于进行10类别分类预测的输出层。
from keras.models import Model
from keras.layers import Input, Dense
from keras.layers.recurrent import LSTM
from keras.layers.merge import concatenate
from keras.utils import plot_model
# 輸入層
mnist_input = Input(shape=(784, 1), name='input') # 把每一個像素想成是一序列有前後關係的time_steps
# 特徵提取層
extract1 = LSTM(128, name='lstm1')(mnist_input)
# 第一個解釋層
interp1 = Dense(10, activation='relu', name='interp1')(extract1) # <-- 看這裡
# 第二個解釋層
interp21 = Dense(64, activation='relu', name='interp21')(extract1) # <-- 看這裡
interp22 = Dense(32, activation='relu', name='interp22')(interp21)
interp23 = Dense(16, activation='relu', name='interp23')(interp22)
# 把兩個特徵提取層的結果併起來
merge = concatenate([interp1, interp23], name='merge')
# 輸出層
output = Dense(10, activation='softmax', name='output')(merge)
# 以Model來組合整個網絡
model = Model(inputs=mnist_input, outputs=output)
# 打印網絡結構
model.summary()
# plot graph
plot_model(model, to_file='shared_feature_extractor.png')
# 秀出網絡拓撲圖
Image('shared_feature_extractor.png')
5.多种输入和输出模型
函数式(functional)API也可用于开发具有多个输入或多个输出的模型的更复杂的模型。
5.1 多输入模型
我们将开发一个图像分类模型,将图像的两个版本作为输入,每个图像的大小不同。特别是一个灰阶的64×64版本和一个32×32的彩色版本。分离的特征提取CNN模型对每个模型进行操作,然后将两个模型的结果连接起来进行解释和最终预测。
请注意,在创建Model()实例(instance)时,我们将两个输入图层定义为一个数组(array)。
# 多輸入模型
from keras.models import Model
from keras.layers import Input, Dense, Flatten
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPool2D
from keras.layers.merge import concatenate
from keras.utils import plot_model
# 第一個輸入層
img_gray_bigsize = Input(shape=(64, 64, 1), name='img_gray_bigsize')
conv11 = Conv2D(32, kernel_size=4, activation='relu', name='conv11')(img_gray_bigsize)
pool11 = MaxPool2D(pool_size=(2, 2), name='pool11')(conv11)
conv12 = Conv2D(16, kernel_size=4, activation='relu', name='conv12')(pool11)
pool12 = MaxPool2D(pool_size=(2, 2), name='pool12')(conv12)
flat1 = Flatten()(pool12)
# 第二個輸入層
img_rgb_smallsize = Input(shape=(32, 32, 3), name='img_rgb_smallsize')
conv21 = Conv2D(32, kernel_size=4, activation='relu', name='conv21')(img_rgb_smallsize)
pool21 = MaxPool2D(pool_size=(2, 2), name='pool21')(conv21)
conv22 = Conv2D(16, kernel_size=4, activation='relu', name='conv22')(pool21)
pool22 = MaxPool2D(pool_size=(2, 2), name='pool22')(conv22)
flat2 = Flatten()(pool22)
# 把兩個特徵提取層的結果併起來
merge = concatenate([flat1, flat2])
# 用隱藏的全連結層來解釋特徵
hidden1 = Dense(128, activation='relu', name='hidden1')(merge)
hidden2 = Dense(64, activation='relu', name='hidden2')(hidden1)
# 輸出層
output = Dense(10, activation='softmax', name='output')(hidden2)
# 以Model來組合整個網絡
model = Model(inputs=[img_gray_bigsize, img_rgb_smallsize], outputs=output)
# 打印網絡結構
model.summary()
# plot graph
plot_model(model, to_file='multiple_inputs.png')
# 秀出網絡拓撲圖
Image('multiple_inputs.png')
5.2 多輸出模型
我们将开发一个模型,进行两种不同类型的预测。给定一个特征的784个时间步长的输入序列,该模型将对该序列进行分类并输出具有相同长度的新序列。
LSTM层解释输入序列并返回每个时间步的隐藏状态。第一个输出模型创建一个堆栈的LSTM,解释这些特征,并进行多类别预测。第二个输出模型使用相同的输出层对每个输入时间步进行多类别预测。
# 多輸出模型
from keras.models import Model
from keras.layers import Input, Dense
from keras.layers.recurrent import LSTM
from keras.layers.wrappers import TimeDistributed
from keras.utils import plot_model
# 輸入層
mnist_input = Input(shape=(784, 1), name='input') # 把每一個像素想成是一序列有前後關係的time_steps
# 特徵擷取層
extract = LSTM(64, return_sequences=True, name='extract')(mnist_input)
# 分類輸出
class11 = LSTM(32, name='class11')(extract)
class12 = Dense(32, activation='relu', name='class12')(class11)
output1 = Dense(10, activation='softmax', name='output1')(class12)
# 序列輸出
output2 = TimeDistributed(Dense(10, activation='softmax'), name='output2')(extract)
# 以Model來組合整個網絡
model = Model(inputs=mnist_input, outputs=[output1, output2])
# 打印網絡結構
model.summary()
# plot graph
plot_model(model, to_file='multiple_outputs.png')
# 秀出網絡拓撲圖
6.总结 (Conclusion)
以上有一些小技巧可以帮助你充分利用函数式API定义自己的模型。
* 一致性的变量名称命名
对输入(可见)和输出神经层(输出)使用相同的变量名,甚至可以使用隐藏层(hidden1,hidden2)。这将有助于正确地将许多的神经层连接在一起。
* 检查图层摘要
始终打印模型摘要并查看图层输出,以确保模型如您所期望的那样连接在一起。
* 查看网络拓朴图像
总是尽可能地创建网络拓朴图像,并审查它,以确保一切按照你的意图连接在一起。
* 命名图层
您可以为图层指定名称,这些名称可以让你的模型图形摘要和网络拓朴图像更容易被解读。例如:Dense(1,name =’hidden1’)。
* 独立子模型
考虑分离出子模型的发展,并最终将子模型结合在一起。
文章转自微信公众号@兀知笔记