Привет всем. В этом посте я хочу описать свой путь по созданию телеграм-бота, который помогает мне (и не только) практиковать разговорный английский. Это будет компиляция 2-х моих статей об этом на хабре, плюс добавлю кое-какие размышления.
Совмещаем gpt-3.5-turbo и whisper от openai, гугловскую text-to-speech, и telegram.
Предисловие
Основная мотивация - хочу заговорить на английском. Мой уровень B1, почти B2, но преимущественно в чтении и письме. Уроки с учителем стоят довольно дорого, ходить в английские клубы я стесняюсь, да и вообще коммуницировать с людьми не люблю. Поэтому решил, что для начала я заставлю себя говорить, хотя бы плохо, а потом отшлифую уровень с живыми людьми.
Еще одним мотиватором было желание сделать свой личный проект с telegram-ботами и серверами. Идеи появлялись и раньше, но тратить деньги на сервер с бесполезным приложением не хотелось. А тут вроде и полезно.
Постановка задачи
Для начала нужно выяснить, а может ли вообще chatGPT помочь мне с английским. Мне нужна была не просто болталка, а коррекция моих фраз. Естественно, языковая модель прекрасно поняла правила игры:
В ходе диалога модель что-то поправляла, что-то пропускала. Игнорирование моих ошибок было особенно заметно на длинных сообщениях.
Но печатать текст - это не так сложно, как разговаривать. При печати у меня больше времени на подумать и исправить. Если я хочу большего реализма, то мне нужно говорить - я зачитываю свой ответ в микрофон, модель распознавания речи (ASR) транскрибирует мою речь в текст, который и будет отправляться языковой модели. Быстрый гуглеж выдал уже готовые поделки, вроде voiceGPT. Но это приложение оказалось слишком неудобным для моих задач: запись голоса обрывалась при первом же распознавании части фразы, а постоянные проверки браузера при подключении к аккаунту openai раздражали. Я решил, что проще будет использовать любую хорошую модель ASR, тем более, что я уже запланировал реализовать передачу сообщений через отдельный сервер, чтобы общаться с ботом в телеграме, удобно развалившись вечером на диване.
Проверка моделей ASR
Забегая вперед, скажу, что в итоге я использую модель whisper от того же openai через API. Но изначально я планировал загружать модель на сервере (тем более, что они работают на CPU), поэтому я пошел на hugginface и выбрал для себя несколько моделей для сравнения. Ниже представляю расшифровку текста задания для chatGPT, которую я надиктовал в диктофон:
facebook/wav2vec2-base-960h
UNCLE SUGGEST A TOPIC FAL CONVERSATION AND YOU WILL ASK AQUATION ON IT THEN CHECK MY AUN SECAL GROMETICAL ERRORS AND OFFER THE CORRECT OPTION THEN YOU ASK THE NEXT COUATION LET'S GO
facebook/wav2vec2-large-960h-lv60-self
I WILL SUGGEST A TOPIC FOR CONVERSATION AND YOU WILL ASK A QUESTION ON IT THEN CHECK MY ANSWER FOR GRAMMETICAL ERRORS AND OFFER THE CORRECT OPTION THEN YOU ASK THE NEXT QUESTION LET'S GO
jonatasgrosman/wav2vec2-large-xlsr-53-english
i will suggest a topic for conversation and you will ask a quetion on it then check my alswey for gramatical errors and offer the correct option then you ask the next question let's go
openai/whisper-medium.en
I will suggest a topic for conversation and you will ask a question on it. Then check my answer for grammatical errors and offer the correct option. Then you ask the next question. Let's go!
И снова openai оказался лучше всех, даже умеет поддерживать регистр и знаки препинания. Вообще, можно было бы и использовать последнюю модель, но на слабом процессоре сервера (к тому же единственном) расшифровка сообщения занимала примерно минуту, из-за чего диалог был похож на типичный диалог с приятелем в вк, который постоянно пропадает. В конце концов, 0.006 доллара за минуту расшифровки аудиосообщения не так уж много.
Код (ретроспективно)
Код написан на python. До этого я не писал телеграм-ботов, поэтому выбрал, как мне теперь кажется, не самую удачную библиотеку - python-telegram-bot. Начиная с версии 12x довольно сильно поменялся интерфейс, а большая часть ответов на stackoverflow относится именно к старым версиям. Возможно, использование aiogram было бы проще, но к тому моменту, как я об этом подумал, было уже поздно.
Сам код довольно простой, прикладываю ссылку на первоначальную версию бота, еще когда я его писал только с одним заданием для chatgpt и с полной фильтрацией всех сообщений по моему user_id. Этот код можно склонировать и c минимальными доработками запустить локально и все будет работать, нужна только самая "мелочь" - создать API ключ в аккаунте openai и создать, собственно, самого бота в BotFather в телеграме (ну и выяснить свой user_id, если так же не хотите, чтобы к вам в бота стучались посторонние). По обоим пунктам есть множество описаний в интернете, не буду описывать это здесь.
Этот код я залил на сервер. С одной стороны, опять же, я купил не самый оптимальный вариант сервера, с учетом того, что все, что делает приложение - пересылает сообщения. Можно обойтись и виртуальной машиной на 512 Мб. С другой - теперь я не жалею, что купил полноценный сервер (хоть и слабый), т.к. в данный момент бот масштабирован на множество пользователей и у меня больше возможностей для расширения. По администрированию серверов есть очень полная статья, сам я в этом не особо разбираюсь.
Промежуточные результаты
На этом моменте я решил остановиться и поделиться наработками с общественностью.
В результате, несколько человек написали мне с вопросом: не хочу ли я сделать такого бота не приватным. Я немного подумал и решил, что хочу, поэтому следующие 2 недели я вечерами дописывал бота вместо практики английского языка. А чтобы разгрузить мозг, после сеансов программирования я смотрел форсажи, как раз хватило.
Новые фичи
Логично было бы начать масштабирование бота с добавления учета пользователей, но мне подумалось, что несерьезно отдавать в народ продукт, который имеет в запасе только одно задание для языковой модели. Я вспомнил, чему учили меня зеленая сова и лингвистический лев, и начал добавлять активности.
Свободное сообщение. Если отправить любое сообщение без выбора из меню, оно направится языковой модели. По сути, это как обычный браузерный доступ к chatGPT, но я установил ограничение как на длину входящего сообщения, так и на длину генерации, так что сгенерировать в моем боте рассказ, дипломную работу или еще что‑нибудь длинное не выйдет.
Conversation. Тот самый основной режим. По нажатию кнопки пользователю предлагается выбрать тему для разговора, а языковой модели отправляется задание: задай вопрос на эту тему, проверь ответ на предмет грамматических ошибок и затем задай следующий вопрос. Здесь раскрывается вся мощь языковой модели — я устраивал себе технические интервью по python, а модель не только исправляла мои речевые ошибки, но и указывала на неточности в самих ответах и дополняла их.
Grammar. Здесь модель генерирует рандомные предложения на русском в любом времени и проверяет перевод пользователя на корректность. По ходу использования я понял, что когда‑нибудь потом стоить разделить задания и добавить генерацию предложений хотя бы в 3 основных временах: настоящем, прошедшем и будущем. Потому что чаще всего модель предлагает present simple, что не такое уж сложное испытание. Ко всему прочему наблюдается низкая вариативность предложений: не обходится ни одной тренировки без «Я люблю гулять по парку осенью» и «Кошка спит на окне».
Впрочем, по ходу общения я успешно просил бота давать более сложные преложения.
Хочу заметить, что я общаюсь с ботом в основном голосом, а распознает речь тоже языковая модель, которая в силу своей вероятностной природы так же исправляет ошибки, в основном окончания и артикли. Поэтому я всегда внимательно смотрю на транскрипцию своих же сообщений. Иногда модель распознавания голоса может сама даже перевести одно-два слова с русского на английский, если все предыдущее предложение на английском было произнесено корректно.
Listening. Оно же аудирование. Можно было бы попросить бота генерировать теперь фразы на английском и озвучивать их, но предыдущее задание показало, что нет смысла так делать. Поэтому я решил взять англоязычные книги, нарезать их по предложениям, а после по запросу брать случайный отрывок из списка и озвучивать его с помощью какой‑нибудь text‑to‑speech модели. Удивительно, но openai умеет только speech‑to‑text, так что пришлось регистрировать проект в гугл клауде и отправлять отрывки туда.
Какие выбрать книги? Я взял ровно те же англоязычные книги, что стоят у меня на полке.
Далее немного NLP-шной рутины, можно пропустить. С сегментацией на предложения пришлось немного повозиться: хотя пакет nltk из коробки неплохо сегментирует текст, все же во фразах остается много мусора. Для выделения диалогов, прямой речи и прочих экспрессий использовались разные символы ("“”`’‘) и менялись от книги к книге. Плюс 3 вида дефисов и тире. При этом нельзя просто выкинуть пунктуацию, иначе при озвучке фраза сольется во что‑то монотонное и неразборчивое.
А еще те, кто читал «Оно» знают, что там главный герой заикается. Волевым решением я убрал все предложения с заиканиями из всех текстов, т.к. это нельзя ни озвучить, ни проверить нормально. Билл Денбро стал еще более молчаливым. А так же я оставил только средней длины фразы. А еще я не фильтровал отрывки по содержанию, так что это упражнение только 18+.
При проверке ответа пользователя и из исходной фразы, и из ответа уже убираются все не буквенные символы, фразы приводятся к нижнему регистру и матчатся. Понимая, что я сам многих слов не знаю, в этом режиме малодушно добавил на клавитару бота кнопки skip — пропустить, и show — показать фразу.
Vocabulary. Классическая тренировка слово — перевод. И снова — можно генерировать слова языковой моделью, а можно взять их из англо‑русских и русско‑английских словарей. Я нашел хорошо структурированный словарь В. К. Мюллера редакции 2013 года на 86 тысяч слов. Столько слов не знал даже Шекспир. Поэтому я взял те же книги, положил в Counter лемматизированные слова из каждой книги, отфильтровал по частоте, чтобы избавиться от общеупотребимых простых слов и имен, и нашел их и их переводы в словаре Мюллера.
Если судить по себе, то в англо‑русском словаре в моем мозге больше слов, чем в русско‑английском. Я могу без особых проблем читать английский текст и все понимать, но перевести аналогичный русскоязычный текст на английский уже проблема. Странно, что не во всех тренажерах, которые я пробовал, есть именно русско‑английские словарные пары. Поэтому я сделал их в своем боте. Для этого я прошелся в двойном цикле по каждому отобранному слову в англо‑русском словаре и по каждому варианту перевода слова нашел перевод в русско‑английском словаре.
Пользователь так же выбирает книгу, выбирает язык словаря, из которого будут выбираться случайные слова, и пытается перевести их на другой язык. Проверка — наличие перевода в списке возможных переводов для этого слова.
Иногда возможные переводы довольно неожиданные. Возможно, лучшим решением было бы найти русскоязычные тексты этих книг и аналогично отобрать слова из них.
В этом режиме я добавил кнопку пропуска слова и кое‑какой полезный функционал: при угадывании слова, оно кладется по ключу пользователя во внешнюю БД с меткой mistake = 0, а при ошибке или пропуске — mistake = 1. А к выбору книг я добавил опцию «My vocab», при выборе которой можно повторить уже изученные слова, где с вероятностью 0.3 будет даваться случайное ранее угаданное слово, и с вероятностью, соответственно, 0.7 — не угаданное.
Из не вошедшего. Хотелось сделать еще задание на понимание текста — аудирование или текстовое. Я просил языковую модель сгенерировать небольшой рассказ и после сгенерировать вопросы по этому рассказу с возможными ответами да/нет. И модель успешно это делала, но вопросы полностью повторяли предложения рассказа, что лишало задание всякого смысла. Например, в рассказе было: «Однажды, маленькая девочка темной ночью пошла за яблоками». Вопрос модели: «Правда ли, что маленькая девочка однажды ночью пошла за яблоками?». И пока мне не удалось заставить модель задавать чуть менее подробные вопросы, хотя бы такие: «Правда ли, что девочка пошла за яблоками?» или «Правда ли, что девочка ушла днем?».
Недостатки
Добавив весь этот функционал, настроив логику поведения при нажатии на кнопки, настроив БД хранения пользовательских данных, я наконец-то снял фильтрацию сообщений по моему айдишнику и пустил бота в мир. Оказалось, что название бота привлекает иностранцев, которым бот не очень-то и нужен.
Есть недостаток, о котором я знал с самого начала. Самый простой способ запуска телеграм бота - т.н. polling, когда бот периодически опрашивает сервера телеграма на предмет новых сообщений от пользователя и только тогда их получает и обрабатывает. Это может приводить к задержкам и вообще плохая практика. Хорошая практика - webhook - когда сообщения шлются напрямую в приложение на удаленном сервере. Но я так и не настроил webhook, потому что на самописный сертификат телеграм ругается. Возможно, в будущем я разберусь с этой проблемой. А пока работоспособность бота зависит от серверов самого телеграма.
А еще работоспособность зависит от openai. Бывают периоды высокой нагрузки, когда невозможно в адекватные сроки дождаться ответа от языковой модели (и это еще при использовании gpt-3.5. gpt-4 вообще сильно ограничена по нагрузке). Но здесь я уже ни на что не могу повлиять.
Каждая генерация — это недетерминированный процесс, и на один и тот же запрос может прилетать разный ответ. Иногда это приводит к замыканию бота: он не понимает задание, которе ранее успешно понимал, теряет в креативности, а иногда в процессе диалога вместо следующего вопроса или фразы присылает сообщение с благодарностью за беседу.
Выводы
Стоит заметить, что нельзя полностью доверять языковой модели свое обучение. Если уровень владения английским ниже A2 (примерно), то не стоит пользоваться этим ботом. Бот может не замечать ошибок, и чем длиннее ответ, тем вероятнее он их пропустит. Нужно, как минимум, знать правила построения предложений и времена, чтобы критично подходить к своим же ответам. Я рассматриваю этого бота как способ снять барьер при попытке заговорить на английском, но сомневаюсь, что бот может заменить живое общение.
Надеюсь, что теперь бот будет полезен не только мне.