NB到语言模型 +简易语言检测器

1、朴素贝叶斯(Naive Bayes),“Naive”在何处?

加上条件独立假设的贝叶斯方法就是朴素贝叶斯方法(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)

也就是说,在朴素贝叶斯眼里,“我司可办理正规发票”与“正规发票可办理我司”完全相同。朴素贝叶斯失去了词语之间的顺序信息。这就相当于把所有的词汇扔进到一个袋子里随便搅和,贝叶斯都认为它们一样。因此这种情况也称作词袋子模型(bag of words)。

2、处理重复词语的三种方式

我们之前的垃圾邮件向量(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”),其中每个词都不重复。而这在现实中其实很少见。因为如果文本长度增加,或者分词方法改变,必然会有许多词重复出现,因此需要对这种情况进行进一步探讨。比如以下这段邮件:

“代开发票。增值税发票,正规发票。” 分词后为向量: (“代开”,“发票”,“增值税”,“发票”,“正规”,“发票”)

其中“发票”重复了三次。

2.1多项式模型:

如果我们考虑重复词语的情况,也就是说,重复的词语我们视为其出现多次,直接按条件独立假设的方式推导,则有

P((“代开”,“发票”,“增值税”,“发票”,“正规”,“发票”)|S)  =P(“代开””|S)P(“发票”|S)P(“增值税”|S)P(“发票”|S)P(“正规”|S)P(“发票”|S) =P(“代开””|S)P3(“发票”|S)P(“增值税”|S)P(“正规”|S) 注意这一项:P3(“发票”|S)

在统计计算P(“发票”|S)时,每个被统计的垃圾邮件样本中重复的词语也统计多次。

P(“发票”|S)=(每封垃圾邮件中出现“发票”的次数的总和)/(每封垃圾邮件中所有词出现次数(计算重复次数)的总和)

你看这个多次出现的结果,出现在概率的指数/次方上,因此这样的模型叫作多项式模型。

2.2伯努利模型

另一种更加简化的方法是将重复的词语都视为其只出现1次,

P((“代开”,“发票”,“增值税”,“发票”,“正规”,“发票”)|S)  =P(“发票”|S)P(“代开””|S)P(“增值税”|S)P(“正规”|S)

统计计算 P(“词语”|S) 时也是如此。

P(“发票”|S)=(出现“发票”的垃圾邮件的封数)/(每封垃圾邮件中所有词出现次数(出现了只计算一次)的总和)

这样的模型叫作伯努利模型(又称为二项独立模型)。这种方式更加简化与方便。当然它丢失了词频的信息,因此效果可能会差一些。

2.3混合模型

第三种方式是在计算句子概率时,不考虑重复词语出现的次数,但是在统计计算词语的概率P(“词语”|S)时,却考虑重复词语的出现次数,这样的模型可以叫作混合模型。
在这里插入图片描述
具体实践中采用那种模型,关键看具体的业务场景,一个简单经验是,对于垃圾邮件识别,混合模型更好些

3去除停用词与选择关键词

我们继续观察(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”) 这句话。其实,像“我”、“可”之类词其实非常中性,无论其是否出现在垃圾邮件中都无法帮助判断的有用信息。所以可以直接不考虑这些典型的词。这些无助于我们分类的词语叫作“停用词”(Stop Words)。这样可以减少我们训练模型、判断分类的时间。 于是之前的句子就变成了(“司”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”) 。
我们进一步分析。以人类的经验,其实“正规发票”、“发票”这类的词如果出现的话,邮件作为垃圾邮件的概率非常大,可以作为我们区分垃圾邮件的“关键词”。而像“司”、“办理”、“优惠”这类的词则有点鸡肋,可能有助于分类,但又不那么强烈。如果想省事做个简单的分类器的话,则可以直接采用“关键词”进行统计与判断,剩下的词就可以先不管了。于是之前的垃圾邮件句子就变成了(“正规发票”,“发票”) 。这样就更加减少了我们训练模型、判断分类的时间,速度非常快。
“停用词”和“关键词”一般都可以提前靠人工经验指定。不同的“停用词”和“关键词”训练出来的分类器的效果也会有些差异。

4实际工程中的tricks

4.1 取对数
将连乘转换为加
4.2转换权重
对于二分类,我们还可以继续提高判断的速度。既然要比较 logC 和 logC’ 的大小,那就可以直接将上下两式相减,并继续化简:

logC/logC’ 如果大于0则属于垃圾邮件。我们可以把其中每一项作为其对应词语的权重,比如logP((“发票”|S)/P(“发票”|H)) 就可以作为词语“发票”的权重,权重越大就越说明“发票”更可能是与“垃圾邮件”相关的特征。这**样可以根据权重的大小来评估和筛选显著的特征,比如关键词。**而这些权重值可以直接提前计算好而存在hash表中 。判断的时候直接将权重求和即可。
4.3选取topK的关键词
前文说过可以通过提前选取关键词来提高判断的速度。有一种方法可以省略提前选取关键词的步骤,就是直接选取一段文本中权重最高的K个词语,将其权重进行加和。比如Paul Graham 在《黑客与画家》中是选取邮件中权重最高的15个词语计算的。

通过权重hash表可知,如果是所有词语的权重,则权重有正有负。如果只选择权重最高的K个词语,则它们的权重基本都是正的。所以就不能像之前那样判断 log(C/C’) 是否大于0来区分邮件了。而这需要依靠经验选定一个正数的阈值(门槛值) ,依据 log(C/C’)与该门槛值的大小来识别垃圾邮件。

如下图所示,蓝色点代表垃圾邮件,绿色点代表正常邮件,横坐标为计算出来的 log(C/C’)值,中间的红线代表阈值。
在这里插入图片描述
4.4分割样本
选取topk个词语的方法对于篇幅变动不大的邮件样本比较有效。但是对篇幅过大或者过小的邮件则会有判断误差。

比如这个垃圾邮件的例子:(“我”,“司”,“可”,“办理”,“正规发票”,“保真”,“增值税”,“发票”,“点数”,“优惠”)。分词出了10个词语,其中有“正规发票”、“发票”2个关键词。关键词的密度还是蛮大的,应该算是敏感邮件。但因为采用最高15个词语的权重求和,并且相应的阈值是基于15个词的情况有效,可能算出来的结果还小于之前的阈值,这就造成漏判了。

类似的,如果一封税务主题的邮件有1000个词语,其中只有“正规发票”、“发票”、“避税方法”3个权重比较大的词语,它们只是在正文表述中顺带提到的内容。关键词的密度被较长的篇幅稀释了,应该算是正常邮件。但是却被阈值判断成敏感邮件,造成误判了。

这两种情况都说明topk关键词的方法需要考虑篇幅的影响。这里有许多种处理方式,它们的基本思想都是选取词语的个数及对应的阈值要与篇幅的大小成正比,本文只介绍其中一种方方法:

(1)对于长篇幅邮件,按一定的大小,比如每500字,将其分割成小的文本段落,再对小文本段落采用topk关键词的方法。只要其中有一个小文本段落超过阈值就判断整封邮件是垃圾邮件。

(2)对于超短篇幅邮件,比如50字,可以按篇幅与标准比较篇幅的比例来选取topk,以确定应该匹配关键词语的个数。比如选取 50500×15≈2 个词语进行匹配,相应的阈值可以是之前阈值的 215 。以此来判断则更合理。
4.5位置权重
到目前为止,我们对词语权重求和的过程都没有考虑邮件篇章结构的因素。比如“正规发票”如果出现在标题中应该比它出现在正文中对判断整个邮件的影响更大;而出现在段首句中又比其出现在段落正文中对判断整个邮件的影响更大。所以可以根据词语出现的位置,对其权重再乘以一个放大系数,以扩大其对整封邮件的影响,提高识别准确度

比如一封邮件其标题是“正规发票”(假设标题的放大系数为2),段首句是“发票”,“点数”,“优惠”(假设段首的放大系数为1.5),剩下的句子是(“我”,“司”,“可”,“办理”,“保真”)
4.6蜜罐
我们通过辛辛苦苦的统计与计算,好不容易得到了不同词语的权重。然而这并不是一劳永逸的。我们我们之前交代过,词语及其权重会随着时间不断变化,需要时不时地用最新的样本来训练以更新词语及其权重。

而搜集最新垃圾邮件有一个技巧,就是随便注册一些邮箱,然后将它们公布在各大论坛上。接下来就坐等一个月,到时候收到的邮件就绝大部分都是垃圾邮件了(好奸诈)。再找一些正常的邮件,基本就能够训练了。这些用于自动搜集垃圾邮件的邮箱叫做“蜜罐”。**“蜜罐”是网络安全领域常用的手段,因其原理类似诱捕昆虫的装有蜜的罐子而得名。**比如杀毒软件公司会利用蜜罐来监视或获得计算机网络中的病毒样本、攻击行为等。

5朴素贝叶斯方法的常见应用

说了这么多理论的问题,咱们就可以探讨一下(朴素)贝叶斯方法在自然语言处理中的一些常见应用了。以下只是从原理上进行探讨,对于具体的技术细节顾及不多。
5.1褒贬分析
一个比较常见的应用场景是情感褒贬分析。比如你要统计微博上人们对一个新上映电影的褒贬程度评价:好片还是烂片。但是一条一条地看微博是根本看不过来,只能用自动化的方法。我们可以有一个很粗略的思路:
(1)首先是用爬虫将微博上提到这个电影名字的微博全都抓取下来,比如有10万条。
(2)然后用训练好的朴素贝叶斯分类器分别判断这些微博对电影是好评还是差评。
(3)最后统计出这些好评的影评占所有样本中的比例,就能形成微博网友对这个电影综合评价的大致估计。

接下来的核心问题就是训练出一个靠谱的分类器。首先需要有打好标签的文本。这个好找,豆瓣影评上就有大量网友对之前电影的评价,并且对电影进行1星到5星的评价。我们可以认为3星以上的评论都是好评,3星以下的评论都是差评。这样就分别得到了好评差评两类的语料样本。剩下就可以用朴素贝叶斯方法进行训练了。基本思路如下:
(1)训练与测试样本:豆瓣影评的网友评论,用爬虫抓取下100万条。
(2)标签:3星以上的是好评,3星以下的是差评。
(3)特征:豆瓣评论分词后的词语。一个简单的方法是只选择其中的形容词,网上有大量的情绪词库可以为我们所用。
(4)然后再用常规的朴素贝叶斯方法进行训练。
但是由于自然语言的特点,在提取特征的过程当中,有一些tricks需要注意:
(1)!!!对否定句进行特别的处理。比如这句话“我不是很喜欢部电影,因为它让我开心不起来。”其中两个形容词“喜欢”、“开心”都是褒义词,但是因为句子的否定句,所以整体是贬义的。有一种比较简单粗暴的处理方式,就是“对否定词(“不”、“非”、“没”等)与句尾标点之间的所有形容词都采用其否定形式” 。则这句话中提取出来的形容词就应该是“不喜欢”和“不开心”。
(2)一般说来,最相关的情感词在一些文本片段中仅仅出现一次,词频模型起得作用有限,甚至是负作用,则
使用伯努利模型代替多项式模型
。这种情况在微博这样的小篇幅文本中似乎不太明显,但是在博客、空间、论坛之类允许长篇幅文本出现的平台中需要注意。
(3)其实,副词对情感的评价有一定影响。“不很喜欢”与“很不喜欢”的程度就有很大差异。但如果是朴素贝叶斯方法的话比较难处理这样的情况。我们可以考虑用语言模型或者加入词性标注的信息进行综合判断。这些内容我们将在之后的文章进行探讨。

当然经过以上的处理,情感分析还是会有一部分误判。这里涉及到许多问题,都是情感分析的难点:

情绪表达的含蓄微妙:“导演你出来,我保证不打死你。”你让机器怎么判断是褒还是贬?

转折性表达:“我非常喜欢这些大牌演员,非常崇拜这个导演,非常赞赏这个剧本,非常欣赏他们的预告片,我甚至为了这部影片整整期待了一年,最后进了电影院发现这是个噩梦。” 五个褒义的形容词、副词对一个不那么贬义的词。机器自然判断成褒义,但这句话是妥妥的贬义。

6、语言检测器练习

import pandas as pd
from sklearn.model_selection import train_test_split
import re
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB


dataSet = pd.read_csv('H:/languageDetectdata.csv',names=['sentence','country'])
# print(dataSet.head(5))
x,y = dataSet['sentence'],dataSet['country']
x_train,x_test,y_train,y_test = train_test_split(x,y,random_state=1)
# print(len(x_train))
# print(len(x_test))


#定义降噪函数
def removeNoise(document):
    noise_patten = re.compile('|'.join(['http\S+','\@\w+','\#\w+']))
    clean_text = re.sub(noise_patten,'',document)
    return clean_text.strip()

# print(removeNoise("Trump images are now more popular than cat gifs. @trump #trends http://www.trumptrends.html"))
# Trump images are now more popular than cat gifs.


#抽取有用的特征
vec = CountVectorizer(
    lowercase=True,         #lowercase the text
    analyzer='char_wb',      #tokenise by charater ngrams
    ngram_range=(1,2),       #use ngram of size 1 and 2
    max_features=1000,        #keep the most 1000 ngtams
    preprocessor = removeNoise
)
vec.fit(x_train)

def get_feature(x):
    return vec.transform(x)

classifier = MultinomialNB()
classifier.fit(vec.transform(x_train),y_train)

scores = classifier.score(vec.transform(x_test),y_test)
print(scores)                     

结果

0.9770621967357741

代码规范化后:

class LanguageDetector():

    def __init__(self, classifier=MultinomialNB()):
        self.classifier = classifier
        self.vectorizer = CountVectorizer(ngram_range=(1,2), max_features=1000, preprocessor=self._remove_noise)

    def _remove_noise(self, document):
        noise_pattern = re.compile("|".join(["http\S+", "\@\w+", "\#\w+"]))
        clean_text = re.sub(noise_pattern, "", document)
        return clean_text

    def features(self, X):
        return self.vectorizer.transform(X)

    def fit(self, X, y):
        self.vectorizer.fit(X)
        self.classifier.fit(self.features(X), y)

    def predict(self, x):
        return self.classifier.predict(self.features([x]))

    def score(self, X, y):
        return self.classifier.score(self.features(X), y)

dataSet = pd.read_csv('H:/languageDetectdata.csv',names=['sentence','country'])
# print(dataSet.head(5))
x,y = dataSet['sentence'],dataSet['country']
x_train,x_test,y_train,y_test = train_test_split(x,y,random_state=1)

language_detector = LanguageDetector()
language_detector.fit(x_train,y_train)
print(language_detector.predict('this is an english sentence'))
['en']

猜你喜欢

转载自blog.csdn.net/weixin_40924580/article/details/83930246