如何在Conv2d中指定样本相关的内核/过滤器?

问题描述

我正在尝试实现一个卷积自动编码器,其中一些卷积滤波器与输入内容有关。例如,在一个简单的玩具示例中,了解MNIST的数字标签可以进一步帮助自动编码器设置中的重构。

更笼统的想法是,可能存在一些有用的相关辅助信息(无论该信息是类标签还是其他信息)。尽管有多种方法可以使用此标签/辅助信息,但我将通过创建一个单独的卷积滤波器来实现。假设该模型有15个典型的卷积滤波器,我想添加一个与MNIST数字相对应的附加卷积滤波器,可以将其视为3x3内核形式的数字嵌入。我们将数字用作网络的附加输入,然后为每个数字学习不同的内核/过滤器嵌入。

但是,我很难实现依赖于输入的卷积滤波器/内核。我没有使用tf.keras.layers.Conv2D层,因为它使用了要使用的#个过滤器,但没有使该输入依赖的实际过滤器参数。

# load and preprocess data
num_classes = 10    
(x_train,y_train),(x_test,y_test) = keras.datasets.mnist.load_data()
x_train,x_test = np.float32(x_train)/255,np.float32(x_test)/255
x_train,x_test = np.expand_dims(x_train,axis=-1),np.expand_dims(x_test,axis=-1)
y_train = keras.utils.to_categorical(y_train,num_classes=num_classes)
y_test = keras.utils.to_categorical(y_test,num_classes=num_classes)

num_filters = 15
input_img = layers.Input(shape=(28,28,1))
conv_0 = keras.layers.Conv2D(num_filters,(3,3),strides=2,padding='same',activation='relu')(input_img) 

# embed the target as a 3x3 kernel/filter -> this should map to a distinct embedding for 
# each target 
target = layers.Input(shape=(10,))  
target_encoded = layers.Dense(9,activation='relu')(target) 
target_encoded = layers.Reshape((3,3,1,1))(target_encoded) 

# Using tf.nn.Conv2d so that I can specify kernel
# Kernel needs to be a 4D tensor of dimensions (filter_height,filter_width,input_channels,output_channels) 
# which in this case is (3,1)
# However it is currently (None,1) because the first dimension is batch size so this doesn't work
target_conv = tf.nn.Conv2d(input_img,target_encoded,strides=[1,1],padding='SAME')

我当前正在使用tf.nn.Conv2d,它将内核作为输入格式(filter_height,filter_width,input_channels,output_channels)。但是,这不能按原样工作,因为数据是分批提供的。因此,批次中的每个样品都有一个标签,因此也有一个相应的内核,因此内核的形状(无,3、3、1、1)与预期格式不兼容。上面的代码块对此进行了说明(不起作用)。什么是可能的解决方法?有没有更简单的方法来实现这种依赖于输入的conv2d过滤器的概念?

解决方法

使用SWAPPABLE内核制作Conv2D!

您需要制作自己的Conv2D,将要处理的图像和要使用的内核作为输入。

# Define our new Convolution
class DynamicConv2D(tf.keras.layers.Layer):
    def __init__(self,padding='SAME'):
        super(DynamicConv2D,self).__init__()
        self.padding = padding
    def call(self,input,kernel):
        return tf.nn.conv2d(input=input,filters=kernel,strides=(1,1),padding=self.padding)

让我们对其进行测试

dc2d = DynamicConv2D(padding='VALID')
input_tensor = np.ones([1,4,3],dtype=np.float32)
kernel_tensor = np.ones([2,2,3,1],dtype=np.float32)
dc2d(input_tensor,kernel_tensor)

返回

array([[[[12.],[12.],[12.]],[[12.],[12.]]]])

看起来不错,但是有个大问题

与KERAS有关的问题-默认情况下分批

是的,这就是问题所在:tensorflow keras确实在所有设置的东西上都设置了,因此第一个维度是批处理。但是,如果您在上方查看,则必须为整个批次指定一个内核。我们不能传入一批kernel_tensor S ,而只能传入一个。

周围有事!

让我们从RNN培训计划中借鉴一些东西,具体地说,我们将通过谨慎对待每批发送的内容来解决此问题。更具体地说,对于批处理,我们将确保所有输入图像都使用相同的kernel_tensor。您必须弄清楚如何使用数据管道高效地执行此操作,但这是一个使您前进的示例。

工作代码

(我们将重写动态conv2d,以便它获取一个类别并存储其 每个类别都有自己的内核)

# Define our new Convolution
class DynamicConv2D(tf.keras.layers.Layer):

    def __init__(self,padding='SAME',input_dim=10,kernel_shape=[3,1,8]):
        super(DynamicConv2D,self).__init__()
        self.padding = padding
        self.input_dim = input_dim
        self.kernel_shape = kernel_shape
        self.kernel_size = kernel_shape[0]*kernel_shape[1]*kernel_shape[2]*kernel_shape[3] # = 3*3*1*8
        self.category_to_kernel = tf.keras.layers.Embedding(self.input_dim,self.kernel_size)

    def call(self,categories):
        just_first_category = tf.slice(categories,(0,0),(1,1))
        flat_kernel = self.category_to_kernel(just_first_category)
        kernel = tf.reshape(flat_kernel,self.kernel_shape)
        return tf.nn.conv2d(input=input,padding=self.padding)

默认情况下,此类进行3x3卷积,从上一层读取1个滤镜并输出8

# Example output
dc2d = DynamicConv2D(padding='VALID')
image_data = np.ones([4,10,dtype=np.float32)

# prove that you can send in a different category and get different results
print( dc2d(image_data,[[3]]*4).numpy()[0,:3] )
print( dc2d(image_data,[[4]]*4).numpy()[0,:3] )

--------

[ 0.014 -0.002  0.108]
[ 0.021  0.014 -0.034]

使用它来制作tf.Keras模型

# model input
image_input = tf.keras.Input(shape=(28,28,dtype=tf.float32)
category_input = tf.keras.Input(shape=(1,),dtype=tf.int32)

# do covolution
dynamic_conv2d = DynamicConv2D(padding='VALID')(image_input,category_input)

# make the model
model = tf.keras.Model(inputs=[image_input,category_input],outputs=dynamic_conv2d)

我们可以像这样使用模型

# use the model
input_as_tensor = tf.constant(image_data,dtype=tf.float32)
category_as_tensor = tf.constant([[4]]*4,dtype=tf.int32)
result = model.predict(x=(input_as_tensor,category_as_tensor))

print('The output shape is',result.shape)
print('The first 3 values of the first output image are',result[0,:3])

---------

The output shape is (4,8,8)
The first 3 values of the first output image are [-0.028 -0.009  0.015]