半监督生成对抗网络SGAN:用无标签数据解决图像分类标注难题

发布时间:2026/10/21 18:20:58
半监督生成对抗网络SGAN:用无标签数据解决图像分类标注难题
做图像分类项目的朋友应该都有这种体验数据攒够了但标注跟不上。尤其是刚起步的小团队一张张打标签的人力成本和时间成本往往比训练模型本身贵得多。这时候半监督生成对抗网络SGAN就派上用场了。它属于生成对抗网络家族里比较特别的一支核心目标不是单纯生成以假乱真的图片而是借助生成器的压力逼着判别器学到更强的特征表示从而在标注样本非常少的情况下仍然把分类任务做得够好。我最早接触SGAN是在一次线上竞赛里训练集有几万张图但只有几百张带完整类别标注。当时试过先预训练再微调、也试过伪标签效果一般。后来把SGAN跑通之后才真正理解了什么叫“用一个无监督任务去辅助有监督任务”。这篇文章会把SGAN的原理、损失函数、训练流程和踩坑经验完整讲一遍适合正在做图像分类、但标注预算有限的读者也适合想搞懂“GAN除了生成图片还能干什么”的人。1. 先搞懂SGAN的应用场景标注不够用的时候它为什么香1.1 标注成本才是真实项目里最大的瓶颈在真实项目里模型结构、训练框架往往都有成熟的现成方案真正卡住进度的是数据标注。一个二分类任务或许还能靠规则扛一扛但一旦类别上升到十类、几十类标注质量直接影响模型上限。有经验的朋友应该都有感触花一周时间清洗和标注数据效果可能比换一个更强的 backbone 还明显。可问题是标注资源永远是有限的尤其是垂直领域还需要有专业背景的人来标成本更高。半监督学习的出发点就是赌一个假设无标签数据里包含大量关于数据分布的信息虽然它没有告诉你它是什么类但它能告诉你“这一类大概长什么样”。如果能把这些信息榨出来哪怕每类只有几十个标注样本也有机会逼近全监督的效果。SGAN就是沿着这个思路走的一条路只不过它用来榨取无标签信息的方式比较特别——不是直接做一致性正则或者自训练而是借助一个生成器让整个系统在对抗博弈中自动学到有用的特征。1.2 为什么GAN也能做半监督很多人听到“生成对抗网络”第一反应是生成图片、风格迁移、超分辨率。但GAN真正有价值的地方不只是生成结果本身而是判别器被迫学习到的数据分布特征。生成器为了让判别器相信假样本是真的会不断模仿真实数据的细节判别器为了分辨真假也必须在特征层面抓住那些“只有真实数据才具备”的统计规律。这些特征天然适合作为分类任务的基础。SGAN的核心思路就是把判别器改造成一个同时又做真假判断、又做类别分类的网络。对于一个没见过标签数据的人他可能没法说出某张图具体是哪一类但他能判断这张图是不是“像这个数据集里的东西”。反过来如果他能精确判断真假说明他对数据分布已经有很深的理解。这种理解迁移到分类上就是免费的标注信息。所以SGAN做的事情可以概括为让真假判别和分类共享同一个特征提取器互为补充互相增强。对比简单粗暴地把一个分类器和一个GAN拼在一起共享网络的好处在于梯度信息是融合的。分类分支的梯度会告诉特征提取器哪些特征是类间差异大的真假分支的梯度会告诉它哪些特征是整体分布特有的两者作用在同一个特征空间上得到的表征既保留了类间判别性又保留了分布真实性。2. 核心思想拆解判别器怎么从二分类变成K1分类2.1 标准GAN里判别器在做的事先回顾一下标准GAN。生成器输入一个随机噪声向量输出一张图像。判别器输入一张图像输出一个标量表示“这张图是真的”的置信度。训练的时候判别器努力把真实图像判定为真、把生成图像判定为假生成器努力让自己的输出被判定为真。最后达到的平衡状态就是生成器学会了生成以假乱真的样本。这个过程中的关键点是判别器最终只是一个二分类器它输出的是一个连续值。它只关心“真还是假”完全不关心“这是猫还是狗”。但是为了把猫的照片和生成器的假照片区分开它必须学会猫的毛发纹理、轮廓结构等细节特征。这些特征不会出现在输出标量里而是藏在网络中间层的特征图里。SGAN的思路很简单既然中间层特征已经包含了这么多关于数据分布的信息不如在最顶部分出K个分支让这个特征提取器顺便把类别也分出来。2.2 SGAN对判别器的关键改动SGAN的改动看起来很轻量但意义很深。原版判别器最后一层输出1个节点SGAN把它改成K1个节点这里的K是分类任务的总类别数。多出来的1个节点表示“这个样本是伪造的”。整条路径经过softmax之后前K个节点的概率加在一起就表示这个样本属于某个真实类别的总概率最后一个节点的概率表示这个样本是生成器伪造的概率。这样一来对不同的输入样本损失函数的处理方式就不一样有标签的真实样本前K个节点参与分类交叉熵计算。它不仅要被判定为“真”还要被分到正确的类别里。无标签的真实样本不关心它属于哪一个具体类别只把最后一个节点拿来计算真假损失。理想情况下最后一个节点的概率应该低因为它是真实样本。生成器生成的样本只关心最后一个节点理想情况下概率应该高也就是要能被判别器识别为伪造。通过这种方式所有的无标签真实样本虽然没有类别标签但它们都参与了真假判别分支的训练而真假判别和类别判断共享同一套特征提取器。所以无标签样本在训练过程中实际上间接影响了特征提取器的更新方向这就实现了半监督的目的。2.3 为什么共享同一个判别器而不是拆成两个网络有人可能会想我能不能单独训练一个GAN把训练好的判别器拿去提取特征再接一个分类头这种做法不是不行但效果通常不如SGAN端到端一起训。原因在于单独训练的GAN其判别器只优化真假判别目标学到的特征虽然在分布层面有区分度却在类别边界上可能模糊。比如判别器可能只学会了“真实照片”和“生成照片”的区分但对“猫”和“狗”的类别边界并不敏感。而SGAN在训练过程中分类分支的损失会持续加上类别约束迫使特征提取器的中间表征同时满足两个任务的需求。反过来真假分支的对抗压力又让特征不会过早坍缩成纯分类特征保持了对数据分布的整体感知能力。这种协同关系是SGAN最重要的设计哲学。理解这一点之后再去看它的损失函数思路会清晰很多。3. 损失函数与训练目标从公式到代码的一次性理顺3.1 三部分损失的职责划分SGAN的损失函数可以分成三块监督分类损失、无监督真假损失、生成器对抗损失。这三块各有各的职责但都作用在同一套网络上只是作用的样本子集不同。监督分类损失只作用在有标签样本上让判别器输出的前K维概率分布尽量接近真实标签。这一块和普通分类网络的交叉熵没有本质区别唯一的特殊之处是它仅仅使用了全部数据中很小的一部分。无监督真假损失作用在所有真实样本和所有生成样本上。对于真实样本我们希望判别器判定其为真实的也就是最后一个节点的概率低对于生成样本我们希望判别器判定其为伪造的也就是最后一个节点的概率高。这一块相当于把标准GAN的二分类损失搬了过来只不过类别的语义从“真假”变成了“不是伪造/是伪造”。生成器对抗损失则完全是另一套梯度方向。生成器想让判别器把假样本判定为真也就是希望最后一个节点的概率低。注意这与判别器训练时的目标正好相反所以训练是交替进行的判别器和生成器在拔河。3.2 判别器的损失怎么算用符号来表示会更清楚。假设判别器对样本x输出的是一个K1维向量经过softmax后前K维对应真实类别的概率第K1维表示“伪造”的概率。对有标签样本x, y监督分类损失定义为L_supervised -log p(y c | x)对所有真实样本包括有标签和无标签真假损失中真实部分定义为L_real -log(1 - p(y K1 | x))对所有生成样本真假损失中伪造部分定义为L_fake -log p(y K1 | G(z))判别器的总损失就是这三项加权求和。通常情况下监督损失和无监督损失的权重都在1附近不需要特别精细地调节。这里有一个容易混淆的点标准GAN中真实样本的损失是 -log D(x)生成样本的损失是 -log(1 - D(G(z)))。在SGAN里D(x) 的语义被替换成了“不是伪造”的概率也就是 1 - p(yK1|x)。所以真实样本的损失写成 -log(1 - p(yK1|x))生成样本的损失写成 -log p(yK1|G(z))。逻辑上完全对应只是符号形式不同。3.3 生成器的对抗损失与Feature Matching生成器的对抗损失很直接就是希望判别器对生成样本输出“不是伪造”的概率高也就是把最后一个节点的概率压低。写成公式就是L_G -log(1 - p(y K1 | G(z)))这个概念上和标准GAN的生成器损失一致。但在实践中我强烈建议额外加一个 Feature Matching 损失尤其在分类任务里。Feature Matching的思路是不让生成器只盯着判别器的最终输出进行优化而是要求生成样本经过判别器中间层得到的特征统计量尽量接近真实样本的特征统计量。具体做法是在判别器某个中间层通常是最后一个卷积层或者全连接层之前提取特征向量分别计算当前batch里真实样本特征和生成样本特征的均值然后让这两个均值向量的均方误差尽量小。这个额外的损失项可以写成L_feat || E[ f(x_real) ] - E[ f(G(z))] ||^2为什么要加这一项因为对抗损失在训练中经常出现信号退化的情况尤其是判别器变得太强之后生成器收到的梯度会变得破碎甚至无意义。Feature Matching相当于给了生成器一个更平缓、更稳定的优化目标让它的输出在特征统计层面接近真实样本而不是只赌判别器最后一层的输出。我在实验里加了这一项之后生成图像的多样性和训练稳定性都有明显改善。3.4 用代码把整个损失写出来下面给一段PyTorch风格的伪代码展示SGAN核心损失是怎么组织的。这里的模型输出和输入我已经做了简化但逻辑是完整可跑的。# 假设 D(x) 的输出 shape: (batch, K1) # logits_class: 前K维用于分类 # logits_fake : 最后一维用于真假判别 def sgan_loss(D, G, real_labelled, labels, real_unlabelled, z, K): # 判别器输出 logits_l D(real_labelled) # 有标签真实样本 logits_u D(real_unlabelled) # 无标签真实样本 logits_f D(G(z)) # 生成样本 # 1. 监督分类损失只对有标签样本 L_sup CrossEntropy(logits_l[:, :K], labels) # 2. 无监督真假损失真实样本的最后一个节点概率要低 L_real BCEWithLogits(logits_u[:, K:], zeros_like(logits_u[:, K:])) # 3. 无监督真假损失生成样本的最后一个节点概率要高 L_fake BCEWithLogits(logits_f[:, K:], ones_like(logits_f[:, K:])) # 判别器总损失 L_D L_sup L_real L_fake # 生成器损失让生成样本被判定为“不是伪造” L_G BCEWithLogits(logits_f[:, K:], zeros_like(logits_f[:, K:])) return L_D, L_G注意前K维的 logits 和最后一维的 logits 是同一个输出张量的两部分。在实现上判别器最后一层全连接层直接输出 K1 个节点即可不需要额外加结构。唯一要注意的是分类损失用的 logits 应该是未经过 softmax 的原始输出而真假损失我习惯直接对最后一维做 BCELoss这样梯度更稳定调试时也容易观察数值。4. 实操落地网络结构、训练流程与关键超参4.1 生成器与判别器的结构参考SGAN对网络结构没有硬性要求完全可以用常用的图像分类 backbone 来做判别器。我建议判别器保持“卷积下采样 全局特征”的经典结构最后一层输出维度改成 K1。常见的做法是几个卷积块每个块包含卷积层、批量归一化、LeakyReLU激活然后在最后接一个全局平均池化或者直接把特征图展平接一个全连接层到 K1 维。生成器方面如果数据是图像用标准的反卷积堆叠或者上采样结构就行。比较关键的一点是在SGAN里我很少给生成器输入类别条件而是让它无条件生成。因为SGAN的关注点是特征学习而不是条件生成给生成器加类别条件反而会把问题带偏。但要注意无条件生成的生成器在训练初期会产生大量质量很差的样本这可能反而干扰判别器的特征学习。解决方法是训练初期调低生成样本在无监督损失中的权重或者先让判别器在真实数据上预热几百步。还有一个容易被忽视的点判别器里尽量不要用 Dropout尤其是监督分支。因为 Dropout 会增加输出的随机性在对抗训练里会让判别器的置信度不稳定。我一般用批量归一化来做正则效果更可控。4.2 训练流程与更新节奏SGAN的训练流程是交替更新判别器和生成器。但和标准GAN有些不同我推荐每训练一轮判别器时有标签样本和无标签样本要分开组织mini-batch不要混在一个batch里。原因很简单有标签样本的监督损失和无标签样本的真假损失在backward的时候虽然都是对判别器求梯度但它们的量级可能不一样。如果混在一个batch里分类分支的梯度很容易被真假分支淹没尤其是标签量非常少的时候。一个稳妥的做法是每个batch里取一部分有标签样本、一部分无标签样本分别计算两个损失然后加在一起更新判别器。生成器则每更新1次或者每更新2-3次判别器后更新1次这个比例可以视稳定性调整。我在实验里一般用1:1的更新比例但如果判别器损失掉得特别快而生成器损失不降就会改成判别器每更新1次、生成器更新2次。整个训练过程建议跑够充分的轮次。半监督任务不会像纯GAN那样几十个epoch就出效果因为分类分支需要足够多的特征学习过程。我的经验是把训练轮次设成普通全监督训练的1.5到2倍让无标签样本的信息有时间充分渗入特征提取器。4.3 超参配置参考表这里给一份我在实验里常用的参数配置可以直接作为起点再根据你的数据规模调整。参数推荐值说明优化器Adam判别器和生成器都用Adam学习率0.0002判别器可以略低于生成器比如0.0001Beta1 / Beta20.5 / 0.999Beta1设为0.5是GAN训练常见选择更稳Batch Size64有标签和无标签分开组织但总batch保持32-128每类标注数量10-100超过100后SGAN优势会下降接近全监督判别器更新次数1若生成器跟不上可调小判别器更新频率生成器更新次数1-2稳定性差时调大Feature Matching权重1.0生成器损失的附加项标签平滑0.9 / 0.1对真实样本的标签做平滑抑制判别器过度自信关于学习率我想多说一句。如果训练过程中出现判别器损失一路降到接近0、生成器损失不降反升的情况第一件事就是调低判别器的学习率或者调低判别器更新频率。相反如果生成器很快就生成出看起来不错的图但分类准确率上不去那可能是判别器学得太弱可以适当提高判别器学习率或者把更新比例调回1:1。4.4 怎么评估训练效果SGAN的训练效果要从两个维度看分类质量和生成质量。分类质量直接用测试集准确率衡量。这部分和普通分类模型一样把判别器接一个softmax取前K维的最大值作为预测类别就行。不需要额外改动。生成质量则用来辅助判断训练是否正常。一个健康的SGAN训练过程生成图像应该逐渐从模糊变成清晰同时类别多样性应该比较丰富。如果生成图像始终单调比如只有某一类风格的样本说明生成器可能处于崩溃状态需要检查特征匹配和标签平滑的设置。还有一个经常被忽略的指标判别器输出分布。训练到后期对真实样本最后一维的输出概率应该保持在一个合理区间比如0.1到0.4之间而不是逼近0。如果对所有真实样本都输出接近0说明判别器对真假判断信心极高它的梯度对生成器已经没什么帮助了。5. 训练中的坑与排查思路半监督GAN最容易翻车的地方5.1 判别器太强导致生成器梯度消失这是SGAN训练里最常见的灾难。现象是训练到中途判别器对真实样本输出“真”的概率极高对生成样本输出“假”的概率也极高生成器不管怎么更新loss都纹丝不动。出现这个情况本质上是因为生成器生成的样本和真实分布差距太大判别器根本不需要学什么精细特征就能把它们区分开。于是生成器拿到的梯度信号里几乎不包含“如何改进样本才能更像真实数据”的信息。我的解决办法有几个按优先级来试。第一调低判别器学习率让它的进化速度慢下来给生成器追赶的时间。第二在生成器损失里加上特征匹配给生成器一个更容易落地的优化目标。第三如果还在崩溃就把生成器更新次数提高到判别器的2倍甚至3倍让生成器有更多机会在固定判别器的情况下调整自己。5.2 标签太少时分类分支过拟合当每类只有十几二十张标注样本时监督分类损失非常容易过拟合。现象也很典型训练集上的分类准确率很快接近100%但测试集准确率还是原地踏步甚至在小数据集上出现震荡下降。这个问题不能靠多加训练数据解决因为标注预算就摆在那里。我常用的一个规避办法是在训练前半段先弱化监督分类损失的权重让真假分支带跑特征提取器等到特征稍微稳定之后再把分类损失的权重提上去。相当于先让网络学会“什么东西是真实的”再学“这个东西属于哪一类”。这个先后顺序非常重要我踩过几次坑一开始同时开两个分支训练分类分支完全带偏了特征提取器后面再怎么调都救不回来。另外给有标签样本加数据增强也非常有效。随机裁剪、翻转、颜色抖动这些操作虽然不能产生全新的类别样本但能极大地扩展标注样本的特征覆盖范围。尤其是图片分类任务增强后的分类监督损失更平滑过拟合的速度会明显变慢。5.3 模式崩溃与类别坍塌模式崩溃是GAN的老问题SGAN里也一样会发生。表现是生成器只输出有限的几种“安全”样本这些样本确实能骗过判别器的真假判断但没有覆盖整个数据分布的多样性。模式崩溃对生成任务的影响是损失了多样性对SGAN分类任务的影响更隐蔽如果生成器只生成很小范围的样本判别器的真假分支就会把注意力集中在一个窄区域分类分支的特征学习也跟着失去全局视野。解决模式崩溃我常用两个办法。一个是给判别器加 Mini-batch Discrimination 的变体思路让判别器在判断真假时参考当前batch内其他样本的特征差异这样生成器如果生成重复样本判别器会更容易识别。另一个是给生成器引入类别条件输入让噪声向量变成噪声类别向量强制生成器为每个类别都产生样本这种方法能有效防止生成器偷懒只学一类样本。要注意后者会引入额外的监督信号需要在实现上把类别条件一起处理。5.4 真假分支与分类分支互相干扰最后一个坑也是SGAN容易让人困惑的地方两个分支共享特征提取器理论上应该互帮互助但实践里会互相干扰。典型情况是分类分支希望特征空间里类别界限清晰而真假分支希望特征空间里真实数据和生成数据界限清晰当生成器质量不高时真假分支会把大量注意力放在“区分生成样本和真实样本”上这些特征对于分类任务其实是不必要的甚至有害。遇到这种干扰我一般会调整两个分支损失的收敛节奏。具体操作是监控两个分支的loss曲线如果真假分支的loss下降太快分类分支的loss却一直震荡说明真假分支主导了训练。这时可以临时调低无监督真假损失的权重或者给生成器一个更强的特征匹配约束让生成样本尽快靠近真实分布。当生成样本的质量上去之后真假分支就不需要过度关注区分度了两个分支自然恢复平衡。另外还有一个容易踩的小细节批量归一化在判别器里的使用方式。在SGAN里判别器要同时处理真实样本和生成样本而批量归一化的统计量是按batch计算的。如果真实样本和生成样本在同一个batch里混合均值和方差会被两者一起拉偏。我建议在判别器里把真实样本和生成样本分batch计算或者用独立统计的归一化方案否则训练过程会平添很多随机波动。我自己训练SGAN的体会是它不是一个“开箱即用永远稳定”的模型需要你对损失曲线保持敏感随时根据指标调整权重和更新节奏。但它带来的回报也很实在在标注量只有全监督十分之一甚至二十分之一的情况下能把分类准确率拉到一个相当可用的水平。如果你的项目正好卡在标注成本上强烈建议先在小规模数据集上把SGAN跑通再逐步放大到正式业务里。