Coursera Machine Learning ex6 附加题

Machine Learning编程作业:用自己的数据训练垃圾邮件分类器

Andrew Ng的machine learning公开课在支持向量机一章的附加题:自己构建数据集,使用SVM来训练垃圾邮件分类器,先放上实现的github链接

数据预处理

数据下载

数据集链接

我使用了上面链接中的数据,其中名字中带hard是难以分辨是否为垃圾邮件的邮件,带ham的是正常邮件,带spam的是垃圾邮件。我把全部的垃圾邮件解压放到一个文件夹spam里,全部的正常邮件解压放到一个文件夹ham里。

邮件预处理

运行getProcesseedEmail.m
因为邮件中包含各种邮件头、网址、邮箱地址等信息,在对邮件进行词频统计和特征提取前需要先要把内容按照一定规范格式化一下,如去掉邮件头、全部改为小写、处理数字、网址、邮箱地址等。具体操作见代码中给出的myprocessEmail.m,处理结果放在./myDataset/processedSpam和./myDataset/processedHam文件夹中。

提取垃圾邮件高频词汇

课程中的词汇表是直接给出的,可能不适用于下载的数据集,我们需要自己提取垃圾邮件高频词汇。

可是octave我不会玩啊(好像很会玩其他一样),这个时候就要基于github编程了。

我在统计时排除掉了一些常见的虚词,因为虚词无论在垃圾邮件中还是在正常邮件中,都挺高频的,这是稍加修改的实现

统计结果如图所示:

特征提取

运行getFetaures.m
统计结果有五万多条,后面大部分都是垃圾邮件为了混淆视听故意拼写错误的单词,我选取了前2000个单词作为特征。
现在需要把每封邮件转化为一个特征向量。代码在作业中已经给出了。

1
2
3
file_contents = readFile('emailSample1.txt');
word_indices = processEmail(file_contents); % 得到单词索引
features = emailFeatures(word_indices); % 得到特征向量

上面的代码是提取一封邮件的特征向量,下面的代码是提取所有邮件的特征向量(geneFeatures.m)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function [X,y] = geneFeatures(dataPath,spamPath,hamPath)

spamPath = strcat(dataPath,spamPath); % 垃圾邮件目录
hamPath = strcat(dataPath,hamPath); % 正常邮件目录

spamList = dir(spamPath);
hamList = dir(hamPath);
spamFileNum = size(spamList,1);
hamFileNum = size(hamList,1);
spamX = zeros(spamFileNum,2001); % 所有垃圾邮件的特征向量,第2001列为标签
hamX = zeros(hamFileNum,2001); % 所有正常邮件的特征向量,第2001列为标签

for i = 3:spamFileNum, % 读取垃圾邮件目录下的所有垃圾邮件
fprintf('%d\n',i);
filename = [spamPath spamList(i).name];
email_contents = readFile(filename);
word_indices = processEmail(email_contents);
features = emailFeatures(word_indices);
spamX(i,:) = [features' 1];
end;



for i = 3:hamFileNum, % 读取正常邮件目录下的所有正常邮件
fprintf('%d\n',i);
filename = [hamPath hamList(i).name];
email_contents = readFile(filename);
word_indices = processEmail(email_contents);
features = emailFeatures(word_indices);
hamX(i,:) = [features' 0];
end;

X = [hamX;spamX]; % 合并两个特征矩阵
rowrank = randperm(size(X, 1));
Xy = X(rowrank, :) % 打乱顺序
X = Xy(:,1:2000); % 得到特征矩阵
y = Xy(:,2001); % 得到标签
save myTrainDataset X y; % 将X、y存储为文件myTrainDataset

经过上面的处理,我们就得到了我们训练需要的输入数据X、y,他们被存进了myTrainDataset文件,使用load命令就可以加载他们。(吐槽一下这个方法好慢,跑了一晚上,而且Octave很奇怪,代码里打印下进度,命令行里却不显示,不知道运行进度很考验耐心)

训练分类器

运行MySpam.m
本来打算使用LIBSVM库进行训练的,但是发现要自己编译,我没有安装Visual Studio,所以就使用作业中自带的训练方法来训练了(代码见MySpam.m)。这里可以使用自带的linearKernel核函数或者gaussianKernel核函数来训练。

1
2
3
4
5
6
7
8
9
10
11
12
13
load('TrainDataset');

fprintf('\nTraining Linear SVM (Spam Classification)\n')
fprintf('(this may take 1 to 2 minutes) ...\n')
X = XTrain(1:4000,:); %提取4000条训练特征
y= yTrain(1:4000,:);
XTest = XTrain(4001:,:); %剩下的都是验证集
yTest = yTrain(4001:,:);
C = 0.1;
model = svmTrain(X, y, C, @linearKernel); % 训练

p = svmPredict(model, XTest); %预测
fprintf('Training Accuracy: %f\n', mean(double(p == yTest)) * 100); %验证

使用两个核函数进行训练的准确率如图。
使用linearKernel,在训练集和测试集上都表现得不错

使用guassianKernel,在测试集上表现得不是很好,高方差有过拟合嫌疑,此时的$\sigma=1$

调整$\sigma=10$,准确率如下,也都表现得不错。