【问题标题】:Statistical mathematics issues统计数学问题
【发布时间】:2010-12-21 22:20:36
【问题描述】:

我正在开发一个德州扑克手牌范围胜率评估器,它使用蒙特卡罗模拟评估手牌分配。我遇到了两个令人讨厌的问题,我无法给出任何理由。

问题 #1:

简而言之,评估员首先从玩家的手牌中挑选手牌。假设我们有以下内容:

AA - 6 手
KK - 6 手

我们拿起一张棋盘牌,然后从双方玩家手中随机抽出不与棋盘牌相撞的手。
给定的示例给出了以下正确的股票:

AA = ~81.95%
KK = ~18.05%

现在的问题。如果评估者先选择底牌,然后再选择棋盘,这是行不通的。然后我得到这样的东西:

AA = ~82.65%
KK = ~17.35&

为什么会有偏见?如果一个人先选择底牌或棋盘牌,这有什么关系?显然是这样,但不明白为什么。

问题 #2:

如果我有以下范围的十手分布:

AA
KK+
QQ+
杰杰+
TT+
99+
88+
77+
66+
55+

我的评估器很慢。这是因为从分布中选择底牌时,会发生很多冲突。在我们得到十张底牌和一个不会碰撞的棋盘之前,有很多试验。所以,我改变了评估者从分布中选择手牌的方法:


// Original - works.

void HandDistribution::Choose(unsigned __int64 &usedCards, bool &collided)
{
        _pickedHand = _hands[(*Random)()];

        collided = (_pickedHand & usedCards) != 0;
        usedCards |= _pickedHand;
}

// Modified - Doesn't work; biased equities.

void HandDistribution::Choose(unsigned __int64 &usedCards, bool &collided)
{
        // Let's try to pick-up a hand from this distribution ten times, before
        // we give up.

        // NOTE: It doesn't matter, how many attempts there are (except one). 2 or 10,
        // same biased results.

        for (unsigned int attempts = 0; i < 10; ++i) {
                _pickedHand = _hands[(*Random)()];

                collided = (_pickedHand & usedCards) != 0;

                if (!collided) {
                        usedCards |= _pickedHand;

                        return;
                }
        }

        // All the picks collided with other hole cards...
}

替代方法要快得多,因为不再有那么多碰撞。但是,结果非常有偏差。为什么?如果评估者通过一次或多次尝试选择一手牌,这有什么关系?同样,显然它确实如此,但我不知道为什么。

编辑:

仅供参考,我正在使用 Boost 的随机数生成器,更准确地说是 boost::lagged_fibonacci607。不过,同样的行为也发生在 mersenne twister 上。

代码如下:


func Calculate()
{
        for (std::vector<HandDistribution *>::iterator it = _handDistributions.begin(); it != _handDistributions.end(); ++it) {
                (*it)->_equity = 0.0;
                (*it)->_wins = 0;
                (*it)->_ties = 0.0;
                (*it)->_rank = 0;
        }

        std::bitset<32> bsBoardCardsHi(static_cast<unsigned long>(_boardCards >> 32)),
                        bsBoardCardsLo(static_cast<unsigned long>(_boardCards & 0xffffffff));
        int cardsToDraw = 5 - (bsBoardCardsHi.count() + bsBoardCardsLo.count()), count = 0;
        HandDistribution *hd_first = *_handDistributions.begin(), *hd_current, *hd_winner;
        unsigned __int64 deadCards = 0;
        boost::shared_array<unsigned __int64> boards = boost::shared_array<unsigned __int64>(new unsigned __int64[2598960]);

        memset(boards.get(), 0, sizeof(unsigned __int64) * 2598960);

        hd_current = hd_first;

        do {
                deadCards |= hd_current->_deadCards; // All the unary-hands.

                hd_current = hd_current->_next;
        } while (hd_current != hd_first);

        if (cardsToDraw > 0)
                for (int c1 = 1; c1 < 49 + (5 - cardsToDraw); ++c1)
                        if (cardsToDraw > 1)
                                for (int c2 = c1 + 1; c2 < 50 + (5 - cardsToDraw); ++c2)
                                        if (cardsToDraw > 2)
                                                for (int c3 = c2 + 1; c3 < 51 + (5 - cardsToDraw); ++c3)
                                                        if (cardsToDraw > 3)
                                                                for (int c4 = c3 + 1; c4 < 52 + (5 - cardsToDraw); ++c4)
                                                                        if (cardsToDraw > 4)
                                                                                for (int c5 = c4 + 1; c5 < 53; ++c5) {
                                                                                        boards[count] = static_cast<unsigned __int64>(1) << c1
                                                                                                      | static_cast<unsigned __int64>(1) << c2
                                                                                                      | static_cast<unsigned __int64>(1) << c3
                                                                                                      | static_cast<unsigned __int64>(1) << c4
                                                                                                      | static_cast<unsigned __int64>(1) << c5;

                                                                                        if ((boards[count] & deadCards) == 0)
                                                                                                ++count;
                                                                                }
                                                                        else {
                                                                                boards[count] = static_cast<unsigned __int64>(1) << c1
                                                                                              | static_cast<unsigned __int64>(1) << c2
                                                                                              | static_cast<unsigned __int64>(1) << c3
                                                                                              | static_cast<unsigned __int64>(1) << c4;

                                                                                if ((boards[count] & deadCards) == 0)
                                                                                        ++count;
                                                                        }
                                                        else {
                                                                boards[count] = static_cast<unsigned __int64>(1) << c1
                                                                              | static_cast<unsigned __int64>(1) << c2
                                                                              | static_cast<unsigned __int64>(1) << c3;

                                                                if ((boards[count] & deadCards) == 0)
                                                                        ++count;
                                                        }
                                        else {
                                                boards[count] = static_cast<unsigned __int64>(1) << c1
                                                              | static_cast<unsigned __int64>(1) << c2;

                                                if ((boards[count] & deadCards) == 0)
                                                        ++count;
                                        }
                        else {
                                boards[count] = static_cast<unsigned __int64>(1) << c1;

                                if ((boards[count] & deadCards) == 0)
                                        ++count;
                        }
        else {
                boards[0] = _boardCards;
                count = 1;
        }

        _distribution = boost::uniform_int<>(0, count - 1);

        boost::variate_generator<boost::lagged_fibonacci607&, boost::uniform_int<> > Random(_generator, _distribution);

        wxInitializer initializer;

        Update *upd = new Update(this);

        _trial = 0;
        _done = false;

        if (upd->Create() == wxTHREAD_NO_ERROR)
                upd->Run();

        hd_current = hd_first;

        ::QueryPerformanceCounter((LARGE_INTEGER *) &_timer);

        do {
                hd_current = hd_first;

                unsigned __int64 board = boards[Random()] | _boardCards, usedCards = _deadCards | board;
                bool collision;

                do {
                        hd_current->Choose(usedCards, collision);

                        hd_current = hd_current->_next;
                } while (hd_current != hd_first && !collision);

                if (collision) {
                        hd_first = hd_current->_next;

                        continue;
                }

                unsigned int best = 0, s = 1;

                // Evaluate all hands.

                do {
                        hd_current->_pickedHand |= board;

                        unsigned long i, l = static_cast<unsigned long>(hd_current->_pickedHand >> 32);
                        int p;
                        bool f = false;

                        if (_BitScanForward(&i, l)) {
                                p = _evaluator[53 + i + 32];
                                l &= ~(static_cast<unsigned long>(1) << i);
                                f = true;
                        }

                        if (f)
                                while (_BitScanForward(&i, l)) {
                                        l &= ~(static_cast<unsigned long>(1) << i);
                                        p = _evaluator[p + i + 32];
                                }

                        l = static_cast<unsigned long>(hd_current->_pickedHand & 0xffffffff);

                        if (!f) {
                                _BitScanForward(&i, l);

                                p = _evaluator[53 + i];
                                l &= ~(static_cast<unsigned long>(1) << i);
                        }

                        while (_BitScanForward(&i, l)) {
                                l &= ~(static_cast<unsigned long>(1) << i);
                                p = _evaluator[p + i];
                        }

                        hd_current->_rank = p;

                        if (p > best) {
                                hd_winner = hd_current;
                                s = 1;
                                best = p;
                        } else if (p == best)
                                ++s;

                        hd_current = hd_current->_next;
                } while (hd_current != hd_first);

                if (s > 1) {
                        for (std::vector<HandDistribution *>::iterator it = _handDistributions.begin(); it != _handDistributions.end(); ++it) {
                                if ((*it)->_rank == best) {
                                        (*it)->_ties += 1.0 / s;
                                        (*it)->_equity += 1.0 / s;
                                }
                        }
                } else {
                        ++hd_winner->_wins;
                        ++hd_winner->_equity;
                }

                ++_trial;

                hd_first = hd_current->_next;
        } while (_trial < trials);
}

【问题讨论】:

  • 您是否使用蒙特卡罗模拟来计算权益?
  • “从双方玩家中随机拿起一只手,然后我们选择一张板卡”和“先选择底牌,然后选择板卡”有什么区别?
  • 关于第二个问题,尝试次数重要吗?
  • unknown:不,尝试多少次都没关系。即使有两个,结果也会有偏差。
  • 那个 if-for-if-for-if-for-if-for-if-for-else-else-else-else-else 金字塔很有趣。它有什么作用?

标签: c++ algorithm random statistics selection


【解决方案1】:

对于问题 #1,我认为偏见不是问题所固有的,而是您的实施所固有的。

我的意思是,如果你发无限多手牌,首先发牌,然后是玩家手(*),并且只考虑一手AA的“发牌”另一个是 KK,赢率应该和你发无限多手牌一样,先发玩家手,然后发牌,再一次只考虑一手 AA 和 KK 的“发牌”。

当您第一次从一组离散的手牌中选择玩家手牌时,您会限制可以放在棋盘上的牌。

如果你先放牌,你就没有限制,如果你在这之后随机选择一对AA / KK直到你没有发生碰撞,你就有(*)的类似物强>

我看看能不能再详细一点。

【讨论】:

  • 我正要发布与您完全相同的内容。正确的做法是模拟剩余的牌堆,并以与游戏相同的方式处理它们。
【解决方案2】:

它可以是一个随机数生成器吗?无法从代码中看出使用了什么数字生成器。5

【讨论】:

  • 我正在使用 Boost 的 laagged_fibonacci607。与 mersenne twister 的行为相同。
【解决方案3】:

我不能肯定地说,因为我显然看不到整个程序源代码,但我看到不同股票时的第一个怀疑是,对于您的蒙特卡洛模拟,您根本没有运行足够的试验来获得好的结果评估这手牌的胜率。

很可能是其他问题,但很容易确定这是否是您的差异的原因。我会尝试多次运行非常大量的试验(超过 100 万次),看看您的股票变化有多大。

【讨论】:

  • 已经这样做了。以两种方式进行约 100-3 亿次试验。如果它首先选择底牌,它就不起作用(有偏见)。如果它先选择棋盘卡,它就可以了。
  • 一开始我犹豫要不要提出来,因为你提到的第一种情况表明你得到了正确的净值。但我现在就去问。你确定你使用的是一种公正的洗牌方法吗?
  • 我相信我是。我正在将所有可能的板生成一个数组(约 250 万个结果),并选择一个具有滞后斐波那契/梅森捻线器的板。
【解决方案4】:

我认为你不应该使用碰撞。它们非常低效,尤其是当它们发生很多时,并且当您尝试减少它们的数量时,很可能会引入偏差:您通过碰撞概率模拟分布,因此拥有完整的可能碰撞集合有必要的。任何减少都必须保持分布不变,所以这就像减少一个分数。

问题 1:您如何确定第一组股票是正确的?独立来源?我会假设首先挑选底牌,然后从剩余的牌中取出棋盘牌“显然”是正确的(或者这是否幼稚?),只要挑选底牌可以独立完成(见问题 2)。

问题 2:当手牌范围重叠时,手牌(底牌)分布不是独立的。如果一个玩家有 AK+,另一手未知,情况与一个玩家有 AK+,另一个 AA 的情况不同:在第二种情况下,第一个玩家实际持有 KK 的可能性比第一种情况更大。在你十手牌的例子中,55+ 的玩家不太可能有 KK 这样的牌。同样,您如何确定正确的股票?

很抱歉,我无法为您的问题提供结论性答案,但我认为您需要进行或阅读一些相关的统计分析,以确定性地产生正确的手牌分布并确保它们也独立于玩家的顺序。

编辑:感谢您发布一些内容,让我们衡量您正在使用的代码类型。尽管听起来很苛刻,但我现在倾向于建议您从头开始,但首先要了解结构化编程。

只是一些提示:您目前似乎将一组卡片表示为一个 52 元素的位域。虽然一开始这似乎很有效,但您会发现仅从这样的套牌中发牌是非常复杂的。所以,保持简单:创建一个描述卡片的类;创建一个描述一副纸牌的类;为此实施Fisher-Yates shuffle;给它一个返回卡片的deal() 方法,等等。哦,不要使用传递引用,而是使用返回值。您想查看值在哪里被修改,而不是深入每个子程序。

编辑 2:关于“我不能使用类,它太慢了”:是什么让您认为使用类会使您的代码变慢?类只是 C++ 中的一个编译时概念(大约)。即使您不想使用类或结构,也要使用适当的数据结构,如数组;这并不比位摆弄效率低。

你的问题是,为了从一副牌中获得一些随机牌,你用一组随机请求来打击它,直到一个完全成功,而不是仅仅请求一定数量的未使用的牌。随着可用卡片数量的减少,这种算法的复杂性接近无穷大。

这就是著名的名言“过早的优化是万恶之源”的意思:你过早地“优化”了你的数据表示,现在你不能再有效地获得随机卡片了。首先让你的算法正确,然后寻找瓶颈(这意味着测试测量,也就是分析)并优化它们。

【讨论】:

  • 嗨。我通过列举所有可能的结果来确定股票。此外,将结果与 PokerStove (AA-KK: 81.95% - 18.05%) 进行了比较。关于第二个问题,我将结果与 PokerStove 和 Holdem Ranger 进行了比较。我将在原始帖子中添加伪代码如何评估。
  • 我不能将类用于卡片。实在是太慢了我需要每秒至少进行约 1-5 百万次评估。最重要的是,我正在使用 2+2 评估器。换句话说,它是一个大约 100Mb 的数组,包含所有可能的 7 张牌组合及其等级。整个事情就像它应该的那样工作。但是,我只有这两个我无法理解的问题。我知道这与统计数学有关。
  • 问题是算法有效,正如我所说。仅当我更改选择卡片的顺序时(棋盘卡 -> 底牌,底牌 -> 棋盘卡)。该项目处于最后阶段,优化过程正在进行中。我已经分析了代码等。请重新阅读我原来的问题。
  • 它可能“有效”,但一般来说,您永远不会以这种方式获得数百万次评估。正如您所写,当已经使用了更多卡时,您会遇到很多冲突。这对于您的数据结构是不可避免的。您的数据结构是“优化”出错了。
  • 嗯,目前,这可以获得大约 150k - 160 万次评估每秒,这很好 (C2D E4500)。
【解决方案5】:

Andreas Brinck 回复了您的问题 #1。

我认为您的问题 #2 是由同一问题引起的。

无需检测碰撞,只需模拟一堆卡片并挑选剩余的卡片。如果不这样做,则必须小心概率,否则可能会导致条件概率出现偏差,具体取决于您的算法。

顺便说一句,这可能对你有一些帮助:

【讨论】:

    猜你喜欢
    • 2021-04-02
    • 1970-01-01
    • 2021-03-06
    • 1970-01-01
    • 2011-12-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多