我正在开发德州扑克手持式股权评估师,该评估员使用蒙特卡罗模拟评估手动分布。我遇到了两个烦人的问题,这些行为我无法给出任何理由。
问题#1:
在坚果壳中,评估者首先从玩家的手部分布中拿起手。说,我们有以下内容:
AA - 6 hands KK - 6 hands
我们拿起一张牌卡,然后从两名牌手中随机抽出一手牌,不与棋盘牌发生碰撞。
给出的示例给出了以下正确的股票:
AA = ~81.95% KK = ~18.05%
现在问题。如果评估者在此之后首先选择孔卡和板卡,则不起作用。然后我得到这样的东西:
AA = ~82.65% KK = ~17.35&
为什么会有偏见?如果首先选择底牌或板卡,这有什么关系?显然确实如此,但无法理解为什么。
问题#2:
如果我有10个具有以下范围的手动分布:
AA KK+ QQ+ JJ+ 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);
}
答案 0 :(得分:1)
对于问题#1,我不认为偏差是问题所固有的,而是你的实施。
我的意思是,如果你交出无数手牌,首先处理棋盘牌然后玩家手牌(*),并且只考虑单手AA的“交易”而另一个是KK,股权应该与你交易无数手牌相同,首先交易玩家手牌然后再交易董事会卡牌,再次只考虑“交易”,其中一手牌是AA,一手牌是KK。
当您首先从一组不连续的牌中选择牌手时,您可以限制可放置在牌面上的牌。
如果您首先放置棋盘卡,则没有任何限制,如果您在此之后随机选择一对AA / KK指针,直到您没有发生碰撞,您将获得(*)强>
我会看看能否详细说明一下。
答案 1 :(得分:0)
可以是随机数生成器吗?无法从代码中看出使用了什么数字生成器.5
答案 2 :(得分:0)
我无法确定,因为我显然无法看到整个节目源,但我首先怀疑看到不同的股票是因为你的蒙特卡罗模拟你只是没有运行足够的试验来获得好的评估手的权益。
很可能是其他一些问题,但很容易弄清楚这是否是导致您出现差异的原因。我会尝试多次进行非常大量试验(超过100万次),看看您的股票变化有多大。
答案 3 :(得分:0)
我认为你不应该使用碰撞。它们非常低效,特别是当它们发生很多时,并且当你试图减少它们的数量时,很可能会引入偏差:你通过碰撞的概率来模拟分布,因此具有完整的可能碰撞集有必要的。任何减少必须保持分布相同,所以这就像减少分数。
问题1:您如何确定第一组股票是否正确?独立来源?我会假设首先选择底牌,那么剩余牌中的牌卡“显然”是正确的(或者这是天真的吗?),只要选择底牌就可以独立完成(见问题2)。 / p>
问题2:当手范围重叠时,手(孔卡)分布不是独立的。如果一个玩家有AK +,另一个玩家不知道,情况与一个玩家有AK +的情况不同,另一个AA:在第二种情况下,第一个玩家实际拥有KK的可能性比第一个情况更可能。在你的10手示例中,55岁以上的玩家不太可能拥有像KK这样的东西。同样,您是如何确定正确的股票的?
对不起,我无法对你的问题给你一个确凿的答案,但我认为你需要做或读一些涉及的统计分析,以确定性地产生正确的手分布,并确保它们也独立于球员的顺序。
编辑:感谢您发布一些内容,让我们可以衡量您正在使用的代码类型。听起来很苛刻,我现在倾向于建议你从头开始,但首先要学习结构化编程。只是一些提示:您目前似乎将一组卡片表示为52个元素的位字段。虽然这一开始看起来效率很高,但你会发现从这样的牌组处理牌只是非常复杂。所以,保持简单:创建一个描述卡片的课程;制作一个描述一副牌的课程;实施Fisher-Yates洗牌;给它一个返回卡片等的deal()
方法。哦,不要使用pass-by-reference,而是使用返回值。您希望查看值的修改位置,而不必深入了解每个子例程。
编辑2:关于“我不能使用类,它太慢了”:是什么让你觉得使用类会让你的代码变慢?类只是C ++中的编译时概念(大约)。即使您不想使用类或结构,也要使用正确的数据结构,如数组; 不效率低于比特摆弄。
你的问题是,为了从牌组中获得一些随机牌,你可以用随机请求集击打它,直到一个完全成功,而不是仅仅请求一定数量的未使用的牌。随着可用卡数量的减少,这种方法的算法复杂度接近无穷大。
这就是着名的名称“过早优化是所有邪恶的根源”是关于:你过早“优化”了你的数据表示,现在你不能再有效地获得随机卡了。首先获取算法,然后查找瓶颈(即测试和测量,a.k.a。分析)并优化这些瓶颈。
答案 4 :(得分:0)
Andreas Brinck回答了你的问题#1。
我认为您的问题#2是由同一问题引起的。
不是检测碰撞,而是模拟一堆牌并挑选剩余的牌。如果不这样做,则必须注意概率,否则可能会导致条件概率出现偏差,具体取决于算法。
顺便说一句,这可能对你有所帮助: