700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 机器学习练习 6 - Support Vector Machines(支持向量机)

机器学习练习 6 - Support Vector Machines(支持向量机)

时间:2023-05-28 22:03:48

相关推荐

机器学习练习 6 - Support Vector Machines(支持向量机)

机器学习练习 6 - Support Vector Machines(支持向量机)

Introduction

在本实验中,将使用支持向量机(SVMs)来构建垃圾邮件分类器。

1 Support Vector Machines(支持向量机)

在本实验的前半部分,将使用包含各种2D2D2D数据集的支持向量机(SVMs)。对这些数据集进行实验可以更加直观地了解SVMs的工作原理,以及如何在SVMs上使用高斯核。在本实验的后半部分,将使用SVMs来构建垃圾邮件分类器。

1.1 Example Dataset 1

从一个可以由线性边界分隔的二维数据集开始。在这个数据集中,正例子(用+表示)和负例子(用o表示)的位置表明可以由线性边界分割。但是,注意,在最左边有一个异常的正例子+,大约是(0.1,4.1)(0.1,4.1)(0.1,4.1)。在本实验中,将看到这种特殊的例子如何影响SVM的决策边界。

import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom scipy.io import loadmatfrom sklearn import svmimport 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']=ypositive=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()

在本部分的实验中,将尝试对SVMs使用不同的C参数值。C参数是一个正数,它控制了对错误分类的训练数据的惩罚,其中C较大,说明SVM尝试正确地分类所有的数据,C类似于1λ\frac{1}{\lambda}λ1​,λ\lambdaλ是之前用于逻辑回归的正则化参数。

大多数SVM库会自动添加额外的特征,比如:x0,θ0x_0,\theta_0x0​,θ0​,所以无需手动添加。

svc = svm.SVC(C=1, kernel='linear')svc

SVC(C=1, kernel='linear')

svc.fit(X,y.flatten())svc.score(X,y.flatten())

0.9803921568627451

可视化分类边界

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的小方格,如果后面感觉运行太久可以适当缩小到500cordinates = [(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 with Gaussian Kernels

在这节实验,将使用SVMs来进行非线性分类并且在非线性可分的数据集上使用高斯核SVMs

1.2.1 Gaussian Kernel(高斯核)

为了用SVM找到非线性决策边界,需要实现一个高斯核。可以把高斯核看作一个相似度函数,用来度量一对数据(x(i),x(j))(x^{(i)},x^{(j)})(x(i),x(j))之间的"距离"。高斯核有一个参数σ\sigmaσ,决定了相似性下降至000的速度。

高斯核定义如下:

这里使用sklearn自带的svm中的核函数即可:

def GaussKernel(x1,x2,sigma):return np.exp(-np.sum((x1-x2)**2)/(2*sigma**2))

使用示例数据x1=[1,2,1],x2=[0,4−1],σ=2x_1=[1,2,1],x_2=[0,4-1],\sigma=2x1​=[1,2,1],x2​=[0,4−1],σ=2运行的结果约为0.3246520.3246520.324652。

x1,x2,sigma=np.array([1,2,1]),np.array([0,4,-1]),2GaussKernel(x1, x2, sigma)

0.32465246735834974

1.2.2 Example Dataset 2

接下来的实验中的数据的分布图如下图所示,可以观察到,该数据集的正例子和负例子之间没有线性决策边界。不过,通过使用高斯核SVM,将能够学习到一个非线性决策边界,它在该数据集表现得相当好。

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.1gamma=np.power(sigma,-2)/2clf=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.3 Example Dataset 3

在这部分实验中,将进了解关于如何使用具有高斯核的SVM。首先绘制样例数据集333中的数据分布图:

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()

第333个数据集给出了训练集和验证集,并且基于验证集的性能来为SVM模型找到最优超参数。对于C,σC,\sigmaC,σ,有候选值(0.01,0.03,0.1,0.3,1,3,10,30)(0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30)(0.01,0.03,0.1,0.3,1,3,10,30)。尝试上面列出的CCC和σ\sigmaσ的888个值中的每个,最终将训练和评估(在交叉验证集上)共646464个不同的模型。

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=0best_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=scorebest_params['C'],best_params['sigma']=C,sigmabest_score,best_params

(0.965, {'C': 3, 'sigma': 30})

clf=svm.SVC(C=best_params['C'],kernel='rbf',gamma=best_params['sigma'])model=clf.fit(X3, y3.flatten())clf.score(X3val, y3val.flatten())

0.965

根据上面的代码找到了最佳参数CsigmaSVM得到了下图的决策边界。

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 Spam Classification(垃圾邮件分类)

在本部分的实验中,将使用SVMs来构建垃圾邮件过滤器。

接下来将训练一个分类器来分类给定的电子邮件(x)是垃圾邮件(y=1)还是非垃圾邮件(y=0),需要将每个电子邮件转换为一个特征向量x∈Rnx \in R^nx∈Rn

2.1 Preprocessing Emails(预处理电子邮件)

在开始执行机器学习任务之前,查看电子邮件的类型,下图显示了一个包含一个URL、电子邮件地址(在末尾)、数字和美元金额的电子邮件。虽然许多电子邮件包含类似实体(例如,数字、URL、电子邮件地址),但特定的实体(例如,特定的URL或特定的金额)在几乎每封电子邮件中都是不同的。因此,在处理电子邮件时经常使用的一种方法是"规范化"这些值,这样所有的url都处理成相同的,所有数字都处理成相同的。例如,用字符串httpaddr替换电子邮件中的URL,表示存在一个URL。这样做可以让垃圾邮件分类器根据是否存在URL来做出分类决定。这可以提高垃圾邮件分类器的性能,因为垃圾邮件发送者通常会将URL随机化,因此在新的垃圾邮件片段中再次看到特定出现过的URL的几率非常小。

with open("./data/emailSample1.txt","r") as f:email=f.read()print(email)

> Anyone knows how much it costs to host a web portal ?>Well, it depends on how many visitors you're expecting.This can be anywhere from less than 10 bucks a month to a couple of $100. You should checkout / or perhaps Amazon EC2 if youre running something big..To unsubscribe yourself from this mailing list, send an email to:groupname-unsubscribe@

所以接下来,需要对读取到的邮件作如下处理:

Lower-casing: 把整封邮件转化为小写Stripping HTML: 移除所有HTML标签,只保留内容Normalizing URLs: 将所有的URL替换为字符串httpaddrNormalizing Email Addresses: 所有的地址替换为emailaddrNormalizing Dollars: 所有dollar符号($)替换为dollarNormalizing Numbers: 所有数字替换为numberWord Stemming(词干提取): 将所有单词还原为词源。例如discount,discounts,discountedanddiscounting都替换为discountRemoval of non-words: 移除所有非文字类型,所有的空格(tabs,newlines,spaces)调整为一个空格

将上面的邮件进行上述的预处理,结果如下图所示。虽然预处理留下了单词片段和非单词,但这种形式对于执行特征提取更容易处理。

import redef 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)

"> anyone knows how much it costs to host a web portal ?\n>\nwell, it depends on how many visitors you're expecting.\nthis can be anywhere from less than number bucks a month to a couple of dollarnumber. \nyou should checkout httpaddr or perhaps amazon ecnumber \nif youre running something big..\n\nto unsubscribe yourself from this mailing list, send an email to:\nemailaddr\n\n"

接下来提取词干并且去除非字符内容。

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)

['anyon','know','how','much','it','cost','to','host','a','web','portal','well','it','depend','on','how','mani','visitor','you','re','expect','thi','can','be','anywher','from','less','than','number','buck','a','month','to','a','coupl','of','dollarnumb','you','should','checkout','httpaddr','or','perhap','amazon','ecnumb','if','your','run','someth','big','to','unsubscrib','yourself','from','thi','mail','list','send','an','email','to','emailaddr']

2.1.1 Vocabulary List

在本节实验中,只选择最经常出现的单词作为单词集(词汇表)。由于在训练集中很少出现的单词一般也只出现在少数电子邮件中,它们可能会导致模型过拟合。词汇表vocab.txt里面存储了在实际中经常使用的单词,共189918991899个。(在实践中,词汇表大约有1万到5万字)。在词汇表中,将预处理电子邮件中的每个单词映射到一个单词索引列表中,其中包含词汇和单词索引。

完成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.valuesprocessEmail_index(email, vocab)

[70,85,88,161,180,237,369,374,430,478,529,530,591,687,789,793,798,809,882,915,944,960,991,1001,1061,1076,1119,1161,1170,1181,1236,1363,1439,1476,1509,1546,1662,1675,1698,1757,1821,1830,1892,1894,1895]

2.2 Extracting Features from Emails(从电子邮件中提取特征)

接下来实现功能提取,将每个电子邮件转换为一个RnR^nRn的向量。电子邮件的特性xi∈{0,1}x_i\in \lbrace0,1\rbracexi​∈{0,1}表示第iii个单词是否出现在电子邮件中,如果第iii个单词在电子邮件中,那么xi=1x_i=1xi​=1;否则xi=0x_i=0xi​=0。因此,一个电子邮件中的特征将看起来就像:

编写函数emailFeatures完成上述的功能,运行代码,可以看到特征向量的长度为189918991899并且有454545个非零项。

def emailFeatures(email):df = pd.read_table('./data/vocab.txt',names=['words'])vocab=df.valuesvector=np.zeros(len(vocab))vocal_index=processEmail_index(email,vocab)for i in vocal_index:vector[i]=1return vector

vector=emailFeatures(email)print('vector had length {} and {} non-zero entries'.format(len(vector), int(vector.sum())))

vector had length 1899 and 45 non-zero entries

2.3 Training SVM for Spam Classification(垃圾邮件分类-SVM)

spamTrain.mat包含400040004000个垃圾邮件和非垃圾邮件的训练数据,spamTest.mat包含100010001000个测试数据。每个原始电子邮件使用processEmail函数和emailFeatures函数处理,并转换为一个向量x(i)∈R1899x^{(i)}\in R^{1899}x(i)∈R1899。

加载数据集之后,训练SVM进行分类:垃圾邮件y=1y=1y=1和非垃圾邮件(y=0)。训练完成,分类器得到的训练准确率约为99.8%99.8\%99.8%,测试准确率约为98.5%98.5\%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))

每个文档已经转换为一个向量,其中189918991899对应于词汇表中的189918991899个单词。 它们的值为二进制,表示文档中是否存在单词。

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

2.4 Top Predictors for Spam

为了更好地理解垃圾邮件分类器是如何工作的,可以检查参数,看看分类器认为哪些词最能预测到垃圾邮件。接下来在分类器中找到最大的参数(正数),并显示相应的单词。因此,如果电子邮件包含"保证"、“删除”、"美元"和"价格"等词,它很可能被归类为垃圾邮件。

kw=np.eye(1899)spam_val=pd.DataFrame({'index':range(1899)})spam_val['is_spam']=clf.decision_function(kw)

spam_val['is_spam'].describe()

count 1899.000000mean 0.184175std 0.086875min -0.41880425% 0.14132550% 0.18538475% 0.225760max 0.686941Name: is_spam, dtype: float64

decision=spam_val[spam_val['is_spam']>0.4]decision

vocab = pd.read_csv('./data/vocab.txt',names=['index','words'],sep='\t')vocab.head()

spam_voc=vocab.loc[list(decision['index'])]spam_voc

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。