接着上篇完成之后,已经过了2天时间了,终于,在今天将代码全部写完,并通过测试。太不容易了,主要是遇到一个坑,浪费好多时间。废话不多说,直接进入正题吧。
特征提取一般的都是使用主成分分析法,即PCA。
PCA的一般处理步骤分为:
- 中心化
- 求协方差矩阵
- 对协方差矩阵求特征值和特征向量,从而得到降维矩阵
- 通过中心化后的矩阵和降维矩阵的乘积即可得到最终的结果。
这里的话,就不介绍协方差矩阵相关的一些东西了,不懂的同学可以自行百度。
首先,我们来了解一下第三方库sklearn中的PCA的使用方法,一看你会觉得so easy的。
# sklearn PCA 调用 import numpy as np import sklearn.decomposition as dp from matplotlib import pyplot as plt from sklearn.datasets import load_iris #''' x,y=load_iris(return_X_y=True) print(x.shape) # 4维(150,4) pca=dp.PCA(n_components=2) # pca.fit(x) # a=pca.transform(x) # print(a.shape) # fit和transform一块计算 reduced_x=pca.fit_transform(x) print(len(reduced_x)) print(reduced_x[:10]) print('特征值:') print(pca.n_features_) print(pca.n_samples_) print('特征向量:') print(pca.components_) print(type(reduced_x)) print(reduced_x.shape) print(pca.explained_variance_ratio_)
可以直接忽略print相关代码,核心代码其实就是fit_transform函数,可以直接得到降维后的数据。
为了熟悉以上所说的步骤,我们通过numpy库来自实现一下这个步骤,
这里说明一下numpy.lianlg.eig方法可用于计算协方差矩阵的特征值和特征向量,返回值即为特征值和特征向量,另外,需要知道协方差矩阵的一个性质。
如此以来,可以对照着看一下如下代码:
'''使用Numpy来实现CPA,只具体到相应步骤''' import numpy as np from sklearn.datasets import load_iris from sklearn.utils.extmath import svd_flip class PCA: def __init__(self,n_components): self.n_components=n_components def fit_transform(self,x): self.feature_count=x.shape[1] #中心化 x=x-x.mean(axis=0) #协方差矩阵 self.convariance=np.dot(x.T,x)/x.shape[0] # #求特征值和特征向量 # eig_vals,eig_vectors=np.linalg.eig(self.convariance) # # 排序获得前n个特征 # idx=np.argsort(-eig_vals) # # 得到由特征向量组成的降维矩阵 # self.components_=eig_vectors[:,idx[:self.n_components]] # print('特征值:') # print(eig_vals) # print('特征向量:') # print(self.components_) # # 对x进行降维处理 # return np.dot(x,self.components_) U,S,V=np.linalg.svd(x,full_matrices=False) U,V=svd_flip(U,V) print(V) # 得到由特征向量组成的降维矩阵 self.components_=V.T[:,:self.n_components] # print('特征值:') # print(eig_vals) print('特征向量:') print(self.components_) # 对x进行降维处理 return np.dot(x,self.components_) pca=PCA(n_components=2) x,y=load_iris(True) x_components=pca.fit_transform(x) print(x_components[:10])
相信眼尖的同学已经看到了,我处理函数中有一部分注释代码,是使用的eig方法,而再用的是svd函数,其实这里即是我要说的坑。
我一开始使用eig函数实现后,将对应的特征值打印出来后发现,第二维的数据与sklearn得到的数据刚好为相反数,这就很奇怪了,按道理的话,要么一致要么都相反才对,然后我怀疑我代码写的有问题,各种分析最后打开百度,一瞬间我就明了了,因为好多人都提了类似的问题,那区别究竟为何呢?查看sklearn的PCA方法可以看到起定义中使用的正是svd方法(奇异值分解法),这个方法只需要将中心化后的矩阵做为参数传入即可,但需要在处理结束后进行svd_flip操作,其实这才是第二维数据互为相反数的原因。其实就是使用的操作方法不一致,而导致的结果。
这里也警醒我,不是万不得已,不要放着现成的库不用,而使用自己写的方法,用自己的方法也并没有那么炫酷,而且容易掉坑里。
方法的详细实现,贵在了解其步骤,熟悉其内在原理,并不是要写出一个可以投入使用的方法。
那么到这里是不是就结束了呢?
其实我觉得,到上一步结束后,大部分都能了解PCA的处理流程了,但是,其实还可以再进一步的加深巩固一下,这部分留给有精力的同学喽,其实就是将中心化等能自己实现的都用np自己实现以下,这里也不多说什么,直接是那个代码就是了
'''PCA算法细节自实现''' import numpy as np from sklearn.datasets import load_iris class PCA: def __init__(self,n_components): self.n_components=n_components def fit_transform(self,X): self.X=X self._centralizaed() self._conv() self._eig() # 这里一定要注意,是使用中心化后的矩阵和降维矩阵做乘 result=np.dot(self.centerX,self.components) return result # 中心化 def _centralizaed(self): mean=np.array([np.mean(col) for col in self.X.T]) #等同于 X.mean(axis=0) self.centerX=self.X-mean # 求协方差矩阵 def _conv(self): self.convariance=np.dot(self.centerX.T,self.centerX)/self.X.shape[0] def _eig(self): eig_values,eig_vectors= np.linalg.eig(self.convariance) idx=np.argsort(-eig_values) self.components=eig_vectors[:,idx[:self.n_components]] print('特征值:') print(eig_values) print('特征向量:') print(self.components[:10]) pca=PCA(n_components=2) x,y=load_iris(return_X_y=True) x_components=pca.fit_transform(x) # print(pca.components.shape) # print(pca.components[:10]) print(x_components.shape) print(x_components[:10])
其实我认为,对于原理的掌握并不一定非要完全理解了,再去敲代码加深理解,在掌握原理的基础上,可以试着先跟着代码的思路其实现以下,运行了解一下相关的步骤结果,这样更有利于对原理的掌握。
对于python小白的我来说,今天这里还学到了,np.ndarray对矩阵的操作方法,其实都是numpy的一些用法,相对于list的基础操作来说,numpy可以支持更多简便的操作,因此我们在ML的过程中,一般都已numpy为运算库,因此需要多多学习numpy的用法了。
b=[[1,2,3],[4,5,6]] b[:,a[:2]] Traceback (most recent call last): File "<input>", line 1, in <module> TypeError: list indices must be integers or slices, not tuple type(b) <class 'list'> import numpy as np c=np.array([[1,2,3],[4,5,6]]) type(c) <class 'numpy.ndarray'> c[:,a[:2]] array([[2, 3], [5, 6]]) c[:,[1,2]] array([[2, 3], [5, 6]]) c[:,[0,2]] array([[1, 3], [4, 6]]) b [[1, 2, 3], [4, 5, 6]] b.mean(axis=0) Traceback (most recent call last): File "<input>", line 1, in <module> AttributeError: 'list' object has no attribute 'mean' c.mean(axis=0) array([2.5, 3.5, 4.5]) c.mean(axis=1) array([2., 5.]) c.T array([[1, 4], [2, 5], [3, 6]]) c.T[0] array([1, 4]) c np.transpose(c) array([[1, 4], [2, 5], [3, 6]])