赞
踩
本文将深入探讨支持向量机的理论基础,并通过PyTorch和PaddlePaddle两个深度学习框架来展示如何实现支持向量机模型。我们将首先介绍支持向量机、优化的基本概念,这些数学工具是理解和实现支持向量机的基础。通过PyTorch和PaddlePaddle的代码示例,我们将展示如何设计、训练和评估一个支持向量机模型,从而让读者能够直观地理解并掌握这两种框架在机器学习问题中的应用。
torch部分代码可点击支持向量机——pytorch与paddle实现支持向量机
import paddle
print("paddle version:",paddle.__version__)
paddle version: 2.6.0
支持向量机(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
这里,
∥
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
这是一个二次规划问题,可以使用标准的优化算法(如 SMO,Sequential Minimal Optimization)来解决。
# 接下来我们将使用PyTorch实现线性支持向量机过程
# 我们首先生成一些数据用于分类问题
def synthetic_data_classfier(w, b, num_examples):
"""生成y=Xw+b+噪声"""
X = paddle.normal(-1, 1, (num_examples, len(w)))
y = paddle.matmul(X, w) + b
y += paddle.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 = paddle.to_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, 1000)
W0411 00:18:11.649781 76220 gpu_resources.cc:119] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 12.0, Runtime API Version: 11.8
W0411 00:18:11.651280 76220 gpu_resources.cc:164] device: 0, cuDNN Version: 8.9.
from sklearn.model_selection import train_test_split
from paddle.io 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=True)
return train_loader, test_loader
train_loader, test_loader = create_data_loaders(features, labels, batch_size=4)
# 让我们来设计一个线性支持向量模型吧
from paddle import nn
# 设计一个线性支持向量模型
class LinearSVM(nn.Layer):
def __init__(self, input_dim):
super(LinearSVM, self).__init__()
# 初始化参数w和b为一线性层
self.linear = nn.Linear(in_features=input_dim, out_features=1)
def forward(self, x):
# 前向传播获得输出
return self.linear(x)
# 加载模型
model = LinearSVM(input_dim=features.shape[1])
# 初始化损失函数和优化器
loss_fn = nn.MSELoss()
optimizer = paddle.optimizer.SGD(learning_rate=0.001, parameters=model.parameters())
# 训练和测试过程
for epoch in range(100):
for features_train, labels_train in train_loader:
# 更新模型参数
# 前向传播
outputs = model(features_train)
loss = loss_fn(outputs, labels_train)
# 反向传播和参数更新
loss.backward()
optimizer.step()
optimizer.clear_grad()
# 测试数据精度
if epoch % 10 == 0 or epoch == 99:
model.eval() # 设置为评估模式
for features_test, labels_test in test_loader:
# 进行预测
predictions = model(features_test) # 假设model是一个paddle.nn.Layer实例,可以直接调用
# 使用paddle.sign函数获取符号
predictions = paddle.sign(predictions).reshape(labels_test.shape)
# 计算准确率
correct = (predictions == labels_test).astype('float32')
# 使用paddle.sum和paddle.numel来计算正确预测的数量和总数量
correct_count = paddle.sum(correct)
total_count = paddle.numel(correct) # 或者使用 labels_test.numel()
accuracy = correct_count / total_count
# 转换为numpy数组以便打印,如果是单个数值则使用item()
accuracy = accuracy.numpy().item()
print("Epoch: {}, Accuracy: {}".format(epoch, accuracy))
break
model.train() # 恢复训练模式
Epoch: 0, Accuracy: 0.75
Epoch: 10, Accuracy: 1.0
Epoch: 20, Accuracy: 1.0
Epoch: 30, Accuracy: 1.0
Epoch: 40, Accuracy: 1.0
Epoch: 50, Accuracy: 1.0
Epoch: 60, Accuracy: 1.0
Epoch: 70, Accuracy: 1.0
Epoch: 80, Accuracy: 1.0
Epoch: 90, Accuracy: 1.0
Epoch: 99, Accuracy: 1.0
由以上代码我们可以发现,当数据线性可分时,利用线性回归的策略都可以得到一个较好的精度。接下来我们参考机器学习算法实践-SVM中的SMO算法对SMO算法的详细介绍,设计一个标准的SVM分类器,并利用SMO算法进行优化。
from paddle import tensor
def clip(a, L, H):
# 对a进行修剪
return paddle.clip(a, min=L, max=H)
class SVM(paddle.nn.Layer):
def __init__(self, input_dim):
super(SVM, self).__init__()
self.w = self.create_parameter(shape=[input_dim], default_initializer=paddle.nn.initializer.Normal())
self.b = self.create_parameter(shape=[1], default_initializer=paddle.nn.initializer.Constant(0.0))
self.alpha = None # 这个将在SMO中作为拉格朗日乘子的缓存
self.C = 1.0 # 正则化参数,可以根据需要调整
self.eps = 1e-3 # 用于判断数值解的容差
self.kernel_function = self.linear_kernel # 核函数,默认为线性核
def forward(self, x):
# 前向传播
return paddle.matmul(x, self.w) + self.b
def linear_kernel(self, x1, x2):
# 线性核函数
if x1.shape[0] != x2.shape[0]:
return paddle.matmul(x1, x2, transpose_y=True)
return paddle.dot(x1, x2)
def f(self, x, data_x, data_y):
# 计算目标函数
return paddle.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 paddle.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.set_value(b_i)
elif 0 < alpha_j_new < self.C:
self.b.set_value(b_j)
else:
self.b.set_value((b_i + b_j)/2)
def refresh_params(self, x, y):
# 利用SMO算法更新参数w,b,alpha
if self.alpha is None:
self.alpha = paddle.zeros([x.shape[0], 1]) # 拉格朗日乘子
# 遍历样本,随机选择第二个样本进行优化
for i in range(x.shape[0]):
# 选择第二个alpha
j = paddle.randint(low=0, high=x.shape[0], shape=[1]).numpy().item()
while j == i: # 确保i和j不是同一个样本
j = paddle.randint(low=0, high=x.shape[0], shape=[1]).numpy().item()
# 调用SMO来尝试更新alpha_i和alpha_j
self.SMO(x, y, i, j)
# 更新权重向量 w
self.w.set_value(paddle.zeros(shape=[x.shape[1]])) # 初始化 w 为零向量
for i in range(x.shape[0]):
self.w.set_value(self.w + self.alpha[i] * y[i] * x[i]) # 根据上述公式累加更新 w
# 接下来让我们选取一部分数据来看看SMO算法吧
model = SVM(input_dim=features.shape[1]) # 加载模型
# 选取数据集中数据进行训练
model.refresh_params(features, labels)
# 测试数据精度
for features_test, labels_test in test_loader:
predictions = model.forward(features_test)
predictions = paddle.sign(predictions).reshape(labels_test.shape)
correct = (predictions == labels_test).astype('float32')
accuracy = correct.sum() / len(correct)
print( "Accuracy: {}".format(accuracy))
break
Accuracy: Tensor(shape=[], dtype=float32, place=Place(gpu:0), stop_gradient=True,
1.)
可以看到利用上述代码,我们可以得到一个精度相对较高的SVM分类器。
在 SVM 中,“支持向量”是指那些距离超平面最近的数据点,它们对确定最优超平面起决定性作用。间隔(margin)是支持向量到超平面的距离,其大小由 1 ∥ w ∥ \frac{1}{\|w\|} ∥w∥1 给出。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)。
常用的核函数包括:
在支持向量机中,对偶问题是通过拉格朗日乘数法将原问题转化为更易求解的形式。对偶问题的求解通常更高效,并且可以引入核函数来处理非线性可分的情况。
原问题可以表示为:
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∗)
支持向量机不仅可以用于分类问题,还可以扩展到回归问题,即支持向量回归(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实现支持向量机和支持向量回归
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)
# 创建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
# 接下来我们使用之前生成的数据进行支持向量回归,看看模型精度
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.9933333333333333
/opt/conda/envs/python35-paddle120-env/lib/python3.10/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设计的支持向量机分类器,我们可以得到一个很高的分类精度。接下来我们实现一下支持向量回归的过程。
# 支持向量回归过程
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.101011335402103
torch部分代码可点击支持向量机——pytorch与paddle实现支持向量机
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。