6.1.SVM建立垃圾邮件分类器
1)题目:
如今,许多电子邮件服务提供垃圾邮件过滤器,能够将电子邮件精确地分类为垃圾邮件和非垃圾邮件。在本部分练习中,您将使用SVM构建自己的垃圾邮件过滤器。
您将训练一个分类器来分类给定的电子邮件x是垃圾邮件(y = 1)还是非垃圾邮件(y = 0)。特别地,你需要将每封电子邮件转换成一个特征向量
x
∈
R
n
x\in R^n
x∈Rn。
本练习中包含的数据集是基于SpamAssassin Public Corpus(http://spamassassin.apache.org/old/publiccorpus/ )的一个子集,对于本练习,您将只使用电子邮件正文(不包括邮件抬头)。
数据集链接: https://pan.baidu.com/s/1cEgQIvehUcLxZ0WVhxcPuQ 提取码: xejn
2)大致步骤:
- 邮件预处理。首先读取样例邮件查看下。然后进行预处理:
1.把整封邮件转化为小写
2.移除所有HTML标签(超文本标记语言)
3.将所有的URL替换为’httpaddr’
4.将所有的地址替换为’emailaddr’
5.将所有数字替换为’number’
6.将所有美元符号($)替换为’dollar’
7.将所有单词还原为词根。例如,“discount”, “discounts”, “discounted” and “discounting”都替换为“discount”
8.移除所有非文字类型,所有的空格(tabs, newlines, spaces)调整为一个空格
然后再对照单词表得到样例对应的序号列。 - 提取特征。利用序号列得到邮件的一个特征向量, x ∈ R n x\in R^n x∈Rn,这里是一个1899维的特征向量。
- 训练SVM。取C=0.1,核函数为线性核,用训练集训练出模型,训练精度为99.8%,再在测试集上测试,精度为98.9%。
- 打印权重最高的前15个词,邮件中出现这些词更容易是垃圾邮件
- 用训练好的模型预测已给的四封邮件
3)关于Python:
-
.lower( )可以转化为小写。
-
邮件预处理时需要用到re模块,正则表达式是由普通字符(例如字符 a 到 z)以及特殊字符(称为”元字符”)组成的文字模式。re模块中的函数让你检查一个特定的字符串是否匹配给定的正则表达式(或给定的正则表达式是否匹配特定的字符串,这可归结为同一件事)。
使用re.sub(pattern, repl, string, count=0, flags=0)
pattern是需要被替换的部分,repl为替换之后的内容,string为查找的范围,count表示模式匹配后替换的最大次数,默认0表示替换所有的匹配。 -
re.split( ) 用来分割字符串。注意在分割时候不能直接用吴恩达给出的MATLAB的代码,因为有一些需要转义。
-
nltk的全称是natural language tool kit,是一套基于python的自然语言处理工具集。这里主要使用了词干提取的Porter提取算法。nltk.stem.PorterStemmer( )
-
try和except语句块可以用来运行可能会有问题的代码。
-
在循环中不想要某些条件执行后面的代码可以用continue跳过本次循环直接进行下一次循环。
-
sorted函数直接对数组进行排序,np.argsort是返回排序后的坐标。
-
关于正则表达式:
字符”r“的意思是表示忽略后面的转义字符,这样简化了后面正则表达式里每遇到一个转义字符还得挨个转义的麻烦;[ ]里面可以用来填要匹配的字符集;\是转义字符,让它后面的字符还原它原有的含义;*匹配前面的子表达式零次或多次;+匹配前面的子表达式一次或多次;$表示只在字符末尾进行匹配。。。这里引用第一个链接里的一些表格以备后用~
更多可参考 http://www.runoob.com/regexp/regexp-syntax.html 或 https://blog.csdn.net/u010254900/article/details/22038741
字符 | 描述 |
---|---|
\s | 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。 |
$ | 匹配输入字符串的结尾位置。如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 ‘\n’ 或 ‘\r’。要匹配 $ 字符本身,请使用 \$。 |
( ) | 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用\( 和 \)。 |
* | 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*。 |
+ | 匹配前面的子表达式一次或多次。要匹配 + 字符,请使用\ +。 |
. | 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \. 。 |
? | 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用\ ?。 |
\ | 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。 |
^ | 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配 ^ 字符本身,请使用 \^。 |
| | 指明两项之间的一个选择。要匹配 |,请使用 \|。 |
4) 代码与结果:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import scipy.io as scio
from sklearn import svm
import re #处理正则表达式的模块
import nltk #自然语言处理工具包
'''============================part1 邮件预处理========================='''
#查看样例邮件
f = open('emailSample1.txt', 'r').read()
print(f)
def processEmail(email):
email = email.lower() #转化为小写
email = re.sub('<[^<>]+>', ' ', email) #移除所有HTML标签
email = re.sub('(http|https)://[^\s]*', 'httpaddr', email) #将所有的URL替换为'httpaddr'
email = re.sub('[^\s]+@[^\s]+', 'emailaddr', email) #将所有的地址替换为'emailaddr'
email = re.sub('\d+', 'number', email) #将所有数字替换为'number'
email = re.sub('[$]+', 'dollar', email) #将所有美元符号($)替换为'dollar'
#将所有单词还原为词根//移除所有非文字类型,空格调整
stemmer = nltk.stem.PorterStemmer() #使用Porter算法
tokens = re.split('[ @$/#.-:&*+=\[\]?!()\{\},\'\">_<;%]', email) #把邮件分割成单个的字符串,[]里面为各种分隔符
tokenlist = []
for token in tokens:
token = re.sub('[^a-zA-Z0-9]', '', token) #去掉任何非字母数字字符
try: #porterStemmer有时会出现问题,因此用try
token = stemmer.stem(token) #词根
except:
token = ''
if len(token) < 1:
continue #字符串长度小于1的不添加到tokenlist里
tokenlist.append(token)
return tokenlist
#查看处理后的样例
processed_f = processEmail(f)
for i in processed_f:
print(i, end=' ')
#得到单词表,序号为索引号+1
vocab_list = np.loadtxt('vocab.txt', dtype='str', usecols=1)
#得到词汇表中的序号
def word_indices(processed_f, vocab_list):
indices = []
for i in range(len(processed_f)):
for j in range(len(vocab_list)):
if processed_f[i]!=vocab_list[j]:
continue
indices.append(j+1)
return indices
#查看样例序号
f_indices = word_indices(processed_f, vocab_list)
for i in f_indices:
print(i, end=' ')
'''============================part2 提取特征========================='''
def emailFeatures(indices):
features = np.zeros((1899))
for each in indices:
features[each-1] = 1 #若indices在对应单词表的位置上词语存在则记为1
return features
sum(emailFeatures(f_indices)) #45
'''============================part3 训练SVM========================='''
#训练模型
train = scio.loadmat('spamTrain.mat')
train_x = train['X']
train_y = train['y']
clf = svm.SVC(C=0.1, kernel='linear')
clf.fit(train_x, train_y)
#精度
def accuracy(clf, x, y):
predict_y = clf.predict(x)
m = y.size
count = 0
for i in range(m):
count = count + np.abs(int(predict_y[i])-int(y[i])) #避免溢出错误得到225
return 1-float(count/m)
accuracy(clf, train_x, train_y) #0.99825
#测试模型
test = scio.loadmat('spamTest.mat')
accuracy(clf, test['Xtest'], test['ytest']) #0.989
'''============================part4 高权重词========================='''
#打印权重最高的前15个词,邮件中出现这些词更容易是垃圾邮件
i = (clf.coef_).size-1
while i >1883:
#返回从小到大排序的索引,然后再打印
print(vocab_list[np.argsort(clf.coef_).flatten()[i]], end=' ')
i = i-1
'''============================part5 预测邮件========================='''
t = open('spamSample2.txt', 'r').read()
#预处理
processed_f = processEmail(t)
f_indices = word_indices(processed_f, vocab_list)
#特征提取
x = np.reshape(emailFeatures(f_indices), (1,1899))
#预测
clf.predict(x)
查看样例邮件
预处理后的样例(吴恩达给出的作业中的样例不对,visitor you re 他给分为visitor your了,这是由于在分割的时候是否把’ 作为分割,这样会导致后面特征提取后少一个非零项,即只有44项)
样例对应的词汇序号
训练精度和测试精度
15个权重最高的词,和作业中有些微差别
测试了给出的四封邮件,都正确分类,下面是spamSample2的结果,分类器把它分为垃圾邮件
本次作业没有给出知识点概括,因为上次作业6.0已经大概描述清楚了SVM的原理和模型选择的一些内容,这次作业其实主要是对练习怎么从文本中提取特征得到一个n维的特征向量,再进行模型训练。
文本提取和处理也是很难啊~ 继续学吧!