简介这份资源是DCGAN在TensorFlow框架下的完整实现面向希望将生成对抗网络用于图像增强与扩充的开发者与研究者尤其适合医学影像、人脸识别等小样本场景的入门与进阶实践。压缩包共235个文件约164.07MB包含6个Python脚本、2组checkpoint与meta模型文件、162张png与9张jpg示例图像以及js、css、html等前端展示文件结构完整替换data文件夹中的数据集即可直接运行。目前已有1463人学习下载说明其参考价值得到一定认可。资源核心价值在于提供了一套跑通的DCGAN训练与生成流程读者可借此理解生成器与判别器的搭建、模型保存与恢复机制并快速迁移到X射线图像增强等创新应用中同时附带的示例图像与模型文件便于对照验证生成效果适合作为数据扩充方案的起点。1. 从数据不够到样本翻倍DCGAN-tensorflow-master 能解决什么做图像分类或者目标检测的同行大概都经历过这种绝望手头某个类别的样本只有两三百张标注完兴冲冲丢进网络训练集准确率倒是挺好看验证集直接原地踏步。数据不够再好的模型也白搭。这份 DCGAN-tensorflow-master 就是冲着这个痛点来的——它把生成对抗网络里最经典、最容易跑通的 DCGAN 结构用 TensorFlow 实现了一遍代码已经跑通替换数据集就能直接用来做数据增强和扩充。说白了你给它一堆小尺寸图片它帮你生成一批风格相似但又不完全一样的新样本把原本捉襟见肘的数据集撑起来。适合谁适合手上有小样本图像数据集、想快速验证 GAN 增强效果、又不想从零搭网络的从业者。新手能照着步骤跑通熟手能直接改结构、调参数、接自己的数据管线。2. DCGAN 的骨架拆解为什么它比原始 GAN 稳DCGAN 不是简单的“GAN 加卷积”它有一套被验证过的结构约束正是这些约束让训练从玄学变成可复现的工程。这一章先把生成器和判别器的骨架讲清楚再落到代码里逐层对照最后给出替换数据集时真正要动的几个地方。2.1 生成器与判别器的对称设计原始 GAN 用全连接层堆叠训练起来极不稳定判别器太强生成器梯度消失判别器太弱生成器又学不到东西。DCGAN 的核心改动是把全连接换成卷积并且定了几条硬规矩判别器里用步长卷积代替池化生成器里用转置卷积做上采样除了输出层之外每层都接批归一化激活函数生成器用 ReLU、判别器用 LeakyReLU。这套设计的意义在于它把特征提取和上采样都交给了可学习的卷积核而不是固定的池化窗口。判别器用步长卷积下采样等于让网络自己学怎么压缩空间信息生成器用转置卷积逐层放大等于让网络自己学怎么从噪声里“画出”图像结构。批归一化则把每层输入拉回稳定分布避免训练后期梯度爆炸。在代码里生成器的典型结构是这样的def generator(z, is_trainingTrue, reuseFalse): with tf.variable_scope(generator, reusereuse): # z 是 100 维噪声先投影成 4x4x1024 的特征图 net tf.layers.dense(z, 4 * 4 * 1024, use_biasFalse) net tf.layers.batch_normalization(net, trainingis_training) net tf.nn.relu(net) net tf.reshape(net, [-1, 4, 4, 1024]) # 转置卷积上采样4x4 - 8x8 net tf.layers.conv2d_transpose(net, 512, 5, strides2, paddingsame, use_biasFalse) net tf.layers.batch_normalization(net, trainingis_training) net tf.nn.relu(net) # 8x8 - 16x16 net tf.layers.conv2d_transpose(net, 256, 5, strides2, paddingsame, use_biasFalse) net tf.layers.batch_normalization(net, trainingis_training) net tf.nn.relu(net) # 16x16 - 32x32输出层用 tanh 把像素压到 [-1, 1] net tf.layers.conv2d_transpose(net, 3, 5, strides2, paddingsame, use_biasFalse) net tf.nn.tanh(net) return net这段代码里几个参数值得盯住噪声维度z一般取 100太小生成多样性不足太大训练收敛慢转置卷积的卷积核大小取 5、步长取 2、padding 用 same保证每次上采样正好放大两倍输出通道数 3 对应 RGB 图如果你处理的是灰度图就改成 1。批归一化在训练和推理时的行为不同所以is_training这个开关必须传对否则生成结果会飘。判别器则是镜像结构用步长卷积逐层下采样最后接一个 sigmoid 输出真假概率def discriminator(x, is_trainingTrue, reuseFalse): with tf.variable_scope(discriminator, reusereuse): # 32x32 - 16x16 net tf.layers.conv2d(x, 64, 5, strides2, paddingsame) net tf.nn.leaky_relu(net, alpha0.2) # 16x16 - 8x8 net tf.layers.conv2d(net, 128, 5, strides2, paddingsame) net tf.layers.batch_normalization(net, trainingis_training) net tf.nn.leaky_relu(net, alpha0.2) # 8x8 - 4x4 net tf.layers.conv2d(net, 256, 5, strides2, paddingsame) net tf.layers.batch_normalization(net, trainingis_training) net tf.nn.leaky_relu(net, alpha0.2) # 展平后接单神经元输出 net tf.reshape(net, [-1, 4 * 4 * 256]) logits tf.layers.dense(net, 1) return logits判别器第一层故意不加批归一化这是 DCGAN 的惯例因为输入图像本身的分布和生成图像差异大过早归一化反而抹掉判别信号。LeakyReLU 的负斜率取 0.2让负区间也有梯度避免神经元死亡。2.2 损失函数与训练循环的交替节奏DCGAN 的损失还是标准的对抗损失但训练时的交替比例很关键。判别器更新一次生成器更新一次这是最常见也最稳的节奏。如果判别器 loss 掉得太快说明它太强了生成器学不动这时候要么降低判别器学习率要么给判别器加输入噪声。# 判别器损失真样本判 1假样本判 0 d_loss_real tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits( logitsd_logits_real, labelstf.ones_like(d_logits_real))) d_loss_fake tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits( logitsd_logits_fake, labelstf.zeros_like(d_logits_fake))) d_loss d_loss_real d_loss_fake # 生成器损失骗过判别器所以假样本标签设为 1 g_loss tf.reduce_mean( tf.nn.sigmoid_cross_entropy_with_logits( logitsd_logits_fake, labelstf.ones_like(d_logits_fake)))注意生成器的损失是把假样本的标签当成 1 来算意思是“我希望判别器认为这是真的”。这个写法比直接用-log(1-D)更稳定因为梯度不会在判别器自信时消失。优化器方面论文里推荐 Adam学习率 0.0002beta1 取 0.5。beta1 默认是 0.9但 GAN 训练里动量太大会导致震荡调到 0.5 能明显改善收敛。d_optimizer tf.train.AdamOptimizer(learning_rate0.0002, beta10.5) g_optimizer tf.train.AdamOptimizer(learning_rate0.0002, beta10.5) # 注意更新生成器时只优化生成器变量更新判别器时只优化判别器变量 d_train_op d_optimizer.minimize(d_loss, var_listd_vars) g_train_op g_optimizer.minimize(g_loss, var_listg_vars)这里有个容易翻车的点var_list必须显式指定否则 TensorFlow 会把两个网络的变量一起更新训练直接崩掉。我一般会在建图之后打印一下d_vars和g_vars的长度确认两边没有交叉。2.3 替换数据集时要动的三个地方这份代码之所以说“替换数据集可直接用”是因为数据入口被封装得比较干净。你只需要改三个地方数据目录路径、图像尺寸、以及对应的生成器输出通道数。数据读取部分通常是一个load_data函数或者Dataset类里面写死了图片文件夹路径和 resize 尺寸。假设你的数据放在./data/my_dataset/下全是 64x64 的 RGB 图那就把路径和尺寸改掉# 原代码可能是 32x32改成你的实际尺寸 IMG_SIZE 64 CHANNELS 3 DATA_DIR ./data/my_dataset/ # 读取时统一 resize 并归一化到 [-1, 1] def load_images(data_dir, img_size): files glob.glob(os.path.join(data_dir, *.jpg)) images [] for f in files: img Image.open(f).convert(RGB).resize((img_size, img_size)) img np.array(img).astype(np.float32) img (img - 127.5) / 127.5 # 归一化到 [-1, 1] images.append(img) return np.array(images)归一化到 [-1, 1] 是为了配合生成器输出层的 tanh如果你改成 sigmoid 输出那归一化就要改成 [0, 1]两边必须一致否则生成出来的图全是灰蒙蒙的。生成器那边如果输入尺寸从 32 变成 64上采样层数要相应增加一层。32x32 是 4-8-16-32 三次上采样64x64 就需要 4-8-16-32-64 四次。每多一层转置卷积的通道数减半从 512 开始往下排。判别器同理下采样层数跟着输入尺寸走。这里有个经验如果你的数据集样本很少比如少于 500 张判别器层数不要堆太多否则它太容易记住训练样本生成器根本骗不过它。常见做法是判别器比生成器浅一层或者把判别器的 dropout 加上。3. 从零跑通环境、命令与训练监控代码跑通和“能生成有用的图”是两回事。这一章把环境配置、启动命令、以及训练过程中怎么判断模型有没有在正常学习讲清楚最后给一个用生成样本做数据增强的落地流程。3.1 环境依赖与版本匹配TensorFlow 的版本兼容性是第一个坑。这份代码用的是tf.layers和tf.train.AdamOptimizer这套旧 API在 TF 2.x 里默认的 eager 模式下跑不起来。最省事的做法是装 TF 1.15或者用 TF 2.x 的tf.compat.v1兼容模式。# 方案一直接装 TF 1.15推荐改动最少 pip install tensorflow1.15.0 # 方案二TF 2.x 下用兼容模式代码开头加两行 import tensorflow.compat.v1 as tf tf.disable_eager_execution()其他依赖就是常规的 numpy、Pillow、scipy。如果你要用 GPU 加速CUDA 版本要和 TF 对应TF 1.15 对应 CUDA 10.0装错了会报libcudart.so找不到。我一般先用 CPU 跑通流程确认代码没问题再切 GPU免得环境问题干扰调试。3.2 启动训练与关键参数主训练脚本通常叫main.py或者train.py启动命令大概长这样python main.py \ --dataset my_dataset \ --input_height 64 \ --input_width 64 \ --output_height 64 \ --output_width 64 \ --batch_size 64 \ --epoch 50 \ --learning_rate 0.0002 \ --beta1 0.5 \ --train几个参数需要根据你的数据规模调整batch_size在显存允许的情况下尽量大64 是起步128 更稳epoch不是越多越好GAN 训练到后面容易过拟合生成器开始重复输出同样的图一般 30 到 50 轮就能看出效果learning_rate保持 0.0002 不要动这是 DCGAN 论文验证过的值。训练过程中会输出两个 loss判别器 loss 和生成器 loss。健康的训练曲线是判别器 loss 在 0.5 到 1.5 之间波动生成器 loss 缓慢下降但不会归零。如果判别器 loss 迅速掉到 0.1 以下说明它太强了生成器已经骗不过它这时候要么停掉重来要么把判别器学习率调低一个数量级。3.3 用生成样本做数据增强的完整流程训练完之后生成器可以批量出图。这一步的关键是控制生成数量和筛选质量。不是生成多少就用多少低质量的生成样本混进训练集反而会拉低模型精度。# 加载训练好的生成器批量生成样本 saver tf.train.Saver() with tf.Session() as sess: saver.restore(sess, ./checkpoints/model.ckpt) # 生成 1000 张新样本 for i in range(0, 1000, 64): z np.random.normal(0, 1, [64, 100]) fake_images sess.run(generator(z, is_trainingFalse, reuseTrue)) # 反归一化到 [0, 255] fake_images ((fake_images 1) * 127.5).astype(np.uint8) for j, img in enumerate(fake_images): Image.fromarray(img).save(f./generated/{ij}.png)生成完之后我一般会做一轮人工筛选或者用简单的图像质量指标过滤。常见做法是算生成图的清晰度拉普拉斯方差和多样性和训练集的相似度把模糊的、和已有样本几乎重复的剔掉。留下来的样本按 1:1 或 1:2 的比例混进原始训练集重新训练分类器验证集准确率通常能涨几个点。提示生成样本不要一次性全用上先拿 20% 混进去跑一轮对比实验确认有正向收益再加大比例。4. 避坑与排查训练不收敛、生成图糊、显存炸了怎么办GAN 训练的坑大多集中在“不收敛”和“生成质量差”这两类。下面这几条是我在实际替换数据集时反复遇到的按现象、原因、解决三步写清楚。4.1 判别器 loss 迅速归零生成器完全学不动现象训练开始几十步后判别器 loss 掉到 0.01 以下生成器 loss 居高不下生成的图全是噪声。原因判别器太强或者生成器太弱两者容量不匹配。常见于数据集很小但判别器层数堆太多的情况。解决先把判别器的学习率降到生成器的 1/5比如生成器 0.0002判别器 0.00004。如果还不行把判别器的层数减一层或者给判别器的输入加高斯噪声噪声标准差从 0.1 开始试。4.2 生成图像模糊、模式单一现象训练 loss 看起来正常但生成出来的图都是同一张脸的模糊版本或者所有图都灰蒙蒙的。原因模式崩溃mode collapse生成器找到了一个能骗过判别器的“万能样本”就不再探索其他模式了。另一个可能是归一化范围没对齐生成器输出 tanh 在 [-1,1]但数据预处理没归一化到这个区间。解决检查数据归一化代码确保和输出层激活函数匹配。如果是模式崩溃把生成器的噪声维度从 100 提到 128 或 256增加输入多样性或者用 minibatch discrimination让判别器能同时看一批样本惩罚过于相似的生成结果。4.3 显存溢出batch_size 降了还是炸现象ResourceExhaustedError显存不够把 batch_size 从 64 降到 16 依然报错。原因TensorFlow 默认会占满整块显存不是按需分配。另外生成器和判别器的计算图如果没复用变量会重复建图导致显存翻倍。解决在 session 配置里开显存按需增长config tf.ConfigProto() config.gpu_options.allow_growth True sess tf.Session(configconfig)同时检查tf.variable_scope的reuse参数生成器和判别器在训练循环里多次调用时第二次开始必须reuseTrue否则每次调用都新建变量显存直接爆掉。4.4 替换数据集后 loss 正常但生成图全是噪声现象用自己的数据跑loss 曲线和原数据集差不多但生成出来的图完全看不出内容。原因图像尺寸和网络结构不匹配。比如你把 128x128 的图塞进原本为 32x32 设计的网络转置卷积的上采样次数不够最后输出尺寸对不上TensorFlow 可能不报错但结果全乱。解决打印生成器每一层输出后的 shape确认从噪声到最终图像的尺寸变化符合预期。32x32 输入对应三次上采样64x64 对应四次128x128 对应五次以此类推。判别器那边同理下采样次数要和上采样对称。4.5 训练到后期生成样本开始重复现象前 20 轮生成的图还挺多样到 40 轮之后开始出现大量重复样本loss 也不再下降。原因过拟合。生成器把训练集里的样本记住了开始直接“抄”而不是“生成”。解决早停。在验证集上监控生成样本的多样性指标一旦连续几轮不再提升就停掉。或者加 dropout 到判别器给生成器加一点权重衰减。我一般会在训练脚本里每 5 轮保存一次生成样本的缩略图肉眼扫一眼就能判断有没有开始重复。5. 进阶技巧条件 DCGAN 与生成样本的筛选策略基础版 DCGAN 是无条件生成你没法控制它生成哪个类别的图。如果你的数据集有多个类别而且你想按类别扩充那就需要把标签信息喂进去改成条件 DCGANcDCGAN。做法不复杂在生成器的输入噪声后面拼接一个类别 one-hot 向量在判别器的输入图像后面拼接同样的类别向量让两边都知道当前处理的是哪一类。# 生成器输入噪声 类别标签 def generator(z, y, is_trainingTrue, reuseFalse): with tf.variable_scope(generator, reusereuse): # y 是 one-hot 向量拼到 z 后面 z_y tf.concat([z, y], axis1) net tf.layers.dense(z_y, 4 * 4 * 512, use_biasFalse) # ... 后续转置卷积层不变判别器那边把类别向量扩展成和图像同尺寸的特征图然后 concat 到输入图像上def discriminator(x, y, is_trainingTrue, reuseFalse): with tf.variable_scope(discriminator, reusereuse): # 把 one-hot 标签扩展成 32x32xnum_classes 再拼到图像上 y_expand tf.reshape(y, [-1, 1, 1, num_classes]) y_expand tf.tile(y_expand, [1, 32, 32, 1]) x_y tf.concat([x, y_expand], axis3) # ... 后续卷积层不变这样训练完之后你可以指定类别生成样本比如“给我生成 500 张类别 3 的图”而不是随机抽。对于类别不均衡的数据集这个改动非常实用。另一个进阶点是生成样本的筛选。我一般会算两个指标一是用预训练的分类器对生成图打标签置信度低于阈值的直接丢掉二是算生成图和训练集最近邻的欧氏距离距离太近的说明几乎是复制也丢掉。留下来的样本再按类别比例混进训练集。注意筛选阈值不要设太严否则生成样本被砍掉一大半增强效果就没了。置信度阈值从 0.6 开始试距离阈值取训练集类内距离的中位数。从那以后我每次替换数据集跑 GAN都会先把生成器的每层 shape 打印一遍确认尺寸对得上再开训练。这个习惯帮我省掉了至少三次“loss 正常但图全糊”的排查时间。希望帮到你。本文还有配套的精品资源点击获取