简述LDA
LDA线性判别式分析也叫Fisher线性判别,其思路和感知机不同。感知机是根据自己的分类错误去调整决策线的位置,而Fisher线性判别的思想是把空间中的样本点向一个向量上投影,最终在这个向量确定的一维空间中就可以根据点的散布确定阈值,从而做线性判别了。
显然这个投影方向应尽可能让投影后的点类间相隔较远,而类内比较聚集,这样才能较好地分类。
原样本空间
如样本i的特征向量是xi:
其d个特征说明是在d维空间中的列向量,则要寻找的那个投影到的向量也是d维的:
在其上的投影,就是两个向量的内积(模乘模乘夹角余弦):
即上图中量段相乘,是一个标量:
w向量的模设为一个固定值时,这个标量的值就只和上图中红色段,也就是投影向量和特征向量的夹角有关了。所以这个值可以表征投影后的一维空间上样本的刻度。
原来的d维样本特征空间中的类均值向量:
用类内离散度矩阵,即是协方差矩阵的求期望项之和来表征一个类内样本的离散程度:
这里之所以不用除以N,是因为它比较的不过是在不同投影向量下的类内离散程度,样本始终这么多,它们都除以N或者都不除以N,不影响比较。
则总的类内离散程度就是用总类内离散度矩阵描述(w是within的意思):
在比较总的类内离散程度时,各个类的离散度矩阵是否要除以自己的样本数是有影响的!之所以不除以样本数,是因为必须考虑到不同类的样本数目差异带来的影响。
而类间的离散程度用类间离散度矩阵描述(b是between的意思):
投影后的一维空间
一维空间中的类内均值也就成了标量:
类内离散度也是标量:
总类内离散度也是标量:
类间离散度也是标量:
如何寻优
投影到一维空间以后,为了类间离散度尽可能大,类内离散度尽可能小,可以使用它们的比值作为Fisher准则函数:
寻找使其最大的方式有很多(如拉格朗日乘子法),这里只列出结论:
是Fisher判别准则下的最优投影方向,也即未增广的权重向量。
是偏置 bais,不考虑先验概率不同,常取:
代码实现
#-*-coding:utf-8-*-
from numpy import *
import operator
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
#获取男女数据集
def getData(pathstr1,pathstr0):
#fr1=open(r'myboy.txt')
#fr0=open(r'mygirl.txt')
fr1=open(pathstr1)
fr0=open(pathstr0)
arrayOLines1=fr1.readlines() #读取文件
arrayOLines0=fr0.readlines()
#特征矩阵
dataMat1=[]
dataMat0=[]
#男同学
for line in arrayOLines1:
line=line.strip() #strip()去掉首尾空格
listFromLine=line.split() #按空白字符分割成列表
#string变float
for i in range(len(listFromLine)):
listFromLine[i]=float(listFromLine[i])
#加入特征矩阵
dataMat1.append(listFromLine)
#女同学
for line in arrayOLines0:
line=line.strip() #strip()去掉首尾空格
listFromLine=line.split() #按空白字符分割成列表
#string变float
for i in range(len(listFromLine)):
listFromLine[i]=float(listFromLine[i])
#加入特征矩阵
dataMat0.append(listFromLine)
return dataMat1,dataMat0
#求均值
def getUSgm(xMat):
#行数1,列数xMat的列数
u=zeros((1,xMat.shape[1]))
u=mat(u)
for vec in xMat:
u+=vec
u/=xMat.shape[0] #除以行数
sgm=zeros((xMat.shape[1],xMat.shape[1]))
sgm=mat(sgm)
for vec in xMat:
cha=vec-u
sgm+=cha.T*cha
return u,sgm
#演示
def Go():
#获取训练集
dataMat1,dataMat0=getData(r'.\boynew.txt',r'.\girlnew.txt')
x1=[dataMat1[i][0] for i in range(len(dataMat1))]
y1=[dataMat1[i][1] for i in range(len(dataMat1))]
z1=[dataMat1[i][2] for i in range(len(dataMat1))]
x0=[dataMat0[i][0] for i in range(len(dataMat0))]
y0=[dataMat0[i][1] for i in range(len(dataMat0))]
z0=[dataMat0[i][2] for i in range(len(dataMat0))]
#绘制男女离散的点
fig=plt.figure()
ax=Axes3D(fig)
ax.scatter(x1,y1,z1,c=u'b',label='male')
ax.scatter(x0,y0,z0,c=u'g',label='female')
#男女特征矩阵
xMat1=mat(dataMat1)
xMat0=mat(dataMat0)
#均值u,各类离散度矩阵
u1,sgm1=getUSgm(xMat1)
u0,sgm0=getUSgm(xMat0)
#总的类内离散度矩阵
sw=sgm1+sgm0
#类间离散度矩阵
cha=u1-u0
sb=cha.T*cha
#权向量(乘完是一个列向量)
w=sw.I*cha.T
print '权向量:'
print w
u11=w.T*u1.T
u00=w.T*u0.T
w0=(-1.0/2)*(float(u11)+float(u00)) #偏置
print '偏置:',w0
#绘制决策面
X=arange(140,210,10) #采样
Y=arange(30,100,10)
X,Y=meshgrid(X,Y) #笛卡尔积
w1=float(w[0,0])
w2=float(w[1,0])
w3=float(w[2,0])
Z=-(w0+w1*X+w2*Y)/w3
ax.plot_surface(X,Y,Z,rstride=1,cstride=1,cmap='rainbow')
#给三个坐标轴注明
ax.set_xlabel('Height',color='r')
ax.set_ylabel('Weight',color='g')
ax.set_zlabel('Shoe Size',color='b')
err,lnth=errTst(w0,w) #传入增广的权向量,以测试错误率
print lnth,'次测试错误率是:',err/lnth
plt.show() #显示
#测试,传入的w是列向量!
def errTst(w0,w):
#获取测试集
dataMat1,dataMat0=getData(r'.\boytst.txt',r'.\girltst.txt')
errCount=0.0 #记录错误分类次数
for line in dataMat1:
xVec=mat(line) #转换成矩阵(向量)以方便运算
if xVec*w+w0<0: #对男生而言,<0是错误分类
errCount+=1
for line in dataMat0:
xVec=mat(line)
if xVec*w+w0>0: #对女生而言,>0是错误分类
errCount+=1
#返回测试错误次数,总测试次数
return errCount,len(dataMat1)+len(dataMat0)
测试
换个角度看下
2018年1月9日更新
低维的情况
这个是一开始做的只有2个特征而且线性可分的情况,可以和感知机得到的线比较一下(和感知机用的训练集相同)。
#-*-coding:utf-8-*-
from numpy import *
import operator
from matplotlib import pyplot as plt
#获取数据
def getData():
fr1=open(r'myboy.txt')
fr0=open(r'mygirl.txt')
arrayOLines1=fr1.readlines() #读取文件
arrayOLines0=fr0.readlines()
#特征矩阵
dataMat1=[]
dataMat0=[]
#男同学
for line in arrayOLines1:
line=line.strip() #strip()去掉首尾空格
listFromLine=line.split() #按空白字符分割成列表
#string变float
for i in range(len(listFromLine)):
listFromLine[i]=float(listFromLine[i])
#加入特征矩阵
dataMat1.append(listFromLine)
#女同学
for line in arrayOLines0:
line=line.strip() #strip()去掉首尾空格
listFromLine=line.split() #按空白字符分割成列表
#string变float
for i in range(len(listFromLine)):
listFromLine[i]=float(listFromLine[i])
#加入特征矩阵
dataMat0.append(listFromLine)
return dataMat1,dataMat0
#求均值
def getUSgm(xMat):
#行数1,列数xMat的列数
u=zeros((1,xMat.shape[1]))
u=mat(u)
for vec in xMat:
u+=vec
u/=xMat.shape[0] #除以行数
sgm=zeros((xMat.shape[1],xMat.shape[1]))
sgm=mat(sgm)
for vec in xMat:
cha=vec-u
sgm+=cha.T*cha
return u,sgm
#演示
def Go(i,j):
dataMat1,dataMat0=getData()
#i,j号特征在男女类各自的列表
Ftr1i=[line[i] for line in dataMat1]
Ftr1j=[line[j] for line in dataMat1]
Ftr0i=[line[i] for line in dataMat0]
Ftr0j=[line[j] for line in dataMat0]
#绘制男女离散的点
plt.scatter(Ftr1i,Ftr1j,c=u'b',label='male')
plt.scatter(Ftr0i,Ftr0j,c=u'g',label='female')
#男的特征矩阵
xMat1=[[Ftr1i[k],Ftr1j[k]] for k in range(len(Ftr1i))]
xMat1=mat(xMat1)
#女的特征矩阵
xMat0=[[Ftr0i[k],Ftr0j[k]] for k in range(len(Ftr0i))]
xMat0=mat(xMat0)
#均值u,各类离散度矩阵
u1,sgm1=getUSgm(xMat1)
u0,sgm0=getUSgm(xMat0)
#总的类内离散度矩阵
sw=sgm1+sgm0
#类间离散度矩阵
cha=u1-u0
sb=cha.T*cha
w=sw.I*cha.T #权向量(乘完是一个列向量)
print w
u11=w.T*u1.T
u00=w.T*u0.T
w0=(-1.0/2)*(float(u11)+float(u00)) #偏置
print w0
#绘制曲线用的横坐标(i特征)
if i==0:
lft=140
rgt=210
elif i==1:
lft=40
rgt=90
else:
lft=30
rgt=47
x1=linspace(lft,rgt,10) #反正是直线,不用采样太多
#绘制得到的直线
wi=float(w[0,0])
wj=float(w[1,0])
plt.plot(x1,-(wi*x1+w0)/wj,c=u'r')
plt.show()
代码重构
#-*-coding:utf-8-*-
from numpy import *
import operator
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
#获取男女数据集
def getData(pathstr1,pathstr0):
#fr1=open(r'myboy.txt')
#fr0=open(r'mygirl.txt')
fr1=open(pathstr1)
fr0=open(pathstr0)
arrayOLines1=fr1.readlines() #读取文件
arrayOLines0=fr0.readlines()
#特征矩阵
dataMat1=[]
dataMat0=[]
#男同学
for line in arrayOLines1:
line=line.strip() #strip()去掉首尾空格
listFromLine=line.split() #按空白字符分割成列表
#string变float
for i in range(len(listFromLine)):
listFromLine[i]=float(listFromLine[i])
#加入特征矩阵
dataMat1.append(listFromLine)
#女同学
for line in arrayOLines0:
line=line.strip() #strip()去掉首尾空格
listFromLine=line.split() #按空白字符分割成列表
#string变float
for i in range(len(listFromLine)):
listFromLine[i]=float(listFromLine[i])
#加入特征矩阵
dataMat0.append(listFromLine)
return dataMat1,dataMat0
#求均值
def getUSgm(xMat):
#行数1,列数xMat的列数
u=zeros((1,xMat.shape[1]))
u=mat(u)
for vec in xMat:
u+=vec
u/=xMat.shape[0] #除以行数
sgm=zeros((xMat.shape[1],xMat.shape[1]))
sgm=mat(sgm)
for vec in xMat:
cha=vec-u
sgm+=cha.T*cha
return u,sgm
#绘制分类面和散点图
def myFig(dataMat1,dataMat0,w0,w):
x1=[dataMat1[i][0] for i in range(len(dataMat1))]
y1=[dataMat1[i][1] for i in range(len(dataMat1))]
z1=[dataMat1[i][2] for i in range(len(dataMat1))]
x0=[dataMat0[i][0] for i in range(len(dataMat0))]
y0=[dataMat0[i][1] for i in range(len(dataMat0))]
z0=[dataMat0[i][2] for i in range(len(dataMat0))]
#绘制男女离散的点
fig=plt.figure()
ax=Axes3D(fig)
ax.scatter(x1,y1,z1,c=u'b',label='male')
ax.scatter(x0,y0,z0,c=u'g',label='female')
#绘制决策面
X=arange(140,210,10) #采样
Y=arange(30,100,10)
X,Y=meshgrid(X,Y) #笛卡尔积
w1=float(w[0,0])
w2=float(w[1,0])
w3=float(w[2,0])
Z=-(w0+w1*X+w2*Y)/w3
ax.plot_surface(X,Y,Z,rstride=1,cstride=1,cmap='rainbow')
#给三个坐标轴注明
ax.set_xlabel('Height',color='r')
ax.set_ylabel('Weight',color='g')
ax.set_zlabel('Shoe Size',color='b')
plt.show() #显示
#训练(获取偏置和权向量)
def Train(dataMat1,dataMat0):
#男女特征矩阵
xMat1=mat(dataMat1)
xMat0=mat(dataMat0)
#均值u,各类离散度矩阵
u1,sgm1=getUSgm(xMat1)
u0,sgm0=getUSgm(xMat0)
#总的类内离散度矩阵
sw=sgm1+sgm0
#类间离散度矩阵
cha=u1-u0
sb=cha.T*cha
#计算权向量(乘完是一个列向量)
w=sw.I*cha.T
#计算偏置
u11=w.T*u1.T
u00=w.T*u0.T
w0=(-1.0/2)*(float(u11)+float(u00))
return w0,w
#演示入口
def Go():
#获取训练集
dataMat1,dataMat0=getData(r'.\boynew.txt',r'.\girlnew.txt')
#训练
w0,w=Train(dataMat1,dataMat0)
print '权向量:'
print w
print '偏置:',w0
err,lnth=errTst(w0,w) #传入增广的权向量,以测试错误率
print lnth,'次测试错误率是:',err/lnth
myFig(dataMat1,dataMat0,w0,w) #绘制散点图和分类面
return w0,w
#测试,传入的w是列向量!
def errTst(w0,w):
#获取测试集
dataMat1,dataMat0=getData(r'.\boytst.txt',r'.\girltst.txt')
errCount=0.0 #记录错误分类次数
for line in dataMat1:
xVec=mat(line) #转换成矩阵(向量)以方便运算
if xVec*w+w0<0: #对男生而言,<0是错误分类
errCount+=1
for line in dataMat0:
xVec=mat(line)
if xVec*w+w0>0: #对女生而言,>0是错误分类
errCount+=1
#返回测试错误次数,总测试次数
return errCount,len(dataMat1)+len(dataMat0)
#绘制ROC曲线
def getROC(w0,w):
#绘制ROC曲线用的采样点
X=[]
Y=[]
#分类面采样阈值从w0-0.1到w0+0.1,步长0.001
#但range只能用int类型,所以乘以1000倍,传入时再除以1000
for b in range(int(1000*w0-100),int(1000*w0+100),1):
#获取假阳率和真阳率
FPR,RECALL=getFprRecall(float(b)/1000,w)
#分别加入到坐标采样列表中
X.append(FPR)
Y.append(RECALL)
#绘制ROC曲线
plt.plot(X,Y,"g-",linewidth=2)
#横纵坐标名称,标题名称
plt.xlabel('FPR')
plt.ylabel('RECALL')
plt.show()
#获取假阳性率FPR和召回率RECALL
def getFprRecall(w0,w):
#获取测试集
dataMat1,dataMat0=getData(r'.\boytst.txt',r'.\girltst.txt')
errPstv=0.0 #记录假阳性样本数
relPstv=0.0 #记录真阳性样本数
for line in dataMat1:
xVec=mat(line) #转换成矩阵(向量)以方便运算
if xVec*w+w0>0: #对男生而言,>0正确分类是真阳性样本
relPstv+=1
for line in dataMat0:
xVec=mat(line)
if xVec*w+w0>0: #对女生而言,>0错误分类是假阳性样本
errPstv+=1
#print errPstv,relPstv
return errPstv/len(dataMat0),relPstv/len(dataMat1)
#留一法获取交叉验证错误率
def getCrossValidationOnly1():
print '正在进行留一法交叉验证...'
#重获训练集
dataMat1,dataMat0=getData(r'.\boynew.txt',r'.\girlnew.txt')
'''
#建立标签向量
labelVec1=[1 for i in range(len(dataMat1))]
labelVec0=[-1 for i in range(len(dataMat0))]
#标签向量合并
labelVec=labelVec1 #用男的
labelVec.extend(labelVec0) #合并女的
#训练集合并
dataMat=dataMat1
dataMat.extend(dataMat0)
'''
errCount=0.0 #错误分类次数
#遍历作留一法交叉验证
#发挥python特性,用切片合并的方式获取新的训练集子集
#留的是男生
for i in range(len(dataMat1)):
subMat=dataMat1[0:i]
subMat_End=dataMat1[i+1:]
subMat.extend(subMat_End) #男生抽离第i个
w0,w=Train(subMat,dataMat0) #训练
xVec=mat(dataMat1[i])
if xVec*w+w0<0: #对男生而言,<0是错误分类
errCount+=1
#留的是女生
for i in range(len(dataMat0)):
subMat=dataMat0[0:i]
subMat_End=dataMat0[i+1:]
subMat.extend(subMat_End) #女生抽离第i个
w0,w=Train(dataMat1,subMat) #训练
xVec=mat(dataMat0[i])
if xVec*w+w0>0: #对女生而言,>0是错误分类
errCount+=1
lnth=len(dataMat1)+len(dataMat0)
print lnth,'个样本的留一法交叉验证错误率:',errCount/lnth
(散点和分类面图没变,不贴了)