机器学习——支持向量机(垃圾邮件过滤器)
在本实验中,将使用支持向量机(SVMs)来构建器。
在本实验中,将使用支持向量机(SVMs)来构建垃圾邮件分类器。
1 支持向量机
我们将从一些简单的2D数据集开始使用SVM来查看它们的工作原理。 然后,我们将对一组原始电子邮件进行一些预处理工作,并使用SVM在处理的电子邮件上构建分类器,以确定它们是否为垃圾邮件。
1.1 数据集示例1
从一个可以由线性边界分隔的二维数据集开始。在这个数据集中,正例子(用+表示)和负例子(用o表示)的位置表明可以由线性边界分割。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
from sklearn import svm
import seaborn as sns
def load_mat(path):
data=loadmat(path)
X=data['X']
y=data['y']
return X,y
path="./data/ex6data1.mat"
X,y=load_mat(path)
def plot_data(X,y,fig,ax):
data=pd.DataFrame(X,columns=['X1','X2'])
data['y']=y
positive=data[data['y'].isin([1])]
negative=data[data['y'].isin([0])]
ax.scatter(positive['X1'],positive['X2'],s=30,marker='+',c='black',label='Positive')
ax.scatter(negative['X1'],negative['X2'],s=30,marker='o',c='orange',label='Negative')
ax.set_xlabel('X1')
ax.set_ylabel('X2')
ax.legend()
fig,ax=plt.subplots(figsize=(8,6))
plot_data(X, y,fig,ax)
plt.title("Example Dataset 1")
plt.show()
可视化分类边界
def plot_boundary(svc,X,y,C,diff=10**-3):
x1,x2=decision_boundary(svc,X,diff)
fig,ax=plt.subplots(figsize=(8,6))
plot_data(X, y,fig,ax)
ax.scatter(x1,x2,s=5,c='blue',label='Boundary')
plt.title("SVM Decision Boundary with C = {} (Example Dataset 1)".format(C))
plt.show()
def decision_boundary(svc,X,diff=10**-3):
x1_min,x1_max=X[:,0].min(),X[:,0].max()
x2_min,x2_max=X[:,1].min(),X[:,1].max()
x1=np.linspace(x1_min,x1_max,2000)
x2=np.linspace(x2_min,x2_max,2000)#将整个平面分割为2000*2000的小方格,如果后面感觉运行太久可以适当缩小到500
cordinates = [(x, y) for x in x1 for y in x2]
x1, x2 = zip(*cordinates)
c_val=pd.DataFrame({'x1':x1,'x2':x2})
c_val['cval']=svc.decision_function(c_val[['x1','x2']])#对所有的小方格的:预测样本的置信度得分(接近0的就是分割边界)
decision=c_val[np.abs(c_val['cval'])<diff]
return decision.x1,decision.x2
当C=1时,发现SVM将决策边界放在两个数据集之间的间隙中,并将最左边的数据点错误分类。
plot_boundary(svc,X,y,1,10**-3)

svc2 = svm.SVC(C=100,kernel='linear')
svc2
SVC(C=100, kernel='linear')
svc2.fit(X,y.flatten())
svc2.score(X,y.flatten())
1.0
当C=100时,发现SVM对每个数据都正确分类,但是该决策边界与数据匹配并不自然。
plot_boundary(svc2,X,y,100,10**-3)

当C比较小时模型对错误分类的惩罚较小,比较宽松,允许一定的错误分类存在,间隔较大。当C比较大时模型对错误分类的惩罚较大,比较严格,错误分类少,间隔较小。
1.2 带有高斯核的SVM
高斯核的公式如下所示:
def GaussKernel(x1,x2,sigma):
return np.exp(-np.sum((x1-x2)**2)/(2*sigma**2))
x1,x2,sigma=np.array([1,2,1]),np.array([0,4,-1]),2
GaussKernel(x1, x2, sigma)
0.32465246735834974
1.2.1 数据集示例2
接下来,我们将检查另一个数据集,这次用非线性决策边界。
path="./data/ex6data2.mat"
X2,y2=load_mat(path)
fig,ax=plt.subplots(figsize=(8,6))
plot_data(X2,y2,fig,ax)
plt.title("Example Dataset 2")
plt.show()
上面已经正确地实现了高斯核函数GaussKernel,接下来将继续在这个数据集上用高斯核训练SVM。下图绘制了具有高斯核的SVM所找到的决策边界,该决策边界能够正确地分离大多数正负例子,并且很自然地遵循了数据集的轮廓。
sigma=0.1
gamma=np.power(sigma,-2)/2
clf=svm.SVC(C=1,kernel='rbf',gamma=gamma)
model=clf.fit(X2, y2.flatten())
clf.score(X2, y2.flatten())
0.9895712630359212
dx1,dx2=decision_boundary(model,X2,diff=0.01) #找到决策边界的点
fig,ax=plt.subplots(figsize=(8,6))
plot_data(X2, y2, fig, ax)
ax.scatter(dx1,dx2,s=5)
plt.title("SVM (Gaussian Kernel) Decision Boundary (Example Dataset 2)")
plt.show()

1.2.2 数据集示例3
在这部分实验中,将进了解关于如何使用具有高斯核的SVM。首先绘制样例数据集3中的数据分布图:
def load_mat3(path):
data=loadmat(path)
X=data['X']
y=data['y']
Xval=data['Xval']
yval=data['yval']
return X,y,Xval,yval
path="./data/ex6data3.mat"
X3,y3,X3val,y3val=load_mat3(path)
fig,ax=plt.subplots(figsize=(8,6))
plot_data(X3,y3,fig,ax)
plt.title("Example Dataset 3")
plt.show()

第3个数据集给出了训练集和验证集,并且基于验证集的性能来为SVM模型找到最优超参数。对于C , σ C,\sigmaC,σ,有候选值( 0.01 , 0.03 , 0.1 , 0.3 , 1 , 3 , 10 , 30 ) 。尝试上面列出的C和σ \sigmaσ的8 个值中的每个,最终将训练和评估(在交叉验证集上)共64个不同的模型。
C_values=[0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30]
sigma_values=[0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30]
best_score=0
best_params={'C':None,'sigma':None}
for C in C_values:
for sigma in sigma_values:
clf=svm.SVC(C=C,kernel='rbf',gamma=sigma)
model=clf.fit(X3, y3.flatten())
score=clf.score(X3val, y3val.flatten())
if score>best_score:
best_score=score
best_params['C'],best_params['sigma']=C,sigma
best_score,best_params
clf=svm.SVC(C=best_params['C'],kernel='rbf',gamma=best_params['sigma'])
model=clf.fit(X3, y3.flatten())
clf.score(X3val, y3val.flatten())
根据上面的代码找到了最佳参数C和sigma,SVM得到了下图的决策边界。
dx1,dx2=decision_boundary(model,X3,diff=0.005) #找到决策边界的点
fig,ax=plt.subplots(figsize=(8,6))
plot_data(X3, y3, fig, ax)
ax.scatter(dx1,dx2,s=5)
plt.title("SVM (Gaussian Kernel) Decision Boundary (Example Dataset 3)")
plt.show()

2 垃圾邮件分类
接下来将训练一个分类器来分类给定的电子邮件(x)是垃圾邮件(y=1)还是非垃圾邮件(y=0),需要将每个电子邮件转换为一个特征向量x ∈ R n
2.1预处理电子邮件
with open("./data/emailSample1.txt","r") as f:
email=f.read()
print(email)
所以接下来,需要对读取到的邮件作如下处理:
Lower-casing: 把整封邮件转化为小写
Stripping HTML: 移除所有HTML标签,只保留内容
Normalizing URLs: 将所有的URL替换为字符串httpaddr
Normalizing Email Addresses: 所有的地址替换为emailaddr
Normalizing Dollars: 所有dollar符号($)替换为dollar
Normalizing Numbers: 所有数字替换为number
Word Stemming(词干提取): 将所有单词还原为词源。例如discount, discounts, discounted and discounting都替换为discount
Removal of non-words: 移除所有非文字类型,所有的空格(tabs, newlines, spaces)调整为一个空格
import re
def processEmail(email):
email=email.lower()
email=re.sub('<[^<>]>',' ',email)
email=re.sub('(http|https)://[^\s]*','httpaddr',email)
email=re.sub('[^\s]+@[^\s]+','emailaddr',email)
email=re.sub('[\$]+','dollar',email)
email=re.sub('[\d]+','number',email)
return email
processEmail(email)
接下来提取词干并且去除非字符内容。
import nltk, nltk.stem.porter # 英文分词算法
def email_TokenList(email):
stemmer=nltk.stem.porter.PorterStemmer()
email=processEmail(email)
tokens=re.split('[ \@\$\/\#\.\-\:\&\*\+\=\[\]\?\!\(\)\{\}\,\'\"\>\_\<\;\%]',email)
tokenList=[]
for token in tokens:
token=re.sub('[^a-zA-Z0-9]','',token)
stemmed = stemmer.stem(token)#提取词根
if(len(token)):
tokenList.append(stemmed)
return tokenList
在对电子邮件进行预处理后,得到电子邮件的单词列表(如下)。接下来选择在分类器中使用哪些词,以及需要忽略哪些词。
email_TokenList(email)
2.1.1 词汇表
在词汇表中,将预处理电子邮件中的每个单词映射到一个单词索引列表中,其中包含词汇和单词索引。
完成processEmail函数,传入字符串str(处理过的电子邮件中的单个单词),然后在词汇表中查找该单词,看它是否存在于词汇表列表中,如果存在,将单词的索引添加到单词索引变量中;如果不存在,可以跳过这个词。
def processEmail_index(email,vocab):
token=email_TokenList(email)
index=[i for i in range(len(vocab)) if vocab[i] in token]
return index
vocab = pd.read_table('./data/vocab.txt',names=['words'])
vocab=vocab.values
processEmail_index(email, vocab)
2.2 从电子邮件中提取特征
def emailFeatures(email):
df = pd.read_table('./data/vocab.txt',names=['words'])
vocab=df.values
vector=np.zeros(len(vocab))
vocal_index=processEmail_index(email,vocab)
for i in vocal_index:
vector[i]=1
return vector
vector=emailFeatures(email)
print('vector had length {} and {} non-zero entries'.format(len(vector), int(vector.sum())))
2.3 垃圾邮件分类-SVM
spamTrain.mat包含4000个垃圾邮件和非垃圾邮件的训练数据,spamTest.mat包含1000 个测试数据。每个原始电子邮件使用processEmail函数和emailFeatures函数处理,并转换为一个向量x ( i ) ∈ R 1899 。
加载数据集之后,训练SVM进行分类:垃圾邮件y = 1 y=1y=1和非垃圾邮件(y=0)。训练完成,分类器得到的训练准确率约为99.8 % ,测试准确率约为98.5 % 。
path="./data/spamTrain.mat"
X,y=load_mat(path)
path="./data/spamTest.mat"
data=loadmat(path)
Xtest,ytest=data["Xtest"],data["ytest"]
X.shape,y.shape,Xtest.shape,ytest.shape
((4000, 1899), (4000, 1), (1000, 1899), (1000, 1))
每个文档已经转换为一个向量,其中1899对应于词汇表中的1899个单词。 它们的值为二进制,表示文档中是否存在单词。
clf=svm.SVC(C=0.1,kernel='linear')
clf.fit(X,y)
clf.score(X,y)
D:\Python\lib\site-packages\sklearn\utils\validation.py:63: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
return f(*args, **kwargs)
0.99825
clf.score(Xtest,ytest)
0.989
更多推荐


所有评论(0)