简介:一份基于MATLAB实现的生成对抗网络(GAN)完整训练代码,面向具备一定深度学习基础、希望快速上手GAN原理与MATLAB工具箱的读者。压缩包共14个文件,以13个.m脚本为主体,覆盖网络初始化、前向/反向传播、梯度更新以及Leaky_ReLU、ReLU、sigmoid等激活函数实现,并附带一张示例灰度图像用于验证生成效果,整包仅65KB,轻量易启动。已有6526人学习使用。代码结构清晰,按数据预处理、模型定义、损失计算、优化器配置与训练循环组织,可帮助读者直观理解生成器与判别器的交替博弈过程;训练完成后可直接利用生成器产出与训练数据分布相似的新样本,适合课程实验、数据增强或图像合成等场景的快速验证。资源还提供详细的代码说明,对参数设置、网络搭建与模型保存方式均有讲解,便于二次开发和深入调优。
1. GAN 生成对抗网络:MATLAB 版本为什么值得上手跑一遍
很多人一提到生成对抗网络(GAN),第一反应是必须用 PyTorch 或 TensorFlow,而 MATLAB 能跑 GAN 这件事,很多人的认知还停留在“可以做仿真”这个层面。实际上 MATLAB 的深度学习工具箱早就支持自定义训练循环和自动微分,完全能承载生成对抗网络的完整训练流程。这套 GAN 的 MATLAB 版本,最大的价值在于不需要搭建 Python 虚拟环境、不需要处理 CUDA 版本问题,装好 MATLAB 就能直接运行。对于做信号处理、图像处理、自动化控制这类工作、日常把 MATLAB 当主力工具的人,或者正在写毕业论文需要快速验证生成模型效果的研究生来说,这套代码提供了一条非常低门槛的路径:从 MNIST 手写数字生成出发,理解对抗训练的完整逻辑,再替换成自己的数据集做迁移。哪怕你之前只在 MATLAB 里做过传统的图像处理,没有碰过神经网络,跟着这套代码走一遍也能把 GAN 的“训练不稳定的玄学”变成可调试的具体问题。
2. 生成对抗网络的对抗逻辑:两个网络的互相博弈与均衡
2.1 生成器与判别器的分工:从空噪声到逼真图像的逼近过程
要理解这套 MATLAB 代码,先得弄清楚 GAN 内部的机制。生成对抗网络由两个独立的神经网络组成:生成器负责把一段随机噪声映射成一张图像,判别器负责判断输入图像是“真实样本”还是“伪造样本”。这个过程可以理解为造假者(生成器)不断改进工艺,检验员(判别器)不断升级检测手段,双方在对抗中共同进步。最终目标是生成器输出的图像能够骗过判别器,让判别器对真假样本的输出概率都收敛到 0.5。
在 MATLAB 实现中,生成器的输入是一个服从正态分布的随机向量,典型维度是 100 维。这个向量经过全连接层和转置卷积层的逐级上采样,最终变成 28×28×1 的灰度图像。判别器则是标准的卷积分类网络,输入为单通道图像,经过几层卷积和池化后输出一个标量概率值。训练时的关键点在于两个网络的更新节奏:通常每训练一次判别器,就训练一次生成器,两者交替进行。
2.2 MATLAB 中的网络定义与自定义训练循环:代码骨架拆解
在 MATLAB 中,实现 GAN 最关键的是自定义训练循环,因为标准的 trainNetwork 函数不支持生成器和判别器的交替梯度更新。需要手动计算每个网络的损失和梯度,再分别调用 adamupdate 函数完成参数更新。以下是这套代码中的核心结构,模拟项目的网络定义部分:
% 生成器网络结构 layersGenerator = [ featureInputLayer(100, 'Name', 'noise_in') % 输入100维噪声 fullyConnectedLayer(7*7*128, 'Name', 'fc1') % 展开为7*7*128的特征图 reluLayer('Name', 'relu1') functionLayer(@(x) reshape(x, 7, 7, 128, []), 'Name', 'reshape1') transposedConv2dLayer(5, 64, 'Stride', 2, 'Cropping', 'same', 'Name', 'tconv1') batchNormalizationLayer('Name', 'bn1') reluLayer('Name', 'relu2') transposedConv2dLayer(5, 1, 'Stride', 2, 'Cropping', 'same', 'Name', 'tconv2') tanhLayer('Name', 'tanh_out') ];这段代码定义了一个典型的 DCGAN 生成器。featureInputLayer 创建了一个输入层,专门接收特征向量而不是图像,它和常见的 imageInputLayer 不同,不需要指定图像尺寸,只需要指定特征维度即可。fullyConnectedLayer 输出的 7×7×128 特征图,是为了匹配后续转置卷积的上采样倍数:从 7×7 经两次 stride=2 的转置卷积,正好变成 28×28。这里有一个值得注意的参数:输出激活函数设计成 tanh 而不是 sigmoid,因为 tanh 的输出范围是 -1 到 1,对应图像像素归一化到 [-1, 1] 后的范围,能提供更强的梯度信号。
2.3 判别器的设计与损失函数细节:为什么输出层只有一个神经元
判别器网络相对简单,但有几个细节直接决定训练成败。判别器输出层采用一个神经元的 sigmoid 或直接输出 logit。如果使用 sigmoid,损失函数可以用交叉熵;而在 MATLAB 的 dlarray 和自动微分环境中,更推荐直接输出 logit,配合自定义的损失计算函数,数值稳定性更好。判别器的卷积层设计通常遵循“卷积越多、通道数越多”的原则,最终把 28×28 的图像压缩成一个标量分数。
损失函数的计算是 GAN 训练中最容易写错的部分。标准 GAN 损失采用最小化交叉熵,二分类中判别器的目标是正确区分真假,生成器的目标是让判别器犯错。实际编码中,生成器和判别器的损失函数不是直接使用 MATLAB 内置的交叉熵函数,而是手动计算:对判别器输出取 log,生成器则使用反向的交叉熵目标。
% 判别器损失计算:真实图像分数趋近1,假图像分数趋近0 dLoss = -mean(log(sigmoid(dReal) + eps) + log(1 - sigmoid(dFake) + eps)); % 生成器损失:让判别器对假图像输出高分,等价于最小化 log(1 - sigmoid(dFake)) 的变体 gLoss = -mean(log(sigmoid(dFake) + eps));代码中给两个 log 都加了 eps 项,防止概率为 0 时 log 计算出现无穷大。这是训练中最常见的数值溢出问题,不加 eps 很容易在前几个 iteration 就出现损失值变成 NaN。生成器损失这里采用了非饱和损失(non-saturating loss),而不是原始论文中的最小化 log(1-sigmoid(dFake)),因为后者在判别器表现太强时梯度会消失,导致生成器无法学习。
3. 运行环境的准备与数据预处理:从下载到第一次训练
3.1 MATLAB 版本与工具箱依赖:哪些函数容易被忽略
这套代码对 MATLAB 版本有一定要求,核心是需要深度学习工具箱中较新的函数支持。建议使用 R2021a 或更高版本,因为代码中大量使用了 dlarray、adamupdate、transposedConv2dLayer 这些深度学习中定义的函数,旧版本要么没有,要么语法不兼容。如果实际使用中遇到 “Unrecognized function or variable ‘dlarray’” 这样的报错,基本可以判断是版本过旧或缺少相应的工具箱。
数据准备部分,代码通常内置了 MNIST 数据集的加载脚本。MATLAB 提供了 helperDownloadMNIST 或者直接通过函数下载数据,首次运行需要联网。如果你在离线环境,可以手动把 MNIST 的四个 .gz 文件下载好,放到代码同目录下的 data 文件夹中。值得注意的是,MNIST 原始数据是 uint8 格式,范围是 0-255,而 GAN 生成器的输出是 tanh 激活,范围是 -1 到 1,所以预处理必须包含归一化步骤:
% 加载MNIST数据集并预处理至[-1,1] images = processImagesMNIST('data'); % 转换为单精度并调整为 28x28x1 的格式 images = single(images); images = (images - 0.5) / 0.5; % 从[0,1]映射到[-1,1]这段预处理代码里有个很容易忽略的细节:processImagesMNIST 函数返回的已经是 4 维数组(宽度×高度×通道×样本数),无需再手动 reshape。归一化到 [-1,1] 这一步不能省,它和生成器最后一层 tanh 的输出范围严格对应。如果用 [0,1] 范围的数据去匹配 tanh 输出,训练过程中损失值会一直波动,生成图像整体偏暗或偏亮。
3.2 训练超参数:学习率、批次大小、噪声维度与迭代轮次
超参数的选择直接决定了训练能否收敛。这套 MATLAB 代码中默认的配置如下表,我根据自己的运行经验做了一些调整建议:
| 参数名 | 代码默认值 | 调整建议 |
|---|---|---|
| 噪声维度 | 100 | 小数据集(如自定义 32×32 图像)可减至 64 |
| 批大小 | 128 | 显存不足时减至 64,但梯度噪声会变大 |
| 生成器学习率 | 0.0002 | 判别器表现过强时适当调低生成器学习率 |
| 判别器学习率 | 0.0002 | 生成器追赶不上时调低至 0.0001 |
| 最大迭代次数 | 10000 | 44800 次迭代可在约 30 分钟完成训练 |
| Adam 参数 | beta1=0.5, beta2=0.999 | beta1 保持 0.5,不要修改为默认的 0.9 |
这里特别说明一下 beta1 的问题:Adam 优化器的默认 beta1 是 0.9,但在 GAN 训练中通常改为 0.5。原因是 0.9 会使梯度估计过于平滑,导致生成器陷入模式崩塌,只生成几种相似的图像。MATLAB 的 adamupdate 函数允许单独指定 beta1,代码如下:
[generator, gradGenAvg] = adamupdate(generator, gradGen, gradGenAvg, iteration, ... learnRateGen, beta1, beta2);其中 gradGenAvg 是梯度的一阶矩估计,需要在循环开始前初始化为与生成器参数维度相同的零数组。刚开始训练时,我一般会先跑 200 次迭代并观察损失曲线,确认损失值没有变成 NaN 或剧烈震荡,再放开来跑全量迭代。这个习惯帮我避免了很多因为数据预处理出错导致的长时间无效训练。
3.3 训练过程中的可视化:如何判断 GAN 是否真的在收敛
这套代码自带训练进度可视化,每经过一定迭代次数,会生成一张当前生成器的输出图像。判断 GAN 是否正常收敛,不能只看损失值——因为两位选手在博弈,损失值不降反升也可能说明对抗在正常进行。更直观的方法是观察生成图像的质量演化:正常情况是从纯噪声逐渐出现轮廓,再慢慢变成有结构的数字形状。如果图像始终停留在模糊色块或者全部变成同一个数字,说明训练出了问题。在代码中,可视化部分通常嵌入在自定义训练循环内,每 100 次迭代调用一次绘图函数。
4. 训练 GAN 的常见问题与调参实践:模式崩塌、损失震荡与梯度消失
4.1 问题一:生成器损失降至零,判别器损失也趋近于零——真实与伪造图像完全可区分
现象:训练到中后期,判别器对真实图像的输出分数一直接近 1,对生成图像的分数一直接近 0,生成器损失不下降,生成图像的质量停滞不前。
原因:判别器能力过强,生成的假图像根本无法骗过它。此时判别器梯度对生成器来说过于“尖锐”,生成器无法从中学习到有效的改进方向。
解决:降低判别器的学习率,或者增加生成器的训练频率。常见做法是每训练 1 次判别器,就训练 2 到 3 次生成器,让生成器有更多机会去追赶。这套 MATLAB 代码中,可以通过修改循环内部判别器和生成器 adamupdate 的执行次数来实现:
% 判别器更新1次 [discriminator, gradDiscAvg] = adamupdate(discriminator, gradDisc, gradDiscAvg, ... iteration, learnRateDisc, beta1, beta2); % 生成器更新2次,追赶判别器的判断标准 for gUpdate = 1:2 [generator, gradGenAvg] = adamupdate(generator, gradGen, gradGenAvg, ... iteration, learnRateGen, beta1, beta2); end4.2 问题二:生成图像多样性不足——模式崩塌的典型特征
现象:生成器产生的图像只有两三种形态,比如 MNIST 里只生成数字 1 和 7,其他数字全部消失。生成图像的单张质量看起来不错,但整体缺乏多样性。
原因:生成器找到了一个能欺骗当前判别器的“捷径”,只需要输出几种固定模式就能获得高分,而判别器也懒得去区分更细的差异。本质上是对抗训练陷入局部均衡。
解决:首先检查判别器结构是否过于简单,特征提取能力不足导致无法区分同类不同数字的差异。其次可以增强生成器的随机性——在生成器输入端除了噪声,还可以添加 Dropout 层,强制生成器不要过度依赖固定路径。如果模式崩塌发生在训练后期,可以考虑临时调高生成器学习率,打破当前的僵局。这套代码中如果使用的是全连接层而不是卷积层,也会更容易出现模式崩塌,因为全连接网络的表达能力有限,更容易走捷径。
4.3 问题三:损失值出现 NaN——数值不稳定与学习率过高
现象:训练进行到几百次迭代后,损失值突然变成 NaN,MATLAB 命令窗口输出警告,后续训练结果全部失效。
原因:最常见的原因是梯度爆炸。Gan 训练的损失函数涉及对概率值的对数操作,当判别器对某类样本的预测概率为 0 或 1 时,log(0) 会产生负无穷,加上梯度反向传播时数值过大,参数更新后网络输出变得无意义。学习率设置过高也是元凶之一,尤其是在前 500 次迭代中。
解决:分三步排查。第一步确认损失计算中是否添加了 eps 常数,必要的话从 1e-8 增大到 1e-6;第二步把生成器和判别器的学习率同时降低一个数量级,从 0.0002 降至 0.00002;第三步,如果仍出现 NaN,需要用梯度裁剪技术限制梯度的最大范数。MATLAB 中可以用 dlupdate 配合手动裁剪:
% 计算梯度后手动裁剪,限制L2范数最大为5 gradGen = dlgradient(gLoss, generator.Learnables); gradGen = clipGradients(gradGen, 5);clipGradients 是自定义函数,计算梯度向量的 L2 范数,如果超过 5,则按比例缩放整个梯度向量。经历过一次凌晨两点跑了一整夜训练、早晨起来发现 NaN 的训练员,都会记得在代码里加上这个保护。
4.4 问题四:训练速度极慢——CPU 环境下运行或用大图片数据集
现象:使用默认 MNIST 配置时,每秒只能迭代 2-3 次,完整训练需要 4 小时以上。或者把自定义数据集替换成 128×128 的高分辨率图像时,迭代一次的时间剧增。
原因:MATLAB 的 GPU 支持需要 NVIDIA 显卡以及对应版本 CUDA,如果在纯 CPU 环境运行,卷积计算的效率非常低。高分辨率图像意味着生成器的转置卷积输出通道数和判别器的卷积计算量都大幅增加。
解决:如果使用 CPU,降低批大小到 32 或 16,同时减小网络隐藏层宽度。另一个有效手段是把输入图像尺寸从 28×28 缩小到 16×16,虽然生成质量下降,但足够验证整个流程是否可运行。如果条件允许,检查 gpuDevice 命令是否可用,MATLAB 检测到 GPU 时会自动执行并行计算,训练速度提升 5 到 10 倍。还有一个小技巧:把图像的读取和预处理从训练循环中挪出来,使用 dlnetwork 的前向传播预计算好常量,避免在每次迭代中重复进行 IO 操作。
5. 把 MNIST 换成自己的数据集:迁移实战与进阶验证
这套 GAN 的 MATLAB 代码最实用的地方在于它把数据集加载、预处理、训练循环、可视化输出做成了模块化结构。从 MNIST 切换到自己的数据只需要改三个地方:图像输入尺寸、数据读取与归一化、生成器网络的输出通道。以下是模拟项目X中更换数据集时的具体做法。
数据读取部分,假设数据是一批 64×64 的 RGB 图像,存放在 data 文件夹下的 .jpg 文件中。MATLAB 图像数据存储可以用 imageDatastore 加载,然后逐个读取并转换为 dlarray。这个过程要注意内存管理,如果图片数量很大,不要一次性把全部数据读入内存,而是使用 minibatchqueue 配合自定义预处理函数。输入网络的图像尺寸变了,生成器的网络结构也要相应调整:噪声维度可以保持 100,但第一层全连接的输出节点需要从 7×7×128 调整为 8×8×512,也就是 32768 维。判别器输入层也需要修改为 imageInputLayer([64 64 3])。
验证一个 GAN 训练效果是否合格,除了看生成图像的主观质量,我一般会额外做一组定量测试:用训练好的生成器生成 10000 张图像,与训练集的真实图像混合,交给一个事先训练好的 MNIST 分类器去分类。如果生成图像的分类准确率能达到 80% 以上,说明生成器不仅模仿了分布,还学到了可分类的结构特征。这个技巧可以用于检验自定义数据集上的训练效果:在你的数据集上训练一个特征提取模型,然后用它来评估生成图像的可识别性。
从那以后我每次在 MATLAB 里训练 GAN,都会强制走一遍排查流程:先跑 200 次迭代观察损失曲线和生成图像,确认没有 NaN 和模式崩塌;再检查生成器输出和真实图像的范围是否一致,最后再看生成图像的多样性。这也换来了一个不能再简单的结论:在 MATLAB 里跑 GAN 并没有想象中那么难,真正的难点是把训练过程的细节读透。希望帮到你。
本文还有配套的精品资源,点击获取