模糊逻辑的操作加速

306 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情

先前我们实现了挺多的数据处理操作。

其中我们还稍微动了点小心思,有一些操作是仅仅定义在向量级别。于是我们做了一个复用了外部的逻辑,以策略模式优化了代码量。

在求模糊相似矩阵时,即使是复用了部分代码之后,我们也只是在向量级别使用了numpy 的向量操作。尽管一定程度上有向量化加速。

但是在外层逻辑中,有一个致命的问题:双重的 Python 循环。

这会带来很大的性能损失,为此我们的目标是通过一定的手法将循环操作去掉。让整个运算都能获得向量化加速。

重点在于意会一下,我们的想法是先不考虑具体的聚合。

把运算的过程先看成是逐元素的,这样一来,比如说这里的复合运算,两个矩阵各自给出一个向量,这两个向量长度一定是相等的。首先两个之间取较小值。然后在者之间取一个较大值作为最终的结果。

手法也是从这里开始用的,如果我这样看这个运算,先得到一个矩阵?也不能叫矩阵了,它的 i,j 位置是一个向量 ,其值是上面的较小值。当计算到了这一步之后,只需要在最后一个维度上求最大值就得到了结果。

我们的想法在于如果实现先运算得到一个这样的结果,一个合理的猜测是利用广播机制,使得它们相当于这种运算。

比如说先前说过的极大极小复合运算可以像下面这样实现:

def composite(x: Tensor, y: Tensor) -> Tensor:
    return np.minimum(x[:, None], y.T).max(axis=2)

比如说计算:

m = np.array([
    [0.5, 0.3],
    [0.4, 0.8]
])
n = np.array([
    [0.8, 0.5, 0.1],
    [0.3, 0.7, 0.5],
])
print(composite(m, n))

m 会变成2,1,2的矩阵,而n会变成4,2的矩阵,这个由于广播机制,最后一个维度不会广播,而中间的维度第一个值会变成4列。第一个维度n相当于复制成了2行。

有了这个想法,那几个求相似度的函数也能以同样的方式加速。

值得注意的点在于,那些运算可以认为是两个类似这种运算相除得到的,而对于其中的每个,都可以用上述的方法加速。另外值得注意的一点是这里的复合运算实际上是行和列进行运算。而相似矩阵中实际上是行和行之间的运算,不需要再使用转置操作。

通过这样的加速,基本上可以提高50倍的运算速度。