【问题标题】:Pick a matrix cell according to its probability根据概率选择一个矩阵单元
【发布时间】:2016-01-30 08:15:05
【问题描述】:

我有一个正实数值的二维矩阵,存储如下:

vector<vector<double>> matrix;

每个单元格可以有一个等于或大于 0 的值,这个值代表了该单元格被选中的可能性。特别是,例如,与值为 1 的单元格相比,值为 3 的单元格具有三倍的被选择概率。

我需要随机选择矩阵的N 单元格(0 N

我该怎么做?

算法应该尽可能快。

【问题讨论】:

  • std::discrete_distribution 是为这种情况量身定做的。
  • @user515430 以哪种方式?
  • @Nick 如果您将矩阵视为一维数组,您可以直接使用discrete_distribution(+1 到@user515430)
  • @fjardon 你能告诉我怎么做吗?
  • @Nick 再次认为它不会解决您的问题,因为您想要一个没有替换的抽样,并且离散分布对每个项目都有一个固定的概率。因此,在第二次抽奖时,您可以在第一次抽奖时获得该物品。

标签: c++ algorithm matrix random probability


【解决方案1】:

我描述了两种方法,A 和 B。

A 在时间上工作大约 N * number of cells,并使用空间 O(log number of cells)N 很小的时候就好了。

B 在时间上工作大约(number of cells + N) * O(log number of cells),并使用空间O(number of cells)。因此,当N 很大(甚至是“中等”)但使用更多内存时,这是很好的,但实际上在某些情况下它可能会因为这个原因而变慢。


方法一:

您需要做的第一件事是规范化条目。 (我不清楚您是否假设它们已标准化。)这意味着,将所有条目相加并除以总和。 (这部分可能很慢,所以最好假设或要求它已经发生。)

然后你像这样采样:

  1. 0n-1的整数范围内随机选择i,j每个矩阵的随机条目。

  2. [0, 1]范围内选择一个均匀随机实数p

  3. 检查是否matrix[i][j] &gt; p。如果是,则返回 [i][j] 对。如果没有,请返回第 1 步。

为什么会这样?我们以任何特定输出在步骤 3 结束的概率等于选择 [i][j] 的概率(每个条目都相同)乘以数字 p 足够小的概率。这与值matrix[i][j] 成正比,因此采样选择具有正确比例的每个条目。也有可能在第 3 步我们回到起点——这是否有偏见?基本上,没有。原因是,假设我们任意选择一个数字k,然后考虑算法的分布,条件是恰好在k 轮之后停止。条件是我们停止在第k'th 轮,无论我们选择什么值k,我们采样的分布必须完全符合上述论点。因为如果我们排除p 太小的情况,其他可能性的比例都是正确的。由于分布对于我们可能以k 为条件的每个值都是完美的,并且整体分布(不以k 为条件)是k 的每个值的分布的平均值,因此整体分布也是完美的.

如果您想以严格的方式分析通常需要的回合数,您可以通过分析我们在任何特定回合中实际在第 3 步停止的概率来实现。由于轮次是独立的,所以每一轮都是一样的,从统计学上来说,这意味着算法的运行时间是泊松分布的。这意味着它紧密地集中在其平均值附近,我们可以通过知道该概率来确定平均值。

我们在第 3 步停止的概率可以通过考虑我们在第 3 步停止的条件概率来确定,假设我们选择了任何特定条目[i][j]。通过条件期望的公式,你得到了

Pr[ stop at step 3 ] = sum_{i,j} ( 1/(n^2) * Matrix[i,j] )

由于我们假设矩阵是归一化的,所以这个和减少到只有1/n^2。因此,无论矩阵中的条目是什么,预期的轮数约为n^2(即n^2 直到一个常数因子)。你不能希望做得比我认为的要好得多——这与读取矩阵的所有条目所花费的时间大致相同,而且很难从一个你甚至无法读取所有的分布中进行抽样.

注意:我描述的是一种正确采样单个元素的方法——要从一个矩阵中获取N 元素,您可以重复N 次。


方法B:

基本上,您只想计算一个直方图并从中逆向采样,这样您就知道您得到了完全正确的分布。计算直方图的成本很高,但一旦有了它,获取样本就变得既便宜又容易。

在 C++ 中可能如下所示:

// Make histogram
typedef unsigned int uint;
typedef std::pair<uint, uint> upair;
typedef std::map<double, upair> histogram_type;
histogram_type histogram;
double cumulative = 0.0f;
for (uint i = 0; i < Matrix.size(); ++i) {
  for (uint j = 0; j < Matrix[i].size(); ++j) {
    cumulative += Matrix[i][j];
    histogram[cumulative] = std::make_pair(i,j);
  }
}

std::vector<upair> result;
for (uint k = 0; k < N; ++k) {
  // Do a sample (this should never repeat... if it does not find a lower bound you could also assert false quite reasonably since it means something is wrong with rand() implementation)
  while(1) {
    double p = cumulative * rand(); // Or, for best results use std::mt19937 or boost::mt19937 and sample a real in the range [0,1] here.
    histogram_type::iterator it = histogram::lower_bound(p);
    if (it != histogram.end()) {
      result.push_back(it->second);
      break;
    }
  }
}
return result;

这里制作直方图的时间类似于number of cells * O(log number of cells),因为插入地图需要时间O(log n)。您需要一个有序的数据结构,以便稍后在重复采样时获得廉价的查找N * O(log number of cells)。可能您可以选择更专业的数据结构以加快速度,但我认为改进的空间有限。

编辑:正如@Bob__ 在 cmets 中指出的那样,在方法 (B) 中,如果矩阵非常大,即使使用类型 double,由于浮点舍入可能会出现一些错误,在这一行:

cumulative += Matrix[i][j];

问题在于,如果 cumulativeMatrix[i][j] 大得多,超出了浮点精度可以处理的范围,那么每次执行此语句时,您可能会观察到大量错误,这些错误累积起来会导致严重的不准确性。

正如他所建议的,如果发生这种情况,最直接的解决方法是首先对值 Matrix[i][j] 进行排序。为了安全起见,您甚至可以在一般实现中执行此操作 - 渐近地对这些人进行排序不会比您已经拥有的时间更多。

【讨论】:

  • 我认为您不必对矩阵进行归一化。计算总和后,在第 2 点中只需在 [0,sum/n^2] 中选择一个随机数。
  • @Bob__:是的,我的意思是,我想这与归一化相同,您只是将 p 相乘而不是除以总和。由于您不必进行 n^2 除法,因此它可能会稍微快一些,但由于您仍然必须得到总和,因此它不会渐近地更快。
  • 你的B方法就是我的想法。也许您可以通过在查找之前对 N 个样本进行排序来优化它。我唯一担心的是,将潜在的小矩阵 [i][j] 求和到越来越大的累积值时的数值不稳定性,即使使用双精度。
  • 我尝试了你的 A 方法,但它对我不起作用。矩阵很大,单元格的值很小(大多数小于 10)。这意味着当我对权重进行归一化时,我得到一个大的总和值,一个小的新权重值。因此,当我选择概率 p [0, 1] 时,该数字(几乎总是)大于归一化权重。这种方法需要几个小时才能终止。
  • @Nick:你的矩阵到底有多大?你能容忍你抽样的分布中的错误/缺陷吗?从具有巨大支持的任意分布中完美采样本质上是复杂的。如果您可以对矩阵做出更多假设,可能会有更好的方法,但鉴于您在问题中发布的内容,我不知道您还能做什么。您可以尝试像其他人发布的那样使用std::discrete_distribution,但我查看了它的作用,我认为它与B 没有显着不同。
猜你喜欢
  • 2014-12-15
  • 2019-05-10
  • 2015-08-22
  • 2017-07-14
  • 1970-01-01
  • 2013-08-27
  • 2021-05-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多