机器学习练习六:用SKLearn实现支持向量机(SVM)

1,869

在本练习中,我们将从一些简单的2D数据集开始使用SVM来查看它们的工作原理。

1.线性核函数(Linear Kernel)SVM

顾名思义,基于线性核函数的SVM主要是用来实现线性决策边界的分类问题的。

1.1 原始数据展示

# 读取数据
mat_data = sio.loadmat('./data/ex6data1.mat')
data = pd.DataFrame(mat_data['X'], columns=['x1', 'x2'])
data['y'] = mat_data['y']

数据展示函数:

def show_data(data):    
    # 展示数据
    # 布尔值索引data['Admitted'].isin([1]):False,True,False...
    positive = data[data['y'].isin([1])]
    negative = data[data['y'].isin([0])]
    # 子画布
    fig, ax = plt.subplots(figsize=(8,6))
    ax.scatter(positive['x1'], positive['x2'], s=50, c='g', marker='o', label='T')
    ax.scatter(negative['x1'], negative['x2'], s=50, c='r', marker='x', label='F')
    ax.legend()  # 标签
    ax.set_xlabel('x1')
    ax.set_ylabel('x2')
    ax.set_title('Source Data')
    plt.show()

数据展示:

可以看到这是一个线性决策边界的简单数据集,并且在(0.2,4.2)位置有一个异常点,下面我们将探索SVM中的超参数C(可以理解为正则化超参数λ的倒数)的值和这个异常点对决策边界造成的影响。

1.2 观察参数C改变时对模型带来的影响

C = \frac{1}{\lambda},λ是正则化超参数。λ越大即C越小时,模型bias越小,方差越大。

当C=1时,画出决策边界

训练模型:

# 当参数C=1时
svc1 = sklearn.svm.LinearSVC(C=1, loss='hinge')
svc1.fit(data[['x1', 'x2']], data['y'])
svc1.score(data[['x1', 'x2']], data['y'])

画出决策边界:

# 计算样本点到决策边界的函数距离
data['SVM1 Boundary'] = svc1.decision_function(data[['x1', 'x2']])
# 画出决策边界
fig, ax = plt.subplots(figsize=(8,6))
ax.scatter(data['x1'], data['x2'], s=50, c=data['SVM1 Boundary'], cmap='RdBu')
ax.set_title('SVM (C=1) Decision Boundary')
plt.show()

结果:

可以看出模型较好的被分割开了,并且异常点距离决策边界较远,几乎没有影响到模型的拟合。

当C=100时,画出决策边界

训练模型:

# 当参数C=100时
svc100 = sklearn.svm.LinearSVC(C=100, loss='hinge')
svc100.fit(data[['x1', 'x2']], data['y'])
svc100.score(data[['x1', 'x2']], data['y'])

画出决策边界:

# 计算样本点到决策边界的函数距离
data['SVM100 Boundary'] = svc100.decision_function(data[['x1', 'x2']])
# 画出决策边界
fig, ax = plt.subplots(figsize=(8,6))
ax.scatter(data['x1'], data['x2'], s=50, c=data['SVM100 Boundary'], cmap='RdBu')
ax.set_title('SVM (C=100) Decision Boundary')
plt.show()

结果:

可以看出决策边界收到了异常点很大的影响,模型过拟合。

2.高斯核函数SVM

在机器学习中,(高斯)径向基函数核(英语:Radial basis function kernel),或称为RBF核,是一种常用的核函数。可以理解为对原特征值做相似度转换,它是支持向量机分类中最为常用的核函数。数学公式如下:

我们将用它实现非线性决策边界的拟合。

2.1 数据展示

2.2 模型训练

svc = sklearn.svm.SVC(C=100, kernel='rbf', gamma=10, probability=True)
# 训练模型
svc.fit(data2[['x1', 'x2']], data2['y'])
svc.score(data2[['x1', 'x2']], data2['y'])

绘制决策边界:

# 返回ndarray,shape(n_samples, n_classes),每列对应样本为该类型的概率
# 绘制边界只取其中一列即可
predict_prob = svc.predict_proba(data2[['x1', 'x2']])[:, 1]
# 绘制决策边界
fig, ax = plt.subplots(figsize=(8,6))
ax.scatter(data2['x1'], data2['x2'], s=30, c=predict_prob, cmap='Reds')
plt.show()

结果:

可以看到基于高斯核的SVM对非线性决策边界的拟合度还是很好的。

3.手动交叉验证和sklearn网格搜索

使用交叉验证方法寻找哪一对(C, σ)参数组合是最优的。其中σ是高斯核函数中的参数,当σ较大时,模型向高bias,低方差方向发展。

3.1 数据展示和参数组合

数据展示:

参数组合:

设置了8*8=64组不同的参数组合。

# 设置可能的参数值
paras = [0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30]
# 组合参数
combine = []
for C in paras:
    combine += [(C, sigma) for sigma in paras]

3.2 提取最优参数

计算交叉验证准确度:

acc = []
# 计算交叉验证准确度
for C, sigma in combine:
    svc = sklearn.svm.SVC(C=C, gamma=sigma)
    svc.fit(data3_train[['x1', 'x2']], data3_train['y'])
    acc.append(svc.score(data3_cv[['x1', 'x2']], data3_cv['y']))
# 提取最佳参数
best_para = combine[np.argmax(acc)]
best_para

最优值为acc=0.965,best_para=(3,30)。

绘制决策边界:

注意这里绘制的是训练集的决策边界。

svc = sklearn.svm.SVC(C=best_para[0], gamma=best_para[1], probability=True)
svc.fit(data3_train[['x1', 'x2']], data3_train['y'])
# 返回ndarray,shape(n_samples, n_classes),每列对应样本为该类型的概率
# 绘制边界只取其中一列即可
predict_prob = svc.predict_proba(data3_train[['x1', 'x2']])[:, 1]
# 绘制决策边界
fig, ax = plt.subplots(figsize=(8,6))
ax.scatter(data3_train['x1'], data3_train['x2'], s=30, c=predict_prob, cmap='Reds')
plt.show()

结果:

使用F1-score评估模型效果:

验证集的准确度。

# F1-score评估
y_pred = svc.predict(data3_cv[['x1', 'x2']])
print(metrics.classification_report(data3_cv['y'], y_pred))

3.3 使用网格搜索实现交叉验证

代码:

# sklearn GridSearchCV (网格搜索)
# 这里的参数名称需要和svm.SVC()中保持一致,否则会报错
parameters = {'C': paras, 'gamma': paras}
svc = svm.SVC()
# if n_jobs=-1,all cpus are used.
cscv = GridSearchCV(svc, parameters, n_jobs=-1)
cscv.fit(data3_train[['x1', 'x2']], data3_train['y'])

结果是best_paras={'C': 10, 'gamma': 30}, acc=0.9004739336492891。

这里与手动交叉验证的结果不同的原因是网格搜索时一部分训练集变成了验证集,即没有使用完整的训练集训练。

F1-score: