支持向量机(support vector machines
,SVM
)是一种二分类模型,它的目的是寻找一个超平面来对样本进行分割,分割的原则是间隔最大化,最终转化为一个凸二次规划问题来求解。由简至繁的模型包括:
给定线性可分训练数据集,通过间隔最大化或等价地求解相应的凸二次规划问题学习得到的分离超平面为:
以及相应的分类决策函数为:
称为线性可分支持向量机。
一般来说,一个点距离分离超平面的远近可以表示分类预测的确信程度。在超平面 w ∗ x + b = 0 w*x+b=0 w∗x+b=0确定的情况下, ∣ w ∗ x + b ∣ |w*x+b| ∣w∗x+b∣能够相对的表示点x距离超平面的远近。而 w ∗ x + b w*x+b w∗x+b的符号与类标记y的符号是否一致能够表示分类是否正确。所以可用 y ( w ∗ x + b ) y(w*x+b) y(w∗x+b)来表示分类的正确性及确信度,这就是函数间隔(functional margin
)的概念。
函数间隔:对于给定的训练数据集T和超平面 ( w , b ) (w,b) (w,b),定义超平面 ( w , b ) (w,b) (w,b)关于样本点 ( x i , y i ) (xi,yi) (xi,yi)的函数间隔为:
定义超平面(w,b)关于训练数据集T的函数间隔为超平面(w,b)关于T中所有样本点 ( x i , y i ) (xi,yi) (xi,yi)的函数间隔最小值,即:
对分类超平面的法向量w加某些约束,如规范化,||w||=1,使得间隔确定,此时函数间隔为几何间隔(geometric margin):
欲找到具有最大间隔的划分超平面,也就是γ最大:
支持向量机的学习策略是间隔最大化,可形式化为一个求解凸二次规划(convex quadratic programming
)。
仅需最大化 ∥ w ∥ − 1 \parallel w \parallel^{-1} ∥w∥−1
这等价于最小化 ∥ w ∥ 2 \parallel w \parallel^{2} ∥w∥2
上式可重写为:
这是支持向量机的基本型,其本身为一个凸二次规划问题。
使用拉格朗日乘子法可得到其“对偶问题”(dual problem
),其拉格朗日函数可写为(其中 α i αi αi是拉格朗日乘子):
利用对偶性的结论, 对 L ( ω , b , α ) L(ω,b,α) L(ω,b,α)关于 ω ω ω和 b b b求偏导数:
将上式带入式(2)中,可得式(1)的对偶问题:
实际任务中,求解式(4)会造成很大的开销,SMO(Sequential Minimal Optimization) 是一种求解的高效算法。
SMO 算法是支持向量机学习的一种快速算法,其特点是不断地将原二次规划问题分解为只有两个变量的二次规划子问题,并对子问题进行解析求解,直到所有变量满足 KKT 条件为止。
SMO的基本思路类似动态规划, 也是一种启发式算法,它将原优化问题分解为多个小优化问题来求解,并且对这些小优化问题进行顺序求解得到的结果作为作为整体的结果。
解出 α α α后,求出 ω ω ω与 b b b即可得到模型:
因式(1)中有不等式约束,上述过程需满足KKT(Karush-Kuhn-Tucker) 条件,即要求:
在前面的讨论中,我们假设训练样本在样本空间或者特征空间中是线性可分的,对线性不可分的训练数据是不适用的。通常情况下,线性不可分的训练数据有一些特异点,去掉这些特异点后,剩下的大部分的样本点组成的集合是线性可分的。
线性不可分意味着某些样本点 ( x i , y i ) (xi,yi) (xi,yi)不能满足间隔大于等于1的条件,样本点落在超平面与边界之间。为解决这一问题,可以对每个样本点引入一个松弛变量 ξ i ≥ 0 ξi≥0 ξi≥0,使得间隔加上松弛变量大于等于1,这样约束条件变为:
同时,对于每一个松弛变量 ξ i ≥ 0 ξi≥0 ξi≥0 ,支付一个代价 ξ i ≥ 0 ξi≥0 ξi≥0
目标函数变为:
其中 C>0为惩罚参数,C值大时对误分类的惩罚增大, C值小时对误分类的惩罚减小。上式包含两层含义:使 1 2 ∣ ∣ w ∣ ∣ 2 \frac{1}{2}||w|{{|}^{2}} 21∣∣w∣∣2尽量小即间隔尽量大,同时使误分类点的个数尽量小,C是调和两者的系数。
有了上式,可以和线性可分支持向量机一样考虑线性支持向量机的学习过程,此时,线性支持向量机的学习问题变成如下凸二次规划问题的求解(原始问题):
与线性可分支持向量机的对偶问题解法一致,上式的拉格朗日函数为:
其中 α i ≥ 0 , μ i ≥ 0 αi≥0,μi≥0 αi≥0,μi≥0是拉格朗日乘子。
令 L ( w , b , α , ξ , μ ) L(w,b,α,ξ,μ) L(w,b,α,ξ,μ)对 w , b , ξ w,b,\xi w,b,ξ的偏导数为0可得如下:
将上式代入拉格朗日函数得:
解出 α \alpha α 之后,根据公式可以求得 w,进而求得 b,可以得到模型:
上述过程的KKT条件为:
对于原始样本空间不是线性可分的情况,可将样本从原始空间映射到一个更高维的特征空间,使得样本在这个特征空间内线性可分。如果原始空间是有限维,即属性数有限,那么一定存在一个高维特征空间使样本可分。
令 ϕ ( x ) ϕ(x) ϕ(x)表示将 x x x映射后的特征向量,于是,在特征空间中划分超平面所对应的模型可表示为:
根据上式,将原始问题转换为:
其对偶问题为:
若遇到高维或无穷维问题,求解 ϕ ( x i ) T ϕ ( x j ) \phi(x_i)^T \phi(x_j) ϕ(xi)Tϕ(xj)会很困难,而利用核函数,可避免这个问题:
求解后即可得到:
这里的函数: κ ( x i , x j ) κ(xi,xj) κ(xi,xj)就是核函数,在实际应用中,通常人们会从一些常用的核函数里选择(根据样本数据的不同,选择不同的参数,实际上就得到了不同的核函数),下面给出常用的核函数:
sigmoid
核函数( β > 0 , θ > 0 β>0,θ>0 β>0,θ>0 ):linearSVC
实现了线性分类支持向量机,它是根据liblinear
实现的,可以用于二类分类,也可用于多类分类。sklearn.svm.LinearSVC
其原型为:sklearn.svm.LinearSVC(penalty=’l2’, loss=’squared_hinge’, dual=True, tol=0.0001, C=1.0, multi_class=’ovr’, fit_intercept=True, intercept_scaling=1, class_weight=None, verbose=0, random_state=None, max_iter=1000)
penalty
:str
,指定l1
或者l2
,罚项的范数,默认为l2
(它是标准SVC采用的)。
loss
:str
,表示损失函数。
hinge
: 此时为合页损失函数(它是标准SVM的损失函数)。squared_hinge
: 此时为合页损失函数的平方。dual
:bool
,如果为True
,则解决对偶问题;如果为False
,则解决原始问题。当n_samples>n_features
时,倾向于采用False
。
tol
:float
,指定终止迭代的阈值。
C
:float
,罚项参数。
multi_class
:str
,指定多类分类问题的策略。
ovr
:采用one-vs-rest
分类策略。crammer_singer
:多类联合分类,很少用。因为它的计算量大,而且精度不会更佳,此时忽略loss
、penalty
、dual
参数。fit_intercept
:bool
,如果为True
,则计算截距,即决策函数的常数项;否则忽略截距。
intercept_scaling
:float
,如果提供了,则实例X变成向量[X, intercept_scaling]
。此时相当于添加了一个人工特征,该特征对所有实例都是常数值。
class_weight
:字典
或者字符串balanced
,指定各个类的权重,若未提供,则认为类的权重为1。
balanced
,则每个类的权重是它出现频率的倒数。verbose
:int
,表示是否开启verbose
输出。
random_state
:int
、一个RandomState
实例或者None
int
:指定随机数生成器的种子。RandomState实例
:指定随机数生成器。None
:使用默认的随机数生成器。max_iter
:int
,指定最大的迭代次数。
coef_
: 一个数组,它给出了各个特征的权重。
intercept_
: 一个数组,它给出了截距,即决策函数中的常数项。
fit(X, y)
:训练模型。
predict(X)
:用模型进行预测,返回预测值。
score(X, y[, sample_weight])
:返回在(X, y)上预测的准确率(accuracy
)。
SVC
实现了非线性分类支持向量机,它是根据libsvm
实现的,可以用于二类分类,也可以用于多类分类。sklearn.svm.SVC
其原型为:sklearn.svm.SVC(C=1.0, kernel=’rbf’, degree=3, gamma=’auto_deprecated’, coef0=0.0, shrinking=True, probability=False, tol=0.001, cache_size=200, class_weight=None, verbose=False, max_iter=-1, decision_function_shape=’ovr’, random_state=None)
C
:float
,罚项参数。
kernel
:str
,指定核函数。
linear
:线性核函数。poly
:多项式核函数。rbf
:默认值,高斯核函数。sigmoid
: sigmod
核函数。precomputed
:表示提供了kernel matrix
,或者提供一个可调用对象,该对象用于计算kernel matrix
。degree
:int
,指定当核函数是多项式核函数时,多项式的系数。对于其他核函数,该参数无效。
gamma
:float
,当核函数是rbf
、poly
、sigmoid
时,核函数的系数。如果为auto
,则表示系数为1/n_features
。
coef0
:fioat
,用于指定核函数中的自由项。只有当核函数是poly
和sigmoid
时有效。
shrinking
:bool
,如果为True
,则使用启发式收缩(shrinking heuristic
)。
probability
:bool
,如果为True
,则会进行概率估计。它必须在训练之前设置好,且概率估计会拖慢训练速度。
tol
:float
,指定终止迭代的阈值。
cache_size
:float
,指定了kernel cache
的大小,单位为MB。
class_weight
:字典
或者字符串balanced
,指定各个类的权重,若未提供,则认为类的权重为1。
balanced
,则每个类的权重是它出现频率的倒数。verbose
:int
,表示是否开启verbose
输出。
max_iter
:int
,指定最大的迭代次数。
decision_function_shape
:str
或者None
,指定决策函数的形状。
ovr
:使用one-vs-rest
准则,那么决策函数形状是(n_samples, n_classes)
。ovo
:使用one-vs-one
准则,那么决策函数形状是(n_samples, n_classes*(n_classes -1) / 2)
。None
:默认值。采用该值时,会使用ovr
。random_state
:int
、一个RandomState
实例或者None
int
:指定随机数生成器的种子。RandomState实例
:指定随机数生成器。None
:使用默认的随机数生成器。support_
:一个数组,形状为[n_SV]
,支持向量的下标。
support_vectors_
:一个数组,形状为[n_SV, n_features]
,支持向量。
n_support_
:一个数组,形状为[n_class]
,每一个类别的支持向量的个数。
dual_coef
:一个数组,形状为[n_class-1, n_SV]
。对偶问题中,在分类决策函数中每一个支持向量的系数。
coef_
:一个数组,形状为[n_class-1, n_features]
。原始问题中,每个特征的系数。只有在linear kernel
中有效。
intercept_
:一个数组,形状为[n_class * (n_class - 1) / 2]
,决策函数中的常数项。
fit(X, y[, sample_weight])
:训练模型。
predict(X)
:用模型进行预测,返回预测值。
score(X, y[, sample_weight])
: 返回在(X, y)上预测的准确率(accuracy
)。
predict_log_proba(X)
: 返回一个数组,数组的元素依次是X预测为各个类别的概率的对数值。
predict_proba(X)
: 返回一个数组,数组的元素依次是X预测为各个类别的概率值。
本文使用支持向量机算法解决乳腺癌检测问题。scikit-learn
中自带一个乳腺癌数据集,为了方便起见,我们直接使用,读者也可直接在网上下载。乳腺癌数据集地址
首先,我们加载数据,输出数据形状和特征。以查看数据:
__author__ = "fpZRobert"
"""
支持向量机实战-乳腺癌检测
"""
import warnings
warnings.filterwarnings("ignore", category=FutureWarning, module="sklearn", lineno=196)
from sklearn.datasets import load_breast_cancer
"""
加载数据
"""
cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
print("Shape of X: {0}; positive example: {1}; negative: {2}".format(X.shape, y[y==1].shape[0], y[y==0].shape[0])) # 查看数据的形状和类别分布
Out:
Shape of X: (569, 30); positive example: 357; negative: 212
我们可以看到,数据集总共有569个样本,每个样本有30个特征,其中357个阳性(y=1)样本,212个阴性(y=0)样本。之前在【机器学习算法笔记系列】逻辑回归(LR)算法详解和实战中详细介绍了数据集,这里不做过多描述,再一次介绍下10个特征:
特征 | 含义 |
---|---|
radius | 半径,即病灶中心点离边界的平均距离 |
texture | 纹理,灰度值的标准偏差 |
perimeter | 周长,即病灶的大小 |
area | 面积,也是反映病灶大小的一个指标 |
smoothness | 平滑度,即半径的变化幅度 |
compactness | 密实度,周长的平方除以面积的商 |
concavity | 凹度,凹陷部分轮廓的严重程度 |
concave points | 凹点,凹陷轮廓的数量 |
symmetry | 对称性 |
fractal dimension | 分形维度 |
把数据集划分为训练集和测试集(划分比例一般80%用于训练,20%用于测试):
"""
拆分数据集
"""
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
根据上述数据的介绍,我们的数据集很少,高斯核函数太复杂,容易造成过拟合,模型效果不会很好。为了验证我们的猜想,我们首先使用高斯核函数试着拟合数据,看下效果:
"""
训练模型
"""
from sklearn.svm import SVC
clf = SVC(C=1.0, kernel="rbf", gamma=0.1) # 使用高斯核函数
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print("train score: {0}; test score: {1}".format(train_score, test_score))
Out:
train score: 1.0; test score: 0.631578947368421
训练数据集评分接近满分,而交叉验证集评分很低,这是典型的过拟合现象。我们画出学习曲线,更加直观的看一下:
"""
绘制学习曲线
"""
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import ShuffleSplit
from sklearn.model_selection import learning_curve
# 绘制学习曲线
def plot_learning_curve(plt, estimator, title, X, y, ylim=None, cv=None,
n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
plt.title(title)
if ylim is not None:
plt.ylim(*ylim)
plt.xlabel("Training examples")
plt.ylabel("Score")
train_sizes, train_scores, test_scores = learning_curve(
estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.grid()
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="r")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="g")
plt.plot(train_sizes, train_scores_mean, 'o--', color="r",
label="Training score")
plt.plot(train_sizes, test_scores_mean, 'o-', color="g",
label="Cross-validation score")
plt.legend(loc="best")
return plt
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
title = "Learning Curves for Gaussian Kernel"
plt.figure(figsize=(10, 4), dpi=144)
plot_learning_curve(plt, SVC(C=1.0, kernel="rbf", gamma=0.01), title, X, y, ylim=(0.5, 1.01), cv=cv)
plt.show()
代码中gamma
的参数选择为0.1,这个值相对已经很小了。通过上述参数介绍,我们知道gamma
是核函数的系数(gamma
值越小,支持向量越多)。我们试着利用之前讲过的GridSearchCV
来自动选择参数:
"""
模型调优
"""
from sklearn.model_selection import GridSearchCV
gammas = np.linspace(0, 0.0003, 30)
param_grid = {"gamma": gammas}
clf = GridSearchCV(SVC(), param_grid, cv=5)
clf.fit(X, y)
print(" best param: {0}\n best score: {1}".format(clf.best_params_, clf.best_score_))
Out:
best score: 0.9367311072056239
gamma
是选择RBF
函数作为kernel
后,该函数自带的一个参数。隐含地决定了数据映射到新的特征空间后的分布,gamma
越大,支持向量越少,gamma
值越小,支持向量越多。支持向量的个数影响训练与预测的速度。通过GridSearchCV
我们选择了最优gamma
参数,交叉验证集的评分增加了不少,是不是此时的模型就是最好的呢?我们来换个模型,使用二阶多项式核函数来拟合模型,看看结果如何:
"""
训练模型: 二阶多项式核函数
"""
clf = SVC(C=1.0, kernel="poly", degree=2)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print("train score: {0}; test score: {1}".format(train_score, test_score))
Out:
train score: 0.9824175824175824; test score: 0.956140350877193
从上述结果,可以看出效果比使用RBF
核函数效果要好。作为对比,我们画出一阶多项式核二阶多项式的学习曲线,观察数据的拟合情况:
"""
绘制学习曲线
"""
cv = ShuffleSplit(n_splits=5, test_size=0.2, random_state=0)
title = "Learning Curves with degree={0}"
degrees = [1, 2]
plt.figure(figsize=(12, 4), dpi=144)
for i in range(len(degrees)):
plt.subplot(1, len(degrees), i+1)
plot_learning_curve(plt, SVC(C=1.0, kernel="poly", degree=degrees[i]), title.format(degrees[i]), X, y, ylim=(0.8, 1.01), cv=cv)
plt.show()
从图中可以看书,二阶多项式核函数的拟合效果更好。平均交叉验证集评分高达0.950,最高时达到0.975。运行该段代码需要注意,二阶多项式核函数计算代价很高,可能需要运行很长时间,请耐心等待结果。
在之前的博客【机器学习算法笔记系列】逻辑回归(LR)算法详解和实战中,我们使用逻辑回归算法来进行乳腺癌的预测,使用二项多项式增加特征,同时使用L1
范数作为正则项,其拟合效果不仅比支持向量机好,而且在运算效率和计算代价上,远远好于二阶多项式核函数的支持向量机。当然,这里的支持向量机算法并未经过严谨的参数调优过程,但结果还是比使用L2
范数的逻辑回归算法好,这里,我想表达的是:**模型选择和参数调优,在工程实践中具有非常重要的作用。**我们不仅需要掌握参数调优的科学方法,而且还需要了解各算法的适用场景和问题,以便选择最合适的算法模型去解决实际问题。
全部代码:
# -*- coding: utf-8 -*-
# Time:2019/4/1 16:48
# versions:Python 3.6
__author__ = "fpZRobert"
"""
支持向量机实战-乳腺癌检测
"""
import warnings
warnings.filterwarnings("ignore", category=FutureWarning, module="sklearn", lineno=196)
from sklearn.svm import SVC
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import ShuffleSplit
from sklearn.model_selection import learning_curve
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
"""
加载数据
"""
cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
print("Shape of X: {0}; positive example: {1}; negative: {2}".format(X.shape, y[y==1].shape[0], y[y==0].shape[0])) # 查看数据的形状和类别分布
"""
拆分数据集
"""
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
"""
训练模型: RBF核函数
"""
clf = SVC(C=1.0, kernel="rbf", gamma=0.1)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print("train score: {0}; test score: {1}".format(train_score, test_score))
"""
绘制学习曲线
"""
# 绘制学习曲线
def plot_learning_curve(plt, estimator, title, X, y, ylim=None, cv=None,
n_jobs=1, train_sizes=np.linspace(.1, 1.0, 5)):
plt.title(title)
if ylim is not None:
plt.ylim(*ylim)
plt.xlabel("Training examples")
plt.ylabel("Score")
train_sizes, train_scores, test_scores = learning_curve(
estimator, X, y, cv=cv, n_jobs=n_jobs, train_sizes=train_sizes)
train_scores_mean = np.mean(train_scores, axis=1)
train_scores_std = np.std(train_scores, axis=1)
test_scores_mean = np.mean(test_scores, axis=1)
test_scores_std = np.std(test_scores, axis=1)
plt.grid()
plt.fill_between(train_sizes, train_scores_mean - train_scores_std,
train_scores_mean + train_scores_std, alpha=0.1,
color="r")
plt.fill_between(train_sizes, test_scores_mean - test_scores_std,
test_scores_mean + test_scores_std, alpha=0.1, color="g")
plt.plot(train_sizes, train_scores_mean, 'o--', color="r",
label="Training score")
plt.plot(train_sizes, test_scores_mean, 'o-', color="g",
label="Cross-validation score")
plt.legend(loc="best")
return plt
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=0)
title = "Learning Curves for Gaussian Kernel"
plt.figure(figsize=(10, 4), dpi=144)
plot_learning_curve(plt, SVC(C=1.0, kernel="rbf", gamma=0.01), title, X, y, ylim=(0.5, 1.01), cv=cv)
plt.show()
"""
模型调优
"""
gammas = np.linspace(0, 0.0003, 30)
param_grid = {"gamma": gammas}
clf = GridSearchCV(SVC(), param_grid, cv=5)
clf.fit(X, y)
print("best param: {0}\n best score: {1}".format(clf.best_params_, clf.best_score_))
"""
训练模型: 二阶多项式核函数
"""
clf = SVC(C=1.0, kernel="poly", degree=2)
clf.fit(X_train, y_train)
train_score = clf.score(X_train, y_train)
test_score = clf.score(X_test, y_test)
print("train score: {0}; test score: {1}".format(train_score, test_score))
"""
绘制学习曲线
"""
cv = ShuffleSplit(n_splits=5, test_size=0.2, random_state=0)
title = "Learning Curves with degree={0}"
degrees = [1, 2]
plt.figure(figsize=(12, 4), dpi=144)
for i in range(len(degrees)):
plt.subplot(1, len(degrees), i+1)
plot_learning_curve(plt, SVC(C=1.0, kernel="poly", degree=degrees[i]), title.format(degrees[i]), X, y, ylim=(0.8, 1.01), cv=cv)
plt.show()