主成分分析(PCA)是一种线性降维算法,它将数据的变化由高维压缩到更少的维度(主成分)。

设想存在这样一个情景:存在p个不同的菌,每个菌有n个基因,每个基因都有一个表达量,现在想要知道:

1、根据基因的表达量,哪些菌是更相近的?

2、不同的菌是否呈现轨迹变化?

这对应了PCA两种常用的功能:数据的质量控制和结构探索。

简单了解:

在绪论的情景中,我们当然可以画一个三维图片,x轴和y轴代表不同的菌和不同的基因,z轴代表这些基因的表达量。但是它的可读性、直观性和美观性都会非常差。所以我们使用PCA去处理这种问题。

在最常见的场景,一个PCA的结果往往被可视化为一个散点图。其横坐标和纵坐标分别一般被命名为PC1和PC2。

以绪论中的情景为例,在p×n个变量中,PC1的含义是数据中最大变异模式对应的线性方向,代表最主要的结构性差异来源;PC2代表在排除 PC1 后剩余变化中最显著的正交模式,代表第二层结构性差异。

如果还是难以理解,我们可以将绪论的问题简化为p个菌中2个基因的表达量。这样对于每个菌都有两个基因和其表达量,我们可以将每个菌两个表达量变为两个坐标,即x坐标和y坐标,这样每个菌就变为了一个二维图像内的点(x,y)。PCA的核心就是**在这个二维平面中寻找一条线,使得所有点投影到该方向上的方差最大;第二条线是在与第一条线垂直的前提下,使投影方差最大的方向。**这两条方向即分别对应 PC1 和 PC2。

简言之,PC1代表最主要的差异,PC2代表次主要的差异。在绪论的例子中,可视化的散点图共有p个点,每个点PC1 或 PC2 的数值,本质上是: **这个样本(菌)在某种变化趋势上的得分。**数值越大,说明越符合该趋势; 数值越小(或为负),说明越偏向相反趋势。

PC1和PC2后往往带有一个百分比数字,如PC1(45%)、PC2(30%),这代表可解释性,即差异最大的方向解释了45%的数据,次大的方向解释了30%的数据,那么理论上PC1+PC2的数据(即从散点图上的数据)理论上可以重现45%+35%=80%的原始数据。

深度了解:

下列内容涉及高等代数知识,但实操方法部分仅需要本科线性代数知识

以绪论的内容为例,给定任意一个p个样本、n个观测的数据对象,可以构成如下样本观测矩阵x

$$ \mathbf{x} =\begin{bmatrix} x_{11}&x_{12} &\dots &x_{1n} \\ x_{21} & x_{22}& \dots&x_{2n} \\ \vdots &\vdots & \ddots &\vdots \\ x_{p1} &x_{p2} & \dots &x_{pn}\end{bmatrix} $$

1、基本样本统计量

先让我们了解一些基本量

定义1 样本均值(μ)、样本方差(σ2):给定某个样本x的n个观测量

$$ x_{1},x_{2},\dots ,x_{n} $$

分别称

$$ \mu = mean(\mathbf{x} ) = \frac{1}{n} \sum_{i=1}^{n}x_{i}= \frac{1}{n}\mathbf{1} ^{\top} \mathbf{x}\\ \sigma ^{2}=var(x)=\frac{1}{n}\sum_{i=1}^{n} (x_{i}-\mu)^{2}=\frac{1}{n}(\mathbf{x} -\mu \mathbf{1} )^{\top }(\mathbf{x} -\mu \mathbf{1} )\\ $$

为随机变量的样本均值(简称均值)和样本方差(简称方差),其中

$$ \mathbf{x} =\left [ x_{1} \quad x_{2}\quad\cdots \quad x_{n}\right ] ^{\top},\mathbf{1} =\left [ 1 \quad 1\quad\cdots \quad 1\right ] ^{\top} $$

定义2 样本协方差(cov):给定某个样本x的n个观测量以及另一个样本y的n个观测量

$$ x_{1},x_{2},\cdots ,x_{n} \\y_{1},y_{2},\cdots ,y_{n} $$

$$ cov(\mathbf{x},\mathbf{y})=\frac{1}{n} \sum_{i=1}^{n} (x_{i}-\mu_{x})(y_{i}-\mu_{y})=\frac{1}{n}(\mathbf{x} - \mu_{x}\mathbf{1})^{\top}(\mathbf{y} - \mu_{y}\mathbf{1}) $$

为样本x和y的样本协方差(简称协方差),其中x1定义如上,y定义与x相同,这里不做赘述。

显而易见的,对p个样本,应该有p×p个协方差。

定义3 样本均值向量(μ)和样本协方差矩阵(Σ) :给定p维样本向量Xn个观测值(即p×n个样本),称

$$ \boldsymbol{\mu }=mean(\mathbf{x })=\begin{bmatrix} mean(\mathbf{x_{1} })\\mean(\mathbf{x_{2} }) \\ \vdots\\mean(\mathbf{x_{p} })\end{bmatrix}=\begin{bmatrix} \mu_{1}\\ \mu_{2}\\\vdots \\\mu_{p}\end{bmatrix}=\frac{1}{n}\mathbf{x1} $$

为样本向量X的样本矩阵x样本均值向量(简称均值向量或均值)。其中

$$ \mu _{i}=mean(\mathbf{\mathit{X_{i}} } )=\frac{1}{n} \sum_{k=1}^{n} x_{ik}=\frac{1}{n}\mathbf{1}^{\top} \mathbf{x} _{i},i=1,2,\cdots ,p $$

为样本Xi的均值。

根据上面协方差的定义,对p个样本,计两两之间的协方差为

$$ \sigma_{ij}= cov(\mathbf{\mathit{X} }_{i}, \mathbf{\mathit{X} }_{j} )=\frac{1}{k} \sum_{i=1}^{k} (\mathbf{\mathit{X} }_{i}-\mu_{i})(\mathbf{\mathit{X} }_{j}-\mu_{j}),i,j=1,2,\cdots ,p $$

$$ \Sigma =cov(\mathbf{x} )=\begin{bmatrix} \sigma _{11} &\sigma _{12} &\dots &\sigma _{1p}\\\sigma _{21} &\sigma _{22} &\dots &\sigma _{2p}\\ \vdots &\vdots &\ddots &\vdots \\\sigma _{p1} &\sigma _{p2} &\dots &\sigma _{pp}\end{bmatrix}\\=\begin{bmatrix} cov(X_{1},X_{1}) &cov(X_{1},X_{2}) &\dots &cov(X_{1},X_{p})\\cov(X_{2},X_{1}) &cov(X_{2},X_{2}) &\dots &cov(X_{2},X_{p})\\ \vdots &\vdots &\ddots &\vdots \\cov(X_{p},X_{1}) &cov(X_{p},X_{2}) &\dots &cov(X_{p},X_{p})\end{bmatrix}\\=\frac{1}{n} (\mathbf{x}-\boldsymbol{\mu1}^{\top } )(\mathbf{x}-\boldsymbol{\mu1}^{\top } )^{\top}=\frac{1}{n} (\mathbf{xx}^{\top}-\boldsymbol{\mu\mu}^{\top}) $$

为样本向量X的样本观测矩阵x样本协方差矩阵(简称x的协方差矩阵)

2、定义任意的方向方差

由前面的内容可以得到,给定的p个样本、n个观测的样本观测矩阵x,PCA的核心目标是寻找一个能够最大程度保留数据方差信息的投影方向。

为了简化运算,我们通过中心化处理将坐标原点平移至样本均值,使数据的均值向量为零。则对于任意方向向量u,观测数据在任意方向u上的投影方差为

$$ var(\mathbf{u}^{\top} \mathbf{x})=\frac{1}{n} \mathbf{u}^{\top} \mathbf{x}(\mathbf{u}^{\top} \mathbf{x})=\mathbf{u}^{\top}(\frac{1}{n}\mathbf{x}\mathbf{x}^{\top} )\mathbf{u}=\mathbf{u}^{\top}\Sigma\mathbf{u} $$

这个公式是如此的优美。它表明,数据在任意方向的方差都可以通过协方差矩阵Σ以及方向向量u以双线性的形式表达。

3、求解方向向量

对于刚才的公式可以发现,不对向量u的长度加以限制的话,var将随着u长度的增加而增加,并且上限趋于无穷大。因此,为了消除向量u的长度的影响,需要将其限制为单位向量,这便有了如下主成分分析的优化模型

$$ \left\{\begin{matrix} \underset{u}{\max}\mathbf{u}^{\top}\mathbf{\Sigma u} \\ \mathrm{s.t.} \ \mathbf{u}^{\top}\mathbf{u}=1 \end{matrix}\right. $$

显然,该模型为等式约束下的目标函数的极值问题,可以用拉格朗日乘数法构建如下目标函数

$$ \mathcal{L}(\mathbf{u},\lambda ) =\frac{1}{2} \mathbf{u}^{\top}\mathbf{\Sigma u}+\frac{\lambda }{2}(1-\mathbf{u}^{\top}\mathbf{u}) $$

两侧对u求导,得到

$$ \frac{\partial \mathcal{L}(\mathbf{u},\lambda ) }{\partial \mathbf{u}} =\mathbf{\Sigma u} -\lambda \mathbf{u} $$

由于L(u,λ)在极值点处的偏导数必定为零向量,因此可得

$$ \mathbf{\Sigma u} =\lambda \mathbf{u} $$

神奇的事情出现了,上述公式突然变成了协方差矩阵Σ的特征值与特征向量问题,我们可以很简单的求得u和λ。

4、进行主成分分析

在得到数据协方差矩阵Σ的所有特征值λ和特征向量u之后,即可对原始数据进行主成分分析。

$$ \mathbf{Y}= \mathbf{U}^{\top}\mathbf{x}\\ \mathbf{U}=\begin{bmatrix} \mathbf{u}_{1} & \mathbf{u}_{2} &\cdots &\mathbf{u}_{p}\end{bmatrix}; \mathbf{Y}=\begin{bmatrix} \mathbf{Y}_{1}\\\mathbf{Y}_{2} \\ \cdots\\\mathbf{Y}_{p}\end{bmatrix} $$

其中U为特征向量u构成的特征向量矩阵,Yi (i=1,2,…,p)称作x的第i个主成分。

假设协方差矩阵的p个特征值满足λ1 ≥ λ2 ≥ … ≥ λp,基于协方差矩阵Σ与特征值λ和特征向量u的关系可得

$$ \mathbf{u}_{i}^{\top} \boldsymbol{\Sigma}\mathbf{u}_{i}=\lambda _{i}\mathbf{u}_{i}^{\top}\mathbf{u}_{i}=\lambda _{i} $$

即协方差矩阵的第i个特征值λi正是数据在第i个特征向量方向上的方差。因此,可以定义数据前k个主成分的贡献率为

$$ \eta (k)=\frac{\lambda _{1}+\lambda _{2}+\cdots +\lambda _{k}}{\lambda _{1}+\lambda _{2}+\cdots +\lambda _{p}} $$

在应用中,可以根据给定的贡献率来确定需要保留的主成分的个数。

实操

接下来,我们通过一个简单的例子,来熟悉主成分分析的主要流程。

对以下一组观测数据进行PCA分析:

$$ \mathbf{x} =\begin{bmatrix} 1 & 2 &3 \\ 1& 1&3 \end{bmatrix} $$

显然,该数据可以视为二维平面上的三个点(即 p = 2、n = 3),坐标为(1 , 1)、(2 , 1)、(3 , 3)

步骤 1 观测矩阵中心化:

首先来计算均值向量μ

$$ \boldsymbol{\mu}= \frac{1}{3} \mathbf{X1} =\frac{1}{3}\begin{bmatrix} 1 & 2&3 \\ 1& 1 &3 \end{bmatrix}\begin{bmatrix} 1\\ 1\\ 1 \end{bmatrix}=\begin{bmatrix} 2\\ \frac{5}{3} \end{bmatrix} $$

对数据中心化:

$$ \mathbf{X} =\mathbf{X}-\boldsymbol{\mu1}^{\top }\begin{bmatrix} -1 & 0 & 1\\ -\frac{2}{3} & -\frac{2}{3} & \frac{4}{3} \end{bmatrix} $$

步骤2 计算协方差矩阵

计算协方差矩阵为:

$$ \boldsymbol{\Sigma}=\frac{1}{3}\mathbf{XX}^{\top } =\frac{1}{9}\begin{bmatrix} 6 & 6\\ 6&8 \end{bmatrix} $$

步骤 3 计算特征矩阵和特征值

对协方差矩阵进行特征分解

$$ \boldsymbol{\Sigma}=\mathbf{U} \boldsymbol{\Lambda }\mathbf{U}^{\top }\\\boldsymbol{\Lambda }=\begin{bmatrix} \lambda _{1}&0 \\ 0 &\lambda _{2} \end{bmatrix}=\begin{bmatrix} 1.4536 & 0\\ 0&0.1019 \end{bmatrix},\mathbf{U}=\begin{bmatrix} 0.6464 & -0.7630\\ 0.7630&0.6464 \end{bmatrix} $$

步骤 4 主成分变换

使用特征向量对数据进行主成分变换:

$$ \mathbf{Y}= \mathbf{U}^{\top }\mathbf{x}=\begin{bmatrix} -1.1551 &-0.5087 & 1.6637\\ 0.3321 & -0.4309 &0.0988 \end{bmatrix} $$

其中矩阵的第一行为Y1,及第一主成分;第二行为Y2,及第二主成分。

由步骤 3可得,两个特征值分别为λ1 = 1.4536,λ2 = 0.1019,故第一主成分的贡献率为:

$$ \eta _{1}=\frac{\lambda _{1}}{\lambda _{1}+\lambda _{2}} =\frac{1.4536}{1.4536+0.1019} =0.9345 $$

即第一主成分可以解释93.45%的内容,数据主要分布在第一主成分,只需要一个维度就可以描述这组数据,即 (-1.1551 , -0.5087 , 1.6637)。维度由二维降到了一维。

代码运行

下面给出一段python代码用于计算一组数据的PCA

import numpy as np

def main():
    # 1. 输入数据
    p = int(input("输入变量个数 p: "))
    n = int(input("输入观测个数 n: "))
    print(f"\n输入 {p} 行数据,每行 {n} 个数字,用空格分隔:")    
    X = []
    for i in range(p):
        row = list(map(float, input(f"第 {i+1} 行: ").split()))
        if len(row) != n:
            raise ValueError("输入数据长度与 n 不一致")
        X.append(row)   
    X = np.array(X)    
    print("\n观测矩阵 X:")
    print(X)  
    
    # 2. 计算样本均值向量 μ
    mu = np.mean(X, axis=1, keepdims=True)   
    print("\n样本均值向量 μ:")
    print(mu)
    
    # 3. 中心化
    X_centered = X - mu    
    print("\n中心化后的矩阵:")
    print(X_centered)
    
    # 4. 计算协方差矩阵
    Sigma = (1/n) * X_centered @ X_centered.T   
    print("\n协方差矩阵 Σ:")
    print(Sigma)
    
    # 5. 求解特征值和特征向量
    eigenvalues, eigenvectors = np.linalg.eig(Sigma)
    print("\n特征值 λ:")
    print(eigenvalues)   
    print("\n特征向量 U (每一列是一个特征向量):")
    print(eigenvectors)
    
    # 6. 按特征值从大到小排序
    idx = np.argsort(eigenvalues)[::-1]
    eigenvalues = eigenvalues[idx]
    eigenvectors = eigenvectors[:, idx]
    print("\n排序后的特征值:")
    print(eigenvalues)
    print("\n排序后的特征向量矩阵 U:")
    print(eigenvectors)
    
    # 7. 主成分
    Y = eigenvectors.T @ X_centered
    print("\n主成分矩阵 Y:")
    print(Y)
    
    # 8. 贡献率
    total_variance = np.sum(eigenvalues)
    contribution_ratio = eigenvalues / total_variance   
    print("\n各主成分贡献率:")
    print(contribution_ratio)
    cumulative = np.cumsum(contribution_ratio)
    print("\n累计贡献率:")
    print(cumulative)
    print("\n===== 计算完成 =====")
    
if __name__ == "__main__":
    main()