Алгоритм вычисления вероятности функции / Метод Монте-Карло - PullRequest
7 голосов
/ 15 сентября 2011

Я пишу программу, которая пытается дублировать алгоритм, описанный в начале этой статьи,

http://www -stat.stanford.edu / ~ cgates / PERSI / документы / MCMCRev.pdf

F - это функция от символа к символу. Предположим, что Pl (f) является мерой «правдоподобия» этой функции. Алгоритм:

Начиная с предварительного предположения о функции, скажем, f, а затем новой функции f * -

  • Вычислить Pl (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. Таким образом, метод транспонирования просто меняет два значения в словаре, который определяет поведение функции.

    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 голосов
/ 15 сентября 2011

Ориентировочно, codereview.stackexchange.com может быть " лучшим форумом для этого ".
Тем не менее, я сделаю быстрый удар по нему:

  • На первый взгляд показанный фрагмент кода является правильной реализацией алгоритма.
  • Вопрос о том, будет ли алгоритм "зависать" в локальных минимумах, является проблемой, относящейся кк алгоритму не к реализации .(см. обсуждение ниже)
  • Похоже, что ваш поиск " оптимального подхода " направлен на изменения в алгоритме (отклонение от исходного алгоритма), а не натвики в реализации (чтобы сделать это быстрее и / или устранить некоторые возможные недостатки).Что касается алгоритма, см. Обсуждение ниже;для обсуждения относительно реализации рассмотрите следующее:
    • убедитесь, что метод Flip () является справедливым
    • убедитесь, что ComputePl () верен: некоторые ошибки часто возникают из-за проблем с арифметической точностью вФункция значения.
    • обеспечивает справедливость (равновероятность) с помощью метода Transpose ().
    • Повышение производительности, скорее всего, будет связано с оптимизацией в методе ComputePl () (не показан), а не в основном цикле.

Обсуждение алгоритма per se и его применимости к различным задачам.
В двух словах алгоритмявляется управляемым стохастическим поиском, при котором [огромное] пространство решения отбирается двумя случайными устройствами: метод Transpose (), который изменяет (очень немного каждый раз) текущую функцию-кандидат, и метод Flip (), который решает, является ли [локально]неоптимальное решение должно выжить.Поиск осуществляется с помощью целевой функции, ComputePl (), которая сама основана на матрице перехода первого порядка в некотором эталонном корпусе.

В этом контексте можно избежать локальных минимумов «битуминозных ям», увеличиваявероятность выбора «неоптимальных» функций: вместо 50-50 Flip (), возможно, попытайтесь с вероятностью сохранения «неоптимальных» решений 66% или даже 75%.Этот подход обычно увеличивает количество поколений, необходимых для достижения оптимального решения, но, как уже было сказано, может избежать застревания в локальных минимумах.

Еще один способ обеспечения применимости алгоритма - обеспечить лучшую оценкуправдоподобие данных функций.Вероятные объяснения относительного успеха и общности алгоритма:

  • Распределение переходов первого порядка в английском языке не очень равномерно (и, следовательно, довольно хорошо моделирует правдоподобие данной функции, награждаяэто для его спичек и наказывая это для его несоответствий).Этот вид многомерной статистики более устойчив к отклонению от эталонного корпуса, чем, скажем, распределение символов «порядка 0» в данном языке / корпусе.
  • Распределение переходов первого порядка, будучи языковымспецифические, как правило, похожи в разных языках и / или в контексте жаргонных словарей [на основе указанных языков].Дело обстоит не так в случае коротких жаргонов, из-за которых обычно не используются такие буквы, как гласные.

Поэтому, чтобы улучшить применимость алгоритма к данной проблеме, убедитесь, что используемая матрица распределения соответствуеткак можно ближе к языку и домену основного текста.

2 голосов
/ 15 сентября 2011

Этот алгоритм, по-видимому, связан с http://en.wikipedia.org/wiki/Simulated_annealing. Если это так, то поведению можно помочь, изменив вероятность, с которой вы принимаете более слабые альтернативы текущему решению, особенно если вы уменьшаете эту вероятность с течением времени.

В качестве альтернативы, вы можете попробовать простое восхождение на холм с нескольких случайных запусков - никогда не принимайте более плохие альтернативы, что означает, что вы будете чаще зависать в локальных максимумах, но многократно запускать алгоритм с разных запусков.

Когда вы проверяете это, вы обычно знаете правильный ответ для своей тестовой задачи.Хорошей идеей будет сравнить значение правдоподобия в правильном ответе с теми, которые придумывает ваш алгоритм, на тот случай, если слабость заключается в формуле правдоподобия, и в этом случае ваш алгоритм найдет неправильные ответы, которые кажутся более правдоподобными, чемправильный.

2 голосов
/ 15 сентября 2011

Из описания в статье ваша реализация кажется правильной (часть, которую вы помечаете как «что делать здесь», действительно не должна быть ничем).

Если у вас есть проблемы с локальными максимумами (что-то в статьеутверждает, что следует избегать броска монеты), убедитесь, что ваши реализации 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; 
    }
}
...