支持向量机------pytorch与paddle实现支持向量机
本文将深入探讨支持向量机的理论基础,并通过PyTorch和PaddlePaddle两个深度学习框架来展示如何实现支持向量机模型。我们将首先介绍支持向量机、优化的基本概念,这些数学工具是理解和实现支持向量机的基础。通过PyTorch和PaddlePaddle的代码示例,我们将展示如何设计、训练和评估一个支持向量机模型,从而让读者能够直观地理解并掌握这两种框架在机器学习问题中的应用。
本文部分为torch框架以及部分理论分析,paddle框架对应代码可见支持向量机paddle
感兴趣的uu可以看看本专栏的往期内容哦~:
深度强化学习开端------DQN算法求解车杆游戏
强化学习的适应性改进------策略梯度算法求解山地车游戏
线性回归------pytorch与paddle实现线性回归的详细过程
接下来我们进入正题!
python
import torch
print("pytorch version:",torch.__version__)
pytorch version: 2.2.2+cu121
支持向量机(SVM)理论分析
支持向量机(Support Vector Machine, SVM)是一种广泛使用的分类器,特别适合于高维数据分类问题。SVM 的主要思想是寻找一个最优超平面,以最大化两个类别之间的边界(即"间隔")。
基本概念
给定训练数据集 { ( x i , y i ) } i = 1 N \{(x_i, y_i)\}_{i=1}^{N} {(xi,yi)}i=1N,其中 x i ∈ R n x_i \in \mathbb{R}^n xi∈Rn, y i ∈ { − 1 , + 1 } y_i \in \{-1, +1\} yi∈{−1,+1}。SVM 旨在找到一个超平面 w ⋅ x + b = 0 w \cdot x + b = 0 w⋅x+b=0,使得该超平面能最好地将两类数据分开。
对于线性可分的数据集,最优超平面可以通过解决以下优化问题来找到:
min w , b 1 2 ∥ w ∥ 2 s.t. y i ( w ⋅ x i + b ) ≥ 1 , i = 1 , 2 , ... , N \begin{align*} &\min_{w, b} \frac{1}{2} \|w\|^2 \\ &\text{s.t.} \quad y_i(w \cdot x_i + b) \geq 1, \quad i = 1, 2, \ldots, N \end{align*} w,bmin21∥w∥2s.t.yi(w⋅xi+b)≥1,i=1,2,...,N
这里, ∥ w ∥ \|w\| ∥w∥ 是向量 w w w 的欧几里得范数,代表超平面的法向量; b b b 是偏置项。约束条件确保了每个数据点都被正确分类,并且距离超平面至少有一定的"间隔"。
训练优化方法
解决上述优化问题通常使用拉格朗日乘子法和对偶理论。引入拉格朗日乘子 α i ≥ 0 \alpha_i \geq 0 αi≥0,我们可以得到拉格朗日函数:
L ( w , b , α ) = 1 2 ∥ w ∥ 2 − ∑ i = 1 N α i [ y i ( w ⋅ x i + b ) − 1 ] L(w, b, \alpha) = \frac{1}{2} \|w\|^2 - \sum_{i=1}^{N} \alpha_i [y_i(w \cdot x_i + b) - 1] L(w,b,α)=21∥w∥2−i=1∑Nαi[yi(w⋅xi+b)−1]
对 L ( w , b , α ) L(w, b, \alpha) L(w,b,α) 关于 w w w 和 b b b 求偏导,并令其等于 0,我们可以将对 w w w 和 b b b 的优化问题转化为对 α \alpha α 的优化问题:
max α ∑ i = 1 N α i − 1 2 ∑ i = 1 N ∑ j = 1 N α i α j y i y j x i ⋅ x j s.t. α i ≥ 0 , ∑ i = 1 N α i y i = 0 \begin{align*} &\max_{\alpha} \sum_{i=1}^{N} \alpha_i - \frac{1}{2} \sum_{i=1}^{N} \sum_{j=1}^{N} \alpha_i \alpha_j y_i y_j x_i \cdot x_j \\ &\text{s.t.} \quad \alpha_i \geq 0, \quad \sum_{i=1}^{N} \alpha_i y_i = 0 \end{align*} αmaxi=1∑Nαi−21i=1∑Nj=1∑Nαiαjyiyjxi⋅xjs.t.αi≥0,i=1∑Nαiyi=0
这是一个二次规划问题,可以使用标准的优化算法(如 SMO,Sequential Minimal Optimization)来解决。
python
# 接下来我们将使用PyTorch实现线性支持向量机过程
# 我们首先生成一些数据用于分类问题
def synthetic_data_classfier(w, b, num_examples):
"""生成y=Xw+b+噪声"""
X = torch.normal(-1, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(-0.00001, 0.00001, y.shape)
# 将大于0的元素设置为1
y[y > 0] = 1.
# 将小于0的元素设置为-1
y[y < 0] = -1.
return X, y.reshape((-1, 1))
true_w = torch.tensor([-0.1, -0.2, -0.3, -0.4, -0.5, -1.0, 1, 0.5, 0.4, 0.3, 0.2, 0.1]) # 参数这样设置是为了让数据期望变为0
true_b = 0
features, labels = synthetic_data_classfier(true_w, true_b, 10000)
python
# 将数据放置在迭代器里边
from sklearn.model_selection import train_test_split
from torch.utils.data import Dataset, DataLoader
class CustomDataset(Dataset):
def __init__(self, features, labels):
self.features = features
self.labels = labels
def __len__(self):
return len(self.labels)
def __getitem__(self, idx):
return self.features[idx], self.labels[idx]
def create_data_loaders(features, labels, batch_size=32, test_size=0.2, random_state=42):
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=test_size, random_state=random_state)
# 创建Dataset对象
train_dataset = CustomDataset(X_train, y_train)
test_dataset = CustomDataset(X_test, y_test)
# 创建DataLoader对象
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)
return train_loader, test_loader
train_loader, test_loader = create_data_loaders(features, labels, batch_size=64)
python
# 让我们来设计一个线性支持向量模型吧
class LinearSVM(torch.nn.Module):
def __init__(self, input_dim):
super(LinearSVM, self).__init__()
self.w = torch.nn.Parameter(torch.randn(input_dim)) # randn为服从标准正态分布的随机数,此处我们生成一个随机向量作为参数w
self.b = torch.nn.Parameter(torch.randn(1)) # 偏置项
self.Loss = None # 损失函数
self.optimizer = None # 优化器
def forward(self, x):
return torch.matmul(x, self.w) + self.b # 前向传播获得输出
def refresh_params(self, x, y):
# 利用x,y数据更新参数w,b
out = self.forward(x)
if self.Loss is None:
self.Loss = torch.nn.MSELoss()
L = self.Loss(out.reshape(-1, 1), y)
# 利用梯度下降法更新参数w,b
if self.optimizer is None:
self.optimezer = torch.optim.SGD([self.w, self.b], lr=0.001) # 优化器
self.optimezer.zero_grad() # 梯度清零
L.backward() # 反向传播
self.optimezer.step() # 更新参数
python
# 接下来让我们进行训练和测试过程吧!
model = LinearSVM(input_dim=features.shape[1]) # 加载模型
for epoch in range(100):
for features_train, labels_train in train_loader:
model.refresh_params(features_train, labels_train)
# 测试数据精度
if epoch % 10 == 0 or epoch == 99:
with torch.no_grad():
for features_test, labels_test in test_loader:
predictions = model.forward(features_test)
predictions = torch.sign(predictions).reshape(labels_test.shape)
correct = (predictions == labels_test).float()
accuracy = correct.sum() / len(correct)
print("Epoch: {}, Accuracy: {}".format(epoch, accuracy))
break
Epoch: 0, Accuracy: 0.453125
Epoch: 10, Accuracy: 0.9375
Epoch: 20, Accuracy: 0.984375
Epoch: 30, Accuracy: 0.984375
Epoch: 40, Accuracy: 0.984375
Epoch: 50, Accuracy: 0.984375
Epoch: 60, Accuracy: 0.984375
Epoch: 70, Accuracy: 0.984375
Epoch: 80, Accuracy: 0.984375
Epoch: 90, Accuracy: 0.984375
Epoch: 99, Accuracy: 0.984375
由以上代码我们可以发现,当数据线性可分时,利用线性回归的策略都可以得到一个较好的精度。接下来我们参考机器学习算法实践-SVM中的SMO算法对SMO算法的详细介绍,设计一个标准的SVM分类器,并利用SMO算法进行优化。
python
def clip(a, L, H):
# 对alpha进行修剪
if a > H:
return H
elif a < L:
return L
else:
return a
class SVM(torch.nn.Module):
def __init__(self, input_dim):
super(SVM, self).__init__()
self.w = torch.randn(input_dim)
self.b = torch.randn(1)
self.alpha = None # 这个将在SMO中作为拉格朗日乘子的缓存
self.C = 1.0 # 正则化参数,可以根据需要调整
self.eps = 1e-3 # 用于判断数值解的容差
self.kernel_function = self.linear_kernel # 核函数,默认为线性核
def forward(self, x):
# 前向传播
return torch.matmul(x, self.w) + self.b
def linear_kernel(self, x1, x2):
# 线性核函数
if x1.shape[0] != x2.shape[0]:
return torch.matmul(x1, x2)
return torch.dot(x1, x2)
def f(self, x, data_x, data_y):
# 计算目标函数
return torch.sum(self.alpha * data_y * self.kernel_function(data_x, x)) + self.b
def SMO(self, x, y, i, j):
# SMO算法,其中输入的i,j为待优化的alpha下标
# 获得没有修建的原始解
# 旧的alpha值
alpha_i_old = self.alpha[i].clone()
alpha_j_old = self.alpha[j].clone()
# 计算两个alpha下标对应的样本点
x_i = x[i]
x_j = x[j]
# 计算两个alpha下标对应的样本点对应的标签
y_i = y[i]
y_j = y[j]
# 计算SVM预测值与真实值的误差
E_i = self.f(x_i, x, y) - y_i
E_j = self.f(x_j, x, y) - y_j
# 计算x_i与x_j的核函数值
k_ii, k_jj, k_ij = self.kernel_function(x_i, x_i), self.kernel_function(x_j, x_j), self.kernel_function(x_i, x_j)
eta = k_ii + k_jj - 2*k_ij
if eta <= 0:
print('WARNING eta <= 0')
return
alpha_j_new_unrestricted = alpha_j_old + (y_j * (E_i - E_j)) / eta
# 对alpha进行修剪
if y_i != y_j:
L = max(0, alpha_j_old - alpha_i_old)
H = min(self.C, self.C + alpha_j_old - alpha_i_old)
else:
L = max(0, alpha_i_old + alpha_j_old - self.C)
H = min(self.C, alpha_j_old + alpha_i_old)
alpha_j_new = clip(alpha_j_new_unrestricted, L, H)
alpha_i_new = alpha_i_old + y_i*y_j*(alpha_j_old - alpha_j_new)
if abs(alpha_j_new - alpha_j_old) < 0.00001:
return
self.alpha[i], self.alpha[j] = alpha_i_new, alpha_j_new
# 更新阈值b
b_i = -E_i - y_i*k_ii*(alpha_i_new - alpha_i_old) - y_j*k_ij*(alpha_j_new - alpha_j_old) + self.b
b_j = -E_j - y_i*k_ij*(alpha_i_new - alpha_i_old) - y_j*k_jj*(alpha_j_new - alpha_j_old) + self.b
if 0 < alpha_i_new < self.C:
self.b = b_i
elif 0 < alpha_j_new < self.C:
self.b = b_j
else:
self.b = (b_i + b_j)/2
def refresh_params(self, x, y):
# 利用SMO算法更新参数w,b,alpha
if self.alpha is None:
self.alpha = torch.zeros(x.shape[0]).reshape(-1, 1) # 拉格朗日乘子
# 遍历样本,随机选择第二个样本进行优化
for i in range(x.shape[0]):
# 选择第二个alpha
j = torch.randint(0, x.shape[0], (1,)).item()
while j == i: # 确保i和j不是同一个样本
j = torch.randint(0, x.shape[0], (1,)).item()
# 调用SMO来尝试更新alpha_i和alpha_j
self.SMO(x, y, i, j)
# 更新权重向量 w
self.w = torch.zeros(x.shape[1]) # 初始化 w 为零向量
for i in range(x.shape[0]):
self.w += self.alpha[i] * y[i] * x[i] # 根据上述公式累加更新 w
python
# 接下来让我们选取一部分数据来看看SMO算法吧
model = SVM(input_dim=features.shape[1]) # 加载模型
# 选取数据集中前2000个数据进行训练
model.refresh_params(features[:3000,:], labels[:3000,:])
# 测试数据精度
with torch.no_grad():
for features_test, labels_test in test_loader:
predictions = model.forward(features_test)
predictions = torch.sign(predictions).reshape(labels_test.shape)
correct = (predictions == labels_test).float()
accuracy = correct.sum() / len(correct)
print( "Accuracy: {}".format(accuracy))
break
Accuracy: 0.9375
可以看到利用上述代码,我们可以得到一个精度相对较高的SVM分类器。
支持向量与间隔
在 SVM 中,"支持向量"是指那些距离超平面最近的数据点,它们对确定最优超平面起决定性作用。间隔(margin)是支持向量到超平面的距离,其大小由 1 ∥ w ∥ \frac{1}{\|w\|} ∥w∥1 给出。SVM 的目标是最大化这个间隔。
核函数与非线性 SVM
对于非线性可分的数据集,我们可以通过引入核函数 K ( x , y ) K(x, y) K(x,y) 来将数据映射到一个更高维的空间,从而在新的空间中实现线性可分。常用的核函数包括多项式核、高斯核(RBF 核)等。
支持向量机(Support Vector Machine, SVM)是一种常用的监督学习模型,主要用于分类和回归分析。当数据不是线性可分时,可以引入核函数,将数据映射到更高维的空间,从而在新的空间中实现线性可分。
具有核函数的支持向量机
给定训练数据集 T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ... , ( x N , y N ) } T = \{(x_1, y_1), (x_2, y_2), \ldots, (x_N, y_N)\} T={(x1,y1),(x2,y2),...,(xN,yN)},其中 $ x_i \in \mathbb{R}^n,y_i \in {+1, -1} $。支持向量机的基本思想是找到一个超平面,以最大化两个类别之间的间隔。
在非线性可分的情况下,引入一个非线性映射 ϕ ( x ) \phi(x) ϕ(x) 将输入空间的数据映射到一个更高维的特征空间,然后在这个特征空间中寻找最优超平面。然而,直接计算 ϕ ( x ) \phi(x) ϕ(x) 可能是复杂的,甚至是不可能的。因此,我们引入核函数 K ( x , z ) K(x, z) K(x,z),它满足 K ( x , z ) = ϕ ( x ) ⋅ ϕ ( z ) K(x, z) = \phi(x) \cdot \phi(z) K(x,z)=ϕ(x)⋅ϕ(z),从而避免显式地计算 ϕ ( x ) \phi(x) ϕ(x)。
常用的核函数包括:
- 线性核: K ( x , z ) = x ⋅ z K(x, z) = x \cdot z K(x,z)=x⋅z
- 多项式核: K ( x , z ) = ( γ x ⋅ z + r ) d K(x, z) = (\gamma x \cdot z + r)^d K(x,z)=(γx⋅z+r)d,其中 γ , r , d \gamma, r, d γ,r,d 是参数。
- 径向基核(RBF): K ( x , z ) = exp ( − γ ∥ x − z ∥ 2 ) K(x, z) = \exp(-\gamma \|x - z\|^2) K(x,z)=exp(−γ∥x−z∥2),其中 γ \gamma γ 是参数。
- Sigmoid核: K ( x , z ) = tanh ( γ x ⋅ z + r ) K(x, z) = \tanh(\gamma x \cdot z + r) K(x,z)=tanh(γx⋅z+r),其中 γ , r \gamma, r γ,r 是参数。
对偶问题
在支持向量机中,对偶问题是通过拉格朗日乘数法将原问题转化为更易求解的形式。对偶问题的求解通常更高效,并且可以引入核函数来处理非线性可分的情况。
原问题可以表示为:
min w , b , ξ 1 2 ∥ w ∥ 2 + C ∑ i = 1 N ξ i s.t. y i ( w ⋅ ϕ ( x i ) + b ) ≥ 1 − ξ i , ξ i ≥ 0 , i = 1 , 2 , ... , N \min_{w, b, \xi} \frac{1}{2} \|w\|^2 + C \sum_{i=1}^{N} \xi_i \\ \text{s.t.} \quad y_i (w \cdot \phi(x_i) + b) \geq 1 - \xi_i, \quad \xi_i \geq 0, \quad i = 1, 2, \ldots, N w,b,ξmin21∥w∥2+Ci=1∑Nξis.t.yi(w⋅ϕ(xi)+b)≥1−ξi,ξi≥0,i=1,2,...,N
其中 w w w 是超平面的法向量, b b b 是偏置项, ξ i \xi_i ξi 是松弛变量,用于处理不可分的情况, C C C 是惩罚参数。
通过拉格朗日乘数法,我们可以得到对偶问题:
max α ∑ i = 1 N α i − 1 2 ∑ i = 1 N ∑ j = 1 N α i α j y i y j K ( x i , x j ) s.t. ∑ i = 1 N α i y i = 0 , 0 ≤ α i ≤ C , i = 1 , 2 , ... , N \max_{\alpha} \sum_{i=1}^{N} \alpha_i - \frac{1}{2} \sum_{i=1}^{N} \sum_{j=1}^{N} \alpha_i \alpha_j y_i y_j K(x_i, x_j) \\ \text{s.t.} \quad \sum_{i=1}^{N} \alpha_i y_i = 0, \quad 0 \leq \alpha_i \leq C, \quad i = 1, 2, \ldots, N αmaxi=1∑Nαi−21i=1∑Nj=1∑NαiαjyiyjK(xi,xj)s.t.i=1∑Nαiyi=0,0≤αi≤C,i=1,2,...,N
其中 α i \alpha_i αi 是拉格朗日乘子, K ( x i , x j ) K(x_i, x_j) K(xi,xj) 是核函数。
求解对偶问题后,我们可以得到最优的 α ∗ \alpha^* α∗,进而通过 α ∗ \alpha^* α∗ 得到最优的超平面参数 w ∗ w^* w∗ 和 b ∗ b^* b∗。最终,决策函数可以表示为:
f ( x ) = sign ( ∑ i = 1 N α i ∗ y i K ( x i , x ) + b ∗ ) f(x) = \text{sign} \left( \sum_{i=1}^{N} \alpha_i^* y_i K(x_i, x) + b^* \right) f(x)=sign(i=1∑Nαi∗yiK(xi,x)+b∗)
支持向量机的拓展:支持向量回归(SVR)
支持向量机不仅可以用于分类问题,还可以扩展到回归问题,即支持向量回归(Support Vector Regression, SVR)。在 SVR 中,目标是找到一个回归函数 f ( x ) = w ⋅ ϕ ( x ) + b f(x) = w \cdot \phi(x) + b f(x)=w⋅ϕ(x)+b,其中 ϕ ( x ) \phi(x) ϕ(x) 是将数据映射到高维空间的函数。SVR 通过引入一个不敏感损失函数 ϵ \epsilon ϵ 来实现回归,该函数只惩罚那些与预测值差距大于 ϵ \epsilon ϵ 的点。
SVR 的优化问题与 SVM 类似,但约束条件和目标函数有所不同。通过解决相应的优化问题,我们可以找到最优的回归函数。
支持向量机是一种强大的分类和回归工具,特别适合于处理高维数据和复杂模式识别问题。通过选择合适的核函数和参数,SVM 可以灵活地处理线性和非线性问题。此外,SVM 还可以通过拓展到支持向量回归来解决回归问题,进一步扩大了其应用范围。
接下来我们使用机器学习库sklearn实现支持向量机和支持向量回归
python
from sklearn import svm
from sklearn.model_selection import train_test_split
from sklearn import datasets
from sklearn.metrics import accuracy_score
# 加载数据集,这里以鸢尾花数据集为例
iris = datasets.load_iris()
X = iris.data
y = iris.target
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
python
# 创建SVM分类器
clf = svm.SVC(kernel='linear') # 线性核函数,你也可以选择其他核函数,如'rbf'、'poly'、'sigmoid'等
# 训练模型
clf.fit(X_train, y_train)
# 进行预测
y_pred = clf.predict(X_test)
# 评估模型
print("Accuracy:", accuracy_score(y_test, y_pred))
Accuracy: 0.9777777777777777
python
# 接下来我们使用之前生成的数据进行支持向量回归,看看模型精度
X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=0.3)
# 创建SVM分类器
clf = svm.SVC(kernel='linear') # 线性核函数,你也可以选择其他核函数,如'rbf'、'poly'、'sigmoid'等
# 训练模型
clf.fit(X_train, y_train)
# 进行预测
y_pred = clf.predict(X_test)
# 评估模型
print("Accuracy:", accuracy_score(y_test, y_pred))
Accuracy: 0.9963333333333333
C:\Users\sswun\AppData\Roaming\Python\Python311\site-packages\sklearn\utils\validation.py:1183: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
y = column_or_1d(y, warn=True)
可以看到,利用sklearn设计的支持向量机分类器,我们可以得到一个很高的分类精度。接下来我们实现一下支持向量回归的过程。
python
# 支持向量回归过程
from sklearn.metrics import mean_squared_error
# 加载California housing数据集
boston = datasets.fetch_california_housing()
X = boston.data
y = boston.target
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 创建SVR模型
svr = svm.SVR(kernel='rbf', C=1e3, gamma=0.1) # 这里使用了RBF核函数
# 训练模型
svr.fit(X_train, y_train)
# 进行预测
y_pred = svr.predict(X_test)
# 评估模型
mse = mean_squared_error(y_test, y_pred)
print("Mean Squared Error:", mse)
Mean Squared Error: 1.1010094730131161
支持向量机(SVM)的发展历程可以追溯到上个世纪60年代。1963年,Vapnik在解决模式识别问题时首次提出了支持向量方法,其中起决定性作用的样本被称为支持向量。然后在1971年,Kimeldorf提出了基于支持向量构建核空间的方法。然而,SVM的真正发展和普及是在1995年,当Vapnik等人正式提出统计学习理论之后。从那时起,SVM逐渐成为一种广泛使用的机器学习算法。
在SVM的发展历程中,还出现了许多改进和变种。例如,模糊支持向量机、最小二乘支持向量机、加权支持向量机、主动学习的支持向量机、以及基于决策树的支持向量机等,都是对原始SVM的改进和扩展。这些改进使得SVM能够更灵活地处理各种不同类型的数据和问题。
至于SVM的未来发展道路,虽然目前SVM已经在许多领域取得了显著的成功,但随着数据科学和机器学习领域的快速发展,SVM仍面临一些挑战和机遇。
一方面,随着大数据时代的到来,如何高效地处理海量数据成为SVM面临的一个重要问题。未来的研究可能会集中在开发更高效的优化算法,以提高SVM在处理大数据时的性能和效率。
另一方面,随着深度学习技术的快速发展,如何将深度学习的思想与方法融入SVM中,以进一步提升其性能,也是未来研究的一个重要方向。例如,可以利用深度学习的特征提取能力来优化SVM的输入特征,或者借鉴深度学习的训练方法来改进SVM的训练过程。
此外,随着越来越多的复杂应用场景的出现,如何对SVM进行改进以适应这些场景也是未来研究的一个重要课题。例如,在处理不平衡数据集、多类别分类、以及在线学习等问题时,SVM可能需要进行相应的改进和优化。
总的来说,虽然SVM已经是一种非常成熟和有效的机器学习算法,但随着技术的不断进步和应用场景的不断拓展,SVM仍然有着广阔的发展空间和潜力。
本文部分为torch框架以及部分理论分析,paddle框架对应代码可见支持向量机paddle