第 1 周任務
分類問題:K-鄰近算法
分類問題:決策樹
第 2 周任務
分類問題:樸素貝葉斯
分類問題:邏輯迴歸
第 3 周任務
分類問題:支持向量機
第 4 周任務
分類問題:AdaBoost
第 5 周任務
迴歸問題:線性迴歸、嶺迴歸、套索方法、逐步迴歸等
迴歸問題:樹迴歸
第 6 周任務
聚類問題:K均值聚類
相關問題:Apriori
第 7 周任務
相關問題:FP-Growth
第 8 周任務
簡化數據:PCA主成分分析
簡化數據:SVD奇異值分解
1.簡介
支持向量機(Support Vector Machine, SVM)是一類按監督學習(supervised learning)方式對數據進行二元分類的廣義線性分類器(generalized linear classifier),其決策邊界是對學習樣本求解的最大邊距超平面(maximum-margin hyperplane)。
SVM使用鉸鏈損失函數(hinge loss)計算經驗風險(empirical risk)並在求解系統中加入了正則化項以優化結構風險(structural risk),是一個具有稀疏性和穩健性的分類器 。SVM可以通過核方法(kernel method)進行非線性分類,是常見的核學習(kernel learning)方法之一 。
SVM被提出於1964年,在二十世紀90年代後得到快速發展並衍生出一系列改進和擴展算法,在人像識別、文本分類等模式識別(pattern recognition)問題中有得到應用。
支持向量機(Suport Vector Machine,常簡稱爲SVM),是一個監督式學習的方式。支持向量機屬於一般化線性分類器,這類分類器的特點是能夠同時最小化經驗誤差與最大化幾何邊緣區,因此支持向量機機也被稱爲最大邊緣區分類器。
藍色和紅色的線圈出來的點就是所謂的支持向量,離分界線最近的點,如果去掉這些點,直線多半要改變位置。Classifier Boundary就是決策函數f(x),在兩個類的中間。紅色和藍色之間的間隙就是我們要的最大化分類的間隙。
1.1. 拉格朗日乘子法
有拉格朗日乘子法的地方,必然是一個組合優化問題。比如
這是一個帶等式約束的優化問題,有目標值,有約束條件,不能直接求導。可以使用拉格朗日方法,把這個約束乘以一個係數加到目標函數中去,這樣相當與既考慮了原目標函數,也考慮了約束條件。然後分別對x求導等於0,
把它帶點菜約束條件中去,可以看到,2個變量兩個等式,最終可再帶回去求x就可以了。更高一層,帶有不等式的約束問題怎麼辦?需要用更一般化的拉格朗日乘子法,即KKT條件,來求解這個問題。
1.2. KKT條件
任何原始問題約束條件無非最多三種,等式約束,大於號約束,小於號約束,而這三種最終通過將約束方程簡化成兩類:約束方程等於0和約束方程小於0。
假設原始問題約束條件爲下例所示:
那麼把約束條件變個樣子
現在拿到目標函數中去變成
那麼KKT條件的定理是什麼呢?就是如果一個優化問題在轉變成
其中g是不等式約束,h是等式約束。那麼KKT條件就是函數的最優值,它必定滿足下面條件:
- L對各個x求導爲0
這三個等式很好理解,重點是第三個句子不好理解,因爲我們知道在約束條件變完或,所有的,且求和還要爲0。那麼爲什麼KKT的條件是這樣的呢?
某次的在爲最優解起作用,那麼它的係數值(可以)不爲0,如果某次沒有爲下一次的最優解起作用,那麼它的係數就必須爲0。
1.3. 對偶算法
爲了求解線性可分支持向量機的最優化問題,將它作爲原始最優化問題,應用到拉格朗日對偶性,通過求解對偶問題得到原始問題的最優解,這就是線性可支持向量機的對偶算法(dual algorithm)。這樣做的優點,一是對偶問題往往根據容易求解;二是自然引入核函數,進而推廣到非線性可分類問題。
首先構建拉格朗日函數(Lagrange function)。爲此,對每一個不等式約束引入拉格朗日乘子(Lagrange multiplier)定義拉格朗日函數:
其中爲拉格朗日乘子向量。
根據拉格朗日對偶性,原始問題的對偶問題是極大極小問題
爲了得到對偶函數問題的解,需要先求對的極小,再求的極大
(1)求
將拉格朗日函數)分別對求偏導數並令其等於
將(1)代入拉格朗日函數,並利用(2),即可得
即
(2)求對的極,即對偶問題
將公式(3)的目標函數由極大值轉換成求極小,就得到下面與之等價的對偶最優化問題
(3)解
假設是對偶最優化問題的解,則存在下標使得,並求按下式求得原始最優化的解
根據KKT條件成立,即得
因此
,且至少存在一個,假設,,那麼不是原始問題的解,所以
那麼分離的超平面可以寫成
決策函數可以寫成
由此可以看出,分類決策函數只依賴於輸入x和訓練樣本輸入的內積,式(8)稱爲線性可分支持向量機的對偶形式。
2. 動手實戰
2.1. 項目案例:預測患疝氣病的馬的存活問題
項目概述
我們先使用簡單的數據集進行測試,數據集下載
可視化數據集
import matplotlib.pyplot as plt
import numpy as np
"""
函數說明:讀取數據
Parameters:
fileName - 文件名
Returns:
dataMat - 數據矩陣
labelMat - 數據標籤
"""
def loadDataSet(fileName):
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.readlines(): #逐行讀取,濾除空格等
lineArr = line.strip().split('\t')
dataMat.append([float(lineArr[0]), float(lineArr[1])]) #添加數據
labelMat.append(float(lineArr[2])) #添加標籤
return dataMat,labelMat
"""
函數說明:數據可視化
Parameters:
dataMat - 數據矩陣
labelMat - 數據標籤
Returns:
無
"""
def showDataSet(dataMat, labelMat):
data_plus = [] #正樣本
data_minus = [] #負樣本
for i in range(len(dataMat)):
if labelMat[i] > 0:
data_plus.append(dataMat[i])
else:
data_minus.append(dataMat[i])
data_plus_np = np.array(data_plus) #轉換爲numpy矩陣
data_minus_np = np.array(data_minus) #轉換爲numpy矩陣
plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1]) #正樣本散點圖
plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1]) #負樣本散點圖
plt.show()
if __name__ == '__main__':
dataMat, labelMat = loadDataSet('testSet.txt')
showDataSet(dataMat, labelMat)
運行結果如下:
簡化版SMO算法
from time import sleep
import matplotlib.pyplot as plt
import numpy as np
import random
import types
"""
函數說明:讀取數據
Parameters:
fileName - 文件名
Returns:
dataMat - 數據矩陣
labelMat - 數據標籤
"""
def loadDataSet(fileName):
dataMat = []; labelMat = []
fr = open(fileName)
for line in fr.readlines(): #逐行讀取,濾除空格等
lineArr = line.strip().split('\t')
dataMat.append([float(lineArr[0]), float(lineArr[1])]) #添加數據
labelMat.append(float(lineArr[2])) #添加標籤
return dataMat,labelMat
"""
函數說明:隨機選擇alpha
Parameters:
i - alpha
m - alpha參數個數
Returns:
j -
"""
def selectJrand(i, m):
j = i #選擇一個不等於i的j
while (j == i):
j = int(random.uniform(0, m))
return j
"""
函數說明:修剪alpha
Parameters:
aj - alpha值
H - alpha上限
L - alpha下限
Returns:
aj - alpah值
Author:
Jack Cui
Blog:
http://blog.csdn.net/c406495762
Zhihu:
https://www.zhihu.com/people/Jack--Cui/
Modify:
2017-09-21
"""
def clipAlpha(aj,H,L):
if aj > H:
aj = H
if L > aj:
aj = L
return aj
"""
函數說明:簡化版SMO算法
Parameters:
dataMatIn - 數據矩陣
classLabels - 數據標籤
C - 鬆弛變量
toler - 容錯率
maxIter - 最大迭代次數
Returns:
無
"""
def smoSimple(dataMatIn, classLabels, C, toler, maxIter):
#轉換爲numpy的mat存儲
dataMatrix = np.mat(dataMatIn); labelMat = np.mat(classLabels).transpose()
#初始化b參數,統計dataMatrix的維度
b = 0; m,n = np.shape(dataMatrix)
#初始化alpha參數,設爲0
alphas = np.mat(np.zeros((m,1)))
#初始化迭代次數
iter_num = 0
#最多迭代matIter次
while (iter_num < maxIter):
alphaPairsChanged = 0
for i in range(m):
#步驟1:計算誤差Ei
fXi = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[i,:].T)) + b
Ei = fXi - float(labelMat[i])
#優化alpha,更設定一定的容錯率。
if ((labelMat[i]*Ei < -toler) and (alphas[i] < C)) or ((labelMat[i]*Ei > toler) and (alphas[i] > 0)):
#隨機選擇另一個與alpha_i成對優化的alpha_j
j = selectJrand(i,m)
#步驟1:計算誤差Ej
fXj = float(np.multiply(alphas,labelMat).T*(dataMatrix*dataMatrix[j,:].T)) + b
Ej = fXj - float(labelMat[j])
#保存更新前的aplpha值,使用深拷貝
alphaIold = alphas[i].copy(); alphaJold = alphas[j].copy();
#步驟2:計算上下界L和H
if (labelMat[i] != labelMat[j]):
L = max(0, alphas[j] - alphas[i])
H = min(C, C + alphas[j] - alphas[i])
else:
L = max(0, alphas[j] + alphas[i] - C)
H = min(C, alphas[j] + alphas[i])
if L==H: print("L==H"); continue
#步驟3:計算eta
eta = 2.0 * dataMatrix[i,:]*dataMatrix[j,:].T - dataMatrix[i,:]*dataMatrix[i,:].T - dataMatrix[j,:]*dataMatrix[j,:].T
if eta >= 0: print("eta>=0"); continue
#步驟4:更新alpha_j
alphas[j] -= labelMat[j]*(Ei - Ej)/eta
#步驟5:修剪alpha_j
alphas[j] = clipAlpha(alphas[j],H,L)
if (abs(alphas[j] - alphaJold) < 0.00001): print("alpha_j變化太小"); continue
#步驟6:更新alpha_i
alphas[i] += labelMat[j]*labelMat[i]*(alphaJold - alphas[j])
#步驟7:更新b_1和b_2
b1 = b - Ei- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[i,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[i,:]*dataMatrix[j,:].T
b2 = b - Ej- labelMat[i]*(alphas[i]-alphaIold)*dataMatrix[i,:]*dataMatrix[j,:].T - labelMat[j]*(alphas[j]-alphaJold)*dataMatrix[j,:]*dataMatrix[j,:].T
#步驟8:根據b_1和b_2更新b
if (0 < alphas[i]) and (C > alphas[i]): b = b1
elif (0 < alphas[j]) and (C > alphas[j]): b = b2
else: b = (b1 + b2)/2.0
#統計優化次數
alphaPairsChanged += 1
#打印統計信息
print("第%d次迭代 樣本:%d, alpha優化次數:%d" % (iter_num,i,alphaPairsChanged))
#更新迭代次數
if (alphaPairsChanged == 0): iter_num += 1
else: iter_num = 0
print("迭代次數: %d" % iter_num)
return b,alphas
"""
函數說明:分類結果可視化
Parameters:
dataMat - 數據矩陣
w - 直線法向量
b - 直線解決
Returns:
無
"""
def showClassifer(dataMat, w, b):
#繪製樣本點
data_plus = [] #正樣本
data_minus = [] #負樣本
for i in range(len(dataMat)):
if labelMat[i] > 0:
data_plus.append(dataMat[i])
else:
data_minus.append(dataMat[i])
data_plus_np = np.array(data_plus) #轉換爲numpy矩陣
data_minus_np = np.array(data_minus) #轉換爲numpy矩陣
plt.scatter(np.transpose(data_plus_np)[0], np.transpose(data_plus_np)[1], s=30, alpha=0.7) #正樣本散點圖
plt.scatter(np.transpose(data_minus_np)[0], np.transpose(data_minus_np)[1], s=30, alpha=0.7) #負樣本散點圖
#繪製直線
x1 = max(dataMat)[0]
x2 = min(dataMat)[0]
a1, a2 = w
b = float(b)
a1 = float(a1[0])
a2 = float(a2[0])
y1, y2 = (-b- a1*x1)/a2, (-b - a1*x2)/a2
plt.plot([x1, x2], [y1, y2])
#找出支持向量點
for i, alpha in enumerate(alphas):
if abs(alpha) > 0:
x, y = dataMat[i]
plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
plt.show()
"""
函數說明:計算w
Parameters:
dataMat - 數據矩陣
labelMat - 數據標籤
alphas - alphas值
Returns:
無
"""
def get_w(dataMat, labelMat, alphas):
alphas, dataMat, labelMat = np.array(alphas), np.array(dataMat), np.array(labelMat)
w = np.dot((np.tile(labelMat.reshape(1, -1).T, (1, 2)) * dataMat).T, alphas)
return w.tolist()
if __name__ == '__main__':
dataMat, labelMat = loadDataSet('testSet.txt')
b,alphas = smoSimple(dataMat, labelMat, 0.6, 0.001, 40)
w = get_w(dataMat, labelMat, alphas)
showClassifer(dataMat, w, b)
運行結果如下:
參考資料
- https://blog.csdn.net/c406495762/article/details/78072313
- https://www.jianshu.com/p/59ed6e68703d