4.3 Keras的网络层

Keras中深度学习网络的构建主要依赖于各个网络层,Keras之所以可以提高开发效率,也正是得益于其高度模块化的网络层设计,可以非常方便地创建各种常见的深度学习网络结构。下面我们将结合例子介绍常见的几种网络层结构。

4.3.1 模型可视化

在介绍网络层之前,我们先介绍Keras里面的网络可视化的方法,如下所示,使用plot_model函数可以把当前的网络模型保存成图片,便于我们直观地查看其结构。


from keras.utils import plot_model 
plot_model(model, to_file='model.png')

其中,plot_model具有两个参数可以配置模型的显示:

·show_shapes指定是否显示输出数据的形状,默认为False。

·show_layer_names指定是否显示层名称,默认为True。

第一次运行可能出现如下报错,使用pip安装pydot和graphviz即可:


ImportError: Failed to import pydot. You must install pydot and graphviz for `pydotprint` to work.

我们构建非常简单的一个网络结构(见图4-2),输入层大小为784,全连接层节点数为32,我们使用默认参数输出网络结构到图片keras-demo1.png:


model = Sequential()
model.add(Dense(32, input_shape=(784,)))
model.add(Activation('relu'))
model.compile(optimizer='rmsprop',
              loss='categorical_crossentropy',
              metrics=['accuracy'])
plot_model(model, to_file='keras-demo1.png')

图4-2 全连接网络结构举例(一)

我们修改代码,显示输出数据的形状,输出网络结构到图片keras-demo2.png(见图4-3):


plot_model(model,show_shapes=True, to_file='keras-demo2.png')

图4-3 全连接网络结构举例(二)

4.3.2 常用层

常用层包括Dense层、Activation层、Dropout层、Embedding层、Flatten层、Permute层和Reshape层等。

1.Dense层

Dense层是最常见的网络层,用于构建一个全连接。如图4-4所示,一个典型的全连接结构包括输入、求和、激活、权重矩阵、偏置和输出。训练的过程就是不断获得最优的权重矩阵和偏置bias的过程。

图4-4 典型的全连接结构

了解了全连接的结构后,也不难理解创建Dense层的几个参数了:


keras.layers.core.Dense(units, activation=None, use_bias=True, 
kernel_initializer='glorot_uniform', bias_initializer='zeros', 
kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, 
kernel_constraint=None, bias_constraint=None)

其中比较重要的几个参数含义为:

·units,隐藏层节点数。

·activation,激活函数。激活函数的详细介绍请见激活层相关内容。

·use_bias,是否使用偏置。

2.Activation层

Activation层(激活层)对一个层的输出施加激活函数,常见的激活函数包括以下几种:

·relu:relu函数当输入小于0时为0;当输入大于0时等于输入。使用代码绘制relu的图像(图4-5),获得图像,代码如下:


def relu(x):
    if x > 0:
        return x
    else:
        return 0
def func4():
    x = np.arange(-5.0, 5.0, 0.02)
    y=[]
    for i in x:
        yi=relu(i)
        y.append(yi)
    plt.xlabel('x')
    plt.ylabel('y relu(x)')
    plt.title('relu')
    plt.plot(x, y)
    plt.show()

·leakyrelu:leakyrelu函数是从relu发展而来的,当输入小于0时为输入乘以一个很小的系数,比如0.1;当输入大于0时等于输入。使用代码绘制leakyrelu的图像(见图4-6),获得图像,代码如下:


def leakyrelu(x):
    if x > 0:
        return x
    else:
        return x*0.1
def func5():
    x = np.arange(-5.0, 5.0, 0.02)
    y=[]
    for i in x:
        yi=leakyrelu(i)
        y.append(yi)
    plt.xlabel('x')
    plt.ylabel('y leakyrelu(x)')
    plt.title('leakyrelu')
    plt.plot(x, y)
    plt.show()

图4-5 relu函数

图4-6 leakyrelu图像

·tanh:tanh也称为双切正切函数,取值范围为[-1,1]。tanh在特征相差明显时的效果会很好,在循环过程中会不断扩大特征效果。tanh的定义如下:

使用代码绘制tanh的图像(图4-7),获得图像,代码如下:


x = np.arange(-5.0, 5.0, 0.02)
y=(np.exp(x)-np.exp(-x))/(np.exp(x)+np.exp(-x))
plt.xlabel('x')
plt.ylabel('y tanh(x)')
plt.title('tanh')
plt.plot(x, y)
plt.show()

图4-7 tanh图像

·sigmoid:sigmoid可以将一个实数映射到(0,1)区间,可以用来做二分类。sigmoid的定义如下:

使用代码绘制sigmoid的图像(见图4-8),获得图像,代码如下:


x = np.arange(-5.0, 5.0, 0.02)
y=1/(1+np.exp(-x))
plt.xlabel('x')
plt.ylabel('y sigmoid(x)')
plt.title('sigmoid')
plt.plot(x, y)
plt.show()

图4-8 sigmoid函数

激活层可以单独使用,也可以作为其他层的参数,比如创建一个输入为784,结点数为32,激活函数为relu的全连接层的代码为:


model.add(Dense(32, input_shape=(784,)))
model.add(Activation('relu'))

等价于下列代码:


model.add(Dense(32, activation='relu',input_shape=(784,)))

3.Dropout层

在深度学习中,动辄几万的参数需要训练,因此非常容易造成过拟合。通常为了避免过拟合,会在每次训练的时候随机选择一定的节点临时失效。形象的比喻是,好比每次识别图像的时候,随机地挡住一些像素,适当比例地遮挡像素不会影响图像的识别,但是却可以比较有效地抑制过拟合。


keras.layers.core.Dropout(rate, noise_shape=None, seed=None)

其中,常用的参数就是rate,表示临时时效的结点的比例,经验值为0.2~0.4比较合适。

4.Embedding

Embedding层负责将输入的向量按照一定的规则改变维度,有点类似于Word2Vec的处理方式,把词可以映射到一个指定维度的向量中,其函数定义如下:


keras.layers.Embedding(input_dim, output_dim, embeddings_initializer='uniform', 
embeddings_regularizer=None, 
activity_regularizer=None, embeddings_constraint=None, mask_zero=False, input_length=None)

其中,比较重要的参数为:

·input_dim,输入的向量的维度。

·output_dim,输出的向量的维度。

·embeddings_initializer,初始化的方式,通常使用glorot_normal或者uniform。

5.Flatten层

Flatten层用来将输入压平,即把多维的输入一维化。

6.Permute层

Permute层将输入的维度按照给定模式进行重排。一个典型场景就是,在Keras处理图像数据时,需要根据底层是Tensorflow还是Theano调整像素的顺序。在Tensorflow中图像保存的顺序是(width,height,channels)而Theano是(channels,width,height),比如MNIST图像,在Tensorflow中的大小是(28,28,1),而Theano中是(1,28,28),代码如下:


if K.image_dim_ordering() == 'tf':
    # (width, height, channels)
    model.add(Permute((2, 3, 1), input_shape=input_shape))
elif K.image_dim_ordering() == 'th':
    # (channels, width, height)
    model.add(Permute((1, 2, 3), input_shape=input_shape))
else:
    raise RuntimeError('Unknown image_dim_ordering.')

7.Reshape层

Reshape层用于将输入shape转换为特定的shape。函数定义如下,其中target_shape为希望转换成的形状:


keras.layers.core.Reshape(target_shape)

4.3.3 损失函数

常见的损失函数如下,其中二分类问题经常使用的是binary_crossentropy,回归问题使用mse和mae。

·mean_squared_error或mse

·mean_absolute_error或mae

·mean_absolute_percentage_error或mape

·mean_squared_logarithmic_error或msle

·squared_hinge

·hinge

·categorical_hinge

·binary_crossentropy

·logcosh

·categorical_crossentropy

·sparse_categorical_crossentrop

4.3.4 优化器

常用的优化器包括SGD、RMSprop和Adam。

1.SGD

SGD即随机梯度下降法,支持动量参数,支持学习衰减率。函数的定义如下:


keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False)

其中,比较重要的参数为:

·lr,学习率。

·momentum,动量参数。

·decay,每次更新后的学习率衰减值。

2.RMSprop

RMSprop是面对递归神经网络时的一个良好选择,函数的定义如下:


keras.optimizers.RMSprop(lr=0.001, rho=0.9, epsilon=1e-06)

其中,比较重要的参数为:

·lr,学习率。

·epsilon,大于或等于0的小浮点数,防止除0错误。

3.Adam

函数的定义如下:


keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08)

其中,比较重要的参数为:

·lr,学习率。

·epsilon,大于或等于0的小浮点数,防止除0错误

4.3.5 模型的保存与加载

深度学习中通常模型的训练需要花费很长的时间,所以模型的训练和使用往往是分开的,需要支持对模型的参数保存和加载,Keras提供了相关的接口函数:


model.save_weights(filepath)
model.load_weights(filepath, by_name=False)

4.3.6 基于全连接识别MNIST

介绍了这么多关于全连接的基础知识,现在我们介绍如何使用这些网络层识别MNIST。

MNIST是一个入门级的计算机视觉数据集,它包含各种手写数字图片,如图4-9所示。

图4-9 MNIST图片示例

它也包含每一张图片对应的标签,告诉我们这个是数字几。比如图4-9这4张图片的标签分别是5,0,4,1。数据集包括6万个的训练数据集和1万个的测试数据集。每一个MNIST数据单元由两部分组成:一张包含手写数字的图片和一个对应的标签。每一张图片包含28×28个像素点,可以把这个数组展开成一个一维向量,长度是28×28=784。

Keras内置了MNIST,直接加载即可:


(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train = x_train.reshape(60000, 784)
x_test = x_test.reshape(10000, 784)

为了便于神经网络处理,将标签进行one-hot编码处理:


y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)

构建基于全连接的神经网络,即多层感知机(Malti-layer Perceptron,MLP)。该MLP结构如图4-10所示,一共两个隐藏层,结点数均为512,输入层大小为784,输出层为num_classes,即0~9一共10种标签,代码如下:


model = Sequential()
model.add(Dense(512, activation='relu', input_shape=(784,)))
model.add(Dropout(0.2))
model.add(Dense(512, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(num_classes, activation='softmax'))
model.summary()
plot_model(model, show_shapes=True, to_file='keras-mlp.png')

经过20轮训练后,准确度为98.05%:


Epoch 20/20
60000/60000 [==============================] - 11s - loss: 0.0182 - acc: 0.9950 - val_loss: 0.1293 - val_acc: 0.9805
('Test loss:', 0.12934140325475263)
('Test accuracy:', 0.98050000000000004)

图4-10 识别MNIST的MLP结构

4.3.7 卷积层和池化层

典型的CNN(卷积神经网络)包含卷积层、全连接层等组件,并采用softmax多类别分类器和多类交叉熵损失函数,一个典型的卷积神经网络如图4-11所示,我们先介绍用来构造CNN的常见组件。

·卷积层。执行卷积操作提取底层到高层的特征,发掘出图片局部关联性质和空间不变性质。

·池化层。通过取卷积输出特征图中局部区块的最大值或者均值,执行降采样操作。降采样也是图像处理中常见的一种操作,可以过滤掉一些不重要的高频信息。

·全连接层。输入层到隐藏层的神经元是全部连接的。

·非线性变化层。卷积层、全连接层后面一般都会接非线性变化层,例如Sigmoid、Tanh、ReLu等来增强网络的表达能力,在CNN里最常使用的是ReLu激活函数。

·Dropout。在模型训练阶段随机让一些隐层节点权重不工作,以提高网络的泛化能力,并在一定程度上防止过拟合。

图4-11 典型的CNN结构

创建卷积层的函数定义如下:


keras.layers.convolutional.Conv2D(filters, kernel_size, strides=(1, 1), padding= 'valid', data_format=None, dilation_rate=(1, 1), activation=None, use_bias=True, kernel_initializer='glorot_uniform', bias_initializer='zeros', kernel_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, bias_constraint=None)

其中,比较重要的参数为:

·Filters,卷积核的数目。

·kernel_size,卷积核的宽度和长度。

·strides,卷积的步长。

·padding,补0策略,分为“valid”和“same”。“valid”代表只进行有效的卷积,即对边界数据不处理。“same”代表保留边界处的卷积结果,通常会导致输出shape与输入shape相同。

·activation,激活函数。

创建池化层的函数定义如下:


keras.layers.pooling.MaxPooling1D(pool_size=2, strides=None, padding='valid')

其中,比较重要的参数为pool_size,即池化的大小。如果希望使用池化方式是取平均值,可以使用AveragePooling2D。

4.3.8 基于卷积识别MNIST

构建基于卷积的神经网络,即CNN。该CNN结构如图4-12所示,一共两个卷积层,一个卷积大小为(3,3),结点数为32;一个卷积大小为(3,3),结点数为64。然后使用大小为(2,2)池化,池化方法是取最大值,最后使用一个结点数为128的全连接接入输出层,输入层大小为784,输出层为num_classes,即0~9一共10种标签,代码如下:


model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
model.compile(loss=keras.losses.categorical_crossentropy,
              optimizer=keras.optimizers.Adadelta(),
metrics=['accuracy'])

经过12轮训练后,准确度为99.04%。相对MLP,CNN的计算过程非常漫长:


Epoch 12/12
60000/60000 [==============================] - 195s - loss: 0.0380 - acc: 0.9888 - val_loss: 0.0285 - val_acc: 0.9904
('Test loss:', 0.02849770837106189)
('Test accuracy:', 0.99039999999999995)

4.3.9 循环层

循环神经网络(Recurrent Neural Networks,RNN)是深度学习算法中非常有名的一种算法。RNN之所以称为循环神经网络,即一个序列当前的输出与前面的输出也有关。具体的表现形式为网络会对前面的信息进行记忆并应用于当前输出的计算中,即隐藏层之间的节点不再无连接而是有连接的,并且隐藏层的输入不仅包括输入层的输出还包括上一时刻隐藏层的输出。理论上,RNN能够对任何长度的序列数据进行处理。但是在实践中,为了降低复杂性往往假设当前的状态只与前面的几个状态相关 [1]

图4-12 识别MNIST的CNN结构

RNN的独特能力来自于它特殊的结构,如图4-13所示,x代表输入,h代表输出,输出的一部分会作为输入的一部分重新输入,于是RNN具有了一定的记忆性。

图4-13 RNN结构示例(一)

把RNN神经元展开来分析,RNN等效于一连串共享系数的神经元串联在一起(见图4-14),这也就解释了RNN特别适合处理时序数据的原因。

图4-14 RNN结构示例(二)

但是生活的经验告诉我们,比较复杂的情况,光分析时序数据的最近几个数据是难以得到合理的结果的,需要更长的记忆来追根溯源,于是就有了LSTM(Long Short Term Memory,长短程记忆),LSTM可以在更长的时间范围来分析时序数据(见图4-15)。

图4-15 RNN之LSTM示例图

LSTM的关键就是神经细胞状态,水平线在图4-16上方贯穿运行。细胞状态类似于传送带。状态通过水平线在细胞之间传递,从而保证长期保存记忆。

图4-16 RNN之LSTM神经细胞状态传递

LSTM有通过精心设计的称作“门”的结构来去除或者增加信息到细胞状态的能力。门是一种让信息选择式通过的方法。包含一个Sigmoid神经网络层和一个乘法操作。Sigmoid层输出0~1之间的数值,描述每个部分有多少量可以通过。0代表“不许任何量通过”,1代表“允许任意量通过” [2] 。由于LSTM的优异表现,它成为RNN的事实标准,后面的例子如果没有特别声明,RNN的实现都是基于LSTM。

4.3.10 基于LSTM进行IMDB情感分类

互联网电影资料库(Internet Movie Database,IMDB)是一个关于电影演员、电影、电视节目、电视明星和电影制作的在线数据库。IMDB另一受欢迎的特色是其对应每个数据库条目,并且有47个主要板块的留言板系统。注册用户可以在这些留言板上分享和讨论关于电影,演员,导演的消息。至今已有超过600万注册用户使用过留言板。我们使用标注为正面评论和负面评论的留言板数据。整个数据集一共10万条记录,5万做了标记,5万没有做标记。5万做了标记的数据集合被随机分配成了训练数据集和测试数据集。

Keras内置了IMDB,直接加载即可,为了LSTM处理方便,需要把文本转换成序列:


print('Loading data...')
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
print(len(x_train), 'train sequences')
print(len(x_test), 'test sequences')
print('Pad sequences (samples x time)')
x_train = sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = sequence.pad_sequences(x_test, maxlen=maxlen)

构建基于LSTM的神经网络。该LSTM结构如图4-17所示,具有一个LSTM层,结点数为128,代码如下:


model = Sequential()
model.add(Embedding(max_features, 128))
model.add(LSTM(128, dropout=0.2, recurrent_dropout=0.2))
model.add(Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

经过10轮训练后,准确度为81.17%。相对MLP,LSTM的计算过程也非常漫长:


25000/25000 [==============================] - 94s - loss: 0.0288 - acc: 0.9904 - val_ loss: 0.8422 - val_acc: 0.8117
24992/25000 [============================>.] - ETA: 0s('Test score:', 0.842233 0823636055)
('Test accuracy:', 0.81167999999999996)

图4-17 识别IMDB的LSTM结构

[1] http://www.jianshu.com/p/9dc9f41f0b29

[2] http://www.sohu.com/a/110602675_157627