假如我们的数据集是n维的,共有m个数据(x,x
,...,x
)。我们希望将这m个数据的维度从n维降到k维,希望这m个k维的数据集尽可能的代表原始数据集。我们知道数据从n维降到k维肯定会有损失,但是我们希望损失尽可能的小。那么如何让这k维的数据尽可能表示原来的数据呢?
我们先看看最简单的情况,也就是n=2,K=1,也就是将数据从二维降维到一维。数据如下图。我们希望找到某一个维度方向,它可以代表这两个维度的数据。图中列了两个向量方向,u1和u2,那么哪个向量可以更好的代表原始数据集呢?从直观上也可以看出,u1比u2好。

可以有两种解释,第一种解释是样本点到这个直线的距离足够近,第二种解释是样本点在这个直线上的投影能尽可能的分开。
假如我们把K从1维推广到任意维,我们希望降维的标准为:样本点到这个超平面的距离足够近,或者说样本点在这个超平面上的投影能尽可能的分开。
PCA(主成分分析)是一种统计技术,它通过正交变换将一组可能相关的变量转换为一组线性不相关的变量,称为主成分。这些主成分按照方差的大小排序,第一个主成分具有最大的方差,第二个主成分具有第二大的方差,依此类推。以下是PCA的工作原理和关键步骤:
1. 标准化数据
由于PCA对数据的尺度敏感,因此在进行PCA之前,通常需要对数据进行标准化处理,使得每个特征的均值为0,标准差为1。这一步是必要的,因为PCA依赖于协方差矩阵,而协方差矩阵会因特征的尺度差异而产生偏差。
2. 计算协方差矩阵
在数据标准化之后,计算数据的协方差矩阵。协方差矩阵是一个方阵,其元素表示不同特征之间的协方差。对于数据集 XX(假设已经中心化),协方差矩阵 ΣΣ 定义为:

其中 n 是样本数量。
3. 计算协方差矩阵的特征值和特征向量
协方差矩阵的特征值和特征向量可以提供关于数据结构的重要信息。特征值表示每个特征的方向上的数据方差量,而特征向量表示这些特征的方向。
4. 选择主成分
根据特征值的大小,选择最大的 k 个特征值对应的特征向量。这些特征向量代表了数据中最重要的 k 个主成分。特征值的大小表示了每个主成分的重要性或方差贡献。
5. 构造新的特征空间
使用选定的特征向量作为新特征空间的基,将原始数据投影到这个新的特征空间上。这个过程实际上是将原始数据集转换为一个新的数据集,其中每个数据点由 k 个主成分的线性组合表示。
6. 解释结果
新数据集中的每个维度(即每个主成分)都代表了原始数据集中的某种结构或模式。通常,第一个主成分捕获了数据中最大的方差,第二个主成分捕获了第二大的方差,且与第一个主成分正交。
PCA的应用
- 降维:通过减少数据的维度来简化数据集,同时保留最重要的信息。
 - 数据可视化:将高维数据投影到二维或三维空间,以便可视化。
 - 去噪:通过去除小的特征值对应的成分来减少数据中的噪声。
 - 特征提取:在机器学习中,PCA可以作为预处理步骤,提取更有意义的特征。
 
下面是他的代码:
import numpy as np
from scipy.linalg import svd
class PCA:
    def __init__(self, n_components=None, whiten=False, copy=True):
        self.n_components = n_components
        self.whiten = whiten
        self.copy = copy
    def fit(self, X, y=None):
        X = np.asarray(X)
        if X.ndim == 1:
            X = X[:, np.newaxis]
        
        # Center the data
        self.mean_ = np.mean(X, axis=0)
        X = X - self.mean_
        # Perform SVD
        U, S, V = svd(X, full_matrices=False)
        if self.n_components is None:
            self.n_components_ = V.shape[1]
        else:
            self.n_components_ = min(self.n_components, V.shape[1])
        # Store the components
        self.components_ = V[:self.n_components_]
        # Store the explained variance
        self.explained_variance_ = S[:self.n_components_]**2 / X.shape[0]
        
        return self
    def transform(self, X):
        check_is_fitted(self)
        X = np.asarray(X)
        if X.shape[1] != self.mean_.shape[0]:
            raise ValueError("Shape of X is different from the shape used to fit the model")
        
        X = X - self.mean_
        return np.dot(X, self.components_.T)
    def fit_transform(self, X, y=None, **kwargs):
        self.fit(X)
        return self.transform(X)
    def inverse_transform(self, X):
        check_is_fitted(self)
        X = np.asarray(X)
        return np.dot(X, self.components_) + self.mean_
    def score_samples(self, X):
        X = self.transform(X)
        return np.sum(X**2, axis=1)
    def score(self, X, y=None):
        # This method is not implemented in this simplified version
        pass
# Helper function to check if the model is fitted
def check_is_fitted(model):
    if not hasattr(model, 'components_'):
        raise ValueError("Model is not fitted")
# Example usage
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data
pca = PCA(n_components=2)
X_r = pca.fit_transform(X)
print(X_r.shape)  # Output: (150, 2) 
1. 导入依赖
import numpy as np
from scipy.linalg import svd 
svd用于执行奇异值分解,我有讲过。
2. PCA 类定义
class PCA:
    def __init__(self, n_components=None, whiten=False, copy=True):
        self.n_components = n_components
        self.whiten = whiten
        self.copy = copy 
这是 PCA 类的构造函数,它初始化了几个重要的属性:
n_components:要保留的主成分数量。如果设置为None,则保留所有成分。whiten:是否进行白化处理,即是否将主成分的方差缩放到1。copy:是否在处理数据时复制数据,以避免修改原始数据。
3. 拟合方法
def fit(self, X, y=None):
    X = np.asarray(X)
    if X.ndim == 1:
        X = X[:, np.newaxis]
        
    # Center the data
    self.mean_ = np.mean(X, axis=0)
    X = X - self.mean_
    # Perform SVD
    U, S, V = svd(X, full_matrices=False)
    if self.n_components is None:
        self.n_components_ = V.shape[1]
    else:
        self.n_components_ = min(self.n_components, V.shape[1])
    # Store the components
    self.components_ = V[:self.n_components_]
    # Store the explained variance
    self.explained_variance_ = S[:self.n_components_]**2 / X.shape[0]
        
    return self 
fit 方法用于计算数据的PCA变换:
- 首先,将数据转换为 
numpy数组,并确保数据是二维的。 - 计算数据的均值,并中心化数据(减去均值)。
 - 执行SVD,得到 
U、S和V三个矩阵。 - SVD可以看之前的文章24/11/6 算法笔记 SVD-CSDN博客
 - 根据 
n_components的设置,确定保留的主成分数量。 - 保存主成分(
V矩阵的列)和解释的方差。 
主成分有什么用:
- PCA可以减少数据的维度,同时保留最重要的特征。这有助于去除噪声和冗余信息,使得数据集更易于管理和分析。
 
4. 转换方法
def transform(self, X):
    check_is_fitted(self)
    X = np.asarray(X)
    if X.shape[1] != self.mean_.shape[0]:
        raise ValueError("Shape of X is different from the shape used to fit the model")
        
    X = X - self.mean_
    return np.dot(X, self.components_.T) 
transform 方法用于将新数据投影到主成分上:
- 确保模型已经被拟合。
 - 将输入数据转换为 
numpy数组,并检查数据的形状是否与拟合时的数据一致。 - 中心化数据。
 - 将数据与主成分的转置矩阵相乘,得到降维后的数据。
 
5. 拟合和转换方法
def fit_transform(self, X, y=None, **kwargs):
    self.fit(X)
    return self.transform(X) 
fit_transform 方法结合了 fit 和 transform 方法,先拟合模型,然后将数据投影到主成分上。
6. 逆变换方法
def inverse_transform(self, X):
    check_is_fitted(self)
    X = np.asarray(X)
    return np.dot(X, self.components_) + self.mean_ 
7.样本得分函数
def score_samples(self, X):
    X = self.transform(X)
    return np.sum(X**2, axis=1) 
8.辅助函数
def check_is_fitted(model):
    if not hasattr(model, 'components_'):
        raise ValueError("Model is not fitted")
                

















