本博客整理了本人使用Keras的笔记。
Keras 是一个用 Python 编写的高级神经网络 API,能够帮助开发者迅速实现自己的想法。
数据预处理
keras对输入数据的格式要求是多维数组(N维张量),通常是(batch_size, input_dim),输入维度可以是1维/2维/3维(图像数据还有一个通道channel)。因此需要对原始数据进行预处理,使其符合keras的格式要求。
keras提供针对文本(text)、序列(sequence)以及图像(image)三中类型数据的预处理方法。
from keras.preprocessing import text, sequence, image
文本数据处理步骤:
- 文本拆分
- 建立索引,转换为纯数值序列
- 补齐序列(padding), 转换为矩阵
def text_transform(texts, num_words=100, kind='zn', padding='post'):
"""
处理多份文档(长字符串)构成的list,每份文档作为一个独立的元素进行独热编码,并补齐转化成矩阵
:param texts: 多个长字符串构成的list
:param num_words: 所有文档含有的最大唯一单词数
:param kind: 语言种类,默认传入中文
:param padding: 补齐序列
:return: 多维数组
"""
if kind == 'zn':
seq = [lcut(t, cut_all=False, HMM=True) for t in texts] # 拆分
seq_code = [one_hot(str(s), num_words) for s in seq] # 对每个长字符串整数独热编码,并传换成序列
elif kind == 'en':
seq_code = [one_hot(t, num_words) for t in texts] # one_hot 内置text_to_word_sequence,但默认以空格拆分
else:
raise TypeError('The {} is not supported now!'.format(kind))
seq_pad = pad_sequences(seq_code, padding=padding) # 补齐序列并转化成矩阵(数组)
return seq_pad
注意:这里的one-hot与一般意义上标记0/1的one-hot方法不一样,这里是采用hash为每个词建立整数索引。
模型
Keras提供两类模型,序列模型(Sequential)以及通用模型(Models)。
序列模型是通用模型的一个子类,故名思意,仅提供最为常见的线性网络,而通用模型可以灵活地构建任意拓扑结构的神经网络。
建模流程:
示例如下:
from keras.models import Sequential, Model
from keras.layers import Dense, Dropout, Conv2D, MaxPool2D, Flatten, Input, Embedding, Reshape, Dot
from keras.utils import plot_model
# 构建序列模型
model = Sequential() # 构建初始模型
model.add(Conv2D(64, (3, 3), strides=(1, 1), padding='same', activation='relu', input_shape=(28, 28, 1))) # 添加卷积层,仅第一次需要设置输入维度input_shape
model.add(MaxPool2D((2, 2))) # 添加池化层
model.add(Flatten()) # 展开
model.add(Dropout(0.5)) # 添加Dropout, 防止过拟合,提高泛化能力
model.add(Dense(32, activation='relu')) # 添加全连接层
model.add(Dense(10, activation='softmax')) # 添加输出层
model.compile('adam', loss='categorical_crossentropy', metrics=['acc']) # 采用adam算法训练模型,损失函数为交叉熵
model.fit(X_train, y_train, batch_size=5000, epochs=100) # 迭代训练100次
loss, accuracy = model.evaluate(X_test, y_test) # 评估
# 构建通用模型
k = 128
user = Input((1,)) # 构建输入层1
user_dense = Embedding(n_user + 1, k, input_length=1)(user) # 添加嵌入层
user_vec = Reshape((k,))(user_dense)
movie = Input((1,)) # 构建输入层2
movie_dense = Embedding(n_user + 1, k, input_length=1)(movie) # 添加嵌入层
movie_vec = Reshape((k,))(movie_dense)
dots = Dot(axes=1)([user_vec, movie_vec]) # 添加融合层,计算点积
h1 = Dense(128, activation='relu')(dots) # 添加全连接层
h1_drop = Dropout(rate=0.3)(h1) # 添加dropout层,设置需舍弃连接的比例
h2 = Dense(32, activation='relu')(h1_drop)
h2_drop = Dropout(rate=0.3)(h2)
out = Dense(1)(h2_drop) # 构建输出层
model = Model(inputs=[user, movie], outputs=out) # 根据输入层以及输出层构建最终的模型
网络层
深度学习是基于人工神经网络发展起来的,最基本的就是全连接层(Dense),即相邻层之间神经元全部连接。
嵌入层(Embedding)
自然语言处理中,通常将词用密集向量来表示,可采用Word2Vec方法将单词映射到向量空间中。
独热编码的缺点是补齐后存在大量0元素,换句话说,向量是稀疏的。嵌入层的作用就是将稀疏向量压缩成密集向量。详细请见参考资料。
举例如下:
注释:词向量的维度是可调参数,例子中采用2维,实际应用由于单词数较多,通常采用几百维。一般会先采用keras的preprocessing将字符串转化为整数索引序列。
Keras中,若使用嵌入层,则必须作为第一层。
from keras.layers import Embedding
model = Sequential()
model.add(Embedding(input_dim=1000, output_dim=100, input_length=10)) # input_dim指唯一词总数,input_length即整数独热编码并补齐后序列的长度。即将10维的稀疏整数向量嵌入到100维的密集向量中
model.add(Flatten()) # 展开成一维向量
卷积层(convolution)
卷积层专门处理网格化数据,在图像识别领域应用非常成功。卷积层不同于传统的人工神经网络,采用矩阵连接层,而是采用卷积的方式。关于卷积的定义请参考另一篇博客:深度学习中卷积操作简单介绍
卷积层重要参数解释:
keras.layers.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)
keras.layers.SeparableConv2D(filters, kernel_size, strides=(1, 1), padding='valid', data_format=None, dilation_rate=(1, 1), depth_multiplier=1, activation=None, use_bias=True, depthwise_initializer='glorot_uniform', pointwise_initializer='glorot_uniform', bias_initializer='zeros', depthwise_regularizer=None, pointwise_regularizer=None, bias_regularizer=None, activity_regularizer=None, depthwise_constraint=None, pointwise_constraint=None, bias_constraint=None)
- filters: 卷积滤波器数量(卷积核个数)
- kernel_size: 卷积核尺寸
- strides: 下采样步长
- padding: 补齐策略。valid表示不补齐,仅进行有效卷积,对输入空间边界数据不处理;same表示保留边界数据卷积结果,从而使得输出与输入维度一致
- dilation_rate: 膨胀卷积中的膨胀比例
- depth_multiplier:可分离卷积操作中,depthwise操作中每个输入通道对应的输出通道个数
注意:dilation_rate!=1与strides !=1 是不兼容的。
池化层(pooling)
池化函数使用某一位置的相邻输出的总体统计特征来代替网络在该位置的输出。例如最大池化(max pooling)函数给出相邻矩形区
域内的最大值,即抛弃了其他位置的信息。
![](https://i-blog.csdnimg.cn/blog_migrate/81af7bb9da17b4e6575c3e1c211df460.png)
keras.layers.MaxPooling1D(pool_size=2, strides=None, padding='valid', data_format='channels_last')
循环层(recurrent)
循环层专门用于处理序列数据。循环神经网络以区别于卷积网的方式实现参数共享,并且能够综合过去序列的信息和当前输入的信息,给出预测结果。
基础循环神经网络:
![](https://i-blog.csdnimg.cn/blog_migrate/3d8bca8c604de78b7835ecdf278e777f.jpeg)
实际应用中,目前效果最好的就是门控RNN(gated RNN),能够较好的处理长期依赖。包括基于长短期记忆的LSTM和基于门控循环单元的GRU, keras均有提供。
LSTM:
![](https://i-blog.csdnimg.cn/blog_migrate/92b79e8b7d9f7d2252968deab3f91c44.png)
注释:
σ
\sigma
σ即为门,是一个sigmoid网络层,决定信息的流动。
⊗
\otimes
⊗表示pointwise乘法操作,同理
⊕
\oplus
⊕表示pointwise加法操作。
GRU:
![](https://i-blog.csdnimg.cn/blog_migrate/9990b36d2d84b51c48195427a24a644a.png)
注释: r t r_t rt选取原细胞状态信息 h t − 1 h_{t-1} ht−1的一部分与新输入信息 x t x_t xt一起构成细胞状态更新信息 h ~ t \tilde h_t h~t, 1 − z t 1-z_t 1−zt决定保留部分原细胞信息。
另外,keras提供了卷积LSTM,与LSTM类似,只不过将其输入变换以及循环变换操作换成卷积操作。
keras.layers.LSTM(units, activation='tanh', recurrent_activation='hard_sigmoid', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', unit_forget_bias=True, kernel_regularizer=None, recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, recurrent_constraint=None, bias_constraint=None, dropout=0.0, recurrent_dropout=0.0, implementation=1, return_sequences=False, return_state=False, go_backwards=False, stateful=False, unroll=False)
keras.layers.GRU(units, activation='tanh', recurrent_activation='hard_sigmoid', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', kernel_regularizer=None, recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, recurrent_constraint=None, bias_constraint=None, dropout=0.0, recurrent_dropout=0.0, implementation=1, return_sequences=False, return_state=False, go_backwards=False, stateful=False, unroll=False, reset_after=False)
keras.layers.ConvLSTM2D(filters, kernel_size, strides=(1, 1), padding='valid', data_format=None, dilation_rate=(1, 1), activation='tanh', recurrent_activation='hard_sigmoid', use_bias=True, kernel_initializer='glorot_uniform', recurrent_initializer='orthogonal', bias_initializer='zeros', unit_forget_bias=True, kernel_regularizer=None, recurrent_regularizer=None, bias_regularizer=None, activity_regularizer=None, kernel_constraint=None, recurrent_constraint=None, bias_constraint=None, return_sequences=False, go_backwards=False, stateful=False, dropout=0.0, recurrent_dropout=0.0)
- units: 输出维度
- return_sequences: 返回每个时间步对应隐藏层的输出。输出维度n_timestep*n_output,通常两个循环层相连时,设置为True。
- return_state: 返回最后一个时间步对应隐藏层的输出以及细胞状态,一般为False
自定义层(Lambda)
lambda层可将任意表达式包装成一个网络层对象。
model.add(Lambda(lambda x: x ** 2))
def antirectifier(x):
x -= K.mean(x, axis=1, keepdims=True)
x = K.l2_normalize(x, axis=1)
pos = K.relu(x)
neg = K.relu(-x)
return K.concatenate([pos, neg], axis=1)
def antirectifier_output_shape(input_shape):
shape = list(input_shape)
assert len(shape) == 2 # only valid for 2D tensors
shape[-1] *= 2
return tuple(shape)
model.add(Lambda(antirectifier, output_shape=antirectifier_output_shape))
优化算法(optimizer)
深度学习的基础优化算法仍旧是基于梯度下降的误差反向传播算法,后来发展了多种变种算法,Keras提供了七种优化算法:
SGD(随机梯度下降)、RMSprop、Adagrad、Adadelta、Adam、Adamax、Nadam。优化算法理论介绍请参考另一篇博客:机器学习中优化算法总结以及Python实现。
踩过的坑(持续更新中……)
- plot_model报错(pydot` failed to call GraphViz)
- 原因:pydot已经停止开发了,python3.5和python3.6已经用不起来。
- 对策:
- pip uninstall pydot
- pip install pydotplus
- 找到keras里面的utils\vis_utils.py,把里面的pydot的都替换成pydotplus。
参考资料
- 《深度学习》Ian Goodfellow,Yoshua Bengio
- 官方中文指导文档
- 官方英文指导文档
- 词嵌入
- 一文详解 Word2vec 之 Skip-Gram 模型(结构篇)
- LSTM与GRU结构
- Understand the Difference Between Return Sequences and Return States for LSTMs in Keras
注:如有不当之处,请指正。