AI 工程地基 10|降维:方差是方差的排名,不是答案的排名
「AI 工程地基」系列第十篇。啃的是 GitHub 58k star 的开源课 ai-engineering-from-scratch,Rohit Ghumare 写的,一课一课来。今天第 10 课:降维,Build 型,90 分钟,前置是第 03 课特征值。这一课把特征向量从指纹用成扳手——784 维的数据,转个角…
「AI 工程地基」系列第十篇。啃的是 GitHub 58k star 的开源课 ai-engineering-from-scratch,Rohit Ghumare 写的,一课一课来。今天第 10 课:降维,Build 型,90 分钟,前置是第 03 课特征值。这一课把特征向量从指纹用成扳手——784 维的数据,转个角度,压到看得见的维度。
每个样本 784 个数,你画不出来。可一个手写的「7」根本不需要 784 个独立数字:笔画倾角、横杠长度、整体歪多少,剩下的全是噪声。降维就是找数据真正住的那个小面。方向感来自第 03 课:矩阵的指纹是特征向量,协方差矩阵的指纹,就是数据伸展最凶的那几个方向。
先看为什么必须压。高维空间有三件事反直觉。距离失效:随机点两两之间的距离会挤成同一个数,最近邻搜索失去意义。体积跑角落:100 维单位立方体,内切球的体积占比是 10 的负 70 次方,数据全摊在角上。数据需求爆炸:每维保持 10 个采样格,2 维要 100 个样本,20 维要 10 的 20 次方个——差 10 的 18 次方倍。你永远凑不齐,只能降维。
PCA 的做法是转坐标系。五步:减均值、算协方差、特征分解、按特征值排序、投影。协方差矩阵就是特征之间的联动表,特征分解找出互相垂直的主方向,第一根轴对准数据最胖的方向。数据在新坐标系下重新记账,每根轴抓走多少方差记在解释方差比里。课程那个示例表:PC1 抓 47.3%,PC2 抓 25.1%,四个成分累积到 92.5%——轴与轴之间的落差,就是冗余的形状。选几根轴有三种思路:凑够 90–95% 方差,看肘部,或者扫 k 值量下游精度。
PCA 只会转轴,不会弯。数据要躺在一个圆上,任何直线都切不开它。办法是把数据先扔进一个更高维的空间——核技巧,SVM 的老搭档。kernel PCA 在高维特征空间里做 PCA,坐标根本不用显式算出来,一个核矩阵全代劳。同心圆进去,内外两环出来就线性可分了。
还有一对为可视化而生的兄弟。t-SNE 只保一件事:谁和谁挨着。原空间里算点对的概率,近的大概率,远的小概率,然后在 2D 里找一个让这概率分布还成立的摆法。簇内距离有意义,簇间距离没有。UMAP 思路相近,用近似最近邻图提速,簇与簇的相对位置也更有含义。课程给了张选型表,经验法则一句话:预处理和压缩用 PCA,看结构用 t-SNE 或 UMAP。
压得好不好,重构误差说了算。投影到 k 维再投回来,和原图比一比,均方差就是丢掉的东西。PCA 这里有个干净的关系:重构误差等于被丢掉的特征值之和除以维度。丢的总能算得清清楚楚,这是 PCA 区别于 t-SNE 的地方——t-SNE 没有逆变换,它只负责让你看。
过一遍海关。先说干净的部分,这一课干净得少见。所有代码逐字可跑:合成圆数据 3 维压 2 维,抓走 99.93% 方差(第三维本来就是前两维的线性组合加噪声,PCA 精确识别);t-SNE 在 5000 个 MNIST 样本上 42.7 秒跑完,同数字的中位距离 18.1、异数字 65.0,3.6 倍分离;UMAP 装上也能跑。最值得点名的是 Step 4 的对账设计:手写 PCA 对 sklearn,解释方差比六位小数一致,坐标最大差 2.66e-15,机器精度。而且它用了 abs(abs(a)-abs(b)) 这个看着古怪的比法——特征向量的正负号是自由的,同一个解乘个负号还是同一个解,直接相减会被符号翻转骗到(实测 raw 差值是 8.36),先取绝对值再比,才是把符号自由度处理干净的真对账。上一课刚说过「同一公式跑两遍没有对账力」,这一课的写法恰好是正面教材:跨实现、跨自由度。
重构误差恒等式也验了。MNIST 上 50 个成分压回 784 维,均方差 746.0020;被丢掉的 734 个特征值之和除以 784,是 746.1512——差着 0.02%,差在哪?np.cov 除的是 n-1,恒等式要的是除以 n 的总体方差,乘上 4999/5000 之后精确吻合。同课两处口径差一个自由度修正,教学无伤,账目上值得记一笔。
然后是四处要拦的。第一处,维度灾难那张距离比表:2 维约 5.0,1000 维约 1.02。数字能复现——我用均匀立方体里 5 个随机点跑了几十组种子取中位数,四个维度给出 4.94、1.70、1.19、1.05,对得上。但表格没写点数,而这个比值极度依赖点数:同样口径换成 100 个点,2 维的比值是 134,不是 5。「约 5.0」不是 2 维空间的属性,是 5 个点的属性。趋势是真的(比值确实收敛到 1),四个数字没有户口页。
第二处就一句话的事:t-SNE「默认 O(n²)」。课程代码用的 sklearn,而 sklearn 的 TSNE 默认 method='barnes_hut',文档原话是 O(N log N);O(N²) 是 method='exact' 才有的价签。「默认」两个字写反了。这也是这课的一个隐性优点:正因为默认不是 O(n²),5000 样本才能 43 秒跑完。
第三处最有意思,出在「用起来」那节。课程说 k 值扫上去,性能远在 784 维之前就走平,走平处就是工作点。我把它的代码逐字跑了:k=10 精度 81.5%,k=30 是 89.1%,k=50 达到 89.7%——然后掉头,k=100 是 85.5%,k=200 是 87.0%,原始 784 维也不过 87.7%。不是平台,是驼峰,峰在 50。同一时间方差单调上涨:49.4%、73.6%、82.9%、91.8%、96.9%、100%。方差一路涨,精度过了 50 维就往下走。当然得说清楚另一件事:k 到 100 以后屏幕上全是收敛警告,课程给的 max_iter=1000 在高维上根本没跑完,掉头里有欠收敛的份。但「走平」的叙事在课程自己的代码和参数下不成立,这是实测事实。方差是方差的排名,不是答案的排名。
顺着这条缝,练习 3 的预设也塌了。题目说:造一个 50 特征、其中 5 个有信息量的数据集,跑 PCA,看解释方差曲线能不能识别出数据「实际上是 5 维的」。我跑了 make_classification 这个官方配方:前十个成分的解释方差比是 0.080、0.056、0.028、0.027、0.026、0.025——第 6 个成分和第 3 个一样胖,根本没有肘部。5 个成分只抓 21.8% 方差,要凑到 90% 得用上 42 个成分。原因藏在数据生成器里:make_classification 的「有信息」是按类别可分性设计的,不是按方差设计的,45 个纯噪声特征每个都贡献约 2% 的方差,把曲线摊平了。「数据实际上是 5 维的」这句话只在信息口径下成立,在方差口径下它就是 50 维。PCA 只看方差。这道练习最好的教法不是确认曲线能识别,而是让学生亲手撞一次识别不出来。
第四处在 kernel PCA。同心圆的例子课程说对了:标准 PCA 投影下来,内环和外环在第一主成分上的区间完全重叠,阈值切分精度 51%,跟抛硬币打平;RBF 核的 kernel PCA 确实能分开。但 γ 是只没写的旋钮。同一个 RBF 核:γ=5,两环完美分离,精度 100%;γ=1,50.5%,还是硬币;γ=10,58%,又塌回去。γ 太小,核太宽,所有点看起来一样近;γ 太大,核太窄,每个点只跟自己玩。课程给了公式 exp(-γ‖x−y‖²),一个字没提 γ 怎么选——而它单方面决定成败。
口号审计收尾。这门课开篇说:高维数据有结构,找对角度就能看见。帽子底下藏着一句没说破的话——PCA 里「对」的角度,被定义成方差最大的角度。方差最大的方向,不一定是你关心的方向。k 扫描的驼峰、练习 3 的平曲线、0.95 阈值要 148 维而 50 维只给 82.9%,全是同一句话的注脚。要看见的如果是类别结构,你还得借 t-SNE 的眼睛,或者把 γ 拧到 5。