Vector 在clojure,什么';用核卷积向量的有效方法是什么?

Vector 在clojure,什么';用核卷积向量的有效方法是什么?,vector,clojure,linear-algebra,convolution,Vector,Clojure,Linear Algebra,Convolution,我想到了这个: (def kernel [0 1 1 2 3 3 0 0 0 0 0 0]) (def data [1 5 7 4 8 3 9 5 6 3 2 1 1 7 4 9 3 2 1 8 6 4]) (defn capped+ [a b c] (let [s (+ a b)] (if (> s c) c s))) (defn *+ [a b] (if (> (count a) (count b)) (reduce + (map-indexed (fn _

我想到了这个:

(def kernel [0 1 1 2 3 3 0 0 0 0 0 0])
(def data [1 5 7 4 8 3 9 5 6 3 2 1 1 7 4 9 3 2 1 8 6 4])

(defn capped+ [a b c] (let [s (+ a b)] (if (> s c) c s)))

(defn *+ [a b]
  (if (> (count a) (count b))  
    (reduce + (map-indexed (fn _ [i x] (* (a i) (b i))) b))
    (reduce + (map-indexed (fn _ [i x] (* (a i) (b i))) a))))

(defn slide*i [d k] 
 (let [ki (into [] (reverse k)) kl (count k) dl (count d)]
   (map-indexed 
      (fn [idx item] 
        (/ (*+ ki (subvec d idx (capped+ idx kl dl))) 
           (reduce + ki))) 
      d)))

(def s (slide*i data kernel))
这不是最优雅的代码,但它工作得很好。 我真的想用它来抚平一些非常尖锐的东西!数据


欢迎任何建议,使它更漂亮、更有效或更准确(就我个人而言,我不在乎尾巴不准确,因为在我的情况下,我从不使用它)

实现这一点的有效方法是创建执行卷积的java类,并从clojure调用它,如果可能的话,向它传递一个java数组。如果需要考虑效率,Clojure实现也应该在java阵列上运行

; unused, but left for demonstration (defn capped+ [a b c] (min (+ a b) c)) (defn *+ [a b] (reduce + (map * a b))) (defn slide*i [d k] (let [ki (reverse k) kl (count k) ks (reduce + k)] (map #(/ (*+ %1 ki) ks) (partition kl 1 d)))) 但是,使用
分区所有
,您将得到解决方案的确切结果:

(59/10 21/5 33/10 5/2 11/5 33/10 22/5 28/5 24/5 49/10 31/10 7/2 43/10 5 3 6/5 0 0 0 0 0 0)
(59/1021/533/1055/2111/533/1022/528/524/549/1031/1073/2433/1036/50000)您当然可以显著提高此操作的性能。好消息是,您不需要为此而使用Java:如果您正确地优化Clojure,它的速度非常快,并且在大多数情况下可以产生与纯Java相同的速度

要在Clojure中实现数字代码的最大性能,您需要使用:

  • 阵列,因为您需要具有快速写入和查找功能的可变存储。Clojure序列和向量非常漂亮,但它们带来了开销,您可能希望避免这些开销,以实现真正的性能关键代码
  • 加倍原语,因为它们提供更快的数学运算速度
  • aset/aget/areduce-这些是为阵列设计的极快操作,基本上为您提供与纯Java等价物相同的字节码
  • 命令式风格-尽管在Clojure中它是单一的,但它可以获得最快的结果(主要是因为您可以避免内存分配、装箱和函数调用带来的开销)。一个例子是用于快速命令循环
  • (设置!*反射时警告*true)-并消除代码产生的任何警告,因为反射是一个巨大的性能杀手
以下内容应该是正确的,可能会使您获得与Java大致相同的性能:

(def kernel (double-array [0 1 1 2 3 3 0 0 0 0 0 0]))
(def data (double-array [1 5 7 4 8 3 9 5 6 3 2 1 1 7 4 9 3 2 1 8 6 4]))

(defn convolve [^doubles kernel-array ^doubles data-array]
  (let [ks (count kernel-array)
        ds (count data-array)
        output (double-array (+ ks ds))
        factor (/ 1.0 (areduce kernel-array i ret 0.0 (+ ret (aget kernel-array i))))]    
    (dotimes [i (int ds)]
      (dotimes [j (int ks)]
        (let [offset (int (+ i j))]
          (aset output offset (+ (aget output offset) (* factor (* (aget data-array i) (aget kernel-array j))))))))
    output))

(seq (convolve kernel data))
=> (0.0 0.1 0.6 1.4 2.4 4.4 5.5 6.1000000000000005 5.600000000000001 6.200000000000001 5.499999999999999 5.9 4.199999999999999 3.3000000000000003 2.5 2.2 3.3 4.4 5.6000000000000005 4.8 4.8999999999999995 3.1 3.5 4.300000000000001 5.0 3.0 1.2000000000000002 0.0 0.0 0.0 0.0 0.0 0.0 0.0)
我没有修剪输出数组或进行任何绑定,因此您可能需要对该解决方案进行一些修改,以获得您想要的输出,但希望您能理解

一些非常粗略的基准测试:

(time (dotimes [i 1000] (seq (convolve kernel data))))
=> "Elapsed time: 8.174109 msecs"

i、 e.每个内核/数据对组合大约30纳秒-我认为这几乎达到了缓存内存访问的极限。

通常(至少对于我所做的事情而言),内核比数据向量短得多(比如一个有100个点的内核和有2000个点的数据)。作为卷积的定义,您需要在滑动内核之前反转(clojure)内核(尽管在大多数情况下,内核是对称的,所以您不需要这样做,或者您可以从一开始反转内核以节省时间和资源)。您认为我的解决方案中有什么不足之处吗?看起来不错,如果
k
是一个向量,你也可以使用
rseq
而不是
reverse
。但如果
k
仅为100个元素,则应该是一种改进
rseq
在向量上是O(1),而
reverse
是O(n)。谢谢@Looka,尽管我不想听这个!我不喜欢,我不知道(多)java,如果想使用java,我最好用C++或ObjuleC或C++来……也许我太喜欢clojure的思维方式了(我正在学习)(而且在CPU或内存效率方面仍然与java不匹配。如果使用普通clojure代码,只需迭代一个序列,就会产生成吨的小分配—增加分配、GC触发的大量执行时间。clojure的所有分配也会导致大内存占用,这将导致成吨的缓存未命中es-缓存未命中需要20-100次正常内存访问。在大数据集上进行卷积时会很痛苦。答案非常棒,注释非常全面。在
defn
中,
^double
是什么意思?为了让我明白,你的意思是我最好尽可能接近我在java中实现它的方式,对吗?我是说basicaly nested
for
循环。^doubles是指示原始双数组的类型提示。它有助于编译器优化aget方法。是的,nested for循环和数组变异基本上是解决此特定问题的最快方法。虽然不是非常Clojure-y,但它很有效!令我惊讶的是,这并没有快多少比下面简单的HOF方法更有效,尤其是启用
*未检查的数学*
(1.3中新增)时。有趣的是-你得到Alex的时间是什么?我还注意到我上面的算法比它需要的写密集度更高,因此可能还有另一种加速。
(time (dotimes [i 1000] (seq (convolve kernel data))))
=> "Elapsed time: 8.174109 msecs"