主成分分析(PCA)
Kong Liangqian Lv6

输入:m个中心化的n维向量,˜X=[˜x1,˜x2,,˜xm],˜xi=xi¯x,i=1,,m

输出:k个n维向量,代表着m个里面最主要的k个。

结论: 第一个为˜X对应特征值最大的特征向量,第二个为˜X对应特征值第二大的特征向量,以此类推1

证明过程

现在有m个n维向量,n维是用列来表示,˜X=[˜x1,˜x2,,˜xm],˜xi=xi¯x,i=1,,m¯x=1mΣmi=1xi

PCA是获取投影到某一方向上z方差的最大值为目标,因为要尽可能多的呈现出所有的样本,代表样本的最主要成分,设某一维度信息投影到z轴的值为αi=˜xTiz,i=1,2,m, 则投影的方差为

1mmi=1(αi¯α)2

注意

¯α=1mmi=1αi=1mmi=1(˜xTiz)=1mmi=1(xi¯x)Tz=(1mmi=1xi1mm¯x)Tz=0

因此

1mmi=1(αi¯α)2=1mmi=1α2i=1mmi=1(˜xTiz)T˜xTiz=1mmi=1zT˜xi˜xTiz=1mzT˜X˜XTz

目标转换为求1mzT˜X˜XTz的最大值。

通过奇异值分解,我们有XXT=UΣVT(UΣVT)T=UΣ2UT

zT˜X˜XTz=zTUΣ2UTz

其中,Σ=diag(λ21,,λ2n)λ1>λ2>>λn,当z=u1,即Ur的第一列时,可以取得zT(˜X˜XT)z 的最大值。

Proof:

H=˜X˜XT, 则根据Rayleigh 商(见附录),我们有

zTHzλmax(H)zTz

其中,
当即的第一列时,可以取得 的最大值。问题就是z取什么值,可以取得H的最大特征值。

我们知道

zTHz=zT(˜X˜XT)z=(UTrz)TΣUTrz=(UTrz)T[λ210...00λ22...000...000...λ2n]UTrz

λ1>λ2>>λn。如果要取得最大特征值λ1, 只需要令除了λ1 之外的对应特征向量为0即可,只需要令z=u1,即Ur的第一列时, 我们有

(UTrz)T=zTUr=uT1(u1,u2,...,un)=(uT1u1,0,...,0)

我们有

zTHz=||u1||2λ1||u1||2=λmax(H)zTz

因此,当z=u1(1)式等号成立. u1正是˜X最大特征值所对应的特征向量

当取得了第一个维度z的向量后,每一个向量都需要减去在z上的投影。uT1˜x代表xu1上的投影的长度,u1(uT1˜x)代表在u1方向上的投影长度

˜xi=˜xiu1(uT1˜x),i=1,...,m

˜X=(Inu1uT1)˜X

因为各个特征向量是线性无关的,当然对˜X,n×m,做SVD分解,

˜X=UΣVT=[u11u12...u1nu21u22...u1n............un1un2...unn]n×n[λ10...000λ2...00...............00...00]n×m[v11v21...vm1v12v22...vm2............v1mv2m...vmm]m×m=ri=1λiuivTi

其中UUT=I,VVT=Ir表示λ的个数,ui=[ui1,ui2,,uin]vi=[vi1,vi2,,vin]。则根据(2),

˜X=ri=1λiuivTiu1uT1ri=1λiuivTi=ri=1λiuivTiri=1λiu1(uT1ui)vTi=ri=1λiuivTiλ1u1uT1uivT1=ri=1λiuivTiλ1uivT1=ri=2λiuivTi

这也就是说,除去所有向量在u1上的投影,剩下的一点没有变

说白了,数据已经被表示成以特征向量为基的表示方法,而各个特征向量是线性无关的,减去在u1上面的投影,当然只有u1被减掉,其它都是正交的,没有影响。

附录:

高代p288 定义3

设A, B为数域P上的两个n级矩阵,如果可以找到数域P上的n级可逆矩阵X,使得

B=X1AX

就说A相似于B。这里X是n组线性无关的正交基。

当B是实对称矩阵即B=BT, 则BT=(X1AX)T=XTAT(X1)T=B=X1AX

我们有XT=X1, 因此

谱定理:当B是实对称矩阵时,我们有

B=XTAX

推理(Inference)与预测(Prediction)_deephub-CSDN博客很明显,因为B是实对称矩阵,所以XTX=X1X=I

Rayleigh 商:

对于维度为(n,1)任意的x和实对称矩阵A,有

λmin(A)xTAxxTxλmax(A)

Proof:

xTAx=xTUTΛUx=¯xTΛ¯x=[¯x1,¯x2,...,¯xn][λ10...00λ2...000...000...λn][¯x1¯x2...¯xn]=niλi¯xTi¯xi=niλixTUTUx=niλixTx

又有

λminnixTixiniλixTixiλmaxnixTixi

因此

λminniλixTixinixTixiλmax

从而(1)式得证。

SVD分解:参考刘建平博客【https://www.cnblogs.com/pinard/p/6251584.html】

完成对非方阵矩阵的分解

SVD也是对矩阵进行分解,但是和特征分解不同,SVD并不要求要分解的矩阵为方阵。假设我们的矩阵A是一个m×nm×n的矩阵,那么我们定义矩阵A的SVD为:

A=UΣVT

    其中U是一个m×m的矩阵,Σ是一个m×n的矩阵,除了主对角线上的元素以外全为0,主对角线上的每个元素都称为奇异值,V是一个n×n的矩阵。U和V都是酉矩阵,即满足UTU=I,VTV=I。下图可以很形象的看出上面SVD的定义:

img

降维

给定一组数据xiRn,i=1,2,m,利用PCA获得其最主要的l个成分[z1,z2,,zl],zjRn.

xi从n维压缩为l

[ai1ai2...ail]=[zT1zT2...zTl]xi=[z11z12...z1nz21z22...z2n............zl1zl2...zln][xi1xi2xi3......xin]

从主成分中重建xi

[xi1xi2xi3......xin]=[z1z2...zl][ai1ai2...ail]=[z11z21...zl1z12z22...zl2............z1nz2n...zln][ai1ai2...ail]

举例:

n个二维数据上的点,投影到数轴上,变成一维数据上的点

img

假设有一组二维的点A

A=[23...112...4]

通过奇异值分解,得到最大值对应的特征向量为a=[2/2;2/2], 此特征向量就是新求出的坐标系。下面开始降维操作,即求出二维点在此坐标系下的投影(坐标是多少)

向量乘法:通常可以表示一个投影的关系,假设有一个点b=[1,2],则ab代表ba上的投影再乘以a的长度。在这里,a作为一个基,他的模长为1。因此ab就是ba下的投影值

因此,可以通过

aTA=[2/2,2/2][23...112...4]

来计算所有的点在新坐标系的下的坐标。

应用实例

投影

本例子是一个把三维点云降维到二维平面的例子,也就是投影!

无论三维点云是如何偏转的(不和Z轴平行),我们都可以找出最主要的两个成分(特征向量),就找到了新的基底,然后做投影。

先随便去一个数据集找到一个点云文件,进行加载,下面的加载需要文件有六列

1
2
3
4
5
6
7
8
9
10
import open3d as o3d
import numpy as np
from pyntcloud import PyntCloud
import matplotlib.pyplot as plt

point_cloud_pynt = PyntCloud.from_file(filename,
sep=",",
names=["x", "y", "z", "nx", "ny", "nz"])
point_cloud_o3d = point_cloud_pynt.to_instance("open3d", mesh=False)
o3d.visualization.draw_geometries([point_cloud_o3d]) # 显示原始点云

airplane

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def PCA(data, correlation=False, sort=True):
# 1.中心化
data = data - np.mean(data)
# 2 求X^TX的特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eig(data.T@data)
if sort:
sort = eigenvalues.argsort()[::-1]
eigenvalues = eigenvalues[sort]
eigenvectors = eigenvectors[:, sort]

return eigenvalues, eigenvectors

# 从点云中获取点,只对点进行处理
points = point_cloud_pynt.points.iloc[:,:3] # get x,y,z cols
print('total points number is:', points.shape[0])

# 用PCA分析点云主方向
w, v = PCA(points)
point_cloud_vector = v[:, :2] # 方向对应的向量点云主
print('the main orientation of this pointcloud is: ', point_cloud_vector)

projection = points @ point_cloud_vector

plt.scatter(projection[0], projection[1])
plt.show()
plt.savefig("PCA.png")

经过投影,其结果为

img

法向量

通过收集某一个点的附近点来近似一个平面,通过这几个点计算出来的前面两个主成分(特征向量)就可以几乎完全表达出平面的基(也就是说这几两个基就可以完全表达出这些点的分布情况了,显然因为他们本身被近似为一个平面),而第三个特征向量是需要垂直于前面两个特征向量的,因此几乎是垂直的,可以近似为此平面的法向量。就是这个点的法向量。

添加法向量点云添加法向量点云

计算法向量的步骤:

  1. 选取一个点
  2. 计算其最近的几个点,这样可以比较好的近似平面
  3. 计算这几个点特征值最小的特征向量,就是法向量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 先建立一颗树
pcd_tree = o3d.geometry.KDTreeFlann(point_cloud_o3d)
normals = []

for idx in range(points.shape[0]):
# 删除噪声
[k, idxs, _] = pcd_tree.search_radius_vector_3d(points.iloc[idx].to_numpy(), 0.02)
if k < 10:
point_cloud_pynt.points.drop([idx])

for idx in range(points.shape[0]):
# 计算法向量
# 1. 找到附近的几个点,
# 2. 计算这几个点的最小特征值的特征向量
[k, idxs, _] = pcd_tree.search_knn_vector_3d(points.iloc[idx].to_numpy(), 200)

w, v = PCA(np.array(points)[idxs])
normals.append(v[:,2])

# 由于最近邻搜索是第二章的内容,所以此处允许直接调用open3d中的函数
normals = np.array(normals, dtype=np.float64)
point_cloud_o3d.normals = o3d.utility.Vector3dVector(normals)
o3d.visualization.draw_geometries([point_cloud_o3d])
 Comments