根据其概率选择矩阵单元

时间:2015-10-30 00:15:14

标签: c++ algorithm matrix random probability

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

vector<vector<double>> matrix;

每个单元格的值可以等于或大于0,该值表示选择单元格的可能性。特别是,例如,与值为1的单元格相比,值等于3的单元格的选择概率是其三倍。

我需要随机选择矩阵的N个单元格(0 <= N&lt; =细胞总数),但要根据它们被选择的概率。

我该怎么做?

算法应该尽可能快。

1 个答案:

答案 0 :(得分:2)

我描述了两种方法,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很大(甚至是'中等')但是使用更多内存时会很好,实际上在某些情况下它可能会因为这个原因而变慢。

方法A:

您需要做的第一件事是规范化条目。 (如果你认为它们是否正常化,我不清楚。)这意味着,将所有条目相加并除以总和。 (这部分可能很慢,所以如果你假设或要求已经发生这种情况会更好。)

然后你这样做:

  1. 选择矩阵的随机[i,j]条目(从整数范围i,j0均匀地随机选择n-1

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

  3. 检查是否matrix[i][j] > p。如果是,请返回[i][j]对。如果没有,请返回步骤1.

  4. 为什么这样做?我们在步骤3中以任何特定输出结束的概率等于[i][j]被选中的概率(每个条目都相同),乘以p数小的概率足够。这与值matrix[i][j]成比例,因此采样选择具有正确比例的每个条目。也有可能在第3步我们回到开始 - 这是否有偏见?基本上没有。原因是,假设我们任意选择一个数字k,然后考虑算法的分布,条件是在k轮后完全停止。假设我们在第k轮停止,无论我们选择什么值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__在注释中指出的那样,在方法(B)中,如果矩阵非常大,即使使用类型double,写入也可能会因浮点舍入而出现一些错误,在这一行:

    cumulative += Matrix[i][j];
    

    问题在于,如果cumulative比浮点精度可以处理的Matrix[i][j]大得多,那么每次执行此语句时,您可能会发现重大错误,这些错误会累积,从而导致严重的错误

    正如他所说,如果发生这种情况,最直接的解决方法是先对值Matrix[i][j]进行排序。你甚至可以在一般的实现中做到这一点是安全的 - 对这些人进行排序不会比你已经拥有的那样花费更多的时间。