一、算法原理及数学推导
在对于数据的处理上,特征维度过高经常是一个无法忽视的问题,但是单纯的降维压缩对于数据本身会使得数据信息遭到严重损失。不过以PCA为代表的线性降维算法对这方面有所保证,它的目标是通过某种线性投影,将高维的数据映射到低维的空间中,并期望在所投影的维度上数据的信息量最大(方差最大),以此使用较少的数据维度,同时保留住较多的原数据点的特性。
PCA算法所做的工作相当于原始特征空间的重构,只不过这个重构的过程是一个寻找主成分(正交方向),使得投影于该方向上的点足够离散的过程,其优化方向有两个,一是保证投影方差最大,二是保证重构代价最小。
以下为推导求解过程:
假设有N个样本数据,P个特征组成的数据集X,X (i=1,2,3…N)表示第i个数据。特征的均值向量为:
每个样本与均值向量的差为:
那么数据集(X_1,X_2,X_3…,X_m)的协方差矩阵为:
假设所选择的主成分方向为u_1,
则特征向量在该方向上的投影为:
投影的二范式为:
1、投影方差最大化求解
如此,便可转化为最优化问题,引入拉格朗日乘子λ:
对L求偏导得:
设
得:
显然,此等式意在求S的特征值及特征向量。则对投影方差最大化问题的求解可直接等价为求协方差矩阵的特征值及特征向量。
2、重构代价最小化求解
重构代价即投影后的点在新的坐标系下反向投影回原坐标系后所得向量的长度。
在二维空间中,x_i为向量,u_1和u_2为投影后含主成分方向的坐标轴,求其重构后向量为:
得出m维空间中:
以此求重构代价:
其中:
P为数据维度即特征维度
q为降维后数据的维度
重构代价最小化:
转化为二次优化问题,可发现求解过程与最大投影方差的角度一样,同时,此处取的是最小的P-q个λ值,可等价于求最大的q个,故而两个角度所求得的解是一样的。
两角度后续共同的求解:
计算协方差矩阵S的P个特征值及特征向量(α_1,α_2,α…,α_P),按特征值非递减排序后,取前面的q个特征值,使用其对应的特征向量组成一个q维的特征空间,记为U。同时,为衡量每个样本向量的贡献度,引入贡献度计算公式:
则原始数据集在特征向量集U下可通过如下方式投影到低维空间:
二、算法代码实现
1、代码:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets.samples_generator import make_blobs
class DimensionValueError(ValueError):
"""定义异常类"""
pass
class PCA(object):
"""定义PCA类"""
def __init__(self, x, n_components=None):
"""x的数据结构应为ndarray"""
self.x = x
self.dimension = x.shape[1]
if n_components and n_components >= self.dimension:
raise DimensionValueError("n_components error")
self.n_components = n_components
def cov(self):
"""求x的协方差矩阵"""
x_T = np.transpose(self.x) # 矩阵转秩
x_cov = np.cov(x_T) # 协方差矩阵
return x_cov
def get_feature(self):
"""求协方差矩阵C的特征值和特征向量"""
x_cov = self.cov()
a, b = np.linalg.eig(x_cov)
m = a.shape[0]
c = np.hstack((a.reshape((m, 1)), b))
c_df = pd.DataFrame(c)
c_df_sort = c_df.sort_values(by=0,ascending=False) # 按照特征值大小降序排列特征向量
return c_df_sort
def explained_varience_(self):
c_df_sort = self.get_feature()
return c_df_sort.values[:, 0]
def paint_varience_(self):
explained_variance_ = self.explained_varience_()
plt.figure()
plt.plot(explained_variance_, 'k')
plt.xlabel('n_components', fontsize=16)
plt.ylabel('explained_variance_', fontsize=16)
plt.show()
def reduce_dimension(self):
"""指定维度降维和根据方差贡献率自动降维"""
c_df_sort = self.get_feature()
varience = self.explained_varience_()
if self.n_components: # 指定降维维度
p = c_df_sort.values[0:self.n_components, 1:]
y = np.dot(p, np.transpose(self.x)) # 矩阵叉乘
return np.transpose(y)
varience_sum = sum(varience) # 利用方差贡献度自动选择降维维度
varience_radio = varience / varience_sum
varience_contribution = 0
for R in range(self.dimension):
varience_contribution += varience_radio[R] # 前R个方差贡献度之和
if varience_contribution >= 0.99:
break
p = c_df_sort.values[0:R + 1, 1:] # 取前R个特征向量
y = np.dot(p, np.transpose(self.x)) # 矩阵叉乘
return np.transpose(y)
x, y = make_blobs(n_samples=10000, n_features=3, centers=[[3, 3, 3], [0, 0, 0], [1, 1, 1], [2, 2, 2]],
cluster_std=[0.2, 0.1, 0.2, 0.2],
random_state=9)
if __name__ == '__main__':
fig=plt.figure()
ax=plt.axes(projection='3d')
ax.scatter(x[:,0],x[:,1],x[:,2])
plt.show()
plt.savefig("C://Users//hasee//Desktop")
pca = PCA(x)
y = pca.reduce_dimension()
plt.scatter(x[:,0],x[:,1])
plt.show()
plt.savefig("C://Users//hasee//Desktop")
print(y.shape)
2、数据降维实例
(1)原数据集
(2)降维后保留2个特征
降维后数据分布特点与之前在很大程度上相似,说明保留了大部分的数据信息。
3、算法存在的问题
用主成分解释数据含义往往具有一定的模糊性,不如原始数据完整。其次,PCA方法寻找的是用来有效表示同一类样本共同特点的主轴方向,这对于表示同一类数据样本的共同特征是非常有效的,但PCA不适合用于区分不同的样本类。Fisher线性判别分析(FDA)是用于寻找最有效地对不同样本类进行区分的方向。其主要思想是考虑将d维空间中的点投影到一条直线上。通过适当地选择直线的方向,有可能找到能够最大限度地区分各类样本数据点的投影方向。