转自 寒小阳
贝叶斯方法是一个历史悠久,有着坚实的理论基础的方法,同时处理很多问题时直接而又高效,很多高级自然语言处理模型也可以从它演化而来。因此,学习贝叶斯方法,是研究自然语言处理问题的一个非常好的切入口。
贝叶斯公式就一行:
P(Y|X)=P(X|Y)P(Y)P(X) P ( Y | X ) = P ( X | Y ) P ( Y ) P ( X )
而它其实是由以下的联合概率公式推导出来:
P(Y,X)=P(Y|X)P(X)=P(X|Y)P(Y) P ( Y , X ) = P ( Y | X ) P ( X ) = P ( X | Y ) P ( Y )
其中 P(Y) P ( Y ) 叫做先验概率, P(Y|X) P ( Y | X ) 叫做后验概率, P(Y,X) P ( Y , X ) 叫做联合概率。
没了,贝叶斯最核心的公式就这么些。
在机器学习的视角下,我们把 X X 理解成“具有某特征”,把 Y Y 理解成“类别标签”(一般机器学习为题中都是X=>特征
, Y=>结果
对吧)。在最简单的二分类问题(是
与否
判定)下,我们将 Y Y 理解成“属于某类”的标签。于是贝叶斯公式就变形成了下面的样子:
P(“属于某类”|“具有某特征”)=P(“具有某特征”|“属于某类”)P(“属于某类”)P(“具有某特征”) P ( “ 属 于 某 类 ” | “ 具 有 某 特 征 ” ) = P ( “ 具 有 某 特 征 ” | “ 属 于 某 类 ” ) P ( “ 属 于 某 类 ” ) P ( “ 具 有 某 特 征 ” )
我们简化解释一下上述公式:
P(“属于某类”|“具有某特征”)= P ( “ 属 于 某 类 ” | “ 具 有 某 特 征 ” ) = 在已知某样本“具有某特征”的条件下,该样本“属于某类”的概率。所以叫做『后验概率』。
P(“具有某特征”|“属于某类”)= P ( “ 具 有 某 特 征 ” | “ 属 于 某 类 ” ) = 在已知某样本“属于某类”的条件下,该样本“具有某特征”的概率。
P(“属于某类”)= P ( “ 属 于 某 类 ” ) = (在未知某样本具有该“具有某特征”的条件下,)该样本“属于某类”的概率。所以叫做『先验概率』。
P(“具有某特征”)= P ( “ 具 有 某 特 征 ” ) = (在未知某样本“属于某类”的条件下,)该样本“具有某特征”的概率。
而我们二分类问题的最终目的就是要判断 P(“属于某类”|“具有某特征”) P ( “ 属 于 某 类 ” | “ 具 有 某 特 征 ” ) 是否大于1/2就够了。贝叶斯方法把计算“具有某特征的条件下属于某类”的概率转换成需要计算“属于某类的条件下具有某特征”的概率,而后者获取方法就简单多了,我们只需要找到一些包含已知特征标签的样本,即可进行训练。而样本的类别标签都是明确的,所以贝叶斯方法在机器学习里属于有监督学习方法。
这里再补充一下,一般『先验概率』、『后验概率』是相对出现的,比如 P(Y) P ( Y ) 与 P(Y|X) P ( Y | X ) 是关于 Y Y 的先验概率与后验概率, P(X) P ( X ) 与 P(X|Y) P ( X | Y ) 是关于 X X 的先验概率与后验概率。
举个例子好啦,我们现在要对邮件进行分类,识别垃圾邮件和普通邮件,如果我们选择使用朴素贝叶斯分类器,那目标就是判断 P(“垃圾邮件”|“具有某特征”) P ( “ 垃 圾 邮 件 ” | “ 具 有 某 特 征 ” ) 是否大于1/2。现在假设我们有垃圾邮件和正常邮件各1万封作为训练集。需要判断以下这个邮件是否属于垃圾邮件:
“我司可办理正规发票(保真)17%增值税发票点数优惠!”
也就是判断概率 P(“垃圾邮件”|“我司可办理正规发票(保真)17%增值税发票点数优惠!”) P ( “ 垃 圾 邮 件 ” | “ 我 司 可 办 理 正 规 发 票 ( 保 真 ) 17 % 增 值 税 发 票 点 数 优 惠 ! ” ) 是否大于1/2。
咳咳,有木有发现,转换成的这个概率,计算的方法:就是写个计数器,然后+1 +1 +1统计出所有垃圾邮件和正常邮件中出现这句话的次数啊!!!好,具体点说:
P(“垃圾邮件”|“我司可办理正规发票(保真)17%增值税发票点数优惠!”) P ( “ 垃 圾 邮 件 ” | “ 我 司 可 办 理 正 规 发 票 ( 保 真 ) 17 % 增 值 税 发 票 点 数 优 惠 ! ” ) =垃圾邮件中出现这句话的次数垃圾邮件中出现这句话的次数+正常邮件中出现这句话的次数 = 垃 圾 邮 件 中 出 现 这 句 话 的 次 数 垃 圾 邮 件 中 出 现 这 句 话 的 次 数 + 正 常 邮 件 中 出 现 这 句 话 的 次 数
一个很悲哀但是很现实的结论: 训练集是有限的,而句子的可能性则是无限的。所以覆盖所有句子可能性的训练集是不存在的。
所以解决方法是? 句子的可能性无限,但是词语就那么些!!汉语常用字2500个,常用词语也就56000个(你终于明白小学语文老师的用心良苦了)。按人们的经验理解,两句话意思相近并不强求非得每个字、词语都一样。比如“我司可办理正规发票,17%增值税发票点数优惠!”,这句话就比之前那句话少了“(保真)”这个词,但是意思基本一样。如果把这些情况也考虑进来,那样本数量就会增加,这就方便我们计算了。
于是,我们可以不拿句子作为特征,而是拿句子里面的词语(组合)作为特征去考虑。比如“正规发票”可以作为一个单独的词语,“增值税”也可以作为一个单独的词语等等。
句子“我司可办理正规发票,17%增值税发票点数优惠!”就可以变成(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”))。
于是你接触到了中文NLP中,最最最重要的技术之一:分词!!!也就是把一整句话拆分成更细粒度的词语来进行表示。另外,分词之后去除标点符号、数字甚至无关成分(停用词)是特征预处理中的一项技术。
中文分词是一个专门的技术领域(我不会告诉你某搜索引擎厂码砖工有专门做分词的!!!),上过之前课程的同学都知道python有一个非常方便的分词工具jieba,假定我们已经完成分词工作:
我们观察(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”),这可以理解成一个向量:向量的每一维度都表示着该特征词在文本中的特定位置存在。这种将特征拆分成更小的单元,依据这些更灵活、更细粒度的特征进行判断的思维方式,在自然语言处理与机器学习中都是非常常见又有效的。
因此贝叶斯公式就变成了:
P(“垃圾邮件”|(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)) P ( “ 垃 圾 邮 件 ” | ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” , “ 保 真 ” , “ 增 值 税 ” , “ 发 票 ” , “ 点 数 ” , “ 优 惠 ” ) ) =P((“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)|"垃圾邮件")P(“垃圾邮件”)P((“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)) = P ( ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” , “ 保 真 ” , “ 增 值 税 ” , “ 发 票 ” , “ 点 数 ” , “ 优 惠 ” ) | " 垃 圾 邮 件 " ) P ( “ 垃 圾 邮 件 ” ) P ( ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” , “ 保 真 ” , “ 增 值 税 ” , “ 发 票 ” , “ 点 数 ” , “ 优 惠 ” ) )
P(“正常邮件”|(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)) P ( “ 正 常 邮 件 ” | ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” , “ 保 真 ” , “ 增 值 税 ” , “ 发 票 ” , “ 点 数 ” , “ 优 惠 ” ) ) =P((“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)|"正常邮件")P(“正常邮件”)P((“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)) = P ( ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” , “ 保 真 ” , “ 增 值 税 ” , “ 发 票 ” , “ 点 数 ” , “ 优 惠 ” ) | " 正 常 邮 件 " ) P ( “ 正 常 邮 件 ” ) P ( ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” , “ 保 真 ” , “ 增 值 税 ” , “ 发 票 ” , “ 点 数 ” , “ 优 惠 ” ) )
下面我们马上会看到一个非常简单粗暴的假设。
概率 P((“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)|"垃圾邮件") P ( ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” , “ 保 真 ” , “ 增 值 税 ” , “ 发 票 ” , “ 点 数 ” , “ 优 惠 ” ) | " 垃 圾 邮 件 " ) 依旧不够好求,我们引进一个很朴素的近似。为了让公式显得更加紧凑,我们令字母S表示“垃圾邮件”,令字母H表示“正常邮件”。近似公式如下:
P((“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)|S) P ( ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” , “ 保 真 ” , “ 增 值 税 ” , “ 发 票 ” , “ 点 数 ” , “ 优 惠 ” ) | S )
=P(“我”|S)×P(“司”|S)×P(“可”|S)×P(“办理”|S)×P(“正规发票”|S) = P ( “ 我 ” | S ) × P ( “ 司 ” | S ) × P ( “ 可 ” | S ) × P ( “ 办 理 ” | S ) × P ( “ 正 规 发 票 ” | S ) ×P(“保真”|S)×P(“增值税”|S)×P(“发票”|S)×P(“点数”|S)×P(“优惠”|S) × 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 ( “ 垃 圾 邮 件 ” ) 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 ( “ 正 常 邮 件 ” )
厉(wo)害(cao)!酱紫处理后式子中的每一项都特别好求!只需要分别统计各类邮件中该关键词出现的概率就可以了!!!比如:
P(“发票”|S)=垃圾邮件中所有“发票”的次数垃圾邮件中所有词语的次数 P ( “ 发 票 ” | S ) = 垃 圾 邮 件 中 所 有 “ 发 票 ” 的 次 数 垃 圾 邮 件 中 所 有 词 语 的 次 数
统计次数非常方便,而且样本数量足够大,算出来的概率比较接近真实。于是垃圾邮件识别的问题就可解了。
加上条件独立假设的贝叶斯方法就是朴素贝叶斯方法(Naive Bayes)。 Naive的发音是“乃一污”,意思是“朴素的”、“幼稚的”、“蠢蠢的”。咳咳,也就是说,大神们取名说该方法是一种比较萌蠢的方法,为啥?
将句子(“我”,“司”,“可”,“办理”,“正规发票”) 中的 (“我”,“司”)与(“正规发票”)调换一下顺序,就变成了一个新的句子(“正规发票”,“可”,“办理”, “我”, “司”)。新句子与旧句子的意思完全不同。但由于乘法交换律,朴素贝叶斯方法中算出来二者的条件概率完全一样!计算过程如下:
P((“我”,“司”,“可”,“办理”,“正规发票”)|S) P ( ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” ) | S ) =P(“我”|S)P(“司”|S)P(“可”|S)P(“办理”|S)P(“正规发票”|S) = P ( “ 我 ” | S ) P ( “ 司 ” | S ) P ( “ 可 ” | S ) P ( “ 办 理 ” | S ) P ( “ 正 规 发 票 ” | S ) =P(“正规发票”|S)P(“可”|S)P(“办理”|S)P(“我”|S)P(“司”|S) = P ( “ 正 规 发 票 ” | S ) P ( “ 可 ” | S ) P ( “ 办 理 ” | S ) P ( “ 我 ” | S ) P ( “ 司 ” | S ) =P((“正规发票”,“可”,“办理”,“我”,“司”)|S) = P ( ( “ 正 规 发 票 ” , “ 可 ” , “ 办 理 ” , “ 我 ” , “ 司 ” ) | S )
也就是说,在朴素贝叶斯眼里,“我司可办理正规发票”与“正规发票可办理我司”完全相同。朴素贝叶斯失去了词语之间的顺序信息。这就相当于把所有的词汇扔进到一个袋子里随便搅和,贝叶斯都认为它们一样。因此这种情况也称作词袋子模型(bag of words)。
词袋子模型与人们的日常经验完全不同。比如,在条件独立假设的情况下,“武松打死了老虎”与“老虎打死了武松”被它认作一个意思了。恩,朴素贝叶斯就是这么单纯和直接,对比于其他分类器,好像是显得有那么点萌蠢。
虽然说朴素贝叶斯方法萌蠢萌蠢的,但实践证明在垃圾邮件识别的应用还令人诧异地好。Paul Graham先生自己简单做了一个朴素贝叶斯分类器,“1000封垃圾邮件能够被过滤掉995封,并且没有一个误判”。(Paul Graham《黑客与画家》)
那个…效果为啥好呢?
“有人对此提出了一个理论解释,并且建立了什么时候朴素贝叶斯的效果能够等价于非朴素贝叶斯的充要条件,这个解释的核心就是:有些独立假设在各个分类之间的分布都是均匀的所以对于似然的相对大小不产生影响;即便不是如此,也有很大的可能性各个独立假设所产生的消极影响或积极影响互相抵消,最终导致结果受到的影响不大。具体的数学公式请参考这篇 paper。”(刘未鹏《:平凡而又神奇的贝叶斯方法》)
恩,这个分类器中最简单直接看似萌蠢的小盆友『朴素贝叶斯』,实际上却是简单、实用、且强大的。
我们之前的垃圾邮件向量(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”),其中每个词都不重复。而这在现实中其实很少见。因为如果文本长度增加,或者分词方法改变,必然会有许多词重复出现,因此需要对这种情况进行进一步探讨。比如以下这段邮件:
“代开发票。增值税发票,正规发票。” 分词后为向量: (“代开”,“发票”,“增值税”,“发票”,“正规”,“发票”)
其中“发票”重复了三次。
如果我们考虑重复词语的情况,也就是说,重复的词语我们视为其出现多次,直接按条件独立假设的方式推导,则有
P((“代开”,“发票”,“增值税”,“发票”,“正规”,“发票”)|S) P ( ( “ 代 开 ” , “ 发 票 ” , “ 增 值 税 ” , “ 发 票 ” , “ 正 规 ” , “ 发 票 ” ) | S ) =P(“代开””|S)P(“发票”|S)P(“增值税”|S)P(“发票”|S)P(“正规”|S)P(“发票”|S) = P ( “ 代 开 ” ” | S ) P ( “ 发 票 ” | S ) P ( “ 增 值 税 ” | S ) P ( “ 发 票 ” | S ) P ( “ 正 规 ” | S ) P ( “ 发 票 ” | S ) =P(“代开””|S)P3(“发票”|S)P(“增值税”|S)P(“正规”|S) = P ( “ 代 开 ” ” | S ) P 3 ( “ 发 票 ” | S ) P ( “ 增 值 税 ” | S ) P ( “ 正 规 ” | S ) 注意这一项: P3(“发票”|S) P 3 ( “ 发 票 ” | S ) 。
在统计计算P(“发票”|S)时,每个被统计的垃圾邮件样本中重复的词语也统计多次。
P(“发票”|S)=每封垃圾邮件中出现“发票”的次数的总和每封垃圾邮件中所有词出现次数(计算重复次数)的总和 P ( “ 发 票 ” | S ) = 每 封 垃 圾 邮 件 中 出 现 “ 发 票 ” 的 次 数 的 总 和 每 封 垃 圾 邮 件 中 所 有 词 出 现 次 数 ( 计 算 重 复 次 数 ) 的 总 和
你看这个多次出现的结果,出现在概率的指数/次方上,因此这样的模型叫作多项式模型。
另一种更加简化的方法是将重复的词语都视为其只出现1次,
P((“代开”,“发票”,“增值税”,“发票”,“正规”,“发票”)|S) P ( ( “ 代 开 ” , “ 发 票 ” , “ 增 值 税 ” , “ 发 票 ” , “ 正 规 ” , “ 发 票 ” ) | S ) =P(“发票”|S)P(“代开””|S)P(“增值税”|S)P(“正规”|S) = P ( “ 发 票 ” | S ) P ( “ 代 开 ” ” | S ) P ( “ 增 值 税 ” | S ) P ( “ 正 规 ” | S )
统计计算 P(“词语”|S) P ( “ 词 语 ” | S ) 时也是如此。
P(“发票”|S)=出现“发票”的垃圾邮件的封数每封垃圾邮件中所有词出现次数(出现了只计算一次)的总和 P ( “ 发 票 ” | S ) = 出 现 “ 发 票 ” 的 垃 圾 邮 件 的 封 数 每 封 垃 圾 邮 件 中 所 有 词 出 现 次 数 ( 出 现 了 只 计 算 一 次 ) 的 总 和
这样的模型叫作伯努利模型(又称为二项独立模型)。这种方式更加简化与方便。当然它丢失了词频的信息,因此效果可能会差一些。
第三种方式是在计算句子概率时,不考虑重复词语出现的次数,但是在统计计算词语的概率P(“词语”|S)时,却考虑重复词语的出现次数,这样的模型可以叫作混合模型。
我们通过下图展示三种模型的关系。
具体实践中采用那种模型,关键看具体的业务场景,一个简单经验是,对于垃圾邮件识别,混合模型更好些。
我们继续观察(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”) 这句话。其实,像“我”、“可”之类词其实非常中性,无论其是否出现在垃圾邮件中都无法帮助判断的有用信息。所以可以直接不考虑这些典型的词。这些无助于我们分类的词语叫作“停用词”(Stop Words)。这样可以减少我们训练模型、判断分类的时间。 于是之前的句子就变成了(“司”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”) 。
我们进一步分析。以人类的经验,其实“正规发票”、“发票”这类的词如果出现的话,邮件作为垃圾邮件的概率非常大,可以作为我们区分垃圾邮件的“关键词”。而像“司”、“办理”、“优惠”这类的词则有点鸡肋,可能有助于分类,但又不那么强烈。如果想省事做个简单的分类器的话,则可以直接采用“关键词”进行统计与判断,剩下的词就可以先不管了。于是之前的垃圾邮件句子就变成了(“正规发票”,“发票”) 。这样就更加减少了我们训练模型、判断分类的时间,速度非常快。
“停用词”和“关键词”一般都可以提前靠人工经验指定。不同的“停用词”和“关键词”训练出来的分类器的效果也会有些差异。
我们来说个问题(中文NLP里问题超级多,哭瞎T_T),比如在计算以下独立条件假设的概率:
P((“我”,“司”,“可”,“办理”,“正规发票”)|S) P ( ( “ 我 ” , “ 司 ” , “ 可 ” , “ 办 理 ” , “ 正 规 发 票 ” ) | S ) =P(“我”|S)P(“司”|S)P(“可”|S)P(“办理”|S)P(“正规发票”|S) = P ( “ 我 ” | S ) P ( “ 司 ” | S ) P ( “ 可 ” | S ) P ( “ 办 理 ” | S ) P ( “ 正 规 发 票 ” | S )
我们扫描一下训练集,发现“正规发票”这个词从出现过!!!*,于是 P(“正规发票”|S)=0 P ( “ 正 规 发 票 ” | S ) = 0 …问题严重了,整个概率都变成0了!!!朴素贝叶斯方法面对一堆0,很凄惨地失效了…更残酷的是这种情况其实很常见,因为哪怕训练集再大,也可能有覆盖不到的词语。本质上还是样本数量太少,不满足大数定律,计算出来的概率失真**。为了解决这样的问题,一种分析思路就是直接不考虑这样的词语,但这种方法就相当于默认给P(“正规发票”|S)赋值为1。其实效果不太好,大量的统计信息给浪费掉了。我们进一步分析,既然可以默认赋值为1,为什么不能默认赋值为一个很小的数?这就是平滑技术的基本思路,依旧保持着一贯的作风,朴实/土
但是直接而有效
。
对于伯努利模型,P(“正规发票”|S)的一种平滑算法是:
P(“正规发票”|S)=出现“正规发票”的垃圾邮件的封数+1每封垃圾邮件中所有词出现次数(出现了只计算一次)的总和+2 P ( “ 正 规 发 票 ” | S ) = 出 现 “ 正 规 发 票 ” 的 垃 圾 邮 件 的 封 数 + 1 每 封 垃 圾 邮 件 中 所 有 词 出 现 次 数 ( 出 现 了 只 计 算 一 次 ) 的 总 和 + 2
对于多项式模型,P(“正规发票”| S)的一种平滑算法是:
P(“发票”|S)=每封垃圾邮件中出现“发票”的次数的总和+1每封垃圾邮件中所有词出现次数(计算重复次数)的总和+被统计的词表的词语数量 P ( “ 发 票 ” | S ) = 每 封 垃 圾 邮 件 中 出 现 “ 发 票 ” 的 次 数 的 总 和 + 1 每 封 垃 圾 邮 件 中 所 有 词 出 现 次 数 ( 计 算 重 复 次 数 ) 的 总 和 + 被 统 计 的 词 表 的 词 语 数 量
说起来,平滑技术的种类其实非常多,有兴趣的话回头我们专门拉个专题讲讲好了。这里只提一点,就是所有的平滑技术都是给未出现在训练集中的词语一个估计的概率,而相应地调低其他已经出现的词语的概率。
平滑技术是因为数据集太小而产生的现实需求。如果数据集足够大,平滑技术对结果的影响将会变小。
我们找了个最简单常见的例子:垃圾邮件识别,说明了一下朴素贝叶斯进行文本分类的思路过程。基本思路是先区分好训练集与测试集,对文本集合进行分词、去除标点符号等特征预处理的操作,然后使用条件独立假设,将原概率转换成词概率乘积,再进行后续的处理。
贝叶斯公式 + 条件独立假设 = 朴素贝叶斯方法
基于对重复词语在训练阶段与判断(测试)阶段的三种不同处理方式,我们相应的有伯努利模型、多项式模型和混合模型。在训练阶段,如果样本集合太小导致某些词语并未出现,我们可以采用平滑技术对其概率给一个估计值。而且并不是所有的词语都需要统计,我们可以按相应的“停用词”和“关键词”对模型进行进一步简化,提高训练和判断速度。
有同学可能会问:“何必费这么大劲算那么多词的概率?直接看邮件中有没有‘代开发票’、‘转售发票’之类的关键词不就得了?如果关键词比较多就认为是垃圾邮件呗。”
其实关键词匹配的方法如果有效的话真不必用朴素贝叶斯。毕竟这种方法简单嘛,就是一个字符串匹配。从历史来看,之前没有贝叶斯方法的时候主要也是用关键词匹配。但是这种方法准确率太低。我们在工作项目中也尝试过用关键词匹配的方法去进行文本分类,发现大量误报。感觉就像扔到垃圾箱的邮件99%都是正常的!这样的效果不忍直视。而加一个朴素贝叶斯方法就可能把误报率拉低近一个数量级,体验好得不要不要的。
另一个原因是词语会随着时间不断变化。发垃圾邮件的人也不傻,当他们发现自己的邮件被大量屏蔽之后,也会考虑采用新的方式,如变换文字、词语、句式、颜色等方式来绕过反垃圾邮件系统。比如对于垃圾邮件“我司可办理正规发票,17%增值税发票点数优惠”,他们采用火星文:“涐司岢办理㊣規髮票,17%增値稅髮票嚸數優蕙”,那么字符串匹配的方法又要重新找出这些火星文,一个一个找出关键词,重新写一些匹配规则。更可怕的是,这些规则可能相互之间的耦合关系异常复杂,要把它们梳理清楚又是大一个数量级的工作量。等这些规则失效了又要手动更新新的规则……无穷无尽猫鼠游戏最终会把猫给累死。
而朴素贝叶斯方法却显示出无比的优势。因为它是基于统计方法的,只要训练样本中有更新的垃圾邮件的新词语,哪怕它们是火星文,都能自动地把哪些更敏感的词语(如“髮”、“㊣”等)给凸显出来,并根据统计意义上的敏感性给他们分配适当的权重 ,这样就不需要什么人工了,非常省事。你只需要时不时地拿一些最新的样本扔到训练集中,重新训练一次即可。
小补充一下,对于火星文、同音字等替代语言,一般的分词技术可能会分得不准,最终可能只把一个一个字给分出来,成为“分字”。效果可能不会太好。也可以用过n-gram之类的语言模型,拿到最常见短语。当然,对于英文等天生自带空格来间隔单词的语言,分词则不是什么问题,使用朴素贝叶斯方法将会更加顺畅。
应用朴素贝叶斯方法的过程中,一些tricks能显著帮助工程解决问题。我们毕竟经验有限,无法将它们全都罗列出来,只能就所知的一点点经验与大家分享,欢迎批评指正。
我们提到用来识别垃圾邮件的方法是比较以下两个概率的大小(字母S表示“垃圾邮件”,字母H表示“正常邮件”):
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 ( “ 垃 圾 邮 件 ” )
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 ( “ 正 常 邮 件 ” )
但这里进行了很多乘法运算,计算的时间开销比较大。尤其是对于篇幅比较长的邮件,几万个数相乘起来还是非常花时间的。如果能把这些乘法变成加法则方便得多。刚好数学中的对数函数log就可以实现这样的功能。两边同时取对数(本文统一取底数为2),则上面的公式变为:
logC=logP(“我”|S)+logP(“司”|S)+logP(“可”|S)+logP(“办理”|S)+logP(“正规发票”|S) 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 )
+logP(“保真”|S)+logP(“增值税”|S)+logP(“发票”|S)+logP(“点数”|S)+logP(“优惠”|S)+logP(“垃圾邮件”) + 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 ( “ 垃 圾 邮 件 ” )
logC¯¯¯¯=logP(“我”|H)+logP(“司”|H)+logP(“可”|H)+logP(“办理”|H)+logP(“正规发票”|H) 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 )
+logP(“保真”|H)+logP(“增值税”|H)+logP(“发票”|H)+logP(“点数”|H)+logP(“优惠”|H)+logP(“正常邮件”) + 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 ( “ 正 常 邮 件 ” )
有同学可能要叫了:“做对数运算岂不会也很花时间?”的确如此,但是可以在训练阶段直接计算 logP l o g P ,然后把他们存在一张大的hash表里。在判断的时候直接提取hash表中已经计算好的对数概率,然后相加即可。这样使得判断所需要的计算时间被转移到了训练阶段,实时运行的时候速度就比之前快得多,这可不止几个数量级的提升。
对于二分类,我们还可以继续提高判断的速度。既然要比较 logC l o g C 和 logC¯¯¯¯ l o g C ¯ 的大小,那就可以直接将上下两式相减,并继续化简:
logCC¯¯¯¯=logP(“我”|S)P(“我”|H)+logP(“司”|S)P(“司”|H)+logP(“可”|S)P(“可”|H)+logP(“办理”|S)P(“办理”|H)+logP(“正规发票”|S)P(“正规发票”|H) l o g C C ¯ = l o g P ( “ 我 ” | S ) P ( “ 我 ” | H ) + l o g P ( “ 司 ” | S ) P ( “ 司 ” | H ) + l o g P ( “ 可 ” | S ) P ( “ 可 ” | H ) + l o g P ( “ 办 理 ” | S ) P ( “ 办 理 ” | H ) + l o g P ( “ 正 规 发 票 ” | S ) P ( “ 正 规 发 票 ” | H )
+logP(“保真”|S)P(“保真”|H)+logP(“增值税”|S)P(“增值税”|H)+logP(“发票”|S)P(“发票”|H)+logP(“点数”|S)P(“点数”|H)+logP(“优惠”|S)P(“优惠”|H)+logP(“正常邮件”|S)P(“正常邮件”) + l o g P ( “ 保 真 ” | S ) P ( “ 保 真 ” | H ) + l o g P ( “ 增 值 税 ” | S ) P ( “ 增 值 税 ” | H ) + l o g P ( “ 发 票 ” | S ) P ( “ 发 票 ” | H ) + l o g P ( “ 点 数 ” | S ) P ( “ 点 数 ” | H ) + l o g P ( “ 优 惠 ” | S ) P ( “ 优 惠 ” | H ) + l o g P ( “ 正 常 邮 件 ” | S ) P ( “ 正 常 邮 件 ” )
logCC¯¯¯¯ l o g C C ¯ 如果大于0则属于垃圾邮件。我们可以把其中每一项作为其对应词语的权重,比如 logP(“发票”|S)P(“发票”|H) l o g P ( “ 发 票 ” | S ) P ( “ 发 票 ” | H ) 就可以作为词语“发票”的权重,权重越大就越说明“发票”更可能是与“垃圾邮件”相关的特征。这样可以根据权重的大小来评估和筛选显著的特征,比如关键词。而这些权重值可以直接提前计算好而存在hash表中 。判断的时候直接将权重求和即可。
关键词hash表的样子如下,左列是权重,右列是其对应的词语,权重越高的说明越“关键”:
前文说过可以通过提前选取关键词来提高判断的速度。有一种方法可以省略提前选取关键词的步骤,就是直接选取一段文本中权重最高的K个词语,将其权重进行加和。比如Paul Graham 在《黑客与画家》中是选取邮件中权重最高的15个词语计算的。
通过权重hash表可知,如果是所有词语的权重,则权重有正有负。如果只选择权重最高的K个词语,则它们的权重基本都是正的。所以就不能像之前那样判断 logCC¯¯¯¯ l o g C C ¯ 是否大于0来区分邮件了。而这需要依靠经验选定一个正数的阈值(门槛值) ,依据 logCC¯¯¯¯ l o g C C ¯ 与该门槛值的大小来识别垃圾邮件。
如下图所示,蓝色点代表垃圾邮件,绿色点代表正常邮件,横坐标为计算出来的 logCC¯¯¯¯ l o g C C ¯ 值,中间的红线代表阈值。
选取topk个词语的方法对于篇幅变动不大的邮件样本比较有效。但是对篇幅过大或者过小的邮件则会有判断误差。
比如这个垃圾邮件的例子:(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)。分词出了10个词语,其中有“正规发票”、“发票”2个关键词。关键词的密度还是蛮大的,应该算是敏感邮件。但因为采用最高15个词语的权重求和,并且相应的阈值是基于15个词的情况有效,可能算出来的结果还小于之前的阈值,这就造成漏判了。
类似的,如果一封税务主题的邮件有1000个词语,其中只有“正规发票”、“发票”、“避税方法”3个权重比较大的词语,它们只是在正文表述中顺带提到的内容。关键词的密度被较长的篇幅稀释了,应该算是正常邮件。但是却被阈值判断成敏感邮件,造成误判了。
这两种情况都说明topk关键词的方法需要考虑篇幅的影响。这里有许多种处理方式,它们的基本思想都是选取词语的个数及对应的阈值要与篇幅的大小成正比,本文只介绍其中一种方方法:
对于长篇幅邮件,按一定的大小,比如每500字,将其分割成小的文本段落,再对小文本段落采用topk关键词的方法。只要其中有一个小文本段落超过阈值就判断整封邮件是垃圾邮件。
对于超短篇幅邮件,比如50字,可以按篇幅与标准比较篇幅的比例来选取topk,以确定应该匹配关键词语的个数。比如选取 50500×15≈2 50 500 × 15 ≈ 2 个词语进行匹配,相应的阈值可以是之前阈值的 215 2 15 。以此来判断则更合理。
到目前为止,我们对词语权重求和的过程都没有考虑邮件篇章结构的因素。比如“正规发票”如果出现在标题中应该比它出现在正文中对判断整个邮件的影响更大;而出现在段首句中又比其出现在段落正文中对判断整个邮件的影响更大。所以可以根据词语出现的位置,对其权重再乘以一个放大系数,以扩大其对整封邮件的影响,提高识别准确度。
比如一封邮件其标题是“正规发票”(假设标题的放大系数为2),段首句是“发票”,“点数”,“优惠”(假设段首的放大系数为1.5),剩下的句子是(“我”,“司”,“可”,“办理”,“保真”)。则计算 logCC¯¯¯¯ l o g C C ¯ 时的公式就可以调整为:
logCC¯¯¯¯=2×logP(“正规发票”|S)P(“正规发票”|H)+1.5×logP(“发票”|S)P(“发票”|H)+1.5×logP(“点数”|S)P(“点数”|H)+1.5×logP(“优惠”|S)P(“优惠”|H) l o g C C ¯ = 2 × l o g P ( “ 正 规 发 票 ” | S ) P ( “ 正 规 发 票 ” | H ) + 1.5 × l o g P ( “ 发 票 ” | S ) P ( “ 发 票 ” | H ) + 1.5 × l o g P ( “ 点 数 ” | S ) P ( “ 点 数 ” | H ) + 1.5 × l o g P ( “ 优 惠 ” | S ) P ( “ 优 惠 ” | H )
+logP(“我”|S)P(“我”|H)+logP(“司”|S)P(“司”|H)+log