我在这里彻底搞砸了。我正在使用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)))))
这对我没有意义。每次调用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))
答案 0 :(得分: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
解决了他的懒惰排序问题。
这仍然让我有一个关于REPL的问题,但它应该在一个不同的问题下进行,所以它不会有这个问题的所有包袱。您可以在上面的问题中看到空列表 正确评估。为什么第一次打印它们会抛出异常?我将对此进行一些实验,如果我无法弄清楚......新问题!