美文网首页
tensorflow与pytorch卷积填充方式的差异

tensorflow与pytorch卷积填充方式的差异

作者: 欠我的都给我吐出来 | 来源:发表于2019-11-19 14:49 被阅读0次

    keras是基于tensorflow backend的,因此tensorlfow的卷积和keras的卷积操作应该是一致的(实验证明也是一致的),因为工作需要,使用pytorch训练densenet模型,然后转为keras模型。因为基于tensorflow backend的keras模型可以转为tf-serving,方便部署,但是在实际训练的时候,发现pytorch的模型训练速度要比keras的训练快两个小时左右。

    但是在实际的测试中,发现pytroch转为keras之后,两者的文本ocr的模型的结果会有微小的差异。经过排查,一个原因如下是keras代码和pytorch代码在卷积填充中的实现细节不一样。在keras中,如果只填充一列(一行)就足够了,那么默认在最后一行之后和最后一列之后填充。在pytorch中,使用padding=1规定了需要在上下左右填充一行(一列)0值,而使用的时候默认从左到右使用,当只需要使用一行(一列)就足够的情况下,默认使用上面和前面的填充。

    具体的实现细节如下:

    keras的实现

    keras代码中有一个conv2d的操作。代码如下

    x = Conv2D(_nb_filter, (3, 3), strides=(2, 2), kernel_initializer='he_normal', padding='same',use_bias=False, kernel_regularizer=l2(_weight_decay), name='conv1')(input)
    

    在keras中,使用same的填充方式,最终的输出大小为math.ceil(Hin/stride),在填充的时候,优先填充的是右边和下边。

    通过实验验证,下面是一个实验代码:

    import keras
    from keras.layers.convolutional import Conv2D
    
    
    data1=np.array([i for i in range(1,37)]).reshape((6,6))
    data1=data1[np.newaxis,np.newaxis,:]
    x = tf.Variable(data1,dtype=tf.float32)
    y = Conv2D(1, (3, 3), strides=(2, 2), data_format='channels_first',kernel_initializer=keras.initializers.Ones(), padding='same',
                   use_bias=False, name='conv1')(x)
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        _,output = sess.run([x,y])
        print(output)
    

    其中data1是这个样子的

    
    Out[31]:
    array([[[[ 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]]]])
    

    最终的output是这个样子的,最后一行和最后一列是右边和下面填充0之后的结果。

    [[[[ 72.  90.  69.]
       [180. 198. 141.]
       [174. 186. 130.]]]]
    

    pytorch的实现

    在pytorch中densenet,同样的卷积操作的代码是这个样子的:

    self.conv1 = nn.Conv2d(1, in_planes, kernel_size=3, stride=(2,2),padding=1, bias=False)
    

    这里的填充是通过padding指定的,当padding为整数n时,默认在上下左右填充n行n列;当padding为二元数组(m,n)时,默认在height的上下填充m行0值,在width维度上左右填充n列0值。会优先使用上面和左边的填充行列。


    2.png 1.png

    以一个6 * 6的input,stride=(2,2),kernel size=3 * 3的例子来说,使用pytorch的卷积代码如下

    import numpy as np
    import tensorflow as tf
    import torch
    import torch.nn as nn
    import torch.nn.functional as F
    
    data1=np.array([i for i in range(1,37)]).reshape((6,6))
    data1=data1[np.newaxis,np.newaxis,:]
    tensor1 = torch.tensor(data1[:,:,:3,:3],dtype=torch.float32)
    kernel = [[1, 1, 1],[1, 1, 1],[1, 1, 1]]
    kernel = torch.FloatTensor(kernel).expand(1,1,3,3)
    weight = nn.Parameter(data=kernel, requires_grad=False)
    made_conv2 = lambda x: F.conv2d(x,weight,bias=None,stride=2,padding=1)
    print(made_conv2(tensor1))
    

    其中tensor1是一个四维数组

    array([[[[ 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]]]])
    

    而得到的结果是这样的

    tensor([[[[ 18.,  36.,  48.],
              [ 81., 135., 153.],
              [153., 243., 261.]]]])
    

    验证了在pytorch中,这个结果是在tensor1的上下左右都填充了0之后,然后从左到右匹配,如果列数不够就放弃。比如,在这个例子中,实际计算的时候,并没有用到最后一列填充的0值。

    或许会好奇,如果不需要填充的时候正好可以被步长完整遍历的时候,会怎么样呢?比如我们的data是一个33的矩阵,而卷积也是33的,padding=1,stride=2.按照公式可以得到结果为(2,2)。也就是会充分的使用填充的上下左右的0值。此时右边和下面的0值也使用了。这个时候就和使用keras的samepadding一致了。

    解决的方法

    在网上找到一份使用pytorch实现tensorflow中的samepadding的代码,只要把这一层添加在pytorch的conv2d之前即可。

    class SamePad2d(nn.Module):
        """Mimics tensorflow's 'SAME' padding.
        """
    
        def __init__(self, kernel_size, stride):
            super(SamePad2d, self).__init__()
            self.kernel_size = torch.nn.modules.utils._pair(kernel_size)
            self.stride = torch.nn.modules.utils._pair(stride)
    
        def forward(self, input):
            in_width = input.size()[2]
            in_height = input.size()[3]
            out_width = math.ceil(float(in_width) / float(self.stride[0]))
            out_height = math.ceil(float(in_height) / float(self.stride[1]))
            pad_along_width = ((out_width - 1) * self.stride[0] +
                               self.kernel_size[0] - in_width)
            pad_along_height = ((out_height - 1) * self.stride[1] +
                                self.kernel_size[1] - in_height)
            pad_left = math.floor(pad_along_width / 2)
            pad_top = math.floor(pad_along_height / 2)
            pad_right = pad_along_width - pad_left
            pad_bottom = pad_along_height - pad_top
            return F.pad(input, (pad_left, pad_right, pad_top, pad_bottom), 'constant', 0)
    
        def __repr__(self):
            return self.__class__.__name__
    

    原文链接:用pytorch实现tensorflow卷积中的SAME

    结果

    在经过一天短期的训练之后,使用pytorch模型进行训练,然后通过代码将其转换为keras的模型,然后测试了4张图片,发现两个模型的实现效果确实改进了一点。之前每张图都会有些微的区别,如下图所示:


    image.png

    在使用samepadding改进之后,发现只有最后一张图还有微小的差异,后续还会继续寻找自己代码中pytorch和keras模型转换存在的细微差距问题。


    image.png

    相关文章

      网友评论

          本文标题:tensorflow与pytorch卷积填充方式的差异

          本文链接:https://www.haomeiwen.com/subject/cwwwictx.html