
简介《机器学习导论》第10章讲义由南京大学出品围绕降维与度量学习展开面向机器学习初学者与期末复习者。内容先以k近邻学习解释“维数灾难”再依次推导MDS多维缩放、PCA主成分分析并介绍核化PCA、ISOMAP、LLE等非线性降维思路最后落到马氏距离与距离度量学习逻辑完整适合对照教材理清公式与概念。资源为1个PDF文件共23页压缩包大小约1.43MB阅读轻量、便于移动端学习。目前已有132人学习下载章节内包含大量特征值分解、方差最大化与保距重构的关键推导能帮助读者直观理解高维空间为何需要降维以及如何选择合适距离度量。1. 南大《机器学习导论》第10章23页讲义为什么把降维和度量学习放在一起讲降维与度量学习是我在机器学习基础入门教程里最不愿意跳过的一章也是期末复习时最容易把两个概念混在一起背的一章。南大这份《机器学习导论》第10章讲义只有23页主线却非常清楚先让你承认维度灾难是真的再给你PCA和度量学习两把工具告诉你怎么用距离判断样本像不像。对刚入门的读者来说这一章解决的是很具体的痛点——特征多了模型反而变差、KNN在几十维数据上彻底失效、高维数据没法可视化。对已经在写机器学习模型的工程师这章更像是把一直在用的PCA、标准化、距离函数统一到一个坐标系里看完能说出每个参数改的是什么。2. 先搞懂降维要解决什么维度灾难与距离度量失效的数学直觉很多机器学期末复习的同学喜欢把这一章拆成两个知识点降维是一个知识度量学习是一个知识考前背背PCA步骤就过了。但南大这份《机器学习导论》第10章把二者放在一起是因为它们回答同一个问题在高维数据里怎样才算两个样本“像”。要理解这一点得先看维度灾难到底怎么发生的。2.1 维度灾难不是玄学高维空间里“最近邻”不再可信先说结论维度增加之后欧氏距离会逐渐失去分辨样本的能力。这不是算法调参的问题是几何本身的性质。考虑在一个d维单位超立方体里均匀撒样本计算所有样本对之间的距离。d小的时候距离分布层次分明有的近、有的远KNN能找到真正的邻居d到几十甚至上百时绝大多数样本对的距离都挤在一个很窄的区间里最近邻居和中等距离样本之间的差距趋近于零。所以KNN在高维数据上失效不是因为你没调好k而是“最近”这个说法本身变得没有意义。import numpy as np def nearest_neighbor_stats(n_samples2000, dims(2, 5, 10, 50, 100)): 在高维单位超立方体内采样观察最近邻距离的分布变化。 for d in dims: X np.random.rand(n_samples, d) # 利用广播计算两两欧氏距离X[:, None, :] 形状 (n,1,d) dist np.linalg.norm(X[:, None, :] - X[None, :, :], axis2) # 对角线是样本与自身的距离填成无穷大后取每一行的最小值 np.fill_diagonal(dist, np.inf) nearest dist.min(axis1) print(fd{d:3d} 最近邻距离均值{nearest.mean():.4f} 标准差{nearest.std():.4f}) nearest_neighbor_stats()输出会显示一个很直观的趋势维度从2涨到100时最近邻距离的均值越来越大标准差却越来越小。均值变大说明样本整体更远标准差变小说明远近差异被抹平。后者才是关键——KNN靠距离远近排序标准差接近0等于排序信息消失。这里有个参数值得注意n_samples取2000是平衡计算量和稳定性的折中。样本量太少标准差本身就大趋势看不清样本量超过一万两两距离矩阵会吃掉几百MB内存跑起来反而拖慢实验节奏。dims列表里的50和100对应真实场景中“特征列数比业务字段还多”的情况比如文本TF-IDF特征、基因表达谱动辄上千维距离失效问题只会更严重。这就是讲义里维度灾难的第一层含义当距离度量失效时降维不再是可选项而是让模型有意义的必要条件。也正是因此这一章把PCA和度量学习放在同一条线上一个直接换坐标一个换距离定义共同目标都是让样本之间的远近重新变得可信。2.2 PCA与MDS线性降维的两条路线讲义为什么先讲PCA再讲MDSPCA的思路很多人已经熟悉找一个方向把数据投影过去让投影后的方差最大换句话说是保留信息量最大的方向。MDS的思路不同它不看方差看样本间的成对距离目标是找一个低维坐标让低维坐标算出来的距离矩阵尽可能逼近原始距离矩阵。讲义先讲PCA后讲MDS是有理由的PCA只需要协方差矩阵的特征分解计算路线短解释直观而MDS把距离作为核心对象正好为后面度量学习里“改距离”铺路。方法优化目标输入输出适用场景PCA投影后方差最大样本特征矩阵主成分方向与低维坐标全局线性结构、特征压缩、可视化MDS保持样本间成对距离距离矩阵低维嵌入坐标只有相似度数据、距离结构可视化读这份讲义时对照周志华《机器学习》西瓜书第10章一起看是很高效的做法。西瓜书里的公式推导更细南大讲义的页码少适合先搭框架。期末复习时我一般拿着讲义目录当提纲拿西瓜书当推导补充两本加起来就是一条完整的知识线。最值得注意的一个结论是当MDS使用的距离是欧氏距离时经典MDS解出的坐标就是PCA主成分坐标。因为欧氏距离矩阵经过双中心化之后等价于对样本协方差矩阵做特征分解。这也是为什么很多入门教程把两者放在同一章讲PCA是“看方差”的降维MDS是“看距离”的降维在欧氏距离下两者殊途同归。MDS给出的坐标本身不唯一因为距离矩阵对平移不变经典解通常以中心化坐标表示这时与PCA的结果一致差别只剩下特征向量的符号翻转。2.3 度量学习把“学降维”看成“学距离矩阵”度量学习的起点是一个很实际的问题欧氏距离不一定是最好的距离。例如在用户画像数据里年龄差10岁和收入差10万哪个算“近”如果特征量纲不同、重要性不同、彼此相关单纯按欧氏距离算相似度会把模型带偏。马氏距离把这个问题参数化( d_M(x_i,x_j)\sqrt{(x_i-x_j)^\top M (x_i-x_j)} )其中M是一个半正定矩阵。当M是单位矩阵时就是欧氏距离M退化为对角矩阵时对角线元素可以理解为每个特征的权重M为普通半正定矩阵时还编码了特征间的相关性。度量学习要做的就是根据训练数据学这个M。因为M半正定可以分解为 ( MA^\top A )代入距离公式后会发现马氏距离等于先把样本用A做线性变换再在新空间里算欧氏距离。于是降维和度量学习在这里汇合了——所谓学一个更好的距离等价于学一个更好的线性投影。这解释了第10章的标题为什么把两个名字写在一起也解释了为什么这一章最后会把PCA、MDS、度量学习放在同一张地图里。理解了这一层后面看代码就不会慌降维改的是坐标度量学习改的是距离但两者在数学上共用同一套线性代数工具。3. 用Python把PCA跑通从讲义公式到最小可复现代码讲义里的PCA推导到这里就结束了但在我的经验里看懂推导和跑出结果是两回事。下面给出一套最小可复现的PCA实现并说明落地时最容易纠结的三个参数。这份代码不依赖scikit-learn能完整跑通适合当作业或课程设计的基础。3.1 动手前的三个参数决策中心化、标准化、保留多少维中心化是第一个要决策的参数。PCA在数学上解的是协方差矩阵的特征分解而协方差矩阵的定义就是中心化后的二阶矩。如果跳过中心化直接对原始样本矩阵做奇异值分解得到的主方向会被均值向量带偏第一主成分经常指向数据中心而不是数据变化最大的方向。自己实现PCA时这一句“先减均值”是第一步也是最大的坑。第二个决策是标准化。是否除以标准差取决于你要保留什么。做可视化和KNN前的预处理一般先用StandardScaler再PCA否则量纲大的特征会主导前两个主成分做图像或文本的特征降维很多实践只做中心化因为特征本来就在同一个数值体系里。先想清楚你要保住的是方差还是语义再决定标准化。第三个决策是保留多少维。最常见的选择法是累计方差贡献率到达0.8到0.95的维度这只能作为初筛。最终维度应放在下游任务里用交叉验证选我后面会给具体做法。先记住这句话方差大不代表有用PCA选出的方向不一定对分类任务友好。3.2 用NumPy实现PCASVD方案与特征值分解方案的取舍import numpy as np def pca_fit(X, n_componentsNone, centerTrue): 用SVD实现的PCA。 X: (n_samples, n_features)行是样本列是特征。 n_components: int 表示保留的维度float 表示保留的方差比例。 center: 是否做中心化。 返回降维后的坐标、主成分方向、奇异值、均值向量。 n_samples, n_features X.shape if center: mean np.mean(X, axis0) X_c X - mean else: mean np.zeros(n_features) X_c X # SVD 不需要显式算协方差矩阵数值稳定性比特征值分解好 U, S, Vt np.linalg.svd(X_c, full_matricesFalse) if n_components is None: n_components min(n_samples, n_features) elif isinstance(n_components, float): # 按方差贡献率反推进保留维数 total_var np.sum(S ** 2) cum_ratio np.cumsum(S ** 2) / total_var n_components int(np.searchsorted(cum_ratio, n_components) 1) n_components min(n_components, Vt.shape[0]) # Vt 的行向量是主成分方向X_c 在方向上的内积就是低维坐标 X_pca np.dot(X_c, Vt[:n_components].T) return X_pca, Vt[:n_components], S[:n_components], mean X_pca, components, singular_values, mean pca_fit(X, n_components2)这里用np.linalg.svd而不是np.linalg.eigh是刻意为之。SVD直接对样本矩阵分解避免显式计算n_features维协方差矩阵当特征数远大于样本数时协方差矩阵可能上万乘上万SVD在数值上和内存上都更稳。Vt的行向量就是主成分方向X_c在Vt前n个方向上的投影就是降维后的坐标。参数说明center默认True对应讲义里的“先中心化”n_components为None时保留到min(n_samples, n_features)维传float时代码按奇异值平方占比自动选维数适合快速画累计方差贡献率曲线。注意返回值里的components行之间是正交的但方向不唯一——符号翻转并不代表实现错误。sklearn的PCA内部也走SVD路线它默认centerTrue且不做标准化如果特征量纲差异很大直接丢进PCA和先标准化再PCA得到的两组主成分完全不同。3.3 把PCA放进机器学习应用流程的三种典型位置第一种位置是可视化。把高维数据压到2维画散点观察类别有没有自然分开这是python机器学习入门教程里最常见的用法也是课程设计里最出效果的一步。第二种位置是监督学习前的特征预处理。PCA放在标准化之后、建模之前用来剔除特征间的强相关性。这里有一条铁律PCA只能在全量数据上fit一次且必须只用在训练集上测试集只能transform。很多人偷懒在全量数据上PCA再切分训练测试模型在验证集上虚高上线就翻车。注意先切分训练集和测试集再在训练集上fit PCA测试集上只transform。全量数据一起fit会把测试集信息带进训练过程属于数据泄漏。第三种位置是异常检测。在机器学习检测场景里PCA的无监督性反而是优点只用正常样本拟合PCA然后看新样本的重构误差误差大的样本很可能偏离正常分布。这种方法的优势是不需要标签也容易解释缺点是它对线性结构敏感非线性分布的数据要改用核PCA或自编码器。很多机器学习课程设计选题也喜欢用PCA加KNN做基线先降维到2维或5维再跑KNN对比不同维度的效果既能出图又有定量指标。这个组合就是第10章落地最典型的路线。4. 度量学习上手用马氏距离改造KNN分类器的完整路线度量学习在机器学习实战里常常被忽略因为大家默认欧氏距离够用。但当特征量纲不一致、特征间强相关、下游任务是KNN或近邻检索时一个学出来的距离往往比调参更有效。4.1 从欧氏距离到马氏距离多出来的那个矩阵在学什么先明确一点度量学习解决的痛点是“默认距离不对”。欧氏距离把所有特征平等对待但在真实数据里特征的重要性、量纲、相关关系都不一样。马氏距离给每个维度组合一个权重刚才的公式里那个M矩阵就是全部秘密。实际落地时很少有人直接学一个上万乘上万的大矩阵常见做法是把M分解成 ( MA^\top A )学一个低秩的A这样既保证M半正定又把问题规模降下来。机器学习实战里最常见的落地方式是把KNN的欧氏距离换成马氏距离。KNN本身是个懒惰学习器不训练权重度量学习正好补上这一步为KNN学一个带权距离。这也是为什么很多机器学习实战项目里NCA和KNN总成对出现。在机器学习预测任务里检索和召回都依赖embedding之间的距离度量学习在这里直接改善下游指标。4.2 用scikit-learn的NCA做度量学习一条Pipeline替换KNN的底距离NCA的全称是Neighborhood Components Analysis中文常译作邻域成分分析。它名字像降维方法实则是度量学习的落地实现学一个线性投影让投影空间里每个样本被同类邻居选中的概率最大化。from sklearn.neighbors import NeighborhoodComponentsAnalysis, KNeighborsClassifier from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler # NCA 学一个投影矩阵KNN 在这个投影空间里找近邻 nca NeighborhoodComponentsAnalysis( n_components2, max_iter200, random_state42, initpca, ) knn KNeighborsClassifier(n_neighbors5) # 把标准化、度量学习、KNN 串成一条完整管线 pipe Pipeline([ (scaler, StandardScaler()), (nca, nca), (knn, knn), ]) pipe.fit(X_train, y_train) test_score pipe.score(X_test, y_test) print(NCAKNN test score:, test_score) # 对照组不学距离直接用原始特征的KNN knn_raw KNeighborsClassifier(n_neighbors5) knn_raw.fit(X_train, y_train) raw_score knn_raw.score(X_test, y_test) print(raw KNN test score:, raw_score)NCA的损失函数来自留一法KNN的一个平滑近似在学到的投影空间里把每个样本被同类邻居选中的概率最大化。因为它对投影矩阵可微能用梯度优化所以叫度量学习。在scikit-learn里NCA放在neighbors模块下所以有些看文档的人会误以为它只是个降维器实际上它的核心作用是学距离。参数说明n_components取2通常是为了可视化做分类时不要压太低我一般从特征维数的一半开始调。max_iter默认值比较保守数据量超过几千条就往上加我在一万条样本上通常调到500。initpca表示用PCA结果做初值收敛更快也避免随机起点带来的不稳定random_state要固定住否则每次跑的结果会有波动。LDA看起来也在做类似的事情——最大化类间距离、最小化类内距离但LDA假设各类共享同一协方差矩阵NCA没有这个强假设优化的是近邻分类概率。类别明显不平衡时LDA容易被大类带偏NCA直接对分类目标建模所以更适合下游是KNN的场景。4.3 度量学习与PCA在特征工程里的分工什么时候不要混用方法监督/无监督优化目标典型使用位置计算成本PCA无监督投影后方差最大特征压缩、可视化低MDS无监督保持距离矩阵距离结构可视化中LDA有监督类间/类内散度比分类前的线性降维低NCA有监督近邻分类正确概率KNN之前的距离学习高迭代优化怎么选没有标签只能上PCA有标签、下游是距离类模型度量学习优先特征高度相关且量纲混乱PCA加标准化能缓解相关性但学出一个带相关性的马氏距离更对症样本量只有几百时别急着上NCAPCA或LDA更稳。一句话PCA管压缩度量学习管“距离怎么算”两者并不互斥——实际中我会先用PCA做初筛压缩到合适维度再用NCA学度量管线里两个都出现。5. 降维与度量学习避坑指南5个最容易翻车的细节下面这些坑是我在一线项目和复现代码里真实踩过的也在帮人debug时反复见过。每条按现象、原因、解决三段式写方便你对照排查。5.1 忘了中心化PCA的第一主成分被均值带偏现象自己实现的PCA第一主成分和样本均值方向几乎重合并且解释的方差占比高得离谱。原因没有做中心化均值向量本身在 ( E[XX^T] ) 里占了大头PCA把“数据中心在哪”误当成了“数据往哪变”。解决在SVD前显式减去均值。用sklearn时默认没问题但用Spark、或者数据是one-hot之后的高维稀疏矩阵预处理里就要检查是否减了均值。这里有个容易混淆的点PCA里的中心化只减均值不除标准差。如果先除标准差再减均值顺序不同效果不同这就是后面要展开的坑。5.2 特征值分解的符号是个黑匣子坐标翻转但效果好现象同一份数据用不同机器学习库跑PCA主成分方向完全相反用自己的实现和sklearn对比某一列的数值符号全反。原因特征向量和奇异向量的符号本来就不唯一由底层线性代数算法决定跟数据本身没关系。解决不要追求主成分列符号一致要比较降维后的散点分布或下游指标。如果团队需要稳定输出固定random_seed并用符号约定比如把每个主成分中绝对值最大的分量统一为正再保存模型。另一个连带问题是部署时如果PCA保存的components和mean顺序不一致线上请求结果会整体镜像准确率却几乎不受影响这种“好用但莫名其妙”的现象很难排查本质就是符号不唯一。5.3 只看累计方差贡献率定维度90%阈值可能不够现象用90%阈值选了8维结果下游KNN还打不过原始特征。原因PCA是无监督方法只按方差排主成分而方差大的方向可能和分类标签无关当标签相关的信息被压缩到低方差方向上时固定阈值会丢掉关键信号。解决把PCA放进Pipeline用GridSearchCV直接搜n_components和下游模型参数以交叉验证分数选维度。同时保留一个不加PCA的基线防止“为了降维而降维”。5.4 标准化顺序错了先归一化再降维和先降维再归一化是两回事现象标准化加PCA的散点图和PCA加标准化的散点完全不同有人觉得这是玄学。原因PCA在第一步就改变了坐标系第二步再做标准化等于在一个已经被压缩过的空间里重新缩放特征间的相对距离已经被主成分重构过。解决确定顺序要看你把PCA当什么角色。若PCA是特征提取一般先标准化再PCA若PCA只是为了可视化中间特征先PCA再标准化也没有错但要保持全流程一致。建议在文档里写明“标准化到PCA到建模”这一根管线避免复现时顺序漂移。5.5 度量学习在小数据集上过拟合NCA训练分很高、测试分拉胯现象NCA在训练集上的KNN分数接近满分测试集比原始欧氏距离KNN还差。原因度量学习学到的投影矩阵自由度很高小数据集上容易记住训练对类别不平衡时损失函数被多数类主导。解决把n_components降到类别数减一的水平甚至更低用交叉验证同时调n_components、max_iter和KNN的k样本量少于1000时先试试LDA或PCA作为基线。scikit-learn的NCA没有单独的正则参数所以控制模型复杂度主要靠限制投影维度。如果数据量很大NCA训练时间会很长。max_iter小则欠拟合大则训练久。我一般对一两万条数据先随机抽样一小部分调参再全量训练能省一半时间。6. 验证降维效果的4个客观指标不再凭“可视化好看”拍板6.1 四个指标我见过太多人把降维效果等同于“画出来分开了”。聚类是分开的分类却有可能是乱的。所以我在验证降维结果时至少看四个指标累计方差贡献率、重构误差、下游KNN交叉验证、稳定性。前两个评估数据结构保留了多少后两个评估它对任务有没有用。累计方差贡献率用奇异值算np.cumsum(S**2) / np.sum(S**2)这个指标只衡量线性重构能力不代表分类效果。重构误差是线性空间里的保留程度PCA是线性重构重构误差越小说明原数据结构保留越多。交叉验证是任务绩效最有说服力。稳定性检查用来揪出样本量不足或特征噪声过大的问题随机抽不同子集拟合PCA两次比较主成分方向之间的余弦相似度或者对同一批样本计算两次投影后的坐标差异。6.2 一个验证脚本import numpy as np from sklearn.decomposition import PCA from sklearn.model_selection import cross_val_score from sklearn.neighbors import KNeighborsClassifier X_c X - X.mean(axis0) # 第一组重构误差随维度变化 for k in [2, 5, 10, 20]: pca PCA(n_componentsk) Z pca.fit_transform(X_c) recon_err np.mean((X - pca.inverse_transform(Z)) ** 2) print(k%d 重构误差%.4f 累计方差%.4f % ( k, recon_err, pca.explained_variance_ratio_.sum())) # 第二组降维后的KNN交叉验证分数 for k in [2, 5, 10, 20]: pca PCA(n_componentsk) X_k pca.fit_transform(X_train) scores cross_val_score(KNeighborsClassifier(n_neighbors5), X_k, y_train, cv5) print(k%d 交叉验证ACC%.4f % (k, scores.mean()))这个脚本把结构指标和任务指标放在一起看。如果重构误差下降很快但交叉验证分数不涨说明你保留的方差对分类没用如果两个都涨说明降维确实在帮你。参数说明k先画稀疏网格再细化cv5在样本量小时可以换成StratifiedKFold避免类别比例在折间抖动。这份23页讲义读到最后我最大的收获其实不是PCA公式而是把“距离”当成一个可以学的对象。有一年做特征筛选我只看重构误差选维度选出来的k让KNN在验证集上表现平平后来把交叉验证指标放进来才发现分类需要的是更少的维度。后来我养成了一个习惯任何降维步骤都会同时留一份不降维的基线用同一把尺子比较再看“可视化好看”到底有没有变成指标提升。希望帮到你。本文还有配套的精品资源点击获取