原文地址:https://zhuanlan.zhihu.com/p/53933876
Keras使得创建深度学习模型变得快速而简单。
序贯(sequential)API允许你为大多数问题逐层堆叠创建模型。虽然说对很多的应用来说,这样的一个手法很简单也解决了很多深度学习网络结构的构建,但是它也有限制-它不允许你创建模型有共享层或有多个输入或输出的网络。
Keras中的函数式(functioanal)API是创建网络模型的另一种方式,它提供了更多的灵活性,包括创建更复杂的模型。
在这篇文章中,你将了解如何使用Keras中更灵活的函数式(functional)API来定义深度学习模型。
完成这篇文章的相关范例,你将知道:
- Sequential和Functional API之间的区别。
- 如何使用功能性(functional)API定义简单的多层感知器(MLP),卷积神经网络(CNN)和递归神经网络(RNN)模型。
- 如何用共享层和多个输入输出来定义更复杂模型
ps: Perceptron–感知机 Radial Basis Network(RBN) – 径向基网络
Hopfield Network–霍普菲尔网络 Boltzmann Machine – 玻尔兹曼机
1.Keras序贯模型(Sequential Models)
Keras提供了一个Sequential模型API。
它是创建深度学习模型的一种相对简单的方法,我们通过创建Keras的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对于在大多数情况下非常有用与方便,但也有一些局限性。例如,网络拓补结构可能具有多个不同输入,产生多个输出或重复使用共享图层的复杂模型。
2.Keras函数式(functional)API构建模型
Keras函数式(functional)API为构建网络模型提供了更为灵活的方式。
它允许你定义多个输入或输出模型以及共享图层的模型。除此之外,它允许你定义动态(ad-hoc)的非周期性(acyclic)网络图。
模型是通过创建层的实例(layer instances)并将它们直接相互连接成对来定义的,然后定义一个模型(model)来指定那些层是要作为这个模型的输入和输出。
让我们依次看看Keras功能(functional)API的三个独特特性:
2.1 定义输入
与Sequential模型不同,你必须创建独立的Input层物件的instance并定义输入数据张量的维度形状(tensor shape)。
输入层采用一个张量形状参数(tensor shape),它是一个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。
与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定义自己的模型时为你提供模板。
3.标准网络模型
在开始使用函数式API时,最好先看一些标准的神经网络模型是如何定义的。
在本节中,我们将着眼于定义一个简单的多层感知机(MLP),卷积神经网络(CNN)和递归神经网络(RNN)。
这些范例将为以后了解更复杂的网络构建提供基础。
3.1 多层感知器(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
import matplotlib.pyplot as plt
from IPython.display import Image
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')
_________________________________________________________________
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
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')
_________________________________________________________________
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
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='lstm')(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
_________________________________________________________________
lstm (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
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_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_1 (Flatten) (None, 4608) 0 pool1[0][0]
__________________________________________________________________________________________________
flatten_2 (Flatten) (None, 1600) 0 pool2[0][0]
__________________________________________________________________________________________________
concatenate_1 (Concatenate) (None, 6208) 0 flatten_1[0][0]
flatten_2[0][0]
__________________________________________________________________________________________________
hidden1 (Dense) (None, 64) 397376 concatenate_1[0][0]
__________________________________________________________________________________________________
output (Dense) (None, 10) 650 hidden1[0][0]
==================================================================================================
Total params: 399,610
Trainable params: 399,610
Non-trainable params: 0
4.2 共享特征提取层(Shared Feature Extraction Layer)
我们将使用两个并行子模型来解释用于序列分类的LSTM特征提取器的输出。
该模型的输入是一个特征的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) # <- 看这里
# 第二个解释层(深层3层模型)
interp21 = Dense(64,activation='relu',name='interp21')(extract1) # <- 看这里
interp22 = Dense(32,activation='relu',name='interp22')(interp21)
interp23 = Dense(10,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_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, 10) 330 interp22[0][0]
__________________________________________________________________________________________________
merge (Concatenate) (None, 20) 0 interp1[0][0]
interp23[0][0]
__________________________________________________________________________________________________
output (Dense) (None, 10) 210 merge[0][0]
==================================================================================================
Total params: 78,726
Trainable params: 78,726
Non-trainable params: 0
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 import Conv2D
from keras.layers import MaxPool2D
from keras.layers.merge import concatenate
from keras.utils import plot_model
from IPython.display import Image
import os
os.environ["PATH"] += os.pathsep + 'D:/Program Files (x86)/Graphviz2.38/bin/' # 安装graphviz的路径
# 第一个输入层
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_bigsize')
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_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_bigsize (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_bigsize[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_3 (Flatten) (None, 2704) 0 pool12[0][0]
__________________________________________________________________________________________________
flatten_4 (Flatten) (None, 400) 0 pool22[0][0]
__________________________________________________________________________________________________
concatenate_2 (Concatenate) (None, 3104) 0 flatten_3[0][0]
flatten_4[0][0]
__________________________________________________________________________________________________
hidden1 (Dense) (None, 128) 397440 concatenate_2[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
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_model可视化
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
6.最佳实践
以上有一些小技巧可以帮助你充分利用函数式API定义自己的模型。
一致性的变量名称命名 对输入(可见)和输出神经层(输出)使用相同的变量名,甚至可以使用隐藏层(hidden1,hidden2)。这些有助于正确地将许多的神经层连接在一起。
检查图层摘要 始终打印模型摘要并查看图层输出,以确保模型如你所期望的那样连接在一起。
查看网络拓补图像 总是尽可能地创建网络拓补图像,并审查它,以确保一切按照你的意图连接在一起。
命名图层 你可以为图层指定名称,这些名称可以让你的模型图形摘要和网络拓补图像更容易被解读。例如:Dense(1,name=‘hidden1’)。
独立子模型 考虑分离出子模型的发展,并最终将子模型结合在一起。
总结
在这篇文章中有一些个人学习到的重点:
使用keras也可以很灵活地来构建复杂的深度学习网络
每一种深度学习网络拓补基本上都可以找得到一篇论文
了解每种深度学习网络拓补架构的原理与应用的方向是强化内力的不二法门