抖音给你天天推荐感兴趣的视频竟然是因为这个算法?
Hi,大家好,我是晨曦
今天这期我们依旧来继续我们机器学习的话题,这期的内容对大家来说可能会熟悉一点,就是我们老生常谈的贝叶斯模型
那我们就开始吧~
本篇推文带有作者强烈的主观想法,欢迎各位小伙伴在评论区讨论哦,如果各位对于机器学习有感兴趣的方向,也欢迎在评论区留言,说不定下一个推文主题就是你感兴趣的话题哦

晨曦碎碎念系列传送门
背景知识
首先,我们需要有一个基本的概念,贝叶斯不是一种模型,而是一类模型,是一类基于贝叶斯算法的模型,我们最常使用的是其中的一种模型被称为朴素贝叶斯(Naive Bayes)
 重点 
后面会讲解算法的一些由来,大家如果觉得太长,只需要记住这句话也可以完成分析,贝叶斯属于有监督学习中执行分类的算法,在输入数据的要求上要求我们的自变量最好是离散型变量,但是如果你是连续型变量也是可以的,要么就把连续型变量转为离散型变量,要么就假设离散变量符合正态分布,然后计算每个变量的概率密度函数,然后根据贝叶斯算法的基本逻辑用先验概率来对后验概率进行分类,如果能理解上面这些,下面这块故事可以选择性阅读哦
有时间就读读看嘛QAQ,还是很有意思的~
贝叶斯算法来自于一个人的名字——托马斯.贝叶斯,其实是为了解决一个问题。以下摘自wikipedia上的简介
所谓的贝叶斯方法源于他生前为解决一个“逆概”问题写的一篇文章,而这篇文章是在他死后才由他的一位朋友发表出来的。在贝叶斯写这篇文章之前,人们已经能够计算“正向概率”,如“假设袋子里面有N个白球,M个黑球,你伸手进去摸一把,摸出黑球的概率是多大”。而一个自然而然的问题是反过来:“如果我们事先并不知道袋子里面黑白球的比例,而是闭着眼睛摸出一个(或好几个)球,观察这些取出来的球的颜色之后,那么我们可以就此对袋子里面的黑白球的比例作出什么样的推测”。这个问题,就是所谓的逆概问题。
即我们可以把这个问题再贴近生活一下,当气象学家提供天气预报时,通常使用“70%的降雨几率”等术语来预测降水。这些预测被称为降水的概率。你有没有考虑过人们是如何计算它们的?这是一个令人费解的问题,因为实际上,它要么下雨要么不下雨。
他们使用过去事件的数据来推断未来的事件。就天气而言,降雨的机会描述了以前具有类似可测量的大气条件的降水发生的比例。因此,70%的降雨机率意味着,假设在过去10例天气模式相似的情况下,有7例的降水发生
其实我们如果简单的学过统计,我们其实可以自然而然的推导出贝叶斯的算法公式,晨曦这里简单的推导一次,大家如果感兴趣可以跟着晨曦推导一次,如果不感兴趣,可以直接往下滑到代码实战部分
我们这里借用wikipedia上的一个例子来帮助我们理解贝叶斯的推导方式
一所学校里面有 60% 的男生,40% 的女生。男生总是穿长裤,女生则一半穿长裤一半穿裙子。有了这些信息之后我们可以容易地计算“随机选取一个学生,他(她)穿长裤的概率和穿裙子的概率是多大”,这个就是前面说的“正向概率”的计算。然而,假设你走在校园中,迎面走来一个穿长裤的学生(很不幸的是你高度近似,你只看得见他(她)穿的是否长裤,而无法确定他(她)的性别),你能够推断出他(她)是男生的概率是多大吗?
我们来计算一下,假设学校里面有Z个人,60%的男生都穿长裤,所以穿长裤的男生为Z✖P(男生)✖P(长裤|男生),40%的女生里面又有一半(50%)穿长裤,所以我们可以得到女生穿长裤为Z✖P(女生)✖P(长裤|女生)
补充概率运算知识:
P(AIB)=P(BIA)P(A)/P(B)
P(AnB)=P(AIB)P(B)=P(BIA)P(A) = P(AB)
1.概率的加法法则
事件A与B并的概率等于事件A与B的概率之和减去事件A与B交的概率∶
P(AU B)=P(A)+P(B)-P(A∩B)
2.互斥事件的加法法则
如果两个事件A与B是互斥的,那么A与B并的概率等于A与B的概率之和∶
P(AU8)=P(A)+P(B)
3.概率的乘法法则
P(A∩B)=P(AIB)P(B)=P(BIA)P(A)
4.独立事件的乘法法则
如果事件A和B是独立的,那么 A和B的交的概率等于A和B的概率乘积,即
P(AnB)=P(A)P(B)
最后经过我们运算,可以整理出这个等式P(女生|长裤) = P(女生) * P(长裤|女生) / [P(男生) * P(长裤|男生) + P(女生) * P(长裤|女生)]
注意,如果把上式收缩起来,分母其实就是 P(Pants) ,分子其实就是 P(Pants, Girl) 。而这个比例很自然地就读作:在穿长裤的人( P(Pants) )里面有多少(穿长裤)的女孩( P(Pants, Girl) )
所以任何事物都可以被浓缩成下面这个公式
P(B|A) = P(A|B) * P(B) / [P(A|B) * P(B) + P(A|~B) * P(~B) ]
P(B|A) = P(AB) / P(A)
最后我们再导一下就是:P(B|A) * P(A) = P(AB)
但其实贝叶斯的本质就是一句话:是将未知的条件概率 P(B|A)转换为已知的条件概率P(A|B)
好,到这里我们就大致了解了贝叶斯的推导过程,当然,仅仅了解即可,因为我们有现成的R包可以帮助我们完成上述的操作
贝叶斯分析的注意细节
优点
(1) 算法逻辑简单,易于实现(算法思路很简单,只要使用贝叶斯公式转化医学即可!)
(2)分类过程中时空开销小(假设特征相互独立,只会涉及到二维存储)
缺点
理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型假设属性之间相独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。
而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。
然而,在大多数情况下,当这些假设被违反时,朴素贝叶斯的表现仍然相当好。即使在特征之间存在很强依赖关系的极端情况下,这也是如此。由于该算法在多种条件下的多功能性和准确性,朴素贝叶斯算法通常是分类学习任务的第一候选算法
尽管朴素贝叶斯的假设(彼此相互独立)错误,但其效果良好,确切原因一直是许多猜测的主题。一种解释是,只要预测的类值为真,那么获得概率的其实并不重要。例如,如果一个垃圾邮件过滤器能够正确地识别出垃圾邮件,那么它对其预测有51%或99%是垃圾邮件这个概率本身就不重要,因为超过50%就会过滤掉
这里我们还需要介绍因为贝叶斯模型都是相乘的形式,所以一旦某个变量发生的机率为0那么很显然我们获得的最后的概率就是0,这显然是我们不想看到的,所以我们会使用一种叫做拉普拉斯的估计量,拉普拉斯估计器本质上为频率表中的每个计数添加了一个小数字,从而确保每个特征对每个类具有非零的概率。通常,拉普拉斯估计器被设置为1
由于朴素贝叶斯使用频率表来学习数据,因此每个特征都必须是分类的,以创建组成矩阵的类和特征值的组合。由于数值特征没有值的类别,所以前面的算法不能直接用于数值数据。然而,有一些方法可以解决这个问题,一种选择是使用分位数离散特征。您可以将数据分成三个带分位数的箱子,四个带四分位数的箱子,或者五个带五分位数的箱子
代码实战
下面是自己建造的一个分类型特征的例子
#准备输入数据#构造训练集data <- matrix(c("sunny","hot","high","weak","no", "sunny","hot","high","strong","no", "overcast","hot","high","weak","yes", "rain","mild","high","weak","yes", "rain","cool","normal","weak","yes", "rain","cool","normal","strong","no", "overcast","cool","normal","strong","yes", "sunny","mild","high","weak","no", "sunny","cool","normal","weak","yes", "rain","mild","normal","weak","yes", "sunny","mild","normal","strong","yes", "overcast","mild","high","strong","yes", "overcast","hot","normal","weak","yes", "rain","mild","high","strong","no"), byrow = TRUE, dimnames = list(day = c(), condition = c("outlook","temperature", "humidity","wind","playtennis")), nrow=14, ncol=5)
#这里针对字符型,我们需要把所有字符型转换为因子的形式data <- data.frame(data)data$playtennis <- factor(data$playtennis)data$temperature<-as.factor(data$temperature)data$outlook<-as.factor(data$outlook)data$humidity<-as.factor(data$humidity)data$wind<-as.factor(data$wind)#构建测试集#所有字符型均是因子的形式test <- data[,1:4]#因为数据量比较少,我们这一步直接纳入全部数据集作为训练集,只是为了展示流程,正规操作下,需要划分训练集和测试集#加载R包library(mlr3verse)library(tidyverse)library(e1071) #包中有naiveBayes函数require(e1071)
#拟合贝叶斯模型levels(data$playtennis)#其实一步就把模型给构建了出来,下面的predict函数是使用模型的函数model <- naiveBayes(playtennis~., data = data)class(model) #使用模型pred <- predict(model,test)#用测试集预测的结果与真实结果进行比较library(gmodels)CrossTable(pred, data$playtennis,#这里应该是用测试集的标志来看,但是因为我们训练集和测试集是一样的,所以这里就换一下 prop.chisq = FALSE, prop.t = FALSE, dnn = c('predicted', 'actual')) # Cell Contents#|-------------------------|#| N |#| N / Row Total |#| N / Col Total |#|-------------------------|#Total Observations in Table:  14 # | actual # predicted | no | yes | Row Total | #-------------|-----------|-----------|-----------|# no | 4 | 0 | 4 | # | 1.000 | 0.000 | 0.286 | # | 0.800 | 0.000 | | #-------------|-----------|-----------|-----------|# yes | 1 | 9 | 10 | # | 0.100 | 0.900 | 0.714 | # | 0.200 | 1.000 | | #-------------|-----------|-----------|-----------|#Column Total | 5 | 9 | 14 | # | 0.357 | 0.643 | | #-------------|-----------|-----------|-----------|#计算准确率correct = sum(as.numeric(pred)==as.numeric(data$playtennis))/nrow(test)correct[1] 0.9285714
做到这里其实贝叶斯模型就结束了,但是我们如何进一步提高呢
其实我们会发现,我们没有设置拉普拉斯,这样的话,我们如果变量中有0%存在,那么显然就会导致模型的失败
所以我们下面重新构建一下代码
#设置拉普拉斯model <- naiveBayes(playtennis~., data = Train,laplace = 1)#通过一个参数即可完成设置
至此,贝叶斯模型就给大家介绍到这里,但是,嘿嘿,还没有完
来感受一下工作流带给大家的丝滑操作吧~
#tidymodels包建立贝叶斯模型library(tidymodels)library(tidyverse)data <- matrix(c("sunny","hot","high","weak","no", "sunny","hot","high","strong","no", "overcast","hot","high","weak","yes", "rain","mild","high","weak","yes", "rain","cool","normal","weak","yes", "rain","cool","normal","strong","no", "overcast","cool","normal","strong","yes", "sunny","mild","high","weak","no", "sunny","cool","normal","weak","yes", "rain","mild","normal","weak","yes", "sunny","mild","normal","strong","yes", "overcast","mild","high","strong","yes", "overcast","hot","normal","weak","yes", "rain","mild","high","strong","no"), byrow = TRUE, dimnames = list(day = c(), condition = c("outlook","temperature", "humidity","wind","playtennis")), nrow=14, ncol=5)data <- data.frame(data)data$playtennis <- factor(data$playtennis)data$temperature<-as.factor(data$temperature)data$outlook<-as.factor(data$outlook)data$humidity<-as.factor(data$humidity)data$wind<-as.factor(data$wind)#划分数据集data_split <- initial_split(data,prop = 0.6)#划分训练集和测试集data_split %>% analysis() %>% glimpse()#简单看一下data_train <- training(data_split)#训练集data_test <- testing(data_split)#测试集data_Bayes <- naive_Bayes(Laplace = 1,mode = "classification") %>% set_engine("naivebayes") %>%  fit(playtennis ~ .,data = data_train)#拟合模型predict(data_Bayes, data_test)#预测data_Bayes %>% predict(data_test) %>% bind_cols(data_test) %>% glimpse()#预测变量添加到测试集上data_Bayes %>% predict(data_test) %>% bind_cols(data_test) %>% metrics(truth = playtennis,estimate = .pred_class)#这里的`kap`行是与accuracy类似的度量方法,但它是由偶然性期望的精度标准化的,当一个或多个类具有较大的频率分布时,它非常有用#Ps:果然,全部数据集拟合模型再用全部数据集去测试,真的就像是提前知道考试答案一样,准确率高估的可怕~data_probs <- data_Bayes %>% predict(data_test, type = "prob") %>% bind_cols(data_test)glimpse(data_probs)data_probs%>% roc_curve(playtennis,.pred_yes) %>% autoplot()#可视化看一下结果
tidymodels包整体的流程就在这里,晨曦并没有放置结果,大家在推文最后回复相关关键词获得代码以后,可以回去自己运行~
Ps:大家的留言晨曦都看到了,等再连载几期机器学习,就给大家更新tidymodels包以及mlr3包的相关教程,期待哦~
关于贝叶斯模型的总结与思考
  • 属于有监督的学习
  • 主要处理离散类型的数据,如果为连续数据可先进行离散化
  • 训练集的特征取值要尽量完备,如果有缺失需进行预处理
  • 关于特征值相互独立的假设,在实际问题中一般无法满足,但基于此假设做的预测是可以接受的(即只要结果是可以接受的就可以)
那么这里我们仍然不可避免来思考一个问题,就是如果我们的样本量很少,连续型数据无法进行离散化处理应该怎么办?
数据太少就不建议拟合模型了,这样子模型的数据不够,准确的也不会很高
我们可以尝试通过贝叶斯判别来解释这个问题
本身来说有监督学习的模型可以分为生成模型和判别模型,生成模型可以转换为判别模型,反之则不可以,本身来说贝叶斯构建的生成模型转换成判别模型的时候即可以进行针对连续型数据的判断
各自变量为连续性或有序分类变量
因变量自然依旧是分类变量
说起来可能有点抽象,我们通过一个例子来判断
已知某人身高6英尺、体重130磅,脚掌8英寸,请问该人是男是女?
library(klaR)library(MASS)df <- data.frame(gender = c("man","man","man","man","woman","woman","woman","woman"), hight = c(6,5.92,5.58,5.92,5,5.5,5.42,5.75), kg = c(180,190,170,165,100,150,130,150), foot = c(12,11,12,10,6,8,7,9))df$gender <- as.factor(df$gender)Bayes_Model<-NaiveBayes(gender~.,data=df)data <- data.frame(hight = 6, kg = 130, foot = 8)Bayes_Model_pre<-predict(Bayes_Model,newdata = data)Bayes_Model_pre$class#[1] woman#Levels: man woman
至此,我们就通过基于贝叶斯算法的贝叶斯判别来解决了连续型变量不能离散化的问题
Ps:这里是晨曦个人的理解,当然欢迎大家在评论区进行讨论,但是针对无法离散化的连续型数据大部分都是数据量比较小的,这种情况应该也不太建议进行模型的构建,因为不管什么算法,都需要充足的数据量作为前提
参考教程:Machine Learning with R
那么,本期推文到这里就结束啦~
我是晨曦,我们下期再见~
Ps:回复”晨曦贝叶斯“可以获得本期推文的全部代码和示例文件哦~
晨曦单细胞笔记系列传送门
晨曦从零开始学画图系列传送门
晨曦单细胞数据库系列传送门

END

撰文丨晨   曦
排版丨四金兄
主编丨小雪球
欢迎大家关注解螺旋生信频道-挑圈联靠公号~
继续阅读
阅读原文