numpy squeeze_不用for循环 - 教你用numpy“手撕”朴素贝叶斯
发布日期:2021-09-12 18:30:28 浏览次数:3 分类:技术文章

本文共 4873 字,大约阅读时间需要 16 分钟。

v2-0d2f70284e94a78841803826b10493a6_1440w.jpg?source=172ae18b

常见的python朴素贝叶斯算法的方式,都是使用for循环来统计各个p(特征|类型)的值。其实机器学习除了常规算法思路外,很关键且很优雅的地方在于矩阵化(向量化),即vectorization。通过各种矩阵运算来去除for循环,是目前机器学习、深度学习中非常关键的技巧。文本不使用各种高阶机器学习库,单纯使用numpy来手撕朴素贝叶斯算法,带你领略机器学习中的矩阵运算之道。


数据准备

为了demo方便,我们先伪造一些数据。这里伪造的是NLP情感分类的数据。分为两部门trainvalid,标签分为1和0,1代表侮辱性文字,0代表正常言论。

train = ['my dog had flea problems help help please',        'mybe not take him to dog park stupid dog',        'my dalmation is so cute I love him',        'stop posting stupid worthless garbage',        'mr licks ate my steak how to stop him',]valid = ['my stupid stupid worthless dog']label = [0,1,0,1,0]valid_y = [1]

数据预处理

NLP的数据预处理主要就是词典的创建和文本的向量化。这里由于是英语文档,不存在分词的问题。

这里由于是手撕教程,不使用spacy等高阶NLP库,词典创建的也即使用Python的set

vocab = set([])for doc in train:    vocab = vocab | set(doc.split())vocabList = list(vocab)# 根据词反查indexvocabList.index('please')

文档向量化

向量话的原理,首先将文档中句子的每一个单词按照字典转换为index

train_vec = []valid_vec = []for doc in train:    train_vec.append([vocabList.index(x) for x in doc.split()])for doc in valid:    valid_vec.append([vocabList.index(x) for x in doc.split()])

可以看看向量化后的结果

train_vec, valid_vec'''[[28, 11, 16, 7, 26, 19, 19, 8],  [20, 24, 21, 14, 6, 11, 12, 22, 11],  [28, 5, 9, 25, 0, 3, 2, 14],  [1, 17, 22, 15, 13],  [18, 4, 27, 28, 10, 23, 6, 1, 14]], [[28, 22, 22, 15, 11]]'''

接着是将词典的每一个单词作为一个feature维度处理,将所有文档处理成相同维度的矩阵,维度大小即为词典的长度,而每个维度的值有多种处理方式,比如按照句子中每个单词的Count计数,或者是每个单词的TFIDF值。这里我们采用了Count,为了体现手撕,使用了Python的Counter,为了体现numpy矩阵运算的思路,使用了scipy的稀疏矩阵sparse.csr_matrix

from collections import Counterfrom scipy.sparse import csr_matrixdef get_term_doc_matrix(label_list, vocab_len):    j_indices = []    indptr = []    values = []    indptr.append(0)    for i, doc in enumerate(label_list):        feature_counter = Counter(doc)        j_indices.extend(feature_counter.keys())        values.extend(feature_counter.values())        indptr.append(len(j_indices))    return csr_matrix((values, j_indices, indptr),                      shape=(len(indptr) - 1, vocab_len), dtype=int)train_matrix = get_term_doc_matrix(train_vec, len(vocabList)valid_matrix = get_term_doc_matrix(valid_vec, len(vocabList)

可以看看矩阵化后的train和valid,分别转换成了samples*feature,29即为字典的维度。

train_matrix, valid_matrix'''<5x29 sparse matrix of type ''    with 37 stored elements in Compressed Sparse Row format>, <1x29 sparse matrix of type ''    with 4 stored elements in Compressed Sparse Row format>'''

我们将生成的train_matrix作图如下(为方便观看省去了部分维度)

v2-24150646b87785b995d8b019dbb75573_b.jpg

朴素贝叶斯算法原理

谈到朴素贝叶斯算法,首先想到的必定是著名的贝叶斯公式:

v2-439c540a190f82e363a6a57f8c2d3104_b.jpg

不过这个定理如何体现到我们机器学习的场景呢?我们把其中的A和B换成实际的物理意义如下:

v2-d871ed1dc6ff7de941d6fe87c802ad54_b.png

在我们的分类监督学习中,其实就是求在一定特征的样本情况下,某个具体类别的概率。根据贝叶斯公式,可以转换成分别求某个类别下这些特征的概率、某类别的概率、特征的概率。

实际场景中,特征一般是多维的,而多维特征的联合概率是比较复杂的。这时候,就体现出朴素的概念了。我们对样本作出假设:样本中的各个特征是相互独立的。这样,可以将联合概率转换成以下的独立概率乘积:

v2-346c09dddc30b0b7f46df325dcc63f73_b.png

当然,这种假设肯定是粗暴的。但是实践过程中,计算量减少带来的益处是远大于粗暴假设带来的失真。这也是最考验算法工程师的地方,既要考虑数学的严谨,同时也要考虑工程实现。“part-science, part-art”,这或许也是机器学习之道。

当然,对于模型最终的应用来说,求出具体的概率绝对值是意义不大的。我们只需要知道不同特征之间概率的比值就行。也就是说,对于二分类问题,我们只需要知道:

v2-b455413175091d2065aa1bc3a8f9e625_b.png

就可以判断出该样本是属于类别1。于是,我们可以接着进行公式变换,将P(类别2|特征)移到等式左边,并取log

v2-7b497f2d28a2e62b90e4733ab5cb24f8_b.jpg

同样,根据朴素的假设,可以得到:

v2-883e6462cbdf079c02a239510e01b8e6_b.png

v2-c0304d772f36f6726e7491c53a0b2ffb_b.png

即,我们需要求出每个特征对应的

v2-933249fdaee888e637add55ee0ed8a3f_b.jpg

以及每个类别对应的

v2-a5dd2a2fd6da486b3b79ceff46eda2d9_b.png

然后全部相加,看结果是否大于0即可判断出样本的类别。而这两块,我们都是可以从训练样本中求出(先验概率),这也就是朴素贝叶斯算法训练的部分。


朴素贝叶斯矩阵运算

前言中说到,一般的教程,直接使用for循环数数一般就可以求出上述的两个log值,完成“训练”。我们这样,要充分应用numpy的矩阵运算特性,不使用for循环

import numpy as npp1 = np.squeeze(np.asarray(train_matrix[np.asarray(label==1].sum(0)))p0 = np.squeeze(np.asarray(train_matrix[np.asarray(label==0].sum(0)))pr1 = (p1+1) / ((np.asarray(label)==1).sum() + 1)pr0 = (p0+1) / ((np.asarray(label)==0).sum() + 1)r = np.log(pr1/pr0)

v2-7bf40d6495d3f3839f4d486344f1034a_b.png

还是以这个图为例,pr1即为类别为1的样本中特征x的概率。在NLP中,每个特征都是一个词,其物理意义就是某个词在某个类别中出现的概率,而最后求出的r即为所有特征的概率在2个类别的比值,注意,这里的r是个向量,是通过样本之间的矩阵运算一次性求出。

r'''array([-0.40546511,  0.28768207, -0.40546511, -0.40546511,-0.40546511,       -0.40546511,  0.28768207, -0.40546511, -0.40546511, -0.40546511,       -0.40546511,  0.69314718,  0.98082925,  0.98082925, -0.11778304,        0.98082925, -0.40546511,  0.98082925, -0.40546511, -0.81093022,        0.98082925,  0.98082925,  1.38629436, -0.40546511,  0.98082925,       -0.40546511, -0.40546511, -0.40546511, -1.09861229])'''

接下来求第二个log,这个直接就是不同类别的比例。

b = np.log((np.asarray(label)==1).mean() / (np.asarra(label)==0).mean())'''-0.4054651081081643'''

这里的rb即为我们训练出的模型的参数。可以将其序列化到磁盘,供后续预测的时候使用(也就是深度学习中的inference)。


朴素贝叶斯推理

由于r代表了训练样本中每一个特征词的在不同类别的概率比值,比如’stupid’等词就很高,而’love’等词就较低。在实际应用中,我们同样取出预测样本中的所有词,然后考虑这些词的概率比值,最后全部相加,即可判断最终样本的类别。同样,我们使用numpy的矩阵运算。

train_matrix@r + b > 0# array([False,  True, False,  True, False])valid_matrix@r + b > 0# array([ True])

这里@是numpy的矩阵乘操作,举例如图

v2-adfc3dffb205c424f4f7795a16ed6005_b.jpg

b作为一个标量,会直接触发numpy中的broadcast操作,直接把+应用到所有样本,同样>也会broadcast,最终得到一个array,表示每一个测试样本是否预测为类别1。


扩展

由于上面推理过程中,train_matrixvalid_matrix都是由词频组成,相当于在求具体概率比值的时候应用词频做了加权。在实际应用中,也可考虑不使用词频,直接使用是否含有某个特征词,如下:

train_matrix.sign()@r + b > 0valid_matrix.sign()@r + b > 0

具体哪种方式好,还是要根据实际的数据样本情况来具体测试。


总结

本文以NLP中的句子情感分析为例,使用numpy的矩阵运算来手撕朴素贝叶斯算法,可以加深对与朴素贝叶斯算法的理解,并体会机器学习中矩阵运算之道。

朴素贝叶斯简单快捷,特别适合较大规模的稀疏矩阵,在情感分析、垃圾邮件分类等场景中有很普遍的应用。在深度学习大行其道的今天,我们仍然可以通过这个简单的算法来快速搭建整个流程的pipeline。在我眼里,在机器学习项目的初期,流程pipeline的重要性大于任何算法。

转载地址:https://blog.csdn.net/weixin_39609822/article/details/110561191 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:macos bigsur如何降级_macOS Big Sur如何降级
下一篇:python爬取英雄联盟所有皮肤_python爬取lol英雄皮肤

发表评论

最新留言

能坚持,总会有不一样的收获!
[***.219.124.196]2024年03月10日 10时18分47秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

script执行php文件_php命令行(cli)下执行PHP脚本文件的相对路径的问题解决方法... 2019-04-21
apache 2.4 php5.4_apache2.4+php5.4+my sql 5.6,网站经常无故不能访问 2019-04-21
php apc.dll下载,PHP之APC缓存详细介绍 apc模块安装 2019-04-21
html贝塞尔曲线在线,贝塞尔曲线的一些事情_html/css_WEB-ITnose 2019-04-21
java blockingqueue源码_Java并发队列BlockingQueue实现之ArrayBlockingQueue源码分析 2019-04-21
Java前台显示近20天的东西_第十次课:前台首页设计及显示商品信息 2019-04-21
java开发web网站的路由设计_理解Web路由(浅谈前后端路由与前后端渲染) 2019-04-21
excel如何把顺序倒过来_在excel中怎么使文字颠倒顺序反过来显示呢? 2019-04-21
java 62进制 转换_序列号生成的另一种玩法--62进制如何玩? 2019-04-21
php正则表达式获取图片路径,php 常用正则表达式实例(图片地址,与指定内容获取)... 2019-04-21
脚本语言php是什么意思,PHP脚本语言 2019-04-21
matlab数学规划模型,数学规划模型 2019-04-21
视频提取音频php,如何提取视频中的音频,从视频文件中提取出音频输出成MP3格式... 2019-04-21
diy.php添加验证码,织梦dedecms自定义表单中加入验证码 2019-04-21
在php脚本中 通过可以获取,在PHP中,可以使用Unix时间戳获取精确的脚本执行时间。... 2019-04-21
s2-045 php exp,S2-045-EXP.py --Struts2任意代码执行漏洞 (S2-045,CVE-2017-5638) 2019-04-21
linux sdk 窗口句柄,Venus: 针对Linux平台上,对常用的系统API进行面向对象的封装SDK。... 2019-04-21
c语言程序设计 科学出版社习题答案,C语言程序设计(科学出版社)第4章 课后习题参考答案.doc... 2019-04-21
c语言 无错 但只运行一半,求哈夫曼编码时程序运行到一半就终止了,编译无错... 2019-04-21
deepin linux 2014安装,2014.2版本的Deepin虚拟机安装浅谈(就是深度Linux) 2019-04-21