框架的 user-defined 参数应该放在哪里?

Where should the user-defined parameters of a framework be ?

我是个新手,我正在创建一个框架,使用进化算法在 C++ 中进化 objects。 进化算法进化 objects 并测试它们以获得最佳解决方案(例如,进化权重神经网络并在样本数据上对其进行测试,以便最终获得具有良好准确性的网络,而无需训练它)。

我的问题是算法有很多参数(selection/crossover/mutation 的类型,每个参数的概率...),因为它是一个框架,用户应该能够轻松访问并修改它们。

当前解决方案

现在,我创建了一个 header 文件 parameters.h 这种形式:

// DON'T CHANGE THESE PARAMETERS
//mutation type
#define FLIP 1
#define ADD_CONNECTION 2
#define RM_CONNECTION 3 

// USER DEFINED
static const int TYPE_OF_MUTATION = FLIP; 

用户修改静态变量TYPE_OF_MUTATION,然后我的变异函数测试TYPE_OF_MUTATION的值是什么,并调用正确的变异函数。

这很好用,但有一些缺点:

其他可能性

我考虑过将这些参数作为top-level函数的参数。问题是该函数将接受 20 个左右的参数,它看起来不太可读...

我对 top-level 函数的意思是,目前,进化算法是 运行 只需这样做:

  PopulationManager myPop; 
  myPop.evolveIt();

如果我将参数定义为实参,我们将得到类似的东西:

  PopulationManager myPop; 
  myPop.evolveIt(20,10,5,FLIP,9,8,2,3,TOURNAMENT,0,23,4);

您可以看到总是以正确的顺序定义参数是多么糟糕!

结论

我知道的框架可以让您从 pre-defined 函数中自己构建算法,但用户不必通过所有代码逐一更改参数。

指出该框架将在内部用于一组特定的项目可能会有用。

欢迎任何关于定义这些参数的最佳方式的输入!

如果选项没有改变,我通常为此使用一个结构:

 enum class MutationType {
  Flip,
  AddConnection,
  RemoveConnection
};

struct Options {
  // Documentation for mutation_type.
  MutationType mutation_type = MutationType::Flip;

  // Documentation for integer option.
  int integer_option = 10;
};

然后提供一个采用这些选项的构造函数。

Options options;
options.mutation_type = MutationType::AddConnection;
PopulationManager population(options);

C++11 使这变得非常简单,因为它允许为选项指定默认值,因此用户只需设置需要与默认值不同的选项。

另请注意,我为选项使用了枚举,这确保用户只能使用正确的值。

这是多态性的一个经典例子。在您建议的实现中,您正在执行常量切换以决定您将选择哪种多态变异算法来决定如何改变参数。在 C++ 中,相应的机制是模板(静态多态性)或虚函数(动态多态性)select 将适当的变异算法应用于参数。

模板方式的优点是一切都可以在编译时解析,并且生成的变异算法可以完全内联,具体取决于实现。您放弃的是在运行时动态 select 参数突变算法的能力。

虚函数方式的优点是您可以将变异算法的选择推迟到运行时,允许它根据用户的输入或诸如此类的东西而变化。缺点是变异算法不能再内联,当你改变参数时,你需要支付虚函数调用的成本(额外的间接级别)。

如果您想查看 "algorithmic mutation" 如何工作的真实示例,请查看 github 上的 evolve.cpp in my Iterated Dynamics 存储库。这是转换为 C++ 的 C 代码,因此它既不使用模板也不使用虚函数。相反,它使用函数指针和一个开关常量来 select 适当的代码。不过思路是一样的。

我的建议是先看看是否可以使用静态多态性(模板)。从你最初的描述来看,你无论如何都在编译时修复了突变,所以你没有放弃任何东西。

如果这只是一个原型设计阶段,并且您打算支持在运行时切换变异算法,那么请查看虚函数。正如另一个答案所建议的那样,请避免像 #define 常量这样的 C 风格编码,而是使用适当的枚举。

为了解决"long parameter list smell",将所有参数打包到一个结构中的想法是一个很好的想法。您可以通过使用 builder pattern to build up the structure of parameters in a more readable way than just assigning a bunch of values into a struct. In this blog post 获得更多的可读性,我将构建器模式应用于 Direct3D 中的资源描述结构。这让我可以更直接地用合理的默认值表达这些 "bags of data",并直接揭示我在必要时用特殊值覆盖或替换默认值的意图。