Алгоритм вычисления правдоподобия функции / метод Монте-Карло
Я пишу программу, которая пытается повторить алгоритм, описанный в начале этой статьи,
Http://www-stat.stanford.edu/~cgates / PERSI / papers / MCMCRev. pdf
F-функция от char к char. Предположим, что 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 ?
}
}
}
Мой вопрос таков: в принципе, похоже ли это на оптимальный подход к реализации этого алгоритма. Похоже, что я могу застрять в некоторых локальных максимумах / локальных минимумах, несмотря на реализацию этого метода.
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;
}
3 ответа:
Ориентировочно, codereview.stackexchange.com может быть " лучшим форумом для этого".
Тем не менее, я сделаю быстрый удар по нему:
- на первый взгляд, приведенный фрагмент являетсяправильной реализацией алгоритма.
- будет ли алгоритм "застревать" в локальных минимумах-это вопрос, относящийся к алгоритму , а не к реализации . (см. обсуждение ниже)
- ваш поиск "оптимального подхода " кажется быть направленным на изменения в алгоритме (отклонение от исходного алгоритма), а не на изменения в реализации (чтобы сделать ее быстрее и/или устранить некоторые возможные недостатки). Для рассмотрения алгоритма см. обсуждение ниже; для обсуждения реализации рассмотрим следующее:
- убедитесь, что метод Flip() справедлив
- убедитесь, что ComputePl() корректен: некоторые ошибки часто возникают из-за проблем с арифметической точностью в функция ценности.
- обеспечить справедливость (равновероятность) с помощью метода Transpose ().
- повышение производительности, скорее всего, будет происходить за счет оптимизации в методе ComputePl () (не показан), а не в основном цикле.
Обсуждение алгоритма per-se и его применимости к различным задачам.
Другим способом обеспечения применимости алгоритма является обеспечение лучшей оценки правдоподобности заданных функций. Вероятные объяснения относительного успеха и общности алгоритма являются
В двух словах алгоритм представляет собой управляемый стохастический поиск, при котором [огромное] пространство решений отбирается двумя случайные устройства: метод транспонирования (), который изменяет (очень незначительно каждый раз) текущую функцию-кандидата и метод флипа (), который решает, должно ли [локально] субоптимальное решение выжить. Поиск осуществляется с помощью целевой функции ComputePl (), которая сама основана на матрице перехода первого порядка в некотором справочном корпусе.
В этом контексте локальных минимумов "смоляных ям" можно избежать, увеличив вероятность выбора "неоптимальных" функций: вместо fair 50-50 Flip (), возможно, попытается с вероятностью сохранения "неоптимальных" решений 66% или даже 75%. Этот подход обычно увеличивает число поколений, необходимых для достижения оптимального решения, но, как уже было сказано, может избежать застревания в локальных минимумах.Распределение переходов первого порядка в английском языке не очень однородно (и поэтому довольно хорошо моделирует правдоподобие данной функции, вознаграждая ее за совпадения и наказывая за несовпадения). Этот вид многомерной статистики более устойчив к отклонению от эталонного корпуса, чем, скажем, распределение символов "порядка 0" в данном языке/корпусе.
Поэтому для улучшения применимости алгоритма к данной задаче убедитесь, что используемая матрица распределения максимально точно соответствует языку и предметной области базового текста.- распределение переходов первого порядка при специфичные для языка, как правило, схожи между различными языками и/или в контексте жаргонных словарей [основанных на указанных языках]. В случае с жаргоном коротких рук, когда такие буквы, как гласные, обычно опущены, все действительно ломается.
Из описания в статье ваша реализация кажется правильной (часть, которую вы отмечаете как "что здесь делать", действительно не должна быть ничем).
Если у вас возникли проблемы с локальными максимумами (то, чего, как утверждается в статье, бросок монеты должен избегать), убедитесь, что ваши имплементации Initial, Transpose, ComputePl и 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; } }
Этот алгоритм, по-видимому, связан с http://en.wikipedia.org/wiki/Simulated_annealing если это так, то поведению может помочь изменение вероятности, с которой вы принимаете более плохие альтернативы текущему решению, особенно если вы уменьшаете эту вероятность с течением времени.
В качестве альтернативы вы можете попробовать простое восхождение на холм из нескольких случайных стартов-никогда не принимайте более бедных альтернатив, что означает, что вы будете чаще застревать в локальных максимумах, но многократно запускайте алгоритм с разных стартов.
Когда вы проверяете это, вы обычно знаете правильный ответ для вашей тестовой задачи. Это хорошая идея, чтобы сравнить значение правдоподобия при правильном ответе с теми, которые ваш алгоритм придумывает, просто в случае, если слабость находится в Формуле правдоподобия, в этом случае ваш алгоритм придумает неправильные ответы, которые кажутся более правдоподобными, чем правильный.