朴素贝叶斯的实施 - 准确性问题

时间:2011-12-30 14:03:47

标签: python machine-learning

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

我使用了来自openClassroom的数据,并开始在Python中使用Naive Bayes的小版本。步骤是通常的训练,然后是预测。我有几个问题,想知道为什么准确性很差。

  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由于某种原因再次玩游戏: - /)

  3. 编辑:我删除了垃圾邮件=垃圾邮件+ 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%意味着实施中必须存在一些明显的错误。

2 个答案:

答案 0 :(得分: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%,这就是你应该瞄准的数字:)

答案 1 :(得分:2)

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

  1. 您将具有相同数量的垃圾邮件和非垃圾邮件的事实硬编码到您的程序中。我建议不要硬编码这个假设。这不是绝对必要的,但在更一般的情况下,您需要将其删除。
  2. 您已将您视为词汇量大小的某个数字编入您的程序。我不建议这样做,因为这个数字可能会随着训练集的任何修改而改变。而且,实际上它是不正确的。我建议在学习期间计算它。
  3. 这可能不是一个错误,但你似乎有训练集中所有单词的词汇。这可能不是最理想的;实际上,您推荐的页面建议仅考虑所有电子邮件中的前2500个单词。然而,这对于获得正确的结果并不是必不可少的 - 即使没有这种过滤,我的实现只会使几封电子邮件无法分类。
  4. 您错误地记录了仅在垃圾邮件或非垃圾邮件中观察到的字词。在其他子集中找到它们的对数概率不是您添加的1,而是log(1/(spamSize+vocabSize))log(1/(nonspamSize+vocabSize)),具体取决于其组。这实际上非常重要 - 您需要将此概率存储在您的数据中,以使程序正常运行。
  5. 您不会忽略训练集中从未观察到的单词。实际上这些可能会以不同的方式处理,但你应该考虑它们。
  6. 由于预测函数中的缩进不正确,您预测不使用整个消息,而只使用消息的第一行。只是一个编程错误。
  7. <强>更新即可。您已经修复了6.当您使用此数据集时,1并不是严格要求修复的,并且不需要3。 您的修改未正确修复4或5.首先,如果在某些集合中从未观察到该单词,则其中的消息概率应该降低。忽略这个词并不是一个好主意,你需要将其视为一个非常难以理解的词 其次,您当前的代码是不对称的,因为垃圾邮件中缺少的单词取消了对非垃圾邮件的检查(但不是另一种方式)。如果您不需要在异常处理程序中执行任何操作,请使用pass,而不是continue,因为后者会立即进入下一个for w in words:次迭代。 问题2仍然存在 - 您使用的词汇量与真实词汇量不匹配。它必须是训练集中观察到的不同单词的数量,而不是所有消息中的单词总数。