(另一个)Clojure 中循环递归的堆栈溢出

问题描述 投票:0回答:1

类似问题:一个两个三个

我在这里彻底困惑了。我正在使用 loop-recur 形式,我正在使用 doall,但对于大循环我仍然会遇到堆栈溢出。我的 Clojure 版本是 1.5.1.

上下文:我正在训练一个神经网络来模拟异或。函数

xor
是前馈函数,获取权重并输入并返回结果;函数
b-xor
是反向传播函数,根据上次调用
xor
的结果返回更新后的权重。

以下循环运行良好,运行速度非常快,并返回结果,并且根据返回的结果,它正在完美地训练权重:

(loop [res 1        ; <- initial value doesn't matter
       weights xorw ; <- initial pseudo-random weights
       k 0]         ; <- count
  (if (= k 1000000)
      res
      (let [n (rand-int 4)
            r (doall (xor weights (first (nth xorset n))))]
        (recur (doall r)
               (doall (b-xor weights r (second (nth xorset n))))
               (inc k)))))

但是当然,这只能给我最后一次运行的结果。显然我想知道为了得到这个结果而训练了哪些权重!下面的循环,除了返回值改变之外什么都没有,会溢出:

(loop [res 1
       weights xorw
       k 0]
  (if (= k 1000000)
      weights              ; <- new return value
      (let [n (rand-int 4)
            r (doall (xor weights (first (nth xorset n))))]
        (recur (doall r)
               (doall (b-xor weights r (second (nth xorset n))))
               (inc k)))))

这对我来说没有意义。每次调用

weights
时都会使用整个
xor
。那么为什么我可以在内部使用
weights
但不能将其打印到 REPL?

正如你所看到的,我在各种地方都卡住了

doall
,超出了我认为我应该需要的程度。 XOR 是一个玩具示例,因此
weights
xorset
都非常小。我相信溢出不是由于执行
xor
b-xor
而发生的,而是当REPL尝试打印
weights
时发生的,原因有两个:

(1) 这个循环最多可以循环 1500 次而不会溢出堆栈。

(2)循环运行的时间与循环的长度一致;也就是说,如果我循环到5000,它会运行半秒,然后打印堆栈溢出;如果我循环到 1000000,它会运行十秒,然后打印堆栈溢出——同样,只有当我在最后打印

weights
而不是
res
时。

(3) 编辑:另外,如果我只是将循环包装在

(def w ... )
中,那么就不会出现堆栈溢出。不过,尝试查看结果变量确实如此。

user=> (clojure.stacktrace/e)
java.lang.StackOverflowError: null
 at clojure.core$seq.invoke (core.clj:133)
    clojure.core$map$fn__4211.invoke (core.clj:2490)
    clojure.lang.LazySeq.sval (LazySeq.java:42)
    clojure.lang.LazySeq.seq (LazySeq.java:60)
    clojure.lang.RT.seq (RT.java:484)
    clojure.core$seq.invoke (core.clj:133)
    clojure.core$map$fn__4211.invoke (core.clj:2490)
    clojure.lang.LazySeq.sval (LazySeq.java:42)
nil

惰性序列在哪里?

如果您有更好的方法来做到这一点的建议(这只是我的即时 REPL 代码),那就太好了,但我真的在寻找关于这种情况下发生的情况的解释。


编辑 2:绝对(?)REPL 有问题。

这很奇怪。

weights
是一个包含六个列表的列表,其中四个为空。到目前为止,一切都很好。但是尝试将其中一个空列表打印到屏幕会导致堆栈溢出,但这只是第一次。第二次打印时没有抛出任何错误。打印非空列表不会产生堆栈溢出。现在我可以继续我的项目了,但是......这里到底发生了什么?有任何想法吗? (请原谅以下丑陋之处,但我认为这可能会有所帮助)

user=> (def ww (loop etc. etc. ))
#'user/ww
user=> (def x (first ww))
#'user/x
user=> x
StackOverflowError   clojure.lang.RT.seq (RT.java:484)
user=> x
()
user=> (def x (nth ww 3))
#'user/x
user=> x
(8.47089879874061 -8.742792338501289 -4.661609290853221)
user=> (def ww (loop etc. etc. ))
#'user/ww
user=> ww
StackOverflowError   clojure.core/seq (core.clj:133)
user=> ww
StackOverflowError   clojure.core/seq (core.clj:133)
user=> ww
StackOverflowError   clojure.core/seq (core.clj:133)
user=> ww
StackOverflowError   clojure.core/seq (core.clj:133)
user=> ww
(() () () (8.471553034351501 -8.741870954507117 -4.661171802683782) () (-8.861958958234174 8.828933147027938 18.43649480263751 -4.532462509591159))
clojure
1个回答
9
投票

如果您在包含更多惰性序列的序列上调用

doall
,则
doall
不会递归地迭代子序列。在这种特殊情况下,
b-xor
的返回值包含从先前的空列表延迟定义的空列表,从先前的空列表延迟定义,依此类推。我所要做的就是向生成空列表的
doall
添加一个
map
(在
b-xor
中),问题就消失了。这个循环(所有的 doall 都被删除)永远不会溢出:

(loop [res 1
       weights xorw
       k 0]
  (if (= k 1000000)
      weights 
      (let [n (rand-int 4)
            r (xor weights (first (nth xorset n)))]
        (recur r
               (b-xor weights r (second (nth xorset n)))
               (inc k)))))

我希望这对其他一些可怜的灵魂有帮助,他们认为他已经用一个位置不好的

doall
解决了他的懒惰排序问题。

© www.soinside.com 2019 - 2024. All rights reserved.