Как стать автором
Обновить
0
AI Talent Hub
Онлайн-комьюнити ML специалистов

Отгадай слово: как мы создали игру с элементами машинного обучения и вышли в ноль за 2 месяца

Уровень сложностиСредний
Время на прочтение12 мин
Количество просмотров11K

Как думает искусственный интеллект? Попробовать разобраться в его логике можно в игре от менторов AI Talent Hub, онлайн-магистратуры Napoleon IT и ИТМО, и студентов ИТМО «Отгадай слово». За два месяца в нее сыграли уже более 107 тысяч уникальных пользователей, а количество подписчиков одноименного телеграм‑канала увеличилось до 5 000 подписчиков. Что делает игру такой популярной, как проект окупился без затрат на продвижение и рекламы на сайте, а также почему при работе с ИИ не избежать ошибок? Рассказываем в статье.

Привет! Меня зовут Мичил Егоров, я главный разработчик игры «Отгадай слово» — игра, в которой нужно угадать слово, поняв логику искусственного интеллекта. Два месяца назад вместе с кофаундером Григорием Спировым мы наткнулись на игру https://contexto.me и решили локализовать ее. На запуск первой версии ушло всего 11 часов. В первую неделю слово отгадывали более 31 000 уникальных пользователей, а за два месяца набралось 107 000! Телеграм‑канал, где публикуется рейтинг слов, тоже растет — сегодня почти 5 000 подписчиков. О том, как мы пришли к идее создания проекта можно прочитать в интервью для ITMO News, а тут рассмотрим технологии и алгоритмы, которые использовались при разработке, и расскажем об совершенных ошибках на пути.

P. S. При внимательном прочтении вы даже сможете запустить первую версию игры;)

Принцип игры

Игра работает по принципу «горячо‑холодно»: чем сильнее ассоциируется введенный игроком вариант с загаданным словом, тем выше он в рейтинге. Первая версия была практически полной копией contexto.me на русском языке, отличалась только урезанным функционалом и названием.

Первая разработанная версия
Первая разработанная версия

Нам не хотелось выпускать игру в таком виде, поэтому перед самым запуском на помощь с дизайном пришла Айталина Кривошапкина, студентка ИТМО, ― буквально за час она создала симпатичный интерфейс для старта. Так образовалась наша команда из трех человек.

Вторая версия игры
Вторая версия игры

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

Если коротко, то алгоритм у игры «Отгадай слово» такой:

  • игра задает вам секретное слово, вы должны его отгадать (как Wordle),

  • у пользователя есть попытки для ввода почти любого слова (как Wordle),

  • у каждой попытки есть ранг — расстояние до загаданного слова в ассоциативной цепочке (отличие от Wordle),

  • игроку нужно добиться ранга 1 — самого секретного слова.

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

Алгоритм для определения рангов слов

Основной алгоритм в игре — сортировка слов по параметру близости к загаданному слову. В этом случае важны такие понятия, как эмбеддинг слова и косинусное расстояние.

Эмбеддинги

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

Например, первая версия игры была построена на векторах, которые генерируются алгоритмом GloVe. Для второй версии мы использовали эмбеддинги, полученные с помощью пользовательских ассоциаций с сайта https://sociation.org. Различия этих способов рассмотрим позже.

Эмбеддинг слова
Эмбеддинг слова

Косинусное расстояние

Косинусное расстояние — это вещественное число, показывающее расстояние между векторами, и соответственно между словами.

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

Косинусное расстояние
Косинусное расстояние

Если собрать эмбеддинги слов и применить к ним косинусное расстояние, то получится что‑то похожее на это:

from sklearn.metrics.pairwise import cosine_distances


class DictOrderer:
    def __init__(self, words: list[str], vectorizer) -> None:
        self.words = words
        self.vectorizer = vectorizer

        # предпосчитываем вектора для всех наших слов
        self._word2vector = {word: vectorizer[word] for word in self.words}

    def get_order(self, word: str) -> list[tuple[str, int]]:
        # получаем вектор для нашего слова
        word_vector = self._word2vector[word]

        # вектора всех наших слов в словаре
        dict_vectors = list(self._word2vector.values())

        # получается матрица 1xN, где N - это кол-во слов в словаре.
        # Каждое i значение показывает расстояние до нашего заданного слова

        distances = cosine_distances(
            [word_vector],
            dict_vectors,
        )[0]
        
        # получаем структуру вида (слово, расстояние до заданного слова) 
        word_distance = zip(list(self._word2vector.keys()), distances)
        
        # сортируем по расстоянию до нашего слова
        word_distance = sorted(word_distance, key=lambda x: x[1])

        return word_distance

Так мы задаем словарь для сортировки, а затем передаем ему объект vectorizer, который в свою очередь имеет реализованный метод __getitem__. Именно он возвращает вектор для слова. Тем самым можно в качестве vectorizer передавать не только алгоритмы с популярной gensim, а также обычные питоновские словари.

В первой версии игры мы использовали алгоритм GloVe (global vectors for word representation) — глобальные векторы для представления слов. Основная его идея состоит в том, чтобы учитывать так локальные, так и глобальные отношения слов в различных корпусах текстов. К счастью, для русского языка написали библиотеку navec, которая обучалась с помощью GloVe.

from navec import Navec


navec = Navec.load("./navec_hudlit_v1_12B_500K_300d_100q.tar")

Так выглядит эмбеддинг слова «поезд»:

print(navec["поезд"])

array([ 0.31074855, -0.4440416 ,  0.3544376 , …, -0.86068416,  0.12352141,  0.01766999])

Вектор к слову получаем через метод __getitem__, поэтомы мы можем передать объект navec в наш DictOrderer:

orderer = DictOrderer(navec.vocab.words, navec)

Тем самым получается готовый алгоритм сортировки слов! Можем опробовать:

print(orderer.get_order("поезд")[:5])

[('поезд', 5.9604645e-08),
 ('вагон', 0.24743819),
 ('автобус', 0.26409537),
 ('поезда', 0.31421912),
 ('вокзал', 0.34018022)]

Вроде бы всё супер! Но не торопитесь. Если мы попробуем ввести слово «вода», то получится совсем не то, что нужно:

print(orderer.get_order("вода")[:5])

[('вода', 0.0),
 ('воды', 0.31439257),
 ('воду', 0.32432437),
 ('водой', 0.35056865),
 ('воде', 0.38383615)]

Поэтому нужно оставить в словаре только существительные в именительном падеже.

Для второй версии «Отгадай слово» мы связались с разработчиком Sociation.org — игры в ассоциации с коллективным разумом. В ней игрокам предлагается водить ассоциации к случайно заданным словам. С помощью собранных данных можно представлять слова в виде векторов, разложив матрицу отношений.

orderer = DictOrderer(navec.vocab.words, sociation)
print(orderer.get_order(“поезд”)[:5])

[('поезд', 6.661338147750939e-16),
 ('вагон', 0.09935828259768476),
 ('рельсы', 0.13258020472841037),
 ('ржд', 0.14348915441665822),
 ('электричка', 0.20177676803251465)]

print(orderer.get_order(“вода”)[:5])

[('вода', 0.0),
 ('водица', 0.36609914765055607),
 ('жидкость', 0.4206119110523321),
 ('родник', 0.4404068720939991),
 ('водоём', 0.45224501168195497)]

Игроки начали отмечать улучшение подбора ассоциаций после замены способа векторизации. За это мы очень благодарны Денису, основателю sociation.org. Благодаря тому, что цепочки ассоциаций формировались людьми, а не статической машиной из корпусов текстов, получается очень интересно и более натурально.

Приглашаю вас поиграть в «Отгадай слово» с версией Navec и написать в комментариях интересные наблюдения! Иногда засиживаюсь часами, пытаясь найти интересные связи между словами.

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

Серверная часть

Самая частая операция в игре — получение рейтинга слова. Но если мы будем каждый раз формировать рейтинг, то это займет у пользователя 3–5 секунд. Поэтому лучше всего закэшировать в памяти рейтинги слов, так мы получим быстрый доступ к определению ранга слова.

Серверная часть для «Отгадай слово» была написана на Django. В первой версии игры мы использовали стек Django + SQLite + Gunicorn. Так, при первичной инициализации проекта определяется сегодняшнее слово и для него формируется рейтинг. Далее этот рейтинг кэшируется в памяти с помощью паттерна Signleton. Если навесить на DictOrder синглтон, то получится первая версия нашего бекенда.

apps.py
class WordsConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'words'

    def ready(self):
        from words.models import DayKeyword, Word, get_today_keyword
        from words.guess import WordGuesser

        day_keyword = get_today_keyword()

        print("initializing app…")
        # инициализация занимает несколько секунд
        WordGuesser(day_keyword.word, [word.word for word in Word.objects.all()])
        print(“app is initialized!”)

views.py
def make_guess(request):
    # объект инициализируется мгновенно
    guesser = WordGuesser()
    guessed_word = json.loads(request.body.decode("utf-8"))["word"]
    guessed_word = guessed_word.strip().lower().replace("ё", "е")

    if not guesser.has_word(guessed_word):
        return JsonResponse({"error_text": f"Я не знаю слово {guessed_word}"}, status=400)
    
    # получаем рейтинг слова
    order = guesser.guess(guessed_word)

    return JsonResponse({
        "order": order
    })

Сразу можно заметить несколько минусов предложенного подхода:

  • одновременно можно хранить рейтинг только для одного слова,

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

Но на самом деле этот подход позволяет получить практически мгновенный ответ на попытку игроков. Протестировать можно в старой версии игры https://old.guess‑word.com.

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

Отдельно для каждой комнаты в течение 24 часов мы храним кэш вида «{room_id}{word} → order».

Если подумать, почему бы не хранить кэш в виде «{word_id}{word} → order»? И при каких обстоятельствах следует хранить рейтинг конкретного слова в комнате через ключ «{room_id}{word}»?

Жду ваши догадки в комментариях! Возможно, именно вы узнаете о наших планах на будущее:)

Клиентская часть (не совсем)

В отличие от NLP и Backend‑разработки я не занимался клиентской разработкой профессионально. Поэтому эта часть до сих пор остается для меня самой сложной. Для стартового запуска игры была написана простенькая html страница с одним запросом в бекенд. Для второй версии нужно было учесть много других факторов и построить проект по оптимальной архитектуре.

И, конечно же, у нас есть для вас забавная история.

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

Мы так спешили, что отдали задачу человеку с большим опытом в разработке веб‑клиента. Предложили 2 варианта: долю в проекте и дальнейшее сотрудничество или разовую оплату. Выбрали последнее. Весь клиент был сделан быстро, без критичных багов и с масштабируемой архитектурой. Но интересно, что через несколько дней подрядчик выпустил такую же игру. Сказать что мы были удивлены — ничего не сказать.

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

Схема продвижения и модель монетизации

Друзья. На digital‑рекламу у нас не хватило бы средств, поэтому рассчитывали только на естественный прирост. Тут нам помогли друзья — они раскрутили колесо игры до огромных размеров!

Сперва в «Отгадай слово» играли наши друзья, потом друзья друзей, а потом и остальные. Мы начали замечать, что на графике новых пользователей происходят резкие скачки вверх. Оказалось, что в «Отгадай слово» играют стримеры — стендапер Дмитрий Гаврилов развлекает так свою аудиторию.

Если вы стример, то предлагаем вам поиграть в игру в реальном времени и оценить реакцию зрителей. Уверены, она будет отличной!

Телеграм‑канал. Даниил Охлопков — победитель Forbes 30 до 30 — в нашей игре обратил внимание на метрику Retention и рекомендовал добавить Browser Notification. Однако мы не знали, как работают эти уведомления, и вдобавок, всегда сами отключали:) Гриша предложил создать телеграм‑канал, где игроки были бы постоянно на связи. Подписываться людей побуждает то, что в игре нет ни подсказок, ни ответов, а в коллаборации с другими можно угадать слово.

Слова пользователей. До последнего мы не хотели видеть рекламу на сайте как единственную возможность зарабатывать и окупать сервера. В итоге мы пришли к выводу, что нужно давать возможность задавать свои слова. Получается отличная модель монетизации. Мы дали возможность создавать комнату нашим премиум‑подписчикам в Boosty.

Всего есть три уровня подписки:

  1. Группа поддержки. Для тех кто хочет просто поддержать проект. Деньги в основном идут на аренду серверов.

  2. Премиум. Подписчики этого уровня получают возможность создавать свои собственные комнаты с заданным словом.

  3. Со‑автор. Пользователи могут задавать свои слова в основную комнату.

Сейчас у игры «Отгадай слово» 32 платных пользователя в Boosty, в месяц получаем 4200 рублей. Тем самым покрывается аренда сервера (2100 рублей). Поэтому можно считать, что разработка игры вышла в ноль.

Сейчас думаем над другими видами монетизации, вот некоторые из них:

  • аренда рекламной площадки,

  • продажа возможности задать слово дня,

  • продажа комнат со своими словами,

  • создание фиксированных комнат с возможность покупать/продавать как NFT,

  • Boosty и Patreon,

  • Onlyfans.

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

Ошибки

Мы за это время сделали много ошибок, решили поделиться самыми яркими.

1. Начали раскрутку, когда сайт выдерживал только 20RPS

Как только мы запустили первую версию игры, я написал Наталье Давыдовой, основательнице движения для помощи Frontend‑разработчиков в Точке. Давно следил за ее новостями и твитами, набрался смелости и написал в телеграм. Наталья лайкнула твит про игру, это увидели потенциальные игроки. Но наш сервер не был готов к наплыву пользователей — 24 тысячи людей за ночь. Поэтому мы просто упустили эту возможность.

Посетители по Яндекс.Метрике от 22 ноября
Посетители по Яндекс.Метрике от 22 ноября

2. Не проверяли топы слов

В первой версии мы задавали слова, не учитывая топ слов. Но игроки ставили себе цель угадать первые 10. Если в топе случайно оказывался глагол или производные в уменьшительно‑ласкательной форме, то начинался сущий кошмар (strike) — игрокам не нравилось. Поэтому мы начали проверять и чистить слова до топ-100.

3. Ошибка с рекламой

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

Реклама на сайте
Реклама на сайте

4. Релиз второй версии… без тестирования

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

На тесте все работало... Но вот мы делаем релиз, и обработка введенного слова длится по 20 секунд. А игра ехидно пишет: «Секунду…» (таков наш UX). Периодически затиралась история пользователей, пропадал последний символ вводимого слова и даже появлялась история других игроков.

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

В итоге мы вернули старый вариант игры и открыли новый домен для тестирования. А уже через месяц запустили вторую версию.

5. Попытки решить все проблемы апгрейдом серверов

Во время наших фиксов аренда сервера достигала 11 000 рублей в месяц, а рентабельность игры падала очень быстро. И даже такие мощные сервера не спасали от зависаний. Оказалось, что у нас был bottle neck при обращении к базе во время высчитывания ранга слов. Мы провели тотальную оптимизацию, и теперь текущую нагрузку в 5 000 визитов ежедневно у нас выдерживает сервер за 2 000 рублей в месяц.

Во время разработки игры «Отгадай слово» мы набили шишки и сделали выводы. Это поможет нам в других проектах. Ведь главное не зацикливаться на совершенных ошибках и идти только вперед.

Благодарности

Мы благодарны Всеволоду Гаргуше, Алексею Фадееву за помощь с релизом второй версии. Денису — за предоставленные эмбединги, играть стало намного лучше! Юрию Михайлову — за постоянные консультации. А также спасибо всем нашим друзьям, знакомым и, конечно же, игрокам. Без вас игра «Отгадай слово» не была бы такой классной!

Планы на будущее

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

С другой стороны, у нас накопились данные о попытках игроков. Это возможность автоматически подбирать ассоциативные цепочки слов, как в sociation, тем самым строить совсем новые методы для формирования эмбеддинга слов. Чтобы развить эту идею, мы планируем провести соревнование среди программистов. Задача — написать алгоритм, который за оптимальное количество попыток сможет угадать загаданное слово. Для соревнования поделимся всеми собранными данными. Так что, Open Data Science, если вам это интересно, давайте вместе делать классные вещи!

Мои контакты:
Телеграм: @loalkota
Почта: egorovmichil9@gmail.com

Теги:
Хабы:
Всего голосов 33: ↑33 и ↓0+33
Комментарии23

Публикации

Информация

Сайт
ai.itmo.ru
Дата регистрации
Численность
2–10 человек
Местоположение
Россия
Представитель
Linda Girsh

Истории