Цепи Маркова в Telegram-боте

в 15:30, , рубрики: искусственный интеллект, телграмм-бот, цепи маркова

Рано или поздно я должен был начать писать статьи для хабра.

Привет, меня зовут Александр и я жёсткая самоучка в области искусственного интеллекта (ИИ). 5 лет назад я задался целью создать сильный искусственный интеллект (СИИ).

Наглядный пример цепей Маркова
Наглядный пример цепей Маркова

Думаю, стоит начать с того, как я создал бота для Телеграма с цепями Маркова.

Это не гайд, но step to step имеется. По большей части это моя маленькая история о том, как я прочитал 10 статей и решил написать своего первого серьезного чат-бота. Все ссылки внизу.

Воспринимаем это как научпок.

А зачем?

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

Почему я решил это засунуть это в телеграм бота? А потому что могу!

Что такое Цепи Маркова

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

Как это работает?

Относительно просто. Для примера возьмем скороговорку. Это станет нашим корпусом, на основе которой, будет генерироваться наш будущий текст. Текст состоит из 19 слов, уникальны из них 8, это звенья.

Примерчик

Примерчик

Алгоритм подсчитывает каждые слова для составления будущих пар и создает словарь

Словарь

Словарь

Далее создается сама цепочка, где учитывается все предыдущие слова. Цифрами указаны количество путей. Мы можем от сюда увидеть все возможные пути и итоговый вариант. На этом этапе алгоритм создает массив со всеми цепочками.

Составление цепочки Маркова
Составление цепочки Маркова

Создание алгоритма

Весь код брал тут, но я его видоизменил, чтобы можно было удобно использовать в будущем. Пишу на python. Создадим файл ChainMarkov.py и напишем туда следующий код:

import numpy as np


def ChainMarkovForFiles(text):
    text = open(text, encoding='utf8').read()
    n_words = 100
    corpus = text.split()

Начну я с импорта библиотеки numpy, потому что она отлично работает с массивами. Создадим функцию и добавим стартовые переменные

  • numpy - библиотека для работы с массивами (необходимо скачать)

  • ChainMarkovForFiles - функция которая, читает файл и возвращает нам сгенерированный текст

  • text - переменная, которая будет на вход получать текстовый файлик

  • n_words - это переменная, которая хранит в себе максимальное количество слов в итоговом тексте. Оно может быть любым, но самое лучшие 100.

  • corpus - это список со всеми словами из текста

Дальше создадим генератор пар make_pairs. Генератор лучше работает, чем функция, ибо yield не хранит никаких значений. Функция генерирует, забывает, и идет дальше.

def make_pairs(corpus):
    for i in range(len(corpus) - 1):
        yield corpus[i], corpus[i + 1]

pairs = make_pairs(corpus)
word_dict = {}
  • pairs - переменная, которая хранит в себе все пары слов

  • word_dict - пустой словарь для начала

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

for word_1, word_2 in pairs:
  if word_1 in word_dict.keys():
      word_dict[word_1].append(word_2)
  else:
      word_dict[word_1] = [word_2]

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

first_word = np.random.choice(corpus)

while first_word.islower():
  first_word = np.random.choice(corpus)

first_word - переменная, которая хранит в себе первое слово

Далее мы должны составить наше звенья и вывести их.

chain = [first_word]

for i in range(n_words):
  chain.append(np.random.choice(word_dict[chain[-1]]))

return ' '.join(chain)
  • chain - переменная для звена, где хранится наше первое слово, переделанное в звено

На самом деле это все, уже сейчас можно ее запустить просто вызвав функцию и дать какой-либо txt файлик. Вот весь код:

import numpy as np


def ChainMarkovForFiles(text):
    text = open(text, encoding='utf8').read()
    n_words = 100
    corpus = text.split()

    def make_pairs(corpus):
        for i in range(len(corpus) - 1):
            yield corpus[i], corpus[i + 1]

    pairs = make_pairs(corpus)
    word_dict = {}

    for word_1, word_2 in pairs:
        if word_1 in word_dict.keys():
            word_dict[word_1].append(word_2)
        else:
            word_dict[word_1] = [word_2]

    first_word = np.random.choice(corpus)

    while first_word.islower():
        first_word = np.random.choice(corpus)

    chain = [first_word]

    for i in range(n_words):
        chain.append(np.random.choice(word_dict[chain[-1]]))

    return ' '.join(chain)

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

def ChainMarkovForText(input_text):
    n_words = 100
    corpus = input_text.split()

    def make_pairs(corpus):
        for i in range(len(corpus) - 1):
            yield corpus[i], corpus[i + 1]

    pairs = make_pairs(corpus)
    word_dict = {}

    for word_1, word_2 in pairs:
        if word_1 in word_dict.keys():
            word_dict[word_1].append(word_2)
        else:
            word_dict[word_1] = [word_2]

    first_word = np.random.choice(corpus)

    while first_word.islower():
        first_word = np.random.choice(corpus)

    chain = [first_word]

    for i in range(n_words):
        last_word = chain[-1]
        if last_word in word_dict:  # проверяем, есть ли текущее слово в словаре
            next_word = np.random.choice(word_dict[last_word])
            chain.append(next_word)
        else:
            break  # если слов больше нет, выходим из цикла

    return ' '.join(chain)

Разработка бота

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

Разрабатывать бота я буду с помощью библиотеки telebot, ее тоже нужно скачать.

Создаем файл main.py и пишем следующий код:

import telebot
import os
import ChainMarkov

bot = telebot.TeleBot('ВАШ_ТОКЕН')
  • os - системная библиотека, которая нужна для установки файла

  • ChainMarkov - это наш предыдущий файл, который мы с вами до этого создали

  • bot - переменная, где будет хранится наш токен. Вставьте его туда

'НАШ_ТОКЕН'

'НАШ_ТОКЕН'

Далее напишем декоратор, который обрабатывает команду /start и выводит что то типа: привет, жду от тебя сообщение или файлик

@bot.message_handler(commands=['start'])
def main(message):
    bot.send_message(message.chat.id, 'Привет, жду от тебя сообщение или txt файлик, '
                                      'на основе которого я тебе вышлю бездумный текст, '
                                      'используя Цепи Маркова')

Создадим еще декоратор на команду /help ради приличия

@bot.message_handler(commands=['help'])
def main(message):
    bot.send_message(message.chat.id, 'Итак, просто напоминаю, что я создан для того, '
                                      'чтобы придумывать бессмысленный текст. Я жду от тебя любое сообщение '
                                      'от 100 слов до 4096 символов или же обычный txt файлик')

Сделаем проверку на тип сообщения, чтобы на все типы сообщений, кроме текста и файла

@bot.message_handler(content_types=['sticker', 'photo', 'animation', 'video', 'image', 'voice'])
def check(message):
    bot.send_message(message.chat.id, 'Я хочу видеть настоящий текст от 100 слов до 4096 символов!')

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

@bot.message_handler(content_types=['text'])
def text_processing(message):
    try:
        word_count = len(message.text.split())

        if word_count <= 100:
            bot.send_message(message.chat.id, 'Очень мало слов, нужно больше. Давайте от 100 слов')
        else:
            generated_text = ChainMarkov.ChainMarkovForText(message.text)
            bot.send_message(message.chat.id, f'Ваш сгенерированный текст:nn{generated_text}')
    except:
        bot.send_message(message.chat.id, 'В общем, я пришел к выводу, что то пошло не так')
  • word_count - счетчик слов из сообщения пользователя

  • generated_text - наш сгенерированный текст, который мы в итоге и отправим пользователю

С функцией для обработки файлов будет немного сложнее. Нам нужно будет вытащить файлик, установить его, сгенерировать текст и удалить файл. Сделал проверку на то, чтобы был точно txt файлик.

@bot.message_handler(content_types=['document'])
def file_processing(message):
    try:
        file_info = bot.get_file(message.document.file_id)

        if message.document.file_name.endswith('.txt'):
            downloaded_file = bot.download_file(file_info.file_path)

            with open('temp.txt', 'wb') as new_file:
                new_file.write(downloaded_file)

            generated_text = ChainMarkov.ChainMarkovForFiles("temp.txt")
            bot.send_message(message.chat.id, f'Ваш сгенерированный текст:nn{generated_text}')

            # Удаляем временный файл
            os.remove('temp.txt')
        else:
            bot.send_message(message.chat.id, 'Это не txt файлик')
    except:
        bot.send_message(message.chat.id, 'В общем, я пришел к выводу, что то пошло не так')
  • file_info - переменная с файлом пользователя

  • downloaded_file - скаченный файл

Небольшой код для того, чтобы бот не выкключался

bot.infinity_polling()

Итоговый код

import telebot
import os
import ChainMarkov

bot = telebot.TeleBot('7539804440:AAHNk3lZDvXnUcG9Me3LuLwNlnl2av9YXYM')


@bot.message_handler(content_types=['sticker', 'photo', 'animation', 'video', 'image', 'voice'])
def check(message):
    bot.send_message(message.chat.id, 'Я хочу видеть настоящий текст от 100 слов до 4096 символов!')


@bot.message_handler(commands=['start'])
def main(message):
    bot.send_message(message.chat.id, 'Привет, жду от тебя сообщение или txt файлик, '
                                      'на основе которого я тебе вышлю бездумный текст, '
                                      'используя Цепи Маркова')


@bot.message_handler(commands=['help'])
def main(message):
    bot.send_message(message.chat.id, 'Итак, просто напоминаю, что я создан для того, '
                                      'чтобы придумывать бессмысленный текст. Я жду от тебя любое сообщение '
                                      'от 100 слов до 4096 символов или же обычный txt файлик')


@bot.message_handler(content_types=['text'])
def text_processing(message):
    try:
        word_count = len(message.text.split())

        if word_count <= 100:
            bot.send_message(message.chat.id, 'Очень мало слов, нужно больше. Давайте от 100 слов')
        else:
            generated_text = ChainMarkov.ChainMarkovForText(message.text)
            bot.send_message(message.chat.id, f'Ваш сгенерированный текст:nn{generated_text}')
    except:
        bot.send_message(message.chat.id, 'В общем, я пришел к выводу, что то пошло не так')


@bot.message_handler(content_types=['document'])
def file_processing(message):
    try:
        file_info = bot.get_file(message.document.file_id)

        if message.document.file_name.endswith('.txt'):
            downloaded_file = bot.download_file(file_info.file_path)

            with open('temp.txt', 'wb') as new_file:
                new_file.write(downloaded_file)

            generated_text = ChainMarkov.ChainMarkovForFiles("temp.txt")
            bot.send_message(message.chat.id, f'Ваш сгенерированный текст:nn{generated_text}')

            os.remove('temp.txt')
        else:
            bot.send_message(message.chat.id, 'Это не txt файлик')
    except:
        bot.send_message(message.chat.id, 'В общем, я пришел к выводу, что то пошло не так')


bot.infinity_polling()

Заключение

На сегодня у меня все. Мы с вами познакомились и я постарался рассказать и показать, что-то интересное. Надеюсь на этом мы не закончим.

Не знаю как тут с саморекламой, но вот мой тг канал
А это список литературы который я использовал, там получилось 9 статей.

P.S.

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

Автор: Fech

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js