目录
一、简介
支持向量机(Support Vector Machine, SVM)是一种强大的分类算法,它是一种监督学习模型,用于分类和回归分析。其核心思想是通过找到一个最优超平面将不同类别的数据分开。
二、算法原理
支持向量机的核心思想是找到一个超平面将数据点分为不同的类别。对于线性可分的数据集,SVM通过寻找一个能够最大化类别间距离的超平面来实现分类。对于线性不可分的数据集,SVM使用核函数(Kernel Function)将数据映射到高维空间,在高维空间中找到线性可分的超平面。
1、超平面和支持向量
在二维空间中,超平面是一个直线;在三维空间中,超平面是一个平面。对于n维空间,超平面是一个n-1维的子空间。假设我们有一个线性可分的数据集,SVM的目标是找到一个能够最大化间隔的超平面。
我们以二维空间为例,对于一组数据如下图:
我们希望通过一条线将两种不同类别的数据分开,这条线的画法有无数种,其中使间隔最大的那条线就是我们最理想的超平面。而间隔就是图中虚线和虚线之间的距离,对于一个分类问题,间隔越大越能体现出类别的差异,这就是为什么我们需要画出间隔最大的超平面。
对于上述数据,我们只需要找出两种类别中距离最近的两点,做一条直线L,使这两点到L上距离最大,此时L就是我们要找的超平面。
过这两点做平行于L的直线L1和L2,在L1和L2上的点就被称为支持向量,同时我们也可以将L1和L2分别定义为负超平面和正超平面。
2、数学推导
还是以二维空间为例,假设超平面的数学表达式为,设数据中一点为,点到直线的距离公式为:
分子拿去绝对值符号可以叫做函数间隔,用表示,,为样本点的标签。分母则是w的范数,因此,式子又可以写成:
正如‘1、超平面和支持向量’中所说的,我们只需要找出两种类别中距离最近的两点,做一条直线L,使这两点到L上距离最大,表达式为:
约束条件:
这里有一个性质需要注意,就是函数间隔是可以缩放的。当变为时,,此时与仍然代表同一个超平面,所以我们可以将函数间隔取1,最终的表达式为:
约束条件:
由于等价于,通过拉格朗日对偶得:
约束条件:
推导的最终结论为:
由此我们可以得出超平面直线的权重w和偏置b。
三、硬间隔和软间隔支持向量机
1、硬间隔支持向量机
对于一组数据,如果可以完全被线性分类,那么这个支持向量机被称为硬间隔支持向量机,如‘二、算法原理’中的图所示。
2、软间隔支持向量机
对于一组数据,如果不能完全被线性分类,那么这个支持向量机被称为软间隔支持向量机,如下图所示:
可以看到有错误的点存在在间隔之中,当存在这样的情况,我们就要引入亏损,也叫松弛变量。约束条件变为:
在优化问题上也加上一个惩罚项:
其中C和都是非零正数。
同样的,也对上述式子做拉格朗日对偶:
约束条件:
推导的最终结论为:
可以看出来硬间隔支持向量机和软间隔支持向量机的结论是基本一致的,只有的取值范围不同。
四、核函数
当数据在原始特征空间中线性不可分时,SVM通过核方法(Kernel Method)将数据映射到高维空间,使得在高维空间中数据线性可分。
可见上图的数据集明显是线性不可分的,因此我们可以通过增维,将他们映射到新的平面上,下图就是通过核函数构建第二个维度的结果:
可以看到,这组数据通过处理又变成线性可分了。 这样的方法也被称作非线性支持向量机。
以上面的数据为例,假设一点为,我们通过函数将映射为:
由此我们可以定义一个函数:
这个函数就是核函数。
我们可以利用这个核函数得到核函数变换的非线性支持向量机的优化函数:
约束条件:
五、函数实现
1、数据准备
# 生成类别1的数据
num_samples_class1 = 30
mean_class1 = [1, 2]
cov_class1 = [[1, 0.5], [0.5, 1]]
class1_data = np.random.multivariate_normal(mean_class1, cov_class1, num_samples_class1)
# 生成类别0的数据
num_samples_class0 = 30
mean_class0 = [7, 8]
cov_class0 = [[1, -0.5], [-0.5, 1]]
class0_data = np.random.multivariate_normal(mean_class0, cov_class0, num_samples_class0)
# 合并训练数据
X_train = np.vstack((class1_data, class0_data))
y_train = np.hstack((np.ones(num_samples_class1), np.zeros(num_samples_class0)))
# 生成类别1的测试数据
num_samples_test_class1 = 10
mean_test_class1 = [1, 2]
cov_test_class1 = [[1, 0.3], [0.3, 1]]
test_data_class1 = np.random.multivariate_normal(mean_test_class1, cov_test_class1, num_samples_test_class1)
y_test_class1 = np.ones(num_samples_test_class1)
# 生成类别0的测试数据
num_samples_test_class0 = 10
mean_test_class0 = [7, 8]
cov_test_class0 = [[1, -0.5], [-0.5, 1]]
test_data_class0 = np.random.multivariate_normal(mean_test_class0, cov_test_class0, num_samples_test_class0)
y_test_class0 = np.zeros(num_samples_test_class0)
# 合并测试数据
X_test = np.vstack((test_data_class1, test_data_class0))
y_test = np.hstack((y_test_class1, y_test_class0))
2、构建支持向量机
class SVM:
def __init__(self, learning_rate=0.001, lambda_param=0.01, n_iters=1000):
self.lr = learning_rate # 学习率
self.lambda_param = lambda_param # 正则化参数
self.n_iters = n_iters # 迭代次数
self.w = None
self.b = None
def fit(self, X, y):
n_samples, n_features = X.shape
y_ = np.where(y <= 0, -1, 1) # 将标签转换为-1和1
self.w = np.zeros(n_features)
self.b = 0
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
if condition:
self.w -= self.lr * (2 * self.lambda_param * self.w)
else:
self.w -= self.lr * (2 * self.lambda_param * self.w - np.dot(x_i, y_[idx]))
self.b -= self.lr * y_[idx]
def predict(self, X):
linear_output = np.dot(X, self.w) - self.b
return np.sign(linear_output)
3、训练模型
# 特征缩放
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
# 训练SVM模型
svm_model = SVM(learning_rate=0.001, lambda_param=0.01, n_iters=1000)
svm_model.fit(X_train, y_train)
4、测试和评估
# 评估模型
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
accuracy = accuracy_score(y_test, predictions)
conf_matrix = confusion_matrix(y_test, predictions)
class_report = classification_report(y_test, predictions)
print(f"准确率: {accuracy:.2f}")
print("混淆矩阵:")
print(conf_matrix)
print("分类报告:")
print(class_report)
5、可视化
# 可视化结果,包括训练数据、测试数据和决策边界
def visualize_svm():
def get_hyperplane_value(x, w, b, offset):
return (-w[0] * x + b + offset) / w[1]
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
plt.scatter(X_train[:, 0], X_train[:, 1], marker='o', c=y_train)
plt.scatter(X_test[:, 0], X_test[:, 1], marker='x', c=predictions)
x0_1 = np.amin(X_train[:, 0])
x0_2 = np.amax(X_train[:, 0])
x1_1 = get_hyperplane_value(x0_1, svm_model.w, svm_model.b, 0)
x1_2 = get_hyperplane_value(x0_2, svm_model.w, svm_model.b, 0)
x1_1_m = get_hyperplane_value(x0_1, svm_model.w, svm_model.b, -1)
x1_2_m = get_hyperplane_value(x0_2, svm_model.w, svm_model.b, -1)
x1_1_p = get_hyperplane_value(x0_1, svm_model.w, svm_model.b, 1)
x1_2_p = get_hyperplane_value(x0_2, svm_model.w, svm_model.b, 1)
ax.plot([x0_1, x0_2], [x1_1, x1_2], 'k')
ax.plot([x0_1, x0_2], [x1_1_m, x1_2_m], 'r--')
ax.plot([x0_1, x0_2], [x1_1_p, x1_2_p], 'r--')
x1_min = np.amin(X_train[:, 1])
x1_max = np.amax(X_train[:, 1])
ax.set_ylim([x1_min - 3, x1_max + 3])
plt.legend()
plt.show()
visualize_svm()
6、实验结果
分析:结果不理想的原因可能是生成训练数据数量太少以及数据分布调整的不到位。
7、完整代码
import numpy as np
import matplotlib.pyplot as plt
class SVM:
def __init__(self, learning_rate=0.001, lambda_param=0.01, n_iters=1000):
self.lr = learning_rate
self.lambda_param = lambda_param
self.n_iters = n_iters
self.w = None
self.b = None
def fit(self, X, y):
n_samples, n_features = X.shape
y_ = np.where(y <= 0, -1, 1) # Convert labels to -1 and 1
self.w = np.zeros(n_features)
self.b = 0
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
if condition:
self.w -= self.lr * (2 * self.lambda_param * self.w)
else:
self.w -= self.lr * (2 * self.lambda_param * self.w - np.dot(x_i, y_[idx]))
self.b -= self.lr * y_[idx]
def predict(self, X):
linear_output = np.dot(X, self.w) - self.b
return np.where(linear_output >= 0, 1, -1)
# 生成类别1的数据
num_samples_class1 = 30
mean_class1 = [1, 2]
cov_class1 = [[1, 0.5], [0.5, 1]]
class1_data = np.random.multivariate_normal(mean_class1, cov_class1, num_samples_class1)
# 生成类别0的数据
num_samples_class0 = 30
mean_class0 = [7, 8]
cov_class0 = [[1, -0.5], [-0.5, 1]]
class0_data = np.random.multivariate_normal(mean_class0, cov_class0, num_samples_class0)
# 合并训练数据
X_train = np.vstack((class1_data, class0_data))
y_train = np.hstack((np.ones(num_samples_class1), np.zeros(num_samples_class0)))
# 特征缩放
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
# 训练SVM模型
svm_model = SVM(learning_rate=0.001, lambda_param=0.01, n_iters=1000)
svm_model.fit(X_train, y_train)
# 生成类别1的测试数据
num_samples_test_class1 = 10
mean_test_class1 = [1, 2]
cov_test_class1 = [[1, 0.5], [0.5, 1]]
test_data_class1 = np.random.multivariate_normal(mean_test_class1, cov_test_class1, num_samples_test_class1)
y_test_class1 = np.ones(num_samples_test_class1)
# 生成类别0的测试数据
num_samples_test_class0 = 10
mean_test_class0 = [7, 8]
cov_test_class0 = [[1, -0.5], [-0.5, 1]]
test_data_class0 = np.random.multivariate_normal(mean_test_class0, cov_test_class0, num_samples_test_class0)
y_test_class0 = np.ones(num_samples_test_class0) # 改为全是1
# 合并测试数据
X_test = np.vstack((test_data_class1, test_data_class0))
y_test = np.hstack((y_test_class1, y_test_class0))
# 特征缩放
X_test = scaler.transform(X_test)
# 预测
predictions = svm_model.predict(X_test)
# 评估模型
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
accuracy = accuracy_score(y_test, predictions)
conf_matrix = confusion_matrix(y_test, predictions)
class_report = classification_report(y_test, predictions)
print(f"准确率: {accuracy:.2f}")
print("混淆矩阵:")
print(conf_matrix)
print("分类报告:")
print(class_report)
# 可视化结果,包括训练数据、测试数据和决策边界
def visualize_svm():
def get_hyperplane_value(x, w, b, offset):
return (-w[0] * x + b + offset) / w[1]
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
plt.scatter(X_train[:, 0], X_train[:, 1], marker='o', c=y_train)
plt.scatter(X_test[:, 0], X_test[:, 1], marker='x', c=predictions)
x0_1 = np.amin(X_train[:, 0])
x0_2 = np.amax(X_train[:, 0])
x1_1 = get_hyperplane_value(x0_1, svm_model.w, svm_model.b, 0)
x1_2 = get_hyperplane_value(x0_2, svm_model.w, svm_model.b, 0)
x1_1_m = get_hyperplane_value(x0_1, svm_model.w, svm_model.b, -1)
x1_2_m = get_hyperplane_value(x0_2, svm_model.w, svm_model.b, -1)
x1_1_p = get_hyperplane_value(x0_1, svm_model.w, svm_model.b, 1)
x1_2_p = get_hyperplane_value(x0_2, svm_model.w, svm_model.b, 1)
ax.plot([x0_1, x0_2], [x1_1, x1_2], 'k')
ax.plot([x0_1, x0_2], [x1_1_m, x1_2_m], 'r--')
ax.plot([x0_1, x0_2], [x1_1_p, x1_2_p], 'r--')
x1_min = np.amin(X_train[:, 1])
x1_max = np.amax(X_train[:, 1])
ax.set_ylim([x1_min - 3, x1_max + 3])
plt.legend()
plt.show()
visualize_svm()
六、总结
通过本文的介绍,我们从零实现了一个线性支持向量机(SVM),并使用随机生成的数据进行了训练、预测和评估。SVM是一种强大的分类算法,能够有效地找到最优超平面,将不同类别的数据分开。希望这篇文章能够帮助你更好地理解SVM的工作原理和实现方法。