Как стать автором
Обновить
505.22
YADRO
Тут про железо и инженерную культуру

Три инженера, три месяца, три RTX 4090: как мы улучшили умную клавиатуру для планшетов KVADRA_T

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

Привет, Хабр. Меня зовут Вадим Воеводкин, я инженер по разработке ПО искусственного интеллекта в YADRO. Наша команда получила задачу улучшить предиктивный ввод на планшетах KVADRA_T. В B2B/B2G-сценариях счет идет на секунды: инспектор на рейде, врач в приемном покое или оператор в пункте обслуживания печатает быстрее, когда система ускоряет ввод текста. Поэтому улучшение предиктивного ввода — не просто фича, а реальный способ сэкономить время и повысить продуктивность. Расскажу, какой непростой, но интересный путь мы прошли и каких результатов добились.

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

Word Prediction и Word Completion

Для начала разобьем общую задачу улучшения предиктивного ввода на простые подзадачи. В качестве примера будем использовать фразу «I saw a cat on a table». 

Здесь наша задача делится на две:

Word Completion (WC) — подсказывает, какое слово вы хотите ввести, если введена хотя бы первая буква. Начинаем вводить «saw», и клавиатура уже предлагает подходящий набор слов. 

Word Prediction (WP) — предсказание следующего слова, которое пользователь еще не начал набирать. Из примера видно, что предлог «on» клавиатура предсказала вполне успешно.

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

Бенчмарки: с чем мы сравнивали нашу клавиатуру

Чтобы проверить, насколько хорошо планшет KVADRA_T справится с рабочими задачами, мы взяли для сравнения наиболее качественные решения для планшетов и смартфонов. Конкретные названия по понятным причинам мы называть не будем, ограничимся условными: клавиатуры M, G и Y. 

В качестве датасетов мы использовали приватные и open source-наборы данных, которые описывают стандартное использование клавиатуры пользователями: ввод сообщений, комментариев и небольших текстов. 

Язык

Тексты из мессенджеров

Комментарии

Ru

≈ 14k уникальных диалогов из двух или трех сообщений. ≈ 95k строк

≈ 40k комментариев к фильмам, книгам и так далее

En

≈ 13k уникальных диалогов из двух или трех сообщений. ≈ 85k строк 

≈ 32k комментариев к фильмам, книгам и так далее

Чтобы получить отправную точку для улучшения, то есть оценить дефолтную клавиатуру планшета KVADRA_T и конкурентов, нам нужно:

  • выбрать релевантные метрики, 

  • создать единую методологию замеров,

  • протестировать клавиатуры на тестовых наборах данных.

Метрики и методология сравнения клавиатур

Разобраться в работе разных моделей предиктивного ввода нам помогли научные публикации. Все подзадачи предиктивного ввода так или иначе являются задачами моделирования языка (language modeling). Есть сформировавшийся набор метрик типа perplexity и cross-entropy, которые показывают вероятностную оценку качества предсказаний. Но мы решили, что с точки зрения пользовательского опыта самой показательной метрикой будет количество сэкономленных нажатий (keystroke savings, KSS) при вводе текста. Чем меньше «тапов» по клавиатуре сделает пользователь, тем лучше работает предиктивный ввод.

Для корпоративных сценариев это особенно важно: кассир в ритейле вводит несколько тысяч символов за смену, а инспектор ГИБДД может заполнять протокол в глубинке, без надежного интернет-соединения. Кроме скорости важна еще и работа модели в офлайне — это критичное требование многих госзаказчиков, где облако недоступно.

Мы вычисляли метрику KSS по этой формуле:

KSS = \frac{keys_{normal} - keys_{predicted}}{keys_{normal}}

В ней: 

  • keys_{normal} — общее количество нажатий без предиктивного ввода,

  • keys_{predicted} — с предиктивным вводом. 

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

  • WPR — Word Prediction Ratio, процент предсказанных слов,

  • WCR — Word Completion Ratio, процент дополненных слов. 

Если успешных предсказаний мало, то пользователь может перестать обращать внимание на эти подсказки.

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

  • AWPL — Average Word Prediction Length, средняя длина предсказанного слова,

  • AWCL — Average Word Completion Length, средняя длина завершенного слова

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

Сравним клавиатуры по KSS. Замер производительности мы проводили с помощью автотестера, который создал мой коллега @masdevas Этот инструмент с помощью Android Studio и OCR автоматически запускает клавиатуры и собирает метрики.

Для сбора метрик определимся с подходом для подсчета keys_{normal} и keys_{predicted} и детально разберем пайплайн работы моделей предсказания и дополнения слов. Он включает две модели: Word Prediction и Word Completion. Этот пайплайн важно разобрать, чтобы понять, что Word Completion и Word Prediction не конкурируют друг с другом, а, наоборот, дополняют. Их работа четко разграничена, рассмотрим ее на примере фразы про кота:

На анимированной схеме выше пользователь вводит текст (printed string), а модели WC и WP дают подсказки. Ниже — строка с конкретными подсказками (prompt set). Модель WC включается, когда пользователь начал набирать слово, а WP — после ввода пробела. Слева расположен счетчик с нажатиями (keys normal), сэкономленными «тапами» (keys saved), количеством введенных (total words) и предсказанных (words predicted) слов. 

Получили нужную фразу, посмотрели на милого котика и переходим к анализу
Получили нужную фразу, посмотрели на милого котика и переходим к анализу

Рассчитаем метрику KSS:

KSS = \frac{11 – 5}{11} * 100 = 54.55\%$,  $WPR = 25 \%

Здесь 11 — количество нажатий, а 5 — количество сэкономленных «тапов». Мы рассматриваем модели WC и WP в связке для расчета целевых метрик, так как они дополняют друг друга. 

Сравниваем предиктивный ввод популярных клавиатур и KVADRA_T

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

Напомню, что мы обозначили клавиатуры конкурентов как G, M и Y. 

Метрики KSS для русского и английского языков
Метрики KSS для русского и английского языков

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

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

В последние годы задача моделирования языка вышла на передний план исследований и разработок — не в последнюю очередь благодаря успехам больших языковых моделей (LLM) и архитектуры трансформеров. 

Мы не стали использовать трансформеры, так как на момент проведения экспериментов для достижения нужного качества было достаточно более простых архитектур. Кроме того, архитектуры, представленные ниже, показали себя более подходящими по производительности. Мы решаем прикладную задачу — ускорить набор текста на Android-клавиатуре, поэтому в нашем случае простота и скорость важнее масштабируемости модели.

Мы остановились на архитектуре RNN (Recurrent Neural Network) с некоторыми модификациями. Расскажу о том, что получилось у трех AI-инженеров YADRO за три месяца работы с тремя картами RTX 4090.

Какие модели мы исследовали и протестировали

Мы провели много экспериментов с той или иной степенью успешности, но я расскажу только о самых интересных и репрезентативных. Рассмотрим три семейства моделей. Каждое — это определенный архитектурно-концептуальный подход к решению задачи моделирования языка. Одно из ключевых различий между ними — минимальная неделимая единица. Она подается на вход модели и предсказывается на выходе.

Например, модель, которая работает с целыми словами, невозможно применить для завершения слов. Char based-модель можно использовать сразу для решения двух задач: WC и WP. Однако она требует более сложный пайплайн для генерации одного предсказания. Начнем обзор именно с этой модели.

Char based-подход: от букв к словам

На первый взгляд, все просто. Модель состоит из двух основных частей: энкодера и декодера. Энкодер как губка (RNN, GRU, LSTM или другие) впитывает информацию о входных символах, таких как буквы и знаки препинания, а затем преобразует ее в единый вектор. Этот вектор можно представить как компактное математическое описание входных данных. Для этого энкодер использует различные типы рекуррентных нейронных сетей (RNN), такие как LSTM или GRU, которые способны «запоминать» контекст и последовательность символов.

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

Проблемы предиктивного ввода

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

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

Ошибки и неточности. Еще одна сложность — сгенерированное слово может быть написано с ошибками. Например, вместо «кот» модель может выдать бессмысленное «кпт». Чтобы минимизировать такие ошибки, нужно внедрять дополнительные механизмы проверки, такие как словари или правила орфографии. Но это добавляет еще больше сложностей в работу системы.

«Жадные» алгоритмы и их ограничения. Для выбора наиболее вероятной последовательности символов часто используется «жадный» (greedy) алгоритм. На каждом шаге своей работы он выбирает символ с наибольшей вероятностью. Однако этот подход не всегда дает оптимальный результат. Давайте посмотрим на формулу:

arg \max_y (\prod_{t = 1}^{n}(p(y_t|y_{< t })) ) \neq  \prod_{t = 1}^{n} arg \max_y (p(y_t|y_{< t }) )

В ней y_t — \text {предсказанный токен на шаге} t, n — \text {длина предложения или слова которое мы хотим предсказать моделью}.

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

Решаем проблемы с Beam Search

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

Пример работы Beam Search
Пример работы Beam Search

На схеме видно, как алгоритм отслеживает несколько параллельных путей и находит более качественные решения, чем «жадный» алгоритм. В нашем случае размер окна (beam width) составляет три слова. Beam Search не гарантирует идеального результата, но предлагает разумный компромисс между точностью и вычислительными затратами.

Основные особенности Сhar based-модели:

  • отсутствуют ограничения на язык и тексты словаря — работа на уровне символов делает модели универсальными,

  • компактность — один миллион  параметров, что позволяет эффективно работать даже на мобильных устройствах,

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

  • обеспечивает значительную экономию нажатий — средний прирост метрики KSS в 1,63 раза для английского и 1,23 для русского языков.

Семейство Word RNN: когда слова важнее букв

Если семейство моделей Char RNN учится «думать» на уровне отдельных символов, то семейство моделей Word RNN поднимается на новый уровень абстракций и оперирует сразу целыми словами. Это концептуальное отличие открывает новые варианты решения задачи Word Prediction, однако для задачи Word Completion семейство Word RNN подходит не очень хорошо. 

Схема работы модели Word RNN
Схема работы модели Word RNN

Концептуально модель максимально похожа на Char RNN и также состоит из энкодера и декодера. В основе декодера лежит словарь, размер которого является гиперпараметром. Он составлен на основе тренировочного и тестового датасетов и содержит несколько тысяч наиболее употребляемых слов. 

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

Однако за этой кажущейся простотой скрывается более объемная модель. С увеличением размера словаря растет и количество параметров модели — в нашем случае до 4,5 миллионов при размере словаря в 20 тысяч слов. Это уже не Char RNN с его компактностью, а полноценная «тяжеловесная» система, требующая больше ресурсов для обучения и работы по сравнению с Char RNN-семейством.

Процесс обучения Word RNN можно описать как «легкий вход, но с некоторым трудом». Модель получает на вход слово и предсказывает следующее — все просто. Однако из-за большого количества параметров и необходимости обрабатывать длинные последовательности обучение становится более ресурсоемким по сравнению с Char RNN, хотя сам процесс остается достаточно прямолинейным и понятным.

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

Отмечу, что модели Word RNN и Char RNN часто работают вместе и дополняют друг друга. Например:

  • Char RNN работает над завершением незаконченных слов (Word Completion): если пользователь ввел «прив», модель может предложить «привет»,

  • Word RNN сосредоточена на предсказании следующего слова целиком (Word Prediction): после «привет» модель может предложить «как дела?».

Такое разделение труда позволяет достичь максимальной эффективности. В результате совместной работы этих двух семейств моделей удалось добиться прироста KSS в 1,06 раз, что выводит наше решение для русского языка на уровень популярных клавиатур, таких как клавиатура G:

Особенности модели Word RNN:

  • словарь содержит более 20 тысяч слов, чего хватает для большинства повседневных сценариев ввода текстов,

  • 4,5 миллиона параметров, которые помогают точнее предсказывать слова,

  • простое обучение модели и реализация пайплайна,

  • в связке с Char RNN модель позволяет догнать одну из популярных клавиатур, что подтверждает эффективность такого подхода.

Char CNN + RNN: когда символы встречаются со сверточными сетями

Представьте себе модель, которая сочетает в себе точность работы на уровне символов и мощь глубоких нейронных сетей. Именно так работает Char CNN+RNN — гибридная архитектура, объединяющая сверточные сети (CNN) для обработки символов и рекуррентные сети (RNN) для предсказания слов. Давайте разберем, как это работает и почему этот подход оказался настолько эффективным.

На вход модели подается Char ID vector — вектор фиксированной длины, например 25 символов. Это число выбрано не случайно: оно достаточно для покрытия большинства слов как в русском, так и в английском языках. Каждому символу из словаря соответствует уникальный порядковый номер (ID), который записывается в соответствующую позицию вектора. Например, для слова «saw»:

  1. На первую позицию ставится ID буквы «s».

  2. На вторую — ID буквы «a».

  3. На третью — ID буквы «w».

  4. Остальные позиции заполняются нулями.

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

Архитектура Char CNN + RNN = идеальный тандем

Разделим эту архитектуру на две части, CNN и RNN, и рассмотрим каждую подробнее. 

Архитектура Char CNN + RNN
Архитектура Char CNN + RNN

RNN-часть похожа на ту, что мы используем в Char RNN и Word RNN. Этот слой преобразует размерность вектора до необходимой для декодера CNN, поэтому модель может эффективно комбинировать преимущества обеих архитектур.

CNN-часть формирует вектор числовых значений (embedding vector), который содержит информацию о каждом слове. Входные данные содержат вектор из нулей и ID символов. Сначала они проходят через несколько сверточных слоев (convolutional layers), которые анализируют локальные зависимости между символами и выявляют паттерны: суффиксы, префиксы или повторяющиеся последовательности. Благодаря сверточным слоям мы можем отказаться от позиционного кодирования (positional encoding). Затем данные обрабатываются через слой pooling, который уменьшает размерность, но сохраняет ключевые особенности. Результирующие значения конкатенируются в единый вектор.

Чтобы улучшить качество представлений, мы добавили highway network — механизм, который позволяет модели «перепрыгивать» через сложные трансформации и извлекать больше полезной информации. Он дал заметный прирост по метрикам, которые мы раскроем ниже.

Раздельное обучение для Word Prediction и Word Completion

Особенность модели Char CNN + RNN в том, что мы обучаем ее решать две разные задачи: завершать вводимое слово (Word Completion) и предсказывать следующее слово (Word Prediction). Эти задачи требуют разных подходов, поэтому мы используем раздельное обучение для каждой. 

Word Completion помогает быстро набрать слово, которое вы уже начали вводить: после набора «ко» модель должна предложить «кот», «котик» или другое подходящее слово.

Для решения этой задачи мы используем Char CNN-часть модели, которая работает на уровне символов по такому алгоритму:

  1. На вход подается частично введенный текст: например «ко».

  2. Модель преобразует его в вектор Char ID и обрабатывает через сверточные слои, чтобы выделить паттерны символов.

  3. Данные проходят через слой pooling и конкатенируются в единый эмбеддинг.

  4. Этот эмбеддинг используется для предсказания наиболее вероятного завершения слова.

Мы обучаем Char CNN-часть на задаче восстановления исходного слова: модель получает на вход неполное слово «ко» и учится восстанавливать его до полной формы «котик». Так она учится кодировать символы и извлекать полезные паттерны для завершения слов.

В отличие от Word Completion, задача Word Prediction заключается в том, чтобы предсказать следующее слово в последовательности слов. Например, после фразы «я вижу» модель должна предложить «котика», «щенка» или «котенка». Для этой задачи мы используем RNN-часть модели, которая работает на уровне контекста следующим образом:

  1. На вход RNN подается последовательность уже введенных слов, например «я вижу».

  2. Модель анализирует контекст и генерирует вектор, который содержит информацию о предыдущих словах.

  3. Этот вектор передается через двухслойную LSTM и fully connected linear layer, чтобы преобразовать его в размерность словаря.

  4. На выходе мы получаем вероятности для каждого возможного следующего слова.

Обучение RNN-части происходит отдельно от Char CNN: модель учится предсказывать следующее слово на основе контекста. Например, если входная последовательность — «я вижу», модель должна научиться предлагать слова, которые чаще всего следуют за этим выражением.

Результаты: мы обогнали клавиатуры G и M

Модель Char CNN + RNN показала впечатляющие результаты, особенно для английского языка. Мы заменили Char RNN на Char CNN + RNN для задачи Word Completion и добились дополнительного прироста в 21%. Это позволило не только догнать, но и обогнать популярные клавиатуры G и M:

Для русского языка прирост был менее значительным — 8%, но даже это позволило нам вплотную приблизиться к клавиатуре M. Отмечу, что работа с русским языком оказалась сложнее из-за его морфологической сложности, но результаты все равно вдохновляют.

Особенности работы с русским языком: нужно расширить словарь

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

В английском языке у одного слова может быть лишь несколько форм (например, «run», «runs», «running»). В русском же языке это разнообразие многократно возрастает:

  • глаголы спрягаются по временам, лицам и числам, а в прошедшем времени — по родам,

  • существительные склоняются по падежам и числам, а также у них есть постоянный грамматический род,

  • прилагательные согласуются с существительными в роде, числе и падеже.

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

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

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

Ключевые выводы по работе с моделью Char CNN + RNN:

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

  • количество параметров составило 4,5 миллиона, но это оправдано качеством предсказаний,

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

  • метрика KSS в среднем выросла в 1,21 раза для английского и в 1,08 раза для русского языков.

Результаты использования CNN RNN для Word Completion

Сразу скажу, что мы были приятно удивлены. В предыдущем лучшем решении использовалась Char RNN для Word Completion и CNN RNN для Word Prediction. Мы заменили для Word Completion модель на CNN RNN и добились прироста на 118% для английского языка по сравнению с дефолтной клавиатурой KVADRA_T. Это ключевой момент, на который стоит обратить внимание. Если раньше мы только стремились приблизиться к клавиатуре G, то теперь обогнали и ее, и клавиатуру M:

Обратите внимание, что увеличив размеров русского словаря до 40 тысяч слов мы добились прироста на 3% по метрике KSS: два крайних столбца CNN-RNN small и CNN-RNN big. 

Для русского языка прирост оказался менее значительным — 60%. Однако это тоже отличный результат, так как мы почти достигли уровня клавиатуры M. В английском языке мы уверенно показали прогресс, но в предсказании русских слов еще есть определенные трудности.

Особенности модели CNN RNN для Word Completion:

  • размер словаря мы увеличили до 40 тысяч слов,

  • количество параметров составило 4,5 миллиона,

  • процесс обучения и реализации пайплайна имеет среднюю сложность,

  • KSS в среднем вырос в 2,18 раза для английского и в 1,6 для русского языков. 

Заключение 

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

Мы уже интегрировали протестированные нами модели в приложение для KVADRA_T. Но это только начало долгого пути разработки SOA-решения. Параллельно мы развиваем собственный MlKit SDK, который помогает в сложных бизнес-кейсах: от аутентификации по лицу и вычислительной фотографии до интеллектуальной обработки документов, включая удаление теней и улучшение читаемости. Следите за нашим блогом — будем рассказывать о самых интересных разработках.

Наконец, хочу поблагодарить своих коллег: @alexgr52и @dok-luck. Вместе с ними мы улучшили умную клавиатуру и написали эту статью. Будем рады ответить на ваши вопросы в комментариях.

Теги:
Хабы:
+14
Комментарии0

Публикации

Информация

Сайт
yadro.com
Дата регистрации
Дата основания
Численность
5 001–10 000 человек
Местоположение
Россия
Представитель
Ульяна Соловьева