【问题标题】:cnn max pooling - non consecutive sliding window (skip gram like)?cnn max pooling - 非连续滑动窗口(跳过克)?
【发布时间】:2017-11-15 09:07:58
【问题描述】:

当使用 keras 构建类似下面代码的简单 cnn 时,以及将其用于文档分类等基于文本的问题时,我理解这就像我们从文本中提取 4-gram(kernel_size of 4 ) 并将它们用作特征。

    model = Sequential()
    model.add(embedding_layer)
    model.add(Conv1D(filters=100, kernel_size=4, padding='same', activation='relu'))
    model.add(MaxPooling1D(pool_size=4))      
    model.add(Dense(4, activation='softmax'))
    model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

在这种情况下,conv1D 层中的内核大小就像一个大小为 4 的滑动窗口,它遍历文本中的标记序列以发出 4-gram。

我想知道这里是否有一种方法可以让我们在卷积中创建“非连续滑动窗口”,即生成“skip-gram”等价物。例如,给定以下一维向量:

[a, b, c, d, e, f]

kernel_size=3 skip=1 的 conv1d 将扫描以下序列:

[(a,c,d),(b,d,e),(c,e,f),(d,f,padding),(e,padding,padding)] union [(a,b,d),(b,c,e),(c,d,f),(d,e,padding),(e,f,padding),(f,padding,padding)]

我说“联合”的原因仅仅是因为我认为从实现的角度来看,生成第 1 部分或第 2 部分可能更容易,为修改后的 conv1d 层提供另一个参数。如果是这种情况并且可行,我可以通过连接多个层来解决这个问题。但最低限度实际上是拥有一个扩展的 conv1d 层,该层将采用额外的参数,以便它执行扫描的第一部分或第二部分。

这个想法并不新鲜,因为本文已经对此进行了实验:http://www.aclweb.org/anthology/D/D16/D16-1085.pdf

但是请原谅我对 keras 缺乏深入的了解,我不知道如何实现它。有什么好的建议,

在此先感谢

【问题讨论】:

  • 这是定期以固定步骤跳过令牌吗?还是您正在定义要跳过的特定标记?
  • 你的意思是跳过1,阅读3,跳过1,阅读3,跳过1,阅读3....?
  • 是的,它经常跳过,但与您在第二个回复中描述的不太一样。我用一个详细的例子修改了这个问题。所以就像你有一个窗口,有一定大小的间隙来忽略某些元素
  • 根据您的更新(不同的频道跳过不同的克数),我在下面更新了我的答案。

标签: machine-learning deep-learning keras convolution


【解决方案1】:

您可以创建一个自定义卷积层,其中权重矩阵中的某些元素为零。

您可以将常规的Conv1D 层作为基类。

但在此之前,请注意,您可以在创建常规卷积层时通过传递dilation_rate=n 参数来创建“扩张”卷积。这将在窗口中的每个克数之间跳过n-1 克。您的窗口将有固定的常规空间。

为此创建一个自定义层:

import keras.backend as K


#a 1D convolution that skips some entries
class SkipConv1D(Conv1D):

    #in the init, let's just add a parameter to tell which grams to skip
    def __init__(self, validGrams, **kwargs):

        #for this example, I'm assuming validGrams is a list
        #it should contain zeros and ones, where 0's go on the skip positions
        #example: [1,1,0,1] will skip the third gram in the window of 4 grams   
        assert len(validGrams) == kwargs.get('kernel_size')
        self.validGrams = K.reshape(K.constant(validGrams),(len(validGrams),1,1))
            #the chosen shape matches the dimensions of the kernel
            #the first dimension is the kernel size, the others are input and ouptut channels


        #initialize the regular conv layer:
        super(SkipConv1D,self).__init__(**kwargs)

        #here, the filters, size, etc, go inside kwargs, so you should use them named
        #but you may make them explicit in this __init__ definition
        #if you think it's more comfortable to use it like this


    #in the build method, let's replace the original kernel:
    def build(self, input_shape):

        #build as the original layer:
        super(SkipConv1D,self).build(input_shape)

        #replace the kernel   
        self.originalKernel = self.kernel
        self.kernel = self.validGrams * self.originalKernel

请注意此答案中未涉及的一些事项:

get_weights() 方法仍将返回原始内核,而不是带有跳过掩码的内核。 (有可能解决这个问题,但是会有额外的工作,如果需要,请告诉我)

此层中有未使用的权重。这是一个简单的实现。这里的重点是使其与现有的 Conv 层尽可能相似,并具有其所有功能。也可以只使用严格必要的权重,但这会大大增加复杂性,并且需要大量重写 keras 原始代码以重新创建所有原始可能性。

如果你的 kernel_size 太长,定义validGrams var 会很无聊。您可能想要创建一个版本,该版本采用一些跳过的索引,然后将其转换为上面使用的列表类型。

不同的频道跳过不同的克数:

也可以在层内执行此操作,如果不是使用形状为(length,) 的validGrams,而是使用形状为(length,outputFilters) 的一个。

在这种情况下,在我们创建 validGrams 矩阵时,我们应该像这样重塑它:

validGrams = np.asarray(validGrams)
shp = (validGrams.shape[0],1,validGrams.shape[1])
validGrams = validGrams.reshape(shp)
self.validGrams = K.constant(validGrams)

您也可以简单地使用许多具有不同参数的并行SkipConv1D,然后将它们的结果连接起来。

inputs = Input(yourInputShape)
out = embedding_layer(inputs)

out1 = SkipConv1D(filters=50,kernel_size=4,validGrams=[1,0,1,1])(out)
out2 = SkipConv1D(filters=50,kernel_size=4,validGrams=[1,1,0,1])(out)

out = Concatenate()([out1,out2]) #if using 'channels_first' use Concatenate(axis=1)
out = MaxPooling1D(pool_size=4)(out)
out = Dense(4, activation='softmax')(out)

model = Model(inputs,out)

【讨论】:

  • 感谢您提供详细信息。我会尝试扩张的 conv,因为虽然它没有完全实现我想要的,但它是相似的并且可能有用。我还将尝试连接多个skipconv1d层,每个层都查看不同形状的skip gram,这似乎比您提到的其他实现更容易解决 - 我对底层实现不太熟悉,所以这似乎是最安全的选择。我会在接下来的几天内报告
  • 代码中有一个问题:K.reshape,什么是'K'?是“后端”作为“从 keras 导入后端作为 K”吗?
  • 是的,它是import keras.backend as K。
  • 我收到一个错误,它的堆栈跟踪很长,但最终原因是“ValueError:从 1 中减去 4 为 'max_pooling1d_12/MaxPool' (op: 'MaxPool') 导致的负尺寸大小输入形状:[?,1,1,100].',当我构建一个连接 4 个 skipconv1d 层的模型时,每个层的内核大小 = 6,并带有以下掩码:[1,1,1,1,0,1], [1,1,1,0,1,1],[1,1,0,1,1,1],[1,0,1,1,1,1]。紧随其后的是一个大小为 4 的最大池化层。 'model.add(MaxPooling1D(pool_size=4))' 时发生错误。不确定这是否足够详细,但只是想问一下我做错了什么?
  • 我假设您的句子长度等于 6 是否正确?这个错误很明显:数据的长度(卷积后)不足以进行长度为 4 的池化。
猜你喜欢
  • 2017-09-23
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 2013-04-27
  • 1970-01-01
  • 2012-12-30
  • 1970-01-01
  • 2017-09-19
相关资源
最近更新 更多