在 SQL 中实现卷积

Implementing Convolution in SQL

我有一个 table d 字段 x, y, f,(PK 是 x,y)并且想实现卷积,其中一个新列 c,被定义为给定任意内核的卷积(2D)。在过程语言中,这很容易定义(见下文)。我相信可以使用 JOIN 在 SQL 中定义它,但我在这样做时遇到了麻烦。

在程序语言中,我会这样做:

def conv(x, y):
     c = 0
     # x_ and y_ are pronounced "x prime" and "y prime", 
     # and take on *all* x and y values in the table; 
     # that is, we iterate through *all* rows
     for all x_, y_
         c += f(x_, y_) * kernel(x_ - x, y_ - y)
     return c

kernel 可以是任意函数。在我的例子中,它是 1/k^(sqrt(x_dist^2, y_dist^2))kernel(0,0) = 1

出于性能原因,我们不需要查看每个 x_, y_。我们可以在距离 < 阈值的地方过滤它。

我认为这可以使用笛卡尔乘积连接、聚合 SQL SUM 以及 WHERE 子句来完成。

在 SQL 中执行此操作的另一个挑战是 NULL。天真的实现会将它们视为零。我想做的是将内核视为加权平均值,而忽略 NULL。也就是说,我将使用一个函数 wkernel 作为我的内核,并将上面的代码修改为:

def conv(x, y):
     c = 0
     w = 0
     for all x_, y_  
         c += f(x_, y_) * wkernel(x_ - x, y_ - y)
         w += wkernel(x_ - x, y_ - y)
     return c/w

这将使 NULL 工作得很好。

澄清一下:您不能进行部分观察,其中 x=NULL 且 y=3。但是,您可能缺少观察结果,例如没有 x=2 和 y=3 的记录。我指的是 NULL,因为整个记录都丢失了。我上面的程序代码可以很好地处理这个问题。

我相信以上可以在 SQL 中完成(假设 wkernel 已经实现为一个函数),但我不知道如何做。我正在使用 Postgres 9.4。


样本table:

 Table d
 x  | y  | f
 0  | 0  | 1.4
 1  | 0  | 2.3
 0  | 1  | 1.7
 1  | 1  | 1.2

输出(只显示一行):

 x  | y  | c
 0  | 0  | 1.4*1 + 2.3*1/k + 1.7*1/k + 1.2*1/k^1.414

卷积https://en.wikipedia.org/wiki/Convolution是整个图像处理和信号处理中使用的标准算法,我相信它可以在SQL中完成,这对于我们现在的大数据集来说非常有用使用。

我假定了一个函数wkernel,例如:

create or replace function wkernel(k numeric, xdist numeric, ydist numeric)
returns numeric language sql as $$
  select 1. / pow(k, sqrt(xdist*xdist + ydist*ydist))
$$;

以下查询提供了您想要的但不限于关闭值:

select d1.x, d1.y, SUM(d2.f*wkernel(2, d2.x-d1.x, d2.y-d1.y)) AS c
from d d1 cross join d d2 
group by d1.x, d1.y;

 x | y |            c            
---+---+-------------------------
 0 | 0 | 3.850257072695778143380
 1 | 0 | 4.237864186319019036455
 0 | 1 | 3.862992722666908108145
 1 | 1 | 3.725299918145074500610
(4 rows)

有一些任意限制:

select d1.x, d1.y, SUM(d2.f*wkernel(2, d2.x-d1.x, d2.y-d1.y)) AS c
from d d1 cross join d d2
where abs(d2.x-d1.x)+abs(d2.y-d1.y) < 1.1
group by d1.x, d1.y;

 x | y |            c            
---+---+-------------------------
 0 | 0 | 3.400000000000000000000
 1 | 0 | 3.600000000000000000000
 0 | 1 | 3.000000000000000000000
 1 | 1 | 3.200000000000000000000
(4 rows)

为加权平均分:

select d1.x, d1.y, SUM(d2.f*wkernel(2, d2.x-d1.x, d2.y-d1.y)) / SUM(wkernel(2, d2.x-d1.x, d2.y-d1.y)) AS c
from d d1 cross join d d2
where abs(d2.x-d1.x)+abs(d2.y-d1.y) < 1.1
group by d1.x, d1.y;

现在谈谈缺少的信息。在下面的代码中,请将 2 替换为要考虑的最大距离。

想法如下:我们找到所考虑图像的边界,并生成可能需要的所有信息。在您的示例中,最大范围为 1,我们需要所有对 (x, y) 满足 (-1 <= x <= 2) 和 (-1 <= y <= 2).

查找边界并确定范围=1 和 k=2(将此关系称为 cfg):

SELECT MIN(x), MAX(x), MIN(y), MAX(y), 1, 2
FROM d;

 min | max | min | max | ?column? | ?column? 
-----+-----+-----+-----+----------+----------
   0 |   1 |   0 |   1 |        1 |        2

正在生成一组完整的值(称此关系已完成):

SELECT x.*, y.*, COALESCE(f, 0)
FROM cfg
CROSS JOIN generate_series(minx - scope, maxx + scope) x
CROSS JOIN generate_series(miny - scope, maxy + scope) y
LEFT JOIN d ON d.x = x.* AND d.y = y.*;

 x  | y  | coalesce 
----+----+----------
 -1 | -1 |        0
 -1 |  0 |        0
 -1 |  1 |        0
 -1 |  2 |        0
  0 | -1 |        0
  0 |  0 |      1.4
  0 |  1 |      1.7
  0 |  2 |        0
  1 | -1 |        0
  1 |  0 |      2.3
  1 |  1 |      1.2
  1 |  2 |        0
  2 | -1 |        0
  2 |  0 |        0
  2 |  1 |        0
  2 |  2 |        0
(16 rows)

现在我们只需要使用之前给出的查询以及 cfg 和完成的关系来计算值。请注意,我们不计算边界值的卷积:

SELECT d1.x, d1.y, SUM(d2.f*wkernel(k, d2.x-d1.x, d2.y-d1.y)) / SUM(wkernel(k, d2.x-d1.x, d2.y-d1.y)) AS c
FROM cfg cross join completed d1 cross join completed d2
WHERE d1.x BETWEEN minx AND maxx
  AND d1.y BETWEEN miny AND maxy
  AND abs(d2.x-d1.x)+abs(d2.y-d1.y) <= scope
GROUP BY d1.x, d1.y;

 x | y |            c            
---+---+-------------------------
 0 | 0 | 1.400000000000000000000
 0 | 1 | 1.700000000000000000000
 1 | 0 | 2.300000000000000000000
 1 | 1 | 1.200000000000000000000
(4 rows)

合二为一,这给出了:

WITH cfg(minx, maxx, miny, maxy, scope, k) AS (
  SELECT MIN(x), MAX(x), MIN(y), MAX(y), 1, 2
  FROM d
), completed(x, y, f) AS (
  SELECT x.*, y.*, COALESCE(f, 0)
  FROM cfg
  CROSS JOIN generate_series(minx - scope, maxx + scope) x
  CROSS JOIN generate_series(miny - scope, maxy + scope) y
  LEFT JOIN d ON d.x = x.* AND d.y = y.*
)
SELECT d1.x, d1.y, SUM(d2.f*wkernel(k, d2.x-d1.x, d2.y-d1.y)) / SUM(wkernel(k, d2.x-d1.x, d2.y-d1.y)) AS c
FROM cfg cross join completed d1 cross join completed d2
WHERE d1.x BETWEEN minx AND maxx
  AND d1.y BETWEEN miny AND maxy
  AND abs(d2.x-d1.x)+abs(d2.y-d1.y) <= scope
GROUP BY d1.x, d1.y;

希望对您有所帮助:-)