Advertisement

贝叶斯算法:垃圾邮件过滤

阅读量:

准备
100封邮件,50封垃圾邮件和50封正常邮件
参考 :
贝叶斯算法原理

程序的具体工作流程解释 垃圾邮件分类任务的数学理论基础来源于贝叶斯推断方法(bayesian inference)。该系统的主要工作模块主要包括以下几个部分:数据预处理模块、特征提取模块、模型训练与评估模块以及结果展示模块。

step 1 : 提取邮件并处理
在指定路径下提取邮件文件并获取其路径列表。
通过TStringList对象根据邮件路径读取相关邮件内容。
采用Split方法对TStringList中的邮件内容进行分割处理,并将其分解后的每一项存入数组中。

这三个步骤都由getEmail(emailClass)函数和processEmail(emailpath)函数分别完成。

步骤二:统计邮件类别中的每个单词频率
该步骤在代码实现中仅需调用函数 getWordFrequency() 即可完成该功能。其中 emailClass 是邮件分类标记(normal 或 junk),其返回结果为一个字典对象。其键值对中的键是单词(即出现次数最多的词汇),对应的键值对中的值则为该单词在整个样本中的出现频率统计结果。

注意,在这里计算某个词语的频率时,并不是简单地将该词在多封邮件中的出现次数相加求总和来作为其频率值的确定依据。相反地,在这种情况下我们采用的是另一种计算方式:这里的频率并不是表示该词在多少次出现中(即总次数),而是表示该词在多少封邮件中出现过(即出现过的邮件数量)。例如,在三个不同的邮件中,单词 sex 分别出现了5、6、4次。根据我们的定义,在这种情况下该词的频率被设定为3(因为该词出现在三个邮件中),而非将所有出现次数相加(即5 + 6 + 4 =15)。

第三步的主要任务是计算每个单词的后验概率

基于step 1和step 2所定义的功能计算normal与junk中每个单词的数量;建立单词的先验概率字典GPosteriorProbabilityDict,并将其中所有单词的初始值设为0.00;通过for循环获取每个单词的概率:

P(S|W) = P(W|S)P(S) / P(W|S)P(S) + P(W|H)P(H)

假设 normal 类和 junk 类的邮件数量相等,则其对应的先验概率 P(S) 和 P(H) 完全相等, 各自占比 50%; 从而导致公式得以简化为以下形式:

P(S|W) = P(W|S) / P(W|S) + P(W|H)

在代码中 P(W|S) 用 xJunkPro 变量表示, P(W|H)用 xNormalPro 表示。

当一个单词在一个类别中从未被记录时,则可认为该单词在该类别的后验概率设定为 0.01。

step 4 : 分类函数
该步骤主要体现在 testEmail 函数中。其中 email 参数标识 email 文件;threshold 参数标识阈值。当计算结果超过阈值时,则判定为垃圾邮件;否则视为正常邮件。默认阈值设为 0.7。

对于给定的测试email,计算过程如下 :

通过调用过程函数processEmail()来获取这封邮件中的单词集合xWordArr。随后,在每封邮件中将每个单词及其对应的后验概率存入数组xPosteriorProbabilityArr中;若该单词不在字典GPosteriorProbabilityDict中,则将其值设为0.4。接着按各词的概率值从小到大进行排序。最后计算联合概率时仅选取先验概率排序后的前15个词进行计算,并采用以下公式:

这里

用 变量xFracTop 在for...in...循环中累乘表示, 用变量xFracBottom累乘表示

执行判定逻辑:当计算出分子与分母的比例超过阈值时,则将结果设为junk;反之,则归类为normal

step 5 : 进入测试流程 对新接收邮件进行测试:将新接收邮件保存为.txt文件,并将路径传递至testEmail()函数用于后续测试。之后等待判定结果。

完整代码

uses System.Generics.Collections ,System.Generics.Defaults;

type
TArrayB = array of TArray ;

step 1 : get E-mail and process them

class function TJunkBayes.processEmail(emailpath: string): TArray ;
var
xWordLst: TStringList ;
xWordArr: TArray ;
const xSep: array[0..1] of string = (' ' ,#13);
begin
xWordLst := TStringList.Create ;
xWordLst.LoadFromFile(emailpath) ;
xWordArr := xWordLst.Text.Split(xSep) ;
Result := xWordArr ;
end;

function class TJunkBayes's getEmail takes a string parameter named emailClass and returns an array of strings.
var
xPath as string;
I as Integer;
xFileArr as array of string;
xEmailArr as array of B;
xLen as Integer;
begin
Assign the concatenation of g_BayesPath and the single-quoted emailClass string to variable xPath.
Retrieve all files within the directory specified by the value stored in variable xPath into array structure named xFileArr.
Assign the length of array structure xFileArr to integer variable xLen.
ReDim array structure named xEmail Arr with dimensions specified by integer value stored in variable xLen.
Loop through each item in array structure named X File Arr. For each item, assign the result of calling process Email on that item to corresponding position in array structure named X Email Arr. End loop. Return array structure named X Email Arr. End function.

step 2 : get word frequency of the email's class

class function TJunkBayes.getWordFrequency(emailClass: string): TDictionary<string,Integer> ;
var
xWordFrequencyDict : TDictionary<string ,Integer> ;
xEmailArr: TArrayB ;
I ,k: Integer;
xWord : string ;
xWordLst: TStringList ;
begin
xEmailArr := getEmail(emailClass) ;
xWord := '';
xWordLst := TStringList.Create ;
xWordFrequencyDict := TDictionary<string,Integer>.Create();
for I := Low(xEmailArr) to High(xEmailArr) do //遍历每封邮件
begin
xWordLst.Clear ;
for k := Low(xEmailArr[I]) to High(xEmailArr[I]) do //遍历一封邮件里的内容
begin
xWord := Trim(LowerCase(xEmailArr[I][k])) ;
if xWord = '' then Continue ;
if xWordLst.IndexOf(xWord) = -1 then
xWordLst.Add(xWord) ;
end;
for k := 0 to xWordLst.Count - 1 do
begin
xWord := xWordLst[k] ;
try
xWordFrequencyDict.Items[xWord] := xWordFrequencyDict.Items[xWord] + 1 ;
except
xWordFrequencyDict.Add(xWord ,1) ;
end;
end;
end;
Result := xWordFrequencyDict ;
end;

step 4 : calculate classified result of test email

class function TJunkBayes.TestEmail(emailpath: string ;Threshold: Double) : Boolean ;
var
xWordArr: TArray ;
xWord : string ;
xProbability : Double ;
xPosteriorProbabilityArr: array of Double ;
xCount: Integer ;
I : Integer ;
xFracTop,xFracBottom : Double ;
begin
Result := False ;
xWordArr := processEmail(emailpath) ;
SetLength(xPosteriorProbabilityArr ,Length(xWordArr));
xCount := 0 ;
for xWord in xWordArr do
begin
try
xProbability := GPosteriorProbabilityDict[xWord] ;
except //当这个单词不存在时
xProbability := 0.4 ;
end;
xPosteriorProbabilityArr[xCount] := xProbability ;
xCount := xCount + 1;
end;
TArray.Sort(xPosteriorProbabilityArr); //从小到大排序
xFracTop := 1 ;
xFracBottom := 1 ;
for I := High(xPosteriorProbabilityArr) downto Length(xPosteriorProbabilityArr) - 15 do
begin
xFracTop := xFracTop * xPosteriorProbabilityArr[I] ;
xFracBottom := xFracBottom * (1 - xPosteriorProbabilityArr[I]) ;
end;
if (xFracTop / (xFracTop + xFracBottom) > Threshold) then
Result := True
else
Result := False ;
end;

#step 3: calculate posterior probability

类过程T Junk Bayes Calculate Word Posterior Probability ;
变元
xNormalDict: TDictionary<string, Integer> ;
xJunkDict: TDictionary<string, Integer> ;
xNormalEmailCount, xJunkEmailCount: Integer;
xWord: string;
I: Integer;
xNormalPro, xJunkPro: Double;
开始
遍历所有属于正常邮件集合中的单词键值对。
然后将这些键值对添加到垃圾邮件集合中。
对于每个键值对,在正常邮件集合中查找其计数。
如果未找到对应键,则设置计数为零点零一。
同样地,在垃圾邮件集合中查找其计数。
如果未找到对应键,则设置计数为零点零一。
最后计算并存储每个单词的后验概率。
结束

#step 5 : test start

调用以下两个过程

T\ JunkBayes\$ . CalculateWordPosteriorProbability\$ ; T\ JunkBayes$ . TestEmail$ ( 'testemailpath' $ , 0.7 $ ) $ ;

全部评论 (0)

还没有任何评论哟~