谱归一化(SpectralNormalization)的理解

更新时间:2023-05-06 12:27:06 阅读: 评论:0

谱归⼀化(SpectralNormalization )的理解
⼀、Gan 的Lipschitz 稳定性约束
Gan好是好,但训练难,主要体现在:1)模式坍塌,即最后⽣成的对象就只有少数⼏个模式;2)不收敛,在训练过程中,Discriminator 很早就进⼊了理想状态,总能perfectly分辨出真假,因此⽆法给Generator提供梯度信息,⽽导致训练⽆法进⾏下去。Martin Arjovsky在《Towards principled methods for training generative adversarial networks》【4】、《Wasrstein GAN》【5】⽂章中,对Gan难训练的原因做了详细的讨论,并给出⼀种新的Loss定义,即Wasrstein Distance:
实际Wasrstein Distance的计算是通过它的变形来完成的:
(2)式只要求 满⾜Lipschitz约束即可,在Gan中,判别器的映射函数可充当(2)式中的 ,于是加⼊此⼀约束的Gan⽹络有了⼀个新的名称:WGan。
引⼊Wasrstein Distance,将传统Gan转变为WGan是有许多好处的,因为Wasrstein Distance具有如下优点:
1、, 等号在分布完全重合时成⽴;
2、是对称的,较常⽤的 KL Divergence 的不对称,有优势;
3、即使两个分布  的⽀撑不相交,亦可以作为衡量差异的距离,并在满⾜⼀定条件下可微,具备了后向传输的能⼒。
当 WGan 的 Discriminator 采⽤了这种距离来训练后,可以消除传统Gan训练时出现的收敛问题,使训练过程变得稳定。另外,要实施此策略也很简单,只需在传统Gan的Discriminator的参数矩阵上加上Lipschitz约束即可,其它的⼏乎不⽤改。
Lipschitz约束简单⽽⾔就是:要求在整个 的定义域内有
其中,M是⼀个常数。满⾜公式(3)的函数,具体表现为:函数变化不会太快,其梯度总是有限的,即使最剧烈时,也被限制在⼩于等于M的范围。
WGan⾸先提出Discriminator的参数矩阵需要满⾜Lipschitz约束,但其⽅法⽐较简单粗暴:直接对参数矩阵中元素进⾏限制,不让其⼤于某个值。这种⽅法,是可以保证Lipschitz约束的,但在削顶的同时,也破坏了整个参数矩阵的结构——各参数之间的⽐例关系。针对这个问题,【1】提出了⼀个既满⾜Lipschitz条件,⼜不⽤破坏矩阵结构的⽅法——Spectral Normalization。
⼆、多层神经⽹络的分析
W (P ,P )=r g E [∥x −γ∈(P ,P )∏r g inf (x ,y )∼γy ∥](1)
W (P ,P )=r g E [f (x )]−E [f (x )](2)
∥f ∥Lip sup x ∼P r x ∼P g f (⋅)f (⋅)W (P ,P )≥r g 0P ,P r g W (P ,P )r g P ,P r g f (⋅)≤∥x −x ∥′2∥f (x )−f (x )∥′2
M (3)
f (⋅)
为简便分析,可将Discriminator看作是多层⽹络,因为CNNs可看作是特殊的多层⽹络。对于多层⽹络的第n层,其输⼊与输出关系可以表⽰为:
其中, 是该层⽹络的⾮线性激活函数,可采⽤ReLU; 是⽹络参数矩阵, 是⽹络的偏置,为推导⽅便,对  进⾏省略处理,则
(4)式可写为:
其中  是对⾓矩阵,⽤于表⽰ReLU的作⽤,当其对应输⼊为负数时,对⾓元素为0;当其对应输⼊为正数时,对⾓元素为1。于是,多层神经⽹络(假设是N层)输⼊输出关系可以写成:
Lipschitz约束是对  的梯度提出的要求:
此处  表⽰矩阵W的谱范数,它的定义如下:
是矩阵W的最⼤奇异值,对于对⾓矩阵D,有 ,即对⾓元素上最⼤的元素。由此,(7)可表⽰为:
因为,ReLU所对应的对⾓矩阵的谱范数最⼤为1。为使  满⾜Lipschitz约束,可对(7)进⾏归⼀化:
由此可见,只需让每层⽹络的⽹络参数除以该层参数矩阵的谱范数即可满⾜Lipschitz=1的约束,由此诞⽣了谱归⼀化(Spectral Normailization)。
三、谱归⼀化的实现
x =n a (W x +n n n −1b )(4)
n a (⋅)n W l b l b l x =n D W x (5)
n n n −1D n f (x )=D W ⋯D W x (6)
N N 11f (x )∥∇(f (x ))∥=x 2∥D W ⋯D W ∥≤N N 112∥D ∥∥W ∥⋯∥D ∥∥W ∥(7)
N 2N 21212∥W ∥σ(A ):==∥h ∥=0max ∥h ∥2∥Ah ∥2∥A ∥(8)
∥h ∥=1max 2σ(W )σ(D )=max(d ,⋯,d )1n ∥∇(f (x ))∥≤x 2σ(W )(9)
i =1∏N
i f (x )∥∇(f (x ))∥=x 2∥D ⋯D ∥≤N σ(W )N W N 1σ(W )1W 12=i =1∏N σ(W )i σ(W )
i 1(10)
为获得每层参数矩阵的谱范数,需要求解  的奇异值,这将耗费⼤量的计算资源,因⽽可采⽤“幂迭代法”来近似求取,其迭代过程如下:
求得谱范数后,每个参数矩阵上的参数皆除以它,以达到归⼀化⽬的。其实,上述算法在迭代了⾜够次数后,就是该矩阵()的最⼤奇异值对应的特征⽮量,有:
谱归⼀具体的pytorch实现代码可以参考【3】,以下摘抄部分如下:
1、计算谱范数
import torch
functional as F
#define _l2normalization
def _l2normalize(v, eps=1e-12):
return v / ((v) + eps)
def max_singular_value(W, u=None, Ip=1):
"""
power iteration for weight parameter
"""
#xp = W.data
if not Ip >= 1:
rai ValueError("Power iteration should be a positive integer")
if u is None:
u = torch.FloatTensor(1, W.size(0)).normal_(0, 1).cuda()
_u = u
for _ in range(Ip):
_v = _l2normalize(torch.matmul(_u, W.data), eps=1e-12)
_u = _l2normalize(torch.matmul(_v, anspo(W.data, 0, 1)), eps=1e-12)
sigma = torch.sum(F.linear(_u, anspo(W.data, 0, 1)) * _v)
return sigma, _u
2、构造带归⼀化的层
线性层:
W i 1、v ←l 0
a random Gaussian vector
2、loop k :u ←l k W v , normalization: u ←l l k −1l k ,
∥u ∥l k u l k v ←l k (W )u , normalization: v ←l T l k l k ,
∥v ∥l k v l k end loop 3、σ(W )=l (u )Wv l k T l k u k W WW u =T σ(W )⋅u ⇒u WW u =T T 1⋅σ(W ), as ∥u ∥=1
σ(W )=u W v , as v =T W u
T
class SNLinear(Linear):
def __init__(lf, in_features, out_features, bias=True):
super(SNLinear, lf).__init__(in_features, out_features, bias)
@property
def W_(lf):
w_mat = lf.weight.view(lf.weight.size(0), -1)
sigma, _u = max_singular_value(w_mat, lf.u)
py_(_u)
return lf.weight / sigma
def forward(lf, input):
return F.linear(input, lf.W_, lf.bias)
卷积层:
class SNConv2d(conv._ConvNd):
def __init__(lf, 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(SNConv2d, lf).__init__(
in_channels, out_channels, kernel_size, stride, padding, dilation,
Fal, _pair(0), groups, bias)
@property
def W_(lf):
w_mat = lf.weight.view(lf.weight.size(0), -1)
sigma, _u = max_singular_value(w_mat, lf.u)
py_(_u)
return lf.weight / sigma
def forward(lf, input):
v2d(input, lf.W_, lf.bias, lf.stride,
lf.padding, lf.dilation, lf.groups)
由这两个层的构造可看到:谱范数的计算和应⽤谱范数的归⼀化层。这些层可以加到Discriminator中,如下:
class ResBlock(nn.Module):
def __init__(lf, in_channels, out_channels, hidden_channels=None, u_BN = Fal, downsample=Fal):
super(ResBlock, lf).__init__()
#lf.conv1 = SNConv2d(n_dim, n_out, kernel_size=3, stride=2)
hidden_channels = in_channels
lf.downsample = downsample
def make_res_block(lf, in_channels, out_channels, hidden_channels, u_BN, downsample):
model = []
if u_BN:
model += [nn.BatchNorm2d(in_channels)]
model += [nn.ReLU()]
model += [SNConv2d(in_channels, hidden_channels, kernel_size=3, padding=1)]
model += [nn.ReLU()]
model += [SNConv2d(hidden_channels, out_channels, kernel_size=3, padding=1)]
if downsample:
model += [nn.AvgPool2d(2)]
return nn.Sequential(*model)
def make_residual_connect(lf, in_channels, out_channels):
model = []
model += [SNConv2d(in_channels, out_channels, kernel_size=1, padding=0)]
if lf.downsample:
model += [nn.AvgPool2d(2)]
return nn.Sequential(*model)
el:
return nn.Sequential(*model)
def forward(lf, input):
sblock(input) + lf.residual_connect(input)
class OptimizedBlock(nn.Module):
def __init__(lf, in_channels, out_channels):
super(OptimizedBlock, lf).__init__()
def make_res_block(lf, in_channels, out_channels):
model = []
model += [SNConv2d(in_channels, out_channels, kernel_size=3, padding=1)]
model += [nn.ReLU()]
model += [SNConv2d(out_channels, out_channels, kernel_size=3, padding=1)]
model += [nn.AvgPool2d(2)]
return nn.Sequential(*model)
def make_residual_connect(lf, in_channels, out_channels):
model = []
model += [SNConv2d(in_channels, out_channels, kernel_size=1, padding=0)]
model += [nn.AvgPool2d(2)]
return nn.Sequential(*model)
def forward(lf, input):
s_block(input) + lf.residual_connect(input)
class SNResDiscriminator(nn.Module):
def __init__(lf, ndf=64, ndlayers=4):
super(SNResDiscriminator, lf).__init__()
lf.fc = nn.Sequential(SNLinear(ndf*16, 1), nn.Sigmoid())
def make_model(lf, ndf, ndlayers):
model = []
model += [OptimizedBlock(3, ndf)]
tndf = ndf
for i in range(ndlayers):
model += [ResBlock(tndf, tndf*2, downsample=True)]
tndf *= 2
model += [nn.ReLU()]
return nn.Sequential(*model)
def forward(lf, input):
out = lf.res_d(input)
out = F.avg_pool2d(out, out.size(3), stride=1)
out = out.view(-1, 1024)
return lf.fc(out)
⽣成器SNResDiscriminator ⽤到两个构建模块ResBlock、OptimizedBlock,这两个模块都⽤SNConv2d层来构建带有谱归⼀化的卷积层。在SNConv2d实现中,⽤到@property def W_(lf),是我第⼀次见到的,接下来要好好研究研究。
⼩结:
Gan要想训练稳定进⾏,就需要其Discriminator的映射函数满⾜Lipschitz约束,[1]提出谱范数可作为Lipschitz约束的实施⽅法,进⽽给出归⼀化的实现思路,整个过程⼗分精巧,值得学习。

本文发布于:2023-05-06 12:27:06,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/90/97946.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:矩阵   参数   训练   范数   过程   作为   约束   可采
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图