目录
1. 学习内容
1. 了解SVM的原理
2. 实现SVM
3. 认识核函数
4. 如何用SVM解决线性回归问题
2. 什么是SVM
要了解什么是SVM,还需要从分类问题中的决策边界不适定问题讲起。我们都知道,一个二分类的模型其决策边界可视化后可以是一个低维度平面或者直线。但是,可以将正样本和负样本完美分隔开的低维平面有很多个,假如每一个平面都对应一个模型,那么我们要如何从中选择最好的那个呢?同一个分类问题可以找到多个决策边界,这就是所谓的不适定问题。这个问题会影响到模型的泛化能力。
逻辑回归模型在解决二分类问题时,其目标函数完全由数据集自身来确定,因此得到的决策边界也是由数据集自身得到的,从而解决了不适定问题。而对于SVM而言,它在解决不适定问题时的做法是:找到一条距离正样本和负样本都最远的直线或平面。更专业一点儿的叫法是边界最大化,边界指的就是决策边界到正负样本的最近距离。之所以要这么规定是因为距离正负样本都最远的决策边界对应的模型容错率更高。
因此,只要我们可以推导出决策边界到正负样本的距离(边界到正样本和负样本的距离是相等的),那么最大化边界问题就变成了一个最优化问题。SVM最优化问题相比于之前的问题都更加复杂,涉及到有限制条件的最优化,对偶化等操作。具体推导过程参考[1]。
3. 硬间隔与软间隔
如果一个二分类问题可以找到一条“完美”的决策边界可以将正负样本完全分隔开,那么这种情况就叫做硬间隔。反之,有时候一些二分类问题无论如何也无法找到它的硬间隔,此时我们就要考虑能否在有噪声的情况下找到一条并不完美但是容错率高的边界。这就是软间隔的情况。软间隔体现在数学推倒上其实就是适当削弱了约束条件。具体过程可以参考[2]。
4. 在sklearn中使用SVM进行分类
import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
iris = datasets.load_iris()
X = iris.data
y = iris.target
X = X[y < 2, :2]
y = y[y < 2]
# 由于最大化边界需要计算距离,因此有必要对数据进行标准化和归一化
standardScaler = StandardScaler()
standardScaler.fit(X)
X = standardScaler.transform(X)
svc = LinearSVC(C = 1e9)
svc.fit(X, y)
def plot_svc_decision_boundary(model, axis):
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
from matplotlib.colors import ListedColormap
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)
# 绘制Margin区域上下两根线
w = model.coef_[0]
b = model.intercept_[0]
# w0 * x0 + w1 * x1 + b = +1
# w0 * x0 + w1 * x1 + b = -1
plot_x = np.linspace(axis[0],axis[1],200)
up_y = -w[0]/w[1] * plot_x - b/w[1] + 1/w[1]
down_y = -w[0]/w[1] * plot_x - b/w[1] - 1/w[1]
up_index = (up_y >= axis[2]) & (up_y <= axis[3])
down_index = (down_y >= axis[2]) & (down_y <= axis[3])
plt.plot(plot_x[up_index], up_y[up_index], color='black')
plt.plot(plot_x[down_index], down_y[down_index], color='black')
plot_svc_decision_boundary(svc, axis=[-3, 3, -3, 3])
plt.scatter(X[y == 0, 0], X[y == 0, 1], color = 'red')
plt.scatter(X[y == 1, 0], X[y == 1, 1], color = 'blue')
plt.show()


5. 核函数
SVM不仅可以解决线性分类问题,非线性的分类问题也一样可以解决。SVM处理非线性数据最典型的思路就是使用多项式的方式:扩充原本数据,制造新的多项式特征[3],而扩充数据特征的函数就叫做核函数。
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.svm import LinearSVC
from sklearn.pipeline import Pipeline
from matplotlib.colors import ListedColormap
X, y = datasets.make_moons(noise = 0.20, random_state = 123)
def PolynomialSVC(degree, C = 1.0):
return Pipeline([
("poly", PolynomialFeatures(degree = degree)),
("std_scaler", StandardScaler()),
("linearSVC", LinearSVC(C = C))
])
poly_svc = PolynomialSVC(degree = 3)
poly_svc.fit(X,y)
def plot_decision_boundary(model, axis):
x0, x1 = np.meshgrid(
np.linspace(axis[0], axis[1], int((axis[1] - axis[0]) * 100)).reshape(-1, 1),
np.linspace(axis[2], axis[3], int((axis[3] - axis[2]) * 100)).reshape(-1, 1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]
y_predict = model.predict(X_new)
zz = y_predict.reshape(x0.shape)
custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])
plt.contourf(x0, x1, zz, linewidth = 5, cmap = custom_cmap)
plot_decision_boundary(poly_svc, axis = [-1.5, 2.5, -1, 1.5])
plt.scatter(X[y == 0, 0], X[y == 0, 1])
plt.scatter(X[y == 1, 0], X[y == 1, 1])
plt.show()

使用不同核函数的SVM相当于把原数据映射到了更高维的空间中,原来的空间无法线性可分的数据在高维空间中就未必仍然线性不可分了。很多人也许会疑惑:核函数会让数据升维,那么算法的复杂度也应该急剧上升,这样做值得吗?实际上,核函数的设计是很有考究的。每一个核函数在处理过数据后,高维中的一些运算实际上是等同于原维度中对数据的计算的。也就是说,时间复杂度的增加是线性的而非指数型的。
关于核函数,更多内容可以参考[4]。
6. 用SVM解决线性回归问题
SVM解决线性回归问题的核心思想是:首先,指定一个最大边界的值,然后要让尽可能多的点落在边界内部。此时,取边界中心线所在的直线作为最后的结果。
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.svm import LinearSVR
from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
boston = datasets.load_boston()
X = boston.data
y = boston.target
X = X[y < 50]
y = y[y < 50]
X = X[:, 5].reshape(-1, 1)
def StandardLinearSVR(epsilon = 0.1):
return Pipeline([
('std_scaler', StandardScaler()),
('linearSVR', LinearSVR(epsilon = epsilon))
])
svr = StandardLinearSVR()
svr.fit(X, y)
predictions = svr.predict(X)
plt.scatter(X, y, c = 'b')
plt.plot(X, predictions, c = 'r')
plt.show()

7. 参考文献
1. https://mp.weixin.qq.com/s/Bv9lqC-Bf9TWUVSwCbPStA
2. https://mp.weixin.qq.com/s/cFCXL_eBs2QbAwMnmwhkTQ
3. https://mp.weixin.qq.com/s/ZGptc2IYSLsX39rVf05Miw