【问题标题】:(Another) Stack overflow on loop-recur in Clojure(另一个)Clojure 中循环递归的堆栈溢出
【发布时间】:2013-09-19 14:04:17
【问题描述】:

类似问题:OneTwoThree

我在这里彻底糊涂了。我正在使用loop-recur 表单,我正在使用doall,但对于大循环,我仍然会出现堆栈溢出。我的 Clojure 版本是 1.5.1。

上下文:我正在训练一个神经网络来模拟 XOR。函数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)))))

这对我来说没有意义。每次调用xor 时都会使用整个weights。那么为什么我可以在内部使用 weights 而不能将其打印到 REPL 中呢?

如您所见,我已将doall 卡在各种地方,超出了我认为我应该需要的范围。 XOR 是一个玩具示例,所以weightsxorset 都非常小。我相信溢出不是来自xorb-xor的执行,而是当REPL尝试打印weights时,出于以下两个原因:

(1) 这个循环可以达到 1500 而不会溢出堆栈。

(2)循环运行的时间与循环的长度一致;也就是说,如果我循环到 5000,它会运行半秒,然后打印堆栈溢出;如果我循环到 1000000,它会运行 10 秒,然后打印堆栈溢出 - 再一次,只有当我最后打印 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))

【问题讨论】:

  • (def xorset [[[0 0 1 1] [0]] [[0 1 1 1] [1]] [[1 0 1 1] [1]] [[1 1 1 1] [0]]])
  • xor/b-xor 返回什么(是惰性序列)?
  • 我以为我把它们包装在doall 中已经无关紧要了。但我只是想通了(部分)问题:doall 不是递归的,b-xor 返回的惰性序列包含惰性子序列。
  • 感谢您花时间看这个!

标签: clojure


【解决方案1】:

如果您在包含更多惰性序列的序列上调用 doalldoall 不会递归地遍历子序列。在这种特殊情况下,b-xor 的返回值包含从先前的空列表中延迟定义的空列表,这些空列表是从先前的空列表中延迟定义的,依此类推。我所要做的就是在产生空列表的map 中添加一个doall(在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 解决了他的懒惰排序问题的可怜人有所帮助。

这仍然给我留下了一个关于 REPL 的问题,但它可能应该考虑一个不同的问题,这样它就不会有这个问题的所有包袱。您可以在我上面的问题中看到空列表被正确评估。为什么第一次打印它们会引发异常?我将对此进行一些实验,如果我无法弄清楚...新问题!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-04-04
    • 2017-01-20
    • 2018-12-02
    • 2017-09-06
    • 2019-07-08
    • 2015-08-05
    • 2017-09-29
    • 1970-01-01
    相关资源
    最近更新 更多