朴素贝叶斯分类

极大似然估计(MLE)

在日常生活中有一个常识:概率大的事件越有可能发生,极大似然估计便是利用这一思想去对参数进行估计:

​ 假设X为离散型随机变量,其分布律为P{X=x} = p{x;θ},存在样本X1,X2,X3…,观察值为x1,x2,x3,…构造似然函数:
L ( Θ ) = P { X 1 = x 1 , X 2 = x 2 , . . . , X n = x n } = ∏ p { x ; Θ } L(\Theta) = P\{ X1=x1,X2=x2,...,Xn=xn\} = \prod p\{x;\Theta\} L(Θ)=P{X1=x1,X2=x2,...,Xn=xn}=p{x;Θ}
该函数求最大值即是满足让当前已发生事件发生概率最大的参数值,为了方便求值,通常将其转换为对数似然函数
KaTeX parse error: Undefined control sequence: \ at position 46: …eta\ \ \ \ \ \ \̲ ̲

最大后验概率(MAP)

当前统计学界分为两大学派,频率学派与贝叶斯学派,二者对于概率的定义不同,从而在计算概率的方法上也有不同:

频率学派:频率学派认为θ是固定的,未知的,通过大量重复的实验,即可得到θ的值

贝叶斯学派:贝叶斯学派认为,概率依赖于已有的经验与知识,也就是说θ满足某个分布H(),在求解θ的值时,应该考虑θ的先验概率,从而计算出相应的概率。

最大后验概率便是贝叶斯思想下常用的参数估计方法:
a r g max ⁡ log ⁡   p ( θ ∣ x ) = a r g max ⁡ log ⁡   p ( x ∣ θ ) p ( θ ) p ( x ) arg\max\log\ p(\theta|x) = arg\max\log\ \frac{p(x|\theta)p(\theta)}{p(x)} argmaxlog p(θx)=argmaxlog p(x)p(xθ)p(θ)
分母与θ无关,求解分子式:
a r g max ⁡ log ⁡   p ( x ∣ θ ) p ( θ ) arg\max\log\ p(x|\theta)p(\theta) argmaxlog p(xθ)p(θ)
可以观察到,MAP可看作由MLE与θ自身分布共同组合而成的,也可以说MLE中,认为p(Θ)为1,即服从U(0,1)。

贝叶斯估计(BE)

贝叶斯估计与MAP前半部分是一样的,相比于MAP,BE将θ完全看作一个概率变量,而不是一个确定的值:
p ( θ ∣ x ) = α p ( x ∣ θ ) p ( θ ) = α ∏ p ( x ∣ θ ) p ( θ ) ( 1 ) p(\theta|x) = \alpha p(x|\theta)p(\theta) = \alpha \prod p(x|\theta)p(\theta) (1) p(θx)=αp(xθ)p(θ)=αp(xθ)p(θ)1
α是与θ无关的部分。

在MAP中,只需要计算出(1)式的最大值即可,但BE会要求出θ分布的期望值作为最后的估计值。

朴素贝叶斯分类器

了解完概率基础知识之后,我们将使用贝努利模型实现一个朴素贝叶斯分类器,对垃圾邮件进行分类。

在对文本数据进行分类任务时,不可能直接对文本进行操作,要将其转换成数字向量,方便我们进一步操作。

文本向量化

读取样本邮件内容,首先对邮件内容进行清洗(包括去掉非字母数字及小写化):

def textParse(text):
    """
    文本分词
    :param text:
    :return: 分词列表
    """
    import re
    text = re.sub(r'[^a-zA-Z0-9_]',' ',text)# 0个或多个非字母数字或下划线字符
    listOfTokens = re.split(' ',text) 
    return [token.lower() for token in listOfTokens if len(token) > 2]

使用清洗后的分词列表,构建一个包含所有出现过的词并且每个词不重复的向量:

def createVocabList(dataset):
    """
    创建词典列表,包含出现过的每一个词,且不重复
    :param dataset:
    :return:
    """
    vocabSet = set([])
    for document in dataset:
        #print(vocabSet)
        vocabSet = vocabSet | set(document) # 求两个集合的并集
    return list(vocabSet)
创建训练集及测试集

这里采用留存交叉验证方法创建训练集及测试集

trainingSet = list(range(50)); testSet = []
    # 随机选择10封邮件创建测试集
    for i in range(10):
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
构建词集模型

构建词集模型,描述邮件内容中出现的单词,其实是一个one-hot向量

def setOfWords2Vec(vecabList, inputSet):
    """
    词集模型SOW,每个单词只出现一次
    :param vecabList:   
    :param inputSet:
    :return:
    """
    returnVec = [0] * len(vecabList)
    for word in inputSet:
        if word in vecabList:
            returnVec[vecabList.index(word)] = 1
        else:
            print("the word is not blonging into this List ", word)
    return returnVec
训练参数

根据贝叶斯模型,我们要训练的参数有
a r g max ⁡ log ⁡   p ( x ∣ θ ) p ( θ ) 求 : p ( θ ) 、 p ( x ∣ θ ) arg\max\log\ p(x|\theta)p(\theta) \\求:p(\theta)、p(x|\theta) argmaxlog p(xθ)p(θ):p(θ)p(xθ)
这里我们计算通过每一个词Wi是垃圾邮件的概率p(Wi|θ),将一封邮件中所有的p(Wi|θ)p(θ)求联合概率

因为程序会下溢出,当连乘的p(w|θ)值很小时,乘积最后会四舍五入为0,所以转化为对数概率求和。
∏ p ( w ∣ θ ) p ( θ ) = ∑ l o g ( p ( w ∣ θ ) p ( θ ) ) \prod p(w|\theta)p(\theta) = \sum log(p(w|\theta)p(\theta)) p(wθ)p(θ)=log(p(wθ)p(θ))
当某个词Wi未出现,即某属性值未出现时,对应SOW位置上的值为0,则p(Wi|θ) = 0,但在之后的连乘中,该值会将其他p(Wi|θ)抹去,所有这里使用到一个拉普拉斯平滑的思想:
P ( c ) = ∣ D c ∣ + 1 ∣ D ∣ + N P ( X i ∣ c ) = ∣ D ( c , x i ) ∣ + 1 ∣ D c ∣ + N i P(c) = \frac{|Dc| + 1}{|D|+N} \\ P(Xi|c) = \frac{|D(c,xi)| + 1}{|Dc| + Ni} P(c)=D+NDc+1P(Xic)=Dc+NiD(c,xi)+1
N为类别数,Ni为该属性上可取的类别数

def trainNB0(trainMatrix, trainCategory):
    """
    训练特征向量
    :param trainMatrix:
    :param trainCategory:
    :return: p1Vect(垃圾邮件的概率向量),p0Vect(非垃圾邮件的概率向量),pAbusive(先验概率)
    """
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    pAbusive = sum(trainCategory)/float(numTrainDocs) # 为1的概率,也就是为垃圾邮件的先验概率
    p0Num = ones(numWords) # 统计对应单词出现次数,拉普拉斯平滑
    p1Num = ones(numWords)
    p0Denom = 2.0 # 总词数,初始化为2,拉普拉斯平滑
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 取对数,防止下溢出
    p1Vect = log(p1Num/p1Denom)
    p0Vect = log(p0Num/p0Denom)
    # 如不取对数,准确率会大大降低
    # p1Vect = p1Num/p1Denom
    # p0Vect = (p0Num/p0Denom)
    return p0Vect,p1Vect,pAbusive
测试结果

将测试集转换为SOW结构,分别计算二分类的概率,取概率大者。

def classifyNB(p0V,p1V,vec2Classify,pClass1):
    """
    分类
    :param p0V:
    :param p1V:
    :param vec2Classify:
    :param pClass1:
    :return: 1 - 垃圾邮件, 0 - 非垃圾邮件
    """
    p1 = sum(vec2Classify * p1V) + log(pClass1)
    p0 = sum(vec2Classify * p0V) + log(1 - pClass1)
    if p1 > p0:
        return 1
    return 0
完整代码
def spamText():
    """
    垃圾邮件分类
    :return:
    """
    docList = []; classList = []; fullText = []
    for i in range(1,26):
        wordList = textParse(open("./email/spam/"+str(i)+".txt",'r').read())
        docList.append(wordList)
        classList.append(1) # 垃圾邮件
        fullText.extend(wordList) #
        wordList = textParse(open("./email/ham/"+str(i)+".txt",'r').read())
        docList.append(wordList)
        classList.append(0)
        fullText.extend(wordList)
    vocabList = createVocabList(docList) # 创建词典
    trainingSet = list(range(50)); testSet = []
    # 随机选择10封邮件创建测试集
    for i in range(10):
        randIndex = int(random.uniform(0,len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del(trainingSet[randIndex])
    #print(trainingSet)
    trainMat = []; trainClasses = [];
    for docIndex in trainingSet:
        trainMat.append(setOfWords2Vec(vocabList,docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V,p1V,pSpam = trainNB0(trainMat,trainClasses)
    # print(p0V,p1V,pSpam)
    # 测试
    errorCount = 0
    for docIndex in testSet:
        wordVector = setOfWords2Vec(vocabList,docList[docIndex])
        if(classifyNB(p0V,p1V,wordVector,pSpam)) != classList[docIndex]:
            errorCount += 1
    # print(errorCount,len(testSet))
    print("error rate is %.2f%%"%(float(errorCount)/len(testSet)*100))

以上参考自《机器学习实战》第四章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值