【问题标题】:Algorithm for computing the plausibility of a function / Monte Carlo Method计算函数合理性的算法/蒙特卡洛方法
【发布时间】:2011-09-14 21:36:07
【问题描述】:

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

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

F 是一个从 char 到 char 的函数。假设 Pl(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 ?
                }
            }
        }

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

EDIT - 这是 Transpose() 方法背后的基本内容。我使用类型为 > 的字典/哈希表,候选函数使用它来查看任何给定的 char -> 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;
    }

【问题讨论】:

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


    【解决方案1】:

    暂定,codereview.stackexchange.com 可能是一个“更好的论坛”。
    无论如何,我会快速尝试一下:

    • 乍一看,显示的 sn-p 是算法的正确实现。
    • 算法是否会“陷入”局部最小值是与算法有关的问题,而不是与实现有关的问题。 (见下文讨论)
    • 您对“最佳方法”的追求似乎是针对算法中的调整(偏离原始算法)而不是实现中的调整(以使其更快和/或消除一些可能的缺陷)。有关算法的考虑,请参见下面的讨论;关于实施的讨论,请考虑以下几点:
      • 确保 Flip() 方法是公平的
      • 确保 ComputePl() 正确:由于值函数中的算术精度问题,经常会出现一些错误。
      • 使用 Transpose() 方法确保公平(等概率)。
      • 性能改进可能来自 ComputePl() 方法(未显示)而不是主循环中的优化。

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

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

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

    • 英语中一阶转换的分布不是很均匀(因此通过奖励匹配和惩罚不匹配来很好地模拟给定函数的合理性)。这种多维统计数据比给定语言/语料库中字符的“0 阶”分布更能适应偏离参考语料库的情况。
    • 一阶转换的分布虽然是特定于语言的,但在不同语言和/或在行话词汇的上下文中通常是相似的 [基于所述语言]。如果是速记行话,通常会省略诸如誓言之类的字母,因此情况确实会出现问题。

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

    【讨论】:

    • Pl(f) 的值似乎在某个点停止了改进。我需要达到一个非常高的价值,但它似乎在到达那里之前就挂了。也许我的转置方法有问题。
    • 或者我需要一种方法让算法更积极地在解决方案上“归零”。
    • @Sean:(我刚刚注意到您使用ComputePl() 方法进行的编辑)。很难准确知道,因为它取决于 _matrix 中的值被标准化的方式,但是在那里完成的计算类型可能会引入舍入错误,这可能会使实现对微小(但理想的)改进“不敏感”,因此解释了为什么朝着最佳解决方案的进展似乎停滞不前。关于您“更积极地在解决方案上归零”的建议:通常不是一个好主意...(这与您需要随机搜索的事实相矛盾)
    • 对于矩阵值——M['a']['b'] 将是“给定第一个字符'a',下一个字符是'b'的概率是多少”。因此,该单元格中的值是((“b”跟随“a”的总情况)/(“a”的总实例))x 100
    【解决方案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; 
        }
    }
    

    【讨论】:

    • 那更清楚了,谢谢。我意识到的另一件事是,这篇文章并没有真正提到您是否应该跟踪已经尝试过的候选人并确保不要重复他们,我想应该是这样......我在我的编辑中发布了使用的附加功能用于转置和计算 Pl.
    • 也许对已经尝试过的候选人的某些事情进行散列会很有用(例如,他们的合理性 - 虽然要小心,因为候选人空间),但我不'不要认为你想完全消除重复。即使您已经访问过候选人,该候选人的随机转置也可以将您带到以前未访问过的候选人。通过消除重复,你甚至可以“把自己困在里面”(成为你已经访问过所有转置的候选人)。
    • 关于 Transpose() 方法——我只传入一个或两个 int 值作为索引参数。基本上,更多的索引意味着更积极的字典改组。所以通常它只是一个随机生成的整数,它被传递给该方法。此外,使用的可用字符的字母表包含许多字符 'ABCDEFG...' 等,但如果编码的消息只是 'bveb' 或其他东西,它只会对 b -> ()、v -> () 的映射进行换位, e -> () 等,其中 () 表示字母表中任何可能的字符。
    • 这听起来对我来说是正确的,我将删除我对 Transpose 方法的编辑。
    • 我认为 0 的 Pl(f) 值可能会影响整个事情。如果候选人创建的输出在所有实际用途中从未出现在英语中,那么即使它非常接近,它也会得到 0 值?据我所知,像“qz”这样的东西从来没有或很少出现在任何英语单词中。
    【解决方案3】:

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

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

    当您对此进行测试时,您通常会知道测试问题的正确答案。将正确答案的可信度值与您的算法得出的值进行比较是一个好主意,以防万一弱点出现在可信度公式中,在这种情况下,您的算法会得出错误的答案,这些答案看起来比您的算法更可信正确的。

    【讨论】:

    • 值得一试。我想这意味着随着时间的推移,抛硬币的可能性越来越小。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-06
    • 1970-01-01
    • 2019-09-17
    • 2019-08-04
    • 2019-09-17
    • 2013-04-21
    相关资源
    最近更新 更多