哈喽,大家好~
今儿咱们详细的聊聊降维与压缩的核心目标、优化侧重点与数学基础。
很核心的一个问题:为什么降维和压缩经常被混淆?
本质上,降维与压缩都 把信息变少:
降维:把数据从高维流形或空间映射到低维连续空间,强调结构保留(几何/拓扑/邻域关系)与下游任务(可视化、聚类、分类)所需的有用变异。
压缩:把数据编码成尽可能少的比特,强调码长与重建失真的权衡(率失真),为存储和传输服务,可能允许一定损失(有损压缩)。
因两者都能降低表示复杂度,许多人误以为它们等价。
事实上,它们的优化目标、约束与评价指标有本质差异。
核心区别
目标层面:
降维,聚焦在找到低维连续表示 ,使得数据的几何关系(距离、邻域、类簇结构、可分性)尽可能保留。常见指标包括:解释方差、邻域信任度、距离保持的相关系数等。
而压缩聚焦在最少比特编码原始数据,满足一定失真限制(有损)或完全无失真(无损)。关键指标是平均码长(比特/样本)与重建误差(MSE、PSNR、SSIM等)。
输出形式:
降维,输出低维实数向量(连续空间),通常无需解码;用于分析或作为下游任务输入。
压缩,输出比特流(离散码),必须有正确的解码器还原近似数据。
优化与约束:
降维,常设正交约束/测地保持/概率分布匹配等;例如PCA的 ,t-SNE的分布匹配。
压缩,必须满足前缀码约束(Kraft–McMillan)、码字分配、比特预算,且典型地包含量化与熵编码。
度量与评价:
降维,解释方差比、邻域信任度、距离相关性、可视化分离度。
压缩,平均码长 (bits/sample)、失真 (如MSE)、率失真曲线 。
可逆性与用途:
降维,一般不可逆(信息丢失),但不强调可重建;用途是更好的表示、可视化与学习。
压缩,强调可重建;在无损情况下可严格逆;在有损情况下追求最佳的率失真权衡。
典型方法:
降维,PCA、MDS、Isomap、LLE、t-SNE、UMAP、线性/非线性流形学习。
压缩,熵编码(Huffman/Arithmetic)、变换编码(DCT/KLT)、量化(标量/矢量)、JPEG/PNG、VAE/神经压缩(深度变换+概率建模)。
降维
给定数据矩阵 ,行向量是样本 。
PCA通过寻找投影矩阵 将高维数据投影到低维空间 ,强调最大化投影方差或最小化重建误差。
重建误差最小化(投影-重建):
等价地,PCA等于选择协方差矩阵 的前 个最大特征值对应的特征向量作为列向量构成 。
当 的特征值为 时,最小化的重建误差为:
解释方差比为:
从SVD视角:若 零均值, ,PCA投影矩阵 由 的前 列构成。投影坐标 。
PCA的侧重点是线性地保留最大能量方向,使低维向量能尽可能代表数据主要变异。
它保持全局结构,但对复杂非线性流形可能不足。
非线性降维:以t-SNE为例
t-SNE通过在高维空间中对样本邻域相似性建模,并在低维空间用重尾分布来表示相似性,最小化两者的Kullback–Leibler散度,实现局部邻域结构的保留和清晰分离的可视化。
高维相似性(条件高斯核):
低维相似性(Student-t分布):
优化目标(KL散度):
t-SNE的重点是局部结构的可视化保真,但低维坐标绝对尺度缺乏明确意义,也难以用于泛化重建。
降维的质量度量(邻域保真)
一个常用的降维质量度量是信任度,衡量低维邻域是否由高维的近邻所构成。
定义:
其中 为低维空间中排位到 近邻但在高维空间中不是 近邻的集合, 为在高维空间的排名。
,越高越好。
压缩
无损压缩的目标是将数据编码成最短的比特串,可无误解码。
信息论核心结论:
香农熵:
对于任何无损编码,平均码长 满足 (源编码定理)。
Kraft–McMillan不等式(前缀码约束):
其中 是码字长度。满足此约束的码可构成前缀码(即无歧义解码)。
哈夫曼编码近似实现最优码长,算术编码可逼近熵极限。
有损压缩与率失真理论
有损压缩允许一定失真,以降低码率。
核心是率失真函数:
即在平均失真不超过 的条件下,最小化编码率(互信息)。通常也以拉格朗日形式表述:
其中 是权衡系数;这是许多现代神经压缩方法与信息瓶颈思想的基础。
常用失真度量为均方误差(MSE):
变换编码与PCA(KLT)在压缩中的角色
在高斯源和MSE条件下,KLT(即PCA)是最优的能量聚集变换:将协方差对角化,使每个系数统计独立,便于量化与熵编码。
对于每个维度 (特征值 ),令量化后失真为 。
高斯模型下最优比特分配满足反水填充:
阈值 由给定的总码率或总失真约束确定。总失真为:
后者对应被截断的主成分的能量损失(降维本身导致的误差)。
高率标量量化近似:
其中 为量化步长, 为变换系数。虽然这里的 是微分熵近似,实际中常用经验直方图估计码长。
压缩的组成模块
变换:如DCT、PCA/KLT、卷积/自编码器变换
量化:标量或矢量;确定码书或步长
熵编码:将符号/索引映射到比特流,尽量逼近熵
总体优化目标是给定码率约束下最小化失真,或给定失真目标下最小化码率。
降维 vs 压缩
降维:
-
优化对象:连续投影坐标 ,不需要码字。 -
约束:结构保留(方差最大、邻域一致、分布匹配)。 -
评估:解释方差、信任度、距离相关性、下游准确率。 -
输出:低维实数向量,常用于可视化与学习,不关心码率。
压缩:
-
优化对象:码率 与失真 的权衡;需量化与熵编码。 -
约束:前缀码、码书、比特预算;保证可解码。 -
评估:率失真曲线 ,PSNR/SSIM等。 -
输出:比特流与解码器;更关注还原质量与存储/传输效率。
降维强调结构的可用性,压缩强调码率的经济性。
在实际系统中,PCA可以既用于降维也用于压缩:前者停止在连续坐标,后者接着做量化并估计码率。
完整案例
我们使用从瑞士卷(Swiss Roll)生成非线性流形数据,再嵌入到高维空间(50维),模拟多簇结构和噪声。
然后进行:
降维:PCA(线性)与t-SNE(非线性可视化)
压缩:PCA变换 + 标量量化 + 经验熵估计(近似码率)+ 逆变换重建
大家要注意,此例中的压缩码率采用经验熵估计,并非严格的算术编码实现,主体是阐释概念差异与趋势。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_swiss_roll
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE, trustworthiness
from sklearn.metrics import pairwise_distances
from matplotlib.colors import ListedColormap
# 随机种子
np.random.seed(42)
# 1) 生成虚拟数据:Swiss Roll -> 3D -> 扩展到50D,带多簇标签
N = 2000
X3d, t = make_swiss_roll(n_samples=N, noise=0.7)
# 生成三个簇标签:依据t分区
labels = np.digitize(t, bins=np.percentile(t, [33, 66])) # 0,1,2
# 扩展到50维:附加非线性与噪声通道
d = 50
X = np.zeros((N, d))
# 前3维放原始Swiss Roll
X[:, :3] = X3d
# 后面维度:一些非线性函数 + 噪声
for i in range(3, d):
# 非线性混合:sin, cos, 多项式,带噪声
X[:, i] = (np.sin(0.05 * t) * (i % 5 + 1) +
np.cos(0.03 * t) * ((i % 7) - 3) +
0.01 * X3d[:,0] * ((i % 3) - 1) +
np.random.normal(0, 0.5, size=N))
# 标准化(零均值),便于PCA
X_mean = X.mean(axis=0)
X_centered = X - X_mean
# 2) 降维:PCA到10维(连续降维表达)与t-SNE到2维
pca_10 = PCA(n_components=10, random_state=42)
X_pca10 = pca_10.fit_transform(X_centered) # 低维连续表示(降维)
# t-SNE到2维用于可视化
tsne_2 = TSNE(n_components=2, perplexity=40, learning_rate=300, random_state=42)
X_tsne2 = tsne_2.fit_transform(X_centered)
# 3) 压缩:PCA变换 + 标量量化 + 熵估计 + 逆变换
# 选择不同主成分数与量化步长,估计率失真曲线
components_list = [2, 5, 10, 20]
delta_list = [0.05, 0.1, 0.2, 0.4] # 量化步长候选
rate_distortion_records = [] # (k, delta, bits_per_sample, mse)
def estimate_bits_per_sample(Z_quant):
# Z_quant是量化后的整数索引(按维度分别量化)
# 用每个维度的经验直方图估计熵,并求和得到每样本的比特近似
N, K = Z_quant.shape
bits_per_sample = 0.0
for i in range(K):
# 统计该维的符号频率
vals, counts = np.unique(Z_quant[:, i], return_counts=True)
p = counts / N
# 避免log(0)
entropy = -np.sum(p * np.log2(p + 1e-12))
bits_per_sample += entropy
return bits_per_sample
def pca_transform(X, k):
pca = PCA(n_components=k, random_state=42)
Z = pca.fit_transform(X) # 投影系数
return pca, Z
def pca_inverse(pca, Z):
X_rec = pca.inverse_transform(Z)
return X_rec
# 为了绘图对比,我们记录一个**仅降维**的重建(不量化)
pca10 = PCA(n_components=10, random_state=42)
Z10 = pca10.fit_transform(X_centered)
X_rec_pca10 = pca10.inverse_transform(Z10)
mse_pca10 = np.mean((X_centered - X_rec_pca10)**2, axis=1) # 每样本MSE分布
for k in components_list:
pca_k = PCA(n_components=k, random_state=42).fit(X_centered)
Zk = pca_k.transform(X_centered) # N x k
# 不量化重建MSE(作为降维的参照)
X_rec_nq = pca_k.inverse_transform(Zk)
mse_nq = np.mean((X_centered - X_rec_nq)**2)
rate_distortion_records.append((k, 0.0, 0.0, mse_nq)) # 没有比特,作为降维参考点(非压缩)
# 量化与熵估计
for delta in delta_list:
# 标量均匀量化:q = floor(z / Δ)
Zq = np.floor(Zk / delta).astype(int)
# 估计码率
bits_ps = estimate_bits_per_sample(Zq)
# 去量化(中心复原)
Z_rec = (Zq + 0.5) * delta
# 逆变换
X_rec = pca_k.inverse_transform(Z_rec)
# MSE失真
mse = np.mean((X_centered - X_rec)**2)
rate_distortion_records.append((k, delta, bits_ps, mse))
# 4) 评估:距离保持与信任度
# 为了计算开销可控,子采样一些点做距离分析
idx = np.random.choice(N, size=600, replace=False)
X_sub = X_centered[idx]
X_tsne_sub = X_tsne2[idx]
X_pca2 = PCA(n_components=2, random_state=42).fit_transform(X_centered)
X_pca2_sub = X_pca2[idx]
D_high = pairwise_distances(X_sub, metric='euclidean')
D_tsne = pairwise_distances(X_tsne_sub, metric='euclidean')
D_pca2 = pairwise_distances(X_pca2_sub, metric='euclidean')
# 计算距离相关性(皮尔逊)
def corrcoef_flat(A, B):
a = A.flatten()
b = B.flatten()
return np.corrcoef(a, b)[0,1]
corr_tsne = corrcoef_flat(D_high, D_tsne)
corr_pca2 = corrcoef_flat(D_high, D_pca2)
# 信任度(以k=15为例)
T_tsne = trustworthiness(X_centered, X_tsne2, n_neighbors=15)
T_pca2 = trustworthiness(X_centered, X_pca2, n_neighbors=15)
# 5) 绘图:一张图中包含四个复杂分析图
plt.figure(figsize=(16, 12))
# 自定义鲜艳配色
cmap_bright = ListedColormap(["#FF0000", "#00CC00", "#0066FF"])
scatter_colors = np.array(["#FF1493", "#FFD700", "#00BFFF"])
# 图1:t-SNE 2D可视化
ax1 = plt.subplot(2,2,1)
for lab in np.unique(labels):
pts = X_tsne2[labels == lab]
ax1.scatter(pts[:,0], pts[:,1], s=12, c=scatter_colors[lab], label=f"Cluster {lab}", alpha=0.8)
ax1.set_title(f"t-SNE 2D 可视化(Trustworthiness={T_tsne:.3f})", fontsize=12)
ax1.set_xlabel("t-SNE dim 1")
ax1.set_ylabel("t-SNE dim 2")
ax1.legend(loc="best", fontsize=10)
ax1.grid(True, alpha=0.3)
# 图2:高维距离 vs 低维距离(PCA与t-SNE)散点对比
ax2 = plt.subplot(2,2,2)
# 为了密集显示,用随机采样对
pair_idx = np.random.choice(D_high.size, size=20000, replace=False)
ax2.scatter(D_high.flatten()[pair_idx], D_tsne.flatten()[pair_idx], s=5, c="#FF4500", alpha=0.5, label=f"t-SNE corr={corr_tsne:.2f}")
ax2.scatter(D_high.flatten()[pair_idx], D_pca2.flatten()[pair_idx], s=5, c="#32CD32", alpha=0.5, label=f"PCA2 corr={corr_pca2:.2f}")
ax2.set_title("高维 vs 低维距离关系(相关性对比)", fontsize=12)
ax2.set_xlabel("高维距离 (50D)")
ax2.set_ylabel("低维距离")
ax2.legend(loc="best", fontsize=10)
ax2.grid(True, alpha=0.3)
# 图3:率失真曲线(不同主成分数k与量化步长Δ)
ax3 = plt.subplot(2,2,3)
# 把记录按k分组,分别画曲线
color_map = {
2: "#FF00FF",
5: "#00FFFF",
10: "#FFA500",
20: "#00FF00"
}
for k in components_list:
# 提取该k的点
pts = [(bits, mse, delta) for kk, delta, bits, mse in rate_distortion_records if kk == k]
pts_sorted = sorted(pts, key=lambda x: x[0])
bits_arr = np.array([p[0] for p in pts_sorted])
mse_arr = np.array([p[1] for p in pts_sorted])
deltas = [p[2] for p in pts_sorted]
ax3.plot(bits_arr, mse_arr, marker='o', c=color_map[k], label=f"k={k}")
# 注释若干Δ
for i in range(len(deltas)):
if deltas[i] > 0:
ax3.annotate(f"Δ={deltas[i]:.2f}", (bits_arr[i], mse_arr[i]), fontsize=8, color=color_map[k])
# 添加仅降维点(无量化):bits=0只是标识参考,不是实际压缩
for k in components_list:
mse_nq = [mse for kk, delta, bits, mse in rate_distortion_records if kk == k and delta == 0.0][0]
ax3.scatter([0.0], [mse_nq], c=color_map[k], marker='^', s=80)
ax3.set_title("率失真曲线(PCA变换 + 标量量化 + 熵估计)", fontsize=12)
ax3.set_xlabel("估计码率(bits/sample)")
ax3.set_ylabel("MSE 失真")
ax3.legend(loc="best", fontsize=10)
ax3.grid(True, alpha=0.3)
# 图4:不同类别的重建误差分布(仅降维 vs 压缩)
ax4 = plt.subplot(2,2,4)
# 选择一个压缩配置,使得码率与**仅降维**的信息量相比具有参考意义
# 在记录中选k=10、Δ=0.1
mse_dr = mse_pca10 # 仅降维(k=10)每样本MSE
# 压缩(k=10, Δ=0.1)每样本MSE
pca10_c = PCA(n_components=10, random_state=42).fit(X_centered)
Z10_c = pca10_c.transform(X_centered)
delta_sel = 0.1
Z10_q = np.floor(Z10_c / delta_sel).astype(int)
Z10_rec = (Z10_q + 0.5) * delta_sel
X_rec_c = pca10_c.inverse_transform(Z10_rec)
mse_comp = np.mean((X_centered - X_rec_c)**2, axis=1)
# 绘制violin图:按类别对比分布
data_dr = [mse_dr[labels == lab] for lab in np.unique(labels)]
data_comp = [mse_comp[labels == lab] for lab in np.unique(labels)]
pos_dr = np.arange(1, 4) - 0.15
pos_comp = np.arange(1, 4) + 0.15
parts1 = ax4.violinplot(data_dr, positions=pos_dr, showmeans=True, showmedians=True)
for pc in parts1['bodies']:
pc.set_facecolor("#8A2BE2")
pc.set_alpha(0.7)
parts2 = ax4.violinplot(data_comp, positions=pos_comp, showmeans=True, showmedians=True)
for pc in parts2['bodies']:
pc.set_facecolor("#FF69B4")
pc.set_alpha(0.7)
ax4.set_xticks([1,2,3])
ax4.set_xticklabels(["Cluster 0", "Cluster 1", "Cluster 2"])
ax4.set_title("每类样本的重建误差分布:仅降维 vs 压缩", fontsize=12)
ax4.set_ylabel("MSE")
ax4.legend([parts1['bodies'][0], parts2['bodies'][0]], ["仅降维 (PCA-10)", f"压缩 (k=10, Δ={delta_sel})"], loc="best")
ax4.grid(True, alpha=0.3)
plt.suptitle("降维 vs 压缩:结构保持与率失真权衡的综合分析图", fontsize=14)
plt.tight_layout()
plt.show()
图1:t-SNE二维可视化:
展示非线性流形数据在低维空间中的类簇分离。这是降维用于可视化的典型用途。
标注了信任度(Trustworthiness),例如 表示低维邻域与高维邻域较一致,说明降维成功保留局部结构。
降维的目标是让低维嵌入忠实呈现邻域与簇结构,而非考虑码长。此图没有任何关于比特率的信息。
图2:高维距离 vs 低维距离的关系(t-SNE与PCA的对比):
横轴是50维的欧式距离,纵轴分别是t-SNE 2D与PCA 2D的距离。通过随机采样展示约2万对点。
我们给出了皮尔逊相关系数(corr)。通常PCA保留全局线性结构,t-SNE仅保留局部结构,因此PCA的距离相关性可能更高,而t-SNE用于可视化的局部拉伸/压缩可能降低整体距离相关性,但提高簇分离的可读性。
降维强调的是结构保真(特别是邻域),并不是严格保持所有点对距离的全局线性映射。t-SNE在可视化上更佳,但距离相关性可能低于PCA。这与压缩的目标无关。
图3:率失真曲线(PCA变换 + 标量量化 + 熵估计):
不同曲线对应不同的主成分数 ;曲线上的点对应不同量化步长 。我们绘制了估计码率(bits/sample)与MSE失真之间的权衡。
每条曲线左边的三角标记点是仅降维而不量化的重建误差(bits=0仅作为参考点,不代表实际压缩码率)。可以看到,当不量化时失真较低,但没有定义码率(没有生成比特流);当进行量化并熵编码(近似)时,码率提升、失真也随量化加粗或主成分数变化而变化。
压缩要在满足码率约束时最小化失真(或反之),这是降维没有的优化目标。此图直观展现了压缩的核心——率失真权衡。选取更大 通常能降低截断误差,但也使码率变高;选取更粗的 降低码率但增大失真。
图4:每类样本的重建误差分布(仅降维 vs 压缩):
使用violins展示三类数据在两种方案下的MSE分布对比:仅降维(PCA-10,无量化)与压缩(k=10, Δ=0.1,有量化)。
可以观察到压缩因为量化引入的误差使各类别的重建误差分布整体上升;但某些类可能受影响较小(取决于该类在投影空间的能量分布与量化对该类信号的敏感度)。
降维追求低维结构可用性,压缩追求码率经济性下的可接受失真。在同样的变换(PCA)下,是否量化与码率管制会直接影响重建质量。这显示两者在优化侧重点上的差别。
总结
降维和压缩都减少表示维度或信息量,但核心区别在于目标与评价:
降维更关注低维空间中的结构保留(邻域、距离、可分性),输出连续向量,不涉及比特流;
压缩更关注码率与失真权衡,输出比特流,可解码还原数据。
最后

