一、簡介
支持向量機(Support Vector Machine,SVM)是一類按監督學習方式對數據進行二元分類的廣義線性分類器,其決策邊界是對學習樣本求解的最大邊距超平面。它的目的是尋找一個超平面來對樣本進行分割,分割的原理則是間隔最大化,最終轉化為一個凸二次規劃問題來求解,由簡至繁的模型包括:
- 當訓練樣本線性可分時,通過硬間隔最大化,學習一個線性可分支持向量機
- 當訓練樣本近似線性可分時,通過軟間隔最大化,學習一個線性支持向量機
- 當訓練樣本線性不可分時,通過核技巧和軟間隔最大化,學習一個非線性支持向量機
二、原理解析
1、點到超平面的距離
假設有一個超平面,在平面外有一個點X,我們要求點X到超平面的距離dist(x,h),但是直接求不好求,我們設超平面上有兩個點(x',x''),即
我們也可以得到:
我們計算一下點X到點X'之間的距離,我們求出x與x'之間的距離然后乘以平面的法向量就得到了dist(x,h),即點x到超平面的距離(線性代數里面的定理)。
[1]
:
2、樣本分類
我們假設數據集,我們規定Y為樣本的類別:
當X為正例時,Y=+1;當X為負例時,Y=-1;
可以推出:
我們的優化目標是找到一條線(求w和b),使得離該線最近的點能夠距離平面最遠,這是 SVM非常重要的原理。我們可以將公式[1]
的絕對值通過乘去掉,因為
,這時公式變成了,
,結果肯定是大于0的,這時我們繼續做一個假設(大于0肯定是一個數值,我們這里假設它大于1,為了計算方便),即對于線(w,b)可以通過放縮使得其結果值|Y| >= 1,
,最終我們的目標就是
我們的目標函數就是,轉成求最小值
3、拉格朗日乘數法求解目標函數
我們的目的是求出和
,這時我們引入拉格朗日乘數法求解,
我們利用對偶問題,轉換一下求解思路,
分別對w和b求偏導,分別得到兩個條件:
[2]
[3]
分別將
[2][3]
式代入化簡可得,
這時我們完成了第一步的求解
繼續對求極大值,
極大值轉換求極小值,
[4]
4、實例求解
已知一個如圖所示的訓練數據集,其正例點是,負例點是
,試求最大間隔分離超平面。
樣本正例用1表示,負例用-1表示:我們帶入
[4]
式中,化簡后的結果為:
我們將代入后分別對參數進行求導得:不符合我們之前的條件,所以最終的解應該為邊界上的點,當 (舍去),當此時,所以最小值在(0.25,0,0.25)處取得。我們由
[2]
可知,所以平面方程為:
我們計算出,通過上圖我們也發現,并非邊界點,最終計算的也跟它無關,這里我們就發現所謂的支持向量機是由邊界上的點所支撐起來的,那么我們就把邊界上的點叫做支持向量!支持向量:真正發揮作用的數據點,
5、軟間隔
- 當C趨近于無窮大時:意味著分類嚴格不能有錯誤(C越大,為了求min,則
越小,此時1-
就越大)
- 當C趨近于很小時:意味著可以有更大的錯誤容忍,C是我們需要指定的一個參數
再進入拉格朗日乘數法后,結果就變成了,
分別對求偏導,
最后利用對偶問題進行化簡后的結果跟之前一樣,只是的取值范圍變成了
6、核函數
如果數據非常復雜,在低維中很難進行區分,我們可以將數據映射到高維空間。這樣特征信息就更多了,決策的邊界也更容易建立。核函數的目的就是將低維數據映射到高維數據上。
這里我們再強調一下,在求目標函數的過程中,我們有求內積的操作,但是維數過大,在高維上求內積的計算量非常大,SVM在數學上有一個特性:在低維上內積,用內積的結果做轉換,相當于把數據在高維上做內積,結果是一樣的。
我們常用的是高斯核函數:
SVM有很多核函數可以幫助我們將數據進行映射,這也是SVM的厲害之處。
三、案例分析
這里我們使用的數據集是sklearn包自帶的人臉數據集fetch_lfw_people
,算法也是sklearn中封裝好的算法包,我們先來看一下API:from sklearn.svm import SVC
這里面的參數有:
svc(C=1.0, kernel='rbf', degree=3, gamma='auto_deprecated', coef0=0.0, shrinking=True, probability=False,tol=1e-3, cache_size=200, class_weight=None,verbose=False, max_iter=-1, decision_function_shape='ovr',random_state=None)
- C:懲罰系數
用來控制損失函數的懲罰系數,這個我們在公式中講過。 - kernel 核函數
參數選擇有RBF, Linear, Poly, Sigmoid,precomputed或者自定義一個核函數, 默認的是"RBF",即徑向基核,也就是高斯核函數;而Linear指的是線性核函數,Poly指的是多項式核,Sigmoid指的是雙曲正切函數tanh核; - degree
當指定kernel為'poly'時,表示選擇的多項式的最高次數,默認為三次多項式;若指定kernel不是'poly',則忽略,即該參數只對'poly'有用 - gamma:核函數系數
該參數是rbf,poly和sigmoid的內核系數;默認是'auto',表示模型復雜度,gamma越大,σ越小,使得高斯分布又高又瘦,造成模型只能作用于支持向量附近,可能導致過擬合;反之,gamma越小,σ越大,高斯分布會過于平滑,在訓練集上分類效果不佳,可能導致欠擬合 - coef0:核函數常數值(y=kx+b中的b值)
只有‘poly’和‘sigmoid’核函數有,默認值是0。 - shrinking:是否進行啟發式
如果能預知哪些變量對應著支持向量,則只要在這些樣本上訓練就夠了,其他樣本可不予考慮,這不影響訓練結果,但降低了問題的規模并有助于迅速求解。進一步,如果能預知哪些變量在邊界上(即a=C),則這些變量可保持不動,只對其他變量進行優化,從而使問題的規模更小,訓練時間大大降低。這就是Shrinking技術。 Shrinking技術基于這樣一個事實:支持向量只占訓練樣本的少部分,并且大多數支持向量的拉格朗日乘子等于C。 - probability:是否使用概率估計,默認是False
必須在 fit( ) 方法前使用,該方法的使用會降低運算速度。 - tol:殘差收斂條件,默認是0.0001
即容忍1000分類里出現一個錯誤,與LR中的一致;誤差項達到指定值時則停止訓練。 - cache_size 緩沖大小,用來限制計算量大小,默認是200M。
- class_weight:{dict, ‘balanced’},字典類型或者'balance'字符串。
權重設置,正類和反類的樣本數量是不一樣的,這里就會出現類別不平衡問題,該參數就是指每個類所占據的權重,默認為1,即默認正類樣本數量和反類一樣多,也可以用一個字典dict指定每個類的權值,或者選擇默認的參數balanced,指按照每個類中樣本數量的比例自動分配權值。 - verbose:是否啟用詳細輸出。
在訓練數據完成之后,會把訓練的詳細信息全部輸出打印出來,可以看到訓練了多少步,訓練的目標值是多少;但是在多線程環境下,由于多個線程會導致線程變量通信有困難,因此verbose選項的值就是出錯,所以多線程下不要使用該參數。 - max_iter:最大迭代次數,默認是-1,即沒有限制。
這個是硬限制,它的優先級要高于tol參數,不論訓練的標準和精度達到要求沒有,都要停止訓練。 - decision_function_shape
原始的SVM只適用于二分類問題,如果要將其擴展到多類分類,就要采取一定的融合策略,這里提供了三種選擇。‘ovo’ 一對一,為one v one,即將類別兩兩之間進行劃分,用二分類的方法模擬多分類的結果,決策所使用的返回的是(樣本數,類別數*(類別數-1)/2); ‘ovr’ 一對多,為one v rest,即一個類別與其他類別進行劃分,返回的是(樣本數,類別數),或者None,就是不采用任何融合策略。默認是ovr,因為此種效果要比oro略好一點。 - random_state
在使用SVM訓練數據時,要先將訓練數據打亂順序,用來提高分類精度,這里就用到了偽隨機序列。如果該參數給定的是一個整數,則該整數就是偽隨機序列的種子值;如果給定的就是一個隨機實例,則采用給定的隨機實例來進行打亂處理;如果啥都沒給,則采用默認的 np.random實例來處理。
這里我們需要用的所有包,
from sklearn.datasets import fetch_lfw_people
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline
from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
import seaborn as sns
接下來,我們進行案例分析,首先先來看一下數據長什么樣,
def svm():
"""
SVM進行簡單人臉分類
:return:
"""
#獲取數據集
faces =fetch_lfw_people(min_faces_per_person=60)
print(faces.target_names)
print(faces.images.shape)
# 圖形
fig,ax=plt.subplots(3,5)
for i,axi in enumerate(ax.flat):
axi.imshow(faces.images[i],cmap='bone')
axi.set(xticks=[],yticks=[],xlabel=faces.target_names[faces.target[i]])
plt.show()
return None
if __name__ == "__main__":
svm()
接下來,我們劃分數據集,由于像素點過多,我們使用PCA進行降維,這里我們先對PCA,SVC進行實例化,
#每個圖的大小是[62x47]
#將數據集劃分為測試集與訓練集
x_train,y_train,x_test,y_test = train_test_split(faces.data,faces.target,random_state=40)
#
#使用PCA降維
#我們降維成150維
# whiten: 白化。所謂白化,就是對降維后的數據的每個特征進行標準化,讓方差都為1。
# random_state:偽隨機數發生器的種子,在混洗數據時用于概率估計
pca = PCA(n_components=150,whiten=True,random_state=42)
#實例化SVM
svc = SVC(kernel='rbf',class_weight='balanced')
然后我們使用sklearn包中pipeline模塊結合PCA和SVC對數據進行處理,它提供了兩種模式:串行化和并行化,這里我們進行串行化就可以,由于兩種算法參數選擇較多,我們使用交叉驗證的方式選擇最優的參數。
model = make_pipeline(pca,svc)
#交叉驗證確定系數
param = {'svc__C':[1,5,10],
'svc__gamma':[0.0001,0.0005,0.001]}
grid = GridSearchCV(model,param_grid =param)
grid.fit(x_train,x_test)
print(grid.best_params_)
我們使用最后的參數模型進行預測,并畫圖像顯示,黑色代表正確,紅色代表錯誤。
model=grid.best_estimator_
yfit = model.predict(y_train)
print(yfit.shape)
#算法分類之后的圖形
fig,ax=plt.subplots(4,6)
for i,axi in enumerate(ax.flat):
axi.imshow(y_train[i].reshape(62,47),cmap='bone')
axi.set(xticks=[],yticks=[])
axi.set_ylabel(faces.target_names[yfit[i]].split()[-1],
color='black' if yfit[i] == y_test[i] else 'red')
fig.suptitle('Predicted Names:Incorrect Labels in Red',size=14)
plt.show()
我們也可以看一下實驗的精確率和召回率,以及混淆矩陣。
print(classification_report(y_test,yfit,target_names=faces.target_names))
#混淆矩陣
mat = confusion_matrix(y_test,yfit)
sns.heatmap(mat.T,square=True,annot=True,fmt='d',
xticklabels=faces.target_names,
yticklabels=faces.target_names)
plt.xlabel('true label')
plt.ylabel('predicted label')
plt.show()
關于SVM支持向量機這部分的學習到這里就結束了,還有補充時,我會再更新,這一節公式推導較多,有不懂的地方可以下方留言或者私信。