Как я локализовал игру в слова с ИИ: делюсь инструкцией и ссылкой для тестов

Привет! Меня зовут Вова, я разработчик. Не так давно локализовал игру №59 — ту, где сайт на английском загадывает слово, а пользователю нужно его угадать. ИИ при этом подсказывает, насколько близко он оказался к цели. В тексте делюсь своей версией «Русо контексто»‎. Скрины, код и ссылка, чтобы сыграть в слова с ИИ на русском, — тоже внутри.

Даже на русском языке игра не самая простая
Даже на русском языке игра не самая простая

Используйте навигацию, если нет времени читать текст целиком:

Почему я решил локализовать игру?

Все началось с коллеги, который закинул в локальный чат сообщение, что он сыграл в #59 и угадал слово с 33 попыток и одной подсказки. Игра оказалась простая и сложная одновременно: сайт загадал слово и нужно его отгадать. В поле ввода отправляешь слово, а искусственный интеллект на сайте определяет, насколько отправленное слово близко по смыслу к загаданному.

Интересная игра, тренирующая ассоциативное мышление и умение строить связи. Новое слово появляется каждый день, что в некотором смысле выглядит ограничителем. Также игра доступна только на португальском и английском языках. С одной стороны, это дополнительная практика, а с другой — сомнения «а знаю ли я это слово?» смазывают впечатления от игры.

Так я задумался о локализации игры на русский язык. Свою игру «Русо контексто» я разместил на объектном хранилище, которое более устойчиво примет читателей vc.ru.

Дисклеймер: оригинальная игра расположена по адресу contexto.me. В процессе подготовки статьи я узнал о существовании русскоязычной версии guess-word.com. Но эта версия имеет более ограниченную функциональность.

Как работает игра?

У сайта минималистичный интерфейс:

Как я локализовал игру в слова с ИИ: делюсь инструкцией и ссылкой для тестов
  • Сведения об игре: номер, количество попыток и количество подсказок.
  • Поле ввода слова.
  • Список отгаданных слов в виде полосы загрузки. Чем ближе, тем более она заполнена. Номер справа обозначает расстояние в словах, но его можно отключить.

В выпадающем меню есть настройки и дополнительные игровые опции:

  • Выбрать игру.
  • Взять подсказку.
  • Сдаться.

Если отгадать слово, то игра предложит поделиться результатом и взглянуть на ближайшие 500 слов.

Игра очень быстро возвращает ответ и умеет определять начальную форму слова. Иными словами, cat и cats считаются одним словом и выводиятся как cat. Все введенные слова трактуются как существительные, и в списке 500 ближайших слов глагола не встретить.

Это наводит на мысль, что список ближайших слов формируется отдельно, а игра просто обращается к списку. Остается вопрос: как составить список ближайших слов?

Текстовые эмбеддинги

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

Здесь на помощь приходят текстовые эмбеддинги. Если упрощать, то эмбеддинг — это превращение слова в набор чисел, который называют кортежем или вектором.

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

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

После операции сопоставления появляется модель — файл, который описывает соответствие «слово — вектор» или как-то описывает правила сопоставления или вычисления. Для работы модели нужно программное обеспечение, которое понимает формат модели.

Проще и быстрее всего «потрогать» эмбеддинги на языке Python. Библиотека gensim реализует один из самых популярных подходов — word2vec.

Для работы необходима модель, обученная на достаточном количестве текстов. В документации gensim есть ссылки на англоязычные модели, но нас это не устраивает.

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

Я использовал модель, обученную на Национальном Корпусе Русского Языка (НКРЯ), ее название — ruscorpora_upos_cbow_300_20_2019. Скачиваем архив и распаковываем. Модель представлена в двух видах: бинарном (model.bin) и текстовом (model.txt).

Попробуем воспользоваться этой моделью. Сперва загружаем.

from gensim.models import KeyedVectors model = KeyedVectors.load_word2vec_format("model.txt", binary=False)

Теперь можем найти слова, ближайшие к слову «провайдер»:

>>> model.most_similar(positive=["провайдер"]) … KeyError: "Key 'провайдер' not present in vocabulary"

К сожалению, такого слова не нашлось. Дело в том, что данная модель принимает слова вместе с меткой, которая определяет часть слова. Это сделано для различия слов с одинаковым написанием. Например, «печь» можно представить как «печь_NOUN» и «печь_VERB», то есть как существительное и глагол соответственно.

>>> model.most_similar(positive=["провайдер_NOUN"]) [ ('ip_PROPN', 0.677890419960022), ('internet_PROPN', 0.6627045273780823), ('интернет_PROPN', 0.6595873832702637), ('интернет_NOUN', 0.6567919850349426), ('веб_NOUN', 0.6510902047157288), ('сервер_NOUN', 0.6460723280906677), ('модем_NOUN', 0.6433334946632385), ('трафик_NOUN', 0.6332165002822876), ('безлимитный_ADJ', 0.6230701208114624), ('ритейлер_NOUN', 0.6218529939651489) ]

Также возьмем более простой пример с несколькими словами. Зададим два слова: король и женщина. Человек догадается, что женщина-король — это скорее всего королева.

>>> model.most_similar(positive=["король_NOUN", "женщина_NOUN"], topn=1) [ ('королева_NOUN', 0.6674807071685791), ('королева_ADV', 0.6368524432182312), ('принцесса_NOUN', 0.6262999176979065), ('герцог_NOUN', 0.613500714302063), ('герцогиня_NOUN', 0.5999450087547302) ]

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

Аргумент topn позволяет задать количество слов, которые мы хотим получить. Таким образом можно запросить какое-нибудь большое количество слов и получить список, необходимый для создания игры. Давайте зададим более современное слово «киберпространство» и посмотрим на ближайшее слово и на слово, например, на десятитысячной позиции.

>>> result = model.most_similar(positive=["киберпространство_NOUN"], topn=10000) >>> result[0] ('виртуальный_ADJ', 0.39892229437828064) >>> result[9998] ('европбыть_VERB', 0.12139307707548141) >>> result[9999] ('татуировкий_NOUN', 0.12139236181974411)

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

Обработка словаря

Один из способов хранения модели word2vec — текстовый. Формат прост: в первой строке задаются два числа — количество строк в документе и количество чисел в векторе. Далее на каждой строке задается слово и далее числа, обозначающие вектор.

Здесь удобно воспользоваться особенностью этой модели, а именно тегами.

Существительные имеют тег _NOUN, что позволяет убрать из модели ненужные слова. Удалить не существительные легко, но как поступить с опечатками и странными словами? Здесь на помощь приходит другой эмбеддинг, который обучался на литературе.

Это эмбеддинг Navec (навек) из проекта Natasha. Ссылку на русскоязычную модель можно увидеть в репозитории проекта. Скачиваем и загружаем модель:

from navec import Navec path = 'navec_hudlit_v1_12B_500K_300d_100q.tar' navec = Navec.load(path)

Теперь можно проверять слова простым синтаксисом:

>>> "виртуальный" in navec True >>> "европбыть" in navec False >>> "татуировкий" in navec False

Таким образом можно отсеять немалое количество слов, которым в игре не место.

Примеры удаленных слов, многие даже великому гуглу неизвестны:

  • цидулка
  • зачатокать
  • магазей
  • антитезть
  • завоевателий
  • налицотец
  • прируба
  • бислой
  • цвть

Но вместе с тем теряются и настоящие слова:

  • агрокомплекс
  • кейтеринг
  • фемтосекунда
  • углепластик
  • электромашиностроение

Алгоритм очистки модели следующий:

  • Если у слова тег не NOUN, то отбрасываем это слово.
  • Удаляем из слова последовательность _NOUN.
  • Проверяем «чистое слово» на наличие в эмбеддинге Navec. Если его там нет, слово отбрасываем.
  • Слово, которое прошло все проверки, записываем в файл.

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

model = KeyedVectors.load_word2vec_format("noun_model.txt", binary=False)

Стало ли после этого лучше?

>>> result = model.most_similar(positive=["киберпространство"], topn=10000) >>> result[0] ('виртуальность', 0.4715898633003235) >>> result[9998] ('компаунд', 0.15783849358558655) >>> result[9999] ('хитрость', 0.15783214569091797)

Определенно. Для статистики: исходная модель содержит 248 978 токенов, из них 59 104 токенов имеют метку существительног. И только 36 269 прошли «сито» второго эмбеддинга.

Время заняться бэкэндом и фронтендом игры.

Умный бэкэнд

Так как Python является моим рабочим языком программирования, бэкэнд я решил реализовать на нем. Поговорим об обработке входных данных. Обрезать пробелы и перевести текст в нижний регистр — само собой разумеющееся. Но как получить начальную форму слова?

Здесь можно воспользоваться инструментом MyStem. Для Python есть обертка pymystem3. Крайне простой инструмент для получения начальной формы слова:

import pymystem3 mystem = pymystem3.Mystem()

Метод lemmatize принимает на вход строку-предложение и возвращает список слов в начальной форме.

>>> mystem.lemmatize("кот коты котов котах кота") ['кот', ' ', 'кот', ' ', 'кот', ' ', 'кот', ' ', 'кот', '\n']

На первый взгляд даже производительность на достойном уровне: на моей виртуальной машине лемматизация одного слова занимает до 10 мс. По меркам современного веба это достаточно быстро.

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

Игра на объектном хранилище

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

  • Сдаться нельзя.
  • Список топ-500 ближайших слов получить можно, только предоставив загаданное слово.
  • Подсказку можно получить по слову и позиции.

Но вскоре мне показалось это слишком суровым.

На данный момент единственное назначение бэкэнда — приведение слов к начальной форме. Правда, как показало тестирование на коллегах, и это не обязательно: все и так старались писать начальные формы слов. Да и модель эмбеддингов не лемматизирована, то есть игра понимает слова не только в начальной форме.

Получается, игру можно полностью перенести в браузер?

Так как я бэкэнд-разработчик, то отказ от бэкэнда в угоду фронтэнду — это стресс. Однако от бэкэнда полностью отказаться не получится: генератор близких слов где-то нужно запускать. Генератор принимает на вход загаданное слово и формирует текстовый файл, где на каждой строке по одному слову в порядке смыслового убывания от загаданного. Содержимое этого файла также дублируется в JSON-словарь, где каждому слову соответствует его дистанция от загаданного слова.

JSON-файл на каждую игру занимает до 2 МБ. При открытии игры файл скачивается в браузер и JavaScript реализует логику игры. Этот способ не самый производительный, но после загрузки файла позволяет играть без подключения к интернету.

Я разместил игру в облачном хранилище Selectel, которое более устойчиво к наплыву посетителей.

Заключение

Итоговый результат доступен по адресу words.f1remoon.com, а исходный код — в репозитории.

Сыграйте в игру и поделитесь впечатлениями. А еще подпишитесь на блог Selectel, чтобы не пропустить обзоры, новости, кейсы и полезные гайды из мира IT.

Читайте также:

3232
17 комментариев

Отличная цидулка! Пора это зачатокать для прирубы.

1
Ответить

16 попыток

1
Ответить

А слова «бухло» то нет((

Ответить

у нас ЗОЖ, вы чего))

Ответить

Спасибо за подробный рассказ с кодом

Ответить

Ещё бы от глаголов избавиться, и от сокращений/аббревиатур (чтоб не спойлерить, например, в игре от 21-12-2022 слово под номером 11). Так же, в игре за указанное число, 46 слово вроде близко к победному, а если пробить такое же, но в именительном падеже, то его порядок аж 4320.
А вообще понравилось

Ответить

Если в contexto ввести несуществующее слово, то это не считается за попытку. А у Вас считается.

Ответить