cnn max pooling - 非连续滑动 window (skip gram like)?
cnn max pooling - non consecutive sliding window (skip gram like)?
当使用 keras 构建一个像下面代码这样的简单 cnn 时,当它用于基于文本的问题(例如文档分类)时,我理解这就像我们从文本中提取 4-grams (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 的滑动 window,它遍历文本中的标记序列以发出 4-grams。
我想知道是否有一种方法可以在卷积中创建“非连续滑动 window,即,这会生成 '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)]
我说 'union' 的原因仅仅是因为我认为从实现的角度来看,生成第 1 部分或第 2 部分可能更容易,为修改后的 conv1d 层提供另一个参数。如果是这种情况并且可行,我可以通过连接多个层来解决这个问题。但最低限度实际上是有一个扩展的 conv1d 层,该层将采用额外的参数,以便它执行扫描的第一部分或第二部分。
这个想法并不新鲜,因为本文已经对其进行了实验:http://www.aclweb.org/anthology/D/D16/D16-1085.pdf
但是请原谅我对keras缺乏深入的了解,我不知道如何实现它。请有任何建议,
非常感谢
您可以创建一个自定义卷积层,其中权重矩阵中的某些元素为零。
你可以把常规的Conv1D
图层作为基础class。
但在执行此操作之前,请注意您可以在创建常规卷积层时通过传递 dilation_rate=n
参数来创建 "dilated" 卷积。这将在 window 中的每个克之间跳过 n-1
克。您的 window 将有固定的常规空格。
为此创建自定义图层:
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()
仍然return原始内核,而不是跳过掩码的内核。 (可以解决这个问题,但会有额外的工作,如果需要,请告诉我)
这一层有未使用的权重。这是一个简单的实现。这里的重点是使其尽可能与现有的 Conv 层最相似,并具有其所有功能。也可以只使用严格必要的权重,但这会增加很多复杂性,并且需要大量重写 keras 原始代码以重新创建所有原始可能性。
如果你的kernel_size太长,定义validGrams
变量会很无聊。您可能想要创建一个版本,该版本采用一些跳过的索引,然后将其转换为上面使用的列表类型。
不同频道跳过不同克数:
也可以在层内执行此操作,如果不使用形状为 (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)
当使用 keras 构建一个像下面代码这样的简单 cnn 时,当它用于基于文本的问题(例如文档分类)时,我理解这就像我们从文本中提取 4-grams (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 的滑动 window,它遍历文本中的标记序列以发出 4-grams。
我想知道是否有一种方法可以在卷积中创建“非连续滑动 window,即,这会生成 '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)]
我说 'union' 的原因仅仅是因为我认为从实现的角度来看,生成第 1 部分或第 2 部分可能更容易,为修改后的 conv1d 层提供另一个参数。如果是这种情况并且可行,我可以通过连接多个层来解决这个问题。但最低限度实际上是有一个扩展的 conv1d 层,该层将采用额外的参数,以便它执行扫描的第一部分或第二部分。
这个想法并不新鲜,因为本文已经对其进行了实验:http://www.aclweb.org/anthology/D/D16/D16-1085.pdf
但是请原谅我对keras缺乏深入的了解,我不知道如何实现它。请有任何建议,
非常感谢
您可以创建一个自定义卷积层,其中权重矩阵中的某些元素为零。
你可以把常规的Conv1D
图层作为基础class。
但在执行此操作之前,请注意您可以在创建常规卷积层时通过传递 dilation_rate=n
参数来创建 "dilated" 卷积。这将在 window 中的每个克之间跳过 n-1
克。您的 window 将有固定的常规空格。
为此创建自定义图层:
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()
仍然return原始内核,而不是跳过掩码的内核。 (可以解决这个问题,但会有额外的工作,如果需要,请告诉我)
这一层有未使用的权重。这是一个简单的实现。这里的重点是使其尽可能与现有的 Conv 层最相似,并具有其所有功能。也可以只使用严格必要的权重,但这会增加很多复杂性,并且需要大量重写 keras 原始代码以重新创建所有原始可能性。
如果你的kernel_size太长,定义validGrams
变量会很无聊。您可能想要创建一个版本,该版本采用一些跳过的索引,然后将其转换为上面使用的列表类型。
不同频道跳过不同克数:
也可以在层内执行此操作,如果不使用形状为 (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)