BP神经网络用于垃圾邮件识别
摘要
本文利用人工神经网络实现对垃圾邮件的识别和分类。神经网络类型选择常用的BP神经网络,编程环境为Matlab。为了加深对神经网络的理解,不使用已有的神经网络工具包,而是根据底层原理编写实现完整的正向输出过程和参数调整过程。此外,根据实验中获取的经验,引入了一种学习速度动态调整的方法,可以加快前期学习速度,在相同学习次数下获得更小的累计误差。作为性能评价的一部分,将BP神经网络与另一种可行分类方法——贝叶斯分类进行横向对比,显示了神经网络的优越性能
1.BP神经网络
1.1原理
如图1所示,BP神经网络由输入层,隐含层,输出层组成。输入层节点数量为n,序号用i表示,即每个节点表示为\({x_i}{\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} i = 1,2 \cdots n\);隐含层的节点数量为p,序号用j表示,隐含层各节点的输入值表示为\(h{I_j}{\kern 1pt} {\kern 1pt} ,{\kern 1pt} {\kern 1pt} j = 1,2 \cdots p\),输出值为;输出层节点数为q,序号用k表示,所以输出层各节点的输入值为\(y{I_k}{\kern 1pt} {\kern 1pt} ,{\kern 1pt} {\kern 1pt} k = 1,2 \cdots q\),输出值为\(y{O_k}{\kern 1pt} {\kern 1pt} ,{\kern 1pt} {\kern 1pt} k = 1,2 \cdots q\). \({w_{ij}}\)是从输入节点xi到隐含节点hj的权值,\({w_{jk}}\)是从隐含层节点到输出层节点的权值。\(b{H_j}\)是隐含层各节点的**阈值,\(b{Y_k}\)是输出层各节点的**阈值。
Fig. 1 BP神经网络结构
这样,各节点之间的关系就可以如下表示:
隐含层输入:\(h{I_j} = \sum\limits_{i = 1}^n {{x_i}{w_{ij}} - } b{H_j}\)
隐含层输出:\(h{O_j} = f(h{I_j})\)
输出层输入:\(y{I_k} = \sum\limits_{j = 1}^p {{w_{jk}}h{I_j} - b{Y_k}} \)
输出层输出:\(y{O_k} = f\left( {y{I_k}} \right)\)
其中,\(f\left( \cdot \right)\)是**函数,选用单极性s型函数可以简化计算,因为其倒数可以用其自身表示。单极性的s型**函数表示为:\(f\left( x \right) = \frac{1}{{1 + {e^{ - bx}}}}\),b为大于零的系数。
对于一个训练样本\({{\bf{x}}_r} = ({x_1},{x_2} \ldots {x_n})\),及其对应的期望输出\({{\bf{d}}_r} = ({d_1},{d_2} \ldots {d_q})\),神经网络输出的均方误差定义为\({E_r} = \frac{1}{2}\sum\limits_{k = 1}^q {{{\left( {{d_k} - y{O_k}} \right)}^2}} \):,这里乘以常系数1/2是为了推导计算方便。
神经网络训练的过程就是为了使单次训练误差\({E_r}\)最小化,而最小化的方法是根据误差调整系数w和阈值b. 一种方法是以误差的梯度下降方向调整系数。\({E_r}\)分别对wij和wjk求导,得:$$\frac{{\partial {E_r}}}{{\partial {w_{jk}}}} = \frac{{\partial {E_r}}}{{\partial yI}}\frac{{\partial yI}}{{\partial {w_{jk}}}} = {\delta _r}{\kern 1pt} \cdot h{O_j}$$
,其中
$${\delta _k}{\kern 1pt} = ({d_k} - y{O_k})f\left( {y{I_k}} \right)\left( {1 - f\left( {y{I_k}} \right)} \right)$$
所以\(Wjk\)的更新为\({w_{jk}}^{N + 1} = {w_{jk}}^N + \eta \cdot {\delta _k} \cdot h{O_j}\),
阈值的更新:$$b{Y_k}^{N + 1} = b{Y_k}^N - \eta \cdot {\delta _k}$$
类似地,可以得到隐含层权值的更新为:
$${w_{ij}}^{N + 1} = {w_{ij}}^N + \eta \cdot {g_j} \cdot {x_i}$$
$$b{H_j}^{N + 1} = b{H_j}^N - \eta \cdot {g_j}$$
,其中$${g_j} = f\left( {h{I_j}} \right)\left( {1 - f\left( {h{I_j}} \right)} \right)\sum\limits_{k = 1}^q {{\delta _k}{w_{jk}}} $$
1.2算法描述
上一小节中详细说明了BP神经网络的结果,并从降低均方误差出发,推导出了梯度下降法的权值调整方法。利用BP神经网络解决问题首先利用已知样本训练神经网络,权系数收敛到一定程度之后,就可以输入未知样本,正向计算输出了。由此可见,关键在于如何训练网络的参数。
输入训练集的的累计误差可以作为是否结束训练的一个依据,定义累计误差:$$E = \frac{1}{m}\sum\limits_{r = 1}^m {{E_k} = } \frac{1}{m}\sum\limits_{r = 1}^m {\left( {\frac{1}{2}\sum\limits_{k = 1}^q {{{\left( {{d_k} - y{O_k}} \right)}^2}} } \right)} $$
当累计误差下降到小于预设的阈值时就可以认为训练已经达到了要求。但是当阈值设置不当时,也许永远也降不到预设的累计误差,这时可以设置最大训练次数M,训练次数达到这个上界时即结束训练。
训练BP神经网络的过程可以用以下流程图表示:
Fig. 2 训练流程图
2.实现细节与优化
2.1 数据样本
2.1.1 数据说明
本实验利用BP神经网络实现对垃圾邮件的识别。实验所用的数据样本spambase.data是一个垃圾邮件的数据库,来自于惠普公司的Hewlett Packard Labs实验室。该数据库中包含了4601个样本,其中1813条为垃圾邮件(spam),占比约40%;每封邮件根据文字内容提取出57个属性,并且具有是否为垃圾邮件的标记。在数据文件中,每一行为一个样本,58个属性按顺序排列,使用”,”分隔。各属性的简单说明表所述。
表 1样本属性说明
属性序号 |
含义 |
范围 |
最大值 |
1-48 |
特定单词的出现频率 |
[0, 100] |
<100 |
49-54 |
特定字符的出现频率 |
[0, 100] |
<100 |
55 |
大写字母游程均长 |
[1, …] |
1102.5 |
56 |
最长大写字母游程 |
[1, …] |
9989 |
57 |
大写字母游程总长 |
[1, …] |
15841 |
58 |
垃圾邮件标识(1代表垃圾邮件) |
{0, 1} |
1 |
2.1.2 数据初步处理
归一化:
数据中第1-57维是训练输入,第58维是期望的输出。由于输入样本的范围各不相同,首先应当归一化到相同的范围,这样权值W才能最终收敛在相对集中的范围内。又由于这些数据大多数为较小值,例如1-48维的取值范围是[0,100]但大部分都是接近于0的数值,整体上表现为泊松分布。为了让归一化后的属性值大部分分布在0值附近,应当取上下界不等的归一化方法,上界的绝对值大于下界绝对值。用数学式表示为:x = (x - min)/(max - min). 根据数据特点,具体取max=5,min=-1.
期望输出转换:
样本类别为2类,第58维是类别标记,原始数据中用0表示正常邮件,1表示垃圾邮件。在神经网络中,c类可以用c个输出节点表示,因此对期望输出转化为:正常邮件:{1,0}, 垃圾邮件:{0,1}.
乱序:
原始样本中按照正常邮件,垃圾邮件排序,无论从训练的性能角度,还是测评的科学性出发,都应当先将数据打乱。具体的打乱方法可以这样实现:1.利用rand函数产生4601个随机数据。2.对这些数据进行排序,输出每个数据对应的顺序,就得到了乱序的索引。3.利用这个索引从原始数据中取出样本,重新排列。
2.2 动态调整的学习速率优化
在参数调整方法(梯度下降)确定之后,学习速率\(\eta \)将会是影响训练速度和性能的重要因素。速率过大,在前期虽然具有较快的累计误差下降速度,但后期会产生震荡,难以收敛;速率过小则训练速度慢,所需的训练次数加大。显然,学习速率\(\eta \)应当随着训练次数的增长而逐渐下降,进一步说,应当是随着累计误差的下降而调低。一种明显的解决方法是将速率\(\eta \)设置为累计误差的函数,在误差较大时速率较大,而误差降低之后\(\eta \)也同步缩小。设置最小控制速率\({\eta _{\min }}\)和最大控制速率\({\eta _{\max }}\),累计误差\({E _{accu }}\),累计误差阈值\({E_{thr}}]\),则学习速率可以这样设置:
$$\eta = {\eta _{\min }} + {\eta _{\max }}{\left( {\frac{{{E_{accu}} - {E_{thr}}}}{{{E_{accu}} + {E_{thr}}}}} \right)^2}$$ .
2.3 参数设置
前述所需设置的参数设置如下,累计误差阈值取0.02;最大训练轮数100;由于输入样本维数是57,输出结果为2维,因此输入节点数57,输出节点2. 隐含层的确定方法根据经验公式hiddenNode=sqrt(inputNode+outputNode) + a, 0<a<10. 因此,隐含层节点数量取15.
学习速率动态调整方面,最低值控制在etaMin=0.01, 最大值控制在etaMax=0.4, 初始时速率为0.05, 在第二轮时eta就会根据误差大小进行调整。所有的参数设置如下:
errorThr=0. 02; %最大容许误差
M=100; %最大训练轮数
inputNode=57;
outputNode=2;
hideNode=15; %sqrt(inoutNode+outputNode)+a , 0<a<10
eta=0.05;%本次训练速度
etaMin=0.01;
etaMax=0.4;
3. 性能测试
3.1 分类正确性
样本数据库中共有4601条数据,选择其中约2/3用作训练,1/3用作测试。由于在第3节预处理过程中已经将数据乱序排列了,因此可以直接取前3000条作为训练数据,剩下的作为测试数据。
由于数据的选择、权值的初始化都是随机的,因此训练结束后,利用剩下的测试数据对所得模型进行正确性检测,每次结果都是稍有差异的。并且显然,识别性能是与累计误差相关的,多次运行次程序,设置不同的训练次数,考察分类性能,训练与检验结果记录在表2中。
表 2 训练与检验统计结果
运行次序 |
正常样本识别率, % |
垃圾邮件检出率, % |
训练轮数M |
累计误差 |
耗时s |
第1次 |
95.1 |
90.3 |
40 |
0.049 |
25.6 |
第2次 |
94.3 |
91.4 |
100 |
0.036 |
63.2 |
第3次 |
94.5 |
89.4 |
100 |
0.039 |
65.3 |
第4次 |
95.0 |
92.4 |
150 |
0.035 |
93.8 |
第5次 |
92.9 |
93.3 |
300 |
0.031 |
195.1 |
3.2 累计误差下降速度
训练过程实质上是累计误差下降的过程,因此每一轮训练结束后的累计误差E_accu可以展现训练的动态过程。本文中使用了一种学习速度自动调整的动态设置方法,可以在初始时使用较大的学习速度,随后慢慢降低。为了证实这种方法的优点,训练100轮,分布采用固定学习速率和采用这种动态调整方法,比较在累计误差下降曲线上的差异。
图3是学习过程中累计误差的下降曲线与训练轮数M的关系图。图中共同展示了本文中提出的学习速率动态调整方法与固定速率方法所得的结果。使用较大的学习速率,在初始时能更快降低累计误差,但随后会出现震荡;较低的学习速率虽然能稳定降低误差,但初始学习速率过慢。动态速率方法(x符号表示的曲线)起初快速下降,随着误差的降低,学习速率也减小,因此能稳定地逼近最优点。从图3中可见,系数动态调整方法在初始时的下降速率与eta=0.4相当,最终稳定下降,在所有固定速率取值方案中得到了最低的累计误差,且没有产生震荡。
Fig. 3 本文中使用的学习速率动态调整方法与固定学习速率对比,动态调整方法初始时具有较快的下降速率,而后能平稳降低累计误差
Fig. 4 学习速率动态调整方法中,eta值逐渐减小
3.3 与贝叶斯分类器横向对比
垃圾邮件的样本是从实际邮件中统计得到的,对垃圾邮件的识别是一个二分类问题。贝叶斯分类器是一种适合用于该场景下的分类器,并且在现实中曾经采用过这种方案进行垃圾邮件的识别。
贝叶斯分类是一种有监督的分类方法。其原理如下:首先统计各类的先验概率\(P({\omega _i})\),以及类条件概率分布\(P({\bf{x}}|{\omega _i})\),然后通过贝叶斯公式
$$P({\omega _i}|{\bf{x}}) = \frac{{P({\bf{x}}|{\omega _i}) \cdot P({\omega _i})}}{{P({\bf{x}})}} = \frac{{p({\bf{x}}|{\omega _i})d{\bf{x}} \cdot P({\omega _i})}}{{p({\bf{x}})d{\bf{x}}}} = \frac{{p({\bf{x}}|{\omega _i}) \cdot P({\omega _i})}}{{p({\bf{x}})}}$$
将观测量转换得到后验概率\(P({\omega _i}|{\bf{x}})\) .
对于两类分类问题,根据后验概率的大小判决所属分类,即:
上式中由于分母都一样,可以得到变换形式的决策式:
笔者曾经利用这些数据进行过贝叶斯分类器的设计和测试。同样采样2/3数据样本作为训练集,1/3数据作为测试集,分类效果如下。这里涉及到将离散属性近似到连续的概率密度函数的问题,因此需要引用“频数量化区间的概念”。总之,利用贝叶斯分类器,在不同统计量化区间下,识别率如下表。
表 3 横向对比,利用贝叶斯分类器分类的性能
频数量化组数 |
垃圾邮件识别率 |
非垃圾邮件正确识别率 |
100 |
0.835820895522 |
0.891832229581 |
200 |
0.85641025641 |
0.929399367756 |
500 |
0.855687606112 |
0.949506037322 |
1000 |
0.809825673534 |
0.953241232731 |
1500 |
0.786991869919 |
0.93237704918 |
2000 |
0.802931596091 |
0.932584269663 |
3000 |
0.78813559322 |
0.924528301887 |
使用贝叶斯分类器,虽然正常邮件的识别率也很高,但垃圾邮件的检出率最大在85.5%左右。对比BP神经网络的性能,正常邮件的识别率与垃圾邮件的检出率都比贝叶斯分类器高,垃圾邮件的识别率在90%以上,长时间训练后(300轮),垃圾邮件检出率达到93.5%,正常邮件识别率达到92.9%. 可见神经网络方法在充分训练之后,得到的两类分类正确率都较高且平衡。
在计算量方面,训练神经网络需要耗费大量的算力,但一旦训练完成,对未知样本正向计算时并不复杂,只是两次矩阵乘法运算。
4. 总结
本实验根据BP神经网络原理从底层编码实现了正向计算和参数反向调整的过程。通过动手操作,我进一步加深了对神经网络的理解。
神经网络训练的目标是要降低累计误差,因而一种显而易见的方法是沿梯度下降方向调整权值。
神经网络具通用性,对不同问题的解决具有极大的普适性。特别是包含隐含层的BP神经网络,可以描述非线性问题。对于模型未知或难以描述的情形,可以直接进行训练,在忽略其模型的情况下得到解决方案。
Reference
- 周志华. 机器学习[M]. 清华大学出版社, 2016.
- lzhalan2016. 深入浅出BP神经网络算法的原理,https://blog.****.net/lzhalan2016/article/details/52332657
- DaleShen0x01. 模式分类与应用-贝叶斯垃圾邮件分类https://blog.****.net/imkiimki/article/details/78634269
附matlab程序代码
%2018年4月29日
%使用自适应调整的学习速率
%方法:eta=etaMin+( (errNow-errTh)/(errNow+errTh))^3*etaMax
clear all;
tic;
%%%----参数设置----
b=1; %激发函数f(x)中的系数
f = @(x) 1./(1+exp(-b*x)); %s型激发函数,其导数为b*f(x)*(1-f(x))
errorThr=0.02; %最大容许误差
M=100; %最大训练轮数
inputNode=57;
outputNode=2;
hideNode=15; %sqrt(inoutNode+outputNode)+a , 0<a<10
eta=0.05;%本次训练速度
etaMin=0.01;
etaMax=0.4;
%连接权值和阈值
wij=rand(inputNode,hideNode)/5;
wjk=rand(hideNode,outputNode )/5;
bH=rand(1,hideNode)/4;
bY=rand(1,outputNode)/4;
%%%----参数设置结束
data_all=dlmread('spambase.data', ',');
%读取原始数据,58维,1-48:特定单词的频率[0,100]的实数;49-54:特定字符的频率[0,100]
%55;大写字母游程平均长度; 56:大写字母最大游程;57:最大字母总数; 58:类别标记
%打乱数据,划分训练集和测试集
data_all_samples=mapminmax(data_all(:,1:57)',-1,10);%样本归一化处理
% data_all_samples=data_all(:,1:57)';
data_all_label=data_all(:,58)'; %提取标签
%打乱原始数据,并分为训练集和测试集
k=rand(1,size(data_all,1));
[m,n]=sort(k);%随机打乱原始数据,n作为序号
data_train_input=data_all_samples(:,n(1:3000));
data_train_output=data_all_label(:,n(1:3000));
data_test_input=data_all_samples(:, n(3001:4601));
data_test_target=data_all_label(:, n(3001:4601),:);
%对训练集的标记扩展为2维,作为输出
data_train_target=zeros(2,size(data_train_output,2));
for i=1:size(data_train_output,2)
switch data_train_output(i)
case 0
data_train_target(:,i)=[1;0]; %非垃圾邮件
case 1
data_train_target(:,i)=[0;1]; %垃圾邮件
end
end
errorAccuRound=[];%每轮训练结束后的累计误差
etaRecord=[];
for round =1:M
errorAccu=0;%单轮训练内的误差
for index=1:3000;
%样本index;的输出
hI=wij'* data_train_input(:,index ) -bH';
hO=f(hI);
yI=(wjk'* hO -bY');
yO=f(yI);
%更新系数:
delta_O= (data_train_target(:,index)-yO).*f(yI).*(1-f(yI));
%更新Wjk和bY
wjkold=wjk;
for j=1:hideNode
for k=1:outputNode
wjk(j,k)=wjk(j,k)+eta*delta_O(k)*hO(j);
end
end
bY=(bY' - eta*delta_O)';
% hI=wij'* data_train_input(:,1) -bH';
% hO=f(hI);
% yI=(wjk'* hO -bY');
% yO=f(yI)
%更新Wij和bH
delta_H=sum(repmat(delta_O',hideNode,1).*wjkold,2).*f(hI).*(1-f(hI));
for i=1:inputNode
for j=1:hideNode
wij(i,j)=wij(i,j)+eta*delta_H(j)*data_train_input(i,index);
end
end
bH=(bH' - eta*delta_H)';
%计算误差:
errorAccu=errorAccu+0.5*(data_train_target(:,index)-yO)'*(data_train_target(:,index)-yO);
end
%根据累计误差判断是否结束训练
errorAccuRound=[errorAccuRound, errorAccu/3000];%存储本轮的累计误差
if errorAccu/3000 < errorThr %累计误差小于要求时结束训练
break
end
%学习速度的动态调整
eta=etaMin + etaMax*((errorAccu/3000 - errorThr) / (errorThr+errorAccu/3000) )^2
etaRecord=[etaRecord eta];
end
%%
count0=0;
count1=0;
for index_test=1:1601;
hI=wij'* data_test_input(:,index_test) -bH';
hO=f(hI);
yI=(wjk'* hO -bY');
yO=f(yI);
if (yO(1)>yO(2))
class=0;
else
class=1;
end
if class==0 && data_test_target(index_test)==0
count0=count0+1;
elseif class==1 && data_test_target(index_test)==1
count1=count1+1;
end
end
fprintf('%s%.3d\n','非垃圾邮件正确识别率',count0/(1601-sum(data_test_target)))
fprintf('%s%.3d\n','垃圾邮件正确识别率',count1/sum(data_test_target))
% data_train_target(:,index_test);
toc
plot(errorAccuRound)