線性判別准則與線性分類編程實踐
1.線性判別分析LDA總結
LDA是一種經典的降維方法線性判別分析(Linear Discriminant Analysis, 以下簡稱LDA)。LDA在模式識別領域(比如人臉識別,艦艇識別等圖形圖像識別領域)中有非常廣泛的應用,因此我們有必要了解下它的算法原理。
1.LDA的思想
LDA是一種監督學習的降維技術,也就是說它的數據集的每個樣本是有類別輸出的。這點和PCA不同。PCA是不考慮樣本類別輸出的無監督降維技術。LDA的思想可以用一句話概括,就是“投影后類內方差最小,類間方差最大”。什么意思呢? 我們要將數據在低維度上進行投影,投影后希望每一種類別數據的投影點盡可能的接近,而不同類別的數據的類別中心之間的距離盡可能的大。
可能還是有點抽象,我們先看看最簡單的情況。假設我們有兩類數據 分別為紅色和藍色,如下圖所示,這些數據特征是二維的,我們希望將這些數據投影到一維的一條直線,讓每一種類別數據的投影點盡可能的接近,而紅色和藍色數據中心之間的距離盡可能的大。

上圖中國提供了兩種投影方式,哪一種能更好的滿足我們的標准呢?從直觀上可以看出,右圖要比左圖的投影效果好,因為右圖的黑色數據和藍色數據各個較為集中,且類別之間的距離明顯。左圖則在邊界處數據混雜。以上就是LDA的主要思想了,當然在實際應用中,我們的數據是多個類別的,我們的原始數據一般也是超過二維的,投影后的也一般不是直線,而是一個低維的超平面。
在我們將上面直觀的內容轉化為可以度量的問題之前,我們先了解些必要的數學基礎知識,這些在后面講解具體LDA原理時會用到。
2.瑞利商(Rayleigh quotient)與廣義瑞利商(genralized Rayleigh quotient)
我們首先來看看瑞利商的定義。瑞利商是指這樣的函數R(A,x)R(A,x):

其中xx為非零向量,而AA為n×nn×n的Hermitan矩陣。所謂的Hermitan矩陣就是滿足共軛轉置矩陣和自己相等的矩陣,即AH=AAH=A。如果我們的矩陣A是實矩陣,則滿足AT=AAT=A的矩陣即為Hermitan矩陣。
瑞利商R(A,x)R(A,x)有一個非常重要的性質,即它的最大值等於矩陣AA最大的特征值,而最小值等於矩陣AA的最小的特征值,也就是滿足

以上就是瑞利商的內容,現在我們再看看廣義瑞利商。廣義瑞利商是指這樣的函數R(A,B,x)R(A,B,x):

其中xx為非零向量,而A,BA,B為n×nn×n的Hermitan矩陣。BB為正定矩陣。它的最大值和最小值是什么呢?其實我們只要通過將其通過標准化就可以轉化為瑞利商的格式。我們令x=B^(−1/2)x′x=B−1/2x′,則分母轉化為:

分子轉化為
此時我們的R(A,B,x)R(A,B,x)轉化為R(A,B,x′)R(A,B,x′):

3. 二類LDA原理
現在我們回到LDA的原理上,我們在第一節說講到了LDA希望投影后希望同一種類別數據的投影點盡可能的接近,而不同類別的數據的類別中心之間的距離盡可能的大,但是這只是一個感官的度量。現在我們首先從比較簡單的二類LDA入手,嚴謹的分析LDA的原理。
假設我們的數據集D={(x1,y1),(x2,y2),…,((xm,ym))},其中任意樣本xi為n維向量,yi∈{0,1}。我們定義Nj(j=0,1)為第j類樣本的個數,Xj(j=0,1)為第j類樣本的集合,而μj(j=0,1)為第j類樣本的均值向量,定義Σj(j=0,1)為第j類樣本的協方差矩陣(嚴格說是缺少分母部分的協方差矩陣)。
μj的表達式為:

Σj的表達式為

由於是兩類數據,因此我們只需要將數據投影到一條直線上即可。假設我們的投影直線是向量w,則對任意一個樣本本xi,它在直線w的投影為wTxi,對於我們的兩個類別的中心點μ0,μ1,在在直線w的投影為wTμ0和wTμ1。由於LDA需要讓不同類別的數據的類別中心之間的距離盡可能的大,也就是我們要最大化||wTμ0−wTμ1||22,同時我們希望同一種類別數據的投影點盡可能的接近,也就是要同類樣本投影點的協方差wTΣ0w和wTΣ1w盡可能的小,即最小化wTΣ0w+wTΣ1w。綜上所述,我們的優化目標為:

我們一般定義類內散度矩陣Sw為:

同時定義類間散度矩陣Sb為:

這樣我們的優化目標重寫為:

仔細一看上式,這不就是我們的廣義瑞利商嘛!這就簡單了,利用我們第二節講到的廣義瑞利商的性質,我們知道我們的J(w′)最大值為矩陣S−12wSbS−12w的最大特征值,而對應的w′為S−12wSbS−12w的最大特征值對應的特征向量! 而S−1wSb的特征值和S−12wSbS−12w的特征值相同,S−1wSb的特征向量w和S−12wSbS−12w的特征向量w′滿足w=S−12ww′
的關系!
注意到對於二類的時候,Sbw的方向恆平行於μ0−μ1,不妨令Sbw=λ(μ0−μ1),將其帶入:(S−1wSb)w=λw,可以得到w=S−1w(μ0−μ1), 也就是說我們只要求出原始二類樣本的均值和方差就可以確定最佳的投影方向w了。
4.優缺點
LDA算法的主要優點有:
1)在降維過程中可以使用類別的先驗知識經驗,而像PCA這樣的無監督學習則無法使用類別先驗知識。
2)LDA在樣本分類信息依賴均值而不是方差的時候,比PCA之類的算法較優。
LDA算法的主要缺點有:
1)LDA不適合對非高斯分布樣本進行降維,PCA也有這個問題。
2)LDA降維最多降到類別數k-1的維數,如果我們降維的維度大於k-1,則不能使用LDA。當然目前有一些LDA的進化版算法可以繞過這個問題。
3)LDA在樣本分類信息依賴方差而不是均值的時候,降維效果不好。
4)LDA可能過度擬合數據
2.線性分類算法(支持向量機,SVM)
1.簡介
支持向量機(Support Vector Machine ,SVM)的主要思想是:建立一個最優決策超平面,使得該平面兩側距離該平面最近的兩類樣本之間的距離最大化,從而對分類問題提供良好的泛化能力。對於一個多維的樣本集,系統隨機產生一個超平面並不斷移動,對樣本進行分類,直到訓練樣本中屬於不同類別的樣本點正好位於該超平面的兩側,滿足該條件的超平面可能有很多個,SVM正式在保證分類精度的同時,尋找到這樣一個超平面,使得超平面兩側的空白區域最大化,從而實現對線性可分樣本的最優分類。
支持向量機中的支持向量(Support Vector)是指訓練樣本集中的某些訓練點,這些點最靠近分類決策面,是最難分類的數據點。SVM中最優分類標准就是這些點距離分類超平面的距離達到最大值;“機”(Machine)是機器學習領域對一些算法的統稱,常把算法看做一個機器,或者學習函數。SVM是一種有監督的學習方法,主要針對小樣本數據進行學習、分類和預測,類似的根據樣本進行學習的方法還有決策樹歸納算法等。
SVM的優點:
1、不需要很多樣本,不需要有很多樣本並不意味着訓練樣本的絕對量很少,而是說相對於其他訓練分類算法比起來,同樣的問題復雜度下,SVM需求的樣本相對是較少的。並且由於SVM引入了核函數,所以對於高維的樣本,SVM也能輕松應對。
2、結構風險最小。這種風險是指分類器對問題真實模型的逼近與問題真實解之間的累積誤差。
3、非線性,是指SVM擅長應付樣本數據線性不可分的情況,主要通過松弛變量(也叫懲罰變量)和核函數技術來實現,這一部分也正是SVM的精髓所在。
2.線性分類
對於最簡單的情況,在一個二維空間中,要求把下圖所示的白色的點和黑色的點集分類,顯然,下圖中的這條直線可以滿足我們的要求,並且這樣的直線並不是唯一的。

SVM的作用就是要查找到最合適的決策直線所在的位置。其他可行的直線可以如下所示:

那么哪條直線才是最優的呢?就是分類兩側距離決策直線距離最近的點離該直線綜合最遠的那條直線,即分割的間隙越大越好,這樣分出來的特征的精確性更高,容錯空間也越大。這個過程在SVM中被稱為最大間隔(Maximum Marginal)。下圖紅色和藍色直線之間的間隙就是要最大化的間隔,顯然在這種情況下,分類直線位於中間位置時可以使得最大間隔達到最大值。
2.線性不可分
現實情況中基於上文中線性分類的情況並不具有代表性,更多情況下樣本數據的分布式雜亂無章的,這種情況下,基於線性分類的直線分割面就無法准確完成分割。如下圖,在黑色點集中摻雜有白色點,白色點集中摻雜有黑色點的情況:

對於這種非線性的情況,一種方法是使用一條曲線去完美分割樣品集,如下圖:

從二維空間擴展到多維,可以使用某種非線性的方法,讓空間從原本的線性空間轉換到另一個維度更高的空間,在這個高維的線性空間中,再用一個超平面對樣本進行划分,這種情況下,相當於增加了不同樣本間的區分度和區分條件。在這個過程中,核函數發揮了至關重要的作用,核函數的作用就是在保證不增加算法復雜度的情況下將完全不可分問題轉化為可分或達到近似可分的狀態。

上圖左側紅色和綠色的點在二維空間中,綠色的點被紅色點包圍,線性不可分,但是擴展到三維(多維)空間后,可以看到,紅綠色點間Z方向的距離有明顯差別,同種類別間的點集有一個共同特征就是他們基本都在一個面上,所以借用這個區分,可以使用一個超平面對這兩類樣本進行分類,如上圖中黃色的平面。
線性不可分映射到高維空間,可能導致很高的維度,特殊情況下可能達到無窮多維,這種情況下會導致計算復雜,伴隨產生驚人的計算量。但是在SVM中,核函數的存在,使得運算仍然是在低維空間進行的,避免了在高維空間中復雜運算的時間消耗。
SVM另一個巧妙之處是加入了一個松弛變量來處理樣本數據可能存在的噪聲問題,如下圖所示:

SVM允許數據點在一定程度上對超平面有所偏離,這個偏移量就是SVM算法中可以設置的outlier值,對應於上圖中黑色實現的長度。松弛變量的加入使得SVM並非僅僅是追求局部效果最優,而是從樣本數據分布的全局出發,統籌考量,正所謂成大事者不拘小節。
3.LDA的實現
1.鳶尾花數據集
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets.samples_generator import make_classification
class LDA():
def Train(self, X, y):
"""X為訓練數據集,y為訓練label"""
X1 = np.array([X[i] for i in range(len(X)) if y[i] == 0])
X2 = np.array([X[i] for i in range(len(X)) if y[i] == 1])
# 求中心點
mju1 = np.mean(X1, axis=0) # mju1是ndrray類型
mju2 = np.mean(X2, axis=0)
# dot(a, b, out=None) 計算矩陣乘法
cov1 = np.dot((X1 - mju1).T, (X1 - mju1))
cov2 = np.dot((X2 - mju2).T, (X2 - mju2))
Sw = cov1 + cov2
# 計算w
w = np.dot(np.mat(Sw).I, (mju1 - mju2).reshape((len(mju1), 1)))
# 記錄訓練結果
self.mju1 = mju1 # 第1類的分類中心
self.cov1 = cov1
self.mju2 = mju2 # 第2類的分類中心
self.cov2 = cov2
self.Sw = Sw # 類內散度矩陣
self.w = w # 判別權重矩陣
def Test(self, X, y):
"""X為測試數據集,y為測試label"""
# 分類結果
y_new = np.dot((X), self.w)
# 計算fisher線性判別式
nums = len(y)
c1 = np.dot((self.mju1 - self.mju2).reshape(1, (len(self.mju1))), np.mat(self.Sw).I)
c2 = np.dot(c1, (self.mju1 + self.mju2).reshape((len(self.mju1), 1)))
c = 1/2 * c2 # 2個分類的中心
h = y_new - c
# 判別
y_hat = []
for i in range(nums):
if h[i] >= 0:
y_hat.append(0)
else:
y_hat.append(1)
# 計算分類精度
count = 0
for i in range(nums):
if y_hat[i] == y[i]:
count += 1
precise = count / nums
# 顯示信息
print("測試樣本數量:", nums)
print("預測正確樣本的數量:", count)
print("測試准確度:", precise)
return precise
if '__main__' == __name__:
# 產生分類數據
n_samples = 500
X, y = make_classification(n_samples=n_samples, n_features=2, n_redundant=0, n_classes=2,n_informative=1, n_clusters_per_class=1, class_sep=0.5, random_state=10)
# LDA線性判別分析(二分類)
lda = LDA()
# 60% 用作訓練,40%用作測試
Xtrain = X[:299, :]
Ytrain = y[:299]
Xtest = X[300:, :]
Ytest = y[300:]
lda.Train(Xtrain, Ytrain)
precise = lda.Test(Xtest, Ytest)
# 原始數據
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y)
plt.xlabel("x1")
plt.ylabel("x2")
plt.title("Test precise:" + str(precise))
plt.show()

2.月亮數據集
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
class LDA():
def Train(self, X, y):
"""X為訓練數據集,y為訓練label"""
X1 = np.array([X[i] for i in range(len(X)) if y[i] == 0])
X2 = np.array([X[i] for i in range(len(X)) if y[i] == 1])
# 求中心點
mju1 = np.mean(X1, axis=0) # mju1是ndrray類型
mju2 = np.mean(X2, axis=0)
# dot(a, b, out=None) 計算矩陣乘法
cov1 = np.dot((X1 - mju1).T, (X1 - mju1))
cov2 = np.dot((X2 - mju2).T, (X2 - mju2))
Sw = cov1 + cov2
# 計算w
w = np.dot(np.mat(Sw).I, (mju1 - mju2).reshape((len(mju1), 1)))
# 記錄訓練結果
self.mju1 = mju1 # 第1類的分類中心
self.cov1 = cov1
self.mju2 = mju2 # 第1類的分類中心
self.cov2 = cov2
self.Sw = Sw # 類內散度矩陣
self.w = w # 判別權重矩陣
def Test(self, X, y):
"""X為測試數據集,y為測試label"""
# 分類結果
y_new = np.dot((X), self.w)
# 計算fisher線性判別式
nums = len(y)
c1 = np.dot((self.mju1 - self.mju2).reshape(1, (len(self.mju1))), np.mat(self.Sw).I)
c2 = np.dot(c1, (self.mju1 + self.mju2).reshape((len(self.mju1), 1)))
c = 1/2 * c2 # 2個分類的中心
h = y_new - c
# 判別
y_hat = []
for i in range(nums):
if h[i] >= 0:
y_hat.append(0)
else:
y_hat.append(1)
# 計算分類精度
count = 0
for i in range(nums):
if y_hat[i] == y[i]:
count += 1
precise = count / (nums+0.000001)
# 顯示信息
print("測試樣本數量:", nums)
print("預測正確樣本的數量:", count)
print("測試准確度:", precise)
return precise
if '__main__' == __name__:
# 產生分類數據
X, y = make_moons(n_samples=100, noise=0.15, random_state=42)
# LDA線性判別分析(二分類)
lda = LDA()
# 60% 用作訓練,40%用作測試
Xtrain = X[:60, :]
Ytrain = y[:60]
Xtest = X[40:, :]
Ytest = y[40:]
lda.Train(Xtrain, Ytrain)
precise = lda.Test(Xtest, Ytest)
# 原始數據
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y)
plt.xlabel("x1")
plt.ylabel("x2")
plt.title("Test precise:" + str(precise))
plt.show()

4、SVM處理月亮數據集
1.線性核分析
導入月亮數據集和svm方法:
#
#這是線性svm
from sklearn import datasets #導入數據集
from sklearn.svm import LinearSVC #導入線性svm
from matplotlib.colors import ListedColormap
from sklearn.preprocessing import StandardScaler
data_x,data_y=datasets.make_moons(noise=0.15,random_state=777)#生成月亮數據集
# random_state是隨機種子,nosie是方
plt.scatter(data_x[data_y==0,0],data_x[data_y==0,1])
plt.scatter(data_x[data_y==1,0],data_x[data_y==1,1])
data_x=data_x[data_y<2,:2]#只取data_y小於2的類別,並且只取前兩個特征
plt.show()

可以發現月亮數據集是交織在一起的兩條弧線
scaler=StandardScaler()# 標准化
scaler.fit(data_x)#計算訓練數據的均值和方差
data_x=scaler.transform(data_x) #再用scaler中的均值和方差來轉換X,使X標准化
liner_svc=LinearSVC(C=1e9,max_iter=100000)#線性svm分類器,iter是迭達次數,c值決定的是容錯,c越大,容錯越小
liner_svc.fit(data_x,data_y)
# 邊界繪制函數
def plot_decision_boundary(model,axis):
x0,x1=np.meshgrid(
np.linspace(axis[0],axis[1],int((axis[1]-axis[0])*100)).reshape(-1,1),
np.linspace(axis[2],axis[3],int((axis[3]-axis[2])*100)).reshape(-1,1))
# meshgrid函數是從坐標向量中返回坐標矩陣
x_new=np.c_[x0.ravel(),x1.ravel()]
y_predict=model.predict(x_new)#獲取預測值
zz=y_predict.reshape(x0.shape)
custom_cmap=ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0,x1,zz,cmap=custom_cmap)
#畫圖並顯示參數和截距
plot_decision_boundary(liner_svc,axis=[-3,3,-3,3])
plt.scatter(data_x[data_y==0,0],data_x[data_y==0,1],color='red')
plt.scatter(data_x[data_y==1,0],data_x[data_y==1,1],color='blue')
plt.show()
print('參數權重')
print(liner_svc.coef_)
print('模型截距')
print(liner_svc.intercept_

可以看到這種情況下線性svm效果並不好。
2.多項式核分析
# 導入月亮數據集和svm方法
#這是多項式核svm
from sklearn import datasets #導入數據集
from sklearn.svm import LinearSVC #導入線性svm
from sklearn.pipeline import Pipeline #導入python里的管道
from matplotlib.colors import ListedColormap
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler,PolynomialFeatures #導入多項式回歸和標准化
data_x,data_y=datasets.make_moons(noise=0.15,random_state=777)#生成月亮數據集
# random_state是隨機種子,nosie是方
plt.scatter(data_x[data_y==0,0],data_x[data_y==0,1])
plt.scatter(data_x[data_y==1,0],data_x[data_y==1,1])
data_x=data_x[data_y<2,:2]#只取data_y小於2的類別,並且只取前兩個特征
plt.show()
def PolynomialSVC(degree,c=10):#多項式svm
return Pipeline([
# 將源數據 映射到 3階多項式
("poly_features", PolynomialFeatures(degree=degree)),
# 標准化
("scaler", StandardScaler()),
# SVC線性分類器
("svm_clf", LinearSVC(C=10, loss="hinge", random_state=42,max_iter=10000))
])
# 進行模型訓練並畫圖
poly_svc=PolynomialSVC(degree=3)
poly_svc.fit(data_x,data_y)
plot_decision_boundary(poly_svc,axis=[-1.5,2.5,-1.0,1.5])#繪制邊界
plt.scatter(data_x[data_y==0,0],data_x[data_y==0,1],color='red')#畫點
plt.scatter(data_x[data_y==1,0],data_x[data_y==1,1],color='blue')
plt.show()
print('參數權重')
print(poly_svc.named_steps['svm_clf'].coef_)
print('模型截距')
print(poly_svc.named_steps['svm_clf'].intercept_)

3.高斯核
gamma=1
## 導入包
from sklearn import datasets #導入數據集
from sklearn.svm import SVC #導入svm
from sklearn.pipeline import Pipeline #導入python里的管道
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler#導入標准化
def RBFKernelSVC(gamma=1.0):
return Pipeline([
('std_scaler',StandardScaler()),
('svc',SVC(kernel='rbf',gamma=gamma))
])
svc=RBFKernelSVC(gamma=1)#gamma參數很重要,gamma參數越大,支持向量越小
svc.fit(data_x,data_y)
plot_decision_boundary(svc,axis=[-1.5,2.5,-1.0,1.5])
plt.scatter(data_x[data_y==0,0],data_x[data_y==0,1],color='red')#畫點
plt.scatter(data_x[data_y==1,0],data_x[data_y==1,1],color='blue')
plt.show()

gamma=10

gamma=100

當參數gamma的值設置的特別大的時候,模型容易出現過擬合的結果,適當的調整gamma的大小,可以得到最優的模型。
5、總結
LDA算法既可以用來降維,又可以用來分類,但是目前來說,主要還是用於降維。在我們進行圖像識別圖像識別相關的數據分析時,LDA是一個有力的工具。SVM則是求出分界線附近的支持向量,通過支持向量來確認分界線
參考學習了以下大神的博客
https://www.cnblogs.com/pinard/p/6244265.html
https://blog.csdn.net/dcrmg/article/details/53000150
https://blog.csdn.net/weixin_46129506/article/details/121071181

