前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >朴素贝叶斯

朴素贝叶斯

作者头像
故事尾音
发布2019-12-18 17:03:27
6650
发布2019-12-18 17:03:27
举报
文章被收录于专栏:NLP算法工程师之路

首先要明确的一点是朴素贝叶斯属于生成式模型,指导思想是贝叶斯公式。

文本分类

假设现在有一些评论数据,需要识别出这篇文本属于正向评论还是负面评论,也就是对文本进行分类。用数学语言描述就是: 假设已经有分好类的N篇文档:(d1,c1)、(d2,c2)、(d3,c3)……(dn,cn),di表示第i篇文档,ci表示第i个类别。目标是:寻找一个分类器,这个分类器能够:当丢给它一篇新文档d,它就输出d(最有可能)属于哪个类别c。

词袋模型

文本分类需要寻找文本的特征。而词袋模型就是表示文本特征的一种方式。词袋模型只考虑一篇文档中单词出现的频率(次数),用每个单词出现的频率作为文档的特征。

eCyJtP.png
eCyJtP.png

朴素贝叶斯分类器

朴素贝叶斯分类器是一个概率分类器。假设现有的类别C={c1,c2,……cm}。给定一篇文档d,文档d最有可能属于哪个类呢?这个问题用数学公式表示如下:

\hat{c}=\underset{c \in C}{\operatorname{argmax}} P(c | d)

c^ 就是:在所有的类别C={c1,c2,……cm} 中,使得:条件概率P(c|d)取最大值的类别。使用贝叶斯公式,将上式转换成如下形式:

\hat{c}=\underset{c \in C}{\operatorname{argmax}} P(c | d)=\underset{c \in C}{\operatorname{argmax}} \frac{P(d | c) P(c)}{P(d)}

对类别C中的每个类型,计算 (p(d|c)* p(c))/p(d) 的值,然后选取最大值对应的那个类型ci ,该ci就是最优解c^,因此,可以忽略掉分母 p(d),可以变成如下形式:

\hat{c}=\underset{c \in C}{\operatorname{argmax}} P(c | d)=\underset{c \in C}{\operatorname{argmax}} P(d | c) P(c)

这个公式由两部分组成,前面那部分P(d|c) 称为似然函数,后面那部分P(c) 称为先验概率。 前面提到使用词袋模型来表示 文档d,文档d的每个特征表示为:d={f1,f2,f3……fn},那么这里的特征fi 其实就是单词wi 出现的频率(次数),因此可以转化成如下形式:

\hat{c}=\underset{c \in C}{\operatorname{argmax}} \overbrace{P\left(f_{1}, f_{2}, \ldots, f_{n} | c\right)}^{\text { likelihood }} \overbrace{P(c)}^{\text { prior }}

对文档d 做个假设:假设各个特征之间是相互独立的。那么p(f1,f2……fn|c)=p(f1|c) p(f2|c) …… * p(fn|c),转化成如下形式:

c_{N B}=\underset{c \in C}{\operatorname{argmax}} P(c) \prod_{f \in F} P(f | c)

由于每个概率值很小(比如0.0001)若干个很小的概率值直接相乘,得到的结果会越来越小。为了避免计算过程出现下溢(underflower),引入对数函数Log,在 log space中进行计算。然后使用词袋模型的每个单词wi 出现频率作为特征,得到如下公式:

c_{N B}=\underset{c \in C}{\operatorname{largmax}} \log P(c)+\sum_{i \in \text {positions}} \log P\left(w_{i} | c\right)

训练朴素贝叶斯分类器

训练朴素贝叶斯的过程其实就是计算先验概率和似然函数的过程。 ①先验概率P(c)的计算 P(c)的意思是:在所有的文档中,类别为c的文档出现的概率有多大?假设训练数据中一共有Ndoc篇文档,只要数一下类别c的文档有多少个就能计算p(c)了,类别c的文档共有Nc篇,先验概率的计算公式如下:

\hat{P}(c)=\frac{N_{c}}{N_{d o c}}

先验概率其实就是准备干一件事情时,目前已经掌握了哪些信息了。 ②似然函数P(wi|c)的计算 由于是用词袋模型表示一篇文档d,对于文档d中的每个单词wi,找到训练数据集中所有类别为c的文档,数一数 单词wi在这些文档(类别为c)中出现的次数:count(wi,c), 然后,再数一数训练数据集中类别为c的文档一共有多少个单词,计算二者之间的比值,就是似然函数的值。似然函数计算公式如下:

\hat{P}\left(w_{i} | c\right)=\frac{\operatorname{count}\left(w_{i}, c\right)}{\sum_{w \in V} \operatorname{count}(w, c)}

其中V,就是词库。(有些单词在词库中,但是不属于类别C,那么 count(w,c)=0)

unknow words的情形

假设只考虑文本二分类:将文档分成 positve类别,或者negative类别,C={positive, negative}。 在训练数据集中,类别为positive的所有文档 都没有 包含 单词wi = fantastic(fantastic可能出现在类别为negative的文档中)那么count(wi=fantastic,ci=positive)=0 。那么:

而注意到前面公式五中的累乘,整篇文档的似然函数值为0,也就是说:如果文档d中有个单词fantastic在类别为c的训练数据集文档中从未出现过,那文档d被分类到类别c的概率为0,尽管文档d中还有一些其他单词(特征),而这些单词所代表的特征认为文档d应该被分类到类别c中。 解决方案就是 add-one smoothing。似然函数公式变成如下形式:

\hat{P}\left(w_{i} | c\right)=\frac{\operatorname{count}\left(w_{i}, c\right)+1}{\sum_{w \in V}(\operatorname{count}(w, c)+1)}=\frac{\operatorname{count}\left(w_{i}, c\right)+1}{\left(\sum_{w \in V} \operatorname{count}(w, c)\right)+|V|}

朴素贝叶斯分类示例

假设训练数据集有五篇文档,其中Negative类别的文档有三篇,用符号 ‘-‘ 标识;Positive类别的文档有二篇,用符号 ‘+’ 标识,它们的内容如下:

代码语言:javascript
复制
-  just plain boring
-  entirely predictable and lacks energy
-  no surprises and very few laughs
+  very powerful
+  the most fun film of the summer

测试数据集T 有一篇文档dt,内容如下:

代码语言:javascript
复制
predictable with no fun

朴素贝叶斯分类器会把“predictable with no fun”归为哪个类呢?根据训练朴素贝叶斯分类器的过程,需要计算先验概率和似然函数。

由于训练数据集中一共有5篇文档,其中类别 ‘+’ 的文档有2篇,类别为 ‘-‘ 的文档有3篇,因此先验概率:P(c)=P(‘-‘)=Nc/Ndoc=3/5=0.6

类别为’+’ 的文档有2篇,故 P(c)=P(‘+’)=Nc/Ndoc=2/5=0.4

对测试数据集文档dt中的每个单词,似然函数采用“add-one smoothing”处理,计算相应的似然概率:

首先单词 predictable 在训练数据集中 类别为’-‘的文档中只出现了1次,类别为’-‘的文档一共有14个单词,训练数据集中两种类型的文档加起来一共有23个单词,但是有三个单词(and、

very、the)重复出现了两次,故词库V的大小为 20。因此单词predictable对应的似然概率如下:

p(predictable|’-‘)=(1+1)/(14+20)=2/34

同理:p(predictable|’+’)=(0+1)/(9+20)=1/29 (predictable没有在类别为’+’的训练数据集中出现过)

类似地:p(no|’-‘)=(1+1)/(14+20) p(no|’+’)=(0+1)/(9+20)

p(fun|’-‘)=(0+1)/(14+20) p(fun|’+’)=(1+1)/(9+20)

因此,测试集中的文档d归类为 ‘-‘ 的概率为:0.6 (221)/343 = 6.110-5

测试集中的文档d归类为 ‘+’ 的概率为:0.4(112)/293 =3.210-5

比较上面两个概率的大小,就可以知道将“predictable with no fun”归为 ‘-‘ 类别。

代码实现

代码语言:javascript
复制
import numpy as np

def loaddata():
    """
    :return: 文本数据集 和 对应的 label
    """
    text=[['just','plain','boring'],

    ['entirely','predictable','and','lacks','energy'],

    ['no','surprises','and','very','few','laughs'],

    ['very','powerful'],

    ['the' ,'most', 'fun' ,'film' ,'of' ,'the', 'summer']]

    label=[0,0,0,1,1]

    return text,label

def createVocabList(text):
    """
    :param text: 文本数据集
    :return: 词语表
    """
    vocabSet=set([])
    for document in text:
        vocabSet=vocabSet|set(document)
    return list(vocabSet)

def bag_words_vec(vocab, text):
    """
    :param vocab: 词表
    :param text: 文本数据集
    :return: 通过词袋模型转换后的向量
    """
    data = []
    for t in text:
        vec = [0]*len(vocab)
        for word in t:
            if word in vocab:
                vec[vocab.index(word)]+=1
        data.append(vec)
    return data

class NB():
    def __init__(self,vocab):
        self.data = None
        self.label = None
        self.vocab = vocab
        self.vocab_len = len(self.vocab)

    def fit(self,data,label):
        self.data = data
        self.label = label

        # 计算每个类别的先验概率
        self.pc0 = label.count(0)/len(label) 
        self.pc1 = label.count(1)/len(label)

        # 分出不同类别的数据
        self.data0 = [data[i] for i,c in enumerate(label) if c==0] 
        self.data1 = [data[i] for i,c in enumerate(label) if c==1]

        # 计算每个类别中单词的数量,注意如果是len(),就不考虑重复元素,sum()考虑重复元素
        self.word_num_0 = sum([i for t in self.data0 for i in t if i!=0]) 
        self.word_num_1 = sum([i for t in self.data1 for i in t if i!=0])
        
        #打印不同类别的单词个数和词表长度
        print("类别0的单词个数:{},类别1的单词个数:{},总词表长度:{}".format(self.word_num_0,self.word_num_1,self.vocab_len))

        self.word_freq_0 = np.sum(self.data0, axis = 0) #计算每个类别中单词的频率
        self.word_freq_1 = np.sum(self.data1, axis = 0)

    def pridict(self, text):
        # 预测过程
        
        pred = []
        
        # 对于预测集的每一个文本
        for t in text:
            
            # 计算属于class 0的概率
            p0 = []
            for w in t:
                # 不在词表中的不计算
                if w in vocab:
                    p0.append((self.word_freq_0[self.vocab.index(w)] + 1)/(self.word_num_0+ self.vocab_len))
            
            # 计算属于class 0的概率
            p1 = []
            for w in t:
                # 不在词表中的不计算
                if w in vocab:
                    p1.append((self.word_freq_1[self.vocab.index(w)] + 1)/(self.word_num_1+self.vocab_len))
            
            print(p0)
            print(p1)

            print(self.pc0*np.prod(p0))
            print(self.pc1*np.prod(p1))

            if self.pc0*np.prod(p0)> self.pc1*np.prod(p1):
                pred.append(0)
            else:
                pred.append(1)

        return pred


if __name__ == '__main__':

    text,label = loaddata()
    vocab = createVocabList(text)
    data = bag_words_vec(vocab, text)


    pred_text = [["predictable","with","no","fun"]]

    model = NB(vocab)
    model.fit(data,label)
    result = model.pridict(pred_text)
    print(result)

输出结果:

代码语言:javascript
复制
类别0的单词个数:14,类别1的单词个数:9,总词表长度:20
[0.058823529411764705, 0.058823529411764705, 0.029411764705882353]
[0.034482758620689655, 0.034482758620689655, 0.06896551724137931]
6.106248727864848e-05
3.280167288531715e-05
[0]
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-07-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文本分类
  • 词袋模型
  • 朴素贝叶斯分类器
  • 训练朴素贝叶斯分类器
  • unknow words的情形
  • 朴素贝叶斯分类示例
  • 代码实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档