机器学习之PCA降维

发布于:2025-08-16 ⋅ 阅读:(20) ⋅ 点赞:(0)

目录

引言

一、PCA 的核心思想

二、PCA 的数学原理

三、为什么要进行主成分分析

1.数据可视化

 2.数据压缩

 3.特征选择

4.数据预处理

四、PCA中的数学

1、基

2、基变换

五、最优基的选择

1、如何才能保留较多的原始数据信息?

2、方差公式

3、多维数据降维

4、协方差

5、协方差矩阵对角化

6、求解步骤

六、参数解析

1.参数

2、属性

七、代码调用

1. 导入必要的库

2. 读取数据并提取特征与标签

3. 数据标准化

4. 使用 PCA 进行降维

5. 划分数据集并训练降维后的逻辑回归模型

6. 用原始数据训练逻辑回归模型(对比实验)

核心目的与结论

完整代码及运行结果如下:


引言

在处理高维数据时,我们经常会遇到 "维度灾难"—— 随着特征数量增加,数据稀疏性上升,模型训练难度增大,甚至可能出现过拟合。主成分分析(PCA)作为一种经典的降维方法,能够在保留数据核心信息的前提下,有效降低特征维度,是数据预处理中的重要工具。本文将从原理到实战,全面解析 PCA 降维技术。

一、PCA 的核心思想

PCA 的本质是通过线性变换将高维数据映射到低维空间,同时尽可能保留数据中的 "重要信息"。这里的 "重要信息" 用方差来衡量 —— 方差越大,说明数据在该方向上的分布越分散,包含的信息量越多。

举个直观的例子:假设我们有一批二维数据点,这些点大致分布在一条直线附近。此时我们可以用每个点在这条直线上的投影值来表示该点(将二维降为一维),因为投影值已经包含了原始数据的大部分信息。这条直线就是我们要找的 "第一主成分"。

PCA 的目标就是找到这样一组相互正交的主成分,使得数据在这些主成分上的投影方差最大化。通常我们只保留前 k 个主成分,即可实现将 n 维数据降为 k 维(k < n)。

二、PCA 的数学原理

PCA 的实现过程可以概括为以下步骤:

  1. 数据标准化:对原始数据进行均值归一化(使各特征均值为 0)和方差缩放(使各特征方差为 1),因为 PCA 对数据的尺度敏感。
  2. 计算协方差矩阵:协方差矩阵描述了特征之间的相关性,对于 n 维数据,协方差矩阵是一个 n×n 的对称矩阵。
  3. 特征值分解:对协方差矩阵进行特征值分解,得到特征值和对应的特征向量。特征值表示对应主成分的方差大小,特征向量则是主成分的方向。
  4. 选择主成分:将特征值从大到小排序,选择前 k 个最大的特征值对应的特征向量,组成投影矩阵。
  5. 数据投影:将标准化后的原始数据与投影矩阵相乘,得到降维后的 k 维数据。

注:在实际实现中,当数据维度很高时,通常会采用 SVD(奇异值分解)代替特征值分解,因为 SVD 在计算效率上更有优势。

三、为什么要进行主成分分析

1.数据可视化

主成分分析可以将高维数据映射到二维或三维空间中,在可视化分析中提供更好的观察和理解数据的能力。通过将数据投影到较低维度的空间,我们可以更容易地检测数据的分布、聚类和异常值。

 2.数据压缩

主成分分析可以通过保留较高方差的主成分,将数据从高维度降低到低维度。这有助于减少存储空间和计算成本,并提高数据处理效率。

 3.特征选择

主成分分析可以帮助识别出在数据中具有最大方差的特征。通过选择具有较高方差的主成分,我们可以从原始数据中提取出最重要的特征。

4.数据预处理

主成分分析可以用于数据预处理的步骤。通过将数据投影到较低维度的空间中,我们可以减少噪声和冗余数据,从而改善后续的数据分析和建模结果。

四、PCA中的数学

1、基

什么是基?基,也称基底,是描述、刻画向量空间的基本工具。图像表示:

                  

 如图所示,横纵坐标轴叫做基底单位向量(0,1)和(1,0)叫做这个二维空间的一组基,而向量A的坐标完全由这组基来表示,如下所示:

                                   

   注意:这里的x和y和之前的表示不同,之前的是前面多少列为特征集,有一列作为标签列,而现在的则是将原本的数据做了一个转置,得到现在的,前多少行为特征集,有一列作为标签行。即,一列表示一条数据。

      所以这里表示的是这一组基乘以x方向的长度与y方向上的长度,最终得到的值就是这个向量在当前的基底表示的坐标为(3,2)

什么样的向量可以成为基?

任何两个线性无关二维向量都可以成为一组基,即表示两组向量互相垂直就是线性无关,如下图所示,蓝色线表示一组新的基底,原本的向量映射在不同基底上对应的坐标不同。

什么样的向量可以成为基?

任何两个线性无关二维向量都可以成为一组基,即表示两组向量互相垂直就是线性无关,如下图所示,蓝色线表示一组新的基底,原本的向量映射在不同基底上对应的坐标不同。

                  

2、基变换

基变换的本质: 在保持向量空间结构(线性运算、内积等)不变的前提下,通过可逆矩阵实现同一向量 / 线性变换在不同基下的坐标表示转换。基变换不改变向量的 “本质”(如长度、夹角),仅改变其坐标表示。

           

  如图所示,将原本的(x1,x2)基底变换为(y1,y2),此时新的坐标系单位向量坐标则不能表示为(1,1),而应该表示为( 1/\sqrt{2}1/\sqrt{2} )

    此时新的基底的一组基则表示为( 1/\sqrt{2}1/\sqrt{2} ),( -1/\sqrt{2}1/\sqrt{2} ),如下图所示

此时坐标虽然变了,但是模没变,长度还是原来的长度,但是表达方式改变了

    

将(3,2)映射到新的基上,由于新的基的模是1,所以直接相乘就能得到(3,2)在新的的坐标系(紫色坐标系)下的坐标,如下:

  多个二维向量变换

   其计算方式和上述类似,同样是将新的基底的一组基乘以多个二维向量,得到的新的坐标:

        此时三组向量(1,1),(2,2),(3,3),他们在新的坐标系中表示的坐标为上图所示的状态

多个坐标转换到新的坐标系

 上图表示的是n维的向量向R维空间映射,作内积得到的结果为R行m列,因为基的第一行乘以特征第一列得到一个结果,第一行乘以第二列得到第二个几个,然后一行乘以所有的列,得到结果的一行,而基有R行,乘以m列后,得到的结果就为R行m列,即将原本的维度为n的数据,转变成了维度为R的数据,这就是卷积,即一个矩阵和另一个矩阵相乘,将一个矩阵变换到新的空间中。

五、最优基的选择

1、如何才能保留较多的原始数据信息?

如下所示有一堆数据点,将他们映射到新的空间后投影到坐标轴上得到的状态:

     

 此时可以发现,投影到不同的轴上的数据点紧密程度不同,从而得到投影的轴上的点的分布状态,而数据的离散程度可以用方差来表示,方差越大则表示数据的离散程度越大,如果我们取方差小的当做新的数据,此时点之间的排布非常紧密,很多个点重合在一起,此时则无法满足较多的保存原始数据,所以我们更需要离散程度更高的当做新的数据。

2、方差公式

3、多维数据降维

对于多次降维,以 三维数据来说,将数据降到二维,寻找的基为 方差最大 的方向,再次将数据降到一维, 必然与降到二维的基无限接近。依次寻找方差次大的基,如果两个基基本重合, 这样的一个基是没有用的,我们应该让两个基 线性无关 。【线性无关才能保留更多的原始信息】

4、协方差

   1、含义

         协方差表示两个字段之间的相关性,当协方差为0时,表示两个字段完全独立。公式如下:

  2、目标

      利用协方差为0,选择另一组基,这组基的方向一定与第一组基正交。

3、协方差矩阵

  假设我们有a,b两个字段,组成矩阵:X,用X乘以其转置,并乘以1/m:

此时得到的即为协方差矩阵,可以发现,其对角线上的数据为方差,斜对角线上的数据为协方差,此时如果协方差的值为0,则表示两个字段完全独立。

5、协方差矩阵对角化

(1)含义
原始数据:X —>协方差矩阵:C
一组基按行组成的矩阵:P
基变换后的数据:Y—>协方差矩阵:D
隐含信息:Y = PX
(2)优化目标
寻找一个矩阵P,满足PC P的转置是一个对角矩阵,并且对角元素按从大到小依次排列,那么P的前K行就是要寻找的基,用P的前K行组成的矩阵乘以X就使得X从N维降到了K维并满足上述优化条件。
协方差矩阵C对角化:

实对称矩阵: 矩阵转置等于其本身均为0

对角化: 除主对角线之外其余元素

6、求解步骤

 1)将原始数据按列组成n行m列矩阵X;

  2)将X的每一行(代表一个属性字段)进行零均值化,即减去这一行的均值

  3)求出协方差矩阵:

  4)求出协方差矩阵的特征值及对应的特征向量;

  5)将特征向量按对应特征值大小从上到下按行排列成矩阵,取前k行组成矩阵P

  6)Y=PX即为降维到k维后的数据。

六、参数解析

PCA(n_components=None, copy=True, whiten=False, svd_solver=’auto’, tol=0.0, iterated_power=’auto’, random_state=None)[source]

1.参数

  1)n_components:指定希望PCA降维后的特征维度数目
            指定整数,表示要降维到的目标,比如十维的数据,指定n_components=5,表示将十维数据降维到五维,如果为小数,表示累计方差百分比。

 2)copy :bool类型,默认为True。
          表示是否在运行算法时,将原始训练数据复制一份。若为True,则运行PCA算法后,原始训练数据的值不会有任何改变,因为是在原始数据的副本上进行运算;若为False,则运行PCA算法后,原始训练数据的值会改,因为是在原始数据上进行降维计算

3)whiten:判断是否进行白化。
           白化就是对降维后的数据的每个特征进行归一化,让方差都为1.默认值是False,即不进行白化。

4)svd_solver:即指定奇异值分解SVD的方法
          由于特征分解是奇异值分解SVD的一个特例,一般的PCA库都是基于SVD实现的。有4个可以选择的值:{‘auto’, ‘full’, ‘arpack’, ‘randomized’}。randomized一般适用于数据量大,数据维度多同时主成分数目比例又较低的PCA降维,它使用了一些加快SVD的随机算法。 full则是传统意义上的SVD,使用了scipy库对应的实现。arpack和randomized的适用场景类似,区别是randomized使用的是scikit-learn自己的SVD实现,而arpack直接使用了scipy库的sparse SVD实现。默认是auto,即PCA类会自己去在前面讲到的三种算法里面去权衡,选择一个合适的SVD算法来降维。

2、属性

        components_:array, shape (n_components, n_features) 指表示主成分系数矩阵
        explained_variance_:降维后的各主成分的方差值。方差值越大,则说明越是重要的主成分。
        explained_variance_ratio_:降维后的各主成分的方差值占总方差值的比例,这个比例越大,则越是重要的主成分。【一般看比例即可 >90%】

七、代码调用

1. 导入必要的库

import pandas as pd  # 数据处理库,用于读取和操作CSV数据
from sklearn.decomposition import PCA  # 主成分分析,用于降维
from sklearn.preprocessing import StandardScaler  # 数据标准化工具
from sklearn.linear_model import LogisticRegression  # 逻辑回归模型
from sklearn.model_selection import train_test_split  # 划分训练集和测试集
from sklearn.metrics import classification_report  # 生成分类评估报告

2. 读取数据并提取特征与标签

数据集如下:

file = pd.read_csv('creditcard.csv')  # 读取信用卡欺诈数据集
x = file.iloc[:, :-1]  # 提取特征(除最后一列外的所有列)
y = file.iloc[:, -1]   # 提取标签(最后一列,通常表示是否为欺诈交易:0=正常,1=欺诈)

数据集creditcard.csv是经典的信用卡欺诈检测数据,包含时间、交易金额等特征,标签列Class标记交易是否为欺诈。

3. 数据标准化

scaler = StandardScaler()  # 初始化标准化器
x_scaled = scaler.fit_transform(x)  # 对特征进行标准化(均值为0,方差为1)
  • 为什么标准化?
    PCA 和逻辑回归对特征的尺度敏感(例如 “交易金额” 可能远大于其他特征),标准化能让所有特征在同一尺度上,避免某一特征对结果产生过度影响。

4. 使用 PCA 进行降维

model = PCA(n_components=20)  # 初始化PCA模型,指定降维到20个主成分
x_reduced = model.fit_transform(x_scaled)  # 对标准化后的特征进行降维
  • PCA 作用:将高维特征(原始数据可能有 30 + 特征)压缩到 20 个主成分,减少数据维度,同时保留大部分关键信息。
  • 降维的好处:减少计算量、避免过拟合、简化模型。

5. 划分数据集并训练降维后的逻辑回归模型

# 划分降维后的数据为训练集(80%)和测试集(20%),固定随机种子保证结果可复现
x_train, x_test, y_train, y_test = train_test_split(x_reduced, y, test_size=0.2, random_state=5)

# 初始化逻辑回归模型:max_iter=1000(确保收敛),class_weight='balanced'(处理类别不平衡)
lr = LogisticRegression(max_iter=1000, class_weight='balanced')
lr.fit(x_train, y_train)  # 用降维后的训练集训练模型

y_pred = lr.predict(x_test)  # 用训练好的模型预测测试集
print("降维分类报告:")
print(classification_report(y_test, y_pred))  # 输出降维后模型的评估报告
  • class_weight='balanced':信用卡数据中欺诈样本(1)通常远少于正常样本(0),此参数会自动调整权重,让模型更关注少数类(欺诈样本)。
  • 分类报告:包含精确率、召回率、F1 分数等指标,评估模型对两类样本的识别能力。

6. 用原始数据训练逻辑回归模型(对比实验)

# 划分原始特征数据为训练集和测试集(保持与上面相同的随机种子,确保对比公平)
x_train1, x_test1, y_train1, y_test1 = train_test_split(x, y, test_size=0.2, random_state=5)

# 用原始数据训练新的逻辑回归模型(参数与上面一致)
lr = LogisticRegression(max_iter=1000, class_weight='balanced')
lr.fit(x_train1, y_train1)

y_pred1 = lr.predict(x_test1)  # 预测原始数据的测试集
print("不降温分类报告:")
print(classification_report(y_test1, y_pred1))  # 输出原始数据模型的评估报告
  • 这部分是对照组:不做降维,直接用原始特征训练模型,用于和降维后的模型对比。
  • 保持random_state=5确保两个实验的训练集 / 测试集划分完全一致,保证对比的公平性。

核心目的与结论

通过对比两个分类报告,可以观察:

  1. 降维后模型的性能(如欺诈样本的召回率)是否与原始模型接近;
  2. 降维是否在损失少量信息的情况下,简化了模型(减少特征数量);
  3. 对于信用卡欺诈检测这类场景,重点关注少数类(欺诈样本)的召回率是否达标。

通常情况下,PCA 降维后的模型性能会略低于原始模型,但计算速度更快,且如果降维保留了 90% 以上的信息,性能损失会很小。

完整代码及运行结果如下:

import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
file = pd.read_csv('creditcard.csv')
x = file.iloc[:, :-1]
y = file.iloc[:, -1]
scaler = StandardScaler()
x_scaled = scaler.fit_transform(x)
model = PCA(n_components=20)
x_reduced = model.fit_transform(x_scaled)
x_train, x_test, y_train, y_test = train_test_split(x_reduced, y, test_size=0.2, random_state=5)
lr=LogisticRegression(max_iter=1000,class_weight='balanced')
lr.fit(x_train,y_train)
y_pred = lr.predict(x_test)
print("降维分类报告:")
print(classification_report(y_test, y_pred))
x_train1,x_test1,y_train1,y_test1=train_test_split(x,y,test_size=0.2,random_state=5)
lr=LogisticRegression(max_iter=1000,class_weight='balanced')
lr.fit(x_train1,y_train1)
y_pred1=lr.predict(x_test1)
print("不降温分类报告:")
print(classification_report(y_test1,y_pred1))