1.4. 支持向量机#
支持向量机(SVM)是一组监督学习方法,用于分类、回归和异常检测。
支持向量机的优点是:
在高维空间中表现出色。
在维度数量大于样本数量的情况下仍然有效。
在决策函数中只使用训练点的一个子集(称为支持向量),因此它也具有内存效率。
多功能:可以为决策函数指定不同的核函数。提供了常见的核函数,也可以指定自定义核函数。
支持向量机的缺点包括:
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 参数来减少这种影响,该参数允许截距项具有与P_other 特征不同的正则化行为。因此,分类结果和分数可能与另外两个分类器有所不同。
与其他分类器一样,SVC、NuSVC 和 LinearSVC 接受两个数组作为输入:一个形状为 (n_samples, n_features) 的数组 X,包含训练样本;以及一个形状为 (n_samples) 的数组 y,包含类别标签(字符串或整数)。
>>> 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_ 中找到
>>> # get support vectors
>>> clf.support_vectors_
array([[0., 0.],
[1., 1.]])
>>> # get indices of support vectors
>>> clf.support_
array([0, 1]...)
>>> # get number of support vectors for each class
>>> clf.n_support_
array([1, 1]...)
示例
1.4.1.1. 多类别分类#
SVC 和 NuSVC 实现了“一对一”(“ovo”)的多类别分类方法,该方法构建了 n_classes * (n_classes - 1) / 2 个分类器,每个分类器都使用来自两个类别的数据进行训练。在内部,求解器始终使用这种“ovo”策略来训练模型。然而,默认情况下,decision_function_shape 参数设置为 "ovr" (“一对多”),通过将“ovo”决策函数单调转换为形状为 (n_samples, n_classes) 的“ovr”决策函数,以保持与其他分类器的一致接口。
>>> 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 classes: 4*3/2 = 6
6
>>> clf.decision_function_shape = "ovr"
>>> dec = clf.decision_function([[1]])
>>> dec.shape[1] # 4 classes
4
另一方面,LinearSVC 实现了“一对多”(“ovr”)多类别策略,从而训练了 n_classes 个模型。
>>> lin_clf = svm.LinearSVC()
>>> lin_clf.fit(X, Y)
LinearSVC()
>>> dec = lin_clf.decision_function([[1]])
>>> dec.shape[1]
4
有关决策函数的完整描述,请参阅数学公式。
多类别策略详情#
请注意,LinearSVC 还实现了另一种多类别策略,即 Crammer 和 Singer [16] 提出的多类别 SVM,通过使用选项 multi_class='crammer_singer'。在实践中,通常首选一对多分类,因为结果大多相似,但运行时显著减少。
对于“一对多”的 LinearSVC,属性 coef_ 和 intercept_ 的形状分别为 (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 vs 1”、“0 vs 2”、...“0 vs n”、“1 vs 2”、“1 vs 3”、“1 vs n”、...“n-1 vs 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\),有两个对偶系数。我们称支持向量 \(v^{j}_i\) 在类别 \(i\) 和 \(k\) 之间的分类器中的系数为 \(\alpha^{j}_{i,k}\)。那么 dual_coef_ 看起来像这样
\(\alpha^{0}_{0,1}\) |
\(\alpha^{1}_{0,1}\) |
\(\alpha^{2}_{0,1}\) |
\(\alpha^{0}_{1,0}\) |
\(\alpha^{1}_{1,0}\) |
\(\alpha^{0}_{2,0}\) |
\(\alpha^{1}_{2,0}\) |
\(\alpha^{0}_{0,2}\) |
\(\alpha^{1}_{0,2}\) |
\(\alpha^{2}_{0,2}\) |
\(\alpha^{0}_{1,2}\) |
\(\alpha^{1}_{1,2}\) |
\(\alpha^{0}_{2,1}\) |
\(\alpha^{1}_{2,1}\) |
类别 0 的支持向量系数 |
类别 1 的支持向量系数 |
类别 2 的支持向量系数 |
||||
示例
1.4.1.2. 分数和概率#
SVC 和 NuSVC 的 decision_function 方法为每个样本提供每个类别的分数(在二元情况下为每个样本提供一个分数)。当构造函数选项 probability 设置为 True 时,会启用类别成员概率估计(通过方法 predict_proba 和 predict_log_proba)。在二元情况下,概率使用 Platt 缩放 [9] 进行校准:对 SVM 分数进行逻辑回归,通过在训练数据上进行额外的交叉验证进行拟合。在多类别情况下,这会根据 [10] 进行扩展。
注意
所有估计器都可以通过 CalibratedClassifierCV 使用相同的概率校准过程(参见概率校准)。对于 SVC 和 NuSVC,此过程内置于内部使用的 libsvm 中,因此不依赖于 scikit-learn 的 CalibratedClassifierCV。
对于大型数据集,Platt 缩放涉及的交叉验证是一个昂贵的操作。此外,概率估计可能与分数不一致
分数的“argmax”可能不是概率的 argmax
在二元分类中,即使
predict_proba的输出小于 0.5,样本也可能被predict标记为属于正类;同样,即使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. 回归#
支持向量分类的方法可以扩展到解决回归问题。这种方法称为支持向量回归。
支持向量分类产生的模型(如上所述)仅取决于训练数据的一个子集,因为构建模型的成本函数不关心位于边界之外的训练点。类似地,支持向量回归产生的模型仅取决于训练数据的一个子集,因为成本函数忽略了预测接近其目标的样本。
有三种不同的支持向量回归实现:SVR、NuSVR 和 LinearSVR。LinearSVR 提供比 SVR 更快的实现,但只考虑线性核函数,而 NuSVR 实现了与 SVR 和 LinearSVR 略微不同的公式。由于其在 liblinear 中的实现,LinearSVR 如果考虑截距,也会对截距进行正则化。然而,可以通过仔细调整其 intercept_scaling 参数来减少这种影响,该参数允许截距项具有与P_other 特征不同的正则化行为。因此,分类结果和分数可能与另外两个分类器有所不同。有关更多详细信息,请参阅实现详情。
与分类类一样,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. 复杂度#
支持向量机是强大的工具,但它们的计算和存储需求随着训练向量数量的增加而迅速增加。SVM 的核心是一个二次规划问题(QP),用于将支持向量与训练数据的其余部分分离。基于 libsvm 的实现所使用的 QP 求解器在 \(O(n_{features} \times n_{samples}^2)\) 和 \(O(n_{features} \times n_{samples}^3)\) 之间扩展,具体取决于 libsvm 缓存的实际使用效率(取决于数据集)。如果数据非常稀疏,则应将 \(n_{features}\) 替换为样本向量中非零特征的平均数量。
对于线性情况,LinearSVC 中使用的基于 liblinear 的算法比其基于 libsvm 的对应项 SVC 效率高得多,并且可以几乎线性扩展到数百万个样本和/或特征。
1.4.5. 实用技巧#
避免数据复制:对于
SVC、SVR、NuSVC和NuSVR,如果传递给某些方法的数据不是 C 顺序连续且双精度的,它将在调用底层 C 实现之前被复制。您可以通过检查 numpy 数组的flags属性来检查它是否为 C-contiguous。对于
LinearSVC(和LogisticRegression),任何作为 numpy 数组传入的输入都将被复制并转换为 liblinear 内部稀疏数据表示(双精度浮点数和非零分量的 int32 索引)。如果您想拟合一个大型线性分类器而不复制密集 numpy C-contiguous 双精度数组作为输入,我们建议改用SGDClassifier类。目标函数可以配置为与LinearSVC模型几乎相同。核缓存大小:对于
SVC、SVR、NuSVC和NuSVR,核缓存大小对较大问题的运行时间有很大影响。如果您有足够的 RAM 可用,建议将cache_size设置为高于默认值 200(MB) 的值,例如 500(MB) 或 1000(MB)。设置 C:
C默认值为1,这是一个合理的默认选择。如果您有很多嘈杂的观测值,您应该减小它:减小 C 对应于更多的正则化。LinearSVC和LinearSVR对较大的C不太敏感,预测结果在达到某个阈值后停止改进。同时,如 [11] 所示,较大的C值需要更长的训练时间,有时甚至长达 10 倍。支持向量机算法不是尺度不变的,因此强烈建议缩放您的数据。例如,将输入向量 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()和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')
>>> # linear kernel computation
>>> gram_train = np.dot(X_train, X_train.T)
>>> clf.fit(gram_train, y_train)
SVC(kernel='precomputed')
>>> # predict on training examples
>>> gram_test = np.dot(X_test, X_train.T)
>>> clf.predict(gram_test)
array([0, 1, 0])
示例
1.4.7. 数学公式#
支持向量机在高级或无限维空间中构建一个超平面或一组超平面,可用于分类、回归或其他任务。直观地说,通过与任何类别的最近训练数据点具有最大距离(即功能裕度)的超平面可以实现良好的分离,因为一般来说,裕度越大,分类器的泛化误差越低。下图显示了线性可分问题的决策函数,其中三个样本位于裕度边界上,称为“支持向量”
通常,当问题不是线性可分时,支持向量是位于裕度边界内的样本。
我们推荐 [13] 和 [14] 作为 SVM 理论和实践的良好参考资料。
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\) 是全 1 向量,\(Q\) 是一个 \(n\) x \(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\))进行访问。
注意
虽然源自 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\) 的样本。这些样本通过 \(\zeta_i\) 或 \(\zeta_i^*\) 来惩罚目标函数,具体取决于它们的预测是位于 \(\varepsilon\) 管道上方还是下方。
对偶问题是
其中 \(e\) 是全 1 向量,\(Q\) 是一个 \(n\) x \(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 进行包装。有关实现和所用算法的详细信息,请参阅它们各自的论文。
References