从第一原理学习,理解和实现功能最强大的通用机器学习算法之一。
支持向量机是非常通用的机器学习算法。它们之所以受欢迎的主要原因是他们能够使用所谓的核技巧来执行线性和非线性分类以及回归。如果您不知道那是什么,不用担心。在本文末尾,您将能够:
· 了解什么是SVM及其工作方式
· 在硬边距SVM和软边距SVM之间进行区分
· 使用Python从头开始编写SVM
因此,事不宜迟,让我们开始吧!
什么是SVM,为什么需要它?
有这么多其他算法(线性回归,逻辑回归,神经网络等)。您可能想知道为什么需要在工具包中添加另一个算法! 也许可以借助图表来回答以下问题:
在这里,我们看到了用于分类数据的三个潜在决策边界:H1,H2和H3。首先,H1根本不分离类,因此它不是一个好的超平面。H2确实将类分开,但是注意点之间的边距(或街道)是如此之小,并且此分类器在看不见的情况下不太可能表现良好。
第三个超平面H3代表SVM分类器的决策边界; 这条线不仅将两个类别分开,而且使两个类别的最极端点之间的距离最远。
您可以将SVM视为适合两个类别之间最大的余量。 这称为大边距分类。
大边距分类
就像我说的那样,一个大容限的SVM分类器本质上试图适应两个类别之间尽可能宽的街道(用平行虚线表示)。重要的是要注意,添加更多"不在街道上"(不在虚线上)的实例不会影响决策边界。
决策边界完全由类的最极端实例(即换句话说,位于街道边缘的实例)确定(或支持)。 这些称为支持向量(在图中用黑色圆圈圈出)。
硬边界分类的限制
因此,从本质上讲,硬边界SVM基本上试图拟合决策边界,该边界最大程度地提高了两类支持向量之间的距离。但是,此模型存在一些问题:
· 对异常值非常敏感
这两个概念可以在此可视化中清楚地突出显示:
问题1:对异常值非常敏感
注意红点是一个极端的异常值,因此SVM算法将其用作支持向量。 因为"硬边界"分类器找到了支持向量之间的最大距离,所以它使用红色离群值和蓝色支持向量来设置决策边界。
这导致决策边界非常差,很可能过度拟合,并且无法很好地预测新类别。
问题2:它仅适用于线性可分离的数据
在此示例中,我们可以清楚地观察到没有可能的线性分类器将各个类分开。 此外,还有一个主要的异常值。 因此,问题是,SVM如何分离非线性可分离的数据?
处理异常值和非线性数据
方法一:软边际支持向量机
一种方法是在保持街道尽可能宽(最大化边距)和限制违反边距(这些实例最终出现在街道中间,甚至在街道的错误一侧)之间找到一个良好的平衡。这称为软边距SVM。
本质上,您正在控制两个目标之间的权衡:
· 最大化决策边界和支持向量之间的距离
· 最大化由决策边界正确分类的点数
这种权衡通常是由一个超参数控制的,该参数可以用λ或更通常(在scikit-learn中)C参数表示。 这实质上控制了错误分类的成本。 具体来说,
· 较小的C值会导致街道更宽,但违反保证金的情况会更多(偏差更大,方差更低)
· C的值越大,街道越窄,但违反边距的情况越少(低偏差,高方差)。
尽管这种方法可行,但我们必须使用交叉验证技术找出最佳的C参数。这会花费大量时间。另外,人们可能想创建一个最佳模型,而没有任何违反边距违规的"松弛"变量。那么,我们现在的解决方案是什么?
方法二:内核方法
尽管线性SVM在大多数情况下都能正常工作,但很少有线性可分离的数据集。 解决此问题的一种方法是添加更多特征,例如多项式特征(这些本质上是通过将值提高到N度多项式来转换您的特征(例如X²,X³等)。)
例如,假设我们有以下数据:
显然,该数据集不是线性可分离的。
但是,当我们通过将根加到20的幂来应用多项式变换时:
我们得到一个线性可分离的数据集。
但是,这对于大型数据集是不可行的。 多项式变换发生的计算复杂度和时间将太长且计算量大。
此外,使用高阶多项式会创建大量特征,从而使模型过于缓慢。
这就是SVM发挥作用的地方。 更具体地说,内核技巧之美
内核技巧
本质上,内核是计算非线性可分离数据点之间关系并将其映射到更高维度的不同函数。然后,它适合标准支持向量分类器。它有效地将要素从相对较低的维度映射到相对较高的维度。
但是,内核函数仅计算数据点之间的高维关系,就好像它们是高维的一样。 它们实际上并没有进行转换,这意味着内核函数不会添加任何功能,但是获得的结果与id相同。
这种技巧(无需实际创建或转换即可计算数据点之间的高维关系)被称为内核技巧。
内核技巧避免了将特征从低维转换为高维的数学运算,从而降低了SVM的计算复杂性!
让我们看一下两个常见的内核功能:
1.多项式内核
2.高斯径向基函数
内核功能1:多项式内核
本质上,这使用多项式内核来计算数据点之间的高维关系,并将数据映射到更高的维,而无需添加任何功能。
多项式内核的公式如下(我很抱歉在没有警告的情况下对您进行数学攻击!):
· 此处的d是超参数,是指函数应使用的多项式的次数。
一个例子
举一个具体的例子,假设我们有如下数据:
显然,该数据不是线性可分离的。 但是,如果将SVM与多项式内核一起使用,我们将获得以下高维映射:
同样,从中获得的重要思想是,内核函数仅计算点之间的高维关系,就好像它们是高维一样,而不会创建或变换新特征。
以下代码使用scikit-learn中的SVC类实现了多项式内核:
from sklearn.svm import SVC svc = SVC(kernel="poly", degree=3, coef0=1, C=5))svc.fit(X_train,y_train)
显然,如果模型过度拟合,则可能需要减少多项式的次数。您可能在这里注意到了一些参数。让我简单地解释一下:
· 内核:定义要使用的内核(我们稍后将探讨其他一些选项)
· 程度:这定义多项式内核的程度
· C:分类误差,该分类误差基本上控制着在具有最大可能余量和最大化由决策边界正确分类的点数之间进行权衡。
内核功能2:高斯RBF内核
另一个非常流行的SVM内核是基于高斯径向函数(Gaussian RBF)。 本质上,这是一个相似度函数,用于计算实例与地标之间的距离。 内核函数的公式如下:
为了澄清一些符号(注意:所有不清楚的内容将在不久后解释):
· x:引用实例
· x':指地标
· σ:这是指伽玛
函数本身遵循钟形曲线(因此为什么是高斯曲线),范围从0(非常远离地标)到1(在地标处)。
我敢肯定,这在您的脑海中仍然很模糊,所以让我们以我唯一知道的方式来消除混乱;举个例子!
一个例子
观察下面的一维数据图:
界标本质上是我们将用来获得两者之间相似度的数据集中的一个点。 在这里,我们有2个地标X2和X3。
现在,我们准备计算新功能。例如,让我们看一下实例X,它等于-1。它与第一地标的距离为1,与第二地标的距离为2。因此,其新的映射功能将是:
x2 = exp (–0.3 × 12) ≈ 0.74
和
x3 = exp (–0.3 × 22) ≈ 0.30
下图所示:
现在,我们只需使用普通的SVC对点进行分类!
您可能在想,太酷了!但:
· 您如何选择地标?
· 您仍然没有解释到底伽玛是什么意思!
选择地标
如此真实。 好吧,要解决第一个问题,通常需要在数据集中每个实例的位置创建一个地标。 这创建了许多维度,因此增加了变换后的训练集可以线性分离的机会。
但是,再一次,就像多项式变换一样,这在计算上是昂贵的,并且需要添加许多功能。试想一下,如果您有一个包含m个实例的训练集,并且n个特征被转换为包含m个实例和m个特征的训练集 (假设您放弃了原始功能)。
换句话说,如果您有20个具有20个要素的实例,那么计算这些转换将得到400个要素!
对我们来说幸运的是,内核把戏发挥了魔力。它使计算这些高维关系成为可能,而无需实际转换或创建新特征,而仍然获得与您相同的结果!
伽玛gamma
现在,伽玛gamma是一个特殊的超参数,它是针对rbf内核的。回到上面的rbf函数图,伽玛控制每个钟形函数的宽度。
具体而言,较大的gamma值将缩短钟形曲线的宽度,并减小每个实例的影响范围,并导致更不稳定的决策边界在各个数据点之间摆动。 相反,较小的gamma值会增加每个数据点的影响范围,并导致更平滑的决策边界。
具有rbf内核的SVC的scikit-learn实现如下:
SVC(kernel="rbf", gamma=5, C=0.001)
注意:在本文中,我将只编写软边界和硬边界SVM。 但是将来,我将撰写有关如何在SVM中实现内核技巧的文章,因此请确保将来继续关注
硬边际和软边际SVM的数学
是的,很抱歉,但是您确实需要理解数学才能进行编码。如果您真的很讨厌数学,请随时跳过本节,但是我强烈建议至少尝试了解正在发生的事情,以便更好地了解当前的问题。
在真正开始数学之前,让我逐步向您介绍SVM的工作原理:
· 将超平面拟合到数据,然后尝试对点进行分类
· 使用优化算法,调整模型的参数,以使其在支持向量之间保持最大的余量。
· 重复n次迭代,或直到成本函数最小化。
现在,让我解释一些关键术语,然后再介绍SVM Math。
成本函数
成本函数本质上是一个公式,用于衡量模型的损失或"成本"。如果您曾经参加过任何Kaggle比赛,则可能遇到过其中一些比赛。一些常见的包括:
· 均方误差
· 均方根误差
· 平均绝对误差
· 对数损失
我们将使用的成本函数称为铰链hinge损失。该函数的公式如下:
在图形上,铰链hinge损失如下所示:
在此图中,蓝色代表正确分类的损失,绿色代表错误分类的损失。
请注意,即使我们对数据点进行了正确分类,铰链损耗也会对数据点位于边距内的预测不利。
本质上,我们将使用它来衡量算法的性能,并确保达到目标(最大限度地提高利润)
优化算法
优化通常定义为改进某些东西以使其发挥最大潜力的过程。 这也适用于机器学习。 在ML领域,优化本质上是试图为特定数据集找到最佳参数组合。 这本质上是机器学习的"学习"部分。
尽管可能存在优化算法,但我将讨论两种最常用的算法:梯度下降和正态方程。
梯度下降
梯度下降是一种优化算法,旨在查找函数的最小值。它通过在斜坡的负方向上反复执行步骤来实现此目标。在我们的示例中,梯度下降将通过沿切线的斜率移动到函数来连续更新权重。太棒了,听起来很棒。请用英语?:)
梯度下降的具体例子
为了更好地说明"梯度下降",让我们看一个简单的示例。 想象一个人在山顶,而他/她想到达山底。 他们可能会做的是环顾四周,看看他们应该朝哪个方向迈出一步,以便更快地下车。 然后,他们可能会朝这个方向迈出一步,而现在他们已接近自己的目标。 但是,它们在下降时必须小心,因为它们可能会卡在某个点上,因此我们必须确保相应地选择步长。
同样,梯度下降的目的是使函数最小化。在我们的案例中,这是为了使模型成本最小化。它通过找到函数的切线并沿该方向移动来实现此目的。算法的"步长"的大小由所谓的学习率定义。这实质上控制了我们向下移动的距离。使用此参数时,我们必须注意以下两种情况:
· 学习率太大,算法可能不会收敛(达到最小值)并在最小值附近反弹,但永远不会达到该值
· 学习速率太小,算法将花费太长时间才能达到最小值,也可能会"陷于"次优点。
我们还有一个参数来控制算法对数据集进行迭代的次数。
在视觉上,该算法将执行以下操作:
由于在机器学习中了解该算法至关重要,因此我们来回顾一下它的作用:
· 随机初始化权重。这称为(您猜对了)随机初始化)
· 然后,模型使用这些随机权重进行预测。
· 该模型的预测通过成本函数进行评估。
· 然后,该模型通过找到函数的切线运行梯度下降,然后在该切线的斜率上走一步
· 重复此过程进行N次迭代,或者满足条件。
这个过程在数学上如下所示:
这里要注意的重要事项:
α:这是学习率的符号(请记住:步长)
m:训练样例数
h(θx):我们的预测
θn:我们算法的第n个系数
梯度下降的优缺点
好处:
· 梯度下降很可能会将成本函数降低到全局最小值(非常接近或= 0)
· 最有效的优化算法之一
缺点:
· 在大型数据集上可能会变慢,因为它使用整个数据集来计算函数切线的梯度
· 容易卡在次优点(或局部最小值)处
· 用户必须手动选择学习速率和迭代次数,这可能很耗时
好的,我知道您确实想开始实际的编码,但是最后一部分是SVM数学中最重要的部分,所以请继续学习!
SVM的数学
对于预测:
我们基本上执行以下操作:
· 如果权重的点积乘以特征减去偏差项的正负号大于或等于1,则预测1
· 如果权重的点积乘以特征减去偏差项的正负号小于或等于-1,则预测为0
条件:
这是肯定分类的铰链hinge损失的条件。 这由我们之前看到的铰链hinge损耗图上的蓝线表示。 基本上,这将检查给定实例是否正确分类。
为了正确分类:
这是正确分类的公式。 澄清:
w:算法的权重
α:我们前面提到的梯度下降的学习率
λ:正则化参数(等效于C参数)
对于错误的分类:
这是分类错误的公式。 注意我们如何调整偏差项。
好的,女士们,先生们,最后,主要事件是:写SVM的代码!
从头开始编写SVM
让我们开始吧! 首先,让我们进行一些基本的导入:
import numpy as np import matplotlib.pyplot as pltfrom sklearn import datasetsfrom sklearn.model_selection import train_test_split
是的,没有sklearn型号! 这将以简单的numpy编码!
接下来,让我们建立一个基本的数据集,然后将我们的数据分为训练和测试:
X, y = datasets.make_classification(n_samples=500, n_features=5, random_state=42, class_sep=0.7)X_train,X_test,y_train,y_test = train_test_split(X,y, random_state=42, test_size=0.2)
我将为本教程创建一个综合数据集,并将class_sep设置为0.7,这意味着分类数据应该相对简单。
现在,为了遵守软件工程原理,我将制作一个SVM类并进行构建,以使代码看起来更简洁并易于共享:
class SVM: def __init__(self, learning_rate=0.001, C=0.001, n_iters=100): self.lr = learning_rate self.C = C self.n_iters = n_iters self.w = None self.b = None
因此,在这里,我们实质上是初始化学习率,正则化参数,迭代次数,然后将权重和偏差设置为零。
接下来,我们定义fit方法:
def fit(self, X, y): n_samples, n_features = X.shape # Convert 0 labels to be -1 to set threshold with hinge loss y_ = np.where(y <= 0, -1, 1) self.w = np.random.rand(n_features) self.b = 0
一旦给了我们训练特征和目标向量,就可以将权重随机初始化为特征数量的向量。请注意,我们如何将数据集中的0值转换为等于-1,以便可以使用铰链损耗。
现在,我们继续该方法的核心:
for _ in range(self.n_iters): for idx, x_i in enumerate(X): condition = y_[idx] * (np.dot(x_i, self.w) - self.b) >= 1
因此,我们基本上在做以下事情:
· 循环 n_iters 次(默认情况下n_iters = 100)
· 对于选定的索引和X值,我们设置条件,检查选定的目标值是否乘以选定实例与权重的点积减去负偏差是否大于或等于1。这实质上检查了我们是否根据铰链损耗正确分类。
接下来,我们将正确分类的公式转换为代码:
if condition: self.w -= self.lr * (2 * self.C * self.w)
以及我们错误分类为代码的公式:
else: self.w -= self.lr * (2 * self.C * self.w - np.dot(x_i, y_[idx])) self.b -= self.lr * y_[idx]
最后,我们定义预测函数:
def predict(self, X): preds = np.sign(np.dot(X, self.w) - self.b) return np.where(preds == -1,0,1)
我们确保将等于-1的标签转换回零。
现在,我们只需调用函数并在测试集上获得模型的准确性即可:
clf = SVM()clf.fit(X_train, y_train)preds = clf.predict(X_test)(preds == y_test).mean()OUT:0.82
我添加了visualise_svm()函数以帮助可视化SVM,可以从我在本文结尾添加的Github存储库中访问该SVM。 但是,运行该函数将输出以下内容:
现在,如果您还没有猜到,我们刚刚实现了软边距SVM。我们将C值设置为0.001,我们可以清楚地看到,决策边界允许某些点位于边距和错误的一侧,但导致了更好的超平面。
现在,当我们将C值更改为0.9(很少正则化)时,将得到以下图:
而且我们的精度从0.82降低到0.7
锁定作业
我希望您尝试一些任务:
· 尝试将C调整为非常小的值和非常大的值吗? 决策边界如何变化?
· 调整梯度下降算法的学习率。 您得到更好的结果吗?
我真的很感谢每个激励我写好的文章的人。我感谢忠实的追随者以及所有决定阅读我的作品的人。我向你保证,它不会被忽视。我希望始终为读者制作有趣且引人入胜的作品。
我希望您学到了一些新知识,并且可能会刷新一些旧知识。请确保继续关注更多信息,祝您一切顺利!
PS:这是Github代码的链接
(本文由闻数起舞翻译自Vagif Aliyev的文章《Machine Learning Algorithms from Start to Finish in Python: SVM》,转载请注明出处,原文链接:https://towardsdatascience.com/machine-learning-algorithms-from-start-to-finish-in-python-svm-d9ff9b48fd1)