包装层

写在开篇

各位读者大家好,今天我们要一起来学习Keras包装层中的两个工具:TimeDistributed 和Bidirectional。虽然并不算十分常见,但自有其神奇之处。

类型说明

TimeDistributed

TimeDistributed这个层还是比较难理解的。事实上通过这个层,可以实现从二维像三维的过渡,甚至通过这个层的包装,可以实现图像分类视频分类的转化。

1
keras.layers.TimeDistributed(layer)

这个封装器将一个层应用于输入的每个时间片。

输入至少为 3D,且第一个维度应该是时间所表示的维度。

考虑 32 个样本的一个 batch, 其中每个样本是 10 个 16 维向量的序列。那么这个 batch 的输入尺寸为 (32, 10, 16), 而 input_shape 不包含样本数量的维度,为 (10, 16)。使用 TimeDistributed 来将 Dense 层独立地应用到 这 10 个时间步的每一个:

可以这么理解,输入数据是一个特征方程,X1+X2+...+X10=Y,从矩阵的角度看,拿出未知数Y,就是10个向量,每个向量有16个维度,这16个维度是评价Y的16个特征方向。TimeDistributed层的作用就是把Dense层应用到这10个具体的向量上,对每一个向量进行了一个Dense操作,假设是下面这段代码:

1
2
3
4
# 作为模型第一层
model = Sequential()
model.add(TimeDistributed(Dense(8), input_shape=(10, 16)))
# 现在 model.output_shape == (None, 10, 8)

输出的尺寸为 (32, 10, 8)。输出还是10个向量,但是输出的维度由16变成了8。

在后续的层中,将不再需要指定 input_shape

1
2
model.add(TimeDistributed(Dense(32)))
# 现在 model.output_shape == (None, 10, 32)

输出的尺寸为 (32, 10, 32)

TimeDistributed 可以应用于任意层,不仅仅是 Dense, 例如运用于 Conv2D 层:

1
2
3
model = Sequential()
model.add(TimeDistributed(Conv2D(64, (3, 3)),
input_shape=(10, 299, 299, 3)))

Bidirectional

1
keras.layers.Bidirectional(layer, merge_mode='concat', weights=None)

RNN 的双向封装器,对序列进行前向和后向计算。

参数

  • layer: Recurrent 实例。
  • merge_mode: 前向和后向 RNN 的输出的结合模式。为 {'sum', 'mul', 'concat', 'ave', None} 其中之一。如果是 None,输出不会被结合,而是作为一个列表被返回。

异常

  • ValueError: 如果参数 merge_mode 非法。

1
2
3
4
5
6
7
model = Sequential()
model.add(Bidirectional(LSTM(10, return_sequences=True),
input_shape=(5, 10)))
model.add(Bidirectional(LSTM(10)))
model.add(Dense(5))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer='rmsprop')

Bidirectional一个常用的例子是双向LSTM,下面的例程展示了如何在IMDB情绪分类任务中,训练一个Bidirectional LSTM模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from __future__ import print_function
import numpy as np

from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Dense, Dropout, Embedding, LSTM, Bidirectional
from keras.datasets import imdb


max_features = 20000
# cut texts after this number of words
# (among top max_features most common words)
maxlen = 100
batch_size = 32

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)
print('x_train shape:', x_train.shape)
print('x_test shape:', x_test.shape)
y_train = np.array(y_train)
y_test = np.array(y_test)

model = Sequential()
model.add(Embedding(max_features, 128, input_length=maxlen))
model.add(Bidirectional(LSTM(64)))
model.add(Dropout(0.5))
model.add(Dense(1, activation='sigmoid'))

# try using different optimizers and different optimizer configs
model.compile('adam', 'binary_crossentropy', metrics=['accuracy'])

print('Train...')
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=4,
validation_data=[x_test, y_test])

写在文末

相信大家经过今天的学习,能够对包装层的两个工具有一定的认识。文末还为有兴趣实践的读者附上了Bidirectional的使用例,感兴趣的读者不妨动手一试。