R中的朴素贝叶斯分类 - 从头开始

Posted

技术标签:

【中文标题】R中的朴素贝叶斯分类 - 从头开始【英文标题】:Naive Bayes Classification in R - from scratch 【发布时间】:2017-07-25 17:09:00 【问题描述】:

我编写了一些代码,使用 iris 数据集在 R 中手工制作 朴素贝叶斯分类器。我做了以下事情:

将数据分为 3 类 计算每个类的均值和方差 使用dnorm计算概率 乘以每个类的先验

每个结果的概率都非常。我想知道 后验函数 的部分是否正确?这是我的代码:

set.seed(1) #reproducibility
training_rows <- sort(c(sample(1:50, 40), sample(51:100, 40), sample(101:150, 40)))
training_x <- as.data.frame(iris[training_rows, 1:4])
training_y <- iris[training_rows, 5]

iris_nb <- function(x, trainx, trainy)
  train <- cbind(trainx, trainy)

  class_virginica <- train[which(train$trainy == 'virginica'),]
  class_setosa <- train[which(train$trainy == 'setosa'),]
  class_versicolor <- train[which(train$trainy == 'versicolor'),]

  posterior <- function(x, classtype)

    # Warning: bug here.
    p_Sepal.Length <- dnorm(x, mean(classtype[,1]), sd(classtype[,1]))
    p_Sepal.Width <- dnorm(x, mean(classtype[,2]), sd(classtype[,2]))
    p_Petal.Length <- dnorm(x, mean(classtype[,3]), sd(classtype[,3]))
    p_Petal.Width <- dnorm(x, mean(classtype[,4]), sd(classtype[,4]))

    vec <-  0.33* p_Sepal.Length * p_Sepal.Width * p_Petal.Length * p_Petal.Width #for each species
    return(vec)


  return(list(virginica = sum(posterior(x, class_virginica)), 
         setosa = sum(posterior(x, class_setosa)),
         versicolor = sum(posterior(x, class_versicolor))))

这是输出:

test_case_1 <- as.matrix(iris[1, 1:4])
iris_nb(test_case_1, training_x, training_y)

## $virginica
## [1] 1.167108e-16

## $setosa
## [1] 2.136291e-54

## $versicolor
## [1] 1.636154e-32

感谢您的帮助!

【问题讨论】:

【参考方案1】:

TL;DR;

代码中有一个错误:

您给dnorm 一个向量作为第一个参数,它返回一个向量。给它一个值以返回一个值。

说明

使用您提供的示例考虑以下内容:

trainx <- training_x; trainy <- training_y
train <- cbind(trainx, trainy)

class_setosa <- train[which(train$trainy == 'setosa'),]

mu <- mean(class_setosa[,1])
sigma <- sd(class_setosa[,1])

dnorm(test_case_1, mu, sigma)

##   Sepal.Length  Sepal.Width Petal.Length  Petal.Width
## 1     1.045569 0.0002481138 4.945912e-22 9.622888e-39

请注意,当给定一个向量时,dnorm 返回一个向量。

posterior 的返回值立即传递给 sum,从而隐藏了 bug。

有很多方法可以解决这个问题,但按照原始代码的意图,最简单的解决方案是索引观察向量。

解决方案是对向量进行索引。

dnorm(test_case_1[1], mu, sigma)

## [1] 1.045569

请注意,dnorm 不会返回有效概率,这在本例中很明显,因为返回的值大于 1。

最终解决方案

最终的解决方案是这样的

iris_nb <- function(x, trainx, trainy)
  train <- cbind(trainx, trainy)

  class_virginica <- train[which(train$trainy == 'virginica'),]
  class_setosa <- train[which(train$trainy == 'setosa'),]
  class_versicolor <- train[which(train$trainy == 'versicolor'),]

  posterior <- function(x, classtype)

    p_Sepal.Length <- dnorm(x[1], mean(classtype[,1]), sd(classtype[,1]))
    p_Sepal.Width  <- dnorm(x[2], mean(classtype[,2]), sd(classtype[,2]))
    p_Petal.Length <- dnorm(x[3], mean(classtype[,3]), sd(classtype[,3]))
    p_Petal.Width  <- dnorm(x[4], mean(classtype[,4]), sd(classtype[,4]))

    vec <-  0.33* p_Sepal.Length * p_Sepal.Width * p_Petal.Length * p_Petal.Width #for each species
    return(vec)
  

  return(list(virginica = sum(posterior(x, class_virginica)), 
         setosa = sum(posterior(x, class_setosa)),
         versicolor = sum(posterior(x, class_versicolor))))

并且使用与原始问题相同的测试用例:

iris_nb(test_case_1, training_x, training_y)

## $virginica
## [1] 1.861745e-24

## $setosa
## [1] 2.82984

## $versicolor
## [1] 8.334494e-22

请注意,答案已更改,现在 argmax 是 setosa 而不是 virginica。 此外,这些不是有效的后验概率,但与相关概率成正比,因此 argmax 仍然有效,这就是用于朴素贝叶斯分类的方法。

【讨论】:

【参考方案2】:

两个cmets:

1) 您计算的后验是基于它与prior * likelihood 成比例的事实。这一切都很好,但请记住贝叶斯定理说posterior = prior * likelihood / marginal。边际非常小,因此从您的计算中删除它会使您的后验概率也非常小(因为您没有除以它)。

一般来说,我们并不关心这些概率本身,我们关心的是它们的相对大小。所以virginica 这个测试用例的可能性似乎比其他物种高很多倍。所以朴素贝叶斯会输出virginicaargmax

2) 通常在使用概率乘积(即您的联合可能性)时,我们会改用对数概率,因为它们是相加的并且不会导致数值问题(这通常发生在将许多小数相乘时) )。

【讨论】:

以上是关于R中的朴素贝叶斯分类 - 从头开始的主要内容,如果未能解决你的问题,请参考以下文章

文末福利 | 一文实战朴素贝叶斯算法(附python演练)

R中的多项朴素贝叶斯分类器

译文:朴素贝叶斯算法简介(Python和R中的代码)

为朴素贝叶斯训练多少文件?

R构建朴素贝叶斯分类器(Naive Bayes Classifier)

如何在文本分类中使用朴素贝叶斯预测所需的类