计算函数可信度的算法 / 蒙特卡罗方法

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

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

F是从字符到字符的函数。假设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 ?
                }
            }
        }

我的问题基本上是,这是否看起来像实现该算法的最佳方法。尽管采用了这种方法,但似乎我仍然可能陷入一些局部最大值/最小值中。 编辑 - 这里基本上是Transpose()方法背后的内容。我使用类型为<< char,char >>的字典/哈希表,候选函数使用它来查找任何给定的char -> char转换。因此,transpose方法只是交换字典中两个值,以确定函数的行为。
    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个回答

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的附加函数。 - Sean Thoman
关于Transpose()方法--我只传递了一个或两个整数值作为索引参数。基本上,更多的索引意味着字典的洗牌更加激进。因此,通常只有一个随机生成的整数被传递给该方法。另外,可用字符集包含许多字符'ABCDEFG...'等,但如果编码消息只是'bveb'或类似的东西,它将仅对b->(),v->(),e->()等映射进行置换,其中()表示字母表中可能的任何字符之一。 - Sean Thoman
这对我来说听起来是正确的,我会删除关于Transpose方法的编辑。 - Stjepan Rajko
我认为Pl(f)值为0可能会影响整个结果。如果一个候选人创建的输出在实际使用中几乎不会出现在英语中,即使它在其他方面非常接近,它也会得到0的值?例如像'qz'这样的单词,据我所知,在任何英语单词中都很少或从未相邻出现。 - Sean Thoman
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/3468/discussion-between-stjepan-rajko-and-sean-thoman - Stjepan Rajko
显示剩余2条评论

2

初步看来,codereview.stackexchange.com可能是一个“更好的论坛”。

尽管如此,我会快速尝试一下:

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

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

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

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

  • 英语中一级转换的分布不是很均匀(因此可以模拟给定函数的可信度,通过奖励它的匹配并惩罚它的不匹配)。这种多维统计比给定语言/语料库中字符的“0阶”分布更能适应参考语料库的偏差。
  • 一级转换的分布虽然具有语言特定性,但在不同语言和/或行话词汇(基于所述语言)的上下文中通常相似。在速记行话的情况下,事情会变得复杂,因为通常会省略元音等字母。

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


似乎 Pl(f) 的值在某个点就停止了改善。我需要达到一个非常高的值,但似乎在到达之前就卡住了。也许我的转置方法有问题。 - Sean Thoman
我需要一种方法让算法更积极地“零点定位”于解决方案。 - Sean Thoman
@Sean:(我刚刚注意到你对ComputePl()方法的编辑)。很难确定,因为它取决于_matrix中的值是如何被归一化的,但在那里进行的计算类型可能会引入舍入误差,这可能使实现对微小(但理想的)改进变得“不敏感”,从而解释了为什么朝着最优解的进展似乎停滞不前。关于你提出的“更积极地聚焦于解决方案”建议:通常不是一个好主意...(这与你需要随机搜索的事实相矛盾) - mjv
对于矩阵值--M ['a'] ['b']将是“给定第一个字符'a',下一个字符是'b'的概率是多少”。 因此,放入该单元格的值为(('b'跟随'a'的总情况数)/('a'的总实例数))x 100 - Sean Thoman

2
这个算法似乎与模拟退火有关http://en.wikipedia.org/wiki/Simulated_annealing。如果是这样的话,你可以通过改变接受当前解较差的替代方案的概率来改善行为,特别是如果你随着时间的推移降低这个概率。
或者,你可以尝试从多个随机起点进行简单的爬山算法-永远不要接受较差的替代方案,这意味着你会更容易陷入局部最大值,但可以从不同的起点反复运行该算法。
当你测试这个算法时,你通常知道测试问题的正确答案。最好将正确答案的可信度值与算法得出的值进行比较,以防万一可信度公式有问题,如果是这样,你的算法将得出看似比正确答案更合理的错误答案。

值得一试。我想这将意味着随着时间的推移,使硬币翻转不太可能偏向劣质结果。 - Sean Thoman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接