1.4. 支持向量机#
支持向量机(SVMs) 是一组用于监督学习的算法,用于 分类 、 回归 和 异常检测 。
支持向量机的优点包括:
在高维空间中有效。
在维数大于样本数的情况下仍然有效。
使用决策函数中的一小部分训练点(称为支持向量),因此内存效率高。
多功能性:可以为决策函数指定不同的 核函数 。提供了常见的核函数,但也可以指定自定义核函数。
支持向量机的缺点包括:
如果特征数量远大于样本数量,选择 核函数 和正则化项以避免过拟合至关重要。
SVMs 不直接提供概率估计,这些估计是通过昂贵的五折交叉验证计算的(参见 Scores and probabilities ,下文)。
scikit-learn 中的支持向量机支持密集( numpy.ndarray
和可由 numpy.asarray
转换的类型)和稀疏(任何 scipy.sparse
)样本向量作为输入。然而,要使用 SVM 对稀疏数据进行预测,必须对这种数据进行拟合。为了获得最佳性能,请使用 C 顺序的 numpy.ndarray
(密集)或 scipy.sparse.csr_matrix
(稀疏),类型为 dtype=float64
。
1.4.1. 分类#
SVC
、NuSVC
和 LinearSVC
是能够在数据集上执行二分类和多分类的类。
SVC
和 NuSVC
是相似的方法,但接受稍微不同的参数集并具有不同的数学公式(参见章节 数学公式 )。另一方面,LinearSVC
是另一种(更快的)线性核情况下的支持向量分类实现。它也缺乏 SVC
和 NuSVC
的一些属性,如 support_
。LinearSVC
使用 squared_hinge
损失,并且由于其在 liblinear
中的实现,它还会对截距进行正则化,如果考虑的话。然而,通过仔细调整其 intercept_scaling
参数,可以减少这种影响,该参数允许截距项与其他特征相比具有不同的正则化行为。因此,分类结果和得分可能与其他两个分类器不同。
与其他分类器一样, SVC
、NuSVC
和 LinearSVC
接受两个数组作为输入:一个形状为 (n_samples, n_features)
的数组 X
保存训练样本,以及一个类标签(字符串或整数)的数组 y
,形状为 (n_samples)
>>> from sklearn import svm
>>> X = [[0, 0], [1, 1]]
>>> y = [0, 1]
>>> clf = svm.SVC()
>>> clf.fit(X, y)
SVC()
拟合后,该模型可用于预测新值:
>>> clf.predict([[2., 2.]])
array([1])
SVM 的决策函数(在 数学公式 中详细说明)依赖于训练数据的一个子集,称为支持向量。这些支持向量的一些属性可以在 support_vectors_
、 support_
和 n_support_
属性中找到:
>>> # 获取支持向量
>>> clf.support_vectors_
array([[0., 0.],
[1., 1.]])
>>> # 获取支持向量的索引
>>> clf.support_
array([0, 1]...)
>>> # 获取每个类的支持向量数量
>>> clf.n_support_
array([1, 1]...)
示例
1.4.1.1. 多类分类#
SVC
和 NuSVC
实现了多类分类的“一对一”方法。总共构建了 n_classes * (n_classes - 1) / 2
个分类器,每个分类器训练来自两个类的数据。为了与其他分类器提供一致的接口, decision_function_shape
选项允许将“一对一”分类器的结果单调地转换为形状为 (n_samples, n_classes)
的“一对多”决策函数。
>>> X = [[0], [1], [2], [3]]
>>> Y = [0, 1, 2, 3]
>>> clf = svm.SVC(decision_function_shape='ovo')
>>> clf.fit(X, Y)
SVC(decision_function_shape='ovo')
>>> dec = clf.decision_function([[1]])
>>> dec.shape[1] # 6 个类:4*3/2 = 6
6
>>> clf.decision_function_shape = "ovr"
>>> dec = clf.decision_function([[1]])
>>> dec.shape[1] # 4 个类
4
另一方面,LinearSVC
实现了“一对多”的多类策略,从而训练了 n_classes
个模型。
>>> lin_clf = svm.LinearSVC()
>>> lin_clf.fit(X, Y)
LinearSVC()
>>> dec = lin_clf.decision_function([[1]])
>>> dec.shape[1]
4
有关决策函数的完整描述,请参见 数学公式 。
#多类策略的详细信息
具有形状 (n_classes, n_features)
和 (n_classes,)
分别。
系数矩阵的每一行对应于 n_classes
个 “一对多” 分类器中的一个,
截距也是如此,按照 “一” 类的顺序排列。
在 “一对一” SVC
和 NuSVC
的情况下,属性的布局稍微复杂一些。
对于线性核,属性 coef_
和 intercept_
的形状分别为
(n_classes * (n_classes - 1) / 2, n_features)
和 ``(n_classes *
(n_classes - 1) / 2)`` 。这与上述 LinearSVC
的布局类似,
每行现在对应于一个二分类器。类的顺序从 0 到 n 是 “0 对 1”,”0 对 2”,… “0 对 n”,
“1 对 2”,”1 对 3”,”1 对 n”,… “n-1 对 n”。
dual_coef_
的形状是(n_classes-1, n_SV)
,布局有点难以理解。
列对应于参与任何 n_classes * (n_classes - 1) / 2
个 “一对一” 分类器的支持向量。
每个支持向量 v
在比较 v
所属类与其他类的 n_classes - 1
个分类器中都有一个对偶系数。
注意,这些对偶系数中的一些(但不是全部)可能为零。
每列中的 n_classes - 1
个条目是这些对偶系数,按对立类的顺序排列。
这可能通过一个例子更清楚:考虑一个三类问题,类 0 有三个支持向量
\(v^{0}_0, v^{1}_0, v^{2}_0\) ,类 1 和 2 各有两个支持向量
\(v^{0}_1, v^{1}_1\) 和 \(v^{0}_2, v^{1}_2\) 。对于每个支持向量 \(v^{j}_i\) ,
有两个对偶系数。我们将在类 \(i\) 和 \(k\) 之间的分类器中支持向量 \(v^{j}_i\) 的系数称为 \(\alpha^{j}_{i,k}\) 。
那么 dual_coef_
看起来像这样:
示例
1.4.1.2. 分数和概率#
SVC
和 NuSVC
的 decision_function
方法为每个样本提供每个类的分数(或在二元情况下为每个样本提供单个分数)。当构造函数选项 probability
设置为 True
时,启用类成员概率估计(通过 predict_proba
和 predict_log_proba
方法)。在二元情况下,概率是
使用Platt缩放 [9] 进行校准:在SVM的分数上进行逻辑回归,通过在训练数据上的额外交叉验证进行拟合。
在多类别情况下,此方法按照 [10] 进行扩展。
Note
相同的概率校准过程可通过 CalibratedClassifierCV
适用于所有估计器(参见 概率校准 )。对于 SVC
和 NuSVC
,此过程内置于 `libsvm`_ 中,该库在底层使用,因此不依赖于 scikit-learn 的 CalibratedClassifierCV
。
Platt缩放中涉及的交叉验证对于大型数据集来说是一项昂贵的操作。 此外,概率估计可能与分数不一致:
分数的“最大值”可能不是概率的最大值
在二分类中,样本可能被
predict
标记为属于正类,即使predict_proba
的输出小于0.5;同样地,它可能被标记为负类,即使predict_proba
的输出大于0.5。
Platt的方法也存在理论上的问题。
如果需要置信度分数,但这些不必须是概率,则建议设置 probability=False
并使用 decision_function
而不是 predict_proba
。
请注意,当 decision_function_shape='ovr'
且 n_classes > 2
时,与 decision_function
不同, predict
方法默认不会尝试打破平局。您可以设置 break_ties=True
,使 predict
的输出与 np.argmax(clf.decision_function(...), axis=1)
相同,否则将始终返回平局类别中的第一个类别;但请注意,这会带来计算成本。参见 SVM 平局打破示例 以了解关于打破平局的示例。
1.4.1.3. 不平衡问题#
在希望对某些样本给予更多重要性的问题中,
对于类别不平衡的分类问题或某些个别样本,可以使用参数 class_weight
和 sample_weight
。
SVC
(但不包括 NuSVC
)在 fit
方法中实现了参数 class_weight
。它是一个形式为 {class_label : value}
的字典,其中 value 是一个大于 0 的浮点数,用于将类别 class_label
的参数 C
设置为 C * value
。下图展示了在不平衡问题中,有和没有权重校正的决策边界。
SVC
、NuSVC
、SVR
、NuSVR
、LinearSVC
、LinearSVR
和 OneClassSVM
还通过 fit
方法中的 sample_weight
参数实现了对个别样本的权重。类似于 class_weight
,这会将第 i 个样本的参数 C
设置为 C * sample_weight[i]
,从而鼓励分类器正确处理这些样本。下图展示了样本权重对决策边界的影响。圆圈的大小与样本权重成正比:
示例
1.4.2. 回归#
支持向量分类的方法可以扩展到解决回归问题。这种方法称为支持向量回归。
支持向量分类(如上所述)产生的模型仅依赖于训练数据的一个子集,因为构建模型的成本函数不关心训练点 那些超出边界的点。类似地,支持向量回归(Support Vector Regression)生成的模型仅依赖于训练数据的一个子集,因为其成本函数忽略了预测值接近目标值的样本。
支持向量回归有三种不同的实现方式:SVR
、NuSVR
和 LinearSVR
。LinearSVR
提供了比 SVR
更快的实现,但仅考虑线性核函数,而 NuSVR
实现了与 SVR
和 LinearSVR
略有不同的公式。由于在 liblinear
中的实现,LinearSVR
还会对截距进行正则化(如果考虑的话)。然而,通过仔细调整其 intercept_scaling
参数,可以减少这种影响,该参数允许截距项与其他特征具有不同的正则化行为。因此,分类结果和评分可能与其他两个分类器不同。更多细节请参见 实现细节 。
与分类类一样,fit 方法将接受向量 X 和 y 作为参数,只是在这种情况下,y 预期具有浮点数值而不是整数值:
>>> from sklearn import svm
>>> X = [[0, 0], [2, 2]]
>>> y = [0.5, 2.5]
>>> regr = svm.SVR()
>>> regr.fit(X, y)
SVR()
>>> regr.predict([[1, 1]])
array([1.5])
示例
1.4.3. 密度估计,新颖性检测#
类 OneClassSVM
实现了一个用于异常检测的一类支持向量机(One-Class SVM)。
有关 OneClassSVM 的描述和使用,请参见 新奇和异常检测 。
1.4.4. 复杂度#
支持向量机是强大的工具,但其计算和存储需求随着训练向量数量的增加而迅速增长。支持向量机的核心是一个二次规划问题(QP), 分离支持向量与训练数据的其余部分。 `libsvm`_ 基于的实现所使用的 QP 求解器在 \(O(n_{features} \times n_{samples}^2)\) 和 \(O(n_{features} \times n_{samples}^3)\) 之间缩放,具体取决于 `libsvm`_ 缓存在实践中使用的效率(数据集依赖)。如果数据非常稀疏,\(n_{features}\) 应替换为样本向量中非零特征的平均数量。
对于线性情况, `liblinear`_ 实现的 LinearSVC
所使用的算法比基于 `libsvm`_ 的 SVC
对应算法效率高得多,并且几乎可以线性缩放到数百万个样本和/或特征。
1.4.5. 实用使用技巧#
避免数据复制:对于
SVC
、SVR
、NuSVC
和NuSVR
,如果传递给某些方法的数据不是 C 顺序连续且为双精度,则在调用底层 C 实现之前会进行复制。您可以通过检查其flags
属性来检查给定的 numpy 数组是否为 C 连续。对于
LinearSVC
(以及LogisticRegression
),任何作为 numpy 数组传递的输入都将被复制并转换为 `liblinear`_ 内部稀疏数据表示(双精度浮点数和非零组件的 int32 索引)。如果您想在不复制密集的 numpy C 连续双精度数组作为输入的情况下拟合大规模线性分类器,我们建议使用SGDClassifier
类。目标函数可以配置为与LinearSVC
模型几乎相同。核缓存大小:对于
SVC
、SVR
、NuSVC
和NuSVR
,核缓存的大小对较大问题的运行时间有强烈影响。如果您有足够的可用 RAM,建议设置较大的核缓存大小。 建议将cache_size
设置为高于默认值 200(MB) 的值,例如 500(MB) 或 1000(MB)。设置 C:
C
默认为1
,这是一个合理的默认选择。如果你有很多噪声观测值,你应该减小它:减小 C 对应于更多的正则化。LinearSVC
和LinearSVR
在C
变得很大时对C
不太敏感,预测结果在某个阈值后停止改善。同时,较大的C
值将花费更多时间进行训练,有时长达 10 倍,如 [11] 所示。支持向量机算法不是尺度不变的,因此**强烈建议对数据进行缩放**。例如,将输入向量 X 上的每个属性缩放到 [0,1] 或 [-1,+1],或标准化为均值 0 和方差 1。请注意,必须对测试向量应用*相同*的缩放以获得有意义的结果。这可以通过使用
Pipeline
轻松完成:>>> from sklearn.pipeline import make_pipeline >>> from sklearn.preprocessing import StandardScaler >>> from sklearn.svm import SVC >>> clf = make_pipeline(StandardScaler(), SVC())
有关缩放和标准化的更多详细信息,请参见 数据预处理 部分。
关于
shrinking
参数,引用 [12]:我们发现,如果迭代次数很大,那么收缩可以缩短训练时间。然而,如果我们宽松地解决优化问题(例如,通过使用较大的停止容差),不使用收缩的代码可能会快得多NuSVC
/OneClassSVM
/NuSVR
中的参数nu
近似于训练误差和支持向量的比例。在
SVC
中,如果数据不平衡(例如,很多正例和很少负例),设置class_weight='balanced'
并/或尝试不同的惩罚参数C
。底层实现的随机性:
SVC
和NuSVC
的底层实现使用随机数生成器仅在概率估计时对数据进行洗牌(当probability
设置为True
时)。这种随机性可以通过random_state
参数进行控制。如果probability
设置为False
,这些估计器不是随机的,random_state
对结果没有影响。OneClassSVM
的底层实现与SVC
和NuSVC
类似。由于OneClassSVM
不提供概率估计,因此它不是随机的。LinearSVC
的底层实现在使用对偶坐标下降法拟合模型时(即当dual
设置为True
时)使用随机数生成器选择特征。因此,对于相同输入数据得到稍微不同的结果并不罕见。如果发生这种情况,可以尝试使用较小的tol
参数。这种随机性也可以通过random_state
参数进行控制。当dual
设置为False
时,LinearSVC
的底层实现不是随机的,random_state
对结果没有影响。使用
LinearSVC(penalty='l1', dual=False)
提供的 L1 正则化会产生稀疏解,即只有一小部分特征权重不为零并贡献于决策函数。增加C
会产生更复杂的模型(选择更多的特征)。产生“空”模型(所有权重等于零)的C
值可以使用l1_min_c
计算。
1.4.6. 核函数#
核函数 可以是以下任意一种:
线性:\(\langle x, x'\rangle\) 。
多项式:\((\gamma \langle x, x'\rangle + r)^d\) ,其中 \(d\) 由参数
degree
指定,\(r\) 由coef0
指定。rbf:\(\exp(-\gamma \|x-x'\|^2)\) ,其中 \(\gamma\) 是
由参数 gamma
指定的值必须大于 0。
sigmoid \(\tanh(\gamma \langle x,x'\rangle + r)\) , 其中 \(r\) 由
coef0
指定。
不同的核函数由 kernel
参数指定:
>>> linear_svc = svm.SVC(kernel='linear')
>>> linear_svc.kernel
'linear'
>>> rbf_svc = svm.SVC(kernel='rbf')
>>> rbf_svc.kernel
'rbf'
另请参阅 核近似 ,了解使用 RBF 核函数的更快且更具可扩展性的解决方案。
1.4.6.1. RBF 核函数的参数#
在使用 径向基函数 (RBF) 核训练 SVM 时,必须考虑两个参数: C
和 gamma
。参数 C
是所有 SVM 核共有的,它在训练样本的误分类与决策面的简单性之间进行权衡。较低的 C
使决策面平滑,而较高的 C
旨在正确分类所有训练样本。 gamma
定义了单个训练样本的影响力。 gamma
越大,其他样本必须越接近才能受到影响。
C
和gamma
的适当选择对 SVM 的性能至关重要。建议使用GridSearchCV
,并以指数间隔选择C
和gamma
的良好值。
示例
1.4.6.2. 自定义核函数#
你可以通过提供一个 Python 函数或预计算 Gram 矩阵来定义自己的核函数。
具有自定义核函数的分类器与其他分类器的行为相同,除了:
字段
support_vectors_
现在是空的,仅在support_
中存储支持向量的索引在
fit()
方法中,第一个参数的引用(而不是副本)被存储以供将来参考。如果该数组在调用fit()
方法之间发生变化,则会受到影响。
使用 fit()
和 predict()
方法可能会产生意外的结果。
#使用Python函数作为核函数
你可以通过将函数传递给 kernel
参数来使用自己定义的核函数。
你的核函数必须接受两个形状为 (n_samples_1, n_features)
和 (n_samples_2, n_features)
的矩阵作为参数,并返回一个形状为 (n_samples_1, n_samples_2)
的核矩阵。
以下代码定义了一个线性核函数,并创建了一个使用该核函数的分类器实例:
>>> import numpy as np
>>> from sklearn import svm
>>> def my_kernel(X, Y):
... return np.dot(X, Y.T)
...
>>> clf = svm.SVC(kernel=my_kernel)
#使用Gram矩阵
你可以通过使用 kernel='precomputed'
选项来传递预计算的核矩阵。然后,你应该将Gram矩阵而不是X传递给 fit
和 predict
方法。所有训练向量和测试向量之间的核值必须提供:
>>> import numpy as np
>>> from sklearn.datasets import make_classification
>>> from sklearn.model_selection import train_test_split
>>> from sklearn import svm
>>> X, y = make_classification(n_samples=10, random_state=0)
>>> X_train , X_test , y_train, y_test = train_test_split(X, y, random_state=0)
>>> clf = svm.SVC(kernel='precomputed')
>>> # 线性核计算
>>> gram_train = np.dot(X_train, X_train.T)
>>> clf.fit(gram_train, y_train)
SVC(kernel='precomputed')
>>> # 对训练样本进行预测
>>> gram_test = np.dot(X_test, X_train.T)
>>> clf.predict(gram_test)
array([0, 1, 0])
示例
1.4.7. 数学公式#
支持向量机在高位或无限维空间中构建超平面或一组超平面,这些超平面可用于分类、回归或其他任务。 分类、回归或其他任务。直观上,一个好的分离是通过具有最大距离的超平面实现的,这个距离是指到任意类别的最近训练数据点的距离(所谓的功能边界),因为在一般情况下,边界越大,分类器的泛化误差越小。下图显示了一个线性可分问题的决策函数,边界上有三个样本,称为“支持向量”:
一般来说,当问题不是线性可分时,支持向量是位于边界内的样本。
我们推荐 [13] 和 [14] 作为支持向量机的理论和实践的良好参考。
1.4.7.1. SVC#
给定训练向量 \(x_i \in \mathbb{R}^p\) ,i=1,…, n,分为两类,以及向量 \(y \in \{1, -1\}^n\) ,我们的目标是找到 \(w \in \mathbb{R}^p\) 和 \(b \in \mathbb{R}\) ,使得由 \(\text{sign} (w^T\phi(x) + b)\) 给出的预测对大多数样本是正确的。
SVC 解决了以下原始问题:
直观上,我们试图最大化边界(通过最小化 \(||w||^2 = w^Tw\) ),同时当样本被错误分类或在边界内时产生惩罚。理想情况下,所有样本的值 \(y_i (w^T \phi (x_i) + b)\) 都应为 \(\geq 1\) ,这表示完美预测。但问题通常不是总能用一个超平面完美分离,因此我们允许一些样本与其正确的边界边界距离为 \(\zeta_i\) 。惩罚项 C
控制了这个惩罚的强度,因此作为正则化参数的倒数(见下文注释)。
原始问题的对偶问题是
其中 \(e\) 是全一的向量, 而 \(Q\) 是一个 \(n\) 乘 \(n\) 的半正定矩阵, \(Q_{ij} \equiv y_i y_j K(x_i, x_j)\) ,其中 \(K(x_i, x_j) = \phi (x_i)^T \phi (x_j)\) 是核函数。\(\alpha_i\) 被称为对偶系数, 并且它们的上限为 \(C\) 。 这个对偶表示突出了一个事实,即训练向量通过函数 \(\phi\) 隐式地映射到一个更高(可能是无限) 维的空间:参见 核技巧 。
一旦优化问题得到解决,给定样本 \(x\) 的 decision_function 输出变为:
预测的类别对应于其符号。我们只需要对支持向量(即位于边界内的样本)求和,因为其他样本的对偶系数 \(\alpha_i\) 为零。
这些参数可以通过属性 dual_coef_
访问,该属性保存了 \(y_i \alpha_i\) 的乘积, support_vectors_
保存了支持向量,以及 intercept_
保存了独立项 \(b\) 。
Note
虽然从 `libsvm`_ 和 `liblinear`_ 派生的 SVM 模型使用 C
作为正则化参数,但大多数其他估计器使用 alpha
。两个模型之间的正则化量的确切等价性取决于模型优化的确切目标函数。例如,当使用的估计器是 Ridge
回归时,它们之间的关系为 \(C = \frac{1}{alpha}\) 。
#LinearSVC
原始问题可以等价地表述为
我们使用 hinge loss 。这是直接由 LinearSVC
优化的形式,但与对偶形式不同,这种形式不涉及样本之间的内积,因此著名的核技巧无法应用。这就是为什么 LinearSVC
仅支持线性核(\(\phi\) 是恒等函数)。
1.4.7.2. SVR#
给定训练向量 \(x_i \in \mathbb{R}^p\) ,i=1,…, n,以及向量 \(y \in \mathbb{R}^n\) ,\(\varepsilon\) -SVR 解决以下原始问题:
在这里,我们惩罚预测值至少与真实目标相差 \(\varepsilon\) 的样本。这些样本根据其预测值位于 \(\varepsilon\) 管道的上方还是下方,分别通过 \(\zeta_i\) 或 \(\zeta_i^*\) 惩罚目标函数。
对偶问题是
其中 \(e\) 是全一的向量, \(Q\) 是一个 \(n\) 乘以 \(n\) 的半正定矩阵, \(Q_{ij} \equiv K(x_i, x_j) = \phi (x_i)^T \phi (x_j)\) 是核函数。这里训练向量通过函数 \(\phi\) 隐式映射到更高 (可能是无限)维的空间。
预测为:
这些参数可以通过属性 dual_coef_
访问,
该属性保存了差值 \(\alpha_i - \alpha_i^*\) , support_vectors_
保存了支持向量,
以及 intercept_
保存了独立项 \(b\)
1.4.8. 实现细节#
内部,我们使用 `libsvm`_ [12] 和 `liblinear`_ [11] 来处理所有 计算。这些库通过 C 和 Cython 进行封装。 有关实现的描述和所用算法的详细信息,请参阅它们各自的论文。
参考文献