Умные субтитры
- понедельник, 7 ноября 2022 г. в 00:54:06
Сегодня я вам расскажу о своем методе для изучения иностранных языков.
С чего начать изучение нового языка? Чаще всего люди на раннем этапе используют стандартный лексико-грамматический метод с доминированием письменного языка, который показал себя медленным и весьма скучным — вам чаще всего нужна зашкаливающая мотивация, чтобы не бросить где-то посередине.
Я предлагаю начать сразу с видео:
Видео просто интересно смотреть (особенно если это нормальные мультики/фильмы/сериалы, созданные для носителей языка).
Вы сразу начнете запоминать звучание слов, что очень сильно пригодится для развития навыка аудирования в будущем.
Однако, если вы просто возьмете видео на новом для себя языке, то вы ничего не поймете. Первый шаг к решению проблемы — смотреть видео с субтитрами на языке оригинала. Однако, когда вы еще не знаете лексику языка, такие субтитры вам не помогут. Вам нужны двойные субтитры — на языке оригинала + перевод на ваш родной язык.
При просмотре видео с двойными субтитрами мозг начинает сопоставлять слова из двух предложений на разных языках. Это зачастую нетривиально (особенно если у языков сильно отличается грамматика), на это мозг тратит время и энергию.
Моя идея состоит в том, чтобы выполнить эту задачу вместо мозга и отображать субтитры как на картинке выше: посередине находятся субтитры на языке оригинала, ниже — их перевод; плюс над иностранными словами подписаны соответствующие им слова из перевода. Таким образом, за время просмотра десятка подобных фильмов/мультиков, каждое слово из базовой лексики будет многократно отображено вместе со своим переводом в текущем контексте, что позволит его запомнить.
Для самых нетерпеливых — вот ссылки на папки в Google Drive с отрендеренными видео:
На английском. На момент написания статьи там только одно короткое видео: песня I will fly из My little pony.
На французском. Сейчас там есть мультик Asterix and Cleopatra.
Чтобы отрендерить такие видео, нам нужно сначала решить задачу матчинга слов между двумя одинаковыми по смыслу предложениями на разных языках.
Наш алгоритм будет принимать два предложения: предложение T — на языке оригинала; предложение N — на родном/понятном для нас языке. N представляет собой перевод T.
Каждое из двух предложений мы будем представлять в виде списка токенов. Мы используем наш кастомный алгоритм токенизации, который не зависит от токенизатора, который используется нашей будущей ML-моделью.
Алгоритм матчинга будет возвращать список соответствующих другу другу элементов. Каждый элемент — это два списка токенов, один взят из T, второй из N.
Рассмотрим пример:
T = “Now all you have to do is tighten the screw, and Bob's your uncle.”
N = “Теперь всё, что тебе надо сделать, это затянуть винт — и дело в шляпе.”
После токенизации получаем:
N = [“Now”, “all”, “you”, “have”, “to”, “do”, “is”, “tighten”, “the”, “screw”, “and”, “Bob”, “is”, “your”, “uncle”, “.”]
T = [“Теперь”, “всё”, “,”, “что”, “тебе”, “надо”, “сделать”, “,”, “это”, “затянуть”, “винт”, “—”, “и”, “дело”, “в”, “шляпе”, “.”]
Алгоритм должен возвращать следующие результаты:
[“Now”] = [“Теперь”]
[“all”] = [“всё”]
[“you”] = [“тебе”]
[“have”] = [“надо”]
[“to”, “do”] = [“сделать”]
[“is”] = [“это”]
[“tighten”] = [“затянуть”]
[“the”, “screw”] = [“винт”]
[“and”] = [“и”]
[“Bob”, “is”, “your”, “uncle”] = [“дело”, “в”, “шляпе”]
Поставим одно ограничение: токены из списков со стороны T должны идти в исходном предложении подряд (потому что иначе сложно будет отобразить матчинг на видео). Например, если T = “Je ne sais pas”, N = “Я не знаю”, то комбинация “ne pas” в реальности соответствует “не”, но наш алгоритм такой результат выдать не сможет (между ne и pas затесалось sais). Однако, если мы перевернем задачу (T станет N, и наоборот), то тогда данный матчинг возможен (поскольку данное ограничение не распространяется на токены из N).
Для решения задачи будем использовать BERT.
BERT принимает токенизированный текст и умеет возвращать контекстуальный эмбеддинг для каждого токена после прохождения каждого из слоев.
Существуют BERT-модели, которые способны работать со множеством языков — как если бы существовал один “супер-язык”, который включает в себя лексику всех поддерживаемых языков.
Для решения нашей задачи мы будем использовать одну из таких мультиязычных моделей - LaBSE (language agnostic BERT sentence embeddings). Ее малая версия поддерживает 15 языков, а большая — аж 109.
Если мы пропустим оба наших предложения T и N через LaBSE и проанализируем эмбеддинги на одном из слоев (совершенно не обязательно на последнем), то те токены, которые друг другу соответствуют (у них похожий смысл, причем в контексте), будут иметь похожие эмбеддинги. Дальше нам остается только выявить эти похожие эмбеддинги.
Наш алгоритм принимает T и N, токенизированные нашим кастомным алгоритмом. Однако, LaBSE умеет работать только со “своими” токенами. Поэтому нам придется обработать каждый наш токен отдельно LaBSE-токенизатором и конкатенировать все токены в одну последовательность. Пропустим две таких последовательности (для T и для N) через LaBSE и извлечем эмбеддинги с одного из слоев.
Мы также должны сохранить информацию для каждого LaBSE-токена, из какого “изначального” токена он был получен. Чтобы не запутаться в разных видах токенов, будем дальше называть “изначальные” токены словами, а LaBSE-токены — токенами.
На предыдущем этапе мы получили 2 матрицы эмбеддингов: E(T) и E(N).
Если мы перемножим эти матрицы M = E(T) * E'(N): элемент M(i, j) теперь равен скалярному произведению эмбеддинга i-го токена из T и j-го токена из N. Скалярное произведение можно использовать как метрику расстояния.
Применим функцию softmax к каждой строке, сумма элементов строки будет равна 1.0, а они сами будут представлять собой своего рода "псевдовероятность" соответствия i-го токена из T каждому из токенов из N. Если мы сделаем то же самое для столбцов, то значения столбца будут показывать "псевдовероятность" матчинга токена из N с каждым из токенов из T. Мы будем использовать обе матрицы: "связность" между двумя токенами (один - из T, второй - из N) будет характеризоваться двумя значениями в диапазоне от 0 до 1: первое получено из матрицы softmax для строк, а второе - из матрицы softmax для столбцов.
Теперь для каждой пары слов из T и N нам нужно агрегировать значения их связности. Для этого для каждой пары слов мы формируем список значений связности, взятый из обеих матриц softmax - мы туда скидываем все элементы softmax-матриц, которые относятся к данной паре слов (в общем случае, это получается две непрерывные подматрицы). Затем получаем единое значение из элементов списка: я экспериментировал с этим, по итогу пока остановился на произведении среднего и максимального значений списка. Подытожим: после данного этапа у нас есть матрица значений связности между всеми словами из T и N.
Далее мы сортируем все пары слов по значению их связности (от наибольшего к наименьшему) и проходим по списку от его начала до конца и добавляем пары слов к результату работы алгоритма, если это возможно (таким образом, мы используем жадный алгоритм матчинга). Существует также определенный порог для значения связности, мы не будем добавлять пары слов со значением связности ниже него. Этот порог меняется динамически в зависимости от того, какой процент слов мы уже заматчили.
Однако, наш алгоритм должен уметь делать матчинг типа many-to-many. На каждом шаге мы сначала проверяем, может ли следующая пара слов присоединиться к одному из уже существующих результирующих пар списков. Чтобы присоединиться, пара должна удовлетворять двум условиям:
Она не должна нарушать условие, что списки со стороны T могут содержать только слова, идущие подряд
Пусть S - текущая сумма всех значений связности для списка, к которому хочет присоединиться эта новая пара. Значение связности между парой слов равно C. Условие состоит в том, что C должно быть больше, чем (S+C)*A. A — параметр "anti glue", который контролирует, насколько алгоритм будет склонен “склеивать” слова вместе.
Если эти условия не выполняются, то новая пара образует свой собственный новый элемент в результирующем списке (и другие пары могут присоединиться к нему позже).
Модуль для рендеринга видео с субтитрами принимает:
Собственно, само видео без субтитров.
Субтитры на языке видео. Добыть их — сложнее всего (особенно если видео не на английском).
Перевод субтитров. Реплики в переводе должны соответствовать репликам в оригинале (иметь те же таймстампы и тот же смысл). Перевод можно получить с помощью машинного переводчика (я использую для этого DeepL и, если видео не очень большое, поправляю результаты вручную). Зачастую, качественный машинный перевод подходит даже больше человеческого (потому что белковые нейросети иногда при переводе креативят, а алгоритму матчинга потом из-за этого страдать).
Для рендеринга видео я использовал библиотеку MoviePy: с ее помощью можно поместить на видео текстовый элемент в произвольную локацию (+ так, чтобы он “жил” в определенном диапазоне кадров).
Что делать, если контекстуальный перевод просто слишком длинный (например, если слову “use” соответствует “использовать”) и в результате подсказки начинают наезжать друг на друга? В таком случае самая длинная из них записывается в две строки, с переносом (см. картинку ниже).
Данный подход для изучения языка больше всего подходит для быстрого наращивания лексики на раннем этапе. Мозг просто бомбардируется примерами употребления слов в контексте с их переводом.
Кроме того, данные видео можно использовать и просто для развлечения. Если вам просто интересен определенный язык (например, потому что вы интересуетесь языками) и вы хотите в него окунуться — теперь вы можете просто открыть видео и наглядно видеть саму структуру речи.