【问题标题】:Implementation of Naive Bayes - accuracy issues朴素贝叶斯的实现 - 准确性问题
【发布时间】:2012-01-30 14:27:27
【问题描述】:

编辑:可以在以下位置找到正确的代码版本: https://github.com/a7x/NaiveBayes-Classifier

我使用了来自 openClassroom 的数据,并开始使用 Python 编写一个小版本的朴素贝叶斯。步骤是通常的训练,然后是预测。我有几个问题,想知道为什么准确性很差。

  1. 对于训练,我通过以下公式计算了对数似然:

    log( P ( word | spam ) +1 ) /( spamSize + vocabSize .)

    我的问题是:为什么我们在这种情况下添加vocabSize :( 这是正确的做法吗?使用的代码如下:

    #This is for training.     Calculate all probabilities and store them in a vector. Better to store it in a file  for easier access 
    from __future__ import division
    import sys,os
    ''' 
    1. The spam and non-spam is already 50%  . So they by default are 0.5
    2. Now we need to calculate probability of each word    , in spam and non-spam separately
      2.1  we can make two dictionaries, defaultdicts basically,  for spam and non-spam 
      2.2 When time comes to calculate probabilities, we just need to substitute values
    '''
    from collections import *
    from math import *
    
    spamDict = defaultdict(int)
    nonspamDict = defaultdict(int)
    spamFolders = ["spam-train"]
    nonspamFolders = ["nonspam-train"]
    path = sys.argv[1] #Base path
    spamVector = open(sys.argv[2],'w') #WRite all spam values into this 
    nonspamVector = open(sys.argv[3],'w') #Non-spam values
    
    #Go through all files in spam and  iteratively add values
    spamSize = 0
    nonspamSize = 0
    vocabSize = 264821
    for f in os.listdir(os.path.join(path,spamFolders[0])):
        data = open(os.path.join(path,spamFolders[0],f),'r')
    
        for line in data:
            words = line.split(" ")
            spamSize = spamSize + len(words)
            for w in words:
                spamDict[w]+=1
    
    for f in os.listdir(os.path.join(path,nonspamFolders[0])):
        data = open(os.path.join(path,nonspamFolders[0],f),'r')
        for line in data:
            words = line.split(" ")
            nonspamSize = nonspamSize + len(words)
            for w in words:
    
                nonspamDict[w]+=1
    logProbspam = {}
    logProbnonSpam = {} #This is to store the log probabilities
    for k in spamDict.keys():
        #Need to calculate P(x | y = 1)
    
        numerator =  spamDict[k] + 1  # Frequency
        print 'Word',k,' frequency',spamDict[k]
        denominator = spamSize + vocabSize
        p = log(numerator/denominator)
        logProbspam[k] = p
    for k in nonspamDict.keys():
        numerator = nonspamDict[k] + 1 #frequency
        denominator = nonspamSize + vocabSize
        p = log(numerator/denominator)
        logProbnonSpam[k] = p
    
    for k in logProbnonSpam.keys():
        nonspamVector.write(k+" "+str(logProbnonSpam[k])+"\n")
    for k in logProbspam.keys():
        spamVector.write(k+" "+str(logProbspam[k])+"\n")
    
  2. 对于预测,我只是拿了一封邮件,将其拆分为单词,添加所有概率,分别为 垃圾邮件/非垃圾邮件,并将它们乘以 0.5。以较高者为准。代码如下:

    http://pastebin.com/8Y6Gm2my(Stackoverflow 出于某种原因又开始玩游戏了:-/)

编辑:我已经删除了垃圾邮件 = 垃圾邮件 + 1 件事。相反,我只是忽略那些话

问题:我的准确性很差。如下所述。

    No of files in spam is 130
    No. of spam in  ../NaiveBayes/spam-test  is  53  no. of non-spam 77
    No of files in non-spam is 130
    No. of spam in  ../NaiveBayes/nonspam-test/  is  6  no. of non-spam 124

请告诉我哪里出了问题。我认为低于 50% 的准确度意味着在实施过程中一定存在一些明显的错误。

【问题讨论】:

  • 您不是在做“朴素贝叶斯”,而是在用拉普拉斯平滑做贝叶斯,“k”(拉普拉斯平滑参数)等于 1。这就是为什么要在分子上加 1( k) 和 vocabsize (k * set_size) 为分母。
  • 上面的代码你运行了吗?它不会给你一个数学领域的错误吗?您需要将计数收集为浮点数,否则在除法时一切都将变为零。
  • 转到ai-class.com/home 并观看有关“机器学习”(第 5 课)的视频,以了解有关拉普拉斯平滑的更多信息以及它是否正确。使用它有利有弊。
  • @JimClay:几乎在多项式朴素贝叶斯的每一个实际实现中都会执行拉普拉斯或利德斯通平滑。
  • 我认为 from future 进口部门负责部门。是的,我加了拉普拉斯来弥补缺席。谢谢

标签: python machine-learning


【解决方案1】:

您的程序中存在多个错误和错误假设 - 在它的两个部分中。这里有几个。

  1. 您将拥有相同数量的垃圾邮件和非垃圾邮件的事实硬编码到您的程序中。我建议不要硬编码这个假设。这不是绝对必要的,但在更一般的情况下,您需要将其删除。
  2. 您将一些数字编码到您的程序中,您将其视为词汇量大小。我不建议这样做,因为这个数字可能会随着训练集的任何修改而改变。此外,实际上它是不正确的。我建议在学习期间计算它。
  3. 这可能不是一个错误,但您似乎拥有训练集中所有单词的词汇表。这可能不是最理想的;实际上,您引用的页面建议仅考虑所有电子邮件中的前 2500 个单词。但是,这对于获得正确结果并不是必需的 - 即使没有此过滤,我的实现也只能收到几封未分类的电子邮件。
  4. 您错误地解释了仅在垃圾邮件或非垃圾邮件中观察到的词。在另一个子集中找到它们的日志概率不是您添加的1,而是log(1/(spamSize+vocabSize))log(1/(nonspamSize+vocabSize)),具体取决于其组。这实际上非常重要 - 您需要将此概率与数据一起存储,以使程序正常运行。
  5. 您不会忽略在训练集中从未观察到的单词。实际上,这些可能会有不同的处理方式,但您应该考虑到它们。
  6. 由于预测函数中不正确的缩进,您预测的不是整条消息,而是消息的第一行。只是一个编程错误。

更新。您已经修复了 6 个。此外,在您使用此数据集时,1 并不是严格需要修复的,并且 3 不是必需的。
您的修改没有正确修复 4 或 5。首先,如果该词从未在某些集合中被观察到,则其中消息的概率应该降低。忽略这个词不是一个好主意,您需要将其视为一个非常不可能的词。
其次,您当前的代码是不对称的,因为垃圾邮件中的单词不存在取消了对非垃圾邮件的检查(但不是相反)。如果您不需要在异常处理程序中执行任何操作,请使用pass,而不是continue,因为后者会立即进入下一个for w in words: 迭代。
问题 2 仍然存在——您使用的词汇量与真实的不符。它必须是在训练集中观察到的不同单词的数量,而不是所有消息中的总单词数。

【讨论】:

  • 1.我对它进行了硬编码,因为它实际上有一个相同的编号。垃圾邮件和非垃圾邮件
  • 2.按照网站上的建议,我将只取 2500,然后 3。我更改了缩进,但遗憾的是答案保持不变:(我将修改仅在垃圾邮件/非垃圾邮件中找到的单词的会计处理。谢谢。
  • 目前主要问题似乎是数字 4。如果您从未在垃圾邮件中看到过某个词,那么它就非常不可能,您需要严重惩罚这个概率,而不是通过添加来增加它一。
  • 我猜你的意思是我做 numerator = spamDict[k]+1 的部分应该改成 spamdict[k]+log(1/(number)) ??
  • 不,你错了。违规行是except 子句中的spamP = spamP + 1,该子句位于predict 函数中。还有一个关于非垃圾邮件的。
【解决方案2】:

这至少是您所犯的错误之一:您将日志概率存储在模型文件中(如您所愿),但随后在预测代码中您假装它们是直接概率:

totalSpamP = spamP * 0.5

应该是

totalSpamP = spamP + math.log(0.5)

另外,我不明白这条线在做什么:

spamP = spamP + 1

它似乎弥补了训练集中垃圾邮件部分中没有的特征,但这些词应该被简单地忽略。现在,它正在将 e (exp(1)) 添加到概率中,根据定义,这是无效的。

(顺便说一句,我刚刚尝试使用my own implementation of Naive Bayes 对这个训练集进行分类,并获得了 97.6% 的准确率,所以这就是你应该瞄准的数字 :)

【讨论】:

  • 嗨,让我进行更改并回复您!非常感谢!
  • 顺便说一句,我们为什么要添加词汇大小?拉普拉斯是否平滑了添加它的原因?
  • 是的 spamP = spamP + 1 加了 1,现在我已经删除了。
  • 您好,我已经按照您的建议进行了更改。至少对于非垃圾邮件的情况,准确性肯定有所提高,但垃圾邮件的情况保持不变:(应该进行哪些其他更改?
  • 您好,有一个小错误导致非垃圾邮件准确性出现问题。它的固定。但是垃圾邮件仍然非常错误:(
猜你喜欢
  • 2017-11-05
  • 2012-04-09
  • 2012-07-31
  • 2016-08-12
  • 2016-08-25
  • 2020-01-13
  • 2017-12-21
  • 2017-11-06
  • 2015-09-16
相关资源
最近更新 更多