Недавно мы поспорили с одним из моих друзей о том, стоит ли оставлять негативные отзывы и отрицательно оценивать чью-то работу. Например, приходишь в банк, а там тебе нахамил консультант. Я убеждён, что стоит, потому что без этой оценки человек будет продолжать хамить. Друг считает, что это большой минус в твою карму, нельзя же обижать людей, они сами всё поймут со временем. Примерно в то же время у нас прошёл хакфест для партнёров, на котором я увидел решение, способное сохранить карму каждого из нас. Грех таким не поделиться. Как вы уже догадались из названия, под катом речь пойдёт о разработке на основе когнитивных сервисов.
Введение
Здесь должен быть текст из серии «вы же знаете, как для любой компании важно оценивать качество обслуживания — это основа развития». На мой взгляд, это вполне банальные истины, поэтому опустим их.
Сохраняем карму, недорого
Сервис Heedbook, о котором я расскажу сегодня, обладает одним очень крутым преимуществом перед другими способами оценки работы сотрудников с клиентами — это автоматическая оценка эмоций клиента в режиме реально времени. То есть, возвращаясь к моему другу, его толерантность не спасёт консультанта от реальной оценки работы. И волки сыты, и овцы целы, и карма друга тоже.
Как это работает:
1. Сотрудник фронт-линии банка (или аптеки, или МФЦ, или магазина, или тому подобных предприятий) в начале рабочего дня входит в систему через браузер.
2. К нему приходит клиент, например, мой друг.
3. Система получает и анализирует видеопоток с веб-камеры в режиме реального времени в фоновом режиме.
4. Информация обрабатывается системами интеллектуального распознания эмоций, речи и других параметров клиента.
5. По результатам анализа система предоставляет детальную аналитику по структуре эмоций и доле положительных/отрицательных эмоций клиента, вниманию клиента к сотруднику, содержание диалога, использование скрипта обслуживание или запрещенных фраз.
6. Руководитель офиса и сотрудники головной компании получают в детальную информацию о качестве клиентского сервиса в разрезе менеджеров и клиентов. (И тут мы видим, как наш консультант начинает нервничать. :) )
Помимо описанного выше, для каждого сотрудника определяется среднее время обслуживания клиентов, количество обслуженных клиентов, структуру клиентской базы по демографическим показателям.
Ещё одна интересная фишка, директор может подключиться к видео-стриму от рабочих мест сотрудников фронт-линии, а также просматривать видео-записи диалогов позже с детальной аналитикой по ним. То есть в нашу историю добавляется новый сценарий, когда консультант начинает хамить моему другу, а потом внезапно у него округляются глаза и он становится милым и вежливым. :)
Ну и последняя интересная деталь, в Heedbook есть рейтинговая система для сотрудников.
Как это работает: глазами разработчика
Микросервисная архитектура Azure Functions
Мы вместе с Димой Сошниковым частично помогали ребятам в проектировании решения. Первое, что сделали — решили уйти от монолитной архитектуры и сделать систему построенную на микросервисах (как вы могли заметить по моим последним статьям, на мой взгляд это очень интересная тема). Для этого использовали Azure Functions. На самом деле мы также думали про WebJob, но у него есть ограничения по производительности и ценообразование идёт не от количества совершенных операций.
Основная среда разработки AF — онлайн редактор функций на портале Azure. Также с конца мая 2017 можно создавать AF с помощью Visual Studio 2017 UPD 3.
Так как AF — новый продукт Microsoft, по нему пока нет полной документации, поэтому ниже разберем пример одной AF проекта Heedbook. Это позволит сэкономить время, если вы решите построить микросервисную архитектуру на базе Azure.
Триггером запуска AF может стать Http запрос, появление Blob в Azure Blob storage, действия в OneDrive или просто таймер. В проекте реализованы практически все представленные выше варианты триггеры для AF. Также мы реализовали каскады AF, когда работа одной AF запускает другую, таким образом обеспечивая единый бизнес процесс анализа данных.
Пример нашей AF триггерится появлением блоба — картинки. При помощи данной AF мы с вами определим количество людей на картинке и их эмоции. Сделаем это с помощью когнитивного сервиса Microsoft Face API.
Сначала необходимо подключить необходимые библиотеки когнитивных сервисов. Для онлайн редактора AF сделать это придётся вручную, создав файл project.json и прописав туда все необходимые зависимости:
{
"frameworks": {
"net46":{
"dependencies": {
"Microsoft.ProjectOxford.Common": "1.0.324",
"Microsoft.ProjectOxford.Face": "1.2.5"
}
}
}
}
В случае с созданием AF в Visual Studio 2017 UPD 3 просто подключаем необходимые зависимости с помощью Nuget.
Дальше нам нужно прописать триггер AF и выходные параметры. В нашем случае — это появление блоба в определенном контейнере и запись результатов распознания в таблицу Azure MsSql. Делается это в файле function.json:
{
"bindings": [
{
"name": "InputFace",
"type": "blobTrigger",
"direction": "in",
"path": "frames/{name}",
"connection": "heedbookhackfest_STORAGE"
},
{
"type": "apiHubTable",
"name": "FaceData",
"dataSetName": "default",
"tableName": "FaceEmotionGuid",
"connection": "sql_SQL",
"direction": "out"
}
],
"disabled": false
}
Итак, сам код Azure Functions!
#r "System.IO"
using System.IO;
using Microsoft.ProjectOxford.Face;
using Microsoft.ProjectOxford.Common.Contract;
public static async Task Run(Stream InputFace, string name, IAsyncCollector<FaceEmotion> FaceData, TraceWriter log)
{
log.Info($"Processing face {name}");
var namea = Path.GetFileNameWithoutExtension(name).Split('-');
var cli = new FaceServiceClient(<Face_Api_Key>);
var res = await cli.DetectAsync(InputFace,false,false,new FaceAttributeType[] { FaceAttributeType.Age, FaceAttributeType.Emotion, FaceAttributeType.Gender});
var fc = (from f in res
orderby f.FaceRectangle.Width
select f).FirstOrDefault();
if (fc!=null)
{
var R = new FaceEmotion();
R.Time = DateTime.ParseExact(namea[1],"yyyyMMddHHmmss",System.Globalization.CultureInfo.InvariantCulture.DateTimeFormat);
R.DialogId = int.Parse(namea[0]);
var t = GetMainEmotion(fc.FaceAttributes.Emotion);
R.EmotionType = t.Item1;
R.FaceEmotionGuidId = Guid.NewGuid();
R.EmotionValue = (int)(100*t.Item2);
R.Sex = fc.FaceAttributes.Gender.ToLower().StartsWith("m");
R.Age = (int)fc.FaceAttributes.Age;
await FaceData.AddAsync(R);
log.Info($" - recorded face, age={fc.FaceAttributes.Age}, emotion={R.EmotionType}");
}
else log.Info(" - no faces found");
}
public static Tuple<string,float> GetMainEmotion(EmotionScores s)
{
float m = 0;
string e = "";
foreach (var p in s.GetType().GetProperties())
{
if ((float)p.GetValue(s)>m)
{
m = (float)p.GetValue(s);
e = p.Name;
}
}
return new Tuple<string,float>(e,m);
}
public class FaceEmotion
{
public Guid FaceEmotionGuidId { get; set; }
public DateTime Time { get; set; }
public string EmotionType { get; set; }
public float EmotionValue { get; set; }
public int DialogId { get; set; }
public bool Sex { get; set; }
public int Age { get; set; }
}
В данном случае это асинхронная процедура в связке с Cognitive Services Face API. AF получает Stream блоба и передает его в CS:
var res = await cli.DetectAsync(InputFace,false,false,new FaceAttributeType[] { FaceAttributeType.Age, FaceAttributeType.Emotion, FaceAttributeType.Gender});
Далее выбирает самое крупное лицо в кадре:
var fc = (from f in res
orderby f.FaceRectangle.Width
select f).FirstOrDefault();
И записывает результаты распознания в базу данных:
await FaceData.AddAsync(R);
Всё просто, не правда ли? Будущее за микросервисами. :)
О проблемах
Ладно, не всё так просто, на самом деле.
AF, к сожалению, на текущий момент имеет ряд ограничений (не работает биндинг имен, есть конфликты библиотек). К счастью, в мире разработки на .Net всегда есть множество walkarround — и если у вас не получается решить проблему по базовому сценарию, можно найти несколько обходных вариантов.
Съемка видео и аудио в фоновом режиме
Как известно, современные ОС стараются максимально экономить ресурс батареи, прекращая проактивную работу всех приложений, находящихся в фоновом режиме. Это касается и стрима в веб-, мобильном или десктопном приложением. Проведя длительную изыскательскую работу по данному поводу. мы сделали выбор в пользу веб-решения.
Видео- и аудиопоток от веб-камеры получаем с помощью GetUserMedia()
. Далее мы должны записать полученный видео- и аудиопоток и извлекать оттуда данные для передачи на бэкенд. Это работает, если окно браузера будет постоянно активным, как только вы делаете закладку браузера неактивной — становится недоступным для записи данных. Наша задача была сделать систему, которая будет работать в фоновом режиме и не будет мешать сотруднику осуществлять свои прямые обязанности. Поэтому решением стало создание собственной стримовой переменной, куда мы записываем и извлекаем данные видео- и аудиопотока.
Качество распознания русского языка
В будущем сервис будет двигаться к созданию своих собственных моделей распознания аудио, но на текущий момент приходится использовать внешних провайдеров услуг распознавания русского языка. Было сложно выбрать хороший сервис, обеспечивающий качественное распознание речи на русском. Текущая конфигурация системы использует комбинацию систем распознания речи, для русской речи используется Goolge Speech Api, в тестировании показавший лучшие результаты качества распознания.
Возвращаемся в реальность
На самом деле это решение — не просто сказки о будущем. В ближайшее время Heedbook начнёт работать в подмосковных МФЦ и крупнейшем банке страны.
Команда Heedbook будет признательна за комментарии по поводу их решения, а также будет рада сотрудничеству с профессионалами в области ML, анализа данных, SEO и работе с крупными клиентами. Пишите свои мысли в комментариях или на почту info@heedbook.com.
Автор: stasus