- 示例工程
【免费下载链接】NYU-DLSP20
NYU Deep Learning Spring 2020
本篇技术指南以 NYU Deep Learning Spring 2020(NYU-DLSP20)第九周课程笔记为骨架,系统梳理两大主线:讲座部分围绕判别类循环稀疏自编码器(Discriminative Recurrent Sparse Auto-Encoder, DrSAE)与组稀疏(Group Sparsity)、世界模型(World Models)与强化学习(RL)的对比展开;动手实践部分则深入生成对抗网络(GAN)与变分自编码器(VAE)的差异、GAN 的三大经典缺陷,以及 PyTorch DCGAN 完整源码。读完本文,你将掌握如何把稀疏编码与判别训练耦合进自编码器、用组稀疏提取不变特征、理解世界模型四模块架构,并能读懂并复现一份可运行的 DCGAN 训练代码。
讲座 A:判别类循环稀疏自编码器(DrSAE)与组稀疏
把稀疏编码与判别训练结合起来
DrSAE 的核心设计思想,是把稀疏编码(Sparse Coding)与判别式训练(Discriminative Training)耦合到同一个网络里,让网络在重建输入的同时,学到"既稀疏、又对分类任务有用"的隐变量。课程原文见 09-1.md。
图 1:判别类循环稀疏自编码器模型结构
DrSAE 的编码器 $W_e$ 与LISTA(Learned Iterative Shrinkage-Thresholding Algorithm)算法中的编码器结构相似。输入 $X$ 先经过编码器 $W_e$ 与一层非线性变换,其结果与一个可调节的变量矩阵 $S$ 相乘,再与 $W_e$ 相加,之后再次经过非线性变换;这一流程可以重复多次,每一次迭代就相当于网络中的一层结构。
这里需要补上 LISTA 的来龙去脉:在第八周笔记 08-2.md 中,稀疏编码被形式化为一个无条件的正则化潜变量能量模型:
$$E(z, y) = \Vert y - Wz\Vert^2 + \lambda \Vert z\Vert_{L^1}$$
其中 $n$ 维向量 $z$ 的非零分量最多为 $m \ll n$ 个。FISTA 通过迭代收缩更新规则求解该能量函数,而 LISTA 则把这个迭代过程展开成一个循环网络——定义 $W_e = \frac{1}{L}W_d$、$S = I - \frac{1}{L}W_d^\top W_d$ 后,迭代更新可写为:
$$z(t+1) = \text{Shrinkage}_{\frac{\lambda}{L}}[W_e^\top y - Sz(t)]$$
该更新规则可被解释为循环网络,于是可以直接学习参数 $W_e$,通过标准的时间反向传播(Backpropagation-Through-Time)在固定的展开步数 $K$ 内训练,最终比 FISTA 用更少的迭代就产出高质量的 $z$。这正是 DrSAE 编码器与 LISTA 相似的根本原因——它们共享"循环展开 + 可学习收缩"的同一套骨架。
DrSAE 的三个训练准则
DrSAE 同时用以下三个准则来训练:
- $L_1$ 稀疏正则化:通过 $L_1$ 范数正则化产生稀疏隐变量 $Z$;
- 重建 $X$:通过解码器 $W_d$ 产生与输入相似的输出,用 $L_2$ 损失函数训练解码器 $W_d$;
- 引入第三变量 $W_c$:训练一个简单的线性分类模型 $W_c$ 来预测类别。
网络通过同时最小化以上三个准则进行训练。这样做的收益是:既能找到可以重建输入的稀疏隐变量,又能让神经网络学到信息含量更丰富的隐变量(同时承载重建与分类信息),这正是"稀疏编码 + 判别训练"组合的价值所在。
组稀疏(Group Sparsity):从逐特征稀疏到逐组稀疏
组稀疏的核心理念是学习"池化后的稀疏特征",而不是单纯"卷积后的稀疏特征"。
图 2:组稀疏自编码器模型结构
组稀疏自编码器与 DrSAE 的主要区别在隐变量 $Z$ 的正则化方式上:
- DrSAE:对 $Z$ 使用 $L_1$ 正则化;
- 组稀疏:对 $Z$ 的每一组使用 $L_2$ 正则化,再把各个组的正则化惩罚因子相加。
由此可以学到稀疏的组别——某些组特征的权重整体变为 0,而每一组内部通常包含相似的特征。这与逐元素稀疏有本质不同:组稀疏施加的是"整组激活或整组休眠"的结构性约束。
关于组稀疏的问答
课程问答环节澄清了几个关键概念(09-1.md):
- Q:图 1 的策略(联合重建 + 判别 + 稀疏)能否用于 VAE?A:VAE 是通过给隐变量加噪声、并控制噪声的变动来限制信息容量,从而防止模型学习到无意义的恒等函数;这与稀疏正则化的机制不同,但"多目标联合训练"的思想可以借鉴。
- Q:幻灯片"AE with Group Sparsity"中的 $P_j$ 代表什么?A:$P_j$ 是第 $j$ 组,每组含有一群特征。若 $Z$ 是一维向量,就把某些元素分类组合起来,且一个元素可以被分到多个组(组间允许重叠);若 $Z$ 是二维图像($\R^{n \times n}$),组可以是其中某个二维子集($\R^{d \times d}$,$d<n$);若 $Z$ 是三维立方体,可以把它展开成树形图(每个节点/每条边对应立方体的顶点/边),每个子树就是一个组。
- Q:解释特征池化(feature pooling)。A:编码器产生隐变量 $Z$,随后通过 $L_2$ 正则化组特征,最后经过解码器重建输入(如图像)。
- Q:组稀疏能帮助归类相似特征吗?A:不一定。组稀疏的研究早于大规模算力与大数据时代,目前尚缺乏大规模实验来给出确切结论。
图像级训练:无权重共享的局域过滤器(Local Filters)
用无权重共享的局域过滤器进行图像级训练未必总是有效,它通常用于图像复原或无人监督学习,且适用于非常小的数据。其网络结构包含卷积编码器与卷积解码器,并先用组稀疏准则进行预训练(pretrain);预训练完成后,只保留网络的编码器,把它放到目标网络(通常是第一层)当作特征提取器,再在其上添加新的网络层。
典型结构如下:图像输入网络后经过编码器(卷积 + ReLU + 缩放层),再经过解码器(线性);整个网络以"重建输入 + 组稀疏正则化"为目标训练。
这一基础结构可以延伸为更多隐藏层,形成堆叠自编码器(Stacked Auto-Encoder):
- 训练完单层"卷积 ReLU 自编码器"后,把训练好的解码器 $W_d$ 与 $L_2$ 正则化的隐变量 $Z$ 结合起来,当作一层$L_2$ 池化层(Pooling Layer);
- 网络通过编码器 + $L_2$ 池化层提取输入图像的特征;
- 用提取的特征作为输入,训练新的组稀疏卷积自编码器实例;
- 把新的自编码器叠加到原有自编码器之上,从而预训练出一个两层(乃至多层)卷积网络。
其收益是通过组稀疏学习到不变特征(invariant features)。
关于树形组结构的补充问答:是否使用全部子树?——由你来决定,可以试验不同大小的子树;也可以先训练大的子树,然后剪掉很少被用到的分支。
侧向抑制:另一种提取不变特征的机制
图 5 展示的模型用 $L_2$ 损失(重构误差)训练一个线性解码器自编码器,但在目标函数中额外增加准则 $\left\lvert z \right\rvert^T S \left\lvert z \right\rvert$。这里的 $S$ 可以预先决定,也可以训练得到并被最大化:如果 $S$ 的元素是正数且数值很大,网络就会学到不能让 $z_i$、$z_j$ 同时不为零——这代表一种相互抑制作用,在神经科学中被称为侧向/自然抑制(lateral inhibition)。
如果把 $S$ 矩阵重构成一棵树形图(每条边代表 $S$ 中数值为 0 的元素),那么树中两个节点之间没有连接就代表对应 $S$ 元素不为零。由此可知:每个特征会抑制除其子树上的特征以外的所有其他特征——这一构造可以看作组稀疏的"反例"。同时可以观察到,这样学到的特征会被排列成近似连续的结构:同一树枝上的特征代表了某种特征的不同方面,特征与其附近特征非常相似(因为它们之间没有抑制作用)。
该网络的训练方式:在每一个迭代中,通过输入 $x$,网络先寻找使图 5 中训练目标最小的 $z$,然后通过梯度下降更新 $W$、通过梯度上升更新 $S$(如果想训练 $S$ 并让它的数值变大)。
讲座 B:世界模型(World Models)与强化学习
自监督学习通向"世界模型"
自监督学习最重要的用途之一是学习世界模型。人类做事情时,头脑中有一个关于世界如何运作的内在模型:例如婴儿大约 9 个月大时,通过观察外界获得物理概念的直觉——这在某种意义上与自监督学习非常相似。自监督模型学习隐变量、预测未来将要发生什么的过程,正是人类学习抽象概念的方式。更进一步,内在模型让我们能对外界做出反应:学会了物理直觉与手部控制后,就能预测并执行"抓住一支正在往下掉的笔"这样的动作。课程原文见 09-2.md。
世界模型的四大模块
一个自动化智能系统由四个主要模块组成:
图 3:自动化智能系统的世界模型结构详解
- 感知模块(Perception):观察世界并计算世界状态的表征。该表征并不完整,原因有二:① 主体无法观察到整个宇宙;② 观察到的信息也不一定准确。值得注意的是,在前馈模型中,感知模块只存在于第一个时间步。
- 演员模块(Actor,也叫策略模块 Policy):基于观察到的世界表征,想象/计算"下一步可能采取的行动"。
- 模型模块(Model):给定世界表征(有时还给定隐变量),预测"采取该行动后世界表征会变成什么样"。这个预测会被传递到下一个时间步,充当下一个世界表征——正因如此,第一时间步之后,模型模块就取代感知模块来负责计算世界表征。
- 评判家模块(Critic):把同样的预测转化为"执行该行动所付出的代价"。例如:给定笔下坠的速度,若人以某种方式移动手,抓到笔的可能性有多低、错过接笔的代价有多大。
经典设定:模型预测控制(MPC)
在经典最优控制(classical optimal control)中,并不存在演员/策略模块,取而代之的是一个行动变量。优化这类问题的方法叫作模型预测控制(Model Predictive Control, MPC)——1960 年代 NASA 从人工计算(主要是黑人女性数学家)切换到电子计算机后,就曾用 MPC 计算火箭轨迹。
我们可以把这个系统看作是未展开的 RNN:把行动变量当作隐变量,用反向传播与梯度下降(离散行动集时也可以用动态规划等其他方法)推断"哪一序列行动能把每一时间步代价的总和降到最低"。
这里有一个术语上的重要区分:虽然优化隐变量和优化参数的过程类似,但我们用"推断"(inference)来形容隐变量的优化,用"学习"(learning)来形容参数的优化。关键区别在于:隐变量因样本而异,参数则被所有样本共享。
改善:训练一个策略网络
每次做规划都要计算复杂的反向传播是很不划算的,于是课程引入了一个与用 VAE 改进稀疏编码完全相同的技巧:训练一个编码器,直接从世界表征预测最优行动序列。在这个场景下,该编码器一般被称为策略网络(Policy Network)。
成功训练好策略网络之后,一旦拿到世界表征,就可以用策略网络立刻预测最优行动序列,无需再实时做代价函数的反向传播。
强化学习(RL)与前面所学内容的区别
强化学习与我们之前学到内容的最大区别有两点:
- 代价函数是个黑盒子:RL 主体根本不知道奖励的本质/内部机制;
- 不使用前向模型推演环境:相反,主体与真实环境互动,通过观察发生了什么来学习。由于对环境的测量并不完美,所以并非每次都能准确预测接下来会发生什么。
RL 最主要的问题在于代价函数不可微,这意味着只能通过反复试验(trial and error)来学习,于是核心难题变成"如何更高效地探索环境"。即便解决了探索效率,还有根本性的探索 vs. 开发(exploration vs. exploitation)权衡:是采取能最大限度了解环境的行动,还是利用已学到的知识尽可能获取高奖励?
演员-评判家(Actor-Critic)算法
RL 中很受欢迎的算法家族之一是演员-评判家算法,它同时训练一个演员和一个评判家。很多其他 RL 算法在"训练代价函数模型(评判家)"这一点上与之类似。其中:
- 评判家的作用:学习价值函数的期望值。由于评判家只是一个普通神经网络,因此可以用反向传播优化它;
- 演员的职责:提出当前应该执行的行动;
- 协作收益:演员与评判家共同合作,比只有演员、没有评判家的 RL 算法学习更高效。
课程强调:如果没有一个好的世界模型,学习会困难得多——例如停在悬崖边的车并不知道"掉下悬崖是个坏主意"。正因为人类和动物头脑中有着很好的世界模型,人类和动物才能比 RL 主体更高效地学习。
不确定性的两种来源
因为内在随机性的存在,我们并不总能准确预测未来,不确定性分两类:
- 偶然性(Aleatoric Uncertainty):由我们不能控制或观察的事情导致;
- 认知不确定性(Epistemic Uncertainty):因为训练数据不足,模型无法预测未来所导致的。
我们希望前向模型(forward model)能够预测:
$$\hat s_{t+1} = g(s_t, a_t, z_t)$$
其中 $z$ 是一个我们不知道具体值的隐变量,代表"世界中我们不知道、但仍会影响到预测结果"的因素(即偶然性)。$z$ 可以通过稀疏性、噪声或编码器来正则化。系统也可以用前向模型学习如何规划:解码器负责解码"状态表征 + 不确定性 $z$"的合并结果。在最好的情况下,$z$ 可以最小化预测 $\hat s_{t+1}$ 与实际观察到的 $s_{t+1}$ 之间的差别。
生成对抗网络(GAN):作为能量模型的对比式方法
GAN 有很多变种,而课程把它看作用对比学习(contrastive method)方法的能量模型(EBM):将对比样本的能量往上推,同时把训练样本的能量往下压。一个基本 GAN 由两个神经网络组成:
- 生成器(Generator):能"智能地"生成对比样本;
- 判别器(Discriminator,有时也叫评判家):本质是一个代价函数,充当能量模型。
GAN 的输入有两种:训练样本和对比样本。
- 训练样本被输入判别器,其能量下降;
- 对比样本则是通过隐变量生成的:先从特定分布中抽样获得隐变量,输入生成器,让生成器生成与训练样本相似的对比样本,再输入判别器让它们的能量上升。
判别器的损失函数为:
$$\sum_i L_d(F(y), F(\bar{y}))$$
其中 $L_d$ 可以是间隔损失函数,例如 $F(y) + [m - F(\bar{y})]^+$ 或 $\log(1 + \exp[F(y)]) + \log(1 + \exp[-F(\bar{y})])$——只要该损失函数能同时让 $F(y)$ 下降、让 $F(\bar{y})$ 上升即可。此处 $y$ 是标签,$\bar{y}$ 是"除 $y$ 以外能量最低"的反应变量。
生成器的损失函数与判别器不同:
$$L_g(F(\bar{y})) = L_g(F(G(z)))$$
其中 $z$ 是隐变量,$G$ 是生成器神经网络。我们想要生成器调整权重,生成能够骗过判别器的低能量 $\bar{y}$。
之所以叫"生成对抗网络",是因为我们有两个互相对抗的目标函数,并且需要同时优化它们。目标是找到这两个函数的纳什均衡(Nash equilibrium)——因此这并不是一个梯度下降问题,因为单纯的梯度下降无法做到这一点。
无限薄流形问题与 WGAN
当样本太靠近真实流形时就会面临困难:假设有一个无限薄的流形,判别器需要在流形外获得 0 的可能性、同时在流形上获得无限大的可能性——这很难实现。于是 GAN 用Sigmoid 函数:流形外输出 0、流形上输出 1。但问题在于,如果模型真的训练到"流形外输出 0"的完美状态,能量函数就完全没用了——能量会变得极不光滑:数据流形外的能量变成无限大、数据流形上的能量变成 0,而我们不希望能量在很短距离内从 0 跳到无限大。
不少学者提出了正则化能量函数的方法来解决这个问题,Wasserstein GAN(WGAN)就是一个很好的例子——它通过限制判别器权重大小(权重裁剪/谱归一化等)来保证能量函数的平滑性。
动手做:GAN 实战与 PyTorch DCGAN 源码解读
GAN 的本质:能量代价网络 + 生成器
GAN 是一种用于无监督机器学习的神经网络,包含两个对抗模块:生成器与代价网络(cost network)。代价网络负责"去掉冒牌货",生成器则尝试生成足够真实的冒牌货 $\hat{x}$ 来骗过它;在这种互相竞争之后,模型就学到了一个能生成十分逼真数据的生成器。它可用于预测未来,或在特定数据集上训练后生成图片。课程原文与代码见 09-3.md。
众多 GAN 都是**能量基础模型(EBM)**的实例:
- 代价网络被训练成:对接近真实数据分布的输入(粉色 $\vect{x}$)只输出低代价;对来自其他分布的数据(蓝色 $\hat{\vect{x}}$)输出高代价;
- 常用**均方误差损失(MSE loss)**计算代价网络的性能;
- 代价函数输出一个正标量,且处于指定范围内,即 $\text{cost} : \mathbb{R}^n \rightarrow \mathbb{R}^+ \cup {0}$——这一点与传统判别器"用离散分类器输出类别"截然不同。
生成器网络($\text{generator} : \mathcal{Z} \rightarrow \mathbb{R}^n$)被训练成改善其对随机变量 $\vect{z}$ 的映射能力,以生成更真实的 $\hat{\vect{x}}$ 来骗过代价网络。生成器根据代价网络的输出来训练,并最小化 $\hat{\vect{x}}$ 的能量,记作 $C(G(\vect{z}))$($C(\cdot)$ 为代价网络,$G(\cdot)$ 为生成器)。
训练机制:
- 训练代价网络:基于最小化 MSE 损失;
- 训练生成器:通过最小化代价网络,利用 $C(\hat{\vect{x}})$ 相对 $\hat{\vect{x}}$ 的梯度。
为确保流形外的点有高代价、流形内的点有低代价,代价网络的损失函数为:
$$\mathcal{L}_C = C(x) + [m - C(G(\vect{z}))]^+$$
其中 $m$ 是代表边界的正数。最小化 $\mathcal{L}_C$ 要求 $C(\vect{x}) \rightarrow 0$ 且 $C(G(\vect{z})) \rightarrow m$;生成器的损失 $\mathcal{L}_G$ 则简单地取 $C(G(\vect{z}))$,促使生成器把 $C(G(\vect{z}))$ 压向 0。然而这会造成不稳定:$C(G(\vect{z}))$ 在 0 与 $m$ 之间来回拉扯,即 $0 \leftarrow C(G(\vect{z})) \rightarrow m$。
GAN 与 VAE 的核心差异
图 4:VAE(左)vs. GAN(右)——架构设计对比
与第八周学习的变分自编码器(VAE,见 08.md)相比,GAN 的生成器构建方式不同:
- VAE:用编码器把输入 $\vect{x}$ 映射到潜空间 $\mathcal{Z}$,再用解码器从 $\mathcal{Z}$ 映射回数据空间得到 $\hat{\vect{x}}$,然后以**重建损失(reconstruction loss)**推动 $\vect{x}$ 与 $\hat{\vect{x}}$ 变得相似;
- GAN:在一个互相对抗的环境中训练,生成器与代价网络互相对抗,两者通过反向传播与基于梯度的方法**依次地(successively)**训练。
两者的另一关键区别在于如何产生和使用 $\vect{z}$:GAN 从抽样 $\vect{z}$ 开始(类似于 VAE 的潜空间),用生成网络把 $\vect{z}$ 映射到 $\hat{\vect{x}}$,再把 $\hat{\vect{x}}$ 送入判别器/代价网络评估"真实度"。核心差异是:GAN 不需要测量生成器输出 $\hat{\vect{x}}$ 与真实数据 $\vect{x}$ 的直接关系(即重建损失)——而是通过训练生成器,让判别器/代价网络对 $\hat{\vect{x}}$ 给出的分数与真实数据 $\vect{x}$ 接近,即更"真实"。
GAN 最常犯错的三个地方
尽管 GAN 在生成数据上非常强大,但存在许多容易踩坑之处:
1. 不稳定的收敛(Unstable convergence)
随着生成器随训练改进,判别器性能会变差,因为它不再能轻易区分真数据和假数据。如果生成器完美,"真数据流形"和"假数据流形"会完全重合,判别器就会出现大量错误分类。这带来 GAN "收敛"时的核心问题:判别器的反馈随时间变得无意义。如果 GAN 持续训练到判别器给出完全随机的反馈之后,生成器就开始用这些"垃圾反馈"来训练,最终生成器质量完全下降。因此,生成器与判别器之间的对抗性质导致的是一个不稳定的平衡点,而不是稳定点。
2. 消失的梯度(Vanishing gradient)
如果 GAN 使用二元交叉熵损失:
$$\mathcal{L} = \mathbb{E}\boldsymbol{x}[\log(D(\boldsymbol{x}))] + \mathbb{E}\boldsymbol{\hat{x}}[\log(1 - D(\boldsymbol{\hat{x}}))]$$
当判别器越来越有把握时,$D(\vect{x})$ 趋近 1、$D(\hat{\vect{x}})$ 趋近 0。这种自信把代价网络的输出推向更平坦的区域,那里的梯度更饱和——这些平坦区域提供细小、消失的梯度,阻碍生成器训练。因此训练 GAN 时,要清楚:判别器越自信,代价就越会逐渐上升。
3. 模式崩溃(Mode collapse)
如果生成器把从采样器来的所有 $\vect{z}$都映射到单个能骗过判别器的 $\hat{\vect{x}}$,那么生成器就只会生成那一个$\hat{\vect{x}}$。最终判别器学会专门检测这一个假输入,于是生成器又去寻找下一个"最合理"的 $\hat{\vect{x}}$,循环往复。结果判别器在多个假 $\hat{\vect{x}}$ 的循环中困在局部最小值(local minima)。最可能的解决方法是对生成器施加惩罚:当输入不同、输出却相同时给予相应惩罚。
DCGAN 源码逐段解读(PyTorch)
动手做部分最后逐行解读了 PyTorch 官方示例Deep Convolutional Generative Adversarial Networks(DCGAN)的源码(原始示例出自 PyTorch examples 仓库的dcgan/main.py,本仓库的完整笔记与代码注释见 09-3.md)。
生成器(Generator)
设计要点:
- 生成器用多个模块对输入进行升采样(upsample),每个模块是
nn.ConvTranspose2d,随后接nn.BatchNorm2d和nn.ReLU; - 在
nn.Sequential的最后,网络用nn.Tanh()把输出压缩到 $(-1, 1)$(与图像归一化范围匹配); - 输入随机向量的尺寸为 $nz$;输出尺寸为 $nc \times 64 \times 64$,其中 $nc$ 是通道数量。
class Generator(nn.Module): def __init__(self): super().__init__() self.main = nn.Sequential( # 输入 Z 被输入到卷积层 nn.ConvTranspose2d( nz, ngf * 8, 4, 1, 0, bias=False), nn.BatchNorm2d(ngf * 8), nn.ReLU(True), # 现在大小为: (ngf*8) x 4 x 4 nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf * 4), nn.ReLU(True), # 现在大小为: (ngf*4) x 8 x 8 nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf * 2), nn.ReLU(True), # 现在大小为: (ngf*2) x 16 x 16 nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False), nn.BatchNorm2d(ngf), nn.ReLU(True), # 现在大小为: (ngf) x 32 x 32 nn.ConvTranspose2d( ngf, nc, 4, 2, 1, bias=False), nn.Tanh() # 现在大小为: (nc) x 64 x 64 ) def forward(self, input): output = self.main(input) return output判别器(Discriminator)
设计要点:
- 必须使用
nn.LeakyReLU作为激活函数,防止负区域的梯度消失——没有了这些梯度,生成器将收不到任何更新; - 在
nn.Sequential的最后,判别器使用nn.Sigmoid()对输入进行分类。
class Discriminator(nn.Module): def __init__(self): super().__init__() self.main = nn.Sequential( # nc 是通道数量,输入大小是 (nc) x 64 x 64 nn.Conv2d(nc, ndf, 4, 2, 1, bias=False), nn.LeakyReLU(0.2, inplace=True), # 现在大小为: (ndf) x 32 x 32 nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 2), nn.LeakyReLU(0.2, inplace=True), # 现在大小为: (ndf*2) x 16 x 16 nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 4), nn.LeakyReLU(0.2, inplace=True), # 现在大小为: (ndf*4) x 8 x 8 nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False), nn.BatchNorm2d(ndf * 8), nn.LeakyReLU(0.2, inplace=True), # 现在大小为: (ndf*8) x 4 x 4 nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False), nn.Sigmoid() ) def forward(self, input): output = self.main(input) return output.view(-1, 1).squeeze(1)以上两个类被分别初始化为netG与netD。
损失函数
用**二元交叉熵(BCE)**对应输出与目标:
criterion = nn.BCELoss()训练前的设置
设定fixed_noise(用于观测生成器训练的固定噪声)尺寸为opt.batchSize、随机向量长度为nz;同时为真实数据与生成(假)数据分别创建标签real_label与fake_label:
fixed_noise = torch.randn(opt.batchSize, nz, 1, 1, device=device) real_label = 1 fake_label = 0然后为判别器网络与生成器网络分别设定 Adam 优化器:
optimizerD = optim.Adam(netD.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999)) optimizerG = optim.Adam(netG.parameters(), lr=opt.lr, betas=(opt.beta1, 0.999))训练:每个 epoch 的两步更新
第一步:更新判别器网络,分两小步完成。
- 第一小步:把来自
dataloaders的真实数据送入判别器,计算输出与real_label的损失并反向传播累积梯度; - 第二小步:把生成器用
fixed_noise生成的假数据送入判别器,计算输出与fake_label的损失并反向传播累积梯度; - 最后用累积的梯度一次性更新判别器参数。
两个关键实现细节:
- 训练判别器时必须
detach假数据,防止梯度传播回生成器; - 只需在开头调用一次
zero_grad()清零梯度,用两次.backward()累积真/假数据的梯度,最后一次optimizerD.step()完成参数更新。
# 用真实数据训练 netD.zero_grad() real_cpu = data[0].to(device) batch_size = real_cpu.size(0) label = torch.full((batch_size,), real_label, device=device) output = netD(real_cpu) errD_real = criterion(output, label) errD_real.backward() D_x = output.mean().item() # 用假数据训练 noise = torch.randn(batch_size, nz, 1, 1, device=device) fake = netG(noise) label.fill_(fake_label) output = netD(fake.detach()) errD_fake = criterion(output, label) errD_fake.backward() D_G_z1 = output.mean().item() errD = errD_real + errD_fake optimizerD.step()第二步:更新生成器网络。这一次仍然把假数据送入判别器,但用real_label(真标签)来计算损失——目的是训练生成器生成更真实的 $\hat{\vect{x}}$:
netG.zero_grad() label.fill_(real_label) # 对生成器代价而言,假标签是真实的 output = netD(fake) errG = criterion(output, label) errG.backward() D_G_z2 = output.mean().item() optimizerG.step()小结
第九周课程把"能量模型视角"贯穿始终:讲座 A 展示了如何把稀疏编码(LISTA 式循环展开)与判别训练耦合为 DrSAE,并通过组稀疏、侧向抑制等方式让自编码器学到不变特征;讲座 B 从世界模型的感知—演员—模型—评判家四模块出发,对比了模型预测控制、策略网络与强化学习(含演员-评判家与两类不确定性);动手做则以能量模型 + 对比法重新解读 GAN,明确其与 VAE 的差异、三大训练缺陷,并用 PyTorch DCGAN 的完整代码演示了"分步更新判别器、再以真标签更新生成器"的标准对抗训练流程。进一步深入可阅读本仓库的 09-1.md、09-2.md、09-3.md,以及承载 LISTA/FISTA 与稀疏编码基础的 08-2.md。
- 示例工程
【免费下载链接】NYU-DLSP20
NYU Deep Learning Spring 2020
相关推荐
NYU-DLSP20 第九周:从判别式稀疏自编码器到世界模型与 GAN 的生成模型实战
NYU DLSP20 第九周:从判别式稀疏自编码器到世界模型与 GAN 的生成模型实战 本文以 NYU Deep Learning Spring 2020(DS
示例工程NYU-DLSP20 第九周深度解析:判别式稀疏自编码器、世界模型与生成对抗网络实战
NYU DLSP20 第九周深度解析:判别式稀疏自编码器、世界模型与生成对抗网络实战 本篇技术指南以 NYU DLSP20(NYU Deep Learning
示例工程NYU-DLSP20 第九周精讲:判别式稀疏自编码器、World Models 与生成对抗网络实战
NYU DLSP20 第九周精讲:判别式稀疏自编码器、World Models 与生成对抗网络实战 本文基于 NYU 深度学习课程(DS GA 1008,Spr
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考