簡體   English   中英

生成隨機加權值

[英]Generate Random Weighted value

編輯:我已經重寫了這個問題,希望目標更加清晰。

這是對這個問題的擴展問題在這里 ,我真的很喜歡在所提供的功能, 這個答案

在上面的答案中,人們能夠設定擊中極端的概率,較高的數字產生較高數字的概率,反之亦然。 問題是我必須設置3組的概率。 這些組是最低值(LV),最高值(HV)和中間值(MV)。 但是,為了簡化請求,我們可以考慮EVP=HVP=LVP

給定任何范圍,HV / LV應基於指定的EVP出現,並且當您從每個極端的范圍內進展/下降時,該范圍中下一個值的概率將根據EVP之間的距離增加或減少。和MVP。

使用1-6的示例范圍,1和6加權為5%(EVP),概率差為1/6為5%,2/4為15%,3/4為30%(MVP) ),總計100%。 反過來也應該是可能的,交換EVP和MVP應該產生下圖的反轉。

這是一個我希望將傳達給定示例預期結果的圖像。

中加權:

中等加權圖

額外獎勵:如果我能夠單獨設置HVP和LVP產生類似於下圖的結果,那將是最優秀的( 注意:圖表不符合上述規范 )。

中等加權(獎金):

中等加權獎金圖

謝謝!

因為我今天因為流感而被困在家里:(我決定嘗試為你解決這個問題。基本上你要求的是某種插值。我使用最簡單的(線性的)這些是我的結果和代碼。代碼有點混亂,我可能會在即將到來的日子里修復它。

<?php

// this function interpolates $a to $b over $steps steps, starting from key $k
// this can be cleaned up significantly
function interpolate($a, $b, $steps, $k) {
    @$per_step = abs($a - $b)/$steps; // suppress warnings in case of division by zero
    if ($a > $b)
        $decreasing = true;
    else
        $decreasing = false;
    $final = array();
    for ($i = 1; $i <= $steps-1; ++$i) {
        if ($decreasing)
            $final[$i+$k] = $a-=$per_step; // linear interpolation
        else
            $final[$i+$k] = $a+=$per_step; // linear interpolation
    }
    return $final;
}

// this function combines probability arrays after the interpolation occurs
// this may happen multiple times, think about 1, 3, 5. interpolation would have to occur
// from 1 -> 2 -> 3, and from 3 -> 4 -> 5.
function interpolateProbabilities ($nodes) {
    $pNodes = array();
    $pNodes = $nodes;
    $keys = array_keys($nodes);
    for ($i = 0; $i < count($keys); $i++) {
        if ($keys[$i+1] - $keys[$i] != 1) {
            $pNodes += interpolate($nodes[$keys[$i]], $nodes[$keys[$i+1]], $keys[$i+1] - $keys[$i], $keys[$i]);
        }
    }
    ksort($pNodes);
    return $pNodes;
}

// this generates a weighed random value and is pretty much copy-pasted from:
// http://w-shadow.com/blog/2008/12/10/fast-weighted-random-choice-in-php/
// it's robust and re-writing it would be somewhat pointless
function generateWeighedRandomValue($nodes) {
    $weights = array_values($nodes);
    $values = array_keys($nodes);
    $count = count($values);
    $i = 0;
    $n = 0;
    $num = mt_rand(0, array_sum($weights));
    while($i < $count) {
        $n += $weights[$i];
        if($n >= $num) {
            break;
           }
        $i++;
       }
    return $values[$i];
}

// two test cases
$nodes = array( 1 => 12, 5 => 22, 9 => 31, 10 => 35); // test 1
$nodes = array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10); // test 2
$export = array();

// run it 1000 times
for ($i = 0; $i < 1000; ++$i) {
    $export[generateWeighedRandomValue(interpolateProbabilities($nodes))]++;
}

// for copy-pasting into excel to test out distribution
print_r($export);

?>

我認為,結果正是您正在尋找的。 如果是:

$nodes = array( 1 => 12, 5 => 22, 9 => 31, 10 => 35); // test 1

我得到了以下(最終)數組:

Array
(
    [5] => 92
    [7] => 94
    [10] => 162
    [8] => 140
    [3] => 71
    [6] => 114
    [2] => 75
    [4] => 69
    [9] => 131
    [1] => 52
)

也就是說, 1應該發生在12%的時間, 5 22%, 9 31%和10 35%的時間。 讓它圖表: 圖1

它看起來很有希望,但讓我們嘗試更瘋狂的東西......

$nodes = array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10); // test 2

在這種情況下, 3應該在50%的時間發生,並且急劇下降到6 讓我們看看發生了什么! 這是數組(回想起來,我應該對這些數組進行排序):

Array
(
    [4] => 163
    [7] => 64
    [2] => 180
    [10] => 47
    [1] => 115
    [5] => 81
    [3] => 227
    [8] => 57
    [6] => 6
    [9] => 60
)

讓我們看看圖片:

替代文字

看起來很有效:)

我希望我能夠解決你的問題(或者至少指出你正確的方向)。 請注意,我的代碼目前有許多規定。 也就是說,您提供的初始節點必須具有高達100%的概率,否則您可能會獲得一些不穩定的行為。

此外,代碼有點亂,但概念相對簡單。 其他一些很酷的東西是嘗試而不是使用線性插值,使用其他類型,這將給你更有趣的結果!


算法

為了避免混淆,我將只展示算法的工作原理。 我給PHP一個$node數組,其形式為integer => frequency in percentage ,最終看起來像array( 1 => 22, 3 => 50, 6 => 2, 7 => 16, 10 => 10) ,這是上面的test 2

Test 2基本上表示你想要在1, 3, 6, 7, and 10個放置5個控制節點1, 3, 6, 7, and 10頻率分別為22%, 50%, 2%, 16%, and 10% 首先,我需要看正是我需要做插值。 例如,我不需要在67之間進行,但我確實需要在13之間(我們需要插值2 )和710 (我們需要插入89 )。

1 -> 3之間的插值有(3 - 1) - 1 = 1步,應插入原始數組中的key[2] 的值( %為) 1 -> 3插是abs($a - $b) / $steps其轉換為的絕對值%1減去%2 ,除以steps + 1 ,其在我們的情況恰好等於14 我們需要看看函數是增加還是減少(你好微積分)。 如果函數增加,我們繼續步長% 添加到新的插值數組,直到我們填充所有空點(如果函數正在減少,我們減去步長% value 。因為我們只需要填充一個點,我們返回2 => 3622 + 14 = 36 )。

我們組合了數組,結果是(1 => 22, 2 => 36, 3 => 50, 6 => 2, 7 => 16, 10 => 10) 程序內插2 ,這是我們沒有明確聲明的百分比值。

7 -> 10的情況下,有2個步驟,步長百分比是2 ,它來自(16-10) / (3 + 1) = 2 函數正在減少,所以我們需要重復減去2 最終的插值陣列是(8 => 14, 9 => 12) 我們結合了所有陣列和瞧。

下圖顯示綠色(初始值)和紅色(插值)。 您可能需要“查看圖像”才能清楚地看到整個事物。 你會注意到我使用±因為算法需要弄清楚我們是否應該在一段時間內增加或減少。

替代文字


這段代碼應該用更多的OOP范例編寫。 我使用數組鍵玩很多(例如,我需要傳遞$k因此一旦我從interpolate($a, $b, $steps, $k)返回數組,它就更容易組合數組interpolate($a, $b, $steps, $k)因為它們會自動擁有正確的鍵。這只是一個PHP的特性,回想起來,我應該開始使用更易讀的OOP方法。


這是我的最后一次編輯,我保證:)由於我喜歡使用Excel,這顯示了一旦插入數字后百分比如何標准化。 這一點很重要,特別是考慮到你的第一張照片,你所展示的內容在某種程度上是不可能的。

Test 1 替代文字 Test 2 替代文字

您會注意到百分比顯着衰減以適應插值。 你現實中的第二張圖看起來更像是這樣的:

替代文字

在此圖中,我稱1 = > 1, 5 => 98, 10 => 1 ,您可以看到阻尼效應的極值。 畢竟,根據定義,百分比必須加起來為100! 重要的是要意識到阻尼效應與極端之間的步數成正比。

假設您可以處理百分比的整數,只需將0到99之間的每個值分配一個結果 - 例如0-9可能有1的結果而95-99可能有6的結果(給你的10%= 1和5%= 6情景)。 一旦你有了這個翻譯功能(但是你實現了這個 - 你可以使用各種方法),你只需要生成0-99范圍內的隨機數並將其轉換為結果。

你想要的代碼(甚至是哪種語言--C#或PHP?)你的問題並不是很清楚,但希望這會有所幫助。

這里有一些C#代碼可以讓你在合理的范圍內得到任何你喜歡的偏見 - 你不必將它表示為百分比,但你可以這樣做:

static int BiasedRandom(Random rng, params int[] chances)
{
    int sum = chances.Sum();
    int roll = rng.Next(sum);
    for (int i = 0; i < chances.Length - 1; i++)
    {
        if (roll < chances[i])
        {
            return i;
        }
        roll -= chances[i];
    }
    return chances.Length - 1;
}

例如,您可以使用

int roll = BiasedRandom(rng, 10, 10, 10, 10, 10, 50) + 1;

這樣每1-5分鍾的幾率為10%,獲得6分的幾率為50%。

C#中快速而骯臟的方式:

T PickWeightedRandom<T>(IEnumerable<Tuple<T,double>> items, Random r)
{
    var sum = 0.0;
    var rand = r.NextDouble();
    return items.First(x => { sum += x.Item2; return rand < sum; }).Item1;
}

測試代碼:

var values = new [] { 
    Tuple.Create(1, 0.05),
    Tuple.Create(2, 0.15),
    Tuple.Create(3, 0.3),
    Tuple.Create(4, 0.3),
    Tuple.Create(5, 0.15),
    Tuple.Create(6, 0.05),
};

const int iterations = 1000;

var counts = new int[values.Length];
var random = new Random();

for (int i = 0; i < iterations; i++)
{
    counts[PickWeightedRandom(values, random)-1]++;
}

foreach (var item in counts)
{
    Console.WriteLine(item/(double)iterations);
}

輸出(迭代次數= 1000000):

0.050224
0.150137
0.300592
0.298879
0.150441
0.049727

好像:

生成非均勻隨機數時的一般技術是使用拒絕采樣 即使在這種情況下它可能無效,你仍然應該知道如何做到這一點,因為它適用於你提供的任何密度函數。

function random($density, $max) {
    do {
        $rand = lcg_value();
        $rand2 = lcg_value() * $max;
    } while ($density($rand) < $rand2);
    return $rand;
}

$density是一個密度函數,它接受0到1之間的浮點數作為參數,並返回一個小於$max 對於您的示例,此密度函數可以是:

$density = function($x) {
    static $values = array(
        1 => 0.05,
        2 => 0.15,
        3 => 0.30,
        4 => 0.30,
        5 => 0.15,
        6 => 0.05,
    );

    return $values[ceil($x * 6)];
};

然后一個示例調用是:

ceil(random($density, 0.3) * 6); // 0.3 is the greatest value returned by $density
// round and * 6 are used to map a 0 - 1 float to a 1 - 6 int.

如果您無法輕松計算分布的倒數,則拒絕采樣尤其有用。 在這種情況下,使用逆變換采樣計算逆變得非常容易,這可能是更好的選擇。 Jon的答案已經涵蓋了這一點。

PS:上面的實現是通用的,因此使用0到1之間的隨機值。通過構建一個僅適用於您的方法的函數,一切都變得更容易:

function random() {
    static $values = array(
        1 => 0.05,
        2 => 0.15,
        3 => 0.30,
        4 => 0.30,
        5 => 0.15,
        6 => 0.05,
    );

    do {
        $rand = mt_rand(1, 6);
        $rand2 = lcg_value() * 0.3;
    } while ($values[$rand] < $rand2);
    return $rand;
}

random();

首先,您需要表征當前的隨機數生成器。 在PHP的情況下,rand()函數返回一個漂亮的平面輪廓 - 因此不需要預處理。

重新映射輸出分布函數,使其下面的區域為1,范圍從0開始。然后計算其積分。 存儲積分(例如,作為值的數組)。 然后,當您需要隨機數matchnig時,首先從內置生成器獲取0到1之間的隨機數,然后在積分上找到Y坐標,其中X坐標是您生成的值。 最后,將值縮放到所需范圍(例如,如果查找介於0和10之間的值,則乘以10,如果查找介於-8和+8之間的值,則多次乘以16並減去8)。

如果您的隨機數生成器不生成平面輪廓,那么最簡單的方法是使用上述方法的反向將其轉換為平面輪廓。

我沒試過,但我認為這可能會奏效:

$random($probability)
{
    $rnd = rand() / getrandmax();

    foreach($probability as $num => $prob)
    {
        $rnd -= $prob;
        if($rnd <=0)
            return $num;
    }

    return -1; //this should never happen
}

並像這樣調用它(使用你的第二個例子):

$distribution = array(
    1 => 0.10,
    2 => 0.15,
    3 => 0.30,
    4 => 0.27,
    5 => 0.14,
    6 => 0.04);

$number = random($distribution);

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM