当编码器将输入数据压缩成潜在表示后,解码器便开始工作。它的主要任务是从这种压缩形式中尽可能忠实地重建原始输入。解码器的设计并非次要,它与编码器配合工作。一个良好设计的解码器能有效地将学到的潜在特征转换回输入空间,这反过来有助于确保编码器学到有意义且有用的特征。下面我们来考察一些设计解码器网络的方法。对称性:一个常见的起点一种被广泛采用且通常有效的设计解码器的方法是使其成为编码器的镜像。如果编码器通过一系列层逐步降低输入的维度,那么解码器将逐步增加维度。层结构: 例如,如果编码器有三个隐藏层,神经元数量为 Input -> 256 -> 128 -> Latent_Dim,那么对称解码器的结构将是 Latent_Dim -> 128 -> 256 -> Output。层类型: 对于使用密集(全连接)层构建的自编码器,解码器也将使用密集层。如果编码器处理图像数据时使用了卷积层(我们将在第5章详细介绍),解码器通常会使用相应的上采样层,例如转置卷积层。这种对称性提供了一种平衡的架构,确保解码器具有相似的能力来“展开”或“解压”编码器已“折叠”或“压缩”的内容。digraph G { rankdir=LR; graph [pad="0.5", nodesep="0.5", ranksep="1"]; node [shape=box, style="filled", fontname="sans-serif", margin="0.2,0.1"]; edge [fontname="sans-serif"]; subgraph cluster_encoder { label="编码器"; style="filled"; color="#e9ecef"; bgcolor="#f8f9fa"; node [fillcolor="#a5d8ff"]; Input_Data [label="输入数据 (X)\n[例如,784个单元]", shape=parallelogram, fillcolor="#b2f2bb"]; Enc_Layer1 [label="隐藏层 1\n[例如,128个单元]\n激活函数: ReLU"]; Enc_Layer2 [label="隐藏层 2\n[例如,64个单元]\n激活函数: ReLU"]; } Bottleneck [label="瓶颈层\n潜在空间 (z)\n[例如,32个单元]", shape=ellipse, fillcolor="#ffe066", style="filled"]; subgraph cluster_decoder { label="解码器"; style="filled"; color="#e9ecef"; bgcolor="#f8f9fa"; node [fillcolor="#a5d8ff"]; Dec_Layer1 [label="隐藏层 1'\n[例如,64个单元]\n激活函数: ReLU"]; Dec_Layer2 [label="隐藏层 2'\n[例如,128个单元]\n激活函数: ReLU"]; Output_Data [label="重建数据 (X')\n[例如,784个单元]\n激活函数: Sigmoid/Linear", shape=parallelogram, fillcolor="#b2f2bb"]; } Input_Data -> Enc_Layer1; Enc_Layer1 -> Enc_Layer2; Enc_Layer2 -> Bottleneck; Bottleneck -> Dec_Layer1; Dec_Layer1 -> Dec_Layer2; Dec_Layer2 -> Output_Data; }一张图示对称自编码器架构(带密集层)的示意图。解码器镜像了编码器的结构。解码器层激活函数的选择解码器中激活函数的选择,尤其是输出层的激活函数,直接与输入数据的性质和预处理有关。输出层激活函数解码器最后一层的激活函数必须选择与原始输入数据的范围和分布相匹配的。Sigmoid:如果您的输入数据被缩放到 $[0, 1]$ 的范围(例如,灰度图像的像素强度,或二元数据),sigmoid 激活函数是一个常用选择。它将输出值压缩到这个确切的范围。 $$ \text{sigmoid}(x) = \frac{1}{1 + e^{-x}} $$线性(无激活):如果您的输入数据包含不限定范围的连续值,或被归一化为零均值和单位方差(例如,使用 StandardScaler),那么 线性 激活(意味着不应用任何激活函数)是合适的。输出可以是任何实数值。Tanh(双曲正切):如果您的输入数据被缩放到 $[-1, 1]$ 的范围,tanh 激活函数是合适的,因为其输出也在此范围内。 $$ \text{tanh}(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} $$ 正确选择这一点对于有效重建来说是根本的。如果您的输入像素在 $[0, 1]$ 范围内,但解码器的输出层使用线性激活,它可能会产生远超出此范围的值,从而导致重建和学习效果不佳。隐藏层激活函数对于解码器中的隐藏层,选择与编码器类似。ReLU(修正线性单元):由于其简单性以及在对抗梯度消失方面的有效性,通常是一个好的默认选择。LeakyReLU, ELU:ReLU 的变体,可以帮助解决“死亡 ReLU”问题。Sigmoid 或 Tanh:也可以使用,但要留意在更深的网络中可能出现的梯度消失问题,这与它们在编码器中的使用类似。目标是为解码器提供足够的非线性,使其能够学习从潜在空间转换回原始数据空间的复杂映射。连接解码器设计与损失函数解码器中输出层激活函数的选择与您将用于训练的损失函数密切相关。如果您在 $[0, 1]$ 范围内输出使用 sigmoid 激活,通常会将其与二元交叉熵 (BCE) 损失函数搭配使用,特别是当输入可以被视为概率或二元值时。对于 $[0,1]$ 范围内的像素值,BCE 通常效果良好。如果您使用 线性 激活(或在输入范围合适时使用 ReLU/tanh),您最常会使用均方误差 (MSE) 损失。MSE 衡量实际值与重建值之间的平均平方差。我们将在“为自编码器选择合适的损失函数”一节中更详细地介绍损失函数,但在设计解码器时记住这种关系是很好的。深度与宽度:考量完美对称性虽然对称性是一个好的起点,但它并非严格要求。解码器容量:解码器需要足够的容量(层和单元)来执行重建任务。如果潜在空间非常小(高压缩),解码器可能需要相对强大才能准确重建数据。更简单的解码器:在某些情况下,特别是当潜在表示丰富且结构良好时,一个比编码器更简单的解码器(更少的层或单元)可能就足够了。实验:最佳的深度和宽度通常通过实验获得。从对称设计开始,然后根据重建性能以及为下游任务提取特征的质量来尝试修改它。例如,如果重建损失很高,您可能会考虑增加解码器的容量。解码器设计的实际考量迭代优化:构建自编码器是一个迭代过程。从一个合理的解码器设计(如对称设计)开始,并根据性能进行优化。监控重建:在训练过程中,密切关注重建损失。对于图像数据,直观检查重建样本的质量可以提供有关解码器表现如何的重要信息。对特征质量的影响:请记住,目标通常是特征提取。编码器能够学到有用的潜在表示,因为它通过解码器的重建工作进行训练。一个不能有效重建的解码器表明编码器在潜在空间中没有保留足够或正确类型的信息。通过仔细考量这些方法,您可以设计一个能有效重建数据的解码器,使得自编码器在其瓶颈层中学到强大的特征。下一步是选择合适的损失函数来引导这个学习过程。