tensorflow与pytorch卷积填充方式的差异
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