- 生成假数据集:创建一个简单的二分类数据集。
- 数据标准化:标准化特征。
- 定义线性核函数:选择一个简单的线性核。
- 定义优化问题:使用对偶问题进行求解。
- 求解对偶问题:通过简单的梯度上升法求解对偶问题。
- 确定支持向量:找到支持向量。
- 计算权重和偏置:计算超平面的参数。
- 进行预测:使用训练好的模型对新数据点进行预测。
1. 生成假数据集
我们创建一个简单的二分类数据集:
import numpy as np# 生成假数据点
X = np.array([[2, 3], [3, 3], [4, 2], [1, 1], [2, 2], [2, 1]])
y = np.array([1, 1, 1, -1, -1, -1]) # 类别标签
2. 数据标准化
将数据标准化,使每个特征具有零均值和单位方差:
X_mean = np.mean(X, axis=0)
X_std = np.std(X, axis=0)
X = (X - X_mean) / X_std
3. 定义线性核函数
我们选择线性核函数:
def linear_kernel(x1, x2):return np.dot(x1, x2)
4. 定义优化问题
SVM 的对偶问题可以表示为:
max α ∑ i = 1 n α i − 1 2 ∑ i = 1 n ∑ j = 1 n α i α j y i y j K ( x i , x j ) \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) αmaxi=1∑nαi−21i=1∑nj=1∑nαiαjyiyjK(xi,xj)
5. 求解对偶问题
通过简单的梯度上升法求解对偶问题:
def train_svm(X, y, C=1.0, max_iter=100, learning_rate=0.01):n_samples, n_features = X.shapealpha = np.zeros(n_samples)for _ in range(max_iter):for i in range(n_samples):gradient = 1 - y[i] * np.sum(alpha * y * np.array([linear_kernel(X[i], X[j]) for j in range(n_samples)]))alpha[i] += learning_rate * gradientalpha[i] = max(0, min(alpha[i], C))return alphaalpha = train_svm(X, y)
下面我们逐行解释 train_svm
函数的代码:
def train_svm(X, y, C=1.0, max_iter=100, learning_rate=0.01):
- 函数定义:
train_svm
是一个用于训练支持向量机的函数。 - 参数说明:
X
:输入数据集,形状为 (n_samples, n_features),表示 n 个样本和每个样本的特征向量。y
:标签数组,长度为 n_samples,取值为 1 或 -1,表示每个样本的类别。C
:正则化参数,默认值为 1.0,控制对误分类的惩罚程度。max_iter
:最大迭代次数,默认值为 100,表示算法将运行的最大迭代次数。learning_rate
:学习率,默认值为 0.01,控制每次更新的步长。
n_samples, n_features = X.shapealpha = np.zeros(n_samples)
- 数据维度:
n_samples
和n_features
分别表示样本数和特征数。 - 初始化:
alpha
是拉格朗日乘子,初始化为零数组,长度为样本数。
for _ in range(max_iter):
- 迭代循环:主循环,控制最大迭代次数。
for i in range(n_samples):
- 样本循环:遍历每个样本。
gradient = 1 - y[i] * np.sum(alpha * y * np.array([linear_kernel(X[i], X[j]) for j in range(n_samples)]))
- 计算梯度:
- 线性核函数:计算所有样本与第 i i i 个样本的线性核(即点积)。
- 梯度计算:
gradient
是对偶问题的梯度,计算公式为 1 − y i ∑ j = 1 n α j y j K ( x i , x j ) 1 - y_i \sum_{j=1}^{n} \alpha_j y_j K(x_i, x_j) 1−yi∑j=1nαjyjK(xi,xj)。 linear_kernel(X[i], X[j])
计算第 i i i 个样本和第 j j j 个样本的点积。
alpha[i] += learning_rate * gradient
- 更新拉格朗日乘子:按照梯度上升法更新
alpha
,
alpha[i] = max(0, min(alpha[i], C))
- 约束拉格朗日乘子:将
alpha[i]
限制在 0 到C
之间,确保满足对偶问题的约束条件。
return alpha
- 返回结果:迭代结束后,返回更新后的
alpha
数组。
逐行解释总结
train_svm
函数实现了一个简化的支持向量机训练过程。主要步骤如下:
- 初始化
alpha
为零。 - 在最大迭代次数内,遍历所有样本,计算梯度并更新
alpha
。 - 每次更新
alpha
后,确保其在合法范围内(0 到C
)。 - 最终返回更新后的
alpha
值。
通过这种方法,我们得到了拉格朗日乘子 alpha
,可以进一步用于计算权重向量和偏置,进而构建 SVM 模型进行分类预测。
6. 确定支持向量
支持向量是那些 α i > 0 \alpha_i > 0 αi>0的数据点:
support_vectors_idx = np.where(alpha > 1e-5)[0]
support_vectors = X[support_vectors_idx]
support_vector_labels = y[support_vectors_idx]
7. 计算权重和偏置
权重向量 w \mathbf{w} w 和偏置 b b b 的计算:
# 计算权重向量 w
w = np.sum(alpha[:, None] * y[:, None] * X, axis=0)
数学公式:
w = ∑ i = 1 n α i y i x i w = \sum_{i=1}^{n} \alpha_i y_i x_i w=∑i=1nαiyixi
# 计算偏置 b
b = np.mean([y[i] - np.dot(w, X[i]) for i in support_vectors_idx])
数学公式:
b = 1 ∣ S ∣ ∑ i ∈ S ( y i − w ⋅ x i ) b = \frac{1}{|S|} \sum_{i \in S} \left( y_i - w \cdot x_i \right) b=∣S∣1∑i∈S(yi−w⋅xi)
通过代码和公式的结合,可以更清晰地理解 SVM 模型的训练过程。
8. 进行预测
定义预测函数:
def predict(X):return np.sign(np.dot(X, w) + b)# 进行预测
new_points = np.array([[3, 2], [1, 3]])
new_points = (new_points - X_mean) / X_std
predictions = predict(new_points)
下面我们逐行解释如何使用高斯核(RBF核)来修改预测函数。首先我们回顾一下高斯核的公式:
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 σ是高斯核的参数,决定了核函数的宽度。
代码解释
import numpy as npdef gaussian_kernel(x, y, sigma=1.0):return np.exp(-np.linalg.norm(x - y) ** 2 / (2 * sigma ** 2))
- 定义高斯核函数:
gaussian_kernel(x, y, sigma=1.0)
计算两个向量x
和y
之间的高斯核值。np.linalg.norm(x - y) ** 2
计算向量x
和y
之间的欧几里得距离平方,然后除以 2 σ 2 2\sigma^2 2σ2并取负数,再用np.exp
计算指数函数,得到核值。
def predict(X, support_vectors, support_vector_labels, alphas, b, sigma=1.0):y_pred = np.zeros(X.shape[0])for i in range(X.shape[0]):kernel_sum = 0for alpha, sv_y, sv in zip(alphas, support_vector_labels, support_vectors):kernel_sum += alpha * sv_y * gaussian_kernel(X[i], sv, sigma)y_pred[i] = kernel_sumreturn np.sign(y_pred + b)
-
定义预测函数:
predict
函数接受新的数据点X
,支持向量support_vectors
,支持向量的标签support_vector_labels
,拉格朗日乘子alphas
,偏置b
,以及高斯核参数sigma
。 -
初始化预测结果:
y_pred = np.zeros(X.shape[0])
创建一个与X
的样本数量相同的零向量,用于存储预测结果。 -
遍历每个新数据点:
for i in range(X.shape[0])
逐个遍历每个新样本X[i]
。 -
计算每个样本的核函数和:
kernel_sum = 0
初始化当前样本的核函数和。接下来,通过for alpha, sv_y, sv in zip(alphas, support_vector_labels, support_vectors)
遍历每个支持向量的拉格朗日乘子alpha
,标签sv_y
,以及支持向量sv
,计算alpha * sv_y * gaussian_kernel(X[i], sv, sigma)
并累加到kernel_sum
中。 -
存储预测值:
y_pred[i] = kernel_sum
将当前样本的核函数和赋值给y_pred[i]
。 -
返回最终预测结果:
return np.sign(y_pred + b)
对y_pred
加上偏置b
后取符号,得到最终的预测结果。
假设我们已经训练好了一个使用高斯核的 SVM 模型,得到了支持向量、支持向量标签、拉格朗日乘子和偏置。使用上述 predict
函数,我们可以对新的数据点进行分类预测。
这样,我们通过逐行解释了如何修改预测函数以使用高斯核来处理非线性分类问题。
小结
以上代码展示了如何手动实现一个简单的线性支持向量机(SVM)模型。我们生成了一些假数据,进行了数据标准化,定义了线性核函数,通过梯度上升法求解对偶问题,确定支持向量,计算了模型参数,并进行了预测。
这个例子只是一个简化的版本,实际应用中,SVM 的训练过程涉及更多的细节和优化算法,如序列最小优化(SMO)等。通过这一例子,你可以理解 SVM 的基本原理和求解过程。