Нейросеть мне в помощь или как я сделал телеграм бота, который умеет переводить песни

от автора

Однажды, когда я искал эффективное решение для преобразования речи в текст (транскрибации), чтобы применить его в своем проекте умной колонки, обнаружил интересное решение под названием Whisper от широко известной компании Open AI. К сожалению, Whisper не подошел для реализации в моем проекте по «аппаратным» причинам, но его функционал отпечатался в моей душе. Прошло время и меня посетила идея: «Почему бы не разработать телеграмм бота, куда бы пользователь мог отправлять аудиофайл, а в ответ получал текстовую расшифровку и перевод (песни) на родной язык». В этой статье я расскажу о реализации данной идеи и Whisper в этом проекте займет одну из ключевых функций.

Чем же меня так впечатлил Whisper?

Во первых, что такое Whisper?

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

Ну и само название модели «Whisper» переводится как шепот, что само по себе намекает о качестве распознавания речи.

Чтобы не быть голословным, давайте запустим базовый пример использования и посмотрим вывод:

import whisper model = whisper.load_model("base")  # load audio and pad/trim it to fit 30 seconds audio = whisper.load_audio("audio.mp3") audio = whisper.pad_or_trim(audio)  # make log-Mel spectrogram and move to the same device as the model mel = whisper.log_mel_spectrogram(audio).to(model.device)  # detect the spoken language _, probs = model.detect_language(mel) print(f"Detected language: {max(probs, key=probs.get)}")  # decode the audio options = whisper.DecodingOptions() result = whisper.decode(model, mel, options)  # print the recognized text print(result)

В результате выполнения скрипта мы получим следующий вывод в формате json:

{'text': ' Солнечное излучение (большой текст обрезал).',  'segments': [ {'id': 0, 'seek': 0, 'start': 0.0, 'end': 4.84, 'text': ' Солнечное излучение является одним из самых мощных источников энергии в Селенной.', 'tokens': [50364, 2933, 20470, 4310, 6126, 3943, 41497, 5627, 29755, 50096, 3943, 37241, 39218, 5783, 12410, 20483, 22122, 40804, 7347, 740, 2933, 21180, 5007, 13, 50606], 'temperature': 0.0, 'avg_logprob': -0.1954750242687407, 'compression_ratio': 2.0371621621621623, 'no_speech_prob': 0.0006262446404434741}, {'id': 1, 'seek': 0, 'start': 4.84, 'end': 8.4, 'text': ' Оно поступает на Землю в виде видимого и инфракрасного света,', 'tokens': [50606, 3688, 1234, 43829, 3310, 1470, 42604, 6578, 740, 12921, 38273, 2350, 1006, 6635, 3619, 481, 1272, 17184, 4699, 4155, 20513, 11, 50784], 'temperature': 0.0, 'avg_logprob': -0.1954750242687407, 'compression_ratio': 2.0371621621621623, 'no_speech_prob': 0.0006262446404434741}, {'id': 2, 'seek': 0, 'start': 8.4, 'end': 10.56, 'text': ' а также в виде космических лучей.', 'tokens': [50784, 2559, 16584, 740, 12921, 31839, 919, 38911, 15525, 2345, 13, 50892], 'temperature': 0.0, 'avg_logprob': -0.1954750242687407, 'compression_ratio': 2.0371621621621623, 'no_speech_prob': 0.0006262446404434741},  {'id': 3, 'seek': 0, 'start': 10.56, 'end': 15.8, 'text': ' Солнечное излучение имеет мощность примерно в квадратный метр поверхности Земли.', 'tokens': [50892, 2933, 20470, 4310, 6126, 3943, 41497, 5627, 33761, 39218, 9930, 37424, 740, 35350, 2601, 11157, 4441, 18791, 481, 44397, 13975, 42604, 1675, 13, 51154], 'temperature': 0.0, 'avg_logprob': -0.1954750242687407, 'compression_ratio': 2.0371621621621623, 'no_speech_prob': 0.0006262446404434741}, {'id': 4, 'seek': 0, 'start': 15.8, 'end': 19.44, 'text': ' Это достаточно для производства электроэнергии и других применений.', 'tokens': [51154, 6684, 28562, 5561, 28685, 12115, 31314, 9938, 7570, 489, 19480, 7347, 1006, 31211, 31806, 1008, 17271, 13, 51336], 'temperature': 0.0, 'avg_logprob': -0.1954750242687407, 'compression_ratio': 2.0371621621621623, 'no_speech_prob': 0.0006262446404434741} ], 'language': 'ru'} 

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

Улучшения приходят в процессе творчества

Изначально планировалось реализовать только текстовую расшифровку аудиофайла(песни), но потом мелькнула мысль: «Почему бы не реализовать перевод текста, отличного от родного языка? Ведь так можно узнать смысл любимой песни всего за пару минут.»
Для реализации этой идеи, я решил пойти простым путём: использовать API сервисов онлайн переводчиков. К сожалению, а может и к счастью, это была моя ошибка, ввиду ограничения запросов на данные API. Ну что ж, подумал я, меньше внешних сервисов — больше свободы, будем использовать локальные алгоритмы перевода на базе нейронных сетей.

Оффлайн перевод с помощью нейронных сетей

Трансформеры нам помогут! Не долго размышляя, решил использовать библиотеку Transformers, которая позволяет применять в своих проектах большое количество моделей, полный список можно посмотреть на сайте проекта.

Для оффлайн перевода будем использовать модели Helsinki-NLP от Группы исследований языковых технологий Хельсинкского университета.

Пример Python скрипта для оффлайн перевода:

from transformers import pipeline import torch  device = "cuda:0" if torch.cuda.is_available() else "cpu" torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32 # устанавливаем количество потоков для torch torch.set_num_threads(4) #Перевод с любого языка на английский translator   = pipeline("translation", model="Helsinki-NLP/opus-mt-mul-en") text         = "Какой-то текст на любом языке" translation  = translator(text) print(translation[0]['translation_text']) 

В примере указан мультиязычный перевод на английский. К сожалению, у Helsinki-NLP нет мультиязычных моделей с переводом на русский язык, поэтому воспользуемся трюком большинства генеративных ИИ и применим двойной перевод, то есть в нашем случае, с английского языка на русский, используя модель Helsinki-NLP/opus-mt-en-ru.

Ну что ж, давайте собирать бота

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

Главный скрипт, отвечающий за работу бота tg_bot.py

import neural_process import file_process import config from telegram import ForceReply, Update from telegram.ext import Application, CommandHandler, MessageHandler, filters, CallbackContext, ContextTypes   # Функция, которая вызывается при отправке файла async def handle_file(update: Update, context: CallbackContext):     audio = update.message.audio     print(audio.mime_type)     # Сохраняем аудиофайл локально и получаем путь сохранения     file = await context.bot.get_file(audio.file_id)     f_name = audio.file_name     local_file_path = file_process.save_audio_file(f_name, file.file_path)     # Отправляем сообщение о успешном сохранении аудиофайла     await update.message.reply_text(         f"Подождите немного, я в ускоренном темпе прослушаю аудиофайл {f_name} и отправлю вам текстовую "         f"расшифровку с переводом."     )     # проверяем файл на длительность     status = True     status_dur = True     try:         file_dur = file_process.file_duration_check(local_file_path)         if file_dur > 600:             status_dur = False     except Exception as e:         print(f"Возникла ошибка: {e}")         status_dur = False      # Получаем перевод      trance_text = 'в эту переменную сохраняется текст транскрибации с переводом'     if status_dur:         try:             start_time = time.time()             trance_text = neural_process.final_process(local_file_path, f_name)         except Exception as e:             print(f"Возникла ошибка: {e}")             status = False     else:         status = False         await update.message.reply_text(             f"Сожалею, но возникла ошибка обработки файла. Длительность файла не должна превышать десять минут."         )      if status:         # Сохраняем вывод в текстовый файл         tx_file_path = file_process.save_text_to_file(f_name, trance_text)         end_time = time.time()         process_time = round(end_time - start_time, 2)         await update.message.reply_text(             f"Перевод готов! Ловите файл с переводом. Затраченное время: {process_time} сек.  "         )         # Отправляем текстовый файл         with open(tx_file_path, "rb") as document:             await context.bot.send_document(chat_id=update.message.chat_id, document=document)      else:         file_process.delete_file(local_file_path)         if status_dur:             await update.message.reply_text(                 f"Сожалею, но возникла ошибка обработки файла. Пожалуйста, убедитесь что файл имеет правильный аудиоформат."             )  # Функция для команды /start async def start(update: Update, context: CallbackContext) -> None:     await update.message.reply_text(         'Привет! Добро пожаловать! Нравится песня, но ты не знаешь о чем она? Я могу перевести песню с любого языка '         'на русский за считанные секунды, просто скинь аудио файл в чат. ')   # Функция для команды / help async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:     """Send a message when the command /help is issued."""     user = update.effective_user     await update.message.reply_html(         rf"{user.mention_html()}, я могу перевести песню с любого языка на русский, просто скинь аудио файл в чат.",         reply_markup=ForceReply(selective=False),     )  # Запускаем бота def main():     # Токен  бота     application = Application.builder().token(config.tg_key).build()     # Регистрируем обработчик для команды /start     application.add_handler(CommandHandler("start", start))     application.add_handler(CommandHandler("help", help_command))     # Регистрируем обработчик для приема файлов     application.add_handler(MessageHandler(filters.AUDIO, handle_file))     # Запускаем бота     application.run_polling()     # Останавливаем бота при нажатии Ctrl+C     application.idle()   if __name__ == '__main__':     main() 

Скрипт для работы с файлами file_process.py

import requests import os from pydub.utils import mediainfo   def delete_file(file_path):     try:         os.remove(file_path)         print(f"Файл {file_path} успешно удален.")     except FileNotFoundError:         print(f"Файл {file_path} не найден.")     except Exception as e:         print(f"Произошла ошибка при удалении файла {file_path}: {e}")   def save_audio_file(file_name, file_link):     response = requests.get(file_link)     # Сохраняем аудиофайл локально для последующей обработки     sound_folder = 'sound'     local_file_path = f"{sound_folder}/{file_name}"      if not os.path.exists(sound_folder):         # Если папки нет, создаем её         os.makedirs(sound_folder)      with open(local_file_path, 'wb') as local_file:         local_file.write(response.content)     return local_file_path   def save_text_to_file(file_name, trance_text):     text_folder = 'output'     local_file_path = f"{text_folder}/{file_name.replace('.', '_') + '.txt'}"      if not os.path.exists(text_folder):         # Если папки нет, создаем её         os.makedirs(text_folder)     # Сохраняем вывод в текстовый файл     with open(local_file_path, "w", encoding="utf-8") as output_file:         output_file.write(trance_text)      return local_file_path   def file_duration_check(file_path):     audio_info = mediainfo(file_path)     duration = float(audio_info['duration'])     print(duration)     return duration 

Скрипт для работы с нейросетями neural_process.py

from transformers import pipeline import torch import whisper import file_process  device = "cuda:0" if torch.cuda.is_available() else "cpu" torch_dtype = torch.float16 if torch.cuda.is_available() else torch.float32 # устанавливаем количество потоков для torch torch.set_num_threads(4)   def sound_to_text(audios):     model = whisper.load_model("base")     # load audio and pad/trim it to fit 30 seconds     audio = whisper.load_audio(audios)     audio = whisper.pad_or_trim(audio)     # make log-Mel spectrogram and move to the same device as the model     mel = whisper.log_mel_spectrogram(audio).to(model.device)     # detect the spoken language     _, probs = model.detect_language(mel)     lang = max(probs, key=probs.get)     print(f"Detected language: {max(probs, key=probs.get)}")     # decode the audio     result = model.transcribe(audios, fp16=False, language=lang)     print(result)     return result['segments'], lang   def final_process(file, file_name):     print(f"Сохранен в директории: {file}")     raw, detected_lang = sound_to_text(file)     translator = pipeline("translation", model="Helsinki-NLP/opus-mt-mul-en")     translator2 = pipeline("translation", model="Helsinki-NLP/opus-mt-en-ru")     text = f"Перевод аудиофайла: {file_name} \n"     text += f"В файле используется {get_language_name(detected_lang)} язык. \n"     for segment in raw:         text += "-------------------- \n"         text += f"ID элемента: {segment['id']} Начало: {int(segment['start'])} --- Конец: {int(segment['end'])} \n"         text += f"Исходный текст:{segment['text']} \n"         if detected_lang == 'en':             text_en = segment['text']             translation2 = translator2(text_en)             text += f"Перевод: {translation2[0]['translation_text']} \n"         elif detected_lang == 'ru':             text += ""         else:             translation = translator(segment['text'])             text_en = translation[0]['translation_text']             translation2 = translator2(text_en)             text += f"Перевод: {translation2[0]['translation_text']} \n"      file_process.delete_file(file)     print(text)     return text   def get_language_name(code):     languages = {         'ru': 'русский',         'en': 'английский',         'zh': 'китайский',         'es': 'испанский',         'ar': 'арабский',         'he': 'иврит',         'hi': 'хинди',         'bn': 'бенгальский',         'pt': 'португальский',         'fr': 'французский',         'de': 'немецкий',         'ja': 'японский',         'pa': 'панджаби',         'jv': 'яванский',         'te': 'телугу',         'ms': 'малайский',         'ko': 'корейский',         'vi': 'вьетнамский',         'ta': 'тамильский',         'it': 'итальянский',         'tr': 'турецкий',         'uk': 'украинский',         'pl': 'польский',     }     return languages.get(code, 'неизвестный язык') 

Для запуска скрипта, нам необходимо установить следующие пакеты, используя команду:

pip install transformers, torch, accelerate, sentencepiece, sacremoses, python-telegram-bot, openai-whisper, pydub 

Деплоим бота

Чтобы всё заработало, не деплойте по пятницам.

Наш телеграмм бот, который работает с использованием алгоритмов ML, достаточно требователен к аппаратным ресурсам. Да, вы можете его использовать на своем ПК, но целесообразнее использовать облачные сервисы для подобных целей. Для размещения своего телеграмм бота, я решил воспользоваться облачной инфраструктурой от компании Timeweb Cloud. Итак, приступим к созданию нашего облачного сервиса.

Для создания нашего облачного сервиса, нам необходимо войти в панель управления под вашей учетной записью. Если учетная запись не создана, то регистрация учетной записи займет всего пару кликов. Я, например, для регистрации и авторизации воспользовался аккаунтом Google.
Создаем новый сервис, выбираем пункт «Облачный сервер»:

Далее нам предстоит выбрать операционную систему для нашего сервера, я выбрал Debian 11:

Выбираем расположение нашего сервера:

Выбираем конфигурацию сервера, как видно на изображении, для начальных тестов я выбрал конфигурацию с четырьмя ядрами CPU и 8 ГБ оперативной памяти:

Вот и все основные опции, которые нам нужны для создания сервера, остальные опции выбираете/указываете по желанию. Чтобы создать сервер, необходимо нажать кнопку «Заказать», которая располагается справа.

Процесс создания сервера:

Сервер создан:

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

Всё выполняется очень быстро, на создание сервера ушло пару минут.

Подключаемся, настраиваем сервер для размещения бота

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

apt-get update & upgrade

Запускать скрипты с root правами — как минимум, это дурной тон, поэтому создаем нового пользователя с именем tg_bot:

adduser tg_bot

и пропишем его в группу sudo:

usermod -aG sudo tg_bot

Затем нам нужно выйти из учетной записи root и подключиться под новым пользователем tg_bot, и далее все операции мы будем выполнять под этой учетной записью.

Проверим обновления Python3 и установим, если имеются:

sudo apt update sudo apt install python3

Установим менеджер пакетов:

sudo apt install python3-pip

Так как мы работаем с медиафайлами, нам необходимо установить набор библиотек FFmpeg:

sudo apt install ffmpeg

Для оценки производительности, на всякий случай установим системный монитор htop:

sudo apt install htop

Устанавливаем необходимые зависимости, для работы нашего бота:

pip install transformers, torch, accelerate, sentencepiece, sacremoses, python-telegram-bot, openai-whisper, pydub

Создаем рабочую директорию для нашего бота:

mkdir tg_bot_scripts

Затем переходим в созданную директорию, с помощью команды:

cd tg_bot_scripts

Далее нам нужно создать файлы Python нашего бота, с помощью команд:

nano tg_bot.py

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

nano neural_process.py

nano file_process.py

nano config.py

файл config.py содержит только одну переменную, которая содержит секретный ключ телеграм-бота.

tg_key = 'Ваш секретный ключ'

Или можно воспользоваться альтернативным способом, клонировав код моего репозитория GitHub.

Чтобы это реализовать, нам нужно установить Git:

 sudo apt install git 

После установки Git, выполнить следующую команду:

 git clone https://github.com/VGCH/music_translate_AI_bot 

Далее нам необходимо переименовать созданную при клонировании Git репозитория папку music_translate_AI_bot с помощью команды:

 mv music_translate_AI_bot tg_bot_scripts

После этого важно не забыть добавить токен телеграмм бота:

 cd tg_bot_scripts  nano config.py 

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

pyton3 tg_bot.py

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

sudo nano /etc/systemd/system/AI_tg_bot.service

Копируем в открывшийся текстовый редактор следующее содержимое и сохраняем файл:

[Unit] Description=My AI Bot service After=network.target  [Service] WorkingDirectory=/home/tg_bot/tg_bot_scripts/ User=tg_bot Type=simple Restart=always ExecStart=/usr/bin/python3 /home/tg_bot/tg_bot_scripts/tg_bot.py > /dev/null  [Install] WantedBy=multi-user.target 

Чтобы добавить созданный сервис в автозагрузку, используйте следующую команду:

 sudo systemctl enable AI_tg_bot.service 

Чтобы запустить сервис используйте команду:

 sudo systemctl start AI_tg_bot.service 

Чтобы остановить:

 sudo systemctl stop AI_tg_bot.service 

Изменение конфигурации сервера

В процессе отладки бота, я решил изменить конфигурацию своего облачного сервера с четырехъядерного CPU на восьмиядерный. Захожу в панель управления Timeweb Cloud, выбрав свой сервер, перехожу на вкладку «конфигурация»:

И выбираю необходимую конфигурацию:

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

При изменении количества ядер CPU на облачном сервере, так же необходимо в скрипте обработки neural_process.py изменить количество потоков для torch, в моем случае на восемь.

torch.set_num_threads(8)

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

htop

Заключение

В итоге у нас получился интересный эксперимент реализации телеграм-бота с применением алгоритмов машинного обучения для распознавания речи и перевода текста.
Спасибо вам за уделенное время и внимание. Есть вопросы, критика, осуждение? Добро пожаловать в комментарии. 🙂

Небольшой бонус к статье. Переводы песен с помощью созданного бота

Перевод аудиофайла: Lana Del Rey West Coast.mp3
В файле используется английский язык.

ID элемента: 16 Начало: 99 — Конец: 103
Исходный текст: Who baby, who baby, I’m in love
Перевод: Кто, детка, я влюблен.

ID элемента: 17 Начало: 103 — Конец: 113
Исходный текст: I’m in love
Перевод: Я влюблен.

ID элемента: 18 Начало: 113 — Конец: 123
Исходный текст: Don’t know the west coast, they got their icons
Перевод: Не знаю западного побережья, у них есть иконы.

ID элемента: 19 Начало: 123 — Конец: 127
Исходный текст: They serve as others, their queens are psychons
Перевод: Они служат как другие, их королевы — психи.

ID элемента: 20 Начало: 127 — Конец: 132
Исходный текст: You, good to music, you, good to music and you
Перевод: Ты, хорош в музыке, ты, хорош в музыке и ты

ID элемента: 21 Начало: 132 — Конец: 134
Исходный текст: Don’t you
Перевод: А ты нет?

ID элемента: 22 Начало: 134 — Конец: 139
Исходный текст: Don’t know the west coast, they love their movies
Перевод: Не знаю западного побережья, они обожают свои фильмы.

ID элемента: 23 Начало: 139 — Конец: 142
Исходный текст: They’re golden-guzzing, rocking all copies
Перевод: Они хвастаются золотом, раскачивают все копии.

ID элемента: 24 Начало: 142 — Конец: 147
Исходный текст: And you, good to music, you, good to music and you
Перевод: А ты, хорош в музыке, ты, хорош в музыке и ты

ID элемента: 25 Начало: 147 — Конец: 149
Исходный текст: Don’t you
Перевод: А ты нет?

Перевод аудиофайла: Edis-Banane.mp3
В файле используется турецкий язык.

ID элемента: 0 Начало: 0 — Конец: 1
Исходный текст: Alo.
Перевод: Привет.

ID элемента: 1 Начало: 2 — Конец: 3
Исходный текст: Bir şey söyle.
Перевод: Скажи что-нибудь.

ID элемента: 2 Начало: 6 — Конец: 7
Исходный текст: Bir şey söylemek istemiyorum.
Перевод: Я не хочу ничего говорить.

ID элемента: 3 Начало: 8 — Конец: 9
Исходный текст: Peki o zaman.
Перевод: Ну что ж.

ID элемента: 4 Начало: 30 — Конец: 32
Исходный текст: Bala ne dediğimi soracak olamazsın.
Перевод: Ты не можешь спросить моего отца, что я сказал.

ID элемента: 5 Начало: 32 — Конец: 33
Исходный текст: Hayır.
Перевод: Нет, нет, нет.

ID элемента: 6 Начало: 33 — Конец: 36
Исходный текст: Bir sebebim yok artık kalamam mı?
Перевод: У меня больше нет причин?

ID элемента: 7 Начало: 36 — Конец: 37
Исходный текст: Hayır.
Перевод: Нет, нет, нет.

ID элемента: 8 Начало: 40 — Конец: 42
Исходный текст: Ki göz dilimi.
Перевод: Вот об этом я и говорю.

ID элемента: 9 Начало: 42 — Конец: 43
Исходный текст: Bu ne?
Перевод: Что это?

Продемонстрирована только часть перевода, более информативная, чтобы не раздувать и так большую статью.

Полезные ссылки:


Возможно, захочется почитать и это:

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

Нравятся ли вам подобные статьи?

74.47% Да35
17.02% Нет8
12.77% Не определился ещё6

Проголосовали 47 пользователей. Воздержались 2 пользователя.

ссылка на оригинал статьи https://habr.com/ru/articles/774806/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *