问题:
判断给定的一个评论是否为垃圾评论(spam)
大致:
应用朴素贝叶斯公式计算该评论为垃圾评论的概率,如果大于一个指定值,则判断该评论为垃圾评论
具体思路:
第一步
对于一个评论来说,它由多个单词组成,在考虑评论是否为垃圾评论的时候,先简化问题为考虑包含某一个特定的单词的评论是否为垃圾评论:
这个公式便是贝叶斯,它计算出了在已知某一个单词的情况下这个评论是spam的可能性Pr(S|W),这个概率由先验概率进行计算。
- Pr(S)代表了一个评论是spam的概率
- Pr(H)代表了一个评论是ham的概率
- Pr(W|S)代表了在spam评论中这个单词出现的概率
- Pr(W|H)代表了在ham评论中这个单词出现的概率
而以上的这些概率均可以通过已有的评论资源进行获取,使用统计得到的频率进行替换。值得一提的是,对于Pr(S)与Pr(H)来说,很多时候虽然大致的概率比在80%:20%,但经过试验,一般来说,不对需要判断的评论做这个假设,而认为这个评论是垃圾评论或正常评论的概率是一样的,所以公式中的Pr(S)与Pr(H)都设为0.5,约掉后公式简化为
在计算公式中的两项的时候,也有一个值得注意的地方,以Pr(W|S)为例,计算垃圾评论中这个单词出现的概率,也有两种方式,一种是,以评论为单位,该单词出现的垃圾评论数 / 总的垃圾评论数,另一种是该单词在垃圾评论中出现的次数(该垃圾评论中可能出现多个该单词)/ 总的垃圾评论中的单词数。本文最终使用的是前一种方式。
第二步
在得到评论中出现某一个单词它的垃圾评论的可能性后,要考虑的就是这个评论包含的所有的单词对于这个评论是垃圾评论可能性的影响。如果按概率论来讨论所有的单词出现在这个评论中的概率会十分复杂,这里就假设所有的单词出现在这个评论中是条件独立的事件,那么对于已知评论中所有的单词,求这条评论为垃圾评论的概率的公式可以简化为:
p1 = Pr(S|W1)
pn = Pr(S|WN)
这个条件独立的假设也就是朴素贝叶斯里的朴素(Naive),公式具体的解释可以看http://www.ruanyifeng.com/blog/2011/08/bayesian_inference_part_two.html
将所有的概率连乘计算得到整条评论的垃圾评论概率,虽然公式很简单,但是在具体计算的时候,由于概率都是0-1的浮点数,所以为了避免出现向下的overflow,在计算的时候使用了对数的加减法,公式如下:
公式的具体解释参看https://en.wikipedia.org/wiki/Naive_Bayes_spam_filtering#Combining_individual_probabilities
第三步
计算得到了概率之后,和一个阈值进行比较,如果大于这个阈值(0.9),那么就可以判断这个评论为垃圾评论。
优化
- 针对罕见词,可能在计算的时候出现概率为0,为了避免这种情况,可以有两种方式优化:
a. 拉普拉斯平滑
http://blog.csdn.net/xueyingxue001/article/details/52396170#t4
Pr(W|S)= (n1 + 1) / (n + N)
这里n1是该单词在spam中出现的次数,n为垃圾评论数,分子分母增加了1和N保证了这一项在任何情况下都不会为0,N为单词的总类数。
b. 修正
s为3,n为单词出现的全部次数。
https://en.wikipedia.org/wiki/Naive_Bayes_spam_filtering#Dealing_with_rare_words
- 只有|Pr - 0.5|的越大,这个单词才越能判断这个评论是否为垃圾评论,越接近1说明这个单词在垃圾评论中出现的概率远大于在正常评论中,越接近0则相反,所以只取最有用的30个单词,也就是|Pr - 0.5|最大的30个词,用它们来计算概率。
具体实现:
分词
中文分词涉及到的内容非常多,现在市面上提供分词服务的也有很多,免费的付费的都有,从成本考虑,本文选择了Ansj分词,使用时只要添加Maven依赖就好。
<dependency>
<groupId>org.ansj</groupId>
<artifactId>ansj_seg</artifactId>
<version>5.1.1</version>
</dependency>
本文借助于Ansj实现的分词器,目前使用的词典文件由library.properties来配置,其中ambiguity.dic是歧义词词典,synonyms.dic是同义词词典,banwords.dic则是禁止词词典。
分词器在分词后,将结果进行过滤与处理:
- 过滤掉不需要的词性,如标点(w)、未知(null)
- 过滤掉不需要的单词符号,如“%”,“'”,“.”等
- 对同义词进行处理,将具有相同意义的词语以一个标准符号返回。
- 对数字与英文符号进行处理,数字返回"__NUM_SHORT__", "__NUM_LONG__", "__NUM_PHONE__"这些类别,英文符号返回"__EN__", "__EN_MEANINGLESS__",而链接地址返回"__URL__ ",这样不会出现太多数字与英文符号,也有助于后面的判断。
处理之后的单词符号结果就可以进行判断了。
spam判断
-
流程图
- 数据库表
CREATE TABLE `T_EBK_TOKEN` (
`FID` int(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`FNAME` varchar(32) NOT NULL COMMENT '词语名',
`FSPAM_A` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '词语出现的spam评论数',
`FHAM_A` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '词语出现的ham评论数',
`FSPAM_B` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '词语出现的spam单词数',
`FHAM_B` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '词语出现的ham单词数',
PRIMARY KEY (`FID`),
UNIQUE KEY `UIDX_FNAME` (`FNAME`)
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8mb4 COMMENT='评论中词语的spam|ham表'
其中的A与B分别代表了以评论为单位以及以单词为单位两种统计方法,实际使用中这两种方式计算出来的结果差距并不大。
在表的第一行,FID = 1,统计的是当前所有评论的数量以及单词数量,后面的每一行记录是某一个单词的结果。
- 计算中的参数
a. 是否有预设,也就是Pr(S)与(1-Pr(S))的值,没有的情况下都为0.5,有的情况下根据数据进行计算。
b. 使用拉普拉斯平滑处理罕见词
c. 使用评论数或是单词数进行计算
模型训练
- 手动分拣数据,得到一个垃圾评论数据集和一个正常评论数据集
- 分词后对数据进行更新。