【问题标题】:How can I quickly populate a 100000x100000 matrix in Python using NumPy?如何使用 NumPy 在 Python 中快速填充 100000x100000 矩阵?
【发布时间】:2019-04-07 08:40:35
【问题描述】:

我真的很喜欢数据结构和算法。

我正在使用 80000 X 80000 矩阵来插入数据。我正在使用 numpy。而且,我的代码如下所示:

n = 80000
similarity = np.zeros((n, n), dtype='int8')
for i, photo_i in enumerate(photos):
    for j, photo_j in enumerate(photos[i:]):
       similarity[i, j] = score(photo_i, photo_j)
    if i % 100 == 0:
        print(i)

这段代码花费了太多时间。 score 函数为 O(1)。我想知道是否有更好的方法来做到这一点。我想在“短时间内”绘制这个矩阵的数据。但是我这样做的方式有 O(n^2) 的复杂度。

是否有“任何东西”,可以“优化”或使用不同的数据结构?

我已经阅读了关于 SO 的类似问题,并且他们提到了 pytables。我一定会尝试的,但还不知道如何。欢迎提出任何建议。

提前致谢。

【问题讨论】:

  • 你可能会摆脱循环,你可能只使用上三角形(如果你的分数函数是对称的),但它的核心:它是一个二次任务,80k^2 是很多工作。
  • 很可能时间花在了score函数上,你没有显示出来;优化这可能会导致尝试优化循环代码的更好结果。如果你坚持,你可以用推导替换显式循环,或者去掉i % 100 检查并打印进度。但与实际的评分逻辑相比,这些可能可以忽略不计。
  • @sascha,注意代码已经只计算了下三角(他们正在做for j, photo_j in enumerate(photos[i:]))。
  • photos[i:] 不是在每次调用该子数组时都创建一个新副本吗?
  • 您正在计算的矩阵的大小为 O(n^2),因此没有比这更好的方法了。问题是你真的需要整个矩阵吗?算法的下一步是什么?

标签: python python-3.x algorithm matrix data-structures


【解决方案1】:

你可以做很多不同的事情,所有这些都围绕着避免显式的 for 循环,这在 Python 中很慢,并委托给 C 级代码(使用 Python 的底层 C 运行时或 numpy 的内置数组创建方法)。

使用fromfunction

Numpy 有一个内置函数,用于从获取坐标的函数中填充矩阵:numpy.fromfunction。这可能会更快,因为它在 C 而不是 Python 中完成所有迭代和赋值。

您必须为其提供一个按坐标计算的函数,例如:

def similarity_value(i, j, photos=photos):
  return score(photos[i], photos[j])

similarity = numpy.fromfunction(similarity_value, (n, n), dtype='int8')

函数定义中的photos=photos 使photos 数组成为函数的本地数组,并节省了每次调用时访问它的时间;这是一种常见的 Python 微优化技术。

请注意,这会计算整个矩阵的相似度,而不仅仅是一个三角形。要解决此问题,您可以这样做:

def similarity_value(i, j, photos=photos):
  return score(photos[i], photos[j]) if i < j else 0

similarity = numpy.fromfunction(similarity_value, (n, n), dtype='int8')
similarity += similarity.T  # fill in other triangle from transposed matrix

使用推导

您还可以尝试从生成器推导(甚至列表推导)创建相似度矩阵,再次避免显式 for 循环以支持更快的推导,但会牺牲三角形优化:

similarity = numpy.fromiter((score(photo_i, photo_j) 
                             for photo_i in photos 
                             for photo_j in photos),
                            shape=(n,n), dtype='int8')

# or:
similarity = numpy.array([score(photo_i, photo_j) 
                          for photo_i in photos 
                          for photo_j in photos],
                         shape=(n,n), dtype='int8')

要重新引入三角形优化,您可以执行以下操作:

similarity = numpy.array([score(photo_i, photo_j) if i < j else 0
                          for i, photo_i in enumerate(photos)
                          for j, photo_j in enumerate(photos)],
                         shape=(n,n), dtype='int8')
similarity += similarity.T

使用triu_indices 直接填充三角形

最后,您可以使用numpy.triu_indices 直接分配到矩阵的上(然后是下)三角形:

similarity_values = (score(photo_i, photo_j
                     for photo_i in photos
                     for photo_j in photos[:i])  # only computing values for the triangle
similarity = np.zeroes((n,n), dtype='int8')
xs, ys = np.triu_indices(n, 1)
similarity[xs, ys] = similarity_values
similarity[ys, xs] = similarity_values
similarity[np.diag_indices(n)] = 1  # assuming score(x, x) == 1

这种方法的灵感来自这个相关问题:https://codereview.stackexchange.com/questions/107094/create-symmetrical-matrix-from-list-of-values

我没有办法对这些方法中哪种方法效果最好进行基准测试,但您可以进行试验并找出答案。祝你好运!

【讨论】:

  • 非常感谢,这对我来说非常有用。
  • 我很想知道这些方法中哪一种最适合您。
  • 我已经尝试过您的建议,但遇到了一些错误。首先,..fromfunction... 给了我这样的错误:The truth value of an array with more than one element is ambiguous. Use a.any() or a.all(),对于列表推导,shapeparameter 不存在。
  • 我无权访问 NumPy,因此无法自己检查,但您可以查看 fromfunction 文档以了解如何正确使用它,我当然可能在这里遗漏了一些东西.对于列表推导,您可以从列表创建一个一维数组,然后对其进行整形,或者您可以创建一个二维列表推导作为数组的输入(即行的列表推导,其中每个元素都是列出该行的列值的理解)。
猜你喜欢
  • 2018-05-05
  • 2018-01-23
  • 2015-08-12
  • 1970-01-01
  • 2018-09-20
  • 2017-02-22
  • 1970-01-01
  • 1970-01-01
  • 2017-08-18
相关资源
最近更新 更多