朴素贝叶斯

哈尔滨工程大学-537

算法原理:

代码实现:

首先导入将会使用到的库:numpy、re、random

import numpy as np
import re
import random

定义一个text_parse函数,将文档进行分词(将整篇文档切分成单词)解析,得到长度大于2的词的列表。

def text_parse(big_string):
    list_of_tokens = re.split(r"\W*", big_string)  # W为大写
    return [tok.lower() for tok in list_of_tokens if len(tok) > 2]
# 遍历列表中每个元素,取出长度大于2的,转化成小写,并以列表的形式return

创建一个所有文档中出现的不重复词的列表,构造向量空间。

def create_vocab_list(data_set):
    vocab_set = set([])  # 创建一个空集合
    for document in data_set:      # 取文档集合中的每一个文档
        vocab_set = vocab_set | set(document)   # 依次与vocab_set取并集,再赋值给vocab_set
    return list(vocab_set)    # 得到所有文档中的词汇的并集,以列表形式返回

基于bag of word词袋模型,将分词后的文档转化为词向量。

def bow2vec(vocab_list, input_set):    # 输入参数为词汇表(向量空间)和分词后的文档
    return_vec = [0] * len(vocab_list)    # 初始化一个和词汇表等长的“0”向量
    for word in input_set:               # 遍历文档中的每个词汇
        if word in vocab_list:            # 如果文档中出现了词汇表中的词
            return_vec[vocab_list.index(word)] += 1   # 找到该词在词汇表中的位置,在return_vec对应位置上加1
        else:
            print("the word %s is not in my Vocabulary!" % word)
    return return_vec   # 返回return_vec向量,其中每个元素代表了对应位置上的词出现的次数

输入参数为文档矩阵train_matrix,以及由每篇文挡类别标签所构成的向量train_category;
为了避免p(w1|c1)*p(w2|c1)…等于0的情况,将每个词出现的次数初始化为1,分母初始化为2。

def train_NB(train_matrix, train_category):
    num_train_docs = len(train_matrix)   # 文档数量,即train_matrix矩阵长度(行数)
    num_words = len(train_matrix[0])     # 词汇数量,即train_matrix矩阵第一行长度
    p_abusive = sum(train_category) / float(num_train_docs)  # 侮辱性文档的概率
    p0_num = np.ones(num_words)      # 初始化分子p0和p1,长度等于词汇数的“1”数组
    p1_num = np.ones(num_words)      # p0和p1中的每个元素代表对应位置上的词汇出现的次数(初始化为出现1次)
    p0_denom = 2.0               # (denominator:分母)
    p1_denom = 2.0               # 初始化分母
    for i in range(num_train_docs):     # 遍历训练集中train_matrix中的所有文档
        if train_category[i] == 1:      # 若某个词出现在“1”类文档出现,则该次对应的个数加1
            p1_num += train_matrix[i]       
            p1_denom += sum(train_matrix[i])   # 该文档的总词数也对应加1
        else:
            p0_num += train_matrix[i]          # 对“0”类文档,同样操作
            p0_denom += sum(train_matrix[i])   
    p1_vect = np.log(p1_num/p1_denom)    # 最终的p1_num的每个元素表示对应每个词在“1”类文档里的出现次数
    p0_vect = np.log(p0_num/p0_denom)    # 最终的p1_denom表示“1”类文档里出现的词的总数
    return p0_vect, p1_vect, p_abusive  
# 最终p1_vect的每个元素表示对应每个词在“1”类文档里出现的频率,取对数避免“下溢”,p0_vect同理

输入为四个参数:要分类的文档的词向量、p(w|c0)、p(w|c1)以及“1”类文档的概率
将待分类文档的词向量的每个元素与p1_vec每个元素相乘,由于待分类文档中不存在的词在词向量对应位置上为0;
故相乘之后为待分类文档中出现的词的p(w|c1)取对数值,其中w为待分类文档中出现的词w1、w2…wn
再将各个对数值相加,得到p(w1|c1)*p(w2|c1)…*p(wn|c1)的取对数值,再加上“1”类文档的概率去对数值;
得到p(w1|c1)*p(w2|c1)…*p(wn|c1)*p(c1)的取对数值,即p(w1,w2…wn|c1)p(c1)的取对数值。

def classify_NB(vec2classify, p0_vec, p1_vec, p_class1):
    p1 = sum(vec2classify * p1_vec) + np.log(p_class1)  
    p0 = sum(vec2classify * p0_vec) + np.log(1.0 - p_class1)  # p0同理
    if p1 > p0:         # 比较p(w1,w2...wn|c1)p(c1)和p(w1,w2...wn|c0)p(c0)的大小;
        return 1        # 即比较p(c1|w1,w2...wn)和p(c2|w1,w2...wn)的大小
    else:
        return 0

最后是实现主要功能函数。

def spam_test():
    doc_list = []; class_list = []; full_text = []
    head_name_spam = "D:\python_data\MachineLearningInaction\machinelearninginaction\Ch04\email\spam"
    head_name_ham = "D:\python_data\MachineLearningInaction\machinelearninginaction\Ch04\email\ham"
    for i in range(1,26):    # 遍历spam文件夹下的25个文档(ham同理),将每个文档内的全部内容作为一个大字符串
        word_list = text_parse(open(head_name_spam + "\\%d.txt" % i).read())  # 调用text_parse函数进行分词
        doc_list.append(word_list)      # 将每个文档分词后得到的列表追加到doc_list列表中
        full_text.extend(word_list)   # 将分词后得到列表中的每个元素追加到full_text列表中
        class_list.append(1)            # 由于是spam文件夹下,所以在class_list列表中追加1,对应该文档的类别
        word_list = text_parse(open(head_name_ham + "\\%d.txt" % i).read())
        doc_list.append(word_list)       
        full_text.extend(word_list)
        class_list.append(0)
# 最终doc_list中的每一个元素是文档进行分词解析后得到的列表;
# full_text中的每一个元素是文档分词解析后得到的列表中的每一个元素
# class_list中每一个元素是word_list中对应文档的类别

# 从50个数字中,随机选择10个作为测试集,剩下的40个作为训练集(留存交叉验证)
    vocab_list = create_vocab_list(doc_list)  # 输入文档集合,得到词汇表(向量空间)
    training_set = list(range(50)); test_set = []   # traing_set是一个整数列表,元素为0-49
    for i in range(10):     # 进行10次循环
        rand_index = int(random.uniform(0, len(training_set)))# 每次产生一个0-len(training_set)之间的随机整数
        test_set.append(training_set[rand_index])   # 将training_set对应下标的数字追加到测试集列表test_set
        del(training_set[rand_index])   # 在training_set中删掉对应下标的数字

# 得到训练集文档的词向量矩阵,和对应的文档类别向量
    train_mat = []; train_classes = []
    for doc_index in training_set:   # 遍历训练集中的每个数字
        train_mat.append(bow2vec(vocab_list, doc_list[doc_index]))# 得到下标与数字doc_index对应的文档的词向量
        train_classes.append(class_list[doc_index])   # 得到下表与数字doc_index对应的文档的类别

# 得到取对数的p(w|c0)和p(w|c1)以及p(c0)
    p0_v, p1_v, p_spam = train_NB(train_mat, train_classes)
    error_count = 0  # 初始化错误计数为0
    for doc_index in test_set:    # 遍历测试集中每个数字
        word_vector = set_of_words2vec(vocab_list, doc_list[doc_index]) 
        # 得到下标与数字doc_index对应的文档的0-1词向量
        if classify_NB(word_vector, p0_v, p1_v, p_spam) != class_list[doc_index]:
        # 如果该下标文档的分类与对应class_list中的类别不同,错误计数加1
            error_count += 1
    print("the error rate is:", float(error_count/len(test_set)))

调用10次spam_test函数,查看10次的错误率。

spam_test()
spam_test()
spam_test()
spam_test()
spam_test()
spam_test()
spam_test()
spam_test()
spam_test()
spam_test()

最后运行代码,得到如下错误率。

the error rate is: 0.1
the error rate is: 0.0
the error rate is: 0.0
the error rate is: 0.0
the error rate is: 0.1
the error rate is: 0.1
the error rate is: 0.0
the error rate is: 0.1
the error rate is: 0.1
the error rate is: 0.0