【问题标题】:How to pass numpy logic functions to Cython correctly?如何正确地将 numpy 逻辑函数传递给 Cython?
【发布时间】:2018-02-18 05:40:07
【问题描述】:

我应该将哪些声明与逻辑函数/索引操作结合起来,以便 Cython 完成繁重的工作?

我有两个大小相等的 numpy 数组形式的大型栅格。第一个数组包含植被指数值,第二个数组包含字段 ID。目标是按字段平均植被指数值。这两个数组都有我想忽略的讨厌的 nodata 值(-9999)。

目前该函数的执行时间超过 60 秒,通常我不会太介意,但我可能会处理数百张图像。即使是 30 秒的改进也会很重要。所以我一直在探索 Cython 来帮助加快速度。我一直使用Cython numpy tutorial 作为指导。

Example data

test_cy.pyx 代码:

import numpy as np
cimport numpy as np
cimport cython
@cython.boundscheck(False) # turn off bounds-checking for entire function
@cython.wraparound(False)  # turn off negative index wrapping for entire function 

cpdef test():
  cdef np.ndarray[np.int16_t, ndim=2] ndvi_array = np.load("Z:cython_test/data/ndvi.npy")

  cdef np.ndarray[np.int16_t, ndim=2] field_array = np.load("Z:cython_test/data/field_array.npy")

  cdef np.ndarray[np.int16_t, ndim=1] unique_field = np.unique(field_array)
  unique_field = unique_field[unique_field != -9999]

  cdef int field_id
  cdef np.ndarray[np.int16_t, ndim=1] f_ndvi_values
  cdef double f_avg

  for field_id in unique_field :
      f_ndvi_values = ndvi_array[np.logical_and(field_array == field_id, ndvi_array != -9999)]
      f_avg = np.mean(f_ndvi_values)

Setup.py 代码:

try:
    from setuptools import setup
    from setuptools import Extension
except ImportError:
    from distutils.core import setup
    from distutils.extension import Extension

from Cython.Build import cythonize
import numpy

setup(ext_modules = cythonize('test_cy.pyx'),
      include_dirs=[numpy.get_include()])

经过一番研究和运行:

cython -a test_cy.pyx

看来索引操作ndvi_array[np.logical_and(field_array == field_id, ndvi_array != -9999)]是瓶颈,还在依赖Python。我怀疑我在这里遗漏了一些重要的声明。包括ndim 没有任何效果。

我对 numpy 也很陌生,所以我可能遗漏了一些明显的东西。

【问题讨论】:

  • 正如它在docs 中所说的那样,“陷阱:这种高效的索引只影响某些索引操作,即那些具有恰好 ndim 数量的类型整数索引的操作。因此,例如,如果没有键入 v,那么查找 f[v, w] 未优化。另一方面,这意味着您可以继续使用 Python 对象进行复杂的动态切片等,就像未键入数组时一样。"
  • 最简单的改进是缓存ndvi_array != -9999(因为它不会改变)。除此之外,您可能必须将逻辑索引和均值计算重写为 2D 循环,但这需要更多工作。
  • @Paul 因此,如果我理解正确,我需要将 ndvi_array != -9999 存储为变量并键入它,以及 field_array = field_id?
  • @wessport 我想这将是一种接近它的方式。或者你可以循环并在里面放一个'if'语句。

标签: python numpy optimization cython


【解决方案1】:

对我来说,您的问题看起来相当可矢量化,因此 Cython 可能不是最好的方法。 (当存在不可避免的细粒度循环时,Cython 会发光。)由于您的 dtype 是 int16,因此可能的标签范围有限,因此使用 np.bincount 应该是相当有效的。尝试类似的事情(假设您所有的有效值都 >= 0,如果不是这种情况,您必须转移 - 或者(更便宜)视图转换为 uint16(因为我们没有对标签进行任何算术运算那应该是安全的)-在使用bincount之前):

mask = (ndvi_array != -9999) & (field_array != -9999)
nd = ndvi_array[mask]
fi = field_array[mask]
counts = np.bincount(fi, minlength=2**15)
sums = np.bincount(fi, nd, minlength=2**15)
valid = counts != 0
avgs = sums[valid] / counts[valid]

【讨论】:

  • 感谢@Paul 的推荐! np.bincount 相对来说效率更高。您能否扩展不可避免的细粒度循环的含义?例如,这将是在不同大小的数组上执行的许多操作?
  • @wessport 我的意思是必须一个接一个地完成数组元素并且不被 ufunc 覆盖的事情。所以添加两个数组,没有问题。因为'a + b'对C中的元素进行循环。类似地'cumsum(a)'虽然这确实是一个接一个地完成它仍然在C中运行。但是如果有一些不太标准的东西不能从 numpy 构建块拼凑而成,您能找到的唯一方法是对某个数组的元素进行 Python 循环,然后 Cython 可以获得可笑的加速。
猜你喜欢
  • 1970-01-01
  • 2014-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-05
  • 1970-01-01
  • 1970-01-01
  • 2019-08-24
相关资源
最近更新 更多