我目前在Haskell中编写遗传算法,其中我的染色体是代表可执行系统的相当复杂的结构。
为了让我评估染色体的适应性,我必须运行evolution
函数,该函数执行给定系统的一个计算周期。然后通过计算在系统没有变化之前可以应用evolution
的次数来计算适应度(在这种情况下系统终止)。
现在的问题如下:一些系统可以无限长时间运行并且永远不会终止 - 我想惩罚那些(通过给予它们很少的分数)。我可以简单地对步数设置一定的限制,但它不能解决另一个问题。
我的一些系统执行指数计算(即使对于它们增长到巨大尺寸的渐变步骤的小值),它们也会导致ERROR - Control stack overflow
。对于人类观察者来说,很明显它们永远不会终止,但算法无法知道它是如何运行和压碎的。
我的问题是:是否可以从这样的错误中恢复?我希望我的算法在遇到此问题后继续运行,并相应地调整染色体分数。
在我看来,最好的解决办法就是告诉程序:“嘿,试试这个,但如果你失败了就不要担心。我知道如何处理它”。但是我甚至不确定这是否可能。如果没有 - 还有其他选择吗?
答案 0 :(得分:5)
在Haskell内部很难可靠地做到这一点 - 尽管在某些条件下GHC will raise exceptions for these conditions。 (你需要GHC 7)。
import Control.Exception
如果你真的只想捕获堆栈溢出,这是可能的,正如这个例子所示:
> handle (\StackOverflow -> return Nothing) $
return . Just $! foldr (+) 0 (replicate (2^25) 1)
Nothing
或者捕获任何异步异常(包括堆耗尽):
> handle (\(e :: AsyncException) -> print e >> return Nothing) $
return . Just $! foldr (+) 0 (replicate (2^25) 1)
stack overflow
Nothing
但是,这很脆弱。
或者,使用GHC flags,您可以在GHC编译的进程上强制执行最大堆栈(或堆)大小,如果超过这些限制则导致它被终止(GHC似乎没有最近的堆栈限制)
如果使用GHC编译Haskell程序(按照建议),则将其运行为:
$ ghc -O --make A.hs -rtsopts
强制执行以下低堆限制:
$ ./A +RTS -M1M -K1M
Heap exhausted;
这需要GHC。 (同样,你不应该使用Hugs进行这类工作)。最后,您应该首先通过profiling in GHC确保您的程序不使用过多的堆栈。
答案 1 :(得分:2)
我认为这里的一般解决方案是提供一种测量计算时间的方法,如果花费太多时间就将其杀死。您可以简单地将计数器添加到评估函数中(如果它是递归的),如果它降为零,则返回错误值 - 例如Nothing
,否则它是Just result
。
这种方法可以通过显式计数参数以其他方式实现,例如将此计数器放入评估使用的monad中(如果您的代码是monadic),或者不明确地,通过在单独的线程中运行计算,该计算将在超时时被终止。
我宁愿使用任何纯粹的解决方案,因为它会更可靠。
答案 2 :(得分:1)
It seems to me like the best solution would be to tell the program:
"Hey, try doing this, but if you fail don't worry. I know how to handle it"
在大多数语言中都是try/catch
块。我不确定haskell中的等价物是什么,或者即使存在某些等价物。此外,我怀疑try/catch
构造可以有效地捕获/处理堆栈溢出情况。
但是可以应用一些合理的约束来防止溢出发生吗?例如,也许您可以设置一个系统大小的上限,并监视每个系统从一次迭代到下一次迭代的接近边界的方式。然后,您可以强制执行一条规则,例如“如果在单个evolution
系统上超出其上限或消耗了其先前分配与其上限之间剩余空间的50%以上,该系统将终止并且遭受分数惩罚“。
答案 3 :(得分:1)
对遗传算法的一种思考:染色体适应性的一部分是它们不会消耗太多的计算资源。您提出的问题将“太多资源”定义为崩溃运行时系统。这是一个相当随意和有点随机的措施。
知道它会增加evolve
函数的复杂性,我仍然建议让这个函数知道染色体消耗的计算资源。这使你可以在它“吃得太多”并且过早死于“饥饿”时进行微调。它还可以让你根据染色体指数变化的速度来调整你的惩罚,因为只有几乎指数的染色体比具有极高分支因子的染色体更合适。