2. CCA原理
好,我们来啃CCA这块硬骨头。说实话刚开始接触CCA时,我对着数学推导看了好几天才明白它在干嘛。但一旦搞懂,你会发现它其实就是把两个数据集之间的关系“拧”成一个可优化的问题。咱们一步步来。
CCA到底想干嘛?
典型相关分析(Canonical Correlation Analysis)本质上是找两组变量之间的线性组合,使得组合后的相关系数最大化。你想想看,假设你有一组基因表达矩阵X和一组蛋白质丰度矩阵Y,你想知道它们之间有没有“整体”的关联——CCA就是干这个的。
它不关心单个基因和单个蛋白的关系,它要找的是投影方向:把X投影到一个向量u上,把Y投影到向量v上,然后让投影结果的相关性最大。嗯,这里有个关键点:你找的不是一组(u,v),而是一系列相互正交的投影对,就像PCA那样。
数学基础:协方差结构
既然是相关性,那自然离不开协方差矩阵。假设X是n×p矩阵(n个样本,p个变量),Y是n×q矩阵。我们定义:
- ΣXX = Cov(X) —— X的自协方差矩阵(p×p)
- ΣYY = Cov(Y) —— Y的自协方差矩阵(q×q)
- ΣXY = Cov(X,Y)—— X和Y的互协方差矩阵(p×q)
CCA要做的就是在线性组合 a = uTX 和 b = vTY 之间,最大化它们的皮尔逊相关系数:
ρ = corr(a, b) = (u<sup>T</sup> Σ<sub>XY</sub> v) / sqrt( (u<sup>T</sup> Σ<sub>XX</sub> u) (v<sup>T</sup> Σ<sub>YY</sub> v) )
我当年第一次看到这个式子觉得头大,但其实它就是个广义瑞利商,跟PCA里的瑞利商一个味道,只是分母多了两组方差约束。
目标函数:最大化相关系数
为了让优化问题有唯一解,我们通常约束投影后的方差为1,即:
u<sup>T</sup> Σ<sub>XX</sub> u = 1
v<sup>T</sup> Σ<sub>YY</sub> v = 1
这样一来目标函数就简化为:
maximize u<sup>T</sup> Σ<sub>XY</sub> v
subject to u<sup>T</sup> Σ<sub>XX</sub> u = 1, v<sup>T</sup> Σ<sub>YY</sub> v = 1
怎么做?拉格朗日乘子法。定义拉格朗日函数:
L = u<sup>T</sup> Σ<sub>XY</sub> v - (λ/2)(u<sup>T</sup> Σ<sub>XX</sub> u - 1) - (μ/2)(v<sup>T</sup> Σ<sub>YY</sub> v - 1)
对u和v求偏导置零,得到:
Σ<sub>XY</sub> v = λ Σ<sub>XX</sub> u (1)
Σ<sub>YX</sub> u = μ Σ<sub>YY</sub> v (2)
由(1)左乘uT、由(2)左乘vT,再利用约束条件,可以推出 λ = μ = uT ΣXY v = ρ。也就是说,拉格朗日乘子就是相关系数本身。
特征向量求解:SVD视角
实际工程中我们不会手撕拉格朗日方程,而是用SVD一步到位。先做“白化”变换:定义
K = Σ<sub>XX</sub><sup>-1/2</sup> Σ<sub>XY</sub> Σ<sub>YY</sub><sup>-1/2</sup>
然后对K做奇异值分解:
K = U D V<sup>T</sup>
其中D是对角矩阵,元素d1 ≥ d2 ≥ ... ≥ 0,它们正是相关系数ρ。而投影向量u和v可以通过逆变换恢复:
u = Σ<sub>XX</sub><sup>-1/2</sup> U<sub>[:,1]</sub>
v = Σ<sub>YY</sub><sup>-1/2</sup> V<sub>[:,1]</sub>
第一对典型变量就对应最大的奇异值。后续的典型变量通过对U、V取第2、3……列得到,且彼此正交。
CCA核心流程图
实际计算中的注意事项
CCA求解其实只涉及矩阵运算,但工程实现时有几个坑。我列一下:
| 步骤 | 典型问题 | 我的建议 |
|---|---|---|
| 计算协方差 | 数据未中心化,结果偏差 | 一定先对X、Y各列减去均值 |
| 矩阵求逆/开方 | 奇异矩阵导致无穷大 | 用伪逆或加正则项(如0.001I) |
| SVD分解 | K矩阵数值不稳定 | 先用PCA降到d=min(p,q,n-1)维 |
| 多对典型变量 | 正交性丢失 | 用SVD自然保证正交,别手工迭代 |
总结一下CCA的数学骨架:最大化投影相关性 → 拉格朗日乘子 → 广义特征值 → SVD一步到位。你只要记住这个链条,在实际代码里调个np.linalg.svd就完事了。下一节我们会动手用Python实现CCA,顺便看看它在MNN批次校正中到底起了什么作用。