计算函数/蒙特卡罗方法合理性的算法

时间:2011-09-14 21:36:08

标签: c# algorithm functional-programming montecarlo markov-chains

我正在编写一个试图复制本文开头讨论的算法的程序,

http://www-stat.stanford.edu/~cgates/PERSI/papers/MCMCRev.pdf

F是从char到char的函数。假设P1(f)是该函数的“合理性”度量。算法是:

从对函数的初步猜测开始,比如f,然后是新函数f * -

  • 计算Pl(f)。
  • 通过对值f进行随机转置来更改为f *,将其分配给两个符号。
  • 计算Pl(f *);如果这大于Pl(f),则接受f *。
  • 如果没有,翻转一个Pl(f)/ Pl(f *)硬币;如果它出现在头部,请接受f *。
  • 如果硬币抛出尾巴,请留在f。

我正在使用以下代码实现此功能。我正在使用c#,但试图让每个人都更加简化。如果有更好的论坛,请告诉我。

 var current_f = Initial();    // current accepted function f
 var current_Pl_f = InitialPl();  // current plausibility of accepted function f

 for (int i = 0; i < 10000; i++)
        {
            var candidate_f = Transpose(current_f); // create a candidate function

            var candidate_Pl_f = ComputePl(candidate_f);  // compute its plausibility

            if (candidate_Pl_f > current_Pl_f)            // candidate Pl has improved
            {
                current_f = candidate_f;            // accept the candidate
                current_Pl_f = candidate_Pl_f; 
            }
            else                                    // otherwise flip a coin
            {
                int flip = Flip(); 

                if (flip == 1)                      // heads
                {
                    current_f = candidate_f;        // accept it anyway
                    current_Pl_f = candidate_Pl_f; 
                }
                else if (flip == 0)                 // tails
                {
                    // what to do here ?
                }
            }
        }

我的问题基本上是否看起来是实现该算法的最佳方法。尽管实施了这种方法,但我似乎可能会陷入某些局部最大值/局部最小值。

编辑 - 这是Transpose()方法背后的基本内容。我使用类型&lt;&lt;的字典/哈希表char,char&gt;&gt;候选函数用于查看任何给定的char - &gt; char变换。因此,转置方法只是在字典中交换两个指示函数行为的值。

    private Dictionary<char, char> Transpose(Dictionary<char, char> map, params int[] indices)
    {
        foreach (var index in indices)
        {
            char target_val = map.ElementAt(index).Value;   // get the value at the index

            char target_key = map.ElementAt(index).Key;     // get the key at the index

            int _rand = _random.Next(map.Count);   // get a random key (char) to swap with

            char rand_key = map.ElementAt(_rand).Key;

            char source_val = map[rand_key]; // the value that currently is used by the source of the swap

            map[target_key] = source_val; // make the swap

            map[rand_key] = target_val;
        }

        return map; 
    }

请记住,使用基础字典的候选函数基本上只是:

public char GetChar(char in, Dictionary<char, char> theMap)
{
     return theMap[char]; 
}

这是计算Pl(f)的函数:

    public decimal ComputePl(Func<char, char> candidate, string encrypted, decimal[][] _matrix)
    {
        decimal product = default(decimal);

        for (int i = 0; i < encrypted.Length; i++)
        {
            int j = i + 1;

            if (j >= encrypted.Length)
            {
                break;
            }

            char a = candidate(encrypted[i]);
            char b = candidate(encrypted[j]);

            int _a = GetIndex(_alphabet, a); // _alphabet is just a string/char[] of all avl chars 
            int _b = GetIndex(_alphabet, b);

            decimal _freq = _matrix[_a][_b]; 

            if (product == default(decimal))
            {
                product = _freq;
            }
            else
            {
                product = product * _freq;
            }
        }

        return product;
    }

3 个答案:

答案 0 :(得分:2)

根据文章中的描述,您的实现似乎是正确的(您标记为“此处要做什么”的部分应该没有任何内容。)

如果您遇到局部最大值问题(文章声称硬币折腾应该避免),请确保您的Initial,Transpose,ComputePl和Flip的实现是正确的。

你也可以尝试让投掷硬币有偏见(增加Flip()== 1的概率会使它更接近随机游走并且不易被卡住)。

这是一个稍微严格的代码版本:

var current_f = Initial();    // current accepted function f
var current_Pl_f = ComputePl(current_f);  // current plausibility of accepted function f

for (int i = 0; i < 10000; i++)
{
    var candidate_f = Transpose(current_f); // create a candidate function
    var candidate_Pl_f = ComputePl(candidate_f);  // compute its plausibility

    if (candidate_Pl_f > current_Pl_f  ||  Flip() == 1)
    {
        // either candidate Pl has improved,
        // or it hasn't and we flipped the coin in candidate's favor
        //  - accept the candidate
        current_f = candidate_f;            
        current_Pl_f = candidate_Pl_f; 
    }
}

答案 1 :(得分:2)

暂时,codereview.stackexchange.com可能是“更好的论坛”。
从来没有,我会快速刺伤它:

  • 一目了然,显示的代码段是算法的正确实现。
  • 算法是否会在局部最小值中“卡住”是一个与算法有关的问题,而不是实现。 (见下面的讨论)
  • 您对“最佳方法”的追求似乎是针对算法中的调整(偏离原始算法)而不是实施中的调整(至使它更快和/或消除一些可能的缺陷)。有关算法的考虑,请参阅下面的讨论;关于实施的讨论考虑以下内容:
    • 确保Flip()方法公平
    • 确保ComputePl()是正确的:由于值函数中的算术精度问题,通常会出现一些错误。
    • 使用Transpose()方法确保公平性(等概率)。
    • 性能改进可能来自ComputePl()方法(未显示)中的优化,而不是主循环中的优化。

关于算法本身的讨论及其对不同问题的适用性。
简而言之,该算法是一个引导随机搜索,其中[巨大的]解空间采用两个随机设备进行采样:Transpose()方法(每次非常轻微地修改当前候选函数)和Flip()方法决定是否[局部]次优解决方案应该存活下来。搜索由目标函数ComputePl()引导,ComputePl()本身基于某些参考语料库中的一阶转换矩阵。

在这种情况下,可以通过增加选择“次优”功能的概率来避免局部最小“焦油坑”:而不是公平的50-50翻转(),可能尝试保留66%的“次优”解决方案的可能性或甚至75%。这种方法通常会延长收敛到最优解的必要代数,但是,正如所说的那样可以避免陷入局部最小值。

确保算法适用性的另一种方法是确保更好地评估给定函数的合理性。对算法的相对成功和普遍性的可能解释是

  • 英语中一阶转换的分布不是很均匀(因此模型很好地模拟了给定函数的合理性,通过奖励它的匹配并惩罚它的不匹配)。这种多维统计,对于偏离参考语料库比对给定语言/语料库中字符的“0阶”分布更具弹性。
  • 在语言特定的情况下,一阶转换的分布在不同语言和/或术语词汇[基于所述语言]的上下文中通常是相似的。在短手的情况下,事情确实会崩溃,因此通常会省略诸如誓言之类的字母。

因此,为了提高算法对给定问题的适用性,请确保使用的分布矩阵尽可能地匹配基础文本的语言和域。

答案 2 :(得分:2)

此算法似乎与http://en.wikipedia.org/wiki/Simulated_annealing有关。如果是这种情况,可以通过改变您接受当前解决方案的较差替代方案的概率来帮助改变行为,特别是如果您随着时间的推移降低这种可能性。

或者,您可以尝试从多个随机开始的简单爬山 - 从不接受较差的替代方案,这意味着您将更频繁地陷入局部最大值,但是从不同的开始重复运行算法。

当您对此进行测试时,您通常会知道测试问题的正确答案。将正确答案的合理性值与您的算法所提出的合理性值进行比较是一个好主意,以防万一这样的弱点在合理性公式中,在这种情况下,您的算法会得出错误的答案,这些答案似乎比正确的。