代码之家  ›  专栏  ›  技术社区  ›  Greg Beech

tdd与贝叶斯垃圾邮件过滤问题

  •  4
  • Greg Beech  · 技术社区  · 16 年前

    众所周知,贝叶斯分类器是一种有效的垃圾邮件过滤方法。这些代码可以相当简洁(我们的代码只有几百个loc),但是所有核心代码都需要在得到任何结果之前预先编写好。

    但是,TDD方法要求只能编写通过测试的最小数量的代码,因此给定以下方法签名:

    bool IsSpam(string text)
    

    以及以下明显是垃圾邮件的文本字符串:

    "Cheap generic viagra"
    

    我能写的最少代码量是:

    bool IsSpam(string text)
    {
        return text == "Cheap generic viagra"
    }
    

    现在我可以添加另一个测试消息,例如。

    "Online viagra pharmacy"
    

    我可以把密码改成:

    bool IsSpam(string text)
    {
        return text.Contains("viagra");
    }
    

    ……等等。直到某个时候,代码变成一堆字符串检查、正则表达式等,因为我们已经 进化的 而不是从一开始就用不同的方式去思考和写作。

    那么,在这种情况下,tdd应该如何工作呢?在这种情况下,将代码从最简单的代码演化为通过测试并不是正确的方法。(特别是如果事先知道,最好的实现不能简单地进化)。

    9 回复  |  直到 16 年前
        1
  •  4
  •   Esko Luontola    16 年前

    首先为垃圾邮件过滤算法的低级部分编写测试。

    首先,你需要有一个粗略的算法设计应该如何。然后分离算法的核心部分并为其编写测试。在垃圾邮件过滤器的情况下,可能会计算一些简单的概率使用贝叶斯定理(我不知道贝叶斯分类器,所以我可能是错的)。你自下而上一步一步地建立它,直到最后你实现了算法的所有部分,把它们放在一起很简单。

    它需要大量的实践来知道哪些测试要按哪个顺序编写,这样您就可以在足够小的步骤中完成tdd。如果您需要编写超过10行的代码来通过一个新的测试,那么您可能是做错了什么。从更小的东西开始,或者模仿一些依赖关系。在较小的方面犯错误会更安全,因为步骤太小,进度也很慢,而不是试图迈出太大的步骤而失败得很惨。

    “便宜的普通伟哥”的例子,你可能更适合 acceptance test . 它甚至可能运行得很慢,因为您首先需要用示例数据初始化垃圾邮件过滤器,所以它在tdd测试中没有用处。TDD测试需要 FIRST (f=快速,如每秒数百或数千次测试)。

        2
  •  2
  •   Svante    16 年前

    我的看法是:测试驱动的开发意味着在编写代码之前编写测试。这并不意味着为其编写测试的每一个代码单元都需要很简单。

    此外,您仍然需要计划您的软件,以合理和有效的方式完成其任务。简单地添加越来越多的字符串似乎不是解决这个问题的最佳设计。

    所以简而言之,你用可能的最小的功能来编写代码(并测试它),但是你没有那样设计你的算法(用伪代码或者你喜欢的方式)。

    如果你和其他人同意的话,我会很感兴趣的。

        3
  •  0
  •   mouviciel    16 年前

    对我来说,你所说的 通过测试的最小代码量 就是整个 IsSpam() 功能。这与它的大小一致(你说只有几百个位置)。

    或者,增量方法不要求先编码,然后再思考。您可以设计解决方案,对其进行编码,然后使用特殊情况或更好的算法优化设计。

    无论如何,重构并不只是简单地在旧的基础上添加新的东西。对我来说,这是一种更具破坏性的方法,在这种方法中,你抛弃了一个简单功能的旧代码,而用一个更精致、更精细的功能的新代码替换它。

        4
  •  0
  •   Lennaert    16 年前

    你有单元测试,对吧?

    这意味着您现在可以重构代码,甚至重写它,并使用单元测试来查看是否有损坏。

    先把它弄好,再把它弄干净 --是时候迈出第二步了:)

        5
  •  0
  •   Daniel Daranas    16 年前

    (1)你不能像说一个数字是素数一样说一个字符串“是spam”或“不是spam”。这不是黑色或白色的。

    (2)仅仅使用用于测试的示例来编写字符串处理函数是不正确的,这当然不是TDD的目的。例子应该代表一种价值观。TDD并不能防止愚蠢的实现,所以你不应该假装你根本不知道,所以你不应该写 return text == "Cheap generic viagra" .

        6
  •  0
  •   Community CDub    8 年前

    在我看来,用贝叶斯垃圾邮件过滤器,你应该使用现有的方法。特别是你会用到贝叶斯定理,可能还有其他一些概率论。

    在这种情况下,最好的方法似乎是根据这些方法来决定你的算法,这些方法要么应该被尝试和测试,要么可能是实验性的。然后,您的单元测试应该设计为测试ispam是否正确实现了您决定的算法,以及测试结果是否在0和1之间的基本测试。

    关键是,你的单元测试不是为了测试你的算法是否合理而设计的。你要么已经知道了,要么你的程序被设计成一个实验,看看它是否合理。

    这并不是说isspam函数的性能不重要。但它不必是单元测试的一部分。数据可以来自阿尔法测试的反馈,新的理论结果,或者你自己的实验。在这种情况下,可能需要一个新的算法,并且需要新的单元测试。

    也见 this question 关于测试随机数生成器。

        7
  •  0
  •   Stompchicken    16 年前

    这里的问题不是测试驱动的开发,而是您的测试。如果您开始针对单个测试开发代码,那么您的测试所做的就是指定一个字符串检查函数。

    tdd的主要思想是在编写代码之前考虑您的测试。你不能对垃圾邮件过滤器进行详尽的测试,但是你可以通过几万或几十万个测试文档得出一个合理的近似值。在许多测试中,naive bayes算法比十万行switch语句更简单。

    实际上,您可能无法通过100%的单元测试,因此您只需尝试通过尽可能多的单元测试。你还必须确保你的测试足够真实。如果你这样想的话,测试驱动开发和机器学习有很多共同点。

        8
  •  0
  •   Tim Ottinger    15 年前

    你所描述的问题是理论上的,通过在测试中添加cruft,你将得到一个大的、杂乱的泥球。你丢失的东西很重要。

    循环是:红色-->绿色-->重构

    你不只是在红色和绿色之间跳跃。一旦测试通过(绿色),就重构生产代码和测试。然后编写下一个失败的测试(红色)。

    如果你正在重构,那么你就在消除重复、混乱和随著它的增长而变得邋遢。你将很快达到提取方法,建立评分和评级,并可能引入外部工具的地步。只要这是最简单的事情,你就会去做。

    不要在红色和绿色之间来回跳转,否则你所有的代码都将是垃圾。重构步骤不是可选的或任意的。这是必要的。

        9
  •  0
  •   Frank Schwieterman    15 年前

    我不认为检查某个字符串是否是垃圾邮件是真正的单元测试,它更多的是一个客户测试。有一个重要的区别,因为它不是真正的红色/贪婪类型的东西。实际上,您可能应该有几百个测试文档。最初,有些将被归类为垃圾邮件,随着您改进产品,分类将更直接地匹配您想要的。所以你应该制作一个定制的应用程序来加载一堆测试文档,对它们进行分类,然后评估整体评分。当你完成了客户测试,由于你还没有实现一个算法,分数将非常糟糕。但现在你有了一种方法来衡量未来的进展,考虑到你可以期待的学习/改变/实验的数量,这是相当有价值的。

    当您实现您的算法时(甚至是第一手的客户测试),您仍然可以使用真实的单元测试来执行tdd。对bayesian filter component的第一个测试不会衡量特定字符串是否计算为垃圾邮件,而是衡量字符串是否适当地通过bayesian filter component。接下来的测试将集中于如何实现贝叶斯过滤器(正确构造节点、应用训练数据等)。

    您确实需要一个关于产品走向的远景,并且您的测试和实现应该指向这个远景。您不能盲目地添加客户测试,您需要在添加测试时考虑到整个产品愿景。任何软件开发目标都有好的测试和坏的测试。