zoukankan      html  css  js  c++  java
  • 机器学习

    简介

    朴素贝叶斯是一种基于概率进行分类的算法,跟之前的逻辑回归有些相似,两者都使用了概率和最大似然的思想。但与逻辑回归不同的是,朴素贝叶斯通过先验概率和似然概率计算样本在每个分类下的概率,并将其归为概率值最大的那个分类。朴素贝叶斯适用于文本分类、垃圾邮件处理等NLP下的多分类问题。

    核心思想

    每个样本的分类由组成这个样本的特征所决定,所以,如果能求出每个特征在每个分类下的概率,就能通过最大似然法求出这个样本在每个分类下的概率,概率值最大的那个分类即是样本的分类。用贝叶斯公式表示,即:

    ( egin{equation*} p(y_i|x) = frac{p(y_i)*p(x|y_i)}{p(x)} end{equation*} )

    其中:
    (qquad p(y_i|x))表示样本 (x) 是类别 (y_i) 的概率;
    (qquad y_i mbox{ 表示某一种类别, } y_i in {y|y_1, y_2, ..., y_n}),所以 (p(y_i)) 即先验概率;
    (qquad p(x|y_i)) 是似然概率,需要先统计样本特征 (x_i)(y_i) 类别下的分布,再计算得出;
    (qquad p(x)) 是样本 (x) 出现的概率,由于当前样本是已知的,所以可以将 (p(x)) 视作常量,在比较概率大小时可忽略。
    所以只要求出先验概率和似然概率,即可预测出样本 (x) 的分类。

    一般步骤

    1.数据预处理

    需要注意的问题有:
    (1)如遇中文单词,需要将其编码成数字或字符,以方便统计;
    (2)将整个数据集分为训练集和测试集,便于评估模型。通常使用留出法进行分割。

    2.数据统计

    统计样本的分类情况以及样本特征在每个分类下的分布情况。
    (1)统计样本的分类情况:即每个分类在整体中的占比个数;
    (2)样本特征在每个分类下的分布情况:对于不同的数据集,有着不同的定义方式,例如在文本分类中,可以将单词是否在文章中出现过作为统计标准(不考虑重复出现的情况),也可以将单词在文章中出现的次数作为统计标准。前者统计的粒度相对粗一些,后者的粒度相对细一些。

    3.训练模型

    根据统计数据,计算先验概率和似然概率,作为训练的模型。
    (1)先验概率:可通过计算每个分类在整体的占比比例得出;
    (2)似然概率:根据特征在每个分类下的分布情况,可计算出特征在每个分类下的概率。仍以文本分类为例,单词出现过的文章篇数/当前分类下文章总篇数(单词出现在该分类下的总次数/当前分类下的单词总数)即每个特征在当前分类下的概率,再使用最大似然法,则可得到每个样本在当前分类下的概率。
    (3)对缺失特征的考虑。训练集的大小有限,不可能包含所有特征,如果测试集或后来需要预测的数据包含模型中不存在的特征时,需要给这些不存在的特征一个较小的概率值。

    4.模型评估

    使用测试集预测分类,并与实际分类相比较,得出模型准确率。
    在用测试集预测分类的过程中,需要计算似然概率,即所有单词的概率相乘。但是考虑到一篇文章的单词量很大,所有概率相乘的话会影响似然概率值的精度,不利于后续的比较,所以统一对似然概率取对数,转换成概率对数的累加:

    ( egin{equation*} p(x|y_i) = log{prod limits_{i=1}^{m}{p(x_i|y_i)}} = sum_{i=1}^{m}log{p(x_i|y_i)} end{equation*} )

    下面就以一个文章分类的例子对朴素贝叶斯分类进行运用。

    文本分类实例

    现在有一个包含了上千篇文章的article数据集(提取码:ivo8),已知文章共分为商业、汽车和体育三大类,试通过朴素贝叶斯建立预测模型。

    1.数据预处理

    首先,我们先来对数据有个初步的了解。打开文件目录,发现共有3000多篇文章,文章的分类已经包含在文件名中了:
    数据标签
    随便打开一篇文章,发现内容是中文,意味着需要对其进行编码,方便后续的统计。不过文章是已经分过词的:
    文章内容
    所以我们在这一阶段需要做的工作有:
    (1)提取文章对应的标签;
    (2)对中文单词进行编码;
    (3)把所有文章分成训练集和测试集两类,并输出到相应目录

    class NB:
        def __init__(self):
            self.article_path = 'articles'  # 存放3000+文章的目录
            self.dataset_path = 'dataset'  # 生成数据集的目录
            self.train_set = 'train.txt'  # 训练集文件名称
            self.test_set = 'test.txt'  # 测试集文件名称
            self.train_threshold = 0.8  # 训练集划分比例
    
        def prepare_data(self):
            if not os.path.exists(self.article_path):
                print('文件目录不存在,请重新指定!')
                return
            # 对中文单词重新编码,然后区分训练集和测试集,输出成文件
            file_list = os.listdir(self.article_path)
            # 如果目录不存在,直接创建
            if not os.path.exists(self.dataset_path):
                os.system('mkdir ' + self.dataset_path)
            train_f = open(os.path.join(self.dataset_path, self.train_set), 'w')
            test_f = open(os.path.join(self.dataset_path, self.test_set), 'w')
            # 对中文进行编码
            trans_dict = {}
            code = 0
            class_id = -1
            if file_list:
                for file_name in file_list:
                    # 获取文章分类
                    if 'business' in file_name:
                        class_id = 0
                    elif 'auto' in file_name:
                        class_id = 1
                    elif 'sports' in file_name:
                        class_id = 2
                    # 每篇文章占一行,第一位存放文章分类
                    output_str = str(class_id) + ' '
                    file = os.path.join(self.article_path, file_name)
                    with open(file, 'r', encoding='utf-8') as f:
                        lines = f.readlines()
                    for line in lines:
                        for word in line.strip().split():
                            # 按照单词出现顺序进行编码
                            if word not in trans_dict:
                                trans_dict[word] = code
                                code += 1
                            output_str += str(trans_dict[word]) + ' '
                    # 使用留出法区分训练集和测试集
                    num = random.random()
                    if num < self.train_threshold:
                        output_f = train_f
                    else:
                        output_f = test_f
                    # 输出编码后的文章
                    output_f.write(output_str + '
    ')
            train_f.close()
            test_f.close()
    

    经过了第一步的处理,我们得到了经过编码的训练集和测试集。

    2.数据统计

    有了训练集,我们就可以统计每个类别的数量以及单词在每个分类中出现的次数。这里我们使用比较细的粒度进行统计,因为文章数量比较多,重复的词肯定有不少,重复出现的次数在一定程度上也影响着样本所属的类别。

    class NB:
        def __init__(self):
            ... ...
    
        def prepare_data(self):
            ... ...
    
        def stat_data(self):
            # 统计每个分类出现的次数
            class_dict = {}
            # 统计每个单词在每个分类下的出现次数
            class_word_dict = {}
            with open(os.path.join(self.dataset_path, self.train_set), 'r') as f:
                lines = f.readlines()
            for line in lines:
                word_list = line.strip().split()
                # 样本分类
                class_id = word_list[0]
                # 处理数据结构
                if class_id not in class_dict:
                    class_dict[class_id] = 0
                    class_word_dict[class_id] = {}
                # 统计分类个数
                class_dict[class_id] += 1
                # 统计每个单词在每个分类下的出现次数
                for word in word_list[1:]:
                    if word not in class_word_dict[class_id]:
                        class_word_dict[class_id][word] = 1
                    else:
                        class_word_dict[class_id][word] += 1
            # 返回统计结果
            return class_dict, class_word_dict
    

    3.训练模型

    有了统计结果,我们就可以计算先验概率以及单词在每个类别中出现的概率,并输出模型:

    class NB:
        def __init__(self):
            ... ...
            self.model_path = 'model'  # 生成预测模型文件的存放目录
            self.miss_word_prob = 0.1  # 缺失单词的默认概率
    
        def prepare_data(self):
            ... ...
    
        def stat_data(self):
            ... ...
    
        def train_model(self):
            class_dict, class_word_dict = self.stat_data()
            # 先验概率p(y)
            class_prob = {}
            # 单词在每个分类中出现的概率
            class_word_prob = {}
            # 默认概率,用于单词不在模型中的情况
            class_default_prob = {}
            for class_id in class_dict:
                # 当前分类下所有文章个数
                class_sum = sum(class_dict.values())
                class_prob[class_id] = class_dict[class_id] / class_sum
            for class_id in class_word_dict:
                # 当前分类下所有单词出现次数的总和
                class_word_sum = sum(class_word_dict[class_id].values())
                if class_id not in class_word_prob:
                    class_word_prob[class_id] = {}
                for word in class_word_dict[class_id]:
                    # 每个单词在当前分类下出现的概率
                    class_word_prob[class_id][word] = class_word_dict[class_id][word] / class_word_sum
                # 计算单词缺失的情况下的概率
                class_default_prob[class_id] = self.miss_word_prob / (1.0 + class_word_sum)
            # 输出model文件至目录
            if not os.path.exists(self.model_path):
                os.system('mkdir ' + self.model_path)
            self.json_file_dump(class_prob, self.model_path, 'class_prob.txt')
            self.json_file_dump(class_word_prob, self.model_path, 'class_word_prob.txt')
            self.json_file_dump(class_default_prob, self.model_path, 'class_default_prob.txt')
    
        @staticmethod
        def json_file_dump(dump_file, dump_file_path, dump_filename):
            # 导出模型到目录
            dump_file_f = open(os.path.join(dump_file_path, dump_filename), 'w')
            json.dump(dump_file, dump_file_f)
            dump_file_f.close()
    

    这里我们补充了两个初始化参数,self.model_path和self.miss_word_prob,分别用来存放模型文件以及计算缺失单词的概率,我们用self.miss_word_prob再除以每个分类的单词总数,当做每个分类下缺失单词的出现概率。

    4.模型评估

    最后我们通过测试集,对模型的预测性能进行评估。

    class NB:
        def __init__(self):
            ... ...
    
        def prepare_data(self):
            ... ...
    
        def stat_data(self):
            ... ...
    
        def train_model(self):
            ... ...
    
        @staticmethod
        def json_file_load(file_path, filename):
            # 加载模型文件
            file_f = open(os.path.join(file_path, filename), 'r')
            file_dict = json.load(file_f)
            file_f.close()
            return file_dict
    
        def predict(self):
            # 存储真实值和预测值
            real_list = []
            pre_list = []
            # 读取模型文件
            class_prob = self.json_file_load(self.model_path, 'class_prob.txt')
            class_word_prob = self.json_file_load(self.model_path, 'class_word_prob.txt')
            class_default_prob = self.json_file_load(self.model_path, 'class_default_prob.txt')
            # 使用测试集计算p(y|x)预测class_id
            # 朴素贝叶斯公式:
            # p(y|x) = p(x|y)*p(y) / p(x)
            with open(os.path.join(self.dataset_path, self.test_set), 'r') as f:
                lines = f.readlines()
            for line in lines:
                # p(y|x)
                article_class_prob = {}
                word_list = line.strip().split()
                # 真实分类
                class_id = word_list[0]
                real_list.append(class_id)
                # 先验概率p(y)取对数
                for class_id in class_prob:
                    article_class_prob[class_id] = math.log(class_prob[class_id])
                # 似然概率p(x|y)
                for word in word_list[1:]:
                    for class_id in class_word_prob:
                        if word not in class_word_prob[class_id]:
                            article_class_prob[class_id] += math.log(class_default_prob[class_id])
                        else:
                            article_class_prob[class_id] += math.log(class_word_prob[class_id][word])
                # 取概率最大的为预测值
                max_prob = max(article_class_prob.values())
                for class_id in article_class_prob:
                    if article_class_prob[class_id] == max_prob:
                        pre_list.append(class_id)
            # 计算准确率
            accurate = 0
            for i in range(len(real_list)):
                if real_list[i] == pre_list[i]:
                    accurate += 1
            accurate_rate = round(accurate / len(real_list) * 100, 2)
            return '模型预测准确率为:' + str(accurate_rate) + '%'
    

    最后,跑一次程序,可以得到以下结果:

    # 初始化
    nb = NB()
    # 数据预处理
    nb.prepare_data()
    # 训练
    nb.train_model()
    # 预测
    accurate_rate = nb.predict()
    print(accurate_rate)
    
    
    # 输出结果
    模型预测准确率为:97.08%
    

    总结

    朴素贝叶斯算法由于简单、易于理解,经常用于文本分类等场景中。但由于它是基于“样本特征之间是相互独立的”这一假设,所以不适合一些基于上下文进行判断的任务。
    该算法不像之前的线性回归和k-means聚类那样,可以通过画图对数据集和分类结果有比较直观的认识,但是只要理解了算法中最大似然的思想,以及利用贝叶斯公式计算似然概率的方法,就已经可以说是掌握了。

  • 相关阅读:
    为什么写技术博客对新人如此重要?
    Javascript经典正则表达式
    关于读书的那些事,其实我一直...没有行动
    dede织梦CMS文件夹目录结构
    jQ初体验,^_^
    vi/vim 基本使用方法
    (X)HTML Strict 下的嵌套规则
    KISS保持简单:纪念丹尼斯·里奇
    关于jQuery性能优化
    编码规范CSSHTML 摘自kissyui
  • 原文地址:https://www.cnblogs.com/dev-liu/p/15104513.html
Copyright © 2011-2022 走看看