Если вы просматриваете Reddit, скорее всего, вы слышали о / r / SubredditSimulator.

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

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

Вы взволнованы? И я тоже; давай начнем.

1. Цепи Маркова.

SubredditSimulator прекрасно демонстрирует невероятный механизм, который существует уже довольно давно: цепи Маркова.

Как вы, наверное, (не) знаете, я все о процедурной генерации данных. Цепи Маркова представляют для меня большой интерес, потому что они являются одним из более простых и в то же время наиболее эффективных инструментов, когда дело доходит до создания полусвязного текста из чистого небытия (читайте: умеренно обширный набор данных) . И, помимо нейронных сетей, я бы сказал, что это лучшее, что у нас есть на данный момент, несмотря на простоту.

Цепи Маркова могут «генерировать» слово B из слова A, тогда и только тогда, когда B следует за A хотя бы один раз во время тренировки. Кроме того, B выбирается из списка кандидатов (read: слова, которые следовали за A во время обучения), который сортируется по вхождению и, следовательно, по вероятности. Затем добавляется немного случайности, чтобы немного оживить ситуацию и (попытаться) предотвратить петли, но в основном это решение проблемы отсутствия достаточно большого набора данных.

Итак, первая деталь реализации цепей Маркова - это обучение. Корпус текста должен войти в наш черный ящик и появиться горизонтальное дерево, где каждое слово представляет собой ветвь. Листья представлены всеми словами, относящимися к определенной ветви, и их «размер» зависит от их относительной частоты. Я нарисовал красивую картину? Я надеюсь, что это так.

2. Обучение

Поскольку мы говорим о деревьях и об обучении, давайте поговорим о Python:

import re
class MarkovChain:
    def __init__(self):
        self.tree = dict()
    def train(self, text, factor=1):
        pass

Ладно, пока ничего удивительного. Наше дерево будет хранилищем ключей и значений. Но как насчет функции train (), а как насчет factor? Вскоре мы увидим, где в игру вступает коэффициент, а пока подумайте о нем как о множителе. Его включение позволяет нашей цепочке усерднее тренироваться с одним текстом и меньше - с другим. Но не только это; когда factor отрицательный, все отношения для каждой пары слов в корпусе будут ослаблены! Это позволяет нам тренироваться более динамично и, в конце концов, формировать уклон в сторону определенных характеристик (например: более длинные слова, больше согласных, меньше аллитераций, рифмования и т. Д.).

Вот полностью аннотированная функция train ():

"[КОД]"

Идея здесь проста: для каждой пары слов, содержащихся в корпусе текста, усилить связь этой пары постоянным коэффициентом (который находится в диапазоне [-1; 1]).

По окончании процесса на нашем дереве появятся новые ветви и листья.

3. Пакетная обработка и сериализация

Вы не поверите, но мы почти у цели. Я имею в виду, на вершине айсберга. Теперь, когда нашу цепочку можно обучить некоторому тексту, давайте напишем несколько помощников для обучения всем файлам в каталоге, а также для сохранения и загрузки состояния нашего генератора с диска. Нам понадобятся glob и pickle.

"[КОД]"

Легче сделать, чем сказано! Это будет полезно, потому что цепи Маркова имеют тенденцию имитировать стиль их набора данных. Здесь, безусловно, необходима способность тренировать и сохранять состояние по желанию.

4. Генерация текста!

А вот и самое интересное. Как только цепь обучена, она может начать свою работу в качестве безумного болтающего робота.

Сначала [КОД], потом пояснения.

Опять же, это просто: вы начинаете с заданного слова (или выбираете случайную ветвь) и сортируете его листья по частоте встречаемости. Некоторая случайность добавлена ​​для обеспечения разнообразия, но моя реализация допускает гибкость даже в этом случае: если вы не хотите случайности, просто передайте lambda x: x в rand вместо значения по умолчанию, и вы получите полностью детерминированное поколение.

Сама функция generate () является генератором Pythonic: она непрерывно выдает значения, вместо того, чтобы строить и затем возвращать весь список. Атрибут max_len предназначен для предотвращения потенциально бесконечного запуска процесса, но никто не мешает вам оставить его на 0 и генерировать слова до тех пор, пока не будет найден тупик (что может быть невозможно).

Затем у нас есть функция generate_formatted (), которая аккуратно обертывает generate () для получения более красивого текста. Он поддерживает перенос слов, разрывы строк и использование заглавных букв. Ничего особенного, но это будет удобно.

Ну как насчет того. Мы готовы проверить наш код! Я обучу его, используя некоторые из моих недавних постов на Reddit.

# Builds a formatted string from the generator
def gen(m):
    return ''.join([w for w in m.generate_formatted(word_wrap=60, soft_wrap=True, start_with=None, max_len=50, verbose=True)])
mkv = MarkovChain()
mkv.bulk_train('E:\\Text Files\\MkvCmd Training Data\\*.txt', verbose=True)
print(gen(mkv))

Выход:

...
Successfully trained on 1 files.
Generating a sentence of max. 50 words, starting with "early":
Early on, if you can be a mac address, but i think that you 
can tell you can be so well (\p{l} matches any compassion.
It’s a lot of the only works with a few downsides to be a drug 
because, when you can be a lot of the same
Generating a sentence of max. 50 words, starting with "bring":
Bring users away from the same be too sure.
Your account to be a few special characters.
You don't know where you don't know where you don't know about 
them.
Even beats c++'s speed (which is a few years, but i think that 
you don't have to be a few

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

Эй, по крайней мере, работает. Теперь мы на шаг ближе к истине.

5. Потяните за шнурки, чтобы отрегулировать веса.

И вот новинка: применение фитнес-функции к нашей цепи Маркова!

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

В нашем случае кандидатом будет пара слов. Ветвь и лист. Мы собираемся оценить эти отношения и укрепить или ослабить их в зависимости от нескольких произвольных критериев.

"[КОД]"

Хорошо, теперь у нас есть функция adjust_weights (). Он принимает параметр max_len и функцию f, которая принимает на вход два слова и возвращает значение в диапазоне [0; 1].

Теоретически, чем выше max_len, тем больше времени потребуется для адаптации цепи. Однако, если max_len слишком мало, такие адаптации могут быть очень разными и не достичь цели. Практически? Установка его на два, минимально возможное значение, работает как шарм. Это может быть потому, что мой набор данных настолько мал.

У нас также есть функция bulk_adjust_weights (), которую мы и будем использовать. Он не только применяет к модели одну функцию приспособленности, но и применяет сразу несколько функций приспособленности для заданного количества итераций. В подробном режиме он также выводит красивую полосу выполнения, что приятно.

Да, и, кстати, класс MarkovChain готов. Не стесняйтесь проверить это на Github.

Пришло время определить некоторые фитнес-функции:

"[КОД]"

Это просто примеры; Единственным ограничением является ваше воображение! Посмотрим, как их применять:

"[КОД]"

И вот новый, улучшенный результат:

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

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

Вот, например, путем микширования и сопоставления мне удалось создать что-то, напоминающее текст поп-песни:

Основные продукты из-за других наркотиков доставляли вам удовольствие.
И не подходят в свою очередь.
В свою очередь.
В свою очередь.
В циклах, если это необходимо или подсознательно Покупка кока-колы
и регулирование уровня серотонина.
Не подходят.
В свою очередь.
Несмотря на обиды.

Но я мог бы использовать полную пунктуацию и создать что-то вроде:

Альфа: мы по-прежнему занимаемся подменой IP-адресов, которая искажает ваше суждение
говорит о том, что вы можете замедлить работу вашего врача до лучшего программиста?
Кто может это победить.
Я на улице, я буду будь слишком уверен.
Твои братья и сестры привыкли.
Довольны этим.
Иногда ты справляешься.
Меня заставили усложнить отслеживание.

Или я могу сказать, что мне нравятся только короткие слова и гласные:

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

Или очень длинные слова с несколькими гласными и многими согласными:

Концентрация.
На их лице.
Они были встроены в реальность, несмотря на то, что они так раздражают.
Людям, конечно, не нравится, mdd в течение 10 лет.
Я может быть.
Настоящие проблемы, ввод перенаправлен, и они не будут
проходить, но вещи должны были на хроме вы говорить все, что они нажимают,
даже

Но хватит. Я оставлю вам отрывок из этой статьи:

Мы только это; когда достаточно большой набор данных.
Корпус моих последних сообщений на Reddit.
# создает красивую картину?

Это почти похоже на хайку, или, возможно, я начинаю видеть закономерности в безумии хаоса.

Спасибо за чтение. Я надеюсь, что вы нашли мою рецензию полезной и / или интересной. Увидимся, как только процедурная генерация предложит новый вызов!

ПОЛНЫЙ ПРОЕКТ: https://github.com/G3Kappa/Adjustable-Markov-Chains