吴恩达机器学习作业六:SVM
知识点回顾:
1 Support Vector Machines
在本练习的前半部分,您将使用支持向量机(SVM)和各种示例2D数据集。对这些数据集进行实验将帮助您获得支持向量机如何工作以及如何将高斯核用于支持向量机。在下半部分练习中,您将使用支持向量机构建垃圾邮件分类器。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
from sklearn import svm
1.1 Example Dataset 1
我们将从一个二维示例数据集开始,该数据集可以用线性边界分隔。在这个数据集中,正例(用+)和负例(用o表示)的位置表明了由间隙表示的自然分离。但是,请注意,在最左边大约(0.1,4.1)处有一个异常值正值示例+。作为本练习的一部分,您还将看到此异常值如何影响SVM决策边界。
path = 'D:编程/ex6data1.mat'
raw_data = loadmat(path)
data = pd.DataFrame(raw_data['X'],columns =['X1','X2'] )
data['y'] = raw_data['y']
def plot_data():
positive = data[data.y.isin(['1'])]
negetive = data[data.y.isin(['0'])]
plt.figure(figsize=(6,6),dpi = 80)
plt.xlabel('X1')
plt.ylabel('X2')
plt.scatter(positive['X1'],positive['X2'],marker= '+',label = 'positive')
plt.scatter(negetive['X1'],negetive['X2'],marker="o",label = 'negetive')
plt.legend()
plt.show()
plot_data()
在本部分练习中,您将尝试在支持向量机中使用不同的C参数值。也就是说,C参数是一个正值,用于控制错误分类训练示例的惩罚。一个较大的C告诉支持向量机尝试正确分类所有的例子。C的作用类似于1/λ, λ 是我们以前用于逻辑回归的正则化参数。
# 配置LinearSVC参数
svc = svm.LinearSVC(C=1,loss='hinge',max_iter=20000)
# 将配置好的模型应用于数据集
clf = svc.fit(data[['X1','X2']],data['y'])
score = svc.score(data[['X1','X2']],data['y'])
print(score)
# 0.9803921568627451
# 画出决策边界
def plot_desicion_boundary(clf,x1min,x1max,x2min,x2max):
u = np.linspace(x1min,x1max,500)
v = np.linspace(x2min,x2max,500)
# 转为网格(500*500)
x,y = np.meshgrid(u,v)
# 因为predict函数输入必须是二维数组
# np.c_按行连接两个矩阵
z = clf.predict(np.c_[x.flatten(),y.flatten()])
z = z.reshape(x.shape) # 重新转为网格
plt.contour(x,y,z,1,colors = 'b')
plt.title('The Decision Boundary')
X = raw_data['X']
y = raw_data['y'].flatten()
plot_data(data)
plot_desicion_boundary(clf,np.min(X[:,0]),np.max(X[:,0]),np.min(X[:,1]),np.max(X[:,1]))
plt.show()
**SVM Decision Boundary with C = 1 **
在此数据集上尝试不同的C值。具体来说,将脚本中C的值更改为C=100,然后再次运行SVM训练。当C=100时,你会发现SVM现在可以正确地对每个示例进行分类,但其决策边界似乎并不适合数据。
**SVM Decision Boundary with C = 100 **
C对应正则化的λ,C = 1/λ, C越大越容易过拟合。
当C=1时,支持向量机将决策边界放在两个数据集之间的间隙中,并对最左侧的数据点进行了错误分类。
1.2 SVM with Gaussian Kernels
线性不可分知识点回顾:
在这部分练习中,您将使用支持向量机进行非线性分类。特别是,您将在不可线性分离的数据集上使用具有高斯核的支持向量机。
1.2.1 Gaussian Kernel
为了用支持向量机寻找非线性决策边界,首先需要实现一个高斯核。你可以把高斯核看作是一个相似性函数,用来度量两个例子(x(i),x(j))之间的“距离”。高斯核也被带宽参数σ所参数化,它决定了当示例之间的距离越远时,相似度度量减少(到0)的速度。
高斯核函数定义如下:
完成gaussianKernel函数后,将在提供的两个示例上测试内核函数,您应该看到值为0.324652。
def Gausskernal(xi,xj,sigma):
return np.exp(np.sum(np.power(xi - xj,2)) / (- 2 * sigma ** 2))
xi = np.array([1, 2, 1])
xj = np.array([0, 4, -1])
sigma = 2
print(Gausskernal(xi, xj, sigma))
# 0.32465246735834974
1.2.2 Example Dataset 2
path2 = 'D:编程/ex6data2.mat'
raw_data2 = loadmat(path2)
data2 = pd.DataFrame(raw_data2['X'],columns =['X1','X2'] )
data2['y'] = raw_data2['y']
plot_data(data2)
plt.show()
从图中,您可以看到没有线性决策边界来分隔此数据集的正示例和负示例。然而,通过使用高斯核和支持向量机,您将能够学习一个非线性的决策边界,它可以很好地执行数据集。
# 为测试集2配置参数
svc2 = svm.SVC(C=100, gamma=10, probability=True)
# 将配置好的模型应用于数据集
clf2 = svc2.fit(data2[['X1','X2']],data2['y'])
X_test2 = raw_data2['X']
plot_data(data2)
plot_desicion_boundary(clf2,np.min(X_test2[:,0]),np.max(X_test2[:,0]),np.min(X_test2[:,1]),np.max(X_test2[:,1]))
plt.show()
上图显示了使用高斯核的SVM发现的决策边界。决策边界能够正确地分离大多数正、负样本,并很好地跟踪数据集的轮廓。
1.2.3 Example Dataset 3
在这部分练习中,您将获得有关如何使用带高斯核的支持向量机的更多实用技能。您将在第三个数据集中使用支持向量机和高斯核。在提供的数据集ex6data3.mat中,为您提供了变量X、y、Xval、yval。ex6.m中提供的代码使用从dataset3Params加载的参数,使用训练集(X,y)训练SVM分类器。您的任务是使用交叉验证集Xval,yval来确定最佳的C和σ 要使用的参数。您应该编写任何必要的附加代码来帮助您搜索参数C和σ. 对于C和σ, 我们建议用乘法步骤来尝试数值(例如,0.01、0.03、0.1、0.3、1、3、10、30)。请注意,您应该尝试C和C的所有可能的值对σ (e、 g.,C=0.3和σ = 0.1). 例如,如果您尝试上面列出的8个C值和σ2.您将最终培训和评估(在交叉验证集上)总共8*8=64个不同的模型。
path3 = 'D:编程/ex6data3.mat'
raw_data3 = loadmat(path3)
data3 = pd.DataFrame(raw_data3['X'],columns =['X1','X2'] )
data3['y'] = raw_data3['y']
plot_data(data3)
plt.show()
选择最佳的C和σ 参数
x3,y3 = raw_data3['X'],raw_data3['y']
Xval,yval = raw_data3['Xval'],raw_data3['yval']
# 找最优超参数
cvalues = [0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30]
gammavalues = cvalues
best_score = 0
best_params = {'C':None,'gamma':None}
for c in cvalues:
for gramma in gammavalues:
svc3 = svm.SVC(C=c, gamma=gramma, probability=True)
# 用训练集训练
clf3 = svc3.fit(x3,y3)
# 用验证集优选
score = svc3.score(Xval,yval)
if score > best_score:
best_score = score
best_params['C'] = c
best_params['gamma'] = gramma
print(best_score,best_params)
# 0.965 {'C': 3, 'gamma': 30}
c = best_params['C']
gamma = best_params['gamma']
svc3 = svm.SVC(C=c, gamma=gamma, probability=True)
clf3 = svc3.fit(Xval,yval)
plot_data(data3)
plot_desicion_boundary(clf3,np.min(Xval[:,0]),np.max(Xval[:,1]),np.min(Xval[:,0]),np.max(Xval[:,1]))
plt.show()
实现技巧:在实现交叉验证时,选择最佳的C和σ 参数,您需要评估交叉验证集上的错误。回想一下,对于分类,错误定义为交叉验证示例中分类错误的部分。在Octave/MATLAB中,可以使用mean(double(predictions ~=yval))计算这个误差,其中predictions是一个包含来自SVM的所有预测的向量,yval是交叉验证集的真实标签。可以使用svmPredict函数为交叉验证集生成预测。
2 Spam Classification
如今,许多电子邮件服务都提供垃圾邮件过滤器,能够以高精度将电子邮件分为垃圾邮件和非垃圾邮件。在本部分练习中,您将使用支持向量机构建自己的垃圾邮件过滤器。您将培训一个分类器来分类给定的电子邮件x是垃圾邮件(y=1)还是非垃圾邮件(y=0)。特别是,您需要将每封电子邮件转换为特征向量。本练习的以下部分将引导您了解如何从电子邮件构造这样的特征向量。
在本练习的其余部分中,您将使用脚本ex6 spam.m。本练习包含的数据集基于SpamAssassin公共语料库的一个子集。在本练习中,您将仅使用电子邮件正文(不包括电子邮件标题)。
大致步骤如下:
这里需要用到正则表达式re:
# SVM建立垃圾邮件分类器
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import scipy.io as scio
from sklearn import svm
import re # 正则
from nltk.stem import PorterStemmer # 自然语言处理
2.1 Preprocessing Emails
# 1.邮件预处理
em = 'D:编程/emailSample1.txt'
f= open(em,'r',encoding='utf-8')
email = f.read()
print(email)
f.close()
我们对邮件中替代的部分使用正则表达式,词干提取使用nltk.stem.porter.PorterStemmer()
def preprocess(email):
"""做除了Word Stemming和Removal of non-words的所有处理"""
# 大写转小写
email = email.lower()
# 移除html标签
email = re.sub(r'<.*>','',email)
# 移除url
email = re.sub(r'(http|https)://[^\s]*','httpaddr',email)
# 移除$,解决dollar 和 number 的连接问题
email = re.sub(r'[\$][0-9]+','dollar number',email)
# 移除单个$
email = re.sub(r'\$','dollar number',email)
# 移除数字
email = re.sub(r'[0-9]+', 'number', email)
# 移除邮箱
email = re.sub(r'[^\s]+@[^\s]+','emailaddr',email)
return email
def preprocess2(email):
"""预处理数据 : 提取词干,去除非字符内容"""
stemmer = PorterStemmer()
email = preprocess(email)
# 将邮件分割为单个单词,re.split()可以设置多种分隔符
tokens = re.split('[ \@\$\/\#\.\-\:\&\*\+\=\[\]\?\!\(\)\{\}\,\'\"\>\_\<\;\%]', email)
# 遍历每个分割出来的内容
tokenlist = []
for token in tokens:
#删除任何非字母的字符
token = re.sub('[^a-zA-Z0-9]','',token)
#提取词根
stemmed = stemmer.stem(token)
# 去除空字符串“:里面不包含任何字符
if not len(token):continue
tokenlist.append(stemmed)
return tokenlist
2.1.1 Vocabulary List
def VocabIndex(email,vocab):
"""提取存在单词的索引"""
tokenlist = preprocess2(email)
# index = []
# for i in range(len(vocab)):
# if vocab[i] in tokenlist:
# index.append(i)
index = [i for i in range(len(vocab)) if vocab[i] in tokenlist]
return index
vocab = 'D:编程/vocab.txt'
v= open(em,'r',encoding='utf-8')
vocab = v.read()
v.close()
print(VocabIndex(email,vocab))
2.2 Extracting Features from Emails
def FeatureVector(email):
df = pd.read_table('D:编程/vocab.txt', names=['words'])
vocab = df.values
vector = np.zeros(len(vocab))
voc_index = VocabIndex(email,vocab)
for j in voc_index:
vector[j] = 1
return vector
vector = FeatureVector(email)
print(vector)
print('length of vector = {}\nnum of non-zero = {}'.format(len(vector), int(vector.sum())))
2.3 Training SVM for Spam Classification
读取已经训提取好的特征向量以及相应的标签。分训练集和测试集。
# Training set
mat1 = loadmat('D:编程/spamTrain.mat')
X, y = mat1['X'], mat1['y']
# Test set
mat2 = loadmat('D:编程/spamTest.mat')
Xtest, ytest = mat2['Xtest'], mat2['ytest']
clf = svm.SVC(C=0.1, kernel='linear')
clf.fit(X, y)
2.4 Top Predictors for Spam
# 预测
predTrain = clf.score(X, y)
predTest = clf.score(Xtest, ytest)
print(predTrain, predTest)