前言:
Linear Discriminant Analysis, 简称LDA。是一种子空间投影技术,目的是做分类,让投影后的向量
对分类任务有很好的区分度。
这里要用到线性代数矩阵秩的三个基本性质
目录:
- 用投影进行分类
- 投影矩阵一维
- 投影矩阵高维
- 例子
一 用投影进行分类
基本思想是通过线性投影来 :
最小化同类样本间的差异,
最大化不同样本之间的差异。
具体做法,寻找一个想低维度投影矩阵W,样本的特征向量x经过投影后得到新的向量
同一类样本投影后结果向量尽可能小,不同类投影后样本差异尽量大。
二 一维投影矩阵
最后结果是一个标量
假设 有n个样本,它们的特征向量,属于两个不同类。
类,样本集为
,有
个样本
类,样本集为
,有
个样本
有一个向量w,所有向量对该向量投影后得到一个标量
投影产生n个标量,分别属于对应的集合
我们希望投影后两个类内部的各样本差异最小化,类之间的差异最大化。
投影之前样本的均值为
投影之后的均值为
等价于样本均值在w上的投影,投影后两类样本的绝对值差为(类之间):
类间的差异可以用方差来衡量。定义类的类内散布为(类间)
目标函数
其中
各类样本的均值差可以写为
定义
则可以写成
则优化目标函数为求解最大值:
s.t
构造拉格朗日函数
对w 求偏导数:
问题1: 特征值为什么只能取最大的那个
求解最大值等同求解最大特征值
问题2: 为什么特征值只能取一个,假设分类为K,最多取K-1个
算法流程:
- 对d维数据进行标准化处理(n为特征数量)
- 对于每一类别,计算n维的均值向量
- 构造类间的散布矩阵
以及 类内散布矩阵
- 计算矩阵
的特征值以及对应的特征向量
- 选取最大特征值所对应的特征向量,构造一个 n∗1 维的转换矩阵 W,其中特征向量以列的形式排列
- 使用转换矩阵 W将样本映射到新的特征子空间上
分类:
类似KNN 算法,因为有两个聚类中心,比较当前样本
哪个更小,就是哪一类
算法流程:
- 对d维数据进行标准化处理(n为特征数量)
- 对于每一类别,计算d维的均值向量,以及总的均值向量
- 构造类间的散布矩阵
以及 类内散布矩阵
4 计算矩阵的特征值以及对应的特征向量
5 选取前c[k-1]个特征值所对应的特征向量,构造一个c∗d维的转换矩阵 W,其中特征向量以列的形式排列
使用转换矩阵 W将样本映射到新的特征子空间上
推导过程:
max: 映射到低维后的类间散布矩阵
拉格朗日对偶变换
对w求偏导数,得到
其中
其中 秩为1 ,根据性质
又因为其中任意一项,可由K-1项线性表示
所以
所以上式最多由K-1一个解,即降维后C最大为K-1
取最大值依然如上:
,其中c<=K-1
四 例子
一 乳房癌预测例子
# -*- coding: utf-8 -*-
"""
Created on Mon Oct 21 10:06:58 2019
@author: chengxf2
"""
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
#from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_breast_cancer
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
import pickle
"""
二分类
"""
class LDAMe():
"""
加载数据集
Args
None
return
None
"""
def LoadData(self):
Breast = load_breast_cancer()
data = Breast.data
print("\n step1 数据标准化处理 \n")
sc = StandardScaler()
self.train_data = sc.fit_transform(data)
self.train_label = Breast.target
self.m, self.n = np.shape(self.train_data) ##2分类
#print("\n m, ",self.m, "\t n",self.n)
def __init__(self):
self.m = 0
self.n = 0
self.newDim = 2 ##降维后的维度
self.U ={}
self.LoadData()
"""
获得均值向量
Args
None
return
None 1行n列的均值
"""
def GetMean(self):
labelItem = [0,1] ##只有2类
dictU ={}
for label in labelItem:
index = (self.train_label == label).ravel()##True False
x = self.train_data[index,:]
u = np.mean(x,axis=0)
dictU[label]=u
self.U[0]= dictU[0].reshape(1,self.n)
self.U[1] = dictU[1].reshape(1,self.n)
"""
总类内散布矩阵
Args
SW:
return
S_W
"""
def GetSW(self):
S_W = np.zeros((self.n, self.n))
for label in [0,1]:
xu = np.zeros((self.n, self.n))
for x in self.train_data[self.train_label == label]:
x_u = x - self.U[label]
xu+= x_u.T*x_u
S_W+= xu
return S_W
"""
总类间散布矩阵
Args
None
return
None
"""
def GetSB(self):
u0 = self.U[0]
u1 = self.U[1]
u = u0-u1
S_B = u.T*u
return S_B
"""
获取SB
Args
None
return
Noe
"""
def GetS(self):
#Sw = None
Sw = self.GetSW()
SB = self.GetSB()
self.S = np.linalg.inv(Sw)*SB
def Train(self):
print("\n step2: 计算每一类别的均值向量 ")
self.GetMean()
print("\n step3 构造散布矩阵SB, SW (n*n)")
self.GetS()
print("\n step4 计算矩阵的特征值以及对应的特征向量" )
e,V = np.linalg.eig(self.S)
# print("e ",e)
print("\n step5 选取前k个特征值所对应的特征向量")
W = np.zeros((self.n,1))
index = np.argsort(-e)
print("\n step6 使用转换矩阵 W将样本映射到新的特征子空间上")
W[:,0]=V[:,index[0]]
self.Perdict(W)
"""
用w来预测
Args
W: 投影矩阵
return
None
"""
def Perdict(self, W):
u0 = self.U[0]
u1 = self.U[1]
errornum = 0
for i in range(self.m):
x = self.train_data[i]
dist1 = np.dot((x-u0).reshape(1,30),W)[0,0]
dist2 = np.dot((x-u1).reshape(1,30),W)[0,0]
#print("dist1",dist1, "\n DIST2 ",dist2)
if np.abs(dist1)<np.abs(dist2):
perdict_label = 0
else:
perdict_label = 1
label = self.train_label[i]
if perdict_label != label:
errornum = errornum+1
error =100*(errornum/self.m)
Result = '错误率 : {:.2%}'.format(error/100)
print("\n ********************\n")
print(Result)
class Data():
def __init__(self, data, color):
self.trainData = data
self.color = color
def Test():
x= np.array([[1,2,7],
[2,3,4],
[0,1,4]])
u = np.mean(x,axis=0)
print("u, ",u)
x2 = x-u
print("\n x2, ",x2)
#GeneData()
ld = LDAMe()
ld.Train()
预测结果
step1 数据标准化处理
step2: 计算每一类别的均值向量
step3 构造散布矩阵SB, SW (n*n)
step4 计算矩阵的特征值以及对应的特征向量
step5 选取前k个特征值所对应的特征向量
step6 使用转换矩阵 W将样本映射到新的特征子空间上
********************
错误率 : 13.88%
例2 :
数据集使用的是:
https://www.jianshu.com/p/28da5c160230
LDA 降维后效果
数据集使用下面方式生成
"""
生成数据
Args
None
return
数据
"""
def GenereData(self):
self.m = 600
self.n = 2
N = 300
self.train_data= np.zeros((self.m, self.n))
self.train_label= np.zeros(self.m)
self.train_label[N:self.m]=1
#print("\n self.train ",self.train_label)
N = 300
# 设椭圆中心center
cx = 5
cy = 6
a = 1/8.0
b = 4
X,scale = 2*a*random_sample((N,))+cx-a,60
Y = [2*b*np.sqrt(1.0-((xi-cx)/a)**2)*random_sample()+cy-b*np.sqrt(1.0-((xi-cx)/a)**2) for xi in X]
self.train_data[0:N,0]= X
self.train_data[0:N, 1] =Y
#print("\n sp ", X,np.shape(Y))
colors = ['green', 'green']*150
fig, ax = plt.subplots()
fig.set_size_inches(4, 6)
ax.scatter(X, Y,c = "none",s=scale,alpha=1, edgecolors=['green']*N)
X1,scale = 2*a*random_sample((N,))+cx-a,60
Y1 = [2*b*np.sqrt(1.0-((xi-cx)/a)**2)*random_sample()+cy-b*np.sqrt(1.0-((xi-cx)/a)**2) for xi in X1]
ax.scatter(X1+0.3, Y1,c = "none",s=scale,alpha=1, edgecolors=['red']*N)
#plt.savefig('lda.png')
plt.show()
self.train_data[N:self.m, 0]= X1+0.3
self.train_data[N:self.m, 1] =Y1
显示部分CODE
newX = np.dot(self.train_data, W)
data1 = newX[0:300]
data2 = newX[300:self.m]
plt.scatter(data1, [0]*data1.size, c='r')
plt.scatter(data2, [0]*data2.size, c='g')
例二: 鸢尾花数据集 高维降维
降维前
降维后:
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 23 15:50:16 2019
@author: chengxf2
"""
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
##https://www.cnblogs.com/yinheyi/p/6056314.html 颜色表
class LDAK:
"""
绘制图形
Args
data: 数据集
label: 标签
return
None
"""
def Draw3D(self, data, label):
fig = plt.figure()
ax = fig.add_subplot(111, projection ='3d')
x= data[:,0]
y = data[:,1]
z = data[:,2]
ax.scatter(x,y, z,c=label,cmap='cool')
ax.set_xlabel("X ")
ax.set_ylabel("Y ")
ax.set_zlabel("Z ")
ax.set_title("Scatter plot", alpha=0.6, color="b", size=25, weight='bold', backgroundcolor="y")
plt.show()
"""
加载数据集
Args
None
return
None
"""
def LoadData(self):
Iris = load_iris()
std = StandardScaler()
data = std.fit_transform(Iris.data)
self.trainData = data
self.trainLabel = Iris.target
self.n , self.d = np.shape(self.trainData)
self.k = len(set(self.trainLabel))
self.u = np.mean(self.trainData,axis=0)
print("u:", self.u)
print("\n self.k ",set(self.trainLabel))
self.Draw3D(self.trainData, self.trainLabel)
def __init__(self):
self.n = 0 ##样本的个数
self.d = 0 ##样本的维度
self.k = 0 ## 样本的总类
self.LoadData()
"""
降维
Args
None
return
None
"""
def Reduce(self):
print("\n step1 类内散布矩阵 SW \n")
SW = np.zeros((self.d, self.d))
UList =[]
NI =[]
for label in range(self.k):
index = (self.trainLabel == label).ravel()
# print("\n index ",index )
D_I = self.trainData[index]
U_I = np.mean(D_I, axis=0)
UList.append(U_I)
NI.append(len(D_I))
A= D_I-U_I
S_I = np.dot(A.T,A)
SW = SW+S_I
print("\n U_I: \n ",SW)
print("\n step2 计算SB \n")
SB = np.zeros((self.d, self.d))
for i in range(self.k):
U_I = UList[i]
B = U_I-self.u
n = NI[i]
B_I = n*np.dot(B.T, B)
SB+=B_I
print("\n SB: \n", SB)
print("\n step3 计算特征向量 \n")
C = np.linalg.inv(SW)
D = np.dot(C, SB)
e,V = np.linalg.eig(D)
print("\n a ",e)
index = np.argsort(-e) ##从大到小排序
print("\n index: ",index)
W = np.zeros((self.d, self.k-1))
for j in range(self.k-1) : ##取k-1个
pos = index[j]
W[:,j]=V[:,pos]
print("\n W: \n",W)
newX = np.dot(self.trainData, W)
x = newX[:,0]
y = newX[:,1]
Tip ="new Dim :%d"%(self.k-1)
plt.title(Tip, fontsize=14)
plt.scatter(x, y, c=self.trainLabel, cmap=plt.cm.cool)
plt.xlabel("$z_1$", fontsize=18)
plt.ylabel("$z_2$", fontsize=18)
plt.grid(True)
ld = LDAK()
ld.Reduce()
参考文档
《机器学习与应用》