Keras 快速上手指南(下):网络配置与数据预处理

设定网络中的损失函数、优化器、激活函数

Posted by Xiaosheng on July 6, 2017

在之前的教程中,我们介绍了 Keras 网络的模型与网络层,并且通过许多示例展示了网络的搭建方式。大家都注意到了,在构建网络的过程中,损失函数、优化器、激活函数等都是需要自定义的网络配置项,下面我们对这些网络配置进行详细的介绍。

1. 损失函数

目标函数 objectives

目标函数,或称损失函数,是编译一个模型必须的两个参数之一:

model.compile(loss='mean_squared_error', optimizer='sgd')

可以通过传递预定义目标函数名字指定目标函数,也可以传递一个 Theano/TensroFlow 的符号函数作为目标函数,该函数对每个数据点应该只返回一个标量值,并以下列两个参数为参数:

  • y_true:真实的数据标签,Theano/TensorFlow 张量
  • y_pred:预测值,与 y_true 相同 shape 的 Theano/TensorFlow 张量
from keras import losses

model.compile(loss=losses.mean_squared_error, optimizer='sgd')

真实的优化目标函数是在各个数据点得到的损失函数值之和的均值。

预定义目标函数

  • mean_squared_error 或 mse:均方误差
  • mean_absolute_error 或 mae:平均绝对误差
  • mean_absolute_percentage_error 或 mape:平均绝对百分比误差
  • mean_squared_logarithmic_error 或 msle:均方误差对数
  • squared_hinge
  • hinge
  • binary_crossentropy:对数损失,logloss
  • logcosh
  • categorical_crossentropy:亦称作多类的对数损失,注意使用该目标函数时,需要将标签转化为形如 (nb_samples, nb_classes) 的二值序列
  • sparse_categorical_crossentrop:如上,但接受稀疏标签。注意,使用该函数时仍然需要你的标签与输出值的维度相同,你可能需要在标签数据上增加一个维度:np.expand_dims(y,-1)
  • kullback_leibler_divergence:从预测值概率分布 Q 到真值概率分布 P 的信息增益,用以度量两个分布的差异
  • poisson:即 (predictions - targets * log(predictions)) 的均值
  • cosine_proximity:即预测值与真实标签的余弦距离平均值的相反数

注意: 当使用”categorical_crossentropy”作为目标函数时,标签应该为多类模式,即 one-hot 编码的向量,而不是单个数值.。可以使用工具中的 to_categorical 函数完成该转换,示例如下:

from keras.utils.np_utils import to_categorical

categorical_labels = to_categorical(int_labels, num_classes=None)

2. 优化器

优化器是编译 Keras 模型必要的两个参数之一。

from keras import optimizers

model = Sequential()
model.add(Dense(64, init='uniform', input_shape=(10,)))
model.add(Activation('tanh'))
model.add(Activation('softmax'))

sgd = optimizers.SGD(lr=0.01, decay=1e-6, momentum=0.9, nesterov=True)
model.compile(loss='mean_squared_error', optimizer=sgd)

可以在调用 model.compile() 之前初始化一个优化器对象,然后传入该函数(如上所示),也可以在调用 model.compile() 时传递一个预定义优化器名。在后者情形下,优化器的参数将使用默认值。

# 传递一个预定义优化器名
model.compile(loss='mean_squared_error', optimizer='sgd')

所有优化器都可用的参数

参数 clipnormclipvalue 是所有优化器都可以使用的参数,用于对梯度进行裁剪。示例如下:

from keras import optimizers

# 所有参数的梯度会被剪裁到最大范数为1.
sgd = optimizers.SGD(lr=0.01, clipnorm=1.)
from keras import optimizers

# 所有参数梯度会被剪裁到最大值为0.5,最小值为-0.5
sgd = optimizers.SGD(lr=0.01, clipvalue=0.5)

SGD

随机梯度下降法,支持动量参数,支持学习衰减率,支持 Nesterov 动量。

keras.optimizers.SGD(lr=0.01, momentum=0.0, decay=0.0, nesterov=False)
  • lr:大于 0 的浮点数,学习率
  • momentum:大于 0 的浮点数,动量参数
  • decay:大于 0 的浮点数,每次更新后的学习率衰减值
  • nesterov:布尔值,确定是否使用 Nesterov 动量

RMSprop

除学习率可调整外,建议保持优化器的其他默认参数不变。该优化器通常是面对递归神经网络时的一个良好选择。

keras.optimizers.RMSprop(lr=0.001, rho=0.9, epsilon=1e-06)
  • lr:大于 0 的浮点数,学习率
  • rho:大于 0 的浮点数
  • epsilon:大于 0 的小浮点数,防止除 0 错误

Adam

该优化器的默认值来源于参考文献。

keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-08)
  • lr:大于 0 的浮点数,学习率
  • beta_1:浮点数, 0<beta<1,通常很接近 1
  • beta_2:浮点数, 0<beta<1,通常很接近 1
  • epsilon:大于 0的小浮点数,防止除 0 错误

3. 激活函数

激活函数用于在模型中引入非线性。激活函数 $\text{sigmoid}​$ 与 $\tanh​$ 曾经很流行,但现在很少用于视觉模型了,主要原因在于当输入的绝对值较大时,其导数接近于零,梯度的反向传播过程将被中断,出现梯度消散的现象。

1

$\text{ReLU}$ 是一个很好的替代:

2

相比于 $\text{sigmoid}$ 与 $\tanh$,它有两个优势:

  • 没有饱和问题,大大缓解了梯度消散的现象,加快了收敛速度。
  • 实现起来非常简单,加速了计算过程。

$\text{ReLU}$ 有一个缺陷,就是它可能会永远“死”掉:

假如有一组二维数据 $X$ $(x_1, x_2)$ 分布在 $x_1:[0,1], x_2:[0,1]$ 的区域内,有一组参数 $W$ $(w_1, w_2)$ 对 $X$ 做线性变换,并将结果输入到 $\text{ReLU}$。 如果 $w_1 = w_2 = -1$,那么无论 $X$ 如何取值,$F$ 必然小于等于零。那么 $\text{ReLU}$ 函数对 $F$ 的导数将永远为零。这个 $\text{ReLU}$ 节点将永远不参与整个模型的学习过程。

造成上述现象的原因是 $\text{ReLU}$ 在负区间的导数为零,为了解决这一问题,人们发明了 $\text{Leaky ReLU}$、 $\text{Parametric ReLU}$、 $\text{Randomized ReLU}$ 等变体。他们的中心思想都是为 $\text{ReLU}$ 函数在负区间赋予一定的斜率,从而让其导数不为零(这里设斜率为 $\alpha$)。

$\text{Leaky ReLU}$ 就是直接给 $\alpha$ 指定一个值,整个模型都用这个斜率:

3

$\text{Parametric ReLU}$ 将 $\alpha$ 作为一个参数,通过学习获取它的最优值。$\text{Randomized ReLU}$ 为 $\alpha$ 规定一个区间,然后在区间内随机选取 $\alpha$ 的值。

4

在实践中,$\text{Parametric ReLU}$ 和 $\text{Randomized ReLU}$ 都是可取的。

激活函数可以通过设置单独的激活层实现,也可以在构造层对象时通过传递 activation 参数实现。

from keras.layers import Activation, Dense

model.add(Dense(64))
model.add(Activation('tanh'))

等价于:

model.add(Dense(64, activation='tanh'))

也可以通过传递一个逐元素运算的 Theano/TensorFlow 函数来作为激活函数:

from keras import backend as K

def tanh(x):
    return K.tanh(x)

model.add(Dense(64, activation=tanh))
model.add(Activation(tanh)

预定义激活函数

  • softmax:对输入数据的最后一维进行 softmax,输入数据应形如 (nb_samples, nb_timesteps, nb_dims)(nb_samples, nb_dims)
  • elu
  • softplus
  • softsign
  • relu
  • tanh
  • sigmoid
  • hard_sigmoid
  • linear

高级激活函数

对于简单的 Theano/TensorFlow 不能表达的复杂激活函数,如含有可学习参数的激活函数,可通过高级激活函数实现,如 PReLU,LeakyReLU 等。下面我们详细介绍一下高级激活层。

4. 高级激活层

LeakyReLU 层

LeakyRelU 是修正线性单元(Rectified Linear Unit,ReLU)的特殊版本,当不激活时,LeakyReLU 仍然会有非零输出值,从而获得一个小梯度,避免 ReLU 可能出现的神经元“死亡”现象。

keras.layers.advanced_activations.LeakyReLU(alpha=0.3)
  • alpha:大于 0 的浮点数,代表激活函数图像中第三象限线段的斜率

该层输入 shape 任意,当使用该层为模型首层时需指定 input_shape 参数,输出 shape 与输入相同。

PReLU 层

该层为参数化的 ReLU(Parametric ReLU),表达式是:$f(x)=\begin{cases}\alpha*x&, x\lt0\\x&,x\ge0\end{cases}$,此处的 $\alpha$ 为一个与 xshape 相同的可学习的参数向量。

keras.layers.advanced_activations.PReLU(shared_axes=None)
  • shared_axes:该参数指定的轴将共享同一组科学系参数,例如假如输入特征图是从 2D 卷积过来的,具有形如 (batch, height, width, channels) 这样的 shape,则或许你会希望在空域共享参数,这样每个 filter 就只有一组参数,设定 shared_axes=[1,2] 可完成该目标

ELU 层

ELU 层是指数线性单元(Exponential Linera Unit),表达式为: 该层为参数化的 ReLU(Parametric ReLU),表达式是:$f(x)=\begin{cases}\alpha*(\exp(x)-1)&, x\lt0\\x&,x\ge0\end{cases}$。

keras.layers.advanced_activations.ELU(alpha=1.0)
  • alpha:控制负因子的参数

该层输入 shape 任意,当使用该层为模型首层时需指定 input_shape 参数,输出 shape 与输入相同。

ThresholdedReLU 层

该层是带有门限的 ReLU,表达式是:$f(x)=\begin{cases}x&, x\gt\theta\\0&,\text{otherwise}\end{cases}$。

keras.layers.advanced_activations.ThresholdedReLU(theta=1.0)
  • theata:大或等于 0 的浮点数,激活门限位置

该层输入 shape 任意,当使用该层为模型首层时需指定 input_shape 参数,输出 shape 与输入相同。

5. 数据预处理

填充序列

将长为 nb_samples 的序列(标量序列)转化为形如 (nb_samples,nb_timesteps) 的 2D numpy array。如果提供了参数 maxlen,nb_timesteps=maxlen,否则其值为最长序列的长度。其他短于该长度的序列都会在后部填充 0以达到该长度。长于 nb_timesteps 的序列将会被截断,以使其匹配目标长度。padding 和截断发生的位置分别取决于 paddingtruncating

keras.preprocessing.sequence.pad_sequences(sequences, maxlen=None, dtype='int32',
    padding='pre', truncating='pre', value=0.)
  • sequences:浮点数或整数构成的两层嵌套列表
  • maxlen:None 或整数,为序列的最大长度。大于此长度的序列将被截短,小于此长度的序列将在后部填 0
  • dtype:返回的 numpy array 的数据类型
  • padding:“pre”或“post”,确定当需要补 0 时,在序列的起始还是结尾补
  • truncating:“pre”或“post”,确定当需要截断序列时,从起始还是结尾截断
  • value:浮点数,此值将在填充时代替默认的填充值 0

返回值返回形如 (nb_samples,nb_timesteps) 的 2D 张量。

句子分割

本函数将一个句子拆分成单词构成的列表。

keras.preprocessing.text.text_to_word_sequence(text, filters=base_filter(), lower=True, split=" ")
  • text:字符串,待处理的文本
  • filters:需要滤除的字符的列表或连接形成的字符串,例如标点符号。默认值为base_filter(),包含标点符号,制表符和换行符等
  • lower:布尔值,是否将序列设为小写形式
  • split:字符串,单词的分隔符,如空格

返回值为字符串列表。

one-hot 编码

本函数将一段文本编码为 one-hot 形式的码,即仅记录词在词典中的下标。

从定义上,当字典长为 n 时,每个单词应形成一个长为 n 的向量,其中仅有单词本身在字典中下标的位置为 1,其余均为 0,这称为 one-hot。

为了方便起见,函数在这里仅把“1”的位置,即字典中词的下标记录下来。

keras.preprocessing.text.one_hot(text, n, filters=base_filter(), lower=True, split=" ")
  • text:字符串,待处理的文本
  • n:整数,字典长度
  • filters:需要滤除的字符的列表或连接形成的字符串,例如标点符号。默认值为base_filter(),包含标点符号,制表符和换行符等
  • lower:布尔值,是否将序列设为小写形式
  • split:字符串,单词的分隔符,如空格

返回值为整数列表,每个整数是 $[1,n]$ 之间的值,代表一个单词(不保证唯一性,即如果词典长度不够,不同的单词可能会被编为同一个码)。

分词器

Tokenizer 是一个用于向量化文本,或将文本转换为序列(即单词在字典中的下标构成的列表,从 1 算起)的类。

keras.preprocessing.text.Tokenizer(nb_words=None, filters=base_filter(), lower=True, split=" ")
  • nb_words:None 或整数,处理的最大单词数量。若被设置为整数,则分词器将被限制为处理数据集中最常见的 nb_words-1 个单词
  • filters:需要滤除的字符的列表或连接形成的字符串,例如标点符号。默认值为base_filter(),包含标点符号,制表符和换行符等
  • lower:布尔值,是否将序列设为小写形式
  • split:字符串,单词的分隔符,如空格

类方法:

  • fit_on_texts(texts)
    • texts:要用以训练的文本列表
  • texts_to_sequences(texts)
    • texts:待转为序列的文本列表
    • 返回值:序列的列表,列表中每个序列对应于一段输入文本
  • texts_to_sequences_generator(texts)
    • 本函数是 texts_to_sequences 的生成器函数版
    • texts:待转为序列的文本列表
    • 返回值:每次调用返回对应于一段输入文本的序列
  • texts_to_matrix(texts, mode):
    • texts:待向量化的文本列表
    • mode:“binary”、“count”、“tfidf”、“freq”之一,默认为“binary”
    • 返回值:形如 (len(texts), nb_words) 的 numpy array
  • fit_on_sequences(sequences):
    • sequences:要用以训练的序列列表
  • sequences_to_matrix(sequences):
    • sequences:待向量化的序列列表
    • mode:“binary”、“count”、“tfidf”、“freq”之一,默认为“binary”
    • 返回值:形如 (len(sequences), nb_words) 的 numpy array

属性:

  • word_counts:字典,将单词(字符串)映射为它们在训练期间出现的次数。仅在调用 fit_on_texts 之后设置。
  • word_docs:字典,将单词(字符串)映射为它们在训练期间所出现的文档或文本的数量。仅在调用 fit_on_texts 之后设置。
  • word_index:字典,将单词(字符串)映射为它们的排名或者索引。仅在调用 fit_on_texts 之后设置。
  • document_count:整数。分词器被训练的文档(文本或者序列)数量。仅在调用 fit_on_textsfit_on_sequences 之后设置。

本文整理自《Keras中文文档》