☰
半监督生成对抗网络SGAN:用无标签数据解决图像分类标注难题
2026/10/21 18:20:48 网站建设 项目流程

做图像分类项目的朋友应该都有这种体验:数据攒够了,但标注跟不上。尤其是刚起步的小团队,一张张打标签的人力成本和时间成本,往往比训练模型本身贵得多。这时候半监督生成对抗网络(SGAN)就派上用场了。它属于生成对抗网络家族里比较特别的一支,核心目标不是单纯生成以假乱真的图片,而是借助生成器的压力,逼着判别器学到更强的特征表示,从而在标注样本非常少的情况下,仍然把分类任务做得够好。

我最早接触SGAN是在一次线上竞赛里,训练集有几万张图,但只有几百张带完整类别标注。当时试过先预训练再微调、也试过伪标签,效果一般。后来把SGAN跑通之后,才真正理解了什么叫“用一个无监督任务去辅助有监督任务”。这篇文章会把SGAN的原理、损失函数、训练流程和踩坑经验完整讲一遍,适合正在做图像分类、但标注预算有限的读者,也适合想搞懂“GAN除了生成图片还能干什么”的人。

1. 先搞懂SGAN的应用场景:标注不够用的时候它为什么香

1.1 标注成本,才是真实项目里最大的瓶颈

在真实项目里,模型结构、训练框架往往都有成熟的现成方案,真正卡住进度的是数据标注。一个二分类任务或许还能靠规则扛一扛,但一旦类别上升到十类、几十类,标注质量直接影响模型上限。有经验的朋友应该都有感触:花一周时间清洗和标注数据,效果可能比换一个更强的 backbone 还明显。可问题是,标注资源永远是有限的,尤其是垂直领域,还需要有专业背景的人来标,成本更高。

半监督学习的出发点就是赌一个假设:无标签数据里包含大量关于数据分布的信息,虽然它没有告诉你它是什么类,但它能告诉你“这一类大概长什么样”。如果能把这些信息榨出来,哪怕每类只有几十个标注样本,也有机会逼近全监督的效果。SGAN就是沿着这个思路走的一条路,只不过它用来榨取无标签信息的方式比较特别——不是直接做一致性正则或者自训练,而是借助一个生成器,让整个系统在对抗博弈中自动学到有用的特征。

1.2 为什么GAN也能做半监督

很多人听到“生成对抗网络”,第一反应是生成图片、风格迁移、超分辨率。但GAN真正有价值的地方,不只是生成结果本身,而是判别器被迫学习到的数据分布特征。生成器为了让判别器相信假样本是真的,会不断模仿真实数据的细节;判别器为了分辨真假,也必须在特征层面抓住那些“只有真实数据才具备”的统计规律。这些特征天然适合作为分类任务的基础。

SGAN的核心思路,就是把判别器改造成一个同时又做真假判断、又做类别分类的网络。对于一个没见过标签数据的人,他可能没法说出某张图具体是哪一类,但他能判断这张图是不是“像这个数据集里的东西”。反过来,如果他能精确判断真假,说明他对数据分布已经有很深的理解。这种理解迁移到分类上,就是免费的标注信息。所以SGAN做的事情可以概括为:让真假判别和分类共享同一个特征提取器,互为补充,互相增强。

对比简单粗暴地把一个分类器和一个GAN拼在一起,共享网络的好处在于梯度信息是融合的。分类分支的梯度会告诉特征提取器哪些特征是类间差异大的,真假分支的梯度会告诉它哪些特征是整体分布特有的,两者作用在同一个特征空间上,得到的表征既保留了类间判别性,又保留了分布真实性。

2. 核心思想拆解:判别器怎么从二分类变成K+1分类

2.1 标准GAN里判别器在做的事

先回顾一下标准GAN。生成器输入一个随机噪声向量,输出一张图像。判别器输入一张图像,输出一个标量,表示“这张图是真的”的置信度。训练的时候,判别器努力把真实图像判定为真、把生成图像判定为假;生成器努力让自己的输出被判定为真。最后达到的平衡状态,就是生成器学会了生成以假乱真的样本。

这个过程中的关键点是:判别器最终只是一个二分类器,它输出的是一个连续值。它只关心“真还是假”,完全不关心“这是猫还是狗”。但是,为了把猫的照片和生成器的假照片区分开,它必须学会猫的毛发纹理、轮廓结构等细节特征。这些特征不会出现在输出标量里,而是藏在网络中间层的特征图里。SGAN的思路很简单:既然中间层特征已经包含了这么多关于数据分布的信息,不如在最顶部分出K个分支,让这个特征提取器顺便把类别也分出来。

2.2 SGAN对判别器的关键改动

SGAN的改动看起来很轻量,但意义很深。原版判别器最后一层输出1个节点,SGAN把它改成K+1个节点,这里的K是分类任务的总类别数。多出来的1个节点表示“这个样本是伪造的”。整条路径经过softmax之后,前K个节点的概率加在一起,就表示这个样本属于某个真实类别的总概率;最后一个节点的概率,表示这个样本是生成器伪造的概率。

这样一来,对不同的输入样本,损失函数的处理方式就不一样:

  • 有标签的真实样本:前K个节点参与分类交叉熵计算。它不仅要被判定为“真”,还要被分到正确的类别里。
  • 无标签的真实样本:不关心它属于哪一个具体类别,只把最后一个节点拿来计算真假损失。理想情况下,最后一个节点的概率应该低,因为它是真实样本。
  • 生成器生成的样本:只关心最后一个节点,理想情况下概率应该高,也就是要能被判别器识别为伪造。

通过这种方式,所有的无标签真实样本虽然没有类别标签,但它们都参与了真假判别分支的训练,而真假判别和类别判断共享同一套特征提取器。所以无标签样本在训练过程中,实际上间接影响了特征提取器的更新方向,这就实现了半监督的目的。

2.3 为什么共享同一个判别器,而不是拆成两个网络

有人可能会想:我能不能单独训练一个GAN,把训练好的判别器拿去提取特征,再接一个分类头?这种做法不是不行,但效果通常不如SGAN端到端一起训。

原因在于,单独训练的GAN,其判别器只优化真假判别目标,学到的特征虽然在分布层面有区分度,却在类别边界上可能模糊。比如判别器可能只学会了“真实照片”和“生成照片”的区分,但对“猫”和“狗”的类别边界并不敏感。而SGAN在训练过程中,分类分支的损失会持续加上类别约束,迫使特征提取器的中间表征同时满足两个任务的需求。反过来,真假分支的对抗压力又让特征不会过早坍缩成纯分类特征,保持了对数据分布的整体感知能力。

这种协同关系是SGAN最重要的设计哲学。理解这一点之后,再去看它的损失函数,思路会清晰很多。

3. 损失函数与训练目标:从公式到代码的一次性理顺

3.1 三部分损失的职责划分

SGAN的损失函数可以分成三块:监督分类损失、无监督真假损失、生成器对抗损失。这三块各有各的职责,但都作用在同一套网络上,只是作用的样本子集不同。

监督分类损失只作用在有标签样本上,让判别器输出的前K维概率分布尽量接近真实标签。这一块和普通分类网络的交叉熵没有本质区别,唯一的特殊之处是它仅仅使用了全部数据中很小的一部分。

无监督真假损失作用在所有真实样本和所有生成样本上。对于真实样本,我们希望判别器判定其为真实的,也就是最后一个节点的概率低;对于生成样本,我们希望判别器判定其为伪造的,也就是最后一个节点的概率高。这一块相当于把标准GAN的二分类损失搬了过来,只不过类别的语义从“真假”变成了“不是伪造/是伪造”。

生成器对抗损失则完全是另一套梯度方向。生成器想让判别器把假样本判定为真,也就是希望最后一个节点的概率低。注意,这与判别器训练时的目标正好相反,所以训练是交替进行的,判别器和生成器在拔河。

3.2 判别器的损失怎么算

用符号来表示会更清楚。假设判别器对样本x输出的是一个K+1维向量,经过softmax后,前K维对应真实类别的概率,第K+1维表示“伪造”的概率。

对有标签样本(x, y),监督分类损失定义为:

  • L_supervised = -log p(y = c | x)

对所有真实样本(包括有标签和无标签),真假损失中真实部分定义为:

  • L_real = -log(1 - p(y = K+1 | x))

对所有生成样本,真假损失中伪造部分定义为:

  • L_fake = -log p(y = K+1 | G(z))

判别器的总损失就是这三项加权求和。通常情况下,监督损失和无监督损失的权重都在1附近,不需要特别精细地调节。

这里有一个容易混淆的点:标准GAN中,真实样本的损失是 -log D(x),生成样本的损失是 -log(1 - D(G(z)))。在SGAN里,D(x) 的语义被替换成了“不是伪造”的概率,也就是 1 - p(y=K+1|x)。所以真实样本的损失写成 -log(1 - p(y=K+1|x)),生成样本的损失写成 -log p(y=K+1|G(z))。逻辑上完全对应,只是符号形式不同。

3.3 生成器的对抗损失与Feature Matching

生成器的对抗损失很直接,就是希望判别器对生成样本输出“不是伪造”的概率高,也就是把最后一个节点的概率压低。写成公式就是:

  • L_G = -log(1 - p(y = K+1 | 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, K+1) # 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 是同一个输出张量的两部分。在实现上,判别器最后一层全连接层直接输出 K+1 个节点即可,不需要额外加结构。唯一要注意的是,分类损失用的 logits 应该是未经过 softmax 的原始输出,而真假损失我习惯直接对最后一维做 BCELoss,这样梯度更稳定,调试时也容易观察数值。

4. 实操落地:网络结构、训练流程与关键超参

4.1 生成器与判别器的结构参考

SGAN对网络结构没有硬性要求,完全可以用常用的图像分类 backbone 来做判别器。我建议判别器保持“卷积下采样 + 全局特征”的经典结构,最后一层输出维度改成 K+1。常见的做法是:几个卷积块,每个块包含卷积层、批量归一化、LeakyReLU激活,然后在最后接一个全局平均池化或者直接把特征图展平,接一个全连接层到 K+1 维。

生成器方面,如果数据是图像,用标准的反卷积堆叠或者上采样结构就行。比较关键的一点是,在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.0001
Beta1 / 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跑通,再逐步放大到正式业务里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询