【问题标题】:How to speed up random choice generation with python?如何使用 python 加速随机选择生成?
【发布时间】:2018-03-28 12:31:36
【问题描述】:

我用 python 和 numpy 做了一个随机游走生成器。给定一个邻接矩阵,我想从每个节点中抽取随机路径。为此,我目前制作了这个类,它将图中每个节点的邻居节点及其相应的概率作为输入:

import numpy as np

class RandomChoice(object):
    def __init__(self, neighbors_choices, neighbors_prob, depth=50):
        C = len(neighbors_choices)
        self.depth = depth
        self.neighbors_choices = neighbors_choices
        self.neighbors_prob = neighbors_prob
        self.index = np.zeros(C, np.uint32)
        self.choices = list()
        for i in range(C):
            self.choices.append(np.random.choice(self.neighbors_choices[i], size=self.depth, p=self.neighbors_prob[i]))

    def __getitem__(self, arg):
        if self.index[arg] == self.depth:
            self.choices[arg] = np.random.choice(self.neighbors_choices[arg], size=self.depth, p=self.neighbors_prob[arg])
            self.index[arg] = 0
        val = self.choices[arg][self.index[arg]]
        self.index[arg] += 1
        return val

我是这样使用它的:

# Example with a 3x3 matrix
#  1 2 1
#  3 0 1
#  0 1 0

number_of_walks_per_node = 5
number_of_nodes = 3
length_of_walks = 10

choices = [
            [0, 1, 2],
            [0, 2],
            [1]
          ]
probs =   [
            [0.25, 0.5, 0.25],
            [0.75, 0.25],
            [1]
          ]
randomChoice = RandomChoice(choices, probs, depth=50)
for i in range(number_of_walks_per_node):
    for starting_node in range(number_of_nodes):
        walker_positions = [starting_node]
        for j in range(length_of_walks):
            walker_positions.append( randomChoice[walker_positions[j]])
        print(walker_positions)

这里的想法是利用 numpy.random.choices 对 RAM 中的一些空间的向量效率。但是这个功能仍然是这段代码的瓶颈。我认为 numpy.random.choices 花时间检查概率总和是否为 1,并且每个概率都大于 0。你知道如何加快这段代码的速度吗?

【问题讨论】:

  • I think numpy.random.choices spends time checking that the probabilities sum to 1 and that each one of them is greater than 0 不只是从列表中选择一个随机元素吗?该列表不需要包含 pdf,因此我认为它不会进行任何检查。
  • 我不知道这是否是最新的,但请查看:github.com/numpy/numpy/issues/4188
  • @debzsub 无法运行您的代码。请您上传一个遵循这些准则stackoverflow.com/help/mcve 的示例,以便更容易为您提供帮助。
  • 对对对,我编辑一下
  • 只看你所拥有的(暂时忽略 np.choice),我建议你使用队列 @9​​87654323@ 或 dequeue docs.python.org/2/library/collections.html#collections.deque(你需要选择合适的) 而不是列表,如果您要进行许多追加,然后弹出结果,因为如果您只使用它们来串行存储数据并且您不关心对它们进行索引,它们会更快。

标签: python performance numpy random-walk


【解决方案1】:

我更新了您的代码以使其运行(见下文)。这是我得到的分析:

看起来 getitem 上的函数开销与其他所有内容相比非常大,但我预计这部分是因为玩具图。

如果通常是这种情况,可以重构您的代码,使您不那么频繁地调用 getitem。相反,您可以将代码从 getitem 移动到脚本中的一组嵌套循环中,就像这样(但它会很丑)...

for i in range(number_of_walks_per_node):
    for starting_node in range(number_of_nodes):
        walkers_positions = [start_node]
        for j in range(length_of_walks):
            if randomChoice.index[walkers_positions[j]] == randomChoice.depth:
                randomChoice.choices[walkers_positions[j]] = np.random.choice(randomChoice.neighbors_choices[walkers_positions[j]], size=randomChoice.depth,
                                                     p=randomChoice.neighbors_prob[walkers_positions[j]])
                randomChoice.index[walkers_positions[j]] = 0
            val = randomChoice.choices[walkers_positions[j]][randomChoice.index[walkers_positions[j]]]
            randomChoice.index[walkers_positions[j]] += 1
            walkers_positions.append( val )

您的代码的工作版本以供参考...

import numpy as np

class RandomChoice(object):
    def __init__(self, neighbors_choices, neighbors_prob, depth=50):
        C = len(neighbors_choices)
        self.depth = depth
        self.neighbors_choices = neighbors_choices
        self.neighbors_prob = neighbors_prob
        self.index = np.zeros(C, np.uint32)
        self.choices = list()
        for i in range(C):
            self.choices.append(np.random.choice(self.neighbors_choices[i], size=self.depth, p=self.neighbors_prob[i]))

    def __getitem__(self, arg):
        if self.index[arg] == self.depth:
            self.choices[arg] = np.random.choice(self.neighbors_choices[arg], size=self.depth, p=self.neighbors_prob[arg])
            self.index[arg] = 0
        val = self.choices[arg][self.index[arg]]
        self.index[arg] += 1
        return val

# Example with a 3x3 matrix
#  1 3 1
#  3 0 1
#  0 1 0

choices = [
            [0, 1, 2],
            [0, 2],
            [1]
          ]
probs =   [
            [0.25, 0.5, 0.25],
            [0.75, 0.25],
            [1]
          ]

number_of_walks_per_node = 10
number_of_nodes = 3
start_node = 0
length_of_walks = 5000
randomChoice = RandomChoice(choices, probs, depth=50)
for i in range(number_of_walks_per_node):
    for starting_node in range(number_of_nodes):
        walkers_positions = [start_node]
        for j in range(length_of_walks):
            walkers_positions.append( randomChoice[walkers_positions[j]] )

【讨论】:

  • (我编辑了代码并得到了相同的结果)。你是对的,getitem 是实际的瓶颈。您能否详细说明“相反,您可以将循环移动到类的方法中。” ?
  • 但是,getitem 仍然在这里,因为我找不到任何加速 random.choice 的方法(如果它很快,我不会使用生成选择缓冲区的类)
  • 对不起,我的错误,如果你想加快速度,你需要将获取项目代码移出类,并进入循环。 WRT np.choice,我怀疑你会找到加速它的方法。它是用 C 语言编写的,并且可能会被 numpy 团队很好地优化。我会更新答案。
  • 好吧,我刚刚使用 deque 进行了测试,它有很大帮助。我认为这里的问题是所有类似于 np.random.choice 的检查(检查一次会很好,然后多次调用)。也许我应该检查代码并在没有检查的情况下实现我的......
猜你喜欢
  • 2018-11-19
  • 2018-04-03
  • 2019-01-21
  • 2022-11-23
  • 2021-01-15
  • 2016-09-26
  • 2013-12-29
  • 2019-11-16
  • 1970-01-01
相关资源
最近更新 更多