可以这样说,Keras Python库使得创建深度学习模型变得快速且简单。
序列API使得你能够为大多数问题逐层创建模型。当然它也是有局限性的,那就是它并不能让你创建拥有共享层或具有多个输入或输出的模型。
Keras中的的函数式API是创建模型的另一种方式,它具有更多的灵活性,包括创建更为复杂的模型。
在本教程中,你将了解如何用Keras中更具灵活性的函数式API来定义深度学习模型。
完成本教程后,你将明白:
序列API和函数式API之间的区别。
如何使用函数式API定义简单的多层感知器、卷积神经网络以及循环神经网络模型。
如何定义具有共享层和多个输入和输出的更为复杂的模型。
教程概述
本教程涵盖六部分内容,分别是:
1.Keras序列模型
2.Keras函数式模型
3.标准网络模型
4.共享层模型
5.多个输入和输出模型
6.最佳实践
1. Keras序列模型
Keras提供了一个序列模型API。
这是一种创建深度学习模型的方法,其中创建了一个序列类的实例,还创建了模型层并将其添加到其中。
例如,可以将层定义为数组的形式并传递给序列:
from keras.models import Sequential
from keras.layers import Dense
model = Sequential([Dense(2, input_dim=1), Dense(1)])
另外,层也是可以分段添加的:
model = Sequential()
model.add(Dense(2, input_dim=1))
model.add(Dense(1))
可以这样说,在大多数情况下,序列模型API是非常适合用于开发深度学习模型的,但同时也具有一定的局限性。
例如,定义一个可能具有多个不同输入源、且能生成多个输出目标或重用层的模型,并不是一件简单的事情。
Keras函数式API提供了定义模型的一种更为灵活的方式。
尤其是,它使得你能够定义具有多个输入或输出以及共享层的模型。不仅如此,它还使得你能够定义特定的非循环网络图。
模型是通过创建层的实例并将它们直接地成对相互连接来定义的,然后定义一个Model,指定层作为模型的输入和输出。
接下来了解一下Keras函数式API的三个独特方面:
定义输入
与序列模型不同的是,你必须创建并定义一个独立的输入层来指定输入数据的形状。
输入层接受一个形状参数,即一个元组,它表示的是输入数据的维数。。
如果输入数据是一维的,例如多层感知器,那么这个形状必须能够明确地为在训练网络过程中进行分割数据时使用的小批量大小的形状留下空间。因此,这个形状数组总是用最后一个维度(2)定义,例如:
from keras.layers import Input
visible = Input(shape=(2,))
连接层
模型中的层是成对连接的。
这是通过在定义每个新层时指定输入的出处完成的。这里使用括号表示法,以便在创建层之后,就指定了来自当前层输入出处的层。
让我们用一个简短的例子来说明这一点。我们可以如上所述那样创建输入层,然后创建一个隐藏层作为密集层,只接受来自输入层的输入。
hidden = Dense(2)(visible)
注意可见性,在创建密集层之后,将输入层的输出作为输入与密集的隐藏层进行连接。
就是这种方式能够将各个层逐次连接起来,从而使得函数式API具有灵活性。例如,你可以看到开始定义层的临时图表是多么容易。
创建模型
在创建了所有模型层并将它们连接在一起之后,你就必须定义模型了。
与序列API一样,模型是你可以进行总结、拟合、评估和用来进行预测的东西。
Keras提供了一个Model类,你可以使用它从已创建的层中创建一个模型。要求就是你只能指定输入和输出层。例如:
from keras.models import Model
model = Model(inputs=visible, outputs=hidden)
既然我们已经了解Keras 函数式API的所有关键部分,那么接下来我们就来定义一套不同的模型并就其做一些实践。
每个示例都是可执行的,可展示结构并创建图表的简图。这样做的好处是,你可以清楚地知晓你所定义的是什么。
我希望,在将来你想要使用函数式API定义自己的模型时,这些示例能够为你提供模板。
当开始使用函数式API时,最好先去了解一些标准的神经网络模型是如何进行定义的。
在本节中,我们将定义一个简单的多层感知器、卷积神经网络和循环神经网络。
这些例子将为理解接下来更为详细的示例奠定基础。
多层感知器
在本节中,我们定义了一个用于二元分类(binary classification)的多层感知器模型。
该模型有10个输入、3个分别具有10、20和10个神经元的隐藏层、以及一个只有一个输出的输出层。在每个隐层中都使用了纠正线性激活函数(Rectified linear activation functions),而在输出层中使用了一个sigmoid激活函数,以用于二元分类。
# Multilayer Perceptron
from keras.utils import plot_model
visible = Input(shape=(10,))
hidden1 = Dense(10, activation='relu')(visible)
hidden2 = Dense(20, activation='relu')(hidden1)
hidden3 = Dense(10, activation='relu')(hidden2)
output = Dense(1, activation='sigmoid')(hidden3)
model = Model(inputs=visible, outputs=output)
# summarize layers
print(model.summary())
# plot graph
plot_model(model, to_file='multilayer_perceptron_graph.png')
运行该示例,展示出该网络的结构:
该模型图的一个简图也被创建并保存到文件中。
多层感知器网络图
卷积神经网络
在本节中,我们将定义一个用于图像分类的卷积神经网络。
该模型接收一个规格为64×64的黑白图像作为输入,然后有一个卷积层和池化层的序列作为特征提取器,随后是一个用以解释特征的完全连接层,以及一个用于两个类预测的sigmoid激活函数。
# Convolutional Neural Network
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPooling2D
visible = Input(shape=(64,64,1))
conv1 = Conv2D(32, kernel_size=4, activation='relu')(visible)
pool1 = MaxPooling2D(pool_size=(2, 2))(conv1)
conv2 = Conv2D(16, kernel_size=4, activation='relu')(pool1)
pool2 = MaxPooling2D(pool_size=(2, 2))(conv2)
hidden1 = Dense(10, activation='relu')(pool2)
output = Dense(1, activation='sigmoid')(hidden1)
plot_model(model, to_file='convolutional_neural_network.png')
运行该示例,总结模型层:
该模型图的简图也被创建并保存到文件。
卷积神经网络图
循环神经网络
在本节中,我们将定义一个用于序列分类的长短型记忆循环神经网络。
该模型期望以一个特征的100个时间步长作为输入。该模型有一个单独的LSTM隐藏层以从序列中提取特征,然后是一个完全连接层用以解释LSTM输出,接下来是一个用于进行二元预测的输出层。
# Recurrent Neural Network
from keras.layers.recurrent import LSTM
visible = Input(shape=(100,1))
hidden1 = LSTM(10)(visible)
hidden2 = Dense(10, activation='relu')(hidden1)
output = Dense(1, activation='sigmoid')(hidden2)
plot_model(model, to_file='recurrent_neural_network.png')
运行该示例,总结模型层。
循环神经网络图
多个层可以共享来自一个层的输出。
例如,可能有多个不同的特征提取层是来自于同一个输入的,或者有多个层是用于解释来自一个特征提取层的输出的。
我们来看看这两个例子。
共享输入层
在本节中,我们定义了具有不同大小内核的多个卷积层来解释图像输入。
该模型采用大小为64×64像素的黑白图像。有两个CNN特征提取子模型共享该输入; 第一个内核大小为4,第二个内核大小为8。这些特征提取子模型的输出被压缩成向量,连接到一个长向量中,并传递到一个完全连接层,以便在最终输出层进行二二元分类之前进行解释。
# Shared Input Layer
from keras.layers import Flatten
from keras.layers.merge import concatenate
# input layer
# first feature extractor
flat1 = Flatten()(pool1)
# second feature extractor
conv2 = Conv2D(16, kernel_size=8, activation='relu')(visible)
flat2 = Flatten()(pool2)
# merge feature extractors
merge = concatenate([flat1, flat2])
# interpretation layer
hidden1 = Dense(10, activation='relu')(merge)
# prediction output
plot_model(model, to_file='shared_input_layer.png')
具有共享输入的神经网络图
共享特征提取层
在本节中,我们将用两个并行子模型来解释LSTM特性提取器的输出,以进行序列分类。
该模型的输入是一个特征的100个时间步长。一个具有10个记忆单元的LSTM层以解释该序列。第一个解释模型是一个浅的单完全连接层,第二个是一个深度3层模型。两个解释模型的输出都被连接到一个长向量中,传递到输出层用以进行二元预测。
# Shared Feature Extraction Layer
# define input
# feature extraction
extract1 = LSTM(10)(visible)
# first interpretation model
interp1 = Dense(10, activation='relu')(extract1)
# second interpretation model
interp11 = Dense(10, activation='relu')(extract1)
interp12 = Dense(20, activation='relu')(interp11)
interp13 = Dense(10, activation='relu')(interp12)
# merge interpretation
merge = concatenate([interp1, interp13])
# output
output = Dense(1, activation='sigmoid')(merge)
plot_model(model, to_file='shared_feature_extractor.png')
共享特征提取层的神经网络图
函数式API也可用于开发具有多个输入的更复杂的模型,可能具有不同的形式。它也可以用于开发产生多个输出的模型。
我们将在本节中查看每个示例。
多输入模型
我们将开发一个图像分类模型,它将两个版本的图像作为输入,每个版本的大小不同。具体是黑白64×64版本和彩色32×32版本。单独的特征提取CNN模型在每个模型上运行,然后将两个模型的结果连接起来进行解释和最终预测。
请注意,在创建Model()实例时,我们将两个输入层定义为数组。具体如下:
model = Model(inputs=[visible1, visible2], outputs=output)
完整的示例如下所示:
# Multiple Inputs
# first input model
visible1 = Input(shape=(64,64,1))
conv11 = Conv2D(32, kernel_size=4, activation='relu')(visible1)
pool11 = MaxPooling2D(pool_size=(2, 2))(conv11)
conv12 = Conv2D(16, kernel_size=4, activation='relu')(pool11)
pool12 = MaxPooling2D(pool_size=(2, 2))(conv12)
flat1 = Flatten()(pool12)
# second input model
visible2 = Input(shape=(32,32,3))
conv21 = Conv2D(32, kernel_size=4, activation='relu')(visible2)
pool21 = MaxPooling2D(pool_size=(2, 2))(conv21)
conv22 = Conv2D(16, kernel_size=4, activation='relu')(pool21)
pool22 = MaxPooling2D(pool_size=(2, 2))(conv22)
flat2 = Flatten()(pool22)
# merge input models
# interpretation model
plot_model(model, to_file='multiple_inputs.png')
该模型图的简图被创建并保存到文件。
具有多个输入的神经网络图
多输出模型
在本节中,我们将开发出一种可以进行两种不同类型预测的模型。给定一个特征的100时间步长的输入序列,该模型将对序列进行分类并输出具有相同长度的新序列。
LSTM层解释输入序列,并返回每个时间步长的隐藏状态。第一个输出模型创建一个堆栈LSTM,解释特征,并进行二元预测。第二个输出模型使用相同的输出层对每个输入时间步长进行实值预测。
# Multiple Outputs
from keras.layers.wrappers import TimeDistributed
extract = LSTM(10, return_sequences=True)(visible)
# classification output
class11 = LSTM(10)(extract)
class12 = Dense(10, activation='relu')(class11)
output1 = Dense(1, activation='sigmoid')(class12)
# sequence output
output2 = TimeDistributed(Dense(1, activation='linear'))(extract)
model = Model(inputs=visible, outputs=[output1, output2])
plot_model(model, to_file='multiple_outputs.png')
具有多个输出的神经网络图
在本节中,我会给你一些建议,以便在定义自己的模型时充分利用函数式API。
一致的变量名:对输入(可见)、输出层(输出),甚至是隐藏层(hidden1,hidden2)使用相同的变量名称。它将有助于正确地将它们联系起来。
回顾层总结:坚持归纳模型总结并回顾层输出,以确保模型按预期那样连接在一起。
回顾图表简图:坚持创建模型图的简图,并对其进行回顾,以确保所有的东西都按照你的意愿放在一起。
命名层:你可以为在回顾模型图的总结和简图时使用的层分配名称。例如:Dense(1,命名 ='hidden1')。
单独的子模型:考虑分离子模型的开发,并在最后将子模型组合在一起。