赞
踩
在本练习前半部分,将对各种2D数据集使用支持向量机,通过这些数据集可以更加直观了解支持向量机的工作方式,以及如何在支持向量机中使用高斯核。后半部分,使用支持向量机搭建一个垃圾邮件分类器
这是一个可以由线性边界分隔的数据集,首先我们先对数据进行可视化,可看到大约在左上角有一个异常的正样例,通过这个异常值,我们可以观察SVM决策边界是如何变化
- import numpy as np
- import pandas as pd
- import matplotlib.pyplot as plt
- import seaborn as sb
- from scipy.io import loadmat
- from sklearn import svm
- from jupyterthemes import jtplot # 用于解决坐标无法显示问题
- jtplot.style(theme='chesterish') #选择一个绘图主题
-
- path1 = r'E:/Code/ML/ml_learning/ex6-SVM/data/ex6data1.mat'
- data1 = loadmat(path1)
- X, y = data1['X'],data1['y']
- # X是坐标点,y标签
-
- def plotData(X, y):
- """数据可视化"""
- plt.figure(figsize=(8,5))
- # 将y展开,并用其值区分正负样本,cmap是设置颜色效果
- plt.scatter(X[:,0], X[:,1], c=y.flatten(), cmap='rainbow')
- plt.xlabel('X1')
- plt.ylabel('X2')
- plotData(X,y)


接下来我们调用就不自己写SVM模型代码,而是直接调用sklearn包,需要注意y得展平再传入模型中,训练好模型后就是将决策边界可视化,需要注意的是在这里我们不需要自己额外添加x0=1,模型中会自动添加。
- # 调用模型,训练模型
- # 这里使用列表推导式减少代码行数
- models = [svm.SVC(C, kernel='linear') for C in [1, 100]]
- clfs = [model.fit(X, y.ravel()) for model in models]
- def plotBoundary(clf, X):
- '''画决策边界'''
- x_min, x_max = X[:,0].min()*1, X[:,0].max()*1
- y_min, y_max = X[:,1].min()*1,X[:,1].max()*1
- # 画等高线
- xx, yy = np.meshgrid(np.linspace(x_min, x_max, 500),
- np.linspace(y_min, y_max, 500))# (500,500)
- Z = clf.predict(np.c_[xx.ravel(), yy.ravel()]) #(250000,)
- Z = Z.reshape(xx.shape)
- plt.contour(xx, yy, Z)
-
- title = ['SVM Decision Boundary with C = {} (Example Dataset 1'.format(C) for C in [1, 100]]
- for model,title in zip(clfs,title):
- plt.figure(figsize=(8,5))
- plotData(X, y)
- plotBoundary(model, X)
- plt.title(title)

C是控制错误分类训练示例的惩罚,类似于1 / λ ,λ是前面逻辑回归时使用的正则化参数。可以看得出来C = 1时,SVM将决策边界放在了正负样本的中间,但是却将异常值做出了误分类,而当C = 100时对异常值做出了正确分类,但是其决策边界与数据并不自然匹配。


在这部分练习中,将使用支持向量机进行非线性分类。特别地,您将在不可线性分离的数据集上使用带有高斯核的支持向量机。
在实现SVM之前,我们需要先实现一个高斯核函数,高斯核函数可看成相似函数,用于衡量两个例子之间的距离。它的公式如下。
- def gaussKernel(x1,x2,sigma):
- return np.exp(-((x1-x2) ** 2).sum() / (2 * sigma ** 2))
- gaussKernel(np.array([1, 2, 1]), np.array([0, 4, -1]), 2.)
写好函数后,我们使用两个测试样例测试内核函数,可以得到0.32465
首先我们将数据可视化,直接调用前面写的plotData函数即可
- path2 = r'E:/Code/ML/ml_learning/ex6-SVM/data/ex6data2.mat'
- data2 = loadmat(path2)
- X2 = data2['X']
- y2 = data2['y']
- plotData(X2,y2)

可以看得出决策边界并非线性的,但是我们可以通过在SVM中使用高斯核来学习到一个非线性的决策边界,注意一下这里svc函数的kernel = 'rbf',其中rbf函数的表达式为exp(-gamma|u-v|^2),所以gamma需要根据上面我们写的公式构造。
- sigma = 0.1
- gamma = np.power(sigma, -2) / 2
- clf2 = svm.SVC(C=1, kernel='rbf', gamma=gamma)
- model2 = clf2.fit(X2, y2.flatten())
- plt.figure(figsize=(8,5))
- plotData(X2,y2)
- plotBoundary(model2, X2)

在这一部分,继续使用高斯核SVM来找出非线性决策边界,并且通过交叉验证集来找出最优的参数,首先先进行数据可视化。
- path3 = r'E:/Code/ML/ml_learning/ex6-SVM/data/ex6data3.mat'
- data3 = loadmat(path3)
- X3, y3 = data3['X'], data3['y']
- Xval, yval = data3['Xval'], data3['yval']
- plotData(X3, y3)

- Cvalues = [0.01, 0.03, 0.1, 0.3, 1., 3., 10., 30.]
- sigmavalues = Cvalues
- best_parameter, best_score = [0, 0], 0
-
- for C in Cvalues:
- for sigma in sigmavalues:
- gamma = np.power(sigma,-2.)/2
- model = svm.SVC(C=C,kernel='rbf',gamma=gamma)
- model.fit(X3, y3.flatten())
- this_score = model.score(Xval, yval)
- if this_score > best_score:
- best_score = this_score
- best_parameter = [C, sigma]
- print('best_parameter={}, best_score={}'.format(best_parameter, best_score))
- # best_parameter=[1.0, 0.1], best_score=0.965
然后使用最优参数来学习决策曲线。
- model = svm.SVC(C=1., kernel='rbf', gamma = np.power(0.1, -2.)/2)
- model.fit(X3, y3.flatten())
- plotData(X3, y3)
- plotBoundary(model, X3)

在这一部分练习,我们将使用SVM构建自己的垃圾邮件过滤器。其中我们的电子邮件,如果是是垃圾邮件则y=1,非垃圾邮件y=0,除此之外我们还需要将每一封电子邮件转化成特征向量。需要注明的是,本练习所包含的数据集是基于SpamAssassin公共语料库的一个子集。为了达到练习目的,我们将只使用电子邮件正文(不包括电子邮件标题)。
首先我们先对一个例子进行可视化。
- path4 = r'E:/Code/ML/ml_learning/ex6-SVM/data/emailSample1.txt'
- with open(path4) 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 http://www.rackspace.com/ or perhaps Amazon EC2
- if youre running something big..
- To unsubscribe yourself from this mailing list, send an email to:
- groupname-unsubscribe@egroups.com
通过上面的内容可以看出其中包含一个URL、一个邮件地址、数字和金额。虽然许多邮件都会包括这些内容,但是不同的邮件是不同的,因此在处理电子邮件前先需要“规范化”这些值,使得所有内容被同等对待,比如我们可以使用唯一字符串httpaddr代替电子邮件里面每一个URL,以表示存在一个URL。这样做的目的是让垃圾分类器根据是否存在URL而不是特定URL是否存在来做出分类决策。下面是一些邮件预处理和规范步骤。
- 大小写字母转换:将整个电子邮件被转换为小写,因此大小写被忽略
- 删除HTML:许多电子邮件通常带有html格式,删除全部html标记,只剩下邮件内容
- 规范化URL:将全部URL都替换为字符串httpaddr
- 规范化邮箱地址:将全部电子邮件地址替换为字符串emailaddr
- 规范化数字:将全部数字替换成字符串number
- 规范化美元符号:将全部美元符号$替换成字符串dollar
- 词干提取:将单词简化成词干形式,如discount,discounts,discounted都替换成discount
- 删除非单词:删除非单词和标点符号,所有空白(制表符、换行符、空格)等都被剪裁为一个空格字符
- import re # 正则表达式
- from stemming.porter2 import stem # 英文分词算法
- import nltk, nltk.stem.porter
-
- 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) # 匹配地址替换成emailaddr
- email = re.sub('[\$]+', 'dollar', email) # 匹配金钱符号替换成dollar
- email = re.sub('[\d]+', 'number', email) # 匹配数字替换成number
- return email
- print(processEmail(email))

下面是提取词干,以及去除非字符内容
- def email2TokenList(email):
- """返回一个干净的单词列表"""
- # 实例化提取器
- stemmer = nltk.stem.porter.PorterStemmer()
- # 预处理邮件
- email = processEmail(email)
- # 分割单词
- tokens = re.split('[ \@\$\/\#\.\-\:\&\*\+\=\[\]\?\!\(\)\{\}\,\'\"\>\_\<\;\%]', email)
- token_list = []
- for token in tokens:
- # 删除非字母数字的字符
- token = re.sub('[^a-zA-Z0-9]', '', token)
- # 丢进提取器提取词根
- stemmed_word = stemmer.stem(token)
- # 去除空字符串, 非零为真
- if not len(token): continue
- token_list.append(stemmed_word)
- return token_list

预处理之后,我们得到了电子邮件的单词列表,下一步是选择我们希望在分类器中使用哪些次,哪些词希望被去掉。
在这练习中,我们只考虑出现频率最高的单词作为我们的词汇表,我们有个词汇表vocab.txt,里面存储了实际中经常使用的单词,共1899个,最后我们计算出处理后的email中含有多少该词汇表中的单词,并返回该单词对应的索引,这便是我们想要的训练单词的索引。
- def email2VocabIndices(email, vocab):
- """提取单词索引"""
- token = email2TokenList(email)
- # for i in range(len(vacab)):
- # if vacab[i] in token
- # index = i
- index = [i for i in range(len(vocab)) if vocab[i] in token]
- return index
- def email2FeatureVector(email):
- """
- email转为词向量 ,n是vocab的长度,存在单词的相应位置为1,其余0
- """
- # 给单词那一列添加列名
- df = pd.read_table(r'E:/Code/ML/ml_learning/ex6-SVM/data/vocab.txt', names=['words'])
- # dataframe转array
- vocab= df.values
- # 长度与单词表长度相同
- vector = np.zeros(len(vocab))
- # 返回邮件单词索引
- vocab_indices = email2VocabIndices(email, vocab)
- # 将有单词的索引置1
- for i in vocab_indices:
- vector[i] = 1
- return vector

- vector = email2FeatureVector(email)
- print('length of vector = {}\nnum of non-zero = {}'.format(len(vector), int(vector.sum())))
- length of vector = 1899
- num of non-zero = 45
- data1 = loadmat(r'E:/Code/ML/ml_learning/ex6-SVM/data/spamTrain.mat')
- X, y = data1['X'], data1['y']
- data2 = loadmat(r'E:/Code/ML/ml_learning/ex6-SVM/data/spamTest.mat')
- Xtest, ytest = data2['Xtest'], data2['ytest']
-
- clf = svm.SVC(C=0.1, kernel='linear')
- clf.fit(X, y)
- predTrain = clf.score(X, y)
- predTest = clf.score(Xtest, ytest)
- predTrain, predTest
(0.99825, 0.989)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。