准备工作

import torch
import tensorflow as tf
import torch.nn.functional as F

Conv2D padding=SAME

Tensorflow中可以指定padding方式为SAME,此时padding将自动设置为使得feature_map经过卷积后尺寸为$ \frac{n_{input}}{stride} $ ;

network = slim.conv2d(
    network, 32, [3, 3], stride=1, activation_fn=nonlinearity,
    padding="SAME", normalizer_fn=batch_norm_fn, scope="conv1_2",
    weights_initializer=conv_weight_init, biases_initializer=conv_bias_init,
    weights_regularizer=conv_regularizer)

找了一圈PyTorch社区,没有类似方便的接口可用,没办法只得自己算咯...参考的代码来源:https://zhuanlan.zhihu.com/p/36933397

from torch.nn.functional import pad
from torch.nn.modules.utils import _pair
class _ConvNd(nn.Module):
    __constants__ = ['stride', 'padding', 'dilation', 'groups', 'bias',
                     'padding_mode', 'output_padding', 'in_channels',
                     'out_channels', 'kernel_size']

    def __init__(self, in_channels, out_channels, kernel_size, stride,
                 padding, dilation, transposed, output_padding,
                 groups, bias, padding_mode='zeros'):
        super(_ConvNd, self).__init__()
        if in_channels % groups != 0:
            raise ValueError('in_channels must be divisible by groups')
        if out_channels % groups != 0:
            raise ValueError('out_channels must be divisible by groups')
        self.in_channels = in_channels
        self.out_channels = out_channels
        self.kernel_size = kernel_size
        self.stride = stride
        self.padding = padding
        self.dilation = dilation
        self.transposed = transposed
        self.output_padding = output_padding
        self.groups = groups
        self.padding_mode = padding_mode
        if transposed:
            self.weight = nn.Parameter(torch.Tensor(
                in_channels, out_channels // groups, *kernel_size))
        else:
            self.weight = nn.Parameter(torch.Tensor(
                out_channels, in_channels // groups, *kernel_size))
        if bias:
            self.bias = nn.Parameter(torch.Tensor(out_channels))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
        if self.bias is not None:
            fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight)
            bound = 1 / math.sqrt(fan_in)
            nn.init.uniform_(self.bias, -bound, bound)

    def extra_repr(self):
        s = ('{in_channels}, {out_channels}, kernel_size={kernel_size}'
             ', stride={stride}')
        if self.padding != (0,) * len(self.padding):
            s += ', padding={padding}'
        if self.dilation != (1,) * len(self.dilation):
            s += ', dilation={dilation}'
        if self.output_padding != (0,) * len(self.output_padding):
            s += ', output_padding={output_padding}'
        if self.groups != 1:
            s += ', groups={groups}'
        if self.bias is None:
            s += ', bias=False'
        if self.padding_mode != 'zeros':
            s += ', padding_mode={padding_mode}'
        return s.format(**self.__dict__)

    def __setstate__(self, state):
        super(_ConvNd, self).__setstate__(state)
        if not hasattr(self, 'padding_mode'):
            self.padding_mode = 'zeros'


class Conv2d_with_padding(_ConvNd):

    def __init__(self, in_channels, out_channels, kernel_size, stride=1,
                 padding=0, dilation=1, groups=1, bias=True):
        kernel_size = _pair(kernel_size)
        stride = _pair(stride)
        padding = _pair(padding)
        dilation = _pair(dilation)
        super(Conv2d_with_padding, self).__init__(
            in_channels, out_channels, kernel_size, stride, padding, dilation,
            False, _pair(0), groups, bias)

    @staticmethod
    def conv2d_same_padding(input, weight, bias=None, stride=(1, 1), padding=(1, 1), dilation=(1, 1), groups=1):
        # padding not relevant, just to be compatible with conv2d
        input_rows = input.size(2)
        filter_rows = weight.size(2)
        out_rows = (input_rows + stride[0] - 1) // stride[0]
        padding_rows = max(0, (out_rows - 1) * stride[0] +
                           (filter_rows - 1) * dilation[0] + 1 - input_rows)
        rows_odd = (padding_rows % 2 != 0)
        padding_cols = max(0, (out_rows - 1) * stride[0] +
                           (filter_rows - 1) * dilation[0] + 1 - input_rows)
        cols_odd = (padding_rows % 2 != 0)

        if rows_odd or cols_odd:
            input = pad(input, [0, int(cols_odd), 0, int(rows_odd)])

        return F.conv2d(input, weight, bias, stride,
                        padding=(padding_rows // 2, padding_cols // 2),
                        dilation=dilation, groups=groups)

    def forward(self, input):
        return Conv2d_with_padding.conv2d_same_padding(input, self.weight, self.bias, self.stride,
                                                       self.padding, self.dilation, self.groups)

L2标准化(L2 Normalization)

features = tf.nn.l2_normalize(features, dim=1)

out = F.normalize(out, dim=1, p=2)  # p=2 indicates an L2 Normalization

L2正则化/规范化(L2 Regularization)

Tensorflow Slim中通过weights_regularizer可实现对网络模块的正则化;

conv_regularizer = slim.l2_regularizer(weight_decay)
network = slim.conv2d(
  network, 32, [3, 3], stride=1, activation_fn=nonlinearity,
  padding="SAME", normalizer_fn=batch_norm_fn, scope="conv1_1",
  weights_initializer=conv_weight_init, biases_initializer=conv_bias_init,
  weights_regularizer=conv_regularizer)

PyTorch的网络模块没有提供这样的接口,简单实现L2正则化需要通过优化器的weight_decay参数;

Tips: 在标准SGD上,weight_decay等同于L2正则化;而对自适应优化器(如Adam)而言,weight_decay不等同于L2正则化,详见:https://zhuanlan.zhihu.com/p/63982470, 因此可以考虑用AdamW替代Adam;

Last modification:August 10th, 2020 at 02:58 pm
If you think my article is useful to you, please feel free to appreciate