寻找 C4.5 算法的C++实现

Looking for a C++ implementation of the C4.5 algorithm

本文关键字:C++ 实现 算法 C4 寻找      更新时间:2023-10-16

我一直在寻找 C4.5 算法的C++实现,但还没有找到。我找到了昆兰的 C4.5 Release 8,但它是用 C 语言编写的......有没有人见过C4.5算法的任何开源C++实现?

如果我找不到开源C++实现,我正在考虑移植 J48 源代码(或者只是围绕 C 版本编写一个包装器),但我希望我不必这样做!如果您遇到该算法的C++实现,请告诉我。

更新

我一直在考虑围绕 C5.0 算法的 C 实现编写薄C++包装器的选项(C5.0 是 C4.5 的改进版本)。我下载并编译了 C5.0 算法的 C 实现,但它看起来不容易移植到C++。C 实现使用大量全局变量,简单地围绕 C 函数编写薄C++包装器不会导致面向对象的设计,因为每个类实例都将修改相同的全局参数。换句话说:我将没有封装,这是我需要的非常基本的东西。

为了获得封装,我需要将 C 代码的完整移植到 C++ 中,这与将 Java 版本(J48)移植到 C++ 中大致相同。

更新 2.0

以下是一些具体要求:

  1. 每个分类器实例必须封装自己的数据(即除了常量变量之外没有全局变量)。
  2. 支持分类器的
  3. 并发训练和分类器的并发评估。

这是一个很好的场景:假设我正在进行 10 倍交叉验证,我想同时训练 10 个决策树及其各自的训练集切片。如果我只为每个切片运行 C 程序,我将不得不运行 10 个进程,这并不可怕。但是,如果我需要实时对数千个数据样本进行分类,那么我将不得不为我要分类的每个样本启动一个新过程,而且效率不是很高。

C4.5 的C++实现称为 YaDT,位于此处的"决策树"部分:
http://www.di.unipi.it/~ruggieri/software.html

这是最新版本的源代码:
http://www.di.unipi.it/~ruggieri/YaDT/YaDT1.2.5.zip

从描述该工具的论文中:

[...]在本文中,我们描述了一种新的决策树归纳算法的从头开始C++实现,该算法以C4.5的风格产生基于熵的决策树。该实现称为 YaDT,是另一个决策树生成器的首字母缩写。本文的预期贡献是介绍实现的设计原则,这些原则允许获得高效的系统。我们讨论了我们在数据和元数据的内存表示和建模方面的选择,算法优化及其对内存和时间性能的影响,以及修剪启发式方法的效率和准确性之间的权衡。[...]

本文可在此处获得。

我可能已经找到了C5.0(See5.0)的可能C++"实现",但我无法深入研究源代码来确定它是否真的像宣传的那样工作。

为了重申我最初的担忧,端口的作者对 C5.0 算法陈述了以下内容:

See5Sam [C5.0] 的另一个缺点是不可能拥有超过 同时使用一个应用程序树。从中读取应用程序 文件,每次运行可执行文件并将其存储在全局中 这里和那里的变量。

一旦我有时间查看源代码,我将立即更新我的答案。

更新

它看起来不错,这是C++界面:

class CMee5
{
  public:
    /**
      Create a See 5 engine from tree/rules files.
      param pcFileStem The stem of the See 5 file system. The engine
             initialisation will look for the following files:
              - pcFileStem.names Vanilla See 5 names file (mandatory)
              - pcFileStem.tree or pcFileStem.rules Vanilla See 5 tree or rules
                file (mandatory)
              - pcFileStem.costs Vanilla See 5 costs file (mandatory)
    */
    inline CMee5(const char* pcFileStem, bool bUseRules);
    /**
      Release allocated memory for this engine.
    */
    inline ~CMee5();
    /**
      General classification routine accepting a data record.
    */
    inline unsigned int classifyDataRec(DataRec Case, float* pOutConfidence);
    /**
      Show rules that were used to classify the last case.
      Classify() will have set RulesUsed[] to
      number of active rules for trial 0,
      first active rule, second active rule, ..., last active rule,
      number of active rules for trial 1,
      first active rule, second active rule, ..., last active rule,
      and so on.
    */
    inline void showRules(int Spaces);
    /**
      Open file with given extension for read/write with the actual file stem.
    */
    inline FILE* GetFile(String Extension, String RW);
    /**
      Read a raw case from file Df.
      For each attribute, read the attribute value from the file.
      If it is a discrete valued attribute, find the associated no.
      of this attribute value (if the value is unknown this is 0).
      Returns the array of attribute values.
    */
    inline DataRec GetDataRec(FILE *Df, Boolean Train);
    inline DataRec GetDataRecFromVec(float* pfVals, Boolean Train);
    inline float TranslateStringField(int Att, const char* Name);
    inline void Error(int ErrNo, String S1, String S2);
    inline int getMaxClass() const;
    inline int getClassAtt() const;
    inline int getLabelAtt() const;
    inline int getCWtAtt() const;
    inline unsigned int getMaxAtt() const;
    inline const char* getClassName(int nClassNo) const;
    inline char* getIgnoredVals();
    inline void FreeLastCase(void* DVec);
}

我会说这是我迄今为止找到的最好的选择。

如果我没看错的话...它似乎不是作为C API组织的,而是作为C程序组织的。 输入数据集,然后运行算法并返回一些规则描述。

我认为你应该走的路取决于你是否:

  1. 只需要一个C++接口来提供数据和从现有引擎检索规则,或者......

  2. 想要一个可以修补的C++实现,以便根据自己的目的调整算法

如果你想要的是(1),那么你真的可以把程序作为一个进程生成,以字符串的形式输入它,并将输出作为字符串。 这可能是开发"包装器"的最简单和最面向未来的方法,然后你只需要开发C++类来表示输入并对规则结果进行建模(或将现有类与这些抽象相匹配)。

但如果你想要的是(2)...那么我建议在 C 或 Java 的现有代码之上尝试你想到的任何技巧——以你最舒服的方式为准。 您将以这种方式了解代码,如果您有任何改进,您可以将它们提供给上游的作者。 如果你建立了长期的关系,那么也许你可以合作,把C代码库慢慢地推进到C++,一次一个方面,就像语言的设计一样。

猜我只是认为"在罗马时"的哲学通常比Port-In-One-Go更有效,尤其是在开始时。


对更新的响应:进程隔离可以处理全局变量问题。 至于性能和数据集大小,您只有与您拥有的一样多的内核/CPU 和内存。 当您谈论该级别的规模问题时,您使用的是进程还是线程通常不是问题。 您遇到的开销是编组是否太昂贵。

证明编组是瓶颈,在多大程度上...您可以构建一个案例来说明为什么进程在线程上出现问题。 但是,可能会对现有代码进行一些小的调整,以使编组更便宜,而不需要重写。