【问题标题】:How do I do convolution in F#?如何在 F# 中进行卷积?
【发布时间】:2009-04-29 15:57:07
【问题描述】:

我想要convolve 带有离散滤波器的离散信号。信号和滤波器是 F# 中的浮点序列。

我能弄清楚如何做到这一点的唯一方法是使用两个嵌套的 for 循环和一个可变数组来存储结果,但感觉不是很实用。

这是我将如何做到非功能性:

conv = double[len(signal) + len(filter) - 1]
for i = 1 to len(signal)
  for j = 1 to len(filter)
    conv[i + j] = conv[i + j] + signal(i) * filter(len(filter) - j) 

【问题讨论】:

  • 您能确认一下您的意思是数学卷积还是计算机科学中的卷积?
  • 嗯...现在我意识到您正在尝试做什么并查看代码示例,似乎您最好使用命令式解决方案,即使是使用函数式语言。我还是想一想……
  • 请注意,这个问题非常适合命令式编程,对于纯函数式编程来说是病态的。

标签: f# signal-processing convolution


【解决方案1】:

我不知道 F#,但我会发布一些 Haskell,希望它足够接近以供使用。 (我只有 VS 2005 和一个古老版本的 F#,所以我认为在我的机器上发布一些可以运行的东西会更加混乱)

首先让我发布你的伪代码的 Python 实现,以确保我得到正确的答案:

def convolve(signal, filter):
    conv = [0 for _ in range(len(signal) + len(filter) - 1)]
    for i in range(len(signal)):
        for j in range(len(filter)):
            conv[i + j] += signal[i] * filter[-j-1]
    return conv

现在convolve([1,1,1], [1,2,3]) 提供[3, 5, 6, 3, 1]。如果这是错误的,请告诉我。

我们可以做的第一件事就是把内循环变成一个zipWith;我们本质上是以一种特殊的方式添加一系列行,在上面的例子中:[[3,2,1], [3,2,1], [3,2,1]]。为了生成每一行,我们将使用反向过滤器压缩 signal 中的每个 i

makeRow filter i = zipWith (*) (repeat i) (reverse filter)

(注意:根据快速谷歌,zipWith 在 F# 中是 map2。您可能必须使用列表推导而不是 repeat) 现在:

makeRow [1,2,3] 1
=> [3,2,1]
makeRow [1,2,3] 2
=> [6,4,2]

要为所有i 获取此信息,我们需要映射信号:

map (makeRow filter) signal
=> [[3,2,1], [3,2,1], [3,2,1]]

很好。现在我们只需要一种正确组合行的方法。我们可以通过注意组合将新行添加到现有数组中来做到这一点,除了第一个元素,它卡在前面。例如:

[[3,2,1], [6,4,2]] = 3 : [2 + 6, 1 + 4] ++ [2]
// or in F#
[[3; 2; 1]; [6; 4; 2]] = 3 :: [2 + 6; 1 + 4] @ [2]

所以我们只需要编写一些在一般情况下执行此操作的代码:

combine (front:combinable) rest =
    let (combinable',previous) = splitAt (length combinable) rest in
    front : zipWith (+) combinable combinable' ++ previous

现在我们有了生成所有行的方法以及将新行与现有数组组合的方法,我们所要做的就是通过折叠将两者结合在一起:

convolve signal filter = foldr1 combine (map (makeRow filter) signal)

convolve [1,1,1] [1,2,3]
=> [3,5,6,3,1]

所以这是一个功能版本。我认为这是相当清楚的,只要您了解foldrzipWith。但它至少与命令式版本一样长,就像其他评论者所说的那样,在 F# 中可能效率较低。这就是一个地方的全部内容。

makeRow filter i = zipWith (*) (repeat i) (reverse filter)
combine (front:combinable) rest =
    front : zipWith (+) combinable combinable' ++ previous
    where (combinable',previous) = splitAt (length combinable) rest
convolve signal filter = foldr1 combine (map (makeRow filter) signal)

编辑:

正如所承诺的,这是一个 F# 版本。这是在 VS2005 上使用非常古老的版本(1.9.2.9)编写的,所以要小心。另外我在标准库中找不到splitAt,但是我不太了解F#。

open List
let gen value = map (fun _ -> value)
let splitAt n l = 
  let rec splitter n l acc =
    match n,l with
    | 0,_ -> rev acc,l
    | _,[] -> rev acc,[]
    | n,x::xs -> splitter (n - 1) xs (x :: acc)
  splitter n l [] 
let makeRow filter i = map2 ( * ) (gen i filter) (rev filter)
let combine (front::combinable) rest =
  let combinable',previous = splitAt (length combinable) rest
  front :: map2 (+) combinable combinable' @ previous
let convolve signal filter = 
  fold1_right combine (map (makeRow filter) signal)

【讨论】:

  • +1 我不知道这是否正确,但看起来你回答得很好
【解决方案2】:

试试这个功能:

let convolute signal filter =
    [|0 .. Array.length signal + Array.length filter - 1|] |> Array.map (fun i ->
        [|0 .. i|] |> Array.sum_by (fun j -> signal.[i] * filter.[Array.length filter - (i - j) - 1]))

这可能不是最好的函数解决方案,但它应该可以胜任。但是,我怀疑是否存在一种纯粹的功能解决方案,可以与速度的命令式解决方案相匹配。

希望对您有所帮助。

注意:该功能目前未经测试(尽管我已经确认它可以编译)。让我知道它是否没有完全做它应该做的。另外,请注意 ij 变量与您的原始帖子所指的内容不同。

【讨论】:

    【解决方案3】:

    确实,您通常希望避免循环(普通、嵌套等)和函数式编程中的任何可变因素。

    在 F#(可能几乎所有其他函数式语言)中恰好有一个非常简单的解决方案:

    let convolution = Seq.zip seq1 seq2
    

    zip 函数只是将两个序列组合成一对,其中包含来自seq1 的元素和来自seq2 的元素。需要注意的是,ListArray 模块也存在类似的 zip 函数,以及将三个列表组合成三元组的变体 (zip3)。如果您希望将 tom ore 通常将 n 个列表压缩(或“卷积”)到一个 n 元组列表中,那么您需要编写自己的函数,但这非常简单。

    (顺便说一句,我一直在使用卷积的this description - 告诉我你是否还有其他意思。)

    【讨论】:

    • 带有 DSP 标签,我认为 Hallgrim 几乎可以肯定意味着数学卷积,例如 \integral x(t)y(t-t') dt'。
    • @Barry:很好看。我完全不确定它最初是什么意思,但它似乎是指数字信号处理。问题中对序列的引用(除了卷积的其他定义更典型的 F# 问题)确实有点误导我......
    • @Nolforin 很抱歉造成混乱。我提到数学卷积。无论如何,我确实了解了正式语言中的 zip 函数和卷积。谢谢!
    • @Hallgrim:不用担心。 :) 无论如何我都会把答案留在这里,以防有人偶然发现这个问题,想知道另一种卷积。
    【解决方案4】:

    原则上,应该可以使用(快速)傅里叶变换或相关的(离散)余弦变换,合理有效地计算两个函数的卷积。您计算这两个函数的 FFT,将它们相乘,然后对结果应用逆 FFT。

    mathematical background

    这就是理论。在实践中,您可能最好找到一个为您实现它的数学库。

    【讨论】:

    • 我希望避免使用 FFT,因为对于简单的卷积来说这似乎有点矫枉过正。
    • 人们使用 FFT 计算卷积,因为它是 O(n log n),而在时域中是 O(n*n)。
    • @Paul:如果过滤器有紧凑的支持,那么它只是 O(n)。
    猜你喜欢
    • 1970-01-01
    • 2013-06-08
    • 1970-01-01
    • 1970-01-01
    • 2021-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-21
    相关资源
    最近更新 更多