GAN中的Spectral Normalization

GAN中的Spectral NormalizationGAN 中的 SpectralNorm SpectralNorm 出自 SpectralNorm 和 SpectralNorm

GAN中的Spectral Normalization

  Spectral Normalization 出自 《Spectral Norm Regularization for Improving the Generalizability of Deep Learning》《Spectral Normalization for Generative Adversarial Networks》,是为了解决GAN训练不稳定的问题,从“层参数”的角度用spectral normalization的方式施加regularization,从而使判别器D具备Lipschitz连续条件


为什么要让D具备Lipschitz连续


为了防止判别器“放飞自我”

根据Wikipedia的定义,Lipschitz连续条件 如下。其意义在于使得 f 足够稳定,在输入发生少量变化时,输出不会有太巨大的变化。如果有图像A,修改少量像素得到图像B,输入判别器D后得到相差非常巨大的判别效果,那么判别器就是不稳定的,它对输入过于敏感。
∣ ∣ f ( x 1 ) − f ( x 2 ) ∣ ∣ ∣ ∣ x 1 − x 2 ∣ ∣ ≤ K , ∀ x 1 , x 2 \frac{||f(x_1)-f(x_2)||}{||x_1-x_2||}\leq K, \forall x_1,x_2 x1x2f(x1)f(x2)K,x1,x2
这里的 K 被称为 f ( x ) f(x) f(x)Lipschitz constantK的最小值(上确界)被称为 ∣ ∣ f ∣ ∣ L i p ||f||_{Lip} fLip ,称 f ( x ) f(x) f(x) 满足 Lipschitz连续条件




《Wasserstein GAN》 给出了衡量真实分布 P r P_r Pr和生成分布 P g P_g PgEarth-Mover(EM) 距离:

W ( P r , P g ) = inf ⁡ γ ∈ ( P r , P g ) E ( x , y ) ∼ γ [ ∣ ∣ x − y ∣ ∣ ] W(P_r, P_g)=\inf_{\gamma \in (P_r, P_g)}E_{(x,y)\sim \gamma}[||x-y||] W(Pr,Pg)=γ(Pr,Pg)infE(x,y)γ[xy]

Kantorovich-Rubinstein duality 可得EM距离的另一个形式,这里有用到 ∣ ∣ f ∣ ∣ L i p ||f||_{Lip} fLip

W ( P r , P g ) = 1 K sup ⁡ ∣ ∣ f ∣ ∣ L i p ≤ K E x ∼ P r [ f ( x ) ] − E x ∼ P g [ f ( x ) ] W(P_r, P_g)=\frac{1}{K} \sup_{||f||_{Lip}\leq K}E_{x\sim P_r}[f(x)]-E_{x\sim P_g}[f(x)] W(Pr,Pg)=K1fLipKsupExPr[f(x)]ExPg[f(x)]

∣ ∣ f ∣ ∣ L i p ≤ 1 ||f||_{Lip}\leq 1 fLip1的条件下,求两个期望之差的上确界,就是EM距离,推导过程可参考这里。


矩阵范数

根据Wikipedia,矩阵的范数如下,它是右边的分式的上确界。

∣ ∣ A ∣ ∣ p = sup ⁡ x ≠ 0 ∣ ∣ A x ∣ ∣ p ∣ ∣ x ∣ ∣ p ||A||_p=\sup_{x\neq 0}\frac{||Ax||_p}{||x||_p} Ap=x̸=0supxpAxp

其中, p = 2 p=2 p=2时被称为 Euclidean NormL2-NormSpectral Norm,它是矩阵的最大奇异值(或者最大特征值的开方),下面 A T A^T AT 表示共轭转置。

σ ( A ) = ∣ ∣ A ∣ ∣ 2 = λ m a x ( A T A ) \sigma (A)=||A||_2=\sqrt{\lambda_{max}(A^TA)} σ(A)=A2=λmax(ATA)

矩阵范数的性质:

∣ ∣ A B ∣ ∣ p ≤ ∣ ∣ A ∣ ∣ p ∣ ∣ B ∣ ∣ p ||AB||_p\leq ||A||_p||B||_p ABpApBp
σ ( A B ) ≤ σ ( A ) σ ( B ) \sigma (AB)\leq \sigma (A)\sigma (B) σ(AB)σ(A)σ(B)


Spectral Normalization

考虑采用了非线性激活函数的MLP网络构成的判别器D, x l = a l ( W l x l − 1 + b l ) x^l=a^l(W^lx^{l-1}+b^l) xl=al(Wlxl1+bl),把它的参数写成 θ = { W l , b l } l = 1 L \theta=\{W^l, b^l\}_{l=1}^{L} θ={
Wl,bl}l=1L
,使的判别器可以写成 f θ ( x 0 ) = x L f_{\theta}(x^0)=x^L fθ(x0)=xL。只考虑 x x x的一个很小的邻域,可以将判别器看作一个线性函数, f θ ( x ) = W θ , x x + b θ , x f_{\theta}(x)=W_{\theta,x}x+b_{\theta,x} fθ(x)=Wθ,xx+bθ,x

∣ ∣ f θ ( x + δ ) − f θ ( x ) ∣ ∣ 2 ∣ ∣ δ ∣ ∣ 2 = ∣ ∣ W θ , x δ ∣ ∣ 2 ∣ ∣ δ ∣ ∣ 2 ≤ σ ( W θ , x ) = sup ⁡ δ ≠ 0 ∣ ∣ W θ , x δ ∣ ∣ 2 ∣ ∣ δ ∣ ∣ 2 \frac{||f_{\theta}(x+\delta)-f_{\theta}(x)||_2}{||\delta||_2}=\frac{||W_{\theta,x}\delta||_2}{||\delta||_2}\leq \sigma(W_{\theta,x})=\sup_{\delta\neq 0}\frac{||W_{\theta,x}\delta||_2}{||\delta||_2} δ2fθ(x+δ)fθ(x)2=δ2Wθ,xδ2σ(Wθ,x)=δ̸=0supδ2Wθ,xδ2

若激活函数使用 ReLU,可以把 a l ( x l − 1 ) a^l(x^{l-1}) al(xl1) 看作 D θ , x l x D^l_{\theta, x}x Dθ,xlx,对角阵 D θ , x l D^l_{\theta, x} Dθ,xl x l − 1 x^{l-1} xl1 非负的对应位置上为1,其他地方是0,这样 σ ( D θ , x l ) = ∣ ∣ D θ , x l ∣ ∣ 2 ≤ 1 \sigma(D^l_{\theta, x})=||D^l_{\theta, x}||_2\leq 1 σ(Dθ,xl)=Dθ,xl21

W θ , x = D θ , x L W θ , x L ⋯ D θ , x 1 W 1 W_{\theta,x}=D^L_{\theta,x}W^{L}_{\theta,x}\cdots D^1_{\theta,x}W^1 Wθ,x=Dθ,xLWθ,xLDθ,x1W1

σ ( W θ , x ) ≤ σ ( D θ , x L ) σ ( W L ) ⋯ σ ( D θ , x 1 ) σ ( W 1 ) ≤ ∏ l = 1 L σ ( W l ) \sigma(W_{\theta,x})\leq \sigma(D^L_{\theta,x})\sigma(W^{L})\cdots\sigma(D^1_{\theta,x})\sigma(W^1)\leq\prod_{l=1}^L\sigma(W^l) σ(Wθ,x)σ(Dθ,xL)σ(WL)σ(Dθ,x1)σ(W1)l=1Lσ(Wl)

若对判别器D的每一层都做 Spectral Normalization:

W ^ S N l = W l σ ( W l ) \hat W^l_{SN}=\frac{W^l}{\sigma(W^l)} W^SNl=σ(Wl)Wl

σ ( W ^ S N l ) = σ ( W ) σ ( W ) = 1 \sigma(\hat W^l_{SN})=\frac{\sigma(W)}{\sigma(W)}=1 σ(W^SNl)=σ(W)σ(W)=1

∣ ∣ f θ ( x + δ ) − f θ ( x ) ∣ ∣ 2 ∣ ∣ δ ∣ ∣ 2 ≤ σ ( W θ , x ) ≤ 1 \frac{||f_{\theta}(x+\delta)-f_{\theta}(x)||_2}{||\delta||_2}\leq\sigma(W_{\theta,x})\leq1 δ2fθ(x+δ)fθ(x)2σ(Wθ,x)1


Spectral Normalization 实现

Spectral Normalization实际上在做的事情,是将每层的参数矩阵除以自身的最大奇异值,本质上是一个逐层SVD的过程,但是真的去做SVD就太耗时了,所以采用幂迭代的方法求(参见Wikipedia)。


GAN中的Spectral Normalization

def spectral_norm(w, iteration=10, name="sn"): ''' Ref: https://github.com/taki0112/Spectral_Normalization-Tensorflow/blob/65218e8cc6916d24b49504c5e1be1/spectral_norm.py ''' w_shape = w.shape.as_list() # [KH, KW, Cin, Cout] or [H, W] w = tf.reshape(w, [-1, w_shape[-1]]) # [KH*KW*Cin, Cout] or [H, W] u = tf.get_variable(name+"_u", [1, w_shape[-1]], initializer=tf.random_normal_initializer(), trainable=False) s = tf.get_variable(name+"_sigma", [1, ], initializer=tf.random_normal_initializer(), trainable=False) u_hat = u # [1, Cout] or [1, W] v_hat = None for _ in range(iteration): v_hat = tf.nn.l2_normalize(tf.matmul(u_hat, tf.transpose(w))) # [1, KH*KW*Cin] or [1, H] u_hat = tf.nn.l2_normalize(tf.matmul(v_hat, w)) # [1, Cout] or [1, W] u_hat = tf.stop_gradient(u_hat) v_hat = tf.stop_gradient(v_hat) sigma = tf.matmul(tf.matmul(v_hat, w), tf.transpose(u_hat)) # [1,1] sigma = tf.reshape(sigma, (1,)) with tf.control_dependencies([u.assign(u_hat), s.assign(sigma)]): # ops here run after u.assign(u_hat) w_norm = w / sigma w_norm = tf.reshape(w_norm, w_shape) return w_norm 

若输入矩阵是全连接层的参数,尺寸为 [ H , W ] [H,W] [H,W],则spectral_norm在效果上会直接对该二维矩阵求最大奇异值,但如果输入矩阵为卷积层的卷积核,其尺寸应该是 [ K H , K W , C i n , C o u t ] [K_H, K_W, C_{in}, C_{out}] [KH,KW,Cin,Cout]spectral_norm会先将该矩阵reshape成一个大小为 [ K H K W C i n , C o u t ] [K_HK_W C_{in}, C_{out}] [KHKWCin,Cout]的矩阵,再用迭代法对该二维矩阵求最大奇异值。不论哪种情况,求得的奇异值都是一个单值scalar


带SN的卷积层

def conv2d(x, channel, k_h=5, k_w=5, d_h=2, d_w=2, stddev=0.02, name='conv2d'): with tf.variable_scope(name): w = tf.get_variable('w', [k_h, k_w, x.get_shape()[-1], channel], initializer=tf.truncated_normal_initializer(stddev=stddev)) w_sn = spectral_norm(w, iteration=3) conv = tf.nn.conv2d(x, filter=w_sn, strides=[1, d_h, d_w, 1], padding='VALID') biases = tf.get_variable('biases', [channel], initializer=tf.constant_initializer(0.0)) conv = tf.reshape(tf.nn.bias_add(conv, biases), conv.get_shape()) return conv 

带SN的FC层

def dense(x, output_size, stddev=0.02, bias_start=0.0, activation=None, sn=False, reuse=False, name='dense'): shape = x.get_shape().as_list() with tf.variable_scope(name, reuse=reuse): W = tf.get_variable( 'weights', [shape[1], output_size], tf.float32, tf.random_normal_initializer(stddev=stddev)) bias = tf.get_variable( 'biases', [output_size], initializer=tf.constant_initializer(bias_start)) if sn: W = spectral_norm(W, 20, name="sn") out = tf.matmul(x, W) + bias if activation is not None: out = activation(out) return out 

检验SpectralNorm

  用dense层检验,首先将W的初始值改为tf.ones_initializer()变成全1矩阵,然后用大小(3,3)的全1输入,在sn=Truesn=False的条件下分别输出结果。

x = tf.ones(shape=(3,3), dtype=tf.float32) y1 = dense_ones(x, output_size=4, sn=False) y2 = dense_ones(x, output_size=4, sn=True) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) print sess.run(y1) print sess.run(y2) 

  dense层的W是一个大小为(3,4)的全1矩阵,用matlabnp.linalg.svd可以算出它的最大奇异值为3.464,而3.0/3.464=0.8660,从下面的结果可见,在sn=True时,FC层输出的结果等于在sn=False时的结果除以FC层权值矩阵W的最大奇异值,该结果本质上是由于权值矩阵W自己除以最大奇异值导致的。
  因此,Spectral Norm的实现是正确的。

# y1 [[3. 3. 3. 3.] [3. 3. 3. 3.] [3. 3. 3. 3.]] # y2 [[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]] 

如何查看sigma的值

  有时候希望查看迭代法求得的最大奇异值是否正确,或者用它作为某种指示指标(比如用作GAN里判别器是否放飞自我的标准),希望以Tensor的形式获取它。只需在spectral_norm中专门声明一个variable用于存储它的值,在迭代法计算完之后把sigma赋给它即可。

def spectral_norm(...): ... s = tf.get_variable("sn_sigma", [1, 1], initializer=tf.random_normal_initializer(), trainable=False) ... with tf.control_dependencies([u.assign(u_hat), s.assign(sigma)]): ... 

  为了用sess.run获取它实时的值,需要先得到这个Tensor,一种简单暴力的方法是首先用tf.global_variables()查看所有variable的名字,找到该Tensor的名字,用tf.get_collection获取。要注意Tensor的名字往往前面带有很多variable_scope,就像文件路径一样,需要完整地传入tf.get_collection

with tf.Session() as sess: sess.run(tf.global_variables_initializer()) v = tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, '你的前缀/sn_sigma:0')[0] sigma = sess.run(v, feed_dict={ 
         ...}) print sigma[0, 0] 

control_dependencies的作用

  在我找到的所有Spectral Normalization的实现代码中,都有control_dependencies存在,其作用在于每次调用spectral_norm时用上一次调用中进行迭代得到的u来初始化本次调用的u,暂时不明白其必要性,如果为了更加精确而多次调用输入同一个权值矩阵,倒不如将iteration设的大一点(20以上就已经非常精确了),如果是不同的输入,则这样初始化没有意义,并不会变得更精确。
  这里演示一下control_dependencies、assign和identity的配合使用。
  在下面的代码中,control_dependencies保证了在每次out=tf.identity(u)前都会先将u_var更新为u的值,只有在最开始u_var的初始值为1.0。




u_var = tf.Variable(1.0) u = u_var u = u * 3 with tf.control_dependencies([tf.assign(u_var, u, name='update_u')]): out = tf.identity(u) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for _ in range(3): print out.eval() ''' 3.0 9.0 27.0 ''' 

关于迭代次数

  迭代法计算最大奇异值,迭代次数达到25即可非常精确,但为了节省时间,一般不需要这么多。


Reference

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/229513.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月16日 下午4:32
下一篇 2026年3月16日 下午4:33


相关推荐

  • 基于华为云码道 + 高德地图MCP Server快速搭建行程规划助手

    基于华为云码道 + 高德地图MCP Server快速搭建行程规划助手

    2026年3月14日
    2
  • 文本分类算法比较_文本匹配算法

    文本分类算法比较_文本匹配算法本文对常用文本分类算法进行了比较,第一部分包括Rocchio算法,boosting,bagging,,逻辑回归,朴素贝叶斯分类器,k最近邻和支持向量机。另外还包括决策树、条件随机场、随机森林和深度学习算法。第二部分将文本分类技术与标准进行了比较:体系结构、作者、模型、新颖性、特征提取、细节、语料库、验证措施和每种技术的局限性。每个文本分类技术(系统)都包含一个模型,该模型是分类器算法,还需要一个特征提取技术,即将文本或文档数据集转换为数字数据。还列出了用于评估系统的验证措施。文章目录文本分类算法文本分类

    2025年7月6日
    3
  • AI Agent+MCP实战课:多工具集成 + 数据库开发,从0到1搭建变现编程智能体(实操指南)

    AI Agent+MCP实战课:多工具集成 + 数据库开发,从0到1搭建变现编程智能体(实操指南)

    2026年3月15日
    6
  • 各平均数介绍(算数平均数、几何平均数、加权算术平均数)

    各平均数介绍(算数平均数、几何平均数、加权算术平均数)1 算数平均数这是日常生活中用到最多的平均数 比如计算一个班的平均成绩 平均身高 2 加权算数平均数加权算术平均数一般用于分组数据 其中 X 是每个组的组中值 3 几何平均数 3 1 简单几何平均数 3 2 加权几何平均数 4 几何平均数和算数平均数的鉴别 1 变量值之间的关系不同如果被平均的各变量值之间是平行关系 相互无影响 则平均数用算数平均数求解 例如 求 3 人的平均年龄 用算数平均数 如求流水作业的 3 个车间平均合格率 由于被平均的 3 个车间合格率之间存在相互影响关系 即其中第一年合格率

    2026年3月16日
    2
  • 问题解决之Cannot find module ‘fs/promises‘

    问题解决之Cannot find module ‘fs/promises‘现象 在 Electron 项目升级了打包工具后打包失败 Cannotfindmo fs promises 原因 经过排查发现 package json 和 package lock json 里打包工具版本不一致解决方法 重新安装指定版本的打包工具来更新 package lock json 对应的版本 npminstallxx xxxxxsave

    2026年3月17日
    2
  • C#编写单片机上位机软件-串口通讯(BMS汽车动力电池管理系统)

    C#编写单片机上位机软件-串口通讯(BMS汽车动力电池管理系统)上位机软件做了有一段时间,本人呕心沥血的作品。之前用python+pyQT5做,主线程特别特别容易闪退,数据重叠等问题,走了很多弯路;后来用VS的C#做,只能说上手很快,调试效果也不错,而且关键是非常非常简单易读,只希望大家不要像我一样,走这么多弯路–想要源码的小伙伴们,欢迎关注点赞三连+留言邮箱哦~华丽的分割线——————————————————————————————–

    2022年5月31日
    55

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号