Суман Ананд, Элиза Монакки, Даррелл Тай, Юн Цзоу, Ван Линг Гуай

В предыдущих статьях (см. Внутренняя чат-платформа Grab, Распределение рабочей силы) мы рассказывали, как чат превратился в один из основных каналов поддержки за последние несколько лет.

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

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

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

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

Как работает смарт-чат

Погружение глубже в проблему

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

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

После некоторого анализа использования стороннего инструмента чата мы обнаружили, что даже с такими функциями, как стандартные сообщения, 85% сообщений по-прежнему печатались бесплатно.

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

Нам нужно было что-то, что сокращает время набора текста, а также:

  • Допускает некоторую степень персонализации, чтобы ответы не казались механическими и повторяющимися.
  • Работает с несколькими языками и нюансами, учитывая, что Grab работает на 8 рынках, даже на некоторых английских рынках есть небольшие различия в часто используемых словах.
  • Она зависит от проблемы и учитывает тип пользователя, сообщение о проблеме и даже время суток.
  • В идеале не требуется никаких усилий по обслуживанию, например необходимости обновлять шаблоны при изменении политик.

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

Удобство использования — ключ к успеху

Чтобы выполнить гипотезу, есть несколько конструктивных соображений:

  1. Минимизация кривой обучения для агентов.
  2. Избегайте визуального беспорядка, если рекомендации не актуальны.

Чтобы увеличить вероятность предсказания сообщения агента, одно из исследований дизайна состоит в том, чтобы позволить агентам выбирать 3 лучших прогноза (Дизайн 1). Для бортовых агентов мы разработали быстрый совет по активации SmartChat с помощью сочетаний клавиш.

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

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

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

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

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

Мы поняли, что на время набора текста агентом влияют:

  • Процент сохраненных символов. Это говорит нам о том, что модель спрогнозировала правильно, а также сэкономила время. Этот показатель должен увеличиваться по мере улучшения модели.
  • Эффективность модели. Агент записывает наименьшее возможное количество символов, прежде чем получить правильное предложение, которое должно уменьшаться по мере обучения модели.
  • Показатель принятия. Это говорит нам, сколько сообщений было написано с помощью модели. Это хороший прокси для использования функций и возможностей модели.
  • Задержка. Если предложение не появится примерно через 100–200 мс, агент не заметит текст и продолжит печатать.

Архитектура

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

У нас есть внутренняя платформа под названием Catwalk, которая представляет собой микрослужбу, предлагающую возможность запуска моделей машинного обучения в качестве службы HTTP. Мы использовали механизм запросов Presto для расчета и анализа результатов эксперимента.

Проектирование модели машинного обучения

Я уверен, что все мы помним эксперимент, который мы проводили в школе, когда нам нужно было поймать падающую линейку. Для тех, кто не проводил этот эксперимент, смело попробуйте это дома! Цель этого эксперимента — определить приблизительное число для типичного времени реакции человека (уравнения также включены в ссылку на видео).

Обычно время реакции человека колеблется от 100 мс до 300 мс, при медиане около 250 мс (подробнее здесь). Следовательно, при выборе подхода мы решили установить верхнюю границу времени отклика SmartChat на уровне 200 мс. В противном случае это повлияет на опыт, поскольку агенты заметят задержку в предложениях. Чтобы достичь этого, нам пришлось управлять сложностью модели и гарантировать, что она обеспечивает оптимальные временные характеристики.

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

Таким образом, были рассмотрены несколько ключевых компонентов:

Токенизация модели

  • Токенизация ввода/вывода модели должна быть реализована вместе с базовой логикой модели, чтобы это выполнялось в одном сетевом запросе.
  • Токенизация модели должна быть легкой и дешевой для вычислений.

Архитектура модели

  • Это типичная задача от последовательности к последовательности (seq2seq), поэтому модель должна быть достаточно сложной, чтобы учитывать авторегрессивный характер задач seq2seq.
  • Мы не могли использовать модели, основанные на чистом внимании, которые обычно являются современными для задач seq2seq, поскольку они громоздки и требуют больших вычислительных ресурсов.

Служба моделей

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

Наше предлагаемое решение учитывает пункты, перечисленные выше. Мы выбрали для разработки Tensorflow (TF), хорошо поддерживаемую платформу для моделей машинного обучения и создания приложений.

Для языков на основе латиницы мы использовали простой токенизатор пробелов, который можно сериализовать в графе TF с помощью пакета tensorflow-text.

import tensorflow_text as text
tokenizer = text.WhitespaceTokenizer()

Для архитектуры модели мы рассмотрели несколько вариантов, но в итоге остановились на простой архитектуре рекуррентной нейронной сети (RNN) в структуре Encoder-Decoder:

Кодировщик

  • Токенизация пробелов
  • Однослойная двунаправленная RNN
  • Ячейка Gated-Recurrent Unit (GRU)

Декодер

  • Однослойная однонаправленная RNN
  • Ячейка Gated-Recurrent Unit (GRU)

Оптимизация

  • Принуждение учителей в обучении, жадное декодирование в производстве
  • Обучено с функцией кросс-энтропийных потерь
  • Использование оптимизатора ADAM (Kingma и Ba)

Функции

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

  • Прошлые разговоры между агентом чата и пользователем
  • Время суток
  • Тип пользователя (Водители-партнеры, Потребители и т.д.)
  • Точка входа в чат (например, статья об отмене заказа еды)

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

Например, модель лучше понимает характер времени при обращении к фразе «Доброе {Утро/День/Вечер}» с учетом введенного времени дня, а также способна интерпретировать время приема пищи в Дело о заказах еды. Например. «Мы связались с водителем, ваш {завтрак/обед/ужин} скоро прибудет».

Решение Typeahead для пользовательского интерфейса

С нашей целью обеспечить беспрепятственный показ предложений для их принятия, мы решили внедрить решение с опережением ввода в области ввода чата. Это решение пришлось реализовать с помощью библиотеки ReactJS, так как внутреннее веб-приложение, используемое нашим специалистом службы поддержки для обработки чатов, построено на React.

Было несколько способов добиться этого:

  1. Измените объектную модель документа (DOM) с помощью Javascript, чтобы отображать предложения, размещая их над HTML-тегом input в зависимости от положения курсора.
  2. Используйте редактируемый контент div и сделайте так, чтобы предложение span отображалось условно.

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

Однако, когда предложение будет принято, нам все равно потребуется обновить редактируемый элемент div с помощью манипуляций с DOM. Его нельзя добавить в состояние React, так как это создает задержки для пользователя, чтобы визуализировать то, что он печатает.

Вот фрагмент кода для реализации:

import React, { Component } from 'react';
import liveChatInstance from './live-chat';
export class ChatInput extends Component {
 constructor(props) {
   super(props);
   this.state = {
     suggestion: '',
   };
 }
 getCurrentInput = () => {
   const { roomID } = this.props;
   const inputDiv = document.getElementById(`input_content_${roomID}`);
   const suggestionSpan = document.getElementById(
     `suggestion_content_${roomID}`,
   );
   // put the check for extra safety in case suggestion span is accidentally cleared
   if (suggestionSpan) {
     const range = document.createRange();
     range.setStart(inputDiv, 0);
     range.setEndBefore(suggestionSpan);
     return range.toString(); // content before suggestion span in input div
   }
   return inputDiv.textContent;
 };
 handleKeyDown = async e => {
   const { roomID } = this.props;
   // tab or right arrow for accepting suggestion
   if (this.state.suggestion && (e.keyCode === 9 || e.keyCode === 39)) {
     e.preventDefault();
     e.stopPropagation();
     this.insertContent(this.state.suggestion);
     this.setState({ suggestion: '' });
   }
   const parsedValue = this.getCurrentInput();
   // space
   if (e.keyCode === 32 && !this.state.suggestion && parsedValue) {
     // fetch suggestion
     const prediction = await liveChatInstance.getSmartComposePrediction(
       parsedValue.trim(), roomID);
     this.setState({ suggestion: prediction })
   }
 };
 insertContent = content => {
   // insert content behind cursor
   const { roomID } = this.props;
   const inputDiv = document.getElementById(`input_content_${roomID}`);
   if (inputDiv) {
     inputDiv.focus();
     const sel = window.getSelection();
     const range = sel.getRangeAt(0);
     if (sel.getRangeAt && sel.rangeCount) {
       range.insertNode(document.createTextNode(content));
       range.collapse();
     }
   }
 };
 render() {
   const { roomID } = this.props;
   return (
     <div className="message_wrapper">
       <div
         id={`input_content_${roomID}`}
         role={'textbox'}
         contentEditable
         spellCheck
         onKeyDown={this.handleKeyDown}
       >
         {!!this.state.suggestion.length && (
           <span
             contentEditable={false}
             id={`suggestion_content_${roomID}`}
           >
             {this.state.suggestion}
           </span>
         )}
       </div>
     </div>
   );
 }
}

Решение использует пробел в качестве триггера для получения предложения из модели машинного обучения и сохраняет их в состоянии React. Затем прогноз модели машинного обучения визуализируется в динамически отображаемом диапазоне.

Мы использовали API window.getSelection() и range для:

  • Найти текущее входное значение
  • Вставьте предложение
  • Очистите ввод, чтобы ввести новое сообщение

При реализации также учитывалось следующее:

  • Кэширование. Вызовы API выполняются для каждого символа пробела для получения прогноза. Чтобы уменьшить количество вызовов API, мы также кэшировали прогноз до тех пор, пока он не будет отличаться от пользовательского ввода.
  • Восстановить заполнитель. Существуют поля данных, относящиеся к агенту и потребителю, такие как имя агента и номер телефона пользователя, и эти поля данных заменяются заполнителями для обучения модели. Реализация восстанавливает заполнители в прогнозе, прежде чем отображать его в пользовательском интерфейсе.
  • Управление внедрением. Поскольку развертывание осуществляется в процентах для каждой страны, реализация должна гарантировать, что только определенные пользователи могут получить доступ к прогнозам из модели чата своей страны.
  • Объединяйте и отправляйте показатели. Метрики собираются и отправляются для каждого сообщения чата.

Результаты

Первоначальные результаты эксперимента показали, что нам удалось сэкономить 20 % символов, что повысило эффективность наших агентов на 12 %, поскольку они могли быстрее решать запросы. Эти цифры превзошли наши ожидания, и в результате мы решили двигаться дальше, развернув SmartChat на региональном уровне.

Что дальше?

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

Поддержка нелатинских языков и кэширование

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

Непрерывное обучение

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

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

Выражаем особую благодарность Kok Keong Matthew Yeow, который помог создать архитектуру и масштабируемую реализацию.

Присоединяйтесь к нам

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

Наша миссия, основанная на технологиях и движимая сердцем, состоит в том, чтобы продвигать Юго-Восточную Азию вперед, создавая экономические возможности для всех. Если эта миссия говорит вам, присоединяйтесь к нашей команде сегодня!