当编码器将输入数据提炼成瓶颈层中的紧凑表示后,解码器便登场了。解码器的主要作用是接收这种压缩的、低维的潜在空间表示,并尝试重构原始的高维输入数据。可以把它想象成一次往返的后半段:数据由编码器进行压缩,然后解码器尝试将其扩展回原始形式。重构的数据与原始数据越接近,说明自编码器越能很好地捕获数据的主要特点。解码器结构:扩展表示在自编码器中,解码器的结构常被设计为编码器的镜像,但这只是一个指南而非严格规定。编码器通常使用一系列层来逐步减少数据维度,而解码器则通过一系列层逐步增加维度,以实现数据的重建。例如,如果编码器通过神经元数量为 [输入维度 $ ightarrow$ 128 $ ightarrow$ 64 $ ightarrow$ 潜在维度] 的层来转换数据,那么一个对应的解码器可能会有 [潜在维度 $ ightarrow$ 64 $ ightarrow$ 128 $ ightarrow$ 输入维度] 这样的层。在本章讨论的基础自编码器背景下,这些层通常是全连接层(Keras/TensorFlow 中的 Dense 层,或 PyTorch 中的 Linear 层)。解码器中的每一层都旨在“反转”编码器中对应层执行的压缩步骤,逐渐增加维度,直到输出层与原始输入的维度匹配。下图描绘了解码器网络的总体结构,展示了它如何接收潜在表示并进行扩展。digraph DecoderStructure { rankdir=TB; graph [fontname="sans-serif", label="解码器结构:扩展表示", labelloc=t, fontsize=12]; node [shape=box, style="filled", fontname="sans-serif", margin=0.2]; edge [fontname="sans-serif"]; Bottleneck [label="瓶颈层\n(潜在表示 z)\n维度: D_latent", fillcolor="#ffec99", width=2.5]; subgraph cluster_decoder_layers { label = "解码器网络"; style="filled"; fillcolor="#e9ecef"; color="#adb5bd"; node [fillcolor="#a5d8ff"]; Dec_Hidden1 [label="解码器层 1\n(例如,全连接层)\n单元数: U1 (> D_latent)", width=2.5]; Dec_Hidden2 [label="解码器层 ...\n(例如,全连接层)\n单元数: ...", width=2.5]; Dec_Output [label="输出层\n(例如,全连接层)\n单元数: D_original (输入维度)", width=2.5, fillcolor="#74c0fc"]; } Reconstructed_Output [label="重构输入 (X̂)\n维度: D_original", fillcolor="#ffc9c9", width=2.5]; Bottleneck -> Dec_Hidden1 [label=" 解码器输入"]; Dec_Hidden1 -> Dec_Hidden2 [label=" 扩展"]; Dec_Hidden2 -> Dec_Output [label=" 扩展"]; Dec_Output -> Reconstructed_Output [label=" 最终重构"]; }解码器网络接收来自瓶颈层的压缩潜在表示,并通过其层逐步扩展,以生成重构的输入。在本课程的后续部分,尤其是在第五章讨论用于图像数据的卷积自编码器时,你将看到转置卷积层(Conv2DTranspose)等专门的层如何在解码器中用于有效地进行上采样和重构空间数据。输出层:匹配输入特点解码器的最后一层非常重要。它必须拥有与它尝试重构的原始输入数据的维度相同数量的神经元(或单元)。例如,如果你正在处理扁平化的 MNIST 图像(28x28 像素 = 784 个特征),你的解码器输出层必须有 784 个单元。此输出层的激活函数选择也重要,并取决于你输入数据的性质和归一化方式:Sigmoid 激活: 如果你的输入数据值被归一化到 [0, 1] 范围内(图像像素强度常见),则解码器的输出层通常使用 sigmoid 激活函数。这能确保重构值也落在此范围内。线性激活: 如果你的输入数据是连续的且没有特定范围的边界(例如,数据标准化为零均值和单位方差),那么线性激活函数(表示没有明确激活,或 activation=None)通常更适合。输出此时可以取任何实数值。Softmax 激活: 如果你的输入代表类别数据(例如,独热编码向量),则可能会使用 softmax 激活,尽管这对于典型的自编码器重构任务不太常见,而更符合分类目的。输出激活函数的选择应与用于训练自编码器的损失函数保持一致。例如,对于归一化到 [0,1] 的输入,使用 sigmoid 输出和均方误差 (MSE) 很常见。如果你的数据是二元的,你可能会使用 sigmoid 输出和二元交叉熵损失。学习重构解码器并非独立学习其工作。编码器和解码器作为一个整体网络共同训练。由反向传播驱动的学习过程,会调整自编码器两部分的权重,以最小化重构损失。正如你从本章介绍中回顾的那样,此损失量化了原始输入 $x$ 和解码器输出 $\hat{x}$ 之间的差异。对于连续数据,这通常是均方误差: $$均方误差 = \frac{1}{N} \sum_{i=1}^{N} (x_i - \hat{x}_i)^2$$ 因此,解码器学习将编码器生成的潜在编码尽可能准确地映射回输入空间。重构 $\hat{x}$ 的质量直接反映了整个自编码器系统对数据底层结构建模的能力如何。重要的是要记住,解码器重构输入的能力完全依赖于编码器在潜在空间中保存的信息。如果编码器丢弃了大量信息,或者瓶颈的容量不足以应对数据的复杂性,即使是最佳解码器也无法完美地再现原始输入。解码器的设计,包括层的数量和类型及其激活函数,必须适当选择,以便有效处理潜在表示并生成期望的输出格式。