一起养成写作习惯!这是我参与「掘金日新计划 · 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倍的运算速度。