Keras使得创建深度学习模型变得快速而简单。
序贯(sequential)API允许您为大多数问题逐层堆叠创建模型。虽然说对很多的应用来说, 这样的一个手法很简单也解决了很多深度学习网络结构的构建,但是它也有限制- 它不允许你创建模型有共享层或有多个输入或输出的网络。
Keras中的函数式(functional)API是创建网络模型的另一种方式,它提供了更多的灵活性,包括创建更复杂的模型。
在这个文章中,您将了解如何使用Keras函数式API进行深度学习。
完成这个文章的相关范例, 您将知道:
- Sequential和Functional API之间的区别。
- 如何使用功能性(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
Platform: Windows-7-6.1.7601-SP1
Tensorflow version: 1.4.0
Keras version: 2.1.1
Keras序贯模型(Sequential Models)
Keras提供了一个Sequential模型API。
它是创建深度学习模型的一种相对简单的方法,我们透过创建Kears的Sequential类别实例(instance), 然后创建模型图层并添加到其中。
例如,可以定义多个图层并将以阵列的方式一次做为参数传递给Sequential:
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 ))
Sequential模型API对于在大多数情况下非常有用与方便,但也有一些局限性。例如,网络拓扑结构可能具有多个不同输入,产生多个输出或重复使用共享图层的复杂模型。
Keras函数式(functional)API构建模型
Keras函数式(functional)API为构建网络模型提供了更为灵活的方式。
它允许您定义多个输入或输出模型以及共享图层的模型。除此之外,它允许您定义动态(ad-hoc)的非周期性(acyclic)网络图。
模型是通过创建层的实例(layer instances)并将它们直接相互连接成对来定义的,然后定义一个模型(model)来指定那些层是要作为这个模型的输入和输出。
让我们依次看看Keras功能(functional)API的三个独特特性:
定义输入
与Sequential模型不同,您必须创建独立的Input层物件的instance并定义输入数据张量的维度形状(tensor shape)。
输入层采用一个张量形状参数(tensor shape),它是一个tuple,用于宣吿输入张量的维度。
例如: 我们要把MNIST的每张图像(28×28)打平成一个一维(784)的张量做为一个多层感知器(MLP)的Input
from keras.layers import Input
mnist_input = Input ( shape = ( 784 ,))
连接不同的网络层
模型中的神经层是成对连接的,就像是一个乐高积木一样有一面是凸一面是凹, 一个神经层的输出会接到另一个神经层的输入。
这是通过在定义每个新神经层时指定输入的来源来完成的。使用括号表示法,以便在创建图层之后,指定作为输入的神经层。
我们用一个简短的例子来说明这一点。我们可以像上面那样创建输入层,然后创建一个隐藏层作为密集层,它接收来自输入层的输入。
from keras.layers import Input
from keras.layers import Dense
mnist_input = Input ( shape = ( 784 ,))
hidden = Dense ( 512 )( mnist_input )
正是这种逐层连接的方式赋予功能性(functional)API灵活性。您可以看到开始一些动态的神经网络是多么容易。
创建模型
在创建所有模型图层并将它们连接在一起之后,您必须定义一个模型(Model)物件的instance。
与Sequential API一样,这个模型是您可以用于总结(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定义自己的模型时为您提供模板。
标准网络模型
在开始使用函数式API时,最好先看一些标准的神经网络模型是如何定义的。
在本节中,我们将着眼于定义一个简单的多层感知器(MLP),卷积神经网络(CNN)和递归神经网络(RNN)。
这些范例将为以后了解更复杂的网络构建提供基础。
多层感知器(Multilayer Perceptron)
让我们来定义了一个多类别分类(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' )
Layer (type) Output Shape Param #
================================================== ===============
input (InputLayer) (None, 784) 0
hidden1 (Dense) (None, 512) 401920
hidden2 (Dense) (None, 216) 110808
hidden3 (Dense) (None, 128) 27776
output (Dense) (None, 10) 1290
================================================== ===============
Total params: 541,794
Trainable params: 541,794
Non-trainable params: 0
卷积神经网络(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' )
Layer (type) Output Shape Param #
================================================== ===============
input (InputLayer) (None, 28, 28, 1) 0
conv1 (Conv2D) (None, 25, 25, 128) 2176
pool1 (MaxPooling2D) (None, 12, 12, 128) 0
conv2 (Conv2D) (None, 9, 9, 64) 131136
pool2 (MaxPooling2D) (None, 4, 4, 64) 0
hidden1 (Dense) (None, 4, 4, 64) 4160
output (Dense) (None, 4, 4, 10) 650
================================================== ===============
Total params: 138,122
Trainable params: 138,122
Non-trainable params: 0
递归神经网络(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' )
Layer (type) Output Shape Param #
================================================== ===============
input (InputLayer) (None, 784, 1) 0
lstm1 (LSTM) (None, 128) 66560
hidden1 (Dense) (None, 128) 16512
output (Dense) (None, 10) 1290
================================================== ===============
Total params: 84,362
Trainable params: 84,362
Non-trainable params: 0
共享层模型
多个神经层可以共享一个神经层的输出来当成输入。
例如,一个输入可能可以有多个不同的特征提取层,或者多个神经层用于解释特征提取层的输出。
我们来看这两个例子。
共享输入层(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' )
Layer (type) Output Shape Param # Connected to
================================================== ================================================
input (InputLayer) (None, 28, 28, 1) 0
conv1 (Conv2D) (None, 25, 25, 32) 544 input[0][0]
conv2 (Conv2D) (None, 21, 21, 16) 1040 input[0][0]
pool1 (MaxPooling2D) (None, 12, 12, 32) 0 conv1[0][0]
pool2 (MaxPooling2D) (None, 10, 10, 16) 0 conv2[0][0]
flatten_9 (Flatten) (None, 4608) 0 pool1[0][0]
flatten_10 (Flatten) (None, 1600) 0 pool2[0][0]
concatenate_5 (Concatenate) (None, 6208) 0 flatten_9[0][0]
flatten_10[0][0]
hidden1 (Dense) (None, 64) 397376 concatenate_5[0][0]
output (Dense) (None, 10) 650 hidden1[0][0]
================================================== ================================================
Total params: 399,610
Trainable params: 399,610
Non-trainable params: 0
共享特征提取层(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' )
Layer (type) Output Shape Param # Connected to
================================================== ================================================
input (InputLayer) (None, 784, 1) 0
lstm1 (LSTM) (None, 128) 66560 input[0][0]
interp21 (Dense) (None, 64) 8256 lstm1[0][0]
interp22 (Dense) (None, 32) 2080 interp21[0][0]
interp1 (Dense) (None, 10) 1290 lstm1[0][0]
interp23 (Dense) (None, 16) 528 interp22[0][0]
merge (Concatenate) (None, 26) 0 interp1[0][0]
interp23[0][0]
output (Dense) (None, 10) 270 merge[0][0]
================================================== ================================================
Total params: 78,984
Trainable params: 78,984
Non-trainable params: 0
多种输入和输出模型
函数式(functional)API也可用于开发具有多个输入或多个输出的模型的更复杂的模型。
多输入模型
我们将开发一个图像分类模型,将图像的两个版本作为输入,每个图像的大小不同。特别是一个灰阶的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' )
Layer (type) Output Shape Param # Connected to
================================================== ================================================
img_gray_bigsize (InputLayer) (None, 64, 64, 1) 0
img_rgb_smallsize (InputLayer) (None, 32, 32, 3) 0
conv11 (Conv2D) (None, 61, 61, 32) 544 img_gray_bigsize[0][0]
conv21 (Conv2D) (None, 29, 29, 32) 1568 img_rgb_smallsize[0][0]
pool11 (MaxPooling2D) (None, 30, 30, 32) 0 conv11[0][0]
pool21 (MaxPooling2D) (None, 14, 14, 32) 0 conv21[0][0]
conv12 (Conv2D) (None, 27, 27, 16) 8208 pool11[0][0]
conv22 (Conv2D) (None, 11, 11, 16) 8208 pool21[0][0]
pool12 (MaxPooling2D) (None, 13, 13, 16) 0 conv12[0][0]
pool22 (MaxPooling2D) (None, 5, 5, 16) 0 conv22[0][0]
flatten_11 (Flatten) (None, 2704) 0 pool12[0][0]
flatten_12 (Flatten) (None, 400) 0 pool22[0][0]
concatenate_6 (Concatenate) (None, 3104) 0 flatten_11[0][0]
flatten_12[0][0]
hidden1 (Dense) (None, 128) 397440 concatenate_6[0][0]
hidden2 (Dense) (None, 64) 8256 hidden1[0][0]
output (Dense) (None, 10) 650 hidden2[0][0]
================================================== ================================================
Total params: 424,874
Trainable params: 424,874
Non-trainable params: 0
多输出模型
我们将开发一个模型,进行两种不同类型的预测。给定一个特征的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' )
#秀出网络拓扑图
Image ( 'multiple_outputs.png' )
Layer (type) Output Shape Param # Connected to
================================================== ================================================
input (InputLayer) (None, 784, 1) 0
extract (LSTM) (None, 784, 64) 16896 input[0][0]
class11 (LSTM) (None, 32) 12416 extract[0][0]
class12 (Dense) (None, 32) 1056 class11[0][0]
output1 (Dense) (None, 10) 330 class12[0][0]
output2 (TimeDistributed) (None, 784, 10) 650 extract[0][0]
================================================== ================================================
Total params: 31,348
Trainable params: 31,348
Non-trainable params: 0
最佳实践
以上有一些小技巧可以帮助你充分利用函数式API定义自己的模型。
一致性的变量名称命名对输入(可见)和输出神经层(输出)使用相同的变量名,甚至可以使用隐藏层(hidden1,hidden2)。这将有助于正确地将许多的神经层连接在一起。
检查图层摘要始终打印模型摘要并查看图层输出,以确保模型如您所期望的那样连接在一起。
查看网络拓朴图像总是尽可能地创建网络拓朴图像,并审查它,以确保一切按照你的意图连接在一起。
命名图层您可以为图层指定名称,这些名称可以让你的模型图形摘要和网络拓朴图像更容易被解读。例如:Dense(1,name =’hidden1’)。
独立子模型考虑分离出子模型的发展,并最终将子模型结合在一起。
总结(Conclusion)
在这篇文章中有一些个人学习到的一些有趣的重点:
使用Keras也可以很灵活地来建构复杂的深度学习网络
每一种深度学习网络拓朴基本上都可以找的到一篇论文
了解每种深度学习网络拓朴架构的原理与应用的方向是强化内力的不二法门