ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

四种主流深度学习架构实战:CNN、Transformer、GAN与胶囊网络全解析

四种主流深度学习架构实战:CNN、Transformer、GAN与胶囊网络全解析 1. 开篇一次把四种主流架构全部过一遍如果你看过这个系列的第一篇应该还记得我们当时用 Keras 把基础的全连接网络和卷积网络从头到尾搭了一遍核心就是在“动手写代码”这个层面把一些看起来很吓人的概念落到实际可运行的模型上。这一篇我们继续往前走目标很明确——把 CNN、Transformer、GAN 和胶囊网络这四种架构放在同一套 TensorFlow Keras 环境里逐一拆开揉碎搞明白它们各自解决什么问题、网络结构怎么搭、训练时有哪些坑以及哪些场景真的值得用它们。这篇内容最适合两类人一类是已经会用 Keras 写简单模型但面对“Transformer 到底怎么实现”“GAN 训练为什么总是不收敛”“胶囊网络到底是干什么的”这类问题时心里没底的同学另一类是准备面试或做课程设计需要快速理解主流模型内部原理并且能动手写代码的人。我会尽可能把每一步代码背后“为什么这么做”也讲清楚而不是丢一段能跑就完事的代码。先说一句总结性的话这四种架构本质上是在不同维度上做特征提取和表示学习。CNN 擅长局部模式识别Transformer 擅长长距离依赖建模GAN 用对抗博弈驱动生成胶囊网络试图解决 CNN 丢失空间层次关系的问题。理解了这一点你再看它们的代码就会觉得顺很多。2. 四种架构的核心差异与选型思路2.1 CNN在局部窗口里找模式卷积神经网络的核心假设是图像或者信号中相邻区域的特征往往高度相关。比如一张猫脸照片耳朵、眼睛、鼻子这些器官是由局部像素组合而成的不需要看整张图才能识别出耳朵。卷积操作就是用一个小窗口比如 3×3 或 5×5扫过整张特征图在每个局部位置做加权求和把局部模式提取出来。在 Keras 里一个标准的卷积块通常长这样from tensorflow.keras import layers, models def conv_block(x, filters, kernel_size3): x layers.Conv2D(filters, kernel_size, paddingsame)(x) x layers.BatchNormalization()(x) x layers.ReLU()(x) x layers.MaxPooling2D(pool_size(2, 2))(x) return x这个块做了四件事卷积提取特征、归一化稳定训练、激活函数引入非线性、池化降低分辨率。很多人写 CNN 时容易忽略 BatchNormalization 的位置——放激活之前还是之后都有讲究。我自己的习惯是先卷积、再 BN、再激活这样 BN 能更直接地规约卷积输出的分布训练更稳定。选型时要知道 CNN 的边界在哪里它擅长捕捉局部关系但如果两个关键特征距离很远需要堆很多层才能把感受野撑大而且训练难度也会增加。这也是为什么后面会出现 Transformer 和胶囊网络。2.2 Transformer关注全局关系Transformer 和 CNN 最大的不同在于它一开始就不假设数据有局部的空间结构。它的核心机制是自注意力Self-Attention让序列里的每一个位置都能直接和序列里所有其他位置计算相关性。比如处理一句话时“它”指代的是前面的“猫”还是“狗”模型可以通过注意力权重自己学出来不需要像 RNN 那样一步步传递信息。用 Keras 实现一个最简 Transformer 编码器核心代码可以这样写class TransformerBlock(layers.Layer): def __init__(self, embed_dim, num_heads, ff_dim, rate0.1): super().__init__() self.att layers.MultiHeadAttention(num_headsnum_heads, key_dimembed_dim) self.ffn models.Sequential([ layers.Dense(ff_dim, activationrelu), layers.Dense(embed_dim), ]) self.layernorm1 layers.LayerNormalization(epsilon1e-6) self.layernorm2 layers.LayerNormalization(epsilon1e-6) self.dropout1 layers.Dropout(rate) self.dropout2 layers.Dropout(rate) def call(self, inputs, trainingFalse): attn_output self.att(inputs, inputs) attn_output self.dropout1(attn_output, trainingtraining) out1 self.layernorm1(inputs attn_output) ffn_output self.ffn(out1) ffn_output self.dropout2(ffn_output, trainingtraining) return self.layernorm2(out1 ffn_output)这段代码里有一个容易忽略但很关键的细节注意力输出之后要先经过 Dropout再做残差连接和 LayerNorm。顺序颠倒或者漏掉残差Transformer 很容易训练崩掉。第二个细节是 LayerNormalization 是对每个样本的特征维度做归一化而不是像 BatchNormalization 那样跨样本做归一化这在 NLP 和序列任务中更稳定。选型时Transformer 的强项是长距离建模弱点是计算量随序列长度平方增长。处理图像时如果直接展平成序列一张 224×224 的图就有 5 万多个像素点实际运算量会非常惊人所以视觉 Transformer 通常先切 patch 再处理。2.3 GAN生成器与判别器的对抗博弈GAN 的思路特别直观有两个网络互相博弈。生成器负责“造假”判别器负责“验真”。生成器想把随机噪声变成以假乱真的样本判别器则努力分辨真实样本和生成样本。两者不断对抗最后生成器学到的分布会越来越接近真实数据的分布。在 Keras 里一个最简单的 GAN 训练循环包含三个模型生成器、判别器、以及把两者拼接起来的对抗模型。generator models.Sequential([ layers.Dense(256, input_shape(latent_dim,)), layers.LeakyReLU(alpha0.2), layers.BatchNormalization(), layers.Dense(512), layers.LeakyReLU(alpha0.2), layers.BatchNormalization(), layers.Dense(28 * 28 * 1, activationsigmoid), layers.Reshape((28, 28, 1)), ])生成器里我故意用了 LeakyReLU 而不是普通 ReLU这是因为 ReLU 会把负数全部置零导致梯度稀疏生成器容易陷入“只会输出重复样本”的模式坍塌状态。至于 BatchNormalization可以稳定生成器内部特征的分布让训练过程平滑很多。判别器是二分类模型输出一个 0 到 1 之间的真假分数。但训练时有一个细节判别器不能训练得太好否则它一眼就识破所有生成样本梯度消失生成器学不到任何东西。所以经验上判别器和生成器的训练节奏要交替进行判别器每训两步生成器才训一步。2.4 胶囊网络试图给 CNN 补上“空间关系课”胶囊网络这个概念最早是为了解决 CNN 的一个先天不足CNN 通过多次池化不断压缩空间尺寸虽然获得了更大的感受野却也丢失了物体部件之间的精确位置关系。比如一张图里“脸”在“身体”上面这种空间位置关系对 CNN 来说并不敏感只要局部特征都出现了它就可能判定为同一个类别。胶囊网络的做法是用一组向量神经元也就是胶囊来表示一个实体向量的长度表示实体存在的概率向量的方向表示实体的姿态属性位置、角度、尺度等。层与层之间用动态路由算法来传递信息让上层胶囊自动决定接收下层哪些胶囊的信息。胶囊网络代码比 CNN 复杂不少后面我会详细拆解它的关键部分。这里先提醒一句胶囊网络目前更多是实验性应用训练成本高、对超参敏感工业落地的案例远不如 CNN 和 Transformer 多。如果你想在简历或课程设计里展示学习深度它是个很好的话题点但真要上生产环境还是得谨慎。3. CNN 实战从零搭一个能用的图像分类模型3.1 数据准备和增强策略实战部分我用一个模拟的图像分类数据集来演示假设有 10 个类别每类 5000 张 64×64 的彩色图片。数据量不大所以数据增强是必须的。from tensorflow.keras.preprocessing.image import ImageDataGenerator datagen ImageDataGenerator( rotation_range20, width_shift_range0.2, height_shift_range0.2, shear_range0.15, zoom_range0.2, horizontal_flipTrue, fill_modenearest, validation_split0.2, )注意数据增强是在线进行的不是在训练前一次性生成一批增强图片并存储。这样每个 epoch 模型看到的都是不同的变换版本相当于隐式地扩大了数据集。轮转角度不要设置太大20 度以内比较合理过大的角度会让语义本身发生变化。比如数字“6”旋转 180 度就变成“9”标签就错了。3.2 网络结构设计与参数选择我们设计一个浅但有效的 CNNdef build_cnn(input_shape(64, 64, 3), num_classes10): inputs layers.Input(shapeinput_shape) x conv_block(inputs, 32) x conv_block(x, 64) x conv_block(x, 128) x layers.GlobalAveragePooling2D()(x) x layers.Dropout(0.5)(x) outputs layers.Dense(num_classes, activationsoftmax)(x) model models.Model(inputs, outputs) return model这里有几个刻意的设计。第一用 GlobalAveragePooling2D 而不是直接 Flatten 再接全连接层前者能把每个通道的特征图压成一个值大幅减少参数量也降低过拟合风险。第二最后一个卷积块输出 128 个通道然后接一个 0.5 丢弃率的 Dropout。CNN 的参数量主要集中在最后的全连接层和较深的卷积层Dropout 在这里效果最明显。编译参数上优化器我推荐用 Adam初始学习率 1e-3。如果训练过程中验证集准确率长时间停滞可以动态降低学习率lr_scheduler tf.keras.optimizers.schedules.ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-6)这里补充一个我自己踩过的坑如果直接用 ImageDataGenerator 的 flow_from_directory 加载数据一定要把 class_mode 设置成正确的类型分类任务用 ‘categorical’单标签二分类可以用 ‘binary’。我第一次跑的时候没注意默认值不对损失函数计算直接报错排查了半天。3.3 训练监控与过拟合判断训练时建议同时监控训练集和验证集的指标。判断过拟合的一个实用标准是训练集准确率持续上升而验证集准确率在某个 epoch 之后不再增长甚至下降同时训练损失还在下降这就是明显的过拟合信号。应对策略优先级从高到低是增加数据增强强度但不改变语义、增加 Dropout 比例、减小网络容量、加入 L2 正则化。千万不要一上来就换一个更大的预训练模型那只会让过拟合更严重。等到增强和正则化都用到位还欠拟合再考虑提高模型容量。3.4 迁移学习用预训练权重快速提升效果如果数据量更少比如每类只有几百张从头训练 CNN 很容易过拟合这时候迁移学习是首选。用 Keras 内置的预训练模型非常方便base_model tf.keras.applications.ResNet50( include_topFalse, weightsimagenet, input_shape(224, 224, 3)) base_model.trainable False inputs layers.Input(shape(224, 224, 3)) x base_model(inputs, trainingFalse) x layers.GlobalAveragePooling2D()(x) x layers.Dropout(0.3)(x) outputs layers.Dense(num_classes, activationsoftmax)(x)核心细节是 base_model.trainable False也就是冻结预训练权重只训练新增的分类头。这一步能防止预训练权重在训练初期被随机初始化的分类层破坏。等分类头收敛后如果想进一步微调可以把 base_model 设置为可训练并用一个很小的学习率比如 1e-5仅解冻后面几层。这个“先冻结再解冻”的两阶段微调策略实测下来比一步到位效果稳定得多。4. Transformer 实现从位置编码到多头注意力4.1 为什么需要位置编码Transformer 的注意力机制本身完全不感知顺序。它对一组输入向量计算相关性时如果把顺序打乱注意力结果不变。这在处理文本或时序数据时是致命的因为“我爱你”和“你爱我”含义截然相反。位置编码就是给每个输入向量加上位置信息。代码实现上最常见的是用正弦余弦函数生成固定位置编码def positional_encoding(length, d_model): pos tf.range(length, dtypetf.float32)[:, tf.newaxis] i tf.range(d_model, dtypetf.float32)[tf.newaxis, :] angle_rates 1 / tf.pow(10000.0, (2 * (i // 2)) / d_model) angle_rads pos * angle_rates angle_rads tf.where( tf.cast(i % 2, tf.bool), tf.math.cos(angle_rads), tf.math.sin(angle_rads)) return angle_rads[tf.newaxis, ...]这个设计的巧妙之处在于不同频率的正弦波组合可以让模型通过线性变换推断出相对位置关系而且编码向量是固定的、不需要学习省去了额外参数。在最新的实现里也有人用可学习的位置编码效果在某些任务上略好但原理上都是为了把“第几个位置”这个信息注入进去。4.2 多头注意力的内部流程多头注意力简单说就是把输入向量投影到多个子空间分别计算注意力再拼接起来做一次线性变换。这样模型能同时关注不同层面的相关性一个头关注语义相似性另一个头可能关注句法位置关系。手写这个流程比较繁琐但 Keras 的 MultiHeadAttention 层已经封装得很好。关键参数只有两个num_heads 和 key_dim。实际使用时要注意 key_dim 一般取 embed_dim / num_heads保证多头输出拼接后维度能和输入匹配。比如 embed_dim128num_heads8那么每个头的 key_dim 就是 16。在视觉任务里如果直接用纯 Transformer 模型比如 ViT需要先把图像切成固定大小的 patch每个 patch 展平并做线性投影得到 patch embedding。一个 32×32 的图像patch_size4就得到 64 个 patch每个 patch 是一个 16×348 维的向量投影到 embed_dim128 后进入 Transformer 编码器。这个预处理本质上就是把图像转成“序列”用 Transformer 的方式建模 patch 之间的全局关系。4.3 用 Keras 搭建一个最小可用的 ViT下面是一个简化版视觉 Transformer 结构def patch_extract(inputs, patch_size): patches tf.image.extract_patches( imagesinputs, sizes[1, patch_size, patch_size, 1], strides[1, patch_size, patch_size, 1], rates[1, 1, 1, 1], paddingVALID) patch_dim patches.shape[-1] patches tf.reshape(patches, [-1, patches.shape[1] * patches.shape[2], patch_dim]) return patches def build_vit(input_shape(64, 64, 3), patch_size8, embed_dim128, num_heads8, ff_dim256, num_layers4): inputs layers.Input(shapeinput_shape) patches patch_extract(inputs, patch_size) x layers.Dense(embed_dim)(patches) x x positional_encoding(x.shape[1], embed_dim) for _ in range(num_layers): x TransformerBlock(embed_dim, num_heads, ff_dim)(x) x layers.GlobalAveragePooling1D()(x) outputs layers.Dense(10, activationsoftmax)(x) model models.Model(inputs, outputs) return model这段代码可以直接替换前面的 CNN 做图像分类对比实验。我实测过在同样的小数据集上CNN 收敛更快、准确率更高因为 CNN 的归纳偏置局部性、平移等变性在小数据上天然有优势。Transformer 在大规模数据和充足算力下才能体现优势。所以选型时要结合自己的数据规模别盲目跟风。4.4 训练 Transformer 的注意事项Transformer 对超参数很敏感尤其注意以下几点学习率不能太大建议从 1e-4 起步配合 warmup 策略。不看 warmup 直接上大学习率loss 经常直接变成 NaN。残差连接和层归一化是标配不能省。去掉残差连接后深层 Transformer 基本训不动。序列长度越长显存占用越高。训练时如果 OOM优先减小 batch size其次减少 num_heads 或 embed_dim。优化器推荐 AdamW带权重衰减的 Adam可以配合学习率调度器使用。Keras 里可以直接用 AdamW 优化器。5. GAN 实战从 DCGAN 到训练稳定性的调优5.1 DCGAN 结构设计与关键约束DCGAN 是 GAN 家族里最经典的卷积版本用转置卷积Conv2DTranspose把低维噪声逐层放大成图像。一个标准的 DCGAN 生成器在 Keras 里长这样def build_generator(latent_dim128): model models.Sequential(namegenerator) model.add(layers.Dense(7 * 7 * 256, input_shape(latent_dim,))) model.add(layers.BatchNormalization()) model.add(layers.ReLU()) model.add(layers.Reshape((7, 7, 256))) model.add(layers.Conv2DTranspose(128, kernel_size4, strides2, paddingsame)) model.add(layers.BatchNormalization()) model.add(layers.ReLU()) model.add(layers.Conv2DTranspose(64, kernel_size4, strides2, paddingsame)) model.add(layers.BatchNormalization()) model.add(layers.ReLU()) model.add(layers.Conv2DTranspose(1, kernel_size4, strides2, paddingsame, activationtanh)) return model注意输出层用 tanh 激活取值范围在 [-1, 1]所以训练数据也要归一化到 [-1, 1]而不用 [0, 1]。这是我见过很多人犯的错误——生成器输出范围定义和输入数据分布不一致模型怎么训练都停留在“灰色模糊图”。判别器是生成器的镜像用普通卷积逐步压缩空间尺寸最后输出一个标量真假分数。判别器内部不要用 BatchNormalization至少不能对每个样本独立做 BN因为判别器要对单个样本输出真假判断样本之间数据分布差异本来就很大BN 反而会引入干扰。用 LeakyReLU 更合适避免判别器梯度消失。5.2 训练循环的写法GAN 训练不能像普通模型那样直接 fit需要手动管理每一步tf.function def train_step(real_images): batch_size tf.shape(real_images)[0] noise tf.random.normal([batch_size, latent_dim]) with tf.GradientTape() as gen_tape, tf.GradientTape() as disc_tape: fake_images generator(noise, trainingTrue) real_logits discriminator(real_images, trainingTrue) fake_logits discriminator(fake_images, trainingTrue) disc_loss_real loss_fn(tf.ones_like(real_logits), real_logits) disc_loss_fake loss_fn(tf.zeros_like(fake_logits), fake_logits) disc_loss disc_loss_real disc_loss_fake gen_loss loss_fn(tf.ones_like(fake_logits), fake_logits) grads_disc disc_tape.gradient(disc_loss, discriminator.trainable_variables) grads_gen gen_tape.gradient(gen_loss, generator.trainable_variables) optimizer_disc.apply_gradients(zip(grads_disc, discriminator.trainable_variables)) optimizer_gen.apply_gradients(zip(grads_gen, generator.trainable_variables))生成器的损失函数用的是“想让判别器把假图判定为真图”的损失这是一种“欺骗”式的训练方式。值得注意的是在计算生成器损失时通常翻转标签或者使用一种特殊形式比如用最小化 -log(D(G(z))) 代替 log(1-D(G(z)))原因是后者在生成器初期梯度太平滑学习速度太慢。还有一个很实用的设计把判别器的真实损失和生成器损失分开计算不要共用一个损失函数否则梯度回传路径会纠缠在一起代码调试难度直接上升。5.3 模式坍塌GAN 最头疼的问题模式坍塌指生成器只学会生成几种固定样本无法覆盖真实数据的多样性。表现形式是训练过程中生成图片一直重复且种类极少。常见的缓解经验减小学习率尤其生成器的学习率让它别那么快锁死到某个模式上。增加噪声向量的维度让生成器有更多可变化的自由度。考虑用 Wasserstein 损失WGAN它对模式坍塌的抑制作用明显但实现时要额外注意判别器权重的裁剪或梯度惩罚工作量会增加不少。每隔几个 epoch 手动可视化生成结果实时观察多样性变化。不要只靠 loss 曲线判断GAN 的 loss 曲线与生成质量并不是严格对应的。我自己的实操习惯是固定随机种子每训练 5 个 epoch 保存一组生成图。这样既能观察训练进度也能在出现模式坍塌时快速回退到之前的稳定版本。5.4 从 DCGAN 到 WGAN-GP 的迁移如果 DCGAN 训练反复震荡一个成熟的升级路径是改造成 WGAN-GP带梯度惩罚的 Wasserstein GAN。核心改动就两点一是把判别器最后一层的 sigmoid 去掉改成输出未归一化的分数二是损失函数从二元交叉熵换成 Wasserstein 距离的近似形式并且加入梯度惩罚项。梯度惩罚的实现通常是在判别器输入空间随机插值然后计算判别器输出的梯度限制其 L2 范数接近 1。这个操作会占不少计算量但在稳定性和生成质量上的收益值得。6. 胶囊网络向量神经元和动态路由的实现细节6.1 胶囊的核心用向量代替标量传统 CNN 的神经元输出一个标量表示“这个特征存在与否”的概率。胶囊网络的神经元输出一个向量除了表示存在概率向量的长度还能携带姿态信息向量方向。这意味着胶囊既能告诉你“这里有一只眼睛”还能告诉你“眼睛朝哪个方向、有多大”。在 Keras 中实现胶囊层需要自定义 Layer手动实现向量神经元的计算和动态路由。一个常用技巧是用 squash 激活函数把向量长度压缩到 [0,1] 之间同时保持方向不变def squash(vectors, axis-1): squared_norm tf.reduce_sum(tf.square(vectors), axisaxis, keepdimsTrue) scale squared_norm / (1 squared_norm) / tf.sqrt(squared_norm 1e-8) return scale * vectors这个公式看起来复杂其实可以理解为向量越长压缩后长度越接近 1代表实体存在的置信度越高向量越短压缩后越接近 0代表“这个实体不存在”。6.2 动态路由是怎么迭代的动态路由解决的问题是低层胶囊应该把信息发送给哪些高层胶囊。传统神经元是全连接每个上层神经元接收所有下层神经元的加权和胶囊网络则是先让低层胶囊预测“自己应该匹配哪个高层胶囊”然后通过迭代调整耦合系数。算法流程简化为如下几步对每个低层胶囊计算它对所有高层胶囊的预测向量。初始化耦合系数为均匀分布。迭代多轮按当前耦合系数加权求和得到高层胶囊输入squash 后得到高层输出再根据高层输出和低层预测的相似度点积更新耦合系数。迭代结束后高层胶囊作为最终输出传给下一层。这个机制的直观理解是低层胶囊和高层胶囊方向一致就增加它们之间的连接强度方向不一致就减少。通过这种方式网络自动学习了“哪些部件组成哪个整体”的层次关系。6.3 胶囊网络的图像分类实现简化版在 Keras 里实现胶囊网络的完整代码量比较大我这里给一个核心胶囊层和路由的骨架方便理解class CapsuleLayer(layers.Layer): def __init__(self, num_capsules, capsule_dim, routings3): super().__init__() self.num_capsules num_capsules self.capsule_dim capsule_dim self.routings routings def build(self, input_shape): input_capsules input_shape[1] input_dim input_shape[2] self.W self.add_weight( shape[input_capsules, self.num_capsules, input_dim, self.capsule_dim], initializerglorot_uniform, trainableTrue) super().build(input_shape) def call(self, inputs): batch_size tf.shape(inputs)[0] inputs_expand tf.expand_dims(inputs, 2) # [B, in_caps, 1, in_dim] inputs_tiled tf.tile(inputs_expand, [1, 1, self.num_capsules, 1]) # [B, in_caps, out_caps, in_dim] votes tf.einsum(bikj,ikjd-bikd, inputs_tiled, self.W) logits tf.zeros([batch_size, inputs_shape[1], self.num_capsules]) for i in range(self.routings): probs tf.nn.softmax(logits, axis-1) weighted_sum tf.einsum(bik,bikd-bid, probs, votes) capsules squash(weighted_sum) if i self.routings - 1: similarity tf.einsum(bid,bikd-bik, capsules, votes) logits similarity return capsules这段代码的核心在 einsum 运算第一次接触可能不习惯但它能把多维张量的加权求和写得很简洁。如果不熟悉 einsum建议先在文档里系统看一遍因为后面对胶囊网络的调参和改结构都离不开这些维度操作。6.4 胶囊网络的局限和实用建议胶囊网络理论框架很优美但实际使用有几个硬伤一定要提前知道计算量比同尺寸 CNN 大一个数量级。动态路由本身是迭代过程每一步都要做矩阵乘法显存和时间的消耗都不是小数。对超参非常敏感耦合系数的迭代轮数、胶囊维度、正则化系数每一个都可能显著影响最终效果。实现难度高、生态支持少。Keras 内置没有胶囊层第三方实现质量参差不齐部署到移动端或者服务端都要额外做算子适配。所以我的建议是胶囊网络很适合作为学习和研究项目用来展示你对“CNN 表达能力的边界”有独立思考。但如果你要做实际产品老老实实选 CNN 或 Transformer 更稳妥。7. 常见问题与排查技巧实录7.1 问题速查表现象常见原因处理方案训练 loss 为 NaN学习率过大、数据未归一化降低学习率检查输入数据范围GAN 生成图片全是模糊噪声生成器输出激活函数和数据范围不匹配输出用 tanh输入归一化到 [-1,1]Transformer 训练不收敛缺少残差连接或学习率设置过大检查残差结构使用 warmup 调度器CNN 验证集准确率停滞学习率过大或过拟合降低学习率增强数据增强或加 Dropout胶囊网络训练极慢路由迭代次数太多、胶囊维度过大减 routings 到 2降低 capsule_dim训练集准确率高但测试集差过拟合增加数据增强、Dropout 或减小模型容量GPU 显存不足序列过长或 batch 过大减小 batch size用混合精度训练损失曲线震荡剧烈学习率过高或 GAN 训练节奏不稳降低学习率检查判别器和生成器的训练步数比例7.2 我踩过的三个坑第一个坑是关于 BatchNormalization 在 GAN 生成器里的使用。生成器输入是随机噪声如果噪声维度太小比如只有 8 或 16BN 层会因为样本统计量波动太大导致输出极不稳定。后来我把噪声维度加到 128并且从头到尾保留 BN才明显改善。第二个坑是 Transformer 输入维度不匹配。我用 MultiHeadAttention 时一开始直接把 key_dim 设成和 embed_dim 一样大结果维度翻倍后面 Dense 层全乱套。后来才意识到 key_dim 应该是 embed_dim / num_heads而不是 embed_dim 本身。第三个坑是数据增强和标签不一致。我在做图像分类时用了 rotation_range90结果某些类别的语义在旋转 90 度后完全变了比如数字 6 和 9、左箭头和上箭头。这种问题不会导致程序报错但验证集准确率会莫名其妙下降。排查方法也很简单把增强后的样本可视化出来看一遍很多问题一眼就能发现。7.3 一套通用的调试顺序我在训练任何新模型时都会先跑一个小型实验用很少的数据和很少的 epoch 过一遍完整流程。如果小实验能顺利跑通再逐步放大数据量和训练轮数。这样可以把“模型结构错误”和“训练策略不合理”两类问题分开排查。如果训练遇到问题我的排查顺序是先检查输入数据形状和标签维度是否匹配输出层的类别数。再看 loss 函数和最后一层激活函数的搭配是否正确二分类用 sigmoid binary crossentropy多分类用 softmax categorical crossentropy。然后检查梯度是否正常最直接的办法是看前几个 batch 的 loss 是否下降如果完全不降很可能是没有调用 apply_gradients 或者梯度计算错位。最后才考虑学习率、正则化、网络深度等超参数问题。这套顺序帮我在很多调试场合省了大量时间特别是能避免把超参数调了半天、结果发现是前面维度写错的尴尬情况。8. 写在最后的几个实操心得四种架构都跑下来之后我心里最深的体会是会调库和真正理解模型之间有一条巨大的鸿沟。Keras 让搭建模型变得特别简单几行代码就能把 Transformer 编码器调出来但真正遇到问题的时候起决定性作用的还是你对模型内部机制的把握以及你那套成体系的调试方法。如果你现在正准备动手实践我建议的顺序是CNN 一定要先吃透它是后续所有架构的基石。Transformer 则要亲自动手写一遍位置编码和注意力层的流程不用追求一次写完美哪怕参考别人的实现也要自己敲进去、跑通、改改参数看效果变化。GAN 是最让人上头的因为它给了你“创造图像”的即时反馈但一定不要期望第一次训练就稳定多准备些随机种子和可视化代码。胶囊网络适合作为进阶探索不用花太长时间但值得亲手跑一跑感受传统 CNN 的局限在哪里。如果你现在正处于“代码能跑但不懂为什么”的阶段不妨在每跑通一个模型后尝试删掉一个关键组件观察效果变化。比如把 BN 删掉、把残差连接删掉、把耦合系数定死成均匀分布这种“破坏性实验”对理解的提升往往比多看十篇文章都管用。下一篇我可能聊聊如何把这四种架构组合起来做混合模型比如用 CNN 提取底层特征再接 Transformer 做全局建模或者用 GAN 做数据增强来提升小样本分类器的效果。你自己动手跑的时候如果遇到什么奇怪的报错或者反直觉的现象也欢迎在评论里拿出来一起讨论很多坑都是互相踩踩才被填平的。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表