7

この記事の冒頭で説明したアルゴリズムを複製しようとするプログラムを作成しています。

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

F は char から char への関数です。Pl(f) がその関数の「妥当性」尺度であると仮定します。アルゴリズムは次のとおりです。

関数の予備的な推測から始めて、たとえば f と、新しい関数 f* を作成します。

  • Pl(f) を計算します。
  • f が 2 つのシンボルに割り当てる値をランダムに転置して、f* に変更します。
  • Pl(f*) を計算します。これが Pl(f) より大きい場合は、f* を受け入れます。
  • そうでない場合は、Pl(f)/Pl(f*) コインを 1 枚投げます。表が出たら、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 >> 型の辞書/ハッシュ テーブルを使用します。そのため、転置メソッドは、関数の動作を指示するディクショナリ内の 2 つの値を単純に交換します。

    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;
    }
4

3 に答える 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; 
    }
}
于 2011-09-14T22:15:43.597 に答える
2

このアルゴリズムはhttp://en.wikipedia.org/wiki/Simulated_annealingに関連しているようです。その場合、特に時間の経過とともにこの確率を減らす場合は、現在のソリューションよりも劣った代替案を受け入れる確率を変更することで、動作が改善される可能性があります。

または、複数のランダムな開始点から単純な山登りを試すこともできます。より貧弱な代替案を受け入れないでください。つまり、極大値に頻繁に行き詰まる可能性がありますが、異なる開始点からアルゴリズムを繰り返し実行します。

これをテストすると、通常、テストの問題に対する正しい答えがわかります。妥当性の公式に弱点がある場合に備えて、正しい答えの妥当性の値とアルゴリズムが導き出す妥当性の値を比較することをお勧めします。正しいもの。

于 2011-09-15T03:48:46.097 に答える
2

暫定的には、codereview.stackexchange.comが「これに適したフォーラム」になる可能性があります。
それでも、私はそれを簡単に突き刺します:

  • 一見すると、示されているスニペットはアルゴリズムの正しい実装です。
  • アルゴリズムが極小値で「スタック」するかどうかは、実装ではなくアルゴリズムに関係する問題です。(以下の説明を参照)
  • 「最適なアプローチ」を求めるあなたの探求は 、実装の微調整(より高速にするため、および/またはいくつかの潜在的な欠陥を根絶するため)ではなく、アルゴリズムの微調整(元のアルゴリズムからの逸脱)に向けられているようです。アルゴリズムに関する考慮事項については、以下の説明を参照してください。実装に関する議論については、次の点を考慮してください。
    • Flip() メソッドが公平であることを確認する
    • ComputePl() が正しいことを確認してください。値関数の算術精度の問題が原因で、エラーが発生することがよくあります。
    • Transpose() メソッドで公平性 (等確率) を確保します。
    • パフォーマンスの向上は、メイン ループではなく、ComputePl() メソッド (表示されていません) での最適化から得られる可能性があります。

アルゴリズム自体と、さまざまな問題への適用可能性に関する議論。
一言で言えば、アルゴリズムはガイド付き確率的探索であり、[巨大な] 解空間が 2 つのランダムなデバイスでサンプリングされます。現在の候補関数を (毎回非常にわずかに) 変更する Transpose() メソッドと、決定する Flip() メソッドです。 [局所的に] 次善の解決策が存続するかどうか。検索は目的関数 ComputePl() によって導かれます。この関数自体は、参照コーパスの一次遷移行列に基づいています。

このコンテキストでは、「最適ではない」関数を選択する確率を増やすことで、極小値の「タール ピット」を回避できます。公正な 50-50 Flip() ではなく、66% の「最適ではない」ソリューションを保持する確率で試してみてください。 75%も。このアプローチは通常、最適な解に収束するために必要な世代数を拡張しますが、前述のように、極小値で行き詰まるのを回避できる可能性があります。

アルゴリズムの適用性を保証するもう 1 つの方法は、特定の関数の妥当性をより適切に評価することです。アルゴリズムの相対的な成功と一般性については、次のような説明が考えられます。

  • 英語における一次遷移の分布はあまり均一ではありません (したがって、特定の関数の妥当性を、一致に対して報酬を与え、不一致に対して罰することによって、かなり適切にモデル化します)。この種の多次元統計は、特定の言語/コーパスの文字の「0次」分布と言うよりも、参照コーパスからの逸脱に対してより回復力があります。
  • 一次遷移の分布は、言語固有でありながら、一般的に、異なる言語間および/または [その言語に基づく] 専門用語の文脈において類似しています。短い専門用語の場合、物事は崩壊し、通常、誓いなどの文字は省略されます.

したがって、特定の問題に対するアルゴリズムの適用性を向上させるには、使用される分散行列が、基礎となるテキストの言語とドメインにできるだけ厳密に一致するようにします。

于 2011-09-14T22:17:47.257 に答える