CoderCastrov logo
CoderCastrov
NLP

Как создать детектор акцентов в португальском языке с помощью TED-лекций

Как создать детектор акцентов в португальском языке с помощью TED-лекций
просмотров
9 мин чтение
#NLP

Я собрал 5000 TED-лекций и создал классификатор португальских акцентов с помощью scikit-learn. Затем я использовал Flask и Vue.js, чтобы поделиться им с вами

Нажмите здесь для английской версии.

Да, я один из тех, кто недавно присоединился к волне Data Science. Учиться, делая, говорит сообщество. Создайте проблему и найдите решение, говорят они. Не бойтесь ломать вещи по пути. Я пытаюсь следовать этой философии. Как переводчик с польского на португальский и наоборот в прошлой жизни, у меня сразу возникла реальная проблема:

Как вы можете себе представить, фриланс-рынок для португальских переводчиков в Польше довольно ограничен. У меня не было возможности специализироваться и ограничиваться только одним акцентом. В роли переводчика с PT-PT и PT-BR, иногда в течение одной рабочей недели, мне приходилось удваивать усилия, чтобы сохранить последовательность в определенном переводе. Поэтому одной из моих первых идей для проекта NLP был классификатор португальских акцентов.

Это не новаторское исследование, открывающее новые пути в области NLP или ИИ. Моя мотивация здесь аналогична предыдущей статье в Medium о моем Tweetbot - показать, насколько просто сделать первые шаги в обработке естественного языка на Python. У меня немного технического образования, поэтому я стараюсь представить это как можно более ясно.

Статья состоит из трех основных частей:

  • Подготовка исходных данных (Scrapy);
  • Обучение модели (scikit-learn);
  • Реализация API с использованием модели (Flask + Vue.js).

Подготовка исходных данных

Какие данные мне были нужны, где я их получил и как? Мне было необходимо найти уже размеченные тексты на европейском португальском или бразильском португальском языках. В данном случае я выбрал TED.com в качестве источника данных. Это достаточно большой репозиторий транскрибированных лекций. Особенно важно то, что переводческая команда TED строго разделяет две вариации португальского языка (что не относится к Википедии, например).

Раньше у TED было официальное API, но они перестали его предоставлять в 2016 году. Поэтому я воспользовался библиотекой Scrapy и создал парсер для сбора данных. Большая часть кода основана на учебнике Scrapy. Парсер проходит по каталогу лекций TED на указанном языке и переходит по ссылкам на отдельные лекции:

def parse_front(self, response):
 
   talk_links = response.css(“a.ga-link::attr(href)”) 
   links_to_follow = talk_links.extract()    for url in links_to_follow: 
      url = url.replace(‘?language’, ‘/transcript?language’) 
      yield response.follow(url=url, callback=self.parse_pages)

Затем второй метод парсит заголовки и тексты отдельных лекций и собирает их в словарь:

def parse_pages(self, response): 
 title = response.xpath(‘//head/title/text()’).extract_first() 
 title = title.strip() 
 title = title.split(‘:’)[0] 
 talk = response.xpath( ‘//div[@class=”Grid__cell flx-s:1 p-r:4"]/p/text()’).extract() 
 for i in range(len(talk)): 
    line = talk[i].strip() 
    line = line.replace(‘\n’,’ ‘) 
    talk[i] = line.replace(‘\t’,’ ‘) 
 talk = ‘ ‘.join(talk) 
 talk = talk.replace(‘\n’,’ ‘) 
 ted_dict[title] = talk

В конце словарь записывается в CSV-файл. Все, что мне нужно было сделать, это определить соответствующие xpath выражения. Я также установил задержку в полминуты между каждым запросом (без нее я слишком часто обращался к серверу, и он блокировал мои запросы):

class TedSpiderPt (scrapy.Spider):
 name = “ted_spider_pt”
 download_delay = 0.5

Остальная часть подготовки данных и процесс обучения описаны в Jupyter Notebook здесь. Ниже я рассмотрю его и выделю ключевые моменты.

Я собрал примерно 2500 лекций TED для каждой версии португальского языка, примерно 12-18 тысяч символов на лекцию. После очистки форматирования я помечаю транскрипции как "0" для PT-PT (европейский португальский) и "1" для PT-BR, а затем применяю метод fit_transform() класса CountVectorizer (который является частью библиотеки scikit-learn).

Если этот материал для вас является введением в обработку естественного языка: CountVectorizer преобразует набор текстовых документов в подсчеты слов. Числовое представление данных вместо текстового значительно улучшает их обработку.

Сначала я разделяю лекции на обучающий и тестовый наборы в соотношении 2:1. Модель будет учиться только на данных обучения. Тестовая часть используется для проверки ее производительности перед выпуском в мир.

Затем я применяю метод fit() векторизатора к данным обучения - с другими словами, метод присваивает уникальный номер каждому слову, найденному в нем, создавая словарь. Чтобы устранить выбросы, я устанавливаю параметр min_df равным 2 - слова, которые встречаются только один раз, не учитываются. Затем я преобразую обучающий набор - то есть считаю количество вхождений каждого слова из словаря. После применения CountVectorizer каждое из 59061 слов подсчитывается в каждой из 3555 лекций, составляющих обучающий набор.

Слово "я" было проиндексировано как "23892" в словаре. В подсчетах слов для этого индекса мы видим лекцию с значительно большим количеством "я". Мы можем вернуться к транскрипции лекции и ... действительно, личный опыт играет важную роль в лекции TED Чики Саркар - отсюда и большее количество "я" в ее речи.

Помимо векторизации и подсчета, я не выполняю никакой другой предварительной обработки. Я не провожу стемминг, то есть не свожу слова к их основам, потому что я хочу сохранить грамматические различия (например, различия в спряжении глаголов) между двумя версиями португальского языка. Таким образом, в предложении "O João está fazendo" я хочу сохранить глагол "fazer" в герундии, потому что я могу предположить, что это текст на бразильском португальском языке.

Тренировка модели

Затем я обучаю классификатор Multinomial Naive Bayes (также из scikit-learn), используя набор данных для обучения. Хотя этот подход довольно простой, он работает удивительно хорошо в таких приложениях NLP, как это.

Еще раз, объяснение для новичков: в принципе, классификатор NaiveBayes сравнивает частоты данного слова в наборах данных BR и PT и определяет, предполагает ли слово более "бразильский" или "португальский" текст. Во время предсказания все слова в тексте учитываются, и мы получаем конечную вероятность.

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

Затем векторизованный набор тестовых данных классифицируется с использованием MultinomialNaiveBayes. В этом конкретном случае я внес небольшую корректировку в набор тестовых данных - я хотел проверить способность модели классифицировать короткие тексты. Поэтому я разделил речи в наборе тестовых данных на более короткие фрагменты от 200 до 760 символов. Для сравнения, параграф, который вы сейчас читаете, содержит 400 символов.

Результаты?

Я достиг точности 86,55% в коротких текстах, с почти идентичным количеством неправильно классифицированных примеров PT-BR и PT-PT. Будет ли это достаточно хорошим результатом? Я не самый объективный судья здесь, но скажу, что это неплохо. Давайте посмотрим на несколько примеров неправильно классифицированных фраз, чтобы оценить модель.

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

Реализация API с использованием модели

Image

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

Любой новый текст, отправленный модели, должен быть преобразован векторизатором, а затем классифицирован. Для этого я объединил векторизатор и классификатор в пайплайн (слово, которое, если я не ошибаюсь, обычно не переводится) из scikit-learn. Затем его можно сохранить в файл joblib:

pipeline = Pipeline([('vectorizer',vectorizer), 'classifier',nb_classifier)])
dump(pipeline, 'ptclassifier.joblib', protocol = 2)

"Легкий" - это также можно описать Flask, легкий фреймворк для веб-приложений, который я использовал для реализации API. Полный код моего приложения Flask находится в этом репозитории на GitHub. В основном он загружает модель из joblib и прослушивает GET-запросы с одним аргументом: строкой. После получения он передает текст через пайплайн и возвращает предсказанную метку вместе с уверенностью предсказания.

Я также использовал пакет flask-cors для разрешения запросов из другого источника. В нашем случае веб-приложение, которое я представлю ниже, размещено в другом месте и должно иметь доступ к этому Flask API. Вы можете узнать больше о CORS здесь.

Я разместил свое приложение Flask на PythonAnywhere, сервисе, о котором я подробно рассказал в статье о tweetbot. Если вы только начинаете работу со скриптами Python, размещенными онлайн, я искренне рекомендую PyA - их служба поддержки всегда была очень полезной в ответах на мои очень наивные вопросы.

Мой извилистый путь к Front-end для AA

Ах, Front-end. Корень и причина моего огромного разочарования. Я был уверен, что последний этап, настройка самой простой страницы с текстовым полем и кнопкой "ОТПРАВИТЬ", будет довольно легким. Так уверен был я, что оказался в порочном круге.

Сначала кто-то посоветовал мне использовать Vue.js, но начать с книги о самом JavaScript, такой как EloquentJS. Ааааа. Не поймите меня неправильно, мне понравился EJS; его онлайн-версия с песочницей для упражнений очень доступна. Но спустя неделю курса я понял, что моя цель на тот момент была просто создать базовый интерфейс и вернуться к созданию моделей. Я еще не чувствую себя достаточно свободно в Python, чтобы начинать изучать новый язык.

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

Наконец, я нашел то, что работало достаточно быстро для моего короткого терпения - представлено Джеймсом Сальваторе в Uptake Tech Blog. Я взял его модель и немного подстроил ее под свои цели. Чтобы упростить, я убрал панель инструментов, добавил изображение в качестве заголовка и оставил только одно текстовое поле. Я разместил производственную версию приложения на AlwaysData, еще одном сервисе, который могу рекомендовать.

Посмотрите на конечный результат здесь, если вы еще этого не сделали.

Поиграйте с ним, попробуйте "обмануть" классификатор. Я буду очень рад, если вы что-нибудь сломаете и напишете мне, что произошло! Кроме того, любая конструктивная критика кода в репозитории будет объектом моей вечной благодарности.

Следующие шаги?

У меня будет вторая часть этой статьи. Обещаю. В ближайшем будущем я хочу:

  • Улучшить фронтенд приложения, чтобы показать пользователю, как отдельные слова влияют на прогноз;
  • Парсить другие источники текста (новости, блоги, субтитры), проверить производительность модели;
  • Исследовать ошибки классификатора и попытаться найти закономерности;
  • Попробовать подход LSTM для этой проблемы и сравнить результаты;
  • И, наконец, но не менее важно, надеюсь получить комментарии от моих читателей, которые стоит поделиться!

Бонус!

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

Я благодарен Пиотру Мигдалю за руководство в этом и других проектах, а также Камилу Хербе и Пиотру Казновскому за проницательные комментарии как по статье, так и по веб-приложению.