【问题标题】:Can I vectorise this python code?我可以对这个 python 代码进行矢量化吗?
【发布时间】:2014-10-08 08:06:04
【问题描述】:

我编写了这个 python 代码来获取标签的邻居(一组共享一些共同属性的像素)。标签的邻居被定义为位于边界另一侧的其他标签(相邻标签共享一个边界)。所以,我写的代码可以工作,但速度极慢:

# segments: It is a 2-dimensional numpy array (an image really)
# where segments[x, y] = label_index. So each entry defines the
# label associated with a pixel.

# i: The label whose neighbours we want.

def get_boundaries(segments, i):
    neighbors = []
    for y in range(1, segments.shape[1]):
        for x in range(1, segments.shape[0]):
            # Check if current index has the label we want 
            if segments[x-1, y] == i:
                # Check if neighbour in the x direction has
                # a different label
                if segments[x-1, y] != segments[x, y]:
                    neighbors.append(segments[x,y])

            # Check if neighbour in the y direction has
            # a different label
            if segments[x, y-1] == i:
                if segments[x, y-1] != segments[x, y]:
                    neighbors.append(segments[x, y])

    return np.unique(np.asarray(neighbors))

你可以想象,我在这里可能完全误用了 python。我想知道是否有办法优化此代码以使其更具 Python 风格。

【问题讨论】:

  • 当然有一些方法可以让代码更符合 Python 风格,但如果这是您所希望的,那么这样做可能不会对性能产生太大影响
  • 你可以将你的 if 语句融合在一起,比如“if segments[x-1, y] == i and segments[x-1, y] != segments[x, y]:” 但是你不能让它更快
  • 对于分割和/或标记测量,我建议看一下scipy-lectures.github.io/advanced/image_processing/… 这就是我用来标记图像内的像素之类的东西。 (看看它,如果它符合你的要求,我可以写一个关于它的答案,提供更多细节)
  • 你真的只想要每个匹配标签像素右侧和下方的邻居吗?这就是您的代码现在所做的......一般不是“邻居”,而只是特定方向的邻居。
  • @John:是的,只有右/下方向的邻居,这将获得所有连接的邻居。

标签: python optimization numpy


【解决方案1】:

给你:

def get_boundaries2(segments, i):
    x, y = np.where(segments == i) # where i is
    right = x + 1
    rightMask = right < segments.shape[0] # keep in bounds
    down = y + 1
    downMask = down < segments.shape[1]
    rightNeighbors = segments[right[rightMask], y[rightMask]]
    downNeighbors = segments[x[downMask], down[downMask]]
    neighbors = np.union1d(rightNeighbors, downNeighbors)
    return neighbors

如您所见,根本没有 Python 循环;我还尝试最小化副本(第一次尝试制作了带有 NAN 边框的 segments 的副本,但后来我设计了“保持边界”检查)。

请注意,我没有从这里的“邻居”中过滤掉i 本身;如果需要,您可以在最后轻松添加。一些时间安排:

输入 2000x3000:原始需要 13 秒,我需要 370 毫秒(35 倍加速)。

输入 1000x300:原始需要 643 毫秒,我需要 17.5 毫秒(36 倍加速)。

【讨论】:

  • 我很困惑right[rightMask] 做了什么。 right 不是 int 类型的吗?而rightMask 应该是一个布尔值?
  • @AaronDigulla:不,rightx + 1xi 所在的第一个维度(“列”)。 len(x) 将等于 isegments 中的次数,len(right) 也是该长度。
  • 好的,我开始明白了。所以len(right)len(x)-1?这意味着rightMask 也不是布尔值?
  • @AaronDigulla:不,这两个方面都不正确。在你自己的 Python REPL 中试试看。 :) 长度相同,而且掩码确实是布尔值。
  • 哇,真是快!
【解决方案2】:

你需要用 numpy 的隐式循环替换你的 for 循环。

我对你的代码了解不多,无法直接转换,但我可以举个例子。

假设你有一个由 100000 个随机整数组成的数组,你需要得到一个每个元素除以其邻居的数组。

import random, numpy as np 
a = np.fromiter((random.randint(1, 100) for i in range(100000)), int)

一种方法是:

[a[i] / a[i+1] for i in range(len(a)-1)]

或者这个更快:

a / np.roll(a, -1)

时间:

initcode = 'import random, numpy as np; a = np.fromiter((random.randint(1, 100) for i in range(100000)), int)'
timeit.timeit('[a[i] / a[i+1] for i in range(len(a)-1)]', initcode, number=100)
5.822079309000401
timeit.timeit('(a / np.roll(a, -1))', initcode, number=100)
0.1392055350006558

【讨论】:

  • 我会看看我是否可以以某种方式使用它。我对代码进行了一些编辑,并将很快更新线程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-09
  • 2016-11-20
  • 1970-01-01
  • 2013-01-05
  • 1970-01-01
相关资源
最近更新 更多