#! https://zhuanlan.zhihu.com/p/611475806
完整的实验代码在我的github上QYHcrossover/ML-numpy: 机器学习算法numpy实现 (github.com) 欢迎star⭐
支持向量机(SVM)是一种常用的二分类模型,其通过在数据集中找到一个超平面(或分割线)来分割两个不同的分类,同时使得该超平面距离两个分类的最近数据点最大化,从而实现了对未知数据进行分类的目的。
支持向量机(Support Vector Machine,SVM)是一种常见的二分类模型。SVM的目标是在数据集中找到一个超平面(或分割线)来分割两个不同的分类,同时使得该超平面距离两个分类的最近数据点最大化,从而实现了对未知数据进行分类的目的。
假设我们有一个二分类问题,数据集为 { ( x i , y i ) } i = 1 m \{(x_i,y_i)\}_{i=1}^m {(xi,yi)}i=1m,其中 x i x_i xi为数据点, y i ∈ { − 1 , 1 } y_i\in\{-1,1\} yi∈{−1,1}为该数据点所属的类别。假设我们的数据集是线性可分的,也就是说,可以找到一个超平面 ω T x + b = 0 \omega^Tx+b=0 ωTx+b=0来将两个类别分开。此时,我们的模型可以表示为:
f ( x ) = sign ( ω T x + b ) f(x) = \text{sign}(\omega^Tx+b) f(x)=sign(ωTx+b)
其中, sign \text{sign} sign为符号函数,即:
sign ( x ) = { 1 , x > 0 − 1 , x < 0 0 , x = 0 \text{sign}(x) = \begin{cases} 1, & x>0 \\ -1, & x<0 \\ 0, & x=0 \end{cases} sign(x)=⎩ ⎨ ⎧1,−1,0,x>0x<0x=0
但是,很多时候,我们的数据集并不是线性可分的,也就是说,不存在一个超平面可以将两个类别完全分开。此时,我们需要使用一些技巧,将数据集映射到一个更高维的空间中,使得数据集在该空间中是线性可分的。这个技巧称为核方法(kernel trick)。
核方法的基本思想是将原始空间中的数据点 x x x映射到一个更高维的特征空间中,然后在该特征空间中寻找一个超平面来进行分类。具体地,我们定义一个核函数 K ( x i , x j ) K(x_i,x_j) K(xi,xj),它表示将 x i x_i xi和 x j x_j xj映射到特征空间中后的内积。于是,我们的模型可以表示为:
f ( x ) = sign ( ∑ i = 1 m α i y i K ( x i , x ) + b ) f(x) = \text{sign}\left(\sum_{i=1}^m\alpha_iy_iK(x_i,x)+b\right) f(x)=sign(i=1∑mαiyiK(xi,x)+b)
其中, α i \alpha_i αi为拉格朗日乘子, b b b为截距。
我们的目标是找到一组 α \alpha α和 b b b,使得分类超平面距离两个分类的最近数据点最大化。也就是说,我们要最大化分类超平面到最近数据点的距离。这个距离可以用间隔(margin)来度量,即分类超平面到两个类别最近数据点的距离之和。我们的优化目标可以表示为:
max α , b W ( α ) = ∑ i = 1 m α i − 1 2 ∑ i , j = 1 m α i α j y i y j K ( x i , x j ) s.t. ∑ i = 1 m α i y i = 0 0 ≤ α i ≤ C , i = 1 , … , m \begin{aligned} \max_{\alpha,b} \quad & W(\alpha) = \sum_{i=1}^m\alpha_i - \frac{1}{2}\sum_{i,j=1}^m\alpha_i\alpha_jy_iy_jK(x_i,x_j) \\ \text{s.t.} \quad & \sum_{i=1}^m\alpha_iy_i=0 \\ & 0\leq\alpha_i\leq C,\quad i=1,\ldots,m \end{aligned} α,bmaxs.t.W(α)=i=1∑mαi−21i,j=1∑mαiαjyiyjK(xi,xj)i=1∑mαiyi=00≤αi≤C,i=1,…,m
其中, C C C为正则化参数,用于平衡模型的复杂度和错误率。 C C C越大,模型越复杂,错误率越低; C C C越小,模型越简单,错误率越高。
我们使用拉格朗日对偶性(Lagrange duality)来求解上述优化问题。经过一些推导,我们可以得到以下对偶问题:
max α ∑ i = 1 m α i − 1 2 ∑ i , j = 1 m α i α j y i y j K ( x i , x j ) s.t. ∑ i = 1 m α i y i = 0 0 ≤ α i ≤ C , i = 1 , … , m \begin{aligned} \max_{\alpha} \quad & \sum_{i=1}^m\alpha_i - \frac{1}{2}\sum_{i,j=1}^m\alpha_i\alpha_jy_iy_jK(x_i,x_j) \\ \text{s.t.} \quad & \sum_{i=1}^m\alpha_iy_i=0 \\ & 0\leq\alpha_i\leq C,\quad i=1,\ldots,m \end{aligned} αmaxs.t.i=1∑mαi−21i,j=1∑mαiαjyiyjK(xi,xj)i=1∑mαiyi=00≤αi≤C,i=1,…,m
其中, α \alpha α为拉格朗日乘子。我们可以使用一些优化算法来求解该对偶问题,进而得到分类超平面。
在实际使用中,我们可以选择不同的核函数来进行数据的映射。常用的核函数有线性核、多项式核和高斯核等。在上述公式中,我们使用了高斯核:
K ( x i , x j ) = exp ( − ∥ x i − x j ∥ 2 2 σ 2 ) K(x_i,x_j) = \exp\left(-\frac{\|x_i-x_j\|^2}{2\sigma^2}\right) K(xi,xj)=exp(−2σ2∥xi−xj∥2)
其中, σ \sigma σ为高斯核的参数,用于控制高斯函数的宽度,即支持向量的影响在使用高斯核时,我们需要选择合适的 σ \sigma σ值。如果 σ \sigma σ太小,那么高斯函数的宽度就会变窄,模型会过度拟合;如果 σ \sigma σ太大,那么高斯函数的宽度就会变宽,模型会出现欠拟合。
支持向量机(SVM)是一种常用的二分类模型,其通过在数据集中找到一个超平面(或分割线)来分割两个不同的分类,同时使得该超平面距离两个分类的最近数据点最大化,从而实现了对未知数据进行分类的目的。
在下面代码实现中,我们使用了cvxopt库来求解二次规划问题,并使用了高斯分布数据集来进行训练和预测。在预测函数中,我们使用已经求解得到的支持向量,通过核函数计算预测结果。在评分函数中,我们计算了预测结果的准确率。
import numpy as np
import numpy as np
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
from cvxopt import matrix, solvers
from tqdm import tqdm
from sklearn.datasets import make_gaussian_quantiles
def plot_clf(X,y,cls):
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
points = np.c_[xx.ravel(), yy.ravel()]
Z = cls.predict(points).reshape(xx.shape)
cs = plt.contourf(xx, yy, Z)
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y)
plt.show()
class SVM:
def __init__(self,sigma=1,C=1,kind="linear"):
assert kind in ["linear","gaussian"]
self.sigma = sigma
self.C = C
gaussian = lambda x,z: np.exp(-0.5*np.sum((x-z)**2)/(self.sigma**2))
linear = lambda x,z: np.sum(x*z)
self.kernel = linear if kind == "linear" else gaussian
def fit(self,X,y):
mat = np.zeros((X.shape[0],X.shape[0]))
for i in range(X.shape[0]):
for j in range(i,X.shape[0]):
result = self.kernel(X[i],X[j])
mat[i,j] = result
mat[j,i] = result
P = mat * (y.reshape(-1,1) @ y.reshape(1,-1))
q = -1*np.ones(X.shape[0]).reshape(-1,1)
G = np.zeros((2*X.shape[0],X.shape[0]))
G[0:X.shape[0]] = - np.identity(X.shape[0])
G[X.shape[0]:] = np.identity(X.shape[0])
h = np.zeros(2*X.shape[0])
h[X.shape[0]:] = self.C
h = h.reshape(-1,1)
A = y.reshape(1,-1)
b = np.zeros(1).reshape(-1,1)
[P,q,G,h,A,b] = [matrix(i,i.shape,"d")for i in [P,q,G,h,A,b]]
result = solvers.qp(P,q,G,h,A,b)
self.A = np.array(result["x"])
support_vector_index = np.where(self.A > 1e-4)[0]
self.support_vectors = X[support_vector_index]
self.support_vector_as = self.A[support_vector_index,0]
self.support_vector_ys = y[support_vector_index]
for i,a in enumerate(self.A):
if a>0+1e-4 and a<self.C-1e-4:
self.b = y[i] - np.sum(self.A.ravel()*y*mat[i])
break
def predict(self,X):
preds = []
for x in tqdm(X):
Ks = [self.kernel(x,support_vector) for support_vector in self.support_vectors]
pred = np.sum(self.support_vector_as * self.support_vector_ys * Ks) + self.b
pred = 1 if pred >=0 else -1
preds.append(pred)
return np.array(preds)
def score(self,X,y):
return np.sum(self.predict(X)==y) / len(y)
if __name__ == "__main__":
X, y = make_gaussian_quantiles(n_samples=200, n_features=2, n_classes=2, mean=[1,2],cov=2,random_state=222)
y[y==0] = -1
svc = SVM(kind="linear")
svc.fit(X,y)
plot_clf(X,y,svc)
接下来,我们看一下代码的实现。代码实现中,我们定义了一个SVM的类,其中包括了初始化函数、拟合函数、预测函数和评分函数。在初始化函数中,我们定义了模型中使用的核函数,包括线性核和高斯核。SVM类的初始化函数包括三个参数:sigma、C和kind。它们的含义如下:
class SVM:
def __init__(self,sigma=1,C=1,kind="linear"):
assert kind in ["linear","gaussian"]
self.sigma = sigma
self.C = C
gaussian = lambda x,z: np.exp(-0.5*np.sum((x-z)**2)/(self.sigma**2))
linear = lambda x,z: np.sum(x*z)
self.kernel = linear if kind == "linear" else gaussian
在拟合函数中,我们计算了核矩阵,并使用cvxopt库中的二次规划函数来求解支持向量。
在SVM类中,拟合函数fit实现了SVM算法的核心部分,其作用是通过求解二次规划问题来得到分类超平面。下面是拟合函数fit的具体实现流程:
def fit(self,X,y):
mat = np.zeros((X.shape[0],X.shape[0]))
for i in range(X.shape[0]):
for j in range(i,X.shape[0]):
result = self.kernel(X[i],X[j])
mat[i,j] = result
mat[j,i] = result
P = mat * (y.reshape(-1,1) @ y.reshape(1,-1))
q = -1*np.ones(X.shape[0]).reshape(-1,1)
G = np.zeros((2*X.shape[0],X.shape[0]))
G[0:X.shape[0]] = - np.identity(X.shape[0])
G[X.shape[0]:] = np.identity(X.shape[0])
h = np.zeros(2*X.shape[0])
h[X.shape[0]:] = self.C
h = h.reshape(-1,1)
A = y.reshape(1,-1)
b = np.zeros(1).reshape(-1,1)
[P,q,G,h,A,b] = [matrix(i,i.shape,"d")for i in [P,q,G,h,A,b]]
result = solvers.qp(P,q,G,h,A,b)
self.A = np.array(result["x"])
support_vector_index = np.where(self.A > 1e-4)[0]
self.support_vectors = X[support_vector_index]
self.support_vector_as = self.A[support_vector_index,0]
self.support_vector_ys = y[support_vector_index]
for i,a in enumerate(self.A):
if a>0+1e-4 and a<self.C-1e-4:
self.b = y[i] - np.sum(self.A.ravel()*y*mat[i])
break
在预测函数predict中,我们首先定义了一个空列表preds用于存储预测结果,然后遍历输入的数据点x。对于每一个数据点x,我们计算其与所有支持向量的核函数值,并将其相乘后求和,得到该数据点的预测结果。最后,将预测结果加入到preds列表中,并返回该列表作为整个预测函数的输出。在评分函数score中,我们计算了预测结果的准确率。
def predict(self,X):
preds = []
for x in tqdm(X):
Ks = [self.kernel(x,support_vector) for support_vector in self.support_vectors]
pred = np.sum(self.support_vector_as * self.support_vector_ys * Ks) + self.b
pred = 1 if pred >=0 else -1
preds.append(pred)
return np.array(preds)
def score(self,X,y):
return np.sum(self.predict(X)==y) / len(y)
最后,我们使用make_gaussian_quantiles函数生成了一个二维高斯分布数据集,并使用SVM模型进行了训练和预测。
def plot_clf(X,y,cls):
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.02),
np.arange(y_min, y_max, 0.02))
points = np.c_[xx.ravel(), yy.ravel()]
Z = cls.predict(points).reshape(xx.shape)
cs = plt.contourf(xx, yy, Z)
plt.scatter(X[:, 0], X[:, 1], marker='o', c=y)
plt.show()
if __name__ == "__main__":
X, y = make_gaussian_quantiles(n_samples=200, n_features=2, n_classes=2, mean=[1,2],cov=2,random_state=222)
y[y==0] = -1
svc = SVM(kind="linear")
svc.fit(X,y)
plot_clf(X,y,svc)
本文介绍了支持向量机(SVM)的原理和代码实现。SVM是一种二分类模型,其核心思想是找到一个最优的超平面来将不同类别的数据点分开。在代码实现部分,我们定义了一个SVM类,并实现了拟合函数、预测函数和评分函数。通过使用make_gaussian_quantiles函数生成的二维高斯分布数据集进行训练和预测,我们展示了SVM模型的性能。
完整的实验代码在我的github上QYHcrossover/ML-numpy: 机器学习算法numpy实现 (github.com) 欢迎star⭐