本文探索了参数标准化(Weight Normalization)这一技术在GAN中的应用。BN在mini-batch的层级上计算均值和方差,容易引入噪声,并不适用于GAN这种生成模型,而WN对参数进行重写,引入噪声更少。
我觉得本文的亮点有二:
1. 提出T-ReLU并配合Affine Tranformation使在引入WN后网络的表达能力维持不变
朴素的参数标准化层有如下的形式:
$$y=\frac{{w}^{T}x}{\|w\|}$$
文中称这样形式的层为“strict weight-normalized layer”。若将线性层换为这样的层,网络的表达能力会下降,因而需要添加如下的affine transformation:
用于恢复网络的表达能力。
将上述变换带入ReLU,简化后可以得到如下T-ReLu:
$$TReLU_\alpha (x) = ReLU(x-\alpha) + \alpha$$
文章的一个重要结论是,在网络的最后一层加入affine transformation层之后,堆叠的“线性层+ReLU”与“strict weight-normalized layer + T-ReLU”表达能力相同(在附录中给出证明)。
下面L表示线性层,R表示ReLU,TR表示TReLU,A表示affine transformation,S表示上述的strict weight-normalized layer。
证明的大致思路是,在ReLU与线性层之间加入affine transformation层,由于线性层的存在,affine transformation带来的效果会被吸收(相当于多个线性层叠在一起还是线性层),网络表达能力不变。而”L+R+A”的结构可以等价于”S+TR+A”。如此递归下去,即可得到结论。个人认为相当于把线性层中的bias转嫁成了TReLU中的threshold(即$\alpha$)。
2. 提出对生成图形的评估指标
生成式模型的生成效果常常难以评价。DcGAN给出的结果也是生成图片的对比。本文中提出一个评价生成效果的指标,且与人的主观评价一致。
评价的具体指标是生成图片与测试集图片的欧氏距离,评价的对象是生成器是Generator。有如下形式:
$$\frac{1}{m} \sum_{i=1}^{m} min_z {\|G(z)-x^{(i)}\|}^2$$其中的$min$指使用梯度下降方法等使生成图片的效果最好。但事实上这样做开销很高。
PyTorch实现
作者将他们的实现代码公布在了GitHub上。
下面是利用PyTorch对T-ReLU的实现:
class TPReLU(Module):
def __init__(self, num_parameters=1, init=0.25):
self.num_parameters = num_parameters
super(TPReLU, self).__init__()
self.weight = Parameter(torch.Tensor(num_parameters).fill_(init))
self.bias = Parameter(torch.zeros(num_parameters))
def forward(self, input):
bias_resize = self.bias.view(1, self.num_parameters, *((1,) * (input.dim() - 2))).expand_as(input)
return F.prelu(input - bias_resize, self.weight.clamp(0, 1)) + bias_resize
对 Weigh-normalized layer 的实现:
class WeightNormalizedLinear(Module):
def __init__(self, in_features, out_features, scale=True, bias=True, init_factor=1, init_scale=1):
super(WeightNormalizedLinear, self).__init__()
self.in_features = in_features
self.out_features = out_features
self.weight = Parameter(torch.Tensor(out_features, in_features))
if bias:
self.bias = Parameter(torch.zeros(1, out_features))
else:
self.register_parameter('bias', None)
if scale:
self.scale = Parameter(torch.Tensor(1, out_features).fill_(init_scale))
else:
self.register_parameter('scale', None)
self.reset_parameters(init_factor)
def reset_parameters(self, factor):
stdv = 1. * factor / math.sqrt(self.weight.size(1))
self.weight.data.uniform_(-stdv, stdv)
if self.bias is not None:
self.bias.data.uniform_(-stdv, stdv)
def weight_norm(self):
return self.weight.pow(2).sum(1).add(1e-6).sqrt()
def norm_scale_bias(self, input):
output = input.div(self.weight_norm().transpose(0, 1).expand_as(input))
if self.scale is not None:
output = output.mul(self.scale.expand_as(input))
if self.bias is not None:
output = output.add(self.bias.expand_as(input))
return output
def forward(self, input):
return self.norm_scale_bias(F.linear(input, self.weight))
观察上面的forward函数可以发现,TReLU添加bias这一习得参数,而weight-normalized layer中则对传入的weight进行了标准化。