k-max-pooling的keras实现

pooling(池化)是卷积神经网络中常用操作,主要目的是减小张量大小,便于计算,可以认为是一步降维操作。前两天项目上需要用到k-max-pooling操作,项目代码是用keras写的,keras没有k-max-pooling层,所以要自己写。

卷积神经网络的最经典的模型是卷积+池化+全连接。
其中卷积操作通过一个滑动的卷积窗口来强化提取特征,可以认为卷积窗是一个权重矩阵,而卷积操作中的每一步,都是一个矩阵的加权求和,随着卷积窗口滑动,每一步的和被记录下来,形成一个新的矩阵。我们有多少种卷积窗,经过一层卷积就留下几个矩阵。然而,如果经过多层卷积,那最后矩阵就越来越大,那我们对资源耗费太大,有没有什么好的操作呢?–pooling

简单理解,pooling就是把一个大矩阵,变成小矩阵。如max-pooling,如size=2,就是在原来的矩阵中,每2*2个块中,取其中最大的一个记录下来,然后下一个块,再下一个十几个,最后记录下来的值就成了一个新矩阵,而这个新矩阵的大小,比以前缩小了4倍。有人会问,那不是损失了信息了么?当然损失了信息,但是我们认为损失的信息并没那么重要,就像一幅超清图,和一张高清图,虽然没那么清晰了,但这张图所描述的信息我们还是一眼能看出来。max-pooling操作,可以这么理解,给你一张图片,你分成了好多块,每块里面,别的地方颜色很浅,就一个地方颜色很深,那你是不是第一眼先看到那个黑点呢?

再说一下mean-pooling.
和max-pooling类似,只不过把每块中取最大值的操作,换成了对每块取均值。这样就会把每个点的值都考虑到,没有max-pooling操作那样粗暴。

还有一种常用的操作是k-max-pooling.这种是在max-pooling上改进来的,因为max-pooling操作太简单粗暴了,k-max-pooling认为每一块不只一个点重要,前几个亮点都比较重要,所以在每一个pooling块中取了前k大的值。

keras中pooling操作好像只有maxpooling和meanpooing.于是自己写了个代码,实现k-max-pooling。
因为项目中用的是LSTM处理文本,我这里就对LSTM层的结果进行处理了,LSTM层输出三维张量(shape=[in_dim1,in_dim2,in_dim3]),其中in_dim1表示样本数量,in_dim2表示一个样本(也就是一段话)的长度(如:这里认为一句话有100个词,那么短于100的句子后面补0,长于100的句子后面截断),in_dim3为lstm结点数量,也就是每个step输出的向量长度。
k-max-pooling操作,就是在dim2中,取出k个最大值,可以想象是取出k个最重要的词。
输入为LSTM层输出的三维张量,输出为一个二维张量,shape=[out_dim1,out_dim2],out_dim1为样本数,out_dim2为in_dim3*k。

代码如下:

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
def test59():

from keras.layers import Lambda, Input
from keras.models import Model
import tensorflow as tf

l = [[1, 2, 3, 4, 5, 6, 7, 8, 9], [11, 22, 33, 44, 55, 66, 77, 88, 99]]
data = np.reshape(l, [2, 3, 3])
print(data)
###output###
#[[[ 1 2 3]
# [ 4 5 6]
# [ 7 8 9]]
# [[11 22 33]
# [44 55 66]
# [77 88 99]]]
#数据为2个样本,每个样本是3个step(可以认为是句子有3个词),每个step是一个长为3的向量(可以认为词向量长度为3)
input = Input(shape=[3, 3], dtype='int32')
la = Lambda(lambda x: tf.reshape(tf.nn.top_k(tf.transpose(x,[0,2,1]),k=2)[0],shape=[-1,6]))(input)
model = Model(inputs=input, outputs=la)
pre = model.predict(data)
print(pre)
#[[ 7 4 8 5 9 6]
#[77 44 88 55 99 66]]

if __name__=='__main__':
test59()

这里的k-max-pooling用Lambda写的,tf.reshape(tf.nn.top_k(tf.transpose(x,[0,2,1]),k=2)[0],shape=[-1,6]),分解成三步:
tf.transpose(x,[0,2,1]–>因为tf有个top_k方法,可以对取张量最后一维的前k大的数,所以我们把step所在维度调整到最后。transpose是一个转置操作。
tf.nn.top_k(,k=2)–>取出最后一个维度前k大的数。
tf.reshape(),top_k操作对每个样本取出的结果是一个二维矩阵in_dim3 × k,所以把它转成向量,向量长度为in_dim3*k

当然,也可以写一个自定义层,处理步骤类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class KMaxPooling(Layer):
"""
k-max-pooling
"""

def __init__(self, k=1, **kwargs):
super().__init__(**kwargs)
self.input_spec = InputSpec(ndim=3)
self.k = k

def compute_output_shape(self, input_shape):
return (input_shape[0], (input_shape[2] * self.k))

def call(self, inputs):
# swap last two dimensions since top_k will be applied along the last dimension
shifted_input = tf.transpose(inputs, [0, 2, 1])

# extract top_k, returns two tensors [values, indices]
top_k = tf.nn.top_k(shifted_input, k=self.k, sorted=True, name=None)[0]

# return flattened output
return Flatten()(top_k)