从C#中的多项式采样的这种简单方法有什么问题?

时间:2017-11-17 21:05:30

标签: c# random probability multinomial statistical-sampling

我想实现一个简单的方法来从C#中的多项分布中进行采样(第一个参数是我们想要采样的整数数组,第二个参数是选择每个整数的概率)。

当我在python中使用numpy时,结果是有意义的。

np.random.choice(np.array([1,2,3,4,5,6]),p=np.array([.624,.23,.08,.04, .02, .006]),size=len(b))

我得到了很多1(概率为62%),一堆2,有些是3等。

然而,当我在C#中尝试下面的实现时(多项式的非常简单的逆变换采样,只依赖于一个统一的随机变量),我得到了非常奇怪的结果。对于所有1000个样本,我经常会找到所有1个样本。有时候,我会找到所有3个(!! ??)。结果永远不会像您期望的那样(以及您从python函数获得的结果 - 尝试自己运行几次)。这非常可怕,因为我们依赖这些原语。有没有人能够深入了解C#版本可能出现的问题?

    static void Main(string[] args)
    {
        int[] iis = new int[7];
        int[] itms = new int[] { 1, 2, 3, 4, 5, 6 };
        double[] probs = new double[] { .624, .23, .08, .04, .02, .006 };
        for (int i = 0; i < 1000; i++)
        {
            iis[MultinomialSample(itms, probs)] += 1;
        }

        foreach (var ii in iis)
        {
            Console.Write(ii + ",");
        }

        Console.Read();
    }


     private static int MultinomialSample(int[] s, double[] ps)
    {
        double[] cumProbs = new double[ps.Length];
        cumProbs[0] = ps[0];

        for (int i = 1; i < ps.Length; i++)
        {
            cumProbs[i] = cumProbs[i - 1] + ps[i];
        }

        Random random = new Random();
        double u = random.NextDouble();

        for (int i = 0; i < cumProbs.Length - 1; i++)
        {
            if (u < cumProbs[i])
            {
                return s[i];
            }
        }

        return s[s.Length - 1];
    }

2 个答案:

答案 0 :(得分:2)

每次致电Random时,您都会初始化MultinomialSample。如果这些调用非常接近,则Random将使用相同的种子进行初始化(基于系统时钟)。尝试将Random作为私有类字段:private static Random random = new Random();或将其作为Main中的参数传递给方法,其中只会初始化一次:

private static Random random = new Random();

private static int MultinomialSample(IReadOnlyList<int> sample, 
    IReadOnlyList<double> probabilities)
{
    var cumProbs = new double[probabilities.Count];
    cumProbs[0] = probabilities[0];

    for (var i = 1; i < probabilities.Count; i++)
    {
        cumProbs[i] = cumProbs[i - 1] + probabilities[i];
    }

    for (var i = 0; i < cumProbs.Length - 1; i++)
    {
        if (random.NextDouble() < cumProbs[i])
        {
            return sample[i];
        }
    }

    return sample[sample.Count - 1];
}

private static void Main()
{
    var iis = new int[7];
    var items = new[] {1, 2, 3, 4, 5, 6};
    var probabilities = new[] {.624, .23, .08, .04, .02, .006};

    for (int i = 0; i < 1000; i++)
    {
        iis[MultinomialSample(items, probabilities)] ++;
    }

    Console.WriteLine(string.Join(", ", iis));

    Console.WriteLine("\nDone!\nPress any key to exit...");
    Console.ReadKey();
}

答案 1 :(得分:1)

我在正在研究的仿真中使用了Rufus的代码,即使在将随机数生成器仅植入一次之后,这仍然存在问题(这是正确的做法)。您会注意到,在进行迭代时,对random.NextDouble()的调用每次都会生成一个新的随机数。这是错误的。

for (var i = 0; i < cumProbs.Length - 1; i++)
{
    if (random.NextDouble() < cumProbs[i])
    {
        return sample[i];
    }
}

随机数应在循环外部生成,如下所示:

var r = random.NextDouble();
for (var i = 0; i < cumProbs.Length - 1; i++)
{
    if (r < cumProbs[i])
    {
        return sample[i];
    }
}

您可以将其与Wikipedia:https://en.wikipedia.org/wiki/Multinomial_distribution上提供的Excel算法进行比较。当对Rufus的代码进行上述更改时,我得到了概率阵列指定的所需频率分布。