机器学习(九)——概率图模型之朴素贝叶斯

朴素贝叶斯分类是一种十分简单的分类算法,叫它朴素贝叶斯分类是因为这种方法的思想真的很朴素,朴素贝叶斯的思想基础是这样的:对于给出的待分类项,求解在此项出现的条件下各个类别出现的概率,哪个最大,就认为此待分类项属于哪个类别。通俗来说,就好比这么个道理,你在街上看到一个黑人,我问你你猜这哥们哪里来的,你十有八九猜非洲。为什么呢?因为黑人中非洲人的比率最高,当然人家也可能是美洲人或亚洲人,但在没有其它可用信息下,我们会选择条件概率最大的类别,这就是朴素贝叶斯的思想基础。

在前面的逻辑回归中,我们在讲逻辑回归的损失函数的推导时,讲到了贝叶斯定理。这里再复习一下:

贝叶斯公式到底在说什么呢?贝叶斯公式就是在描述,你有多大把握能相信一件证据。
假设我们把A计作“汽车被砸了”,B计作“警报响了”,带进贝叶斯公式里看,等式左边就是在求:当汽车的警报器响了后,我们得出汽车被砸了的概率。这即是说,警报响这个证据有了,多大把握能相信它确实是在报警说汽车被砸了?
那么等式右边说的是什么呢?等式右边说,警报器响了后,汽车被砸了的概率等于 用警报响起、汽车也被砸了这事件的数量,除以响警报事件的数量,也即,警报响起、汽车也被砸了的事件的数量,除以警报响起、汽车被砸了的事件数量加上警报响起、汽车没被砸的事件数量。
如果警报响起、汽车没被砸的事件数量很小,比如为0,即杜绝了汽车被球踢、被行人碰到等等其他所有情况,那自然,警报响了,只剩下一种可能——汽车被砸了。这即是提高了响警报这个证据的说服力。

把贝叶斯公式换成机器学习中的表达方式,即为:

更一般的情况:
P(类别|特征1,特征2,特征3……特征n)=\frac{P(特征1,特征2,特征3……特征n|类别)*P(类别)}{P(特征1,特征2,特征3……特征n)}

在给定样本的情况下,P(特征1,特征2,特征3……特征n)是常数,所以,我们在做分类时,如果要想在最大概率的情况下得到正确的分类,也即让能取到最大值,我们只需要保证分子,也就是能取到最大值。

为了便于计算,朴素贝叶斯假设各个特征之间相互独立,也就是说每个属性独立的对分类结果发生影响(这就是为什么要叫朴素的原因)。如果每个属性独立,则:

因此,我们的目标就转换为:

用数学公式来表达,则为:

这就是朴素贝叶斯分类器的表达式。
令表示训练集D中c类样本组成的集合,代表集合中的样本数量,若有充足的独立同分布样本,则可容易地估计出类先验概率:。

对于离散属性而言,令表示中第i 个属性上取值为的样本组成的集合,则条件概率可表示为:

对于连续属性可考虑概率密度函数,假定:
则,

下面是用R的实现步骤:

library(NLP)
library(tm)

#设置文件路径
path_to_neg_folder<-"aclImdb/train/neg"
path_to_pos_folder<-"aclImdb/train/pos"

#读取文件
nb_pos <- VCorpus(DirSource(path_to_pos_folder), readerControl = list(reader = 
                                    reader(DirSource(path_to_pos_folder)), language = "en"))
nb_neg <- VCorpus(DirSource(path_to_neg_folder), readerControl = list(reader = 
                                    reader(DirSource(path_to_neg_folder)), language = "en"))
#合并两个数据
nb_all <- c(nb_pos,nb_neg)

#观察数据
View(nb_all[[2]])
meta(nb_all[[20000]])

#获得所有文件名,后面根据文件名解析出评分
ids<-sapply(1:length(nb_all),function(x) meta(nb_all[[x]],"id"))
head(ids)

#解析评分,大于等于5的分为postive,小于5分的为negative
scores<-as.numeric(sapply(ids,function(x) sub("[0-9]+_([0-9]+)\\.txt","\\1",x)))
scores<-factor(ifelse(scores>=5,"positive","negative"))
summary(scores)

#文本处理
nb_all <- tm_map(nb_all, content_transformer(removeNumbers)) #移除数字
nb_all <- tm_map(nb_all, content_transformer(removePunctuation)) #移除标点符号
nb_all <- tm_map(nb_all, content_transformer(tolower)) #转换为小写
nb_all <- tm_map(nb_all, content_transformer(removeWords), stopwords("english"))
nb_all <- tm_map(nb_all, content_transformer(stripWhitespace)) #去除空格

#将数据转换为矩阵
nb_dtm <-DocumentTermMatrix(nb_all)
dim(nb_dtm)

#压缩矩阵,去掉为空的项
nb_dtm<-removeSparseTerms(x=nb_dtm, sparse = 0.99)
dim(nb_dtm)

nb_dtm <- weightBin(nb_dtm) ## 所有元素变换成二元因子
inspect(nb_dtm[1000:1006,100:106]) #查看数据框
nb_df <- as.data.frame(as.matrix(nb_dtm)) #将矩阵变为dataframe

#划分训练集和测试集
library(caret)
set.seed(443452342)
nb_sampling_vector <- createDataPartition(scores, p = 0.80, list = FALSE)
nb_df_train <- nb_df[nb_sampling_vector,]
nb_df_test <- nb_df[-nb_sampling_vector,]
scores_train = scores[nb_sampling_vector]
scores_test = scores[-nb_sampling_vector]

#v:属性向量,包括了每个属性的具体值, data:全量样本数据;
#函数用于计算给定的属性向量,计算在不同分类条件下的每个属性上的概率乘积
#即计算:P(特征1|类别)*P(特征2|类别)*P(特征3|类别)...P(特征n|类别),
#然后对比不同分类下的概率大小,进行预测分类
calcPx <- function(v,data){
  data_p = data[scores_train == "positive",] #分类为“positive”的全量样本数据;
  data_n = data[scores_train == "negative",] #分类为“negative”的全量样本数据;
  pp = length(data_p) / length(data) #计算"positive"出现的概率
  np = length(data_n) / length(data) #计算"negative"出现的概率
  result_p <- 1
  result_n <- 1
  for(i in 1:length(v)){
    #计算两个类别下的:P(特征1|类别)*P(特征2|类别)*P(特征3|类别)...P(特征n|类别)
    result_p <- result_p * (sum(data_p[,i]==v[i])/length(data_p[,i])) 
    result_n <- result_n * (sum(data_n[,i]==v[i])/length(data_n[,i]))
  }
  result_p <- result_p * pp
  result_n <- result_n * np
  return(ifelse(result_p>=result_n,"positive","negative"))
}

#循环训练集上的每一条记录,根据模型统计预测值
rest <- apply(nb_df_train,1,function(p){calcPx(p,nb_df_train)})
#查看预测的准确性
mean(rest == scores_train)

当然,在R中,我们也可以使用naiveBayes函数来直接实现:

library("e1071")
nb_model <- naiveBayes(nb_df_train, scores_train)

nb_train_predictions <- predict(nb_model, nb_df_train)
mean(nb_train_predictions == scores_train)
table(actual = scores_train, predictions = nb_train_predictions)

nb_test_predictions <- predict(nb_model, nb_df_test)
mean(nb_test_predictions == scores_test)
table(actual = scores_test, predictions = nb_test_predictions)

拉普拉斯平滑

从上面的计算过程可以看出,我们来进行预测分类是依靠训练集数据在各属性上的分布情况来决定。但是在某些极端情况下,尤其是在文本分析的过程中,有可能我们的样本数据在某个属性上的分布并没有完全涵盖这个属性的所有可能情况。参考网上一个非常经典的案例:


假如男生的四个特征是,长相帅,性格爆好,身高高,上进,那么他的女朋友嫁还是不嫁呢?
我们会发现,在我们给定的训练集中,性格属性中,没有爆好这个分类。这样导致的结果会使得 P(性格=爆好|嫁) 的概率和P(性格=爆好|不嫁) 的概率都为0。根据朴素贝叶斯的公式: ,我们将会发现无法进行分类,因为我们计算出来的嫁与不嫁的概率都为0(因为是连乘,有一项为0,则全部都会为0)。 为了解决这种问题,我们在计算 时(主要是离散属性),采用如下公式进行修正:

其中, 代表第i个属性中,可能的取值个数。
在我们上面这个例子中,我们要计算 P(性格=爆好|嫁) 的概率,就等于
(因为性格属性有3个可能的值:爆好,好,不好。如果有四个属性值:爆好,好,一般,不好的话,分母就是加4)

以上就是对拉普拉斯平滑的通俗解释。下面来看代码:

#对函数进行修正,增加拉普拉斯平滑。新增的参数向量t,代表每个特征可能的属性值数量
calcPx <- function(v,t,data){
  data_p = data[scores_train == "positive",] #分类为“positive”的全量样本数据;
  data_n = data[scores_train == "negative",] #分类为“negative”的全量样本数据;
  pp = length(data_p) / length(data) #计算"positive"出现的概率
  np = length(data_n) / length(data) #计算"negative"出现的概率
  result_p <- 1
  result_n <- 1
  for(i in 1:length(v)){
    #计算两个类别下的:P(特征1|类别)*P(特征2|类别)*P(特征3|类别)...P(特征n|类别)
    #增加拉普拉斯平滑
    result_p <- result_p * (sum(data_p[,i]==v[i])+1/length(data_p[,i])+t[i]) 
    result_n <- result_n * (sum(data_n[,i]==v[i])+1/length(data_n[,i])+t[i])
  }
  result_p <- result_p * pp
  result_n <- result_n * np
  return(ifelse(result_p>=result_n,"positive","negative"))
}

如果使用naiveBayes函数,可以直接指定拉普拉斯平滑系数(一般为1,代表给每个特征加1,也可以为其他较小的数)

nb_model_laplace <- naiveBayes(nb_df_train, scores_train, laplace=1)

【参考文档】
如何通俗的理解贝叶斯定理
朴素贝叶斯的R实现
带你理解朴素贝叶斯分类算法
理解朴素贝叶斯分类的拉普拉斯平滑
概率图模型-朴素贝叶斯
概率图模型(3)朴素贝叶斯分类
机器学习21:概率图--朴素贝叶斯模型

你可能感兴趣的:(机器学习(九)——概率图模型之朴素贝叶斯)