- 深度学习
- 机器学习
【免费下载链接】chainer
A flexible framework of neural networks for deep learning
本教程基于 Chainer 官方仓库中的 DCGAN 示例(examples/dcgan 目录)与配套教程文档(docs/source/examples/dcgan.rst),系统讲解生成对抗网络(GAN)的核心原理、DCGAN 的架构设计要点,以及如何在 Chainer 中从零搭建生成器、判别器、自定义训练器并完成端到端的图像生成训练。读完本文,你将掌握 GAN 的数学机理、DCGAN 的四种关键训练技巧,以及 Chainer 中Chain、Deconvolution2D、StandardUpdater、Trainer等组件的实战用法,能够独立复现并调优一个可运行的 DCGAN 图像生成任务。
1. 引言:GAN 能做什么
生成对抗网络(Generative Adversarial Networks,GAN)是一类用于**生成式建模(generative modeling)**的深度神经网络,最常见的应用场景是图像生成。GAN 系模型已经进入实际产品——例如本教程文档提到的自动上色服务 PaintsChainer 就使用了 GAN 相关技术。
通过本教程你将掌握两件事:
- 生成对抗网络(GAN)的核心原理与数学形式;
- 在 Chainer 框架中实现 DCGAN(Deep Convolutional GAN)的完整步骤。
示例代码位于仓库的 examples/dcgan 目录,包含四个核心文件:
| 文件 | 职责 |
|---|---|
| net.py | 定义Generator、Discriminator网络与add_noise辅助函数 |
| updater.py | 定义交替训练判别器与生成器的DCGANUpdater |
| train_dcgan.py | 入口脚本:参数解析、数据加载、优化器/训练器装配与训练循环 |
| visualize.py | 定义训练过程中周期性输出生成图像的out_generated_image扩展 |
2. 生成对抗网络(GAN)原理
2.1 什么是 GAN
按照 NIPS 2016 的 GAN 教程(Goodfellow 等人),生成模型大体可分为三类:
- 全可见置信网络(Fully Visible Belief Networks,FVBNs);
- 变分自编码器(Variational Autoencoder,VAE);
- 生成对抗网络(GAN)。
与 FVBNs、VAE 不同,GAN不显式建模训练数据的概率分布$p(\mathbf{s})$,而是直接建模一个生成器 $G: \mathbf{z} \mapsto \mathbf{s}$。生成器 $G$ 从隐变量 $\mathbf{z}$ 出发采样得到 $\mathbf{s} \sim p(\mathbf{s})$。除了生成器 $G$ 之外,GAN 还构造一个判别器 $D(\mathbf{x})$,用来区分「来自生成器 $G$ 的样本」与「来自训练数据的真实样本」。
训练过程中:判别器 $D$ 负责分辨真假;生成器 $G$ 则尽力提高判别器 $D$ 犯错的概率,从而让生成样本看起来与训练数据服从同一分布。
GAN 的优点在于采样成本低,且在图像生成任务上表现突出;缺点是无法计算似然$p_{\mathrm{model}}(\mathbf{s})$(因为没有建模任何概率分布),也无法从样本反推隐变量 $\mathbf{z}$。
2.2 GAN 是如何工作的
GAN 依赖两个模型——生成器与判别器。训练的核心目标,是让数据分布 $p(\mathbf{s})$ 与生成器产出的分布($\mathbf{s} = G(\mathbf{z})$)逐步对齐。理想情况下,双方博弈最终会收敛到博弈论意义上的纳什均衡(Nash equilibrium)。
一个经典而直观的类比是「假币制造者与警察」:造假者努力制造足以乱真的假钞,警察努力鉴别真伪;随着警察鉴别能力提升,造假者被迫造出更逼真的假钞,双方能力在对抗中同步提升,最终造假者能造出与真钞几乎无法区分的假币。这正是生成器与判别器关系的生动写照。
数学形式。判别器 $D(\mathbf{s})$ 表示样本 $\mathbf{s}$ 来自数据分布的概率,可写成:
$$D(\mathbf{s}) = \frac{p(\mathbf{s})}{p(\mathbf{s}) + p_{\mathrm{model}}(\mathbf{s})}$$
要让生成分布匹配数据分布,等价于最小化两个分布之间的差异,通常用Jensen-Shannon 散度$D_{\mathrm{JS}}$ 度量。以 $D(\mathbf{s})$ 表达:
$$2 D_{\mathrm{JS}} = D_{\mathrm{KL}}(p(\mathbf{s}),|,\bar{p}(\mathbf{s})) + D_{\mathrm{KL}}(p_{\mathrm{model}}(\mathbf{s}),|,\bar{p}(\mathbf{s}))$$
$$= \mathbb{E}{p(\mathbf{s})} \log D(\mathbf{s}) + \mathbb{E}{p_{\mathbf{z}}} \log (1 - D(G(\mathbf{z}))) + \log 4$$
其中 $\bar{p}(\mathbf{s}) = \frac{p(\mathbf{s}) + p_{\mathrm{model}}(\mathbf{s})}{2}$。$D_{\mathrm{JS}}$ 由判别器 $D$ 最大化、由生成器 $G$(即 $p_{\mathrm{model}}$)最小化,从而让 $G$ 生成的分布 $p_{\mathrm{model}}(\mathbf{s})$ 逼近数据分布 $p(\mathbf{s})$。整个训练可以归结为如下的min-max 目标函数:
$$\min_{G} \max_{D} ; \mathbb{E}{p(\mathbf{s})} \log D(\mathbf{s}) + \mathbb{E}{p_{\mathbf{z}}} \log (1 - D(G(\mathbf{z})))$$
实际训练时,上述 min-max 问题通过交替更新判别器 $D(\mathbf{s})$ 与生成器 $G(\mathbf{z})$求解:先固定生成器、更新判别器若干步,再固定判别器、更新生成器,循环往复,直至两者达到平衡。
2.3 什么是 DCGAN
DCGAN(Deep Convolutional GAN,深度卷积生成对抗网络)由 Radford 等人提出,名字已经说明其要点——使用 CNN(卷积神经网络)作为生成器与判别器的主体结构,其典型架构如下:
GAN 以训练困难著称,而 DCGAN 论文针对这一点提出了四项至关重要的训练技巧:
- 将max-pooling 层替换为更大步幅(strided)的卷积层(判别器用 stride 卷积下采样,生成器用分数步幅/反卷积上采样);
- 判别器中用全局平均池化(global average pooling)替代全连接层,降低参数量、增强稳定性;
- 生成器与判别器中都使用批归一化(batch normalization)层;
- 判别器使用leaky ReLU激活函数,避免梯度消失。
这些技巧正是 Chainer 示例 net.py 中网络设计的直接依据,下文将逐条对照源码说明。
3. 在 Chainer 中实现 DCGAN
3.1 定义生成器模型
首先定义生成器网络。完整的Generator类代码如下(与 examples/dcgan/net.py 一致):
class Generator(chainer.Chain): def __init__(self, n_hidden, bottom_width=4, ch=512, wscale=0.02): super(Generator, self).__init__() self.n_hidden = n_hidden self.ch = ch self.bottom_width = bottom_width with self.init_scope(): w = chainer.initializers.Normal(wscale) self.l0 = L.Linear(self.n_hidden, bottom_width * bottom_width * ch, initialW=w) self.dc1 = L.Deconvolution2D(ch, ch // 2, 4, 2, 1, initialW=w) self.dc2 = L.Deconvolution2D(ch // 2, ch // 4, 4, 2, 1, initialW=w) self.dc3 = L.Deconvolution2D(ch // 4, ch // 8, 4, 2, 1, initialW=w) self.dc4 = L.Deconvolution2D(ch // 8, 3, 3, 1, 1, initialW=w) self.bn0 = L.BatchNormalization(bottom_width * bottom_width * ch) self.bn1 = L.BatchNormalization(ch // 2) self.bn2 = L.BatchNormalization(ch // 4) self.bn3 = L.BatchNormalization(ch // 8) def make_hidden(self, batchsize): dtype = chainer.get_dtype() return numpy.random.uniform(-1, 1, (batchsize, self.n_hidden, 1, 1))\ .astype(dtype) def forward(self, z): h = F.reshape(F.relu(self.bn0(self.l0(z))), (len(z), self.ch, self.bottom_width, self.bottom_width)) h = F.relu(self.bn1(self.dc1(h))) h = F.relu(self.bn2(self.dc2(h))) h = F.relu(self.bn3(self.dc3(h))) x = F.sigmoid(self.dc4(h)) return x在 Chainer 中构建网络有三条约定,示例代码完整遵循了它们:
- 网络类必须继承
chainer.Chain; - 在
__init__初始化器的init_scope():中创建chainer.links的实例(即注册可学习参数); - 在
__call__运算符(本示例命名为forward)中,用chainer.links实例与chainer.functions定义网络的前向连接。
从__init__可见,Generator使用反卷积层L.Deconvolution2D与批归一化层L.BatchNormalization;在forward中,每一层之后都接F.relu激活,只有最后一层(dc4)例外,输出经过F.sigmoid映射到 $[0,1]$ 区间,以匹配像素归一化后的取值范围。
通道数变化规律。L.Deconvolution2D的第一个参数是输入通道数、第二个参数是输出通道数。以默认ch=512为例:dc1从 512 → 256,dc2从 256 → 128,dc3从 128 → 64,dc4从 64 → 3(RGB 三通道),每一层通道数近似减半。若以教程文档中的ch=1024构造Generator,网络形态则与 Radford 论文的原始架构图完全一致。各反卷积层均使用kernel=4, stride=2, pad=1(除最后一层为kernel=3, stride=1, pad=1),这正对应 DCGAN 技巧中「用分数步幅卷积上采样」的做法。
注意:将全连接层输出传入卷积层时,必须保证输入具备「通道 × 高 × 宽」的额外维度。看
forward的第一行:全连接层l0的输出经过F.reshape被重塑为(len(z), ch, bottom_width, bottom_width),即添加了通道、宽、高三维,这正是 DCGAN 论文中把全连接层映射为 $4\times4\times ch$ 特征图的关键一步。make_hidden则负责从 $[-1, 1]$ 均匀分布中采样隐变量 $\mathbf{z}$,形状为(batchsize, n_hidden, 1, 1)。
3.2 定义判别器模型
判别器网络是生成器的「镜像」,代码同样位于 examples/dcgan/net.py:
class Discriminator(chainer.Chain): def __init__(self, bottom_width=4, ch=512, wscale=0.02): w = chainer.initializers.Normal(wscale) super(Discriminator, self).__init__() with self.init_scope(): self.c0_0 = L.Convolution2D(3, ch // 8, 3, 1, 1, initialW=w) self.c0_1 = L.Convolution2D(ch // 8, ch // 4, 4, 2, 1, initialW=w) self.c1_0 = L.Convolution2D(ch // 4, ch // 4, 3, 1, 1, initialW=w) self.c1_1 = L.Convolution2D(ch // 4, ch // 2, 4, 2, 1, initialW=w) self.c2_0 = L.Convolution2D(ch // 2, ch // 2, 3, 1, 1, initialW=w) self.c2_1 = L.Convolution2D(ch // 2, ch // 1, 4, 2, 1, initialW=w) self.c3_0 = L.Convolution2D(ch // 1, ch // 1, 3, 1, 1, initialW=w) self.l4 = L.Linear(bottom_width * bottom_width * ch, 1, initialW=w) self.bn0_1 = L.BatchNormalization(ch // 4, use_gamma=False) self.bn1_0 = L.BatchNormalization(ch // 4, use_gamma=False) self.bn1_1 = L.BatchNormalization(ch // 2, use_gamma=False) self.bn2_0 = L.BatchNormalization(ch // 2, use_gamma=False) self.bn2_1 = L.BatchNormalization(ch // 1, use_gamma=False) self.bn3_0 = L.BatchNormalization(ch // 1, use_gamma=False)虽然Discriminator大体上是Generator的镜像结构,但有三个关键差异:
- 激活函数改用
F.leaky_relu(对应 DCGAN 技巧 4); - 网络更深——在 32×32 输入上先用
3×3, stride=1卷积细化特征,再用4×4, stride=2卷积做下采样(对应 DCGAN 技巧 1 中「用 stride 卷积替代池化」); - 对每层中间输出都叠加随机噪声(见下方
add_noise),提升训练的稳定性。
噪声注入函数同样定义在 examples/dcgan/net.py 中:
def add_noise(device, h, sigma=0.2): if chainer.config.train: xp = device.xp if device.xp is chainerx: fallback_device = device.fallback_device with chainer.using_device(fallback_device): randn = device.send(fallback_device.xp.random.randn(*h.shape)) else: randn = xp.random.randn(*h.shape) return h + sigma * randn else: return hadd_noise只在chainer.config.train为真(即训练阶段)时向特征图添加标准差为sigma=0.2的高斯噪声,推理阶段则原样返回。判别器的forward将每个卷积/批归一化输出先加噪、再过leaky_relu,最终由全连接层l4输出一个标量 logit。注意:这里的BatchNormalization使用了use_gamma=False,即不学习缩放参数 $\gamma$,这是判别器的一个常见简化。
3.3 准备数据集与迭代器
示例默认使用CIFAR-10数据集,通过 Chainer 的数据集工具函数chainer.datasets.get_cifar10获取(实现见 chainer/datasets/cifar.py)。CIFAR-10 是 32×32 的小尺寸自然图像集合,每个样本是一张 RGB 彩色图像;原始图像中每个像素的 R、G、B 分量用单字节无符号整数表示(0~255)。get_cifar10支持通过scale参数把像素值缩放到[0, scale]的浮点区间。
训练脚本中的数据加载代码(examples/dcgan/train_dcgan.py):
if args.dataset == '': # Load the CIFAR10 dataset if args.dataset is not specified train, _ = chainer.datasets.get_cifar10(withlabel=False, scale=255.) else: all_files = os.listdir(args.dataset) image_files = [f for f in all_files if ('png' in f or 'jpg' in f)] print('{} contains {} image files' .format(args.dataset, len(image_files))) train = chainer.datasets\ .ImageDataset(paths=image_files, root=args.dataset)- 默认加载 CIFAR-10 训练集,
withlabel=False表示只取图像不要标签,scale=255.把像素缩放到 $[0,255]$(后续在 updater 中会再除以 255 归一到 $[0,1]$,见 3.5 节); - 也可以通过
--dataset参数传入一个包含 png/jpg 图片的目录,改用chainer.datasets.ImageDataset训练自定义数据。需要留意的是:示例网络假定输入分辨率是32×32,若使用其他分辨率,必须相应修改 net.py 中的网络结构(这一点在 examples/dcgan/README.md 中有明确说明)。
接下来设置迭代器(examples/dcgan/train_dcgan.py):
# Setup an iterator train_iter = chainer.iterators.SerialIterator(train, args.batchsize)这里使用SerialIterator(单进程顺序迭代),每个 minibatch 包含args.batchsize(默认 50)张图像。若需加速数据读取,可替换为MultiprocessIterator或MultithreadIterator(见 chainer/iterators 目录)。
3.4 准备模型与优化器
实例化生成器与判别器,并将模型搬运到指定设备(examples/dcgan/train_dcgan.py):
# Set up a neural network to train gen = Generator(n_hidden=args.n_hidden) dis = Discriminator() gen.to_device(device) # Copy the model to the device dis.to_device(device)随后为两个模型分别创建优化器(examples/dcgan/train_dcgan.py):
# Setup an optimizer def make_optimizer(model, alpha=0.0002, beta1=0.5): optimizer = chainer.optimizers.Adam(alpha=alpha, beta1=beta1) optimizer.setup(model) optimizer.add_hook( chainer.optimizer_hooks.WeightDecay(0.0001), 'hook_dec') return optimizer opt_gen = make_optimizer(gen) opt_dis = make_optimizer(dis)要点:
- 两个模型都使用Adam 优化器(实现见 chainer/optimizers/adam.py),学习率
alpha=0.0002、一阶矩衰减beta1=0.5。beta1=0.5是 DCGAN 论文针对生成器训练给出的经验值——相比默认的 0.9 更小的动量有助于稳定对抗训练;Adam还支持beta2、eps、amsgrad等超参数,可按需调整; - 通过
optimizer.add_hook为每个模型挂载了WeightDecay(权重衰减)hook(见 chainer/optimizer_hooks/weight_decay.py),衰减系数 0.0001; - 参数初始化使用标准差
wscale=0.02的正态分布(chainer.initializers.Normal(0.02)),这也是 DCGAN 论文推荐的初始化方式。
3.5 自定义 Updater:交替训练的关键
GAN 需要两个模型,而 Chainer 预置的默认 updater 通常只接收一个模型,因此必须为 GAN 训练自定义 updater。DCGANUpdater的定义位于 examples/dcgan/updater.py:
class DCGANUpdater(chainer.training.updaters.StandardUpdater): def __init__(self, *args, **kwargs): self.gen, self.dis = kwargs.pop('models') super(DCGANUpdater, self).__init__(*args, **kwargs) def loss_dis(self, dis, y_fake, y_real): batchsize = len(y_fake) L1 = F.sum(F.softplus(-y_real)) / batchsize L2 = F.sum(F.softplus(y_fake)) / batchsize loss = L1 + L2 chainer.report({'loss': loss}, dis) return loss def loss_gen(self, gen, y_fake): batchsize = len(y_fake) loss = F.sum(F.softplus(-y_fake)) / batchsize chainer.report({'loss': loss}, gen) return loss def update_core(self): gen_optimizer = self.get_optimizer('gen') dis_optimizer = self.get_optimizer('dis') batch = self.get_iterator('main').next() device = self.device x_real = Variable(self.converter(batch, device)) / 255. gen, dis = self.gen, self.dis batchsize = len(batch) y_real = dis(x_real) z = Variable(device.xp.asarray(gen.make_hidden(batchsize))) x_fake = gen(z) y_fake = dis(x_fake) dis_optimizer.update(self.loss_dis, dis, y_fake, y_real) gen_optimizer.update(self.loss_gen, gen, y_fake)DCGANUpdater继承自chainer.training.updaters.StandardUpdater(框架实现见 chainer/training/updaters/standard_updater.py),几乎所有基础设施(迭代器管理、优化器管理、序列化等)都由父类提供,这里只需重写__init__与update_core两个方法:
__init__的约定。额外的关键字参数models传入(gen, dis)二元组,并在调用父类构造函数前从中弹出;同时通过关键字参数传递iterator、optimizer、device。特别地,optimizer参数必须是一个字典——两个不同的模型需要两个不同的优化器,因此传入{'gen': opt_gen, 'dis': opt_dis}。在DCGANUpdater内部,通过self.get_iterator('main')访问主迭代器,通过self.get_optimizer('gen')与self.get_optimizer('dis')分别访问两个优化器。
说明:
loss_dis与loss_gen只在update_core内部被调用,无需定义为方法;这里单独抽取出来纯粹是为了提高代码可读性。
update_core的交替更新流程。具体步骤为:
- 取出两个优化器引用(
gen_optimizer、dis_optimizer); - 用
self.get_iterator('main').next()获取下一个 minibatch,交给self.converter拷贝到目标设备,再包成Variable——即x_real = Variable(self.converter(batch, device)) / 255.,除以 255 是把像素值归一化到 $[0,1]$,与生成器sigmoid输出的取值范围对齐; - 让判别器对真实图像打分:
y_real = dis(x_real); - 采样隐变量
z(gen.make_hidden(batchsize)),生成假图像x_fake = gen(z),再让判别器打分y_fake = dis(x_fake); - 先更新判别器:
dis_optimizer.update(self.loss_dis, dis, y_fake, y_real); - 再更新生成器:
gen_optimizer.update(self.loss_gen, gen, y_fake)。
损失函数的形式。这里使用的是 GAN 目标函数的非饱和(non-saturating)形式,以softplus实现:
loss_dis:$L = \frac{1}{N}\sum \big[\mathrm{softplus}(-y_{real}) + \mathrm{softplus}(y_{fake})\big]$。由于 $\mathrm{softplus}(-y) = \log(1+e^{-y}) = -\log \sigma(y)$,等价于判别器在真实样本上输出尽量大、在假样本上输出尽量小(即最小化 $-\log D(x_{real}) - \log(1-D(G(z)))$);loss_gen:$L = \frac{1}{N}\sum \mathrm{softplus}(-y_{fake})$,即最小化 $-\log D(G(z))$,推动生成器让判别器对假样本输出尽量大(骗过判别器)。
两个损失函数还通过chainer.report({'loss': loss}, ...)上报,供Trainer的LogReport、PrintReport扩展记录到日志中——这就是训练输出中gen/loss、dis/loss两列数据的来源。
关于数组模块:在自定义
update_core时,常需用 numpy/cupy 操作Variable底层的array。注意 CPU 上数组类型是numpy.ndarray,GPU 上是cupy.ndarray,无需手写if分支——可用xp = chainer.backend.get_array_module(variable.array)获得正确的数组模块:variable在 GPU 上时xp为cupy,否则为numpy。本示例代码中则直接使用device.xp与self.device,效果相同。
3.6 装配 Trainer 与训练扩展
设置好 updater 后,将其交给Trainer,并挂载一系列扩展(examples/dcgan/train_dcgan.py):
# Setup a trainer trainer = training.Trainer(updater, (args.epoch, 'epoch'), out=args.out) snapshot_interval = (args.snapshot_interval, 'iteration') display_interval = (args.display_interval, 'iteration') trainer.extend( extensions.snapshot(filename='snapshot_iter_{.updater.iteration}.npz'), trigger=snapshot_interval) trainer.extend(extensions.snapshot_object( gen, 'gen_iter_{.updater.iteration}.npz'), trigger=snapshot_interval) trainer.extend(extensions.snapshot_object( dis, 'dis_iter_{.updater.iteration}.npz'), trigger=snapshot_interval) trainer.extend(extensions.LogReport(trigger=display_interval)) trainer.extend(extensions.PrintReport([ 'epoch', 'iteration', 'gen/loss', 'dis/loss', ]), trigger=display_interval) trainer.extend(extensions.ProgressBar(update_interval=10)) trainer.extend( out_generated_image( gen, dis, 10, 10, args.seed, args.out), trigger=snapshot_interval)各扩展的作用:
extensions.snapshot:每snapshot_interval(默认 1000)次迭代保存整个 trainer 的快照snapshot_iter_{iteration}.npz,用于中途断点恢复;extensions.snapshot_object:分别保存生成器与判别器的参数快照(gen_iter_*.npz、dis_iter_*.npz),便于事后单独加载某个模型;extensions.LogReport:每display_interval(默认 100)次迭代记录日志;extensions.PrintReport:在控制台打印epoch / iteration / gen/loss / dis/loss;extensions.ProgressBar:显示训练进度条与预估剩余时间;out_generated_image:来自 examples/dcgan/visualize.py 的自定义扩展,每个快照间隔生成一张 $10\times10$ 网格的生成图像,保存到out/preview/image{iteration:08d}.png(固定seed以保证同一隐变量在不同时期可以对比)。
visualize.py中out_generated_image的实现要点(examples/dcgan/visualize.py):设置随机种子后采样 $10\times10=100$ 个隐变量,在chainer.using_config('train', False)上下文(关闭训练模式,从而跳过add_noise)中让生成器产出图像,把输出乘 255、裁剪到[0, 255]并转成uint8,再按(rows, cols, 3, H, W)重排成网格后保存为 PNG。
另外,训练脚本支持从快照断点续训(examples/dcgan/train_dcgan.py):
if args.resume is not None: # Resume from a snapshot chainer.serializers.load_npz(args.resume, trainer) # Run the training trainer.run()3.7 命令行参数一览
入口脚本 examples/dcgan/train_dcgan.py 通过argparse解析全部参数:
| 参数 | 别名 | 默认值 | 说明 |
|---|---|---|---|
--batchsize | -b | 50 | 每个 minibatch 的图像数量 |
--epoch | -e | 1000 | 遍历数据集的训练轮数 |
--device | -d | '-1' | 设备说明符:ChainerX 设备描述符或整数;非负整数使用指定 id 的 CuPy 数组(GPU),负整数使用 NumPy 数组(CPU) |
--dataset | -i | '' | 图像文件目录;缺省时使用 CIFAR-10 |
--out | -o | result | 结果输出目录 |
--resume | -r | 无 | 从快照恢复训练 |
--n_hidden | -n | 100 | 隐变量 z 的维度 |
--seed | 无 | 0 | 可视化阶段隐变量采样的随机种子 |
--snapshot_interval | 无 | 1000 | 快照保存间隔(迭代数) |
--display_interval | 无 | 100 | 控制台日志打印间隔(迭代数) |
--gpu | -g | 无 | 已废弃参数:GPU id(负数表示 CPU),实际映射到--device |
注意--gpu已被标记为deprecated,推荐使用--device指定设备;--device也支持 ChainerX 设备描述符(如cuda:0、native:0)。
3.8 启动训练
在examples/dcgan目录下执行:
$ pwd /root2chainer/chainer/examples/dcgan $ python train_dcgan.py --gpu 0 GPU: 0 # Minibatch-size: 50 # n_hidden: 100 # epoch: 1000 epoch iteration gen/loss dis/loss ................] 0.01% 0 100 1.2292 1.76914 total [..................................................] 0.02% this epoch [#########.........................................] 19.00% 190 iter, 0 epoch / 1000 epochs 10.121 iters/sec. Estimated time to finish: 1 day, 3:26:26.372445.脚本启动时会先打印设备、minibatch 大小、隐变量维度与总 epoch 数,随后由PrintReport与ProgressBar输出每 100 次迭代的gen/loss、dis/loss以及进度和预估剩余时间。教程文档中的运行环境为单 GPU(--gpu 0,等价于--device 0)。
训练结束后,所有结果保存在输出目录(默认examples/dcgan/result/,由--out指定)下,包括:
snapshot_iter_*.npz、gen_iter_*.npz、dis_iter_*.npz:训练快照与模型参数;log:LogReport产生的训练日志;preview/image*.png:周期性生成的 $10\times10$ 图像网格。
下面的结果是生成器训练 1000 个 epoch 后的输出,教程页面顶部的 GIF 则展示了每 10 个 epoch 的生成图像演变过程:
从图中可以看到,经过充分训练后,生成器产出的图像在色彩、纹理与物体结构上已接近 CIFAR-10 真实样本的分布。
4. 小结与扩展阅读
本教程完整走通了「GAN 原理 → DCGAN 架构设计 → Chainer 代码实现 → 端到端训练」的整条链路:
- GAN 通过生成器与判别器的 min-max 博弈学习数据分布,对应目标函数 $\min_G \max_D \mathbb{E}[\log D(\mathbf{s})] + \mathbb{E}[\log(1-D(G(\mathbf{z})))]$;
- DCGAN 用全卷积结构 + 批归一化 + leaky ReLU 克服 GAN 训练不稳定问题;
- Chainer 侧的关键实现:
Generator/Discriminator(net.py)、交替更新的DCGANUpdater(updater.py)、训练入口(train_dcgan.py)与可视化扩展(visualize.py)。
如果你想更深入:
- 学习 Chainer 网络构建规范,可参考 docs/source/guides 中的模型创建教程;
- 了解
StandardUpdater的默认更新逻辑与序列化机制,可阅读 chainer/training/updaters/standard_updater.py; - 将 CIFAR-10 换成自己的图片数据集,只需用
--dataset指向图片目录(注意保持 32×32 分辨率或同步修改网络结构); - 本示例其余相关资料见 examples/dcgan/README.md。
参考论文
- Goodfellow 等人,NIPS 2016 教程《Generative Adversarial Networks》
- Nash 均衡(博弈论概念)
- Jensen-Shannon 散度(分布差异度量)
- Goodfellow 等人,《Generative Adversarial Networks》(GAN 原始论文)
- Radford 等人,《Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks》(DCGAN 原始论文,本文架构与训练技巧的直接来源)
- 深度学习
- 机器学习
【免费下载链接】chainer
A flexible framework of neural networks for deep learning
相关推荐
Chainer生成对抗网络(GAN)实现:DCGAN、StyleGAN案例详解
想要快速掌握深度学习框架中生成对抗网络的实现方法吗?Chainer作为一款灵活的神经网络框架,为GAN的实现提供了完整而简单的解决方案。本文将详细介绍如何使用C
深度学习机器学习从零实现DCGAN:生成逼真人脸图像完整指南
从零实现DCGAN:生成逼真人脸图像完整指南 你还在为GAN训练不稳定、生成图像模糊而困扰吗?作为深度学习领域最具革命性的生成模型之一,深度卷积生成对抗网络(D
OpCore Simplify:黑苹果 OpenCore EFI 自动配置完整指南
OpCore Simplify:黑苹果 OpenCore EFI 自动配置完整指南 手动改 EFI 改了一周,一个参数写错就白屏,这是很多人做黑苹果配置时的真实
开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考