前言
本篇文章是为了理解PCA的基本原理来进行代码部分实现的,由于本篇博文只考虑了其中的原理,因此采用的特征向量都是维度较小的向量。
具体流程
首先需要创建一个数组,存储这些特征向量,为了便于进一步的操作,将其转为np.array的格式。
import numpy as np
ftmat=np.array([
[1,2,3],
[4,5,6],
])
m,n=ftmat.shape
# (m,n)=(2,3)
计算整个特征向量的均值,便于进一步求取特征向量的协方差矩阵:
具体求解方法可以调用mean函数:
u=np.mean(ftmat,axis=0)
# u=[2.5 3.5 4.5]
在求解协方差矩阵的时候,其实是遇到了一些问题的,主要是不知道如何将其对应的均值数组进行增广化处理来便于进一步利用每个特征向量减去均值向量来进行中心化处理。这里想到的方法是一个比较简单的思路,即新建一个与原本特征向量维度一致的零数组,将每个元素设置为均值向量。
u_ex=np.zeros((m,n))
for i in range(m):
u_ex[i]=u
print(u_ex)
# u_ex=[[2.5 3.5 4.5]
# [2.5 3.5 4.5]]
将原本的特征向量与之相减即可得到对应的中心化后的特征向量:
ft_cen=ftmat-u_ex
# ft_cen=[[-1.5 -1.5 -1.5]
# [ 1.5 1.5 1.5]]
计算协方差矩阵,利用的方法是将原本数组中的每个元素都进行矩阵乘法,自己乘以自己的转置;我们常用的矩阵以及矩阵转置的方法如下,要注意的地方在于如果一个向量想要转置那么至少应当保证其维度大于等于二维,具体如下:
m=np.array([[1,2,3]])
m_t=m.T
print(m,m_t)
print(m*m_t)
# m*m_t=
#[[1 2 3]
# [2 4 6]
# [3 6 9]]
有了上述的思路后,只需要利用for循环,将所有的数组都利用如此的方法进行实现即可。
cor=list()
for i in ft_cen:
cor.append(np.array([i])*np.array([i]).T)
print(cor[0],cor[1],sep='\n')
# cor=
#[[2.25 2.25 2.25]
# [2.25 2.25 2.25]
# [2.25 2.25 2.25]]
#[[2.25 2.25 2.25]
# [2.25 2.25 2.25]
# [2.25 2.25 2.25]]
求解出协方差矩阵之后,下一步是利用协方差矩阵来进行特征值与特征向量的求解,由于python中有求解特征值的较为方便的函数,因此直接调用即可:
l,l_mat=np.linalg.eig(cor)
但是要注意的是,我们每个协方差矩阵的特征值与特征向量只需要保存前几个就足够了,并不需要全部保存所有的,因此这里为了方便只选择了特征值最大的作为特征向量进行下一步的计算。用l1表示特征值对应的矩阵,用l2表示其对应特征向量,单个数组具体的处理步骤如下:
l1=[6.7500000e+00 0.0000000e+00 -5.1279005e-16]
l2=[[-5.77350269e-01 -5.41302883e-17 8.04908467e-01]
[-5.77350269e-01 -7.07106781e-01 -5.21162995e-01]
[-5.77350269e-01 7.07106781e-01 -2.83745472e-01]]
# 为了模拟上面返回的np.array所以进行一下处理,增加适配性:
l1,l2=map(np.array,[l1,l2])
# 获得最大值的下标:
l1_max_idx=list(l1).index(max(l1))
# 获取最大特征值对应的特征向量
l2_max=list()
for i in l2:
l2_max.append(i[l1_max_idx])
l2_max=l2_max/l2_max[0]
利用上面的处理步骤,不难得出对应的协方差矩阵的处理方法:
cor_ft=list()
for i in range(len(l)):
l_max_idx=list(l[i]).index(max(l[i]))
l_max=list()
for j in l_mat[i]:
l_max.append(j[l_max_idx])
l_max=l_max/l_max[0]
cor_ft.append(l_max)
print(cor_ft)
# cor_ft=[array([1., 1., 1.]), array([1., 1., 1.])]
为了减少计算量,可以采用归一化处理,即求得向量的模,并用原本的向量除以这个模进而得到归一化处理后的向量。
mod=list()
for i in cor_ft:
mod_tmp=0
for j in i:
mod_tmp=mod_tmp+j**2
mod.append(mod_tmp)
print(mod)
for i in range(len(cor_ft)):
cor_ft[i]=cor_ft[i]/mod[i]
print(cor_ft)
# cor_ft=[array([0.33333333, 0.33333333, 0.33333333]), array([0.33333333, 0.33333333, 0.33333333])]
将原本的特征向量进行降维处理,利用特征向量与原本的向量进行矩阵乘法即可,由于此处均为一维向量,所以直接利用点积即可。压缩过程如下所示:
ft_zip=list()
for i in range(len(ftmat)):
ft_zip.append(np.dot(ftmat[i],cor_ft[i]))
print(ft_zip)
# ft_zip=[1.9999999999999996, 4.999999999999999]
最终压缩后的向量即为ft_zip,之后即可利用新的向量,采用其他的分类方法进行分类操作。
汇总代码如下所示:
import numpy as np
ftmat=np.array([
[1,2,3],
[4,5,6],
])
m,n=ftmat.shape
##print(ftmat)
##print(f'({m},{n})')
u=np.mean(ftmat,axis=0)
##print(u)
u_ex=np.zeros((m,n))
for i in range(m):
u_ex[i]=u
##print(u_ex)
ft_cen=ftmat-u_ex
##print(ft_cen)
##m=np.array([[1,2,3]])
##m_t=m.T
##print(m,m_t)
##print(m*m_t)
cor=list()
for i in ft_cen:
cor.append(np.array([i])*np.array([i]).T)
##print(cor[0],cor[1],sep='\n')
l,l_mat=np.linalg.eig(cor)
##print(l,l_mat)
##l1=[6.7500000e+00,0.0000000e+00,-5.1279005e-16]
##l2=[[-5.77350269e-01,-5.41302883e-17,8.04908467e-01],
## [-5.77350269e-01,-7.07106781e-01,-5.21162995e-01],
## [-5.77350269e-01, 7.07106781e-01,-2.83745472e-01]]
##l1,l2=map(np.array,[l1,l2])
####print(l1,l2)
####print(list(l1).index(max(l1)))
##l1_max_idx=list(l1).index(max(l1))
##l2_max=list()
##for i in l2:
## l2_max.append(i[l1_max_idx])
##l2_max=l2_max/l2_max[0]
cor_ft=list()
for i in range(len(l)):
l_max_idx=list(l[i]).index(max(l[i]))
l_max=list()
for j in l_mat[i]:
l_max.append(j[l_max_idx])
l_max=l_max/l_max[0]
cor_ft.append(l_max)
print(cor_ft)
mod=list()
for i in cor_ft:
mod_tmp=0
for j in i:
mod_tmp=mod_tmp+j**2
mod.append(mod_tmp)
print(mod)
for i in range(len(cor_ft)):
cor_ft[i]=cor_ft[i]/mod[i]
print(cor_ft)
ft_zip=list()
for i in range(len(ftmat)):
ft_zip.append(np.dot(ftmat[i],cor_ft[i]))
print(ft_zip)