音乐播放器
My Brain
 
文章 标签
8

Powered by Gridea | Theme: Fog
载入天数...
载入时分秒...

机器学习实战(六):朴素贝叶斯

朴素贝叶斯

一、基本思想

在机器学习中,朴素贝叶斯分类器是在强独立假设下基于贝叶斯定理的一系列简单概率分类器,强独立假设为:

D={(x1,y1),(x2,y2),...,(xn,yn)}\begin{aligned} &对于训练数据:D=\{(x_1,y_1),(x_2,y_2),...,(x_n,y_n)\} \end{aligned}

我们假设它是从未知分布中抽取的独立同分布,则有:

P(D)=P((x1,y1),(x2,y2),...,(xn,yn))=α=1nP(xα,yα)P(D)=P((x_1,y_1),(x_2,y_2),...,(x_n,y_n))=\prod_{\alpha=1}^nP(x_\alpha,y_\alpha)

如果我们有足够的数据,我们可以估算P(X,Y)P(X,Y),类似于上一节的硬币例子,我们想象一个巨大的骰子,每个可能的(X,Y)(X,Y)值都有一面。我们可以通过计数来估计某一特定方面出现的概率。

P^(x,y)=i=1nI(xi=xyi=y)nxi=xyi=yI(xi=xyi=y)=10\hat{P}(x,y)=\frac{\sum_{i=1}^nI(x_i=x\cap y_i=y)}{n}\\ x_i=x且y_i=y时,I(x_i=x\cap y_i=y)=1,否则为0

当然,如果我们主要感兴趣的是从特征xx预测标签yy,我们可以直接估计P(yx)P(y | x),而不是P(xy)P(x,y)。我们可以使用贝叶斯最优分类器对P(yx)P(y | x)进行预测:

P^(yx)=P^(xy)P^(y)P(x)=P^(x,y)P(x)=i=1nI(xi=xyi=y)/ni=1nI(xi=x)/n=i=1nI(xi=xyi=y)i=1nI(xi=x)\hat{P}(y|x)=\frac{\hat{P}(x|y)\hat{P}(y)}{P(x)}=\frac{\hat{P}(x,y)}{P(x)}=\frac{\sum_{i=1}^nI(x_i=x\cap y_i=y)/n}{\sum_{i=1}^nI(x_i=x)/n}=\frac{\sum_{i=1}^nI(x_i=x\cap y_i=y)}{\sum_{i=1}^nI(x_i=x)}

如上图韦恩图所示,我们最终的预测结果可以表示为:

P^(yx)=CB\hat{P}(y|x)=\frac{|C|}{|B|}

二、朴素贝叶斯算法

2.1 贝叶斯规则

朴素贝叶斯算法的核心是贝叶斯公式:

P(yx)=P(xy)P(y)P(x)P(y|x)=\frac{P(x|y)P(y)}{P(x)}

根据上述贝叶斯公式我们可以知道:如果我们可以预测P(xy)P(x|y)P(y)P(y),那么我们即可预测P(yx)P(y|x)
①预测P(y)P(y)很容易,假如yy取离散的值,我们只需要记录观察到结果为yy的次数即可:

P^(y=c)=i=1nI(yi=c)n=π^c\hat{P}(y=c)=\frac{\sum_{i=1}^nI(y_i=c)}{n}=\hat{\pi}_c

②但是预测P(xy)P(x|y)并不容易,因此我们需要引入朴素贝叶斯假设。

2.2 朴素贝叶斯假设

朴素贝叶斯假设为:

P(xy)=α=1dP(xαy)x=[x1,x2,...,xd],xαdxαP(x|y)=\prod_{\alpha=1}^dP(x_\alpha|y)\\ x=[x_1,x_2,...,x_d],x_\alpha是d维特征向量x在第\alpha维度上的值

即:对于给定的标签,其特征值是独立的,这是一个非常大胆的假设

经常使用朴素贝叶斯分类器的一个例子是垃圾邮件的过滤,此处数据为电子邮件,标签为是垃圾邮件还是非垃圾邮件;
朴素贝叶斯假设意味着电子邮件中的单词在条件上是独立的,因为我们知道电子邮件是否是垃圾邮件,显然这不是真的。无论是垃圾邮件还是非垃圾邮件都不是独立随机抽取的。然而,即使违反了这一假设,由此产生的分类器在实践中也可以很好地工作。

由此我们可以对P(xy)P(x|y)进行估计:假设朴素贝叶斯假设成立,则贝叶斯分类器定义如下:

h(x)=argmaxy P(yx)=argmaxy P(xy)P(y)P(x)=argmaxy P(xy)P(y)         =argmaxy (α=1dP(xαy))P(y)=argmaxy (α=1dlogP(xαy))+logP(y)\begin{aligned} &h(x)=\underset{y}{argmax}~P(y|x)=\underset{y}{argmax}~\frac{P(x|y)P(y)}{P(x)}=\underset{y}{argmax}~P(x|y)P(y)\\ &~~~~~~~~~=\underset{y}{argmax}~\big(\prod_{\alpha=1}^dP(x_\alpha|y)\big)P(y)=\underset{y}{argmax}~\big(\sum_{\alpha=1}^d\log P(x_\alpha|y)\big)+\log P(y) \end{aligned}

估计P(xαy)P(x_\alpha|y)P(y)P(y)都很容易,因此我们则可实现贝叶斯分类器。

三、估算P(xαy)P(x_\alpha|y)

3.1 Case#1:分类特征

分类特征:对于dd维特征向量xx,它的第α\alpha维特征xαx_\alphaKαK_\alpha个取值,即分类问题的特征

例如:年龄、性别、省份等特征,特们都有固定个数的KαK_\alpha个取值,xα{f1,f2,...,fKα}x_\alpha\in\{f_1,f_2,...,f_{K_\alpha} \}

P(xα=fjy=c)=[θjc]α,j=1Kαθjc=1P(x_\alpha=f_j|y=c)=[\theta_{jc}]_\alpha,\sum_{j=1}^{K_\alpha}\theta_{jc}=1

[θjc]α[θ_{jc}]_α是特征αα在假设标签是cc时,具有值fjf_j的概率。约束表明xαx_α必须具有一个类别{1Kα}\{1,…,K_α\}

下面我们对[θjc]α[\theta_{jc}]_\alpha进行估计:

[θjc]^α=i=1nI(xi=fjyi=c)+li=1nI(yi=c)+lKα\hat{[\theta_{jc}]}_\alpha=\frac{\sum_{i=1}^nI(x_i=f_j\cap y_i=c)+l}{\sum_{i=1}^nI(y_i=c)+lK_\alpha}

 l ~l~是一个平滑参数:
l=0l=0时,我们得到MLEMLE估计量,l>0l>0时,我们得到MAPMAP估计量
l=1l=1时,我们得到拉普拉斯平滑

最终我们得到的模型为:

h(x)=argmaxy (α=1d[θjc]^α)π^c=argmaxy (α=1dlog[θjc]α^)+logπ^c\begin{aligned} &h(x)=\underset{y}{argmax}~\big(\prod_{\alpha=1}^d\hat{[\theta_{jc}]}_\alpha\big)\hat\pi_c=\underset{y}{argmax}~\big(\sum_{\alpha=1}^d\log\hat{[\theta_{jc}]_\alpha}\big)+\log\hat\pi_c \end{aligned}

3.2 Case#2:多项式特征

多项式特征:特征值不是诸如男女之类的分类特征,而是计数值,即回归问题的特征,但是计数值是有限的

例如:垃圾邮件过滤的例子中,各个特征是不同的单词,维度dd即为单词表的大小,特征值是一个单词出现的次数,比如某个单词αα出现十次,即xαx_α=10,可能意味着该邮件为垃圾邮件。

xα{0,1,2,...,m},m=α=1dxαx_\alpha\in\{0,1,2,...,m\},m=\sum_{\alpha=1}^dx_\alpha

新的估计方式如下:P(xm,y=c)P(x|m,y=c)表示标签y=cy=c时,一个文本长度为mm的邮件中特征向量为xx的概率

P^(xm,y=c)=m!x1!x2!xd!α=1d(θαc)xα\hat{P}(x|m,y=c)=\frac{m!}{x_1!\cdot x_2!\cdot\cdot\cdot\cdot\cdot\cdot x_d!}\prod_{\alpha=1}^d(\theta_{\alpha c})^{x_\alpha}

θαc\theta_{\alpha c}是在标签y=cy=c时,选中特征向量中的xαx_\alpha的概率,有α=1dθαc=1\sum_{\alpha=1}^d\theta_{\alpha c}=1,以垃圾邮件为例,θαc\theta_{\alpha c}即为单词xαx_\alpha在文本中所占的比例。

下面我们对θαc\theta_{\alpha c}进行估计:

θ^αc=i=1nI(yi=c)xiα+li=1nI(yi=c)mi+ld\hat{\theta}_{\alpha c}=\frac{\sum_{i=1}^nI(y_i=c)x_{i\alpha}+l}{\sum_{i=1}^nI(y_i=c)m_i+l\cdot d}

xiαx_{i\alpha}是第ii个电子邮件的特征向量的第α\alpha维度的值,mi=α=1dxiαm_i=\sum_{\alpha=1}^dx_{i\alpha}即第ii个电子邮件中的文本总数

最终我们得到的模型为:

h(x)=argmaxy P(y=cx)=argmaxy (α=1d(θαc)xα)π^c=argmaxy (α=1dxαlogθαc)+logπ^ch(x)=\underset{y}{argmax}~P(y=c|x)=\underset{y}{argmax}~\big(\prod_{\alpha=1}^d(\theta_{\alpha c})^{x_\alpha}\big)\cdot\hat\pi_c=\underset{y}{argmax}~\big(\sum_{\alpha=1}^dx_\alpha\log\theta_{\alpha c}\big)+\log\hat\pi_c

3.3 Case#3:连续特征

连续特征:计数值是连续的值,例如身高体重,连续特征所对应的朴素贝叶斯也称为高斯朴素贝叶斯

xαRx_\alpha\in R

新的估计方式如下:

P^(xαy=c)=N(μαc,σαc2)=12πσαce12(xαμαcσαc)2\hat{P}(x_\alpha|y=c)=\mathcal{N}(\mu_{\alpha c},\sigma_{\alpha c}^2)=\frac1{\sqrt{2\pi}\sigma_{\alpha c}}e^{-\frac12(\frac{x_\alpha-\mu_{\alpha c}}{\sigma_{\alpha c}})^2}

注意,上面指定的模型基于我们对数据的假设,即每个特征α来自一类条件高斯分布。

对参数进行估计:

μ^αc=1nci=1nI(yi=c)xiασ^αc=1nci=1nI(yi=c)(xiαμαc)2nc=i=1nI(yi=c)\hat\mu_{\alpha c}=\frac1{n_c}\sum_{i=1}^nI(y_i=c)x_{i\alpha}\\\hat\sigma_{\alpha c}=\frac1{n_c}\sum_{i=1}^nI(y_i=c)(x_{i\alpha}-\mu_{\alpha c})^2\\ n_c=\sum_{i=1}^nI(y_i=c)

高斯朴素贝叶斯分类器本质上为逻辑回归模型:

P(yx)=11+ey(wTx+b)P(y|x)=\frac{1}{1+e^{-y(w^Tx+b)}}

四、朴素贝叶斯分类器

朴素贝叶斯分类器为一个线性分类器:

对于一个多项式特征的数据集,其进行分类的过程如下:假设yi{1,+1}y_i\in\{-1,+1\}且特征都是多项式特征,有:

h(x)=argmaxc P(y=cx)h(x)=+1,P(y=+1x)>P(y=1x)P(xy=+1)P(y=+1)P(x)>P(xy=1)P(y=1)P(x)P(y=+1)m!x1!x2!xd!α=1d(θα(+1))xα>P(y=1)m!x1!x2!xd!α=1d(θα(1))xαlogP(y=+1)+α=1dxαlogθα(+1)>logP(y=1)+α=1dxαlogθα(1)[logP(y=+1)logP(y=1)]+α=1dxα(logθα(+1)logθα(1))>0\begin{aligned} &h(x)=\underset{c}{argmax}~P(y=c|x)\\ &设h(x)=+1,则P(y=+1|x)>P(y=-1|x)\\ &根据贝叶斯公式,则有:\frac{P(x|y=+1)P(y=+1)}{P(x)}>\frac{P(x|y=-1)P(y=-1)}{P(x)}\\ &即:P(y=+1)\cdot\frac{m!}{x_1!\cdot x_2!\cdot\cdot\cdot\cdot\cdot\cdot x_d!}\prod_{\alpha=1}^d(\theta_{\alpha (+1)})^{x_\alpha}>P(y=-1)\cdot\frac{m!}{x_1!\cdot x_2!\cdot\cdot\cdot\cdot\cdot\cdot x_d!}\prod_{\alpha=1}^d(\theta_{\alpha (-1)})^{x_\alpha}\\ &即:\log P(y=+1)+\sum_{\alpha=1}^dx_\alpha\log \theta_{\alpha(+1)}>\log P(y=-1)+\sum_{\alpha=1}^dx_\alpha\log \theta_{\alpha(-1)}\\ &即:\big[\log P(y=+1)-\log P(y=-1)\big]+\sum_{\alpha=1}^dx_\alpha(\log \theta_{\alpha(+1)}-\log \theta_{\alpha(-1)})>0 \end{aligned}

我们可以发现上述正确分类的形式与感知机很像,我们可以进行如下变换:

b=logP(y=+1)logP(y=1),wα=logθα(+1)logθα(1)h(x)=+1wTx+b>0\begin{aligned} &令:b=\log P(y=+1)-\log P(y=-1),w_\alpha=\log \theta_{\alpha(+1)}-\log \theta_{\alpha(-1)}\\ &则:h(x)=+1\leftrightarrow w^Tx+b>0 \end{aligned}

与感知机的形式完全相同,最终我们将朴素贝叶斯分类器等效为了感知机。

五、算法实现

我们将通过手动实现与调库的方式去构造朴素贝叶斯模型。

5.1 数据集

本次我们使用的数据集为垃圾邮件数据集,其下载链接为:UCI Machine Learning Repository: SMS Spam Collection Data Set

下载后的SMSSpamCollection中的内容如下图所示,显然我们需要对文件进行处理整合,数据处理过程如下:

'''屏蔽warning'''
import warnings
warnings.filterwarnings("ignore")
'''导入重要的库'''
import numpy as np
import pandas as pd
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB

'''自定义处理文本处理函数'''
def text_dispose(text):
    '''
    因为数据集中的内容为英文,这里我使用 NLTK 库进行处理,如果选择中文数据更推荐使用 jieba 库
    :param text:
    :return: 处理后的文本
    '''
    # 将每个单词和符号分开
    sentences = nltk.sent_tokenize(text)
    words = [word for sent in sentences for word in nltk.word_tokenize(sent)]
    stops = stopwords.words('english')
    # 去除停用词
    words = [word for word in words if word not in stops]
    words = [word.lower() for word in words if len(word) >= 3]
    # 词汇化
    lemmatizer = WordNetLemmatizer()
    words = [lemmatizer.lemmatize(word) for word in words]
    dispose_text = ' '.join(words)
    return dispose_text

'''数据预处理'''
sms_data = open('data/SMSSpamCollection', 'r', encoding='utf-8')    #读取数据:5574个样本
X,y=[],[]
for line in sms_data.readlines():
    data = line.split('\t') #标签与正文之间用'\t'分隔
    y.append(data[0])
    X.append(text_dispose(data[1].split('\n')[0]))
sms_data.close()

上述数据集预处理过程中有几点需要注意:

①nltk报错问题,不妨尝试运行以下两部分代码完善nltk库的安装

import nltk
nltk.download("punkt")
import nltk
nltk.download("wordnet")
nltk.download("omw-1.4")

②TF-IDF特征矩阵:TF-IDF是Term Frequency - Inverse Document Frequency的缩写,即“词频——逆文本频率”。它由两部分组成,TF和IDF,也就是这两部分的乘积。TF指的就是常用的词频,即某个单词在当前文本中出现的频率。IDF,即“逆文本频率”,反映了一个单词在当前文本中的重要性

TF(t,D)=tDDIDF(t)=log+1t+1TFIDF(t,D)=TF(t,D)IDF(t)\begin{aligned} &TF(t,D)=\frac{单词t在文本D中出现的次数}{文本D的总单词数}\\ &IDF(t)=\log\frac{语料库文本总数+1}{包含词语t的文本总数+1}\\ &TF-IDF(t,D)=TF(t,D)\cdot IDF(t) \end{aligned}

我们处理后得到的 vectorizer.fit_transform(X) 输出的特征矩阵形式为(A,B) C(A,B)~CAA为文件索引,BB为特定词的向量索引,CC为文件AA中单词BBTFIDFTF-IDF分数

5.2 手动实现模型

我们选择多项式特征的数据集,即在上述数据处理的基础上将文本转为统计单词数量的矩阵,数据预处理与存储过程如下:

'''统计单词词频'''
vectorizer=CountVectorizer()
X=vectorizer.fit_transform(X).toarray()
y=np.where(np.array(y)=='spam',1,0)
df=pd.DataFrame(data=X)
df["label"]=y
df.to_csv("data/spam_count.csv",index=False)

参考多项式特征的模型,我们可以构造我们的训练代码:

θ^αc=i=1nI(yi=c)xiα+li=1nI(yi=c)mi+ld\hat{\theta}_{\alpha c}=\frac{\sum_{i=1}^nI(y_i=c)x_{i\alpha}+l}{\sum_{i=1}^nI(y_i=c)m_i+l\cdot d}

h(x)=argmaxy P(y=cx)=argmaxy (α=1d(θαc)xα)π^c=argmaxy (α=1dxαlogθαc)+logπ^ch(x)=\underset{y}{argmax}~P(y=c|x)=\underset{y}{argmax}~\big(\prod_{\alpha=1}^d(\theta_{\alpha c})^{x_\alpha}\big)\cdot\hat\pi_c=\underset{y}{argmax}~\big(\sum_{\alpha=1}^dx_\alpha\log\theta_{\alpha c}\big)+\log\hat\pi_c

'''屏蔽warning'''
import warnings
warnings.filterwarnings("ignore")
'''导入重要的库'''
import numpy as np
import pandas as pd
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB

class Naive_Bayes(object):
    def __init__(self,l):
        self.l=l    #平滑参数
    '''模型训练'''
    def fit(self,X_train, y_train):
        '''统计数目'''
        numTrainDocs = len(X_train) #计算训练电子邮件数目
        d = len(X_train[0])  #计算电子邮件的词条数
        '''计算P(y=c)'''
        pAbusive = sum(y_train)/float(numTrainDocs)#文档属于垃圾邮件类的的概率P(y=spam)=πc
        '''初始化'''
        p0Num = np.ones(d)
        p1Num = np.ones(d)
        p0Denom = self.l*d   #用于记录ham类的电子邮件单词总数+l
        p1Denom = self.l*d   #用于记录spam类的电子邮件单词总数+l
        '''数据统计'''
        for i in range(numTrainDocs):   #遍历每一个电子邮件
            if y_train[i] == 1:         #是垃圾邮件spam
                p1Num += X_train[i]     #用于spam类的各个单词数量
                p1Denom += sum(X_train[i])
            else:                       #是正常邮件ham
                p0Num += X_train[i]     #用于统计ham类的各个单词数量
                p0Denom += sum(X_train[i])
        #相除计算概率向量
        p1Vect = np.log(p1Num+self.l / p1Denom)
        p0Vect = np.log(p0Num+self.l / p0Denom)
        '''保留计算的数据'''
        self.p1Vect=p1Vect
        self.p0Vect=p0Vect
        self.pAbusive=pAbusive
    '''结果预测'''
    def predict(self,X_test):
        y_pred=[]
        for x in X_test:
            p1=np.dot(x,self.p1Vect)+np.log(self.pAbusive)
            p0=np.dot(x,self.p0Vect)+np.log(1-self.pAbusive)
            if p1>p0:
                y_pred.append(1)
            else:
                y_pred.append(0)
        return np.array(y_pred)

'''读取数据'''
df=pd.read_csv("data/spam_count.csv")
y=df['label'].values
df.drop("label",axis=1,inplace=True)
X=df.values
'''数据划分'''
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.2)#选取20%的数据作为测试集
naive_bayes=Naive_Bayes(1)
naive_bayes.fit(X_train,y_train)
y_pred=naive_bayes.predict(X_test)
print(accuracy_score(y_test,y_pred))    #0.979372197309417

5.3 调库实现模型

调库实现模型时,我们可以使用另一种方法,即上面提到过的TF-IDF特征矩阵,即连续特征的数据集,数据预处理与存储过程如下:

'''TF-IDF特征矩阵'''
vectorizer=TfidfVectorizer()
X=vectorizer.fit_transform(X).toarray()
y=np.where(np.array(y)=='spam',1,0)
df=pd.DataFrame(data=X)
df["label"]=y
df.to_csv("data/spam_tfidf.csv",index=False)

我们直接利用sklearn所提供的朴素贝叶斯分类器,观察分类情况,可以发现调库的准确率达到了0.9596412556053812,也较为准确。

'''屏蔽warning'''
import warnings
warnings.filterwarnings("ignore")
'''导入重要的库'''
import numpy as np
import pandas as pd
import nltk
from nltk.corpus import stopwords
from nltk.stem import WordNetLemmatizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB

'''读取数据'''
df=pd.read_csv("data/spam_tfidf.csv")
y=df['label'].values
df.drop("label",axis=1,inplace=True)
X=df.values
'''数据划分'''
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.2)#选取20%的数据作为测试集
naive_bayes=MultinomialNB()
naive_bayes.fit(X_train,y_train)
y_pred=naive_bayes.predict(X_test)
print(accuracy_score(y_test,y_pred))	#0.9596412556053812