SVM算法概述
SVM(Support Vector Mac)又称为支持向量机,是一种二分类的模型。它在解决 小样本、非线性及高维模式识别问题中表现出许多特有的优势,并能够推广应用到函数 拟合等问题。
支持向量机可以分为线性核非线性两大类。其主要思想为找到空间中的一个能够将 所有数据样本划开的超平面,并且使得本集中所有数据到这个超平面的距离最短。
基本概念
最大间隔分离超平面
支持向量机的目标是求解一个超平面,使得超平面和离超平面较近的点之间的间距 更大。在二维空间中,可以理解为,希望寻找到这样的直线,使得距离这条直线最近的 点到这条直线的距离最短。在高维空间中这样的直线称之为超平面,因为当维数大于三 的时候我们已经无法想象出这个平面的具体样子。那些距离这个超平面最近的点就是所 谓支持向量。
最大几何间隔分离超平面的求解可以表示为下面的带约束最优化问题:
$$ \begin{align} \max _{w, b} \frac{\widehat{\gamma}}{\|w\|} \\ \text {s.t. } y_i\left[\left (\frac{w}{\|w\|} \right)^{T} x_{i}+\frac{b}{\|w\|}\right], i & = 1,2, \ldots m \end{align} $$寻找最大间隔
点到超平面的距离公式:
$$
d=\frac{|w_1 \times x_1+w_2 \times x_2+\ldots w_n \times x_n+b \mid}{\sqrt{w_1^{2}+w_2^{2}+\ldots+w_n^{2}}}=\frac{\left|W^{T} \times X+b\right|}{|W|}
$$
最大间隔的优化模型目标函数:
$$ \arg \max _{w, b}\left\{\min \left(y\left(w^{T} x+b\right)\right) \times \frac{1}{\|w\|}\right\} $$可等价替换为:
$$
\min \frac{1}{2 \times |w|^2}
$$
寻找松弛变量:
引入一个松弛变量$\zeta$来允许一些数据可以处于分隔面错误的一侧。 这时完整的模型变为:
$$ \begin{array}{l} \min \frac{1}{2}\|w\|^{2}+C \sum_{i = 1}^{N} \zeta\\ s.t.\left\{\begin{matrix} \zeta \geq 0, i = 1,2 \ldots n \\ y_i\left(w^{T} x_i+b\right) \geq 1-\zeta_{i}, i = 1,2 \ldots n \end{matrix}\right. \end{array} $$核函数:
当数据不是线性可分时,通过一个非线性变换将输入空间映射到一个特征空 间,使得在输入空间中的超曲面模型对应于特征空间H中的超平面模型,从而通过特 征空间H中求解线性支持向量机就可以分类。 下面是一些常用核函数表:
核函数名称 | 核函数表达式 |
---|---|
线性核 | $K(x,y)=x^Ty$ |
多项式核 | $K(x,y)=(ax^Ty+c)^d$ |
高斯核 | $K(x,y)=exp(-\frac{|x-y|^2}{2{\sigma}^2})$ |
指数核 | $K(x,y)=exp(-\frac{|x-y|}{2{\sigma}^2})$ |
拉普拉斯核 | $K(x,y)=exp(-\frac{\|x-y\|}{{\sigma}})$ |
Sigmoid核 | $K(x,y)=\tanh(ax^Ty+c)$ |
数据集介绍
本次实验选取的数据集是YaleB
人脸数据集,它一共有15类,每一类有11个样本,每个样本是80*80
的灰度图
任务简介
使用三种核函数(高斯核、线性核、多项式核)在YaleB
数据集上验证SVM算法
整体思路
数据预处理
图片读取
将图片全部读取转化为矩阵,并且缩放成40*40
大小的矩阵,最后将该矩阵拉成一个1*1600
的向量,表示一个样本,然后将所有样本拼接成一个165*1600
的矩阵,作为输入矩阵。
构建标签
直接利用文件夹名,去掉f
即可构建标签,标签即为1~15
这15个数字,最后的标签组成一个列向量,并返回
数据标准化
对于样本矩阵中的每一个样本,将其数字减去均值再除以方差,进行标准化
代码实现
数据预处理的代码如下:
# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
from sklearn import preprocessing
def process(path):
faces = os.listdir(path)
# 数据以及标签
data = []
labels = []
for face in faces:
label = int(face[1:])
pic_names = os.listdir(os.path.join(path, face))
for pic in pic_names:
temp = np.array(cv2.imread(os.path.join(path, face, pic), 0))
# 缩小图像进行降维
temp = cv2.resize(temp, (40, 40))
data.append(temp)
labels.append(label)
data = np.array(data).reshape(len(labels), -1)
data = preprocessing.scale(data)
# print(data)
labels = np.array(labels)
# print(data.shape)
# 展示单张读取的图片
# plt.imshow(temp, cmap='gray')
# plt.show()
return data, labels
划分训练集测试集
此次用到的划分函数还是跟之前一样,使用train_test_split
函数进行划分,测试集的比例为0.25
构建分类器并训练
本次实验分别选取了高斯核以及多项式核进行SVM分类器的构建,其中:
- 高斯核:需要调整的参数为$\gamma$以及$C$($C$为惩罚项)
- 多项式核:需要调整的参数是$degree$(多项式的阶数)和$C$($C$为惩罚项)
分类结果展示
代码实现
import process_data as pd
from sklearn.model_selection import train_test_split
import random
from sklearn import svm
from sklearn.metrics import classification_report
# 划分训练集、测试集的函数
def train_test(data, target, num):
train_data, test_data, train_target, test_target = \
train_test_split(data, target, test_size=0.25, random_state=num, shuffle=True)
return train_data, test_data, train_target, test_target
def main():
# 数据预处理部分
path = "./YALE"
data, labels = pd.process(path)
# print(data.shape)
# 产生随机种子
# nums=random.sample(range(0,10000),100)
train_data, test_data, train_labels, test_labels =train_test(data,labels,5)
# 构建svm分类器
## 线性核
# clf_linear = svm.SVC(decision_function_shape="ovo", kernel="linear", C=10)
## 高斯核
clf_rbf = svm.SVC(decision_function_shape="ovr", kernel="rbf", gamma='auto', C=1.5)
## 多项式核
clf_ploy = svm.SVC(decision_function_shape="ovr", kernel="poly", gamma='auto', degree=1, C=10)
#拟合训练集
# clf_linear.fit(train_data, train_labels)
clf_rbf.fit(train_data, train_labels)
clf_ploy.fit(train_data, train_labels)
# linear_pred_labels = clf_linear.predict(test_data)
rbf_pred_labels = clf_rbf.predict(test_data)
ploy_pred_labels = clf_ploy.predict(test_data)
# print(linear_pred_labels)
print("实际标签:\n",test_labels)
print("高斯核预测的标签:\n",rbf_pred_labels)
print("多项式核预测的实际标签:\n",ploy_pred_labels)
# 计算分类准确率
# acc_linear = sum(linear_pred_labels==test_labels)/len(test_labels)
# print('linear kernel: The accuracy is', acc_linear)
acc_rbf = sum(rbf_pred_labels==test_labels)/len(test_labels)
print('rbf kernel: The accuracy is {:.2f}%'.format(acc_rbf*100))
acc_ploy = sum(ploy_pred_labels==test_labels)/len(test_labels)
print('ploy kernel: The accuracy is {:.2f}%'.format(acc_ploy*100))
if __name__ == "__main__":
main()
结果分析
可以看出,SVM的性能还是很不错的,测试准确率都达到了90%以上,但在调参数的过程中,我发现高斯核对参数十分敏感,有时参数改动很小对结果的影响也挺大。
手动调参有时候效率比较低下,本人觉得可以利用定步长(步长越小精度越高)二维(对应两个参数)搜索法,来探究两个参数取何值时分类效果最好