Pytorch 與 TensorFlow 二維卷積(Conv2d)填充(padding)上的差異

????????熟悉 TensorFlow 的讀者知道,在調用其卷積 conv2d 的時候,TensorFlow 有兩種填充方式,分別是 padding = 'SAME'padding = 'VALID',其中前者是默認值。如果卷積的步幅(stride)取值為 1,那么 padding = 'SAME' 就是指特征映射的分辨率在卷積前后保持不變,而 padding = 'VALID' 則是要下降 k - 1 個像素(即不填充,k 是卷積核大?。?。比如,對于長度為 5 的特征映射,如果卷積核大小為 3,那么兩種填充方式對應的結果是:

兩種卷積填充方式

padding = 'SAME' 為了保持特征映射分辨率不變,需要在原特征映射四周填充不定大小的 0,然后再計算卷積,而 padding = 'VALID' 則是自然計算,不做任何填充。對于步幅 stride = 1,

slim.conv2d(kernel_size=k, padding='SAME', ...)

torch.nn.Conv2d(kernel_size=k, padding=k // 2, ...)

結果是一致的,如果權重是一樣的話。但如果步幅 stride = 2,則兩者的結果會有差異,比如對于 224x224 分辨率的特征映射,指定 k = 5,雖然兩者的結果都得到 112x112 分辨率的特征映射,但結果卻是不同的。比如,在輸入和權重都一樣的情況下,我們得到結果(運行后面給出的的代碼:compare_conv.py,將第 22 行簡化為:p = k // 2,將第 66/67 行注釋掉):

Shape: (1, 112, 112, 16)
Shape: (1, 112, 112, 16)
y_tf:
[[ 0.15286588 -0.13643302 -0.09014875 0.25550553 0.05999924 -0.01149828
-0.30093179 -0.13394017 -0.16866598 0.17772882 0.08939055 -0.15882357
0.02846589 0.18959665 0.09113002 0.13065471]]
y_pth:
[[ 0.01814898 -0.26733394 0.16750193 0.25537257 0.21831602 0.31476249
0.01923549 -0.0464759 -0.02368551 0.05874638 -0.26061299 -0.33947413
-0.20543707 -0.05527851 0.00162258 0.10928829]]

你也可以嘗試將 torch.nn.Conv2d() 中的 padding 改成其它值,但得到的特征映射要么分辨率不對,要么值不對。

????????這種差異是由 TensorFlow 和 Pytorch 在卷積運算時使用的填充方式不同導致的。Pytorch 在填充的時候,上、下、左、右各方向填充的大小是一樣的,但 TensorFlow 卻允許不一樣。我們以一個實際例子來說明這個問題。假設輸入的特征映射的分辨率(resolution)為 R\times R,卷積核(kernel size)大小為 k,步幅(stride)為 ss\le 3),空洞率(dilation)為 d,那么輸出的特征映射的大小將變為 R^\prime\times R^\prime,其中
R^\prime = \lfloor \frac{R + p - d(k - 1) - 1}{s} \rfloor + 1. \\
為了算出總填充的大小 p,考慮到目標特征映射的寬、高是:
\lfloor\frac{R - 1}{s}\rfloor + 1, \\
就得到
\lfloor \frac{R + p - d(k - 1) - 1}{s} \rfloor + 1 = \lfloor\frac{R - 1}{s}\rfloor + 1. \\
即:
\lfloor \frac{R + p - d(k - 1) - 1}{s} \rfloor = \lfloor\frac{R - 1}{s}\rfloor. \\
因此,最終的總填充大小為:
p = d(k - 1) -1,\ s=2, 3. \\
但因為 Pytorch 總是上、下、左、右 4 個方向的填充量都一樣大,因此
\mathrm{padding} = \lfloor \frac{p}{2} \rfloor = \lfloor \frac{d(k - 1) - 1}{2}\rfloor, \\
這樣就會出現
p \ne 2\ \mathrm{padding} \\
的情況。比如,當 k=5, s=2, d=1 時,p=k - 2 = 3,而 \mathrm{padding} = 1,就算人為的設成 \mathrm{padding} = 2,也避免不了矛盾。另一方面,我們來看 TensorFlow 的填充方式:padding = 'SAME'。因為 TensorFlow 允許不同方向填充不同的大小,而且遵循上小下大、左小右大的原則,因此對于總填充大小 p 來說,上、下、左、右的填充量分別是:
\lfloor \frac{p }{2} \rfloor, \ p - \lfloor \frac{p}{2} \rfloor, \ \lfloor \frac{p}{2} \rfloor, \ p - \lfloor \frac{p}{2} \rfloor. \\
對于我們舉的特殊例子來說,p = 3,因此填充量分別是 (1, 2, 1, 2),相比于 Pytorch 的 (1, 1, 1, 1) 或者 (2, 2, 2, 2),自然結果就不一致。

????????知道了以上內容,為了消除 Pytorch 與 TensorFlow 填充方面的差別,采取一個簡單而有效的策略:

  • 先對輸入的特征映射按填充量:
    \lfloor \frac{p }{2} \rfloor, \ p - \lfloor \frac{p}{2} \rfloor, \ \lfloor \frac{p}{2} \rfloor, \ p - \lfloor \frac{p}{2} \rfloor. \\
    進行 0 填充;
  • 然后接不做任何填充的卷積:torch.nn.Conv2d(padding=0, ...)

以下為這個策略的驗證代碼(命名為 compare_conv.py):

# -*- coding: utf-8 -*-
"""
Created on Sat Dec 14 16:44:31 2019

@author: shirhe-lyh
"""

import numpy as np
import tensorflow as tf
import torch

tf.enable_eager_execution()

np.random.seed(123)
tf.set_random_seed(123)
torch.manual_seed(123)

h = 224
w = 224
k = 5
s = 2
p = k // 2 if s == 1 else 0


x_np = np.random.random((1, h, w, 3))
x_tf = tf.constant(x_np)
x_pth = torch.from_numpy(x_np.transpose(0, 3, 1, 2))


def pad(x, kernel_size=3, dilation=1):
    """For stride = 2 or stride = 3"""
    pad_total = dilation * (kernel_size - 1) - 1
    pad_beg = pad_total // 2
    pad_end = pad_total - pad_beg
    x_padded = torch.nn.functional.pad(
        x, pad=(pad_beg, pad_end, pad_beg, pad_end))
    return x_padded


conv_tf = tf.layers.Conv2D(filters=16, 
                           padding='SAME',
                           kernel_size=k,
                           strides=(s, s))

# Tensorflow prediction
with tf.GradientTape(persistent=True) as t:
    t.watch(x_tf)
    y_tf = conv_tf(x_tf).numpy()
    print('Shape: ', y_tf.shape)
    
    
conv_pth = torch.nn.Conv2d(in_channels=3,
                           out_channels=16,
                           kernel_size=k,
                           stride=s,
                           padding=p)

# Reset parameters
weights_tf, biases_tf = conv_tf.get_weights()
conv_pth.weight.data = torch.tensor(weights_tf.transpose(3, 2, 0, 1))
conv_pth.bias.data = torch.tensor(biases_tf)


# Pytorch prediction
conv_pth.eval()
with torch.no_grad():
    if s > 1:
        x_pth = pad(x_pth, kernel_size=k)
    y_pth = conv_pth(x_pth)
    y_pth = y_pth.numpy().transpose(0, 2, 3, 1)
    print('Shape: ', y_pth.shape)
    
    
# Compare results
print('y_tf: ')
print(y_tf[:, h//s-1, 0, :])
print('y_pth: ')
print(y_pth[:, h//s-1, 0, :])  

運行該代碼,Pytorch 和 TensorFlow 的輸出結果是一致的:

Shape: (1, 112, 112, 16)
Shape: (1, 112, 112, 16)
y_tf:
[[ 0.15286588 -0.13643302 -0.09014875 0.25550553 0.05999924 -0.01149828
-0.30093179 -0.13394017 -0.16866598 0.17772882 0.08939055 -0.15882357
0.02846589 0.18959665 0.09113002 0.13065471]]
y_pth:
[[ 0.15286588 -0.13643302 -0.09014875 0.25550553 0.05999924 -0.01149828
-0.30093179 -0.13394017 -0.16866598 0.17772882 0.08939055 -0.15882357
0.02846589 0.18959665 0.09113002 0.13065471]]

:因為 slim.conv2d 等二維卷積函數都是調用的底層類 tf.layers.Conv2D,因此拿 tf.layers.Conv2Dtorch.nn.Conv2d 來做對比。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容