2、自编码器的数学基础:线性变换、激活函数、损失函数

学自编码器之前,我建议你先搞懂三样东西:线性变换、激活函数、损失函数。说白了,自编码器就是一个“挤压再还原”的神经网络。编码器把数据压成低维码,解码器再把码还原回来。整个过程全靠这三样数学工具撑着。

2.1 线性变换 —— 自编码器的骨架

自编码器的每一层,本质上就是一次线性变换加激活函数。线性变换的公式很简单:

y = W · x + b

其中 W 是权重矩阵,x 是输入向量,b 是偏置。我刚开始做这个项目时,总觉得偏置 b 可有可无。后来有一次,数据分布整体偏移了一截,不加偏置的模型死活学不好……嗯,从那以后我再也不敢省这步了。

给你个直观理解:线性变换就是在高维空间里做旋转、缩放、平移。编码器通过多次线性变换把原始数据映射到低维空间,解码器再逆变换回来。

组件形状作用
输入 x(n_dims,)原始样本,比如784维的图片
权重 W(latent_dim, n_dims)编码矩阵,决定映射方向
偏置 b(latent_dim,)平移偏移,让网络更灵活
输出 y(latent_dim,)压缩后的低维码

为什么不直接用单层线性变换?因为单层线性变换只能做“仿射变换”,表达能力太弱。多层非线性叠加才能捕捉复杂结构。这一点,你想想看——如果只用一层,自编码器就退化成PCA了。

2.2 激活函数 —— 给网络注入非线性

没有激活函数,堆再多线性层也等于一层。我习惯用这几个:

  • Sigmoid:输出 (0,1),适合二分类输出层。但梯度容易饱和,我很少在隐藏层用。
  • Tanh:输出 (-1,1),均值为0,比Sigmoid好点,但仍有饱和问题。
  • ReLU:max(0, x),简单暴力,梯度不饱和。但小心“神经元死亡”——负值永远不激活。
  • Leaky ReLU:给负区间一个小斜率,比如0.01。我项目中用这个最多,既避免死亡,又保持非线性。

避坑指南:我曾经在一个图像自编码器上用ReLU,训练到一半损失突然不降了。查了半天发现一半神经元死了,输出全是0。换成Leaky ReLU后马上恢复。你如果遇到类似问题,先检查激活函数。

2.3 损失函数 —— 衡量重构的好坏

自编码器的目标是最小化重构误差——输入 x 和输出 x̂ 之间的差异。常用的损失函数有两种:

MSE(均方误差)

公式:

L = (1 / N) * Σ (x_i - x̂_i)^2

适合连续值数据,比如图像像素(0~1或0~255)。我处理MNIST图片时,默认都用MSE。它对大误差惩罚重,收敛快。

交叉熵

公式(二分类版本):

L = - (1 / N) * Σ [ x_i * log(x̂_i) + (1 - x_i) * log(1 - x̂_i) ]

适合二值数据,比如黑白图片、one-hot编码。为什么?因为交叉熵天然对概率分布敏感。如果你用MSE去拟合二值数据,模型往往只学会平均值,细节全糊了。

经验选择: 数据是连续数值(如浮点像素)→ 用MSE。
数据是0/1或稀疏二值(如文本词袋)→ 用交叉熵。

2.4 重构误差的直观理解

重构误差不是随便一个数字。它告诉你:模型在压缩时丢了多少信息。我曾做过一个实验:把手写数字从784维压到2维,再解压回来。用MSE算出来的误差是0.03左右,肉眼看去数字轮廓还在,但笔画的毛刺变多了。换成交叉熵,误差更小?不一定,因为量纲不同。但关键是两点:

  • 误差越小,压缩损失越小——但可能过拟合。
  • 误差分布比误差绝对值更重要——如果某个样本误差特别大,说明它在低维空间里与其他样本纠缠不清。
注意: 不要只看训练集误差。验证集误差才是真本事。我遇到过模型在训练集上重构得极其完美,但测试集一塌糊涂——典型的过拟合。加上正则化或降维约束就好。

2.5 核心流程框架

下面这张图把本章知识点串起来了。你盯着看半分钟,就能在脑子里搭出自编码器的数学骨架。

自编码器数学核心流程 输入 x 线性变换 Wx+b 激活函数 (ReLU/Sigmoid/Tanh) 低维码 z —— 解码(逆过程) —— 线性变换 W'z+b' 激活函数 (常用 Sigmoid) 重构 x̂ 损失函数 MSE / 交叉熵 重构误差 L = ||x - x̂||² 或 -Σ x log x̂ 编码 → 低维码 → 解码 → 重构 → 计算损失 反向传播更新W和b

2.6 代码片段:如何定义损失

你用PyTorch搭自编码器时,损失函数就两行。我习惯把MSE和交叉熵都准备好,根据数据切换:

import torch.nn.functional as F

# 连续值数据
loss_mse = F.mse_loss(output, input)

# 二值数据(假设input经过sigmoid落在(0,1))
loss_bce = F.binary_cross_entropy(output, input)

注意:交叉熵要求输出在(0,1)之间,所以解码器最后一层用Sigmoid激活。这一点忘了就出bug——我曾经把输出直接塞进交叉熵,结果loss全是nan,找了半天才发现没加Sigmoid。

本章要点:
  • 线性变换提供基础映射,但必须搭配激活函数引入非线性。
  • 激活函数选型影响训练稳定性,建议默认用Leaky ReLU。
  • 损失函数根据数据类型选:连续值用MSE,二值用交叉熵。
  • 重构误差越小,但不要忽视过拟合风险。