作者: 龙心尘 && 寒小阳
时间:2016年2月。
出处:http://blog.csdn.net/longxinchen_ml/article/details/50629110
http://blog.csdn.net/han_xiaoyang/article/details/50629587
声明:版权所有,转载请联系作者并注明出处
1. 引言
上一篇文章我们主要从理论上梳理了朴素贝叶斯方法进行文本分类的基本思路。这篇文章我们主要从实践上探讨一些应用过程中的tricks,并进一步分析贝叶斯方法,最后以情绪褒贬分析和拼写纠错为例展示这种简单强大的方法在自然语言处理问题上的具体应用。
2. 为什么不直接匹配关键词来识别垃圾邮件?
看了上一篇文章的一些同学可能会问:“何必费这么大劲算那么多词的概率?直接看邮件中有没有‘代开发票’、‘转售发票’之类的关键词不就得了?如果关键词比较多就认为是垃圾邮件呗。”
咳咳,其实关键词匹配的方法如果有效的话真不必用朴素贝叶斯。毕竟这种方法简单嘛,就是一个字符串匹配。从历史来看,之前没有贝叶斯方法的时候主要也是用关键词匹配。但是这种方法准确率太低。我们在工作项目中也尝试过用关键词匹配的方法去进行文本分类,发现大量误报。感觉就像扔到垃圾箱的邮件99%都是正常的!这样的效果不忍直视。而加一个朴素贝叶斯方法就可能把误报率拉低近一个数量级,体验好得不要不要的。
另一个原因是词语会随着时间不断变化。发垃圾邮件的人也不傻,当他们发现自己的邮件被大量屏蔽之后,也会考虑采用新的方式,如变换文字、词语、句式、颜色等方式来绕过反垃圾邮件系统。比如对于垃圾邮件“我司可办理正规发票,17%增值税发票点数优惠”,他们采用火星文:“涐司岢办理㊣規髮票,17%增値稅髮票嚸數優蕙”,那么字符串匹配的方法又要重新找出这些火星文,一个一个找出关键词,重新写一些匹配规则。更可怕的是,这些规则可能相互之间的耦合关系异常复杂,要把它们梳理清楚又是大一个数量级的工作量。等这些规则失效了又要手动更新新的规则……无穷无尽猫鼠游戏最终会把猫给累死。
而朴素贝叶斯方法却显示出无比的优势。因为它是基于统计方法的,只要训练样本中有更新的垃圾邮件的新词语,哪怕它们是火星文,都能自动地把哪些更敏感的词语(如“髮”、“㊣”等)给凸显出来,并根据统计意义上的敏感性给他们分配适当的权重 ,这样就不需要什么人工了,非常省事。你只需要时不时地拿一些最新的样本扔到训练集中,重新训练一次即可。
小补充一下,对于火星文、同音字等替代语言,一般的分词技术可能会分得不准,最终可能只把一个一个字给分出来,成为“分字”。当然也可以用过n-gram之类的语言模型(后续博客马上提到,尽请关注),拿到最常见短语。对于英文等天生自带空格来间隔单词的语言,分词则不是什么问题,使用朴素贝叶斯方法将会更加顺畅。
3.工程上的一些tricks
应用朴素贝叶斯方法的过程中,一些tricks能显著帮助工程解决问题。我们毕竟经验有限,无法将它们全都罗列出来,只能就所知的一点点经验与大家分享,欢迎批评指正。
3.1 trick1:取对数
我们上一篇文章用来识别垃圾邮件的方法是比较以下两个概率的大小(字母S表示“垃圾邮件”,字母H表示“正常邮件”):
C = P ( “ 我 ” ∣ S ) P ( “ 司 ” ∣ S ) P ( “ 可 ” ∣ S ) P ( “ 办 理 ” ∣ S ) P ( “ 正 规 发 票 ” ∣ S ) C = P(“我”|S)P(“司”|S)P(“可”|S)P(“办理”|S)P(“正规发票”|S) C=P(“我”∣S)P(“司”∣S)P(“可”∣S)P(“办理”∣S)P(“正规发票”∣S)
× P ( “ 保 真 ” ∣ S ) P ( “ 增 值 税 ” ∣ S ) P ( “ 发 票 ” ∣ S ) P ( “ 点 数 ” ∣ S ) P ( “ 优 惠 ” ∣ S ) P ( “ 垃 圾 邮 件 ” ) ×P(“保真”|S)P(“增值税”|S)P(“发票”|S)P(“点数”|S)P(“优惠”|S)P(“垃圾邮件”) ×P(“保真”∣S)P(“增值税”∣S)P(“发票”∣S)P(“点数”∣S)P(“优惠”∣S)P(“垃圾邮件”)
C ‾ = P ( “ 我 ” ∣ H ) P ( “ 司 ” ∣ H ) P ( “ 可 ” ∣ H ) P ( “ 办 理 ” ∣ H ) P ( “ 正 规 发 票 ” ∣ H ) \overline{C}=P(“我”|H)P(“司”|H)P(“可”|H)P(“办理”|H)P(“正规发票”|H) C=P(“我”∣H)P(“司”∣H)P(“可”∣H)P(“办理”∣H)P(“正规发票”∣H)
× P ( “ 保 真 ” ∣ H ) P ( “ 增 值 税 ” ∣ H ) P ( “ 发 票 ” ∣ H ) P ( “ 点 数 ” ∣ H ) P ( “ 优 惠 ” ∣ H ) P ( “ 正 常 邮 件 ” ) ×P(“保真”|H)P(“增值税”|H)P(“发票”|H)P(“点数”|H)P(“优惠”|H)P(“正常邮件”) ×P(“保真”∣H)P(“增值税”∣H)P(“发票”∣H)P(“点数”∣H)P(“优惠”∣H)P(“正常邮件”)
但这里进行了很多乘法运算,计算的时间开销比较大。尤其是对于篇幅比较长的邮件,几万个数相乘起来还是非常花时间的。如果能把这些乘法变成加法则方便得多。刚好数学中的对数函数log就可以实现这样的功能。两边同时取对数(本文统一取底数为2),则上面的公式变为:
l o g C = l o g P ( “ 我 ” ∣ S ) + l o g P ( “ 司 ” ∣ S ) + l o g P ( “ 可 ” ∣ S ) + l o g P ( “ 办 理 ” ∣ S ) + l o g P ( “ 正 规 发 票 ” ∣ S ) log{C} = log{P(“我”|S)}+log{P(“司”|S)}+log{P(“可”|S)}+log{P(“办理”|S)}+log{P(“正规发票”|S)} logC=logP(“我”∣S)+logP(“司”∣S)+logP(“可”∣S)+logP(“办理”∣S)+logP(“正规发票”∣S)
+ l o g P ( “ 保 真 ” ∣ S ) + l o g P ( “ 增 值 税 ” ∣ S ) + l o g P ( “ 发 票 ” ∣ S ) + l o g P ( “ 点 数 ” ∣ S ) + l o g P ( “ 优 惠 ” ∣ S ) + l o g P ( “ 垃 圾 邮 件 ” ) +log{P(“保真”|S)}+log{P(“增值税”|S)}+log{P(“发票”|S)}+log{P(“点数”|S)}+log{P(“优惠”|S)}+log{P(“垃圾邮件”)} +logP(“保真”∣S)+logP(“增值税”∣S)+logP(“发票”∣S)+logP(“点数”∣S)+logP(“优惠”∣S)+logP(“垃圾邮件”)
l o g C ‾ = l o g P ( “ 我 ” ∣ H ) + l o g P ( “ 司 ” ∣ H ) + l o g P ( “ 可 ” ∣ H ) + l o g P ( “ 办 理 ” ∣ H ) + l o g P ( “ 正 规 发 票 ” ∣ H ) log{\overline{C}}=log{P(“我”|H)}+log{P(“司”|H)}+log{P(“可”|H)}+log{P(“办理”|H)}+log{P(“正规发票”|H)} logC=logP(“我”∣H)+logP(“司”∣H)+logP(“可”∣H)+logP(“办理”∣H)+logP(“正规发票”∣H)
+ l o g P ( “ 保 真 ” ∣ H ) + l o g P ( “ 增 值 税 ” ∣ H ) + l o g P ( “ 发 票 ” ∣ H ) + l o g P ( “ 点 数 ” ∣ H ) + l o g P ( “ 优 惠 ” ∣ H ) + l o g P ( “ 正 常 邮 件 ” ) +log{P(“保真”|H)}+log{P(“增值税”|H)}+log{P(“发票”|H)}+log{P(“点数”|H)}+log{P(“优惠”|H)}+log{P(“正常邮件”)} +logP(“保真”∣H)+logP(“增值税”∣H)+logP(“发票”∣H)+logP(“点数”∣H)+lo