基本思想.
- 线性判别分析LDA于1936年由Fisher提出,其基本思想是将给定的训练集设法投影到一条直线 lll 上,并且很自然地,我们力求投影点集合中同类的点相距越近越好,异类的点相距越远越好。
- 给定一个新样本,采用同样的投影方法,将其投影到 lll 上,再根据投影点的位置来确定其类别。
优化目标推导.
- 给定训练集 D={(xi,yi)}i=1m,yi∈{0,1,..,K}D=\{(x_i,y_i)\}_{i=1}^m,y_i∈\{0,1,..,K\}D={(xi,yi)}i=1m,yi∈{0,1,..,K} ,记 μi,Σi\mu_i,\Sigma_iμi,Σi分别为第 kkk 类样本的均值向量和协方差矩阵。不妨假定将数据集 DDD 投影到直线 y=wTxy=w^Txy=wTx 上,截距项只是对直线产生平移效果,对于第一部分我们所关心的同类点、异类点的距离并无影响。
- 先从二分类问题入手,即 yi∈{0,1}y_i∈\{0,1\}yi∈{0,1},那么投影后,我们得到两类样本点的均值向量在直线上的投影分别为 【wTμ0、wTμ1w^T\mu_0、w^T\mu_1wTμ0、wTμ1】,两类样本投影点的协方差分别为 【wTΣ0w、wTΣ1ww^T\Sigma_0w、w^T\Sigma_1wwTΣ0w、wTΣ1w】。此时所有的样本投影点点都位于直线上,易知投影点均值以及投影点协方差皆为实数。
- 考虑第一部分给出的基本思想,同类点尽可能靠近,异类点尽可能远离,即我们希望两类样本投影点的协方差都尽可能小,而两个均值要差距尽可能大,形式化的描述如下:
- 其中 JJJ 即为我们希望最大化的目标,其分母使得类内距离趋向小,分子使得类间距离趋向大。在原数据维度定义类内散度矩阵以及类间散度矩阵,改写优化目标如下:
- 上式中 SwS_wSw 代表类内散度矩阵with-in,SbS_bSb 代表类间散度矩阵between,JJJ 称为二者的广义瑞利商Generalized Rayleigh Quotient。目标是最大化 JJJ,无论是直观理解还是从广义瑞利商的定义式出发,参数 www 的大小并不影响LDA的结果,只有其方向会影响投影点的分布。
- 不失其一般性,我们在最大化 JJJ 的过程中加入限制,令 wTSww=1w^TS_ww=1wTSww=1,那么该优化问题改写为:
- 使用等式约束下的拉格朗日乘数法,得到如下的推导过程:
- 最终得到了参数 www 的解析解,实际应用中考虑到数值解的稳定性,在计算类间散度矩阵 SwS_wSw 的逆时会先进行奇异值分解,而后在求出其逆矩阵。
多分类推广.
- 从二分类问题推广到多分类问题,假定共有 KKK 个类,mmm 个样本,定义全局散度矩阵total如下所示 :
应用实例.
LdaData = np.loadtxt("data2.txt")
Pos0 = np.where(LdaData[:,2] == 0)
Pos1 = np.where(LdaData[:,2] == 1)
X1 = LdaData[Pos0,0:2]
X1 = X1[0,:,:]
X2 = LdaData[Pos1,0:2]
X2 = X2[0,:,:]MeanX1 = np.mean(X1,axis=0)
MeanX2 = np.mean(X2,axis=0)
print(X1.shape)
print(X2.shape)
print(MeanX1)
- 计算类内、类间散度矩阵,本例中基于式 Sbw=λSwwS_bw=\lambda S_wwSbw=λSww,从而有:Sw−1Sbw=λwS_w^{-1}S_bw=\lambda wSw−1Sbw=λw 对矩阵 Sw−1SbS_w^{-1}S_bSw−1Sb 进行特征分解后选择垂直于 μ0−μ1\mu_0-\mu_1μ0−μ1 的特征向量即可。(下面代码中直接选择主特征值对应的特征向量,现在看应该是诈胡了,我也记不清自己当时怎么想的了,修改后的部分在最后).
def ComputeS_w(X,MeanX):S = np.dot((X-MeanX).T,X-MeanX) return STestS_i = ComputeS_w(X1,MeanX1)
print("TestS_i = ",TestS_i)def ComputeS_b(X1Mean,X2Mean):S = np.dot(X1Mean-X2Mean,(X1Mean-X2Mean).T) return SSb = ComputeS_b(MeanX1,MeanX2)
print("Sb = ", Sb)Sw_1 = ComputeS_w(X1,MeanX1)
Sw_2 = ComputeS_w(X2,MeanX2)
Sw = Sw_1 + Sw_2
[V,L] = np.linalg.eig(np.dot(np.linalg.inv(Sw),Sb))
print(V)
print(L)
index = np.argsort(-V)
W = L[:,index[0]]
print("W = ", W)def TranLine(X,K):X_i = []Y_i = []for i in range(np.size(X,0)):y_0 = X[i,1]x_0 = X[i,0]x1 = (k*(y_0-b)+x_0)/(k**2+1)y1 = K*x1+bX_i.append(x1)Y_i.append(y1)return X_i,Y_ib = 0
x = np.arange(2,10)
k = W[1]/W[0]
y=k*x+b
plt.plot(x,y)
plt.scatter(X1[:,0],X1[:,1],marker='+',color='r',s=40)
plt.scatter(X2[:,0],X2[:,1],marker='*',color='b',s=40)
plt.grid()
plt.show()
X_1,Y_1 = TranLine(X1,k)
X_2,Y_2 = TranLine(X2,k)
plt.plot(x,y)
plt.scatter(X1[:,0],X1[:,1],marker='+',color='r',s=60)
plt.scatter(X2[:,0],X2[:,1],marker='*',color='b',s=60)
plt.grid()
plt.plot(X_1,Y_1,'r+')
plt.plot(X_2,Y_2,'b>')
def ComputeS_w(X,MeanX):S = np.dot((X-MeanX).T,X-MeanX) return STestS_i = ComputeS_w(X1,MeanX1)
print("TestS_i = ",TestS_i)def ComputeS_b(X1Mean,X2Mean):S = np.dot(X1Mean-X2Mean,(X1Mean-X2Mean).T) return SSb = ComputeS_b(MeanX1,MeanX2)
print("Sb = ", Sb)Sw_1 = ComputeS_w(X1,MeanX1)
Sw_2 = ComputeS_w(X2,MeanX2)
Sw = Sw_1 + Sw_2
W = np.dot(np.linalg.inv(Sw),MeanX1-MeanX2)
print("W = ", W)