如何有效地计算步步高中的污点暴露

时间:2016-10-08 20:44:07

标签: algorithm machine-learning artificial-intelligence reinforcement-learning temporal-difference

我正在尝试实现类似于td-gammon的步步高算法,如here所述。

如本文所述,td-gammon的初始版本仅在特征空间中使用原始板编码创建了良好的播放代理,但要获得世界级代理,您需要添加一些预先计算的功能与良好的发挥相关联。其中一个最重要的特征就是印迹暴露。

将污点暴露定义为here

对于给定的印迹,从36中滚出的数量可以让对手击中污点。总印迹暴露是36个中的滚动次数,这将允许对手击中任何印迹。污点暴露取决于:(a)所有敌人在污点前的位置; (b)污点与敌人之间的阻挡点的数量和位置,以及(c)杆上敌人的数量,以及允许他们重新进入棋盘的滚动,因为栏上的男人必须重新进入在可以击中污点之前进入中心。

我尝试过各种方法来有效地计算这个功能,但我的计算速度仍然太慢,我不知道如何加快它的速度。

请记住,td-gammon方法会评估给定骰子掷骰子的每个可能的棋盘位置,因此每个玩家骰子每转一圈就需要为每个可能的棋盘位置计算此特征。

一些粗略的数字:假设每回合大约有30个棋盘位置,平均游戏持续50回合我们可以运行1,000,000个游戏模拟:(x * 30 * 50 * 1,000,000)/(1000 * 60 * 60 * 24)天x是计算特征的毫秒数。假设x = 0.7,我们大约需要12天来模拟1,000,000场比赛。

我真的不知道这是否合理,但我觉得必须有一个明显更快的方法。

所以这就是我尝试过的:

方法1(通过骰子滚动)

对于21个可能的骰子卷中的每一个,递归检查以查看命中发生。以下是此程序的主要工作原因:

private bool HitBlot(int[] dieValues, Checker.Color checkerColor, ref int depth)
    {
        Moves legalMovesOfDie = new Moves();

        if (depth < dieValues.Length)
        {
            legalMovesOfDie = LegalMovesOfDie(dieValues[depth], checkerColor);
        }

        if (depth == dieValues.Length || legalMovesOfDie.Count == 0)
        {
            return false;
        }

        bool hitBlot = false;

        foreach (Move m in legalMovesOfDie.List)
        {
            if (m.HitChecker == true)
            {
                return true;
            }

            board.ApplyMove(m);
            depth++;
            hitBlot = HitBlot(dieValues, checkerColor, ref depth);
            board.UnapplyMove(m);
            depth--;

            if (hitBlot == true)
            {
                break;
            }
        }

        return hitBlot;
    }

这个函数的作用是将一个骰子值作为输入(即如果播放器滚动1,1数组将是[1,1,1,1]。该函数然后递归检查是否存在命中,如果是,则退出为真。函数LegalMovesOfDie计算特定骰子值的合法移动。

方法2(通过印迹)

通过这种方法,我首先找到所有的印迹,然后对于每个印迹,我循环每个可能的骰子值,看看是否发生了命中。该函数经过优化,一旦骰子值记录命中,我就不再使用它进行下一次印迹。它也被优化为仅考虑在印迹前面的移动。我的代码:

public int BlotExposure2(Checker.Color checkerColor)
    {
        if (DegreeOfContact() == 0 || CountBlots(checkerColor) == 0)
        {
            return 0;
        }

        List<Dice> unusedDice = Dice.GetAllDice();

        List<int> blotPositions = BlotPositions(checkerColor);

        int count = 0;

        for(int i =0;i<blotPositions.Count;i++)
        {
            int blotPosition = blotPositions[i];

            for (int j =unusedDice.Count-1; j>= 0;j--) 
            {
                Dice dice = unusedDice[j];

                Transitions transitions = new Transitions(this, dice);

                bool hitBlot = transitions.HitBlot2(checkerColor, blotPosition);

                if(hitBlot==true)
                {
                    unusedDice.Remove(dice);

                    if (dice.ValuesEqual())
                    {
                        count = count + 1;
                    }
                    else
                    {
                        count = count + 2;
                    }
                } 
            }
        }


        return count;
    }

方法transitions.HitBlot2采用了blotPosition参数,该参数确保只考虑那些位于污点前面的移动。

这两个实现都很慢,当我使用分析器时,我发现递归是原因,所以我尝试重构这些如下:

  1. 使用for循环而不是递归(丑陋的代码,但它更快)
  2. 要使用parallel.foreach,以便不是一次检查1个骰子值,而是并行检查这些值。
  3. 以下是我为该功能进行50000次计算的平均计时结果(请注意每种方法的计时时间均为相同数据):

    1. 使用递归的方法1:每次计算2.28 ms
    2. 使用递归的方法2:每次计算1.1 ms
    3. 使用for循环的方法1:每次计算1.02 ms
    4. 使用for循环的方法2:每次计算0.57 ms
    5. 方法1使用parallel.foreach:每次计算0.75 ms 6方法2使用parallel.foreach:每次计算0.75 ms
    6. 我发现时间非常不稳定(可能取决于神经网络权重的随机初始化)但是大约0.7毫秒似乎是可以实现的,如果你记得导致为期100天的训练进行了100天的训练。

      我的问题是:有谁知道这是否合理?是否有更快的算法我不知道可以减少训练?

      最后一条信息:我正在使用一台相当新的机器。 Intel Cote(TM)i7-5500U CPU @ 2.40 GHz。

      需要更多信息,请告诉我,我会提供。

      谢谢, 奥菲尔

1 个答案:

答案 0 :(得分:0)

是的,计算这些功能会使代码变得非常繁琐。看看GNU步步高代码。找到modelBuilder.Entity<Category>().HasKey(e => e.Id); 并查看1008到1267的行。是的,这是260行代码。该代码计算击中至少一个检查器的卷数,以及击中至少2个检查器的卷数。如你所见,代码很毛茸茸。

如果您找到更好的计算方法,请发布结果。为了改进,我认为你必须看看董事会代表。你能用不同的方式代表电路板,使计算速度更快吗?