SpeechMarkup API - перетворюємо мова в дані


У статті піде мова про те, як з будь-якого запиту на природній мові отримати реальні дані, з якими може працювати ваше додаток. А саме, про REST API сервісу SpeechMarkup, який перетворює звичайну рядок тексту в JSON з усіма знайденими смисловими сутностями з конкретними даними в кожній з них.

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

У статті я розповім, для чого можна використовувати даний API і наведу невеликий приклад працюючого додатка.



Для чого нам це?
Сьогодні всі користувальницькі інтерфейси стають все більш мінімалістичним і простими. Дійсно, чим простіше інтерфейс, тим швидше і комфортніше буде користуватися вашим сервісом або додатком.
І замість того, щоб пропонувати користувачеві складні форми, в яких потрібно перемикатися між полями, щось набирати, де щось вибирати і т. д., буває простіше і зручніше ввести кілька слів на одному полі.

Більше того, наприклад, у Використанні в будь-який момент можна натиснути на микрофончик і вимовити ті дані, які не хочеться/незручно/довго вбивати руками. У iOS ситуація з голосовим уведенням теж покращилася у зв'язку з підтримкою російської в диктуванні. Вже сьогодні нічого не заважає прикрутити голосове введення до свого додатком, поставити роботів в колл-центр або навіть створити власного голосового асистента для розумного будинку.


Але навіть якщо не брати до уваги розпізнавання мови (ситуація з яким хоч і далека від ідеалу, але поліпшується рік від року), то можна сказати, що у багатьох випадках заміна форм на єдине поле зі звичайним текстовим введенням допоможе зробити сервіс більш зручним і зрозумілим.
Написав/сказав користувач, скажімо, «Два квитки пітер москва завтра вранці», і ваш сервіс тут же видав відповідні рейси! Або «В суботу в 6 вечора футбол» — і подія збереглося в календарі! «Михалич прийди завтра вранці на роботу раніше» і потрібного контакту пішла sms, або назначилась завдання в трекері завдань (а краще — і те і те).

Але не все так просто...
Ну добре, текст ми отримали від користувача (або від якої-небудь системи розпізнавання мови), і що далі з ним робити? Правильно — потрібно просто висмикнути з нього необхідні для нашого сервісу дані і все! Наприклад, дату і час рейсу, місто відправлення і прибуття. Або дату, час і текст нагадування.

Ну як просто… З'ясовується, що досить непросто…
З урахуванням того, що це природний мову, з притаманними йому особливостями, такими як морфологія, довільний порядок слів, помилки розпізнавання і т. п., завдання правильної інтерпретації навіть невеликого пропозиції в 5-10 слів стає дійсно складним.

Скажімо, дату можна вказати як абсолютну, так і відносну — "післязавтра" або "через два дні", "Другого грудня" або "В суботу". З часом — те ж саме. А числа можуть бути вказані і з допомогою цифр і слова! У міст є синоніми (Пітер, Санкт-Петербург, Ленінград), їх можна записати з дефісом і без (Нью-Йорк). А зрозуміти, що текст — це ПІБ, а дві поруч стоять прізвища — це різні люди, ще складніше…

Вам хочеться вирішити це за допомогою регекспов? Або копатися в премудростях NLP, мат-лінгвістики, теорії ШІ і т. п.? Ось і мені не хочеться. Тому що мені потрібно лише витягнути з рядка пару даних, які необхідні логікою мого додатка.
Що ж робити?

Читати далі
Тому що саме для вирішення цього завдання і потрібен такий API SpeechMarkup.
По суті він не виконує розпізнавання мови. Він отримує на вхід звичайну фразу, яку потім перетворює в JSON, де вказані всі сутності, наведені до потрібного формату. Скажімо, «Через п'ять хвилин» перетвориться в «18:15», «У суботу» — «15.11.2014» і т. д.

А точніше — ось приклад відповіді
{
"string": "через тиждень васі пупкіну з пітера виповнюється п'ятдесят два роки",
"tokens": [
{ 
"type": "Date",
"substring": "через тиждень",
"formatted": "17.11.2014",
"value": {"day": 17, "month": 10, "year": 2014}
},
{
"type": "Person",
"substring": "васі пупкіну",
"formatted": "Вася Пупкін",
"value": {"firstName": "Вася", "surName": "Пупкін"}
},
{ "type": "Text", "substring": "з", "value": "з" },
{
"type": "Центр",
"substring": "пітера",
"value": [{"lat": 59.93863, "lon": 30.31413, "population": 5028000, "countryCode": "RU", "timezone": "Europe/Moscow", 
"id": "498817", "name": "Санкт-Петербург"}]
},
{ "type": "Text", "substring": "виповнюється", "value": "виповнюється" },
{
"type": "Number",
"substring": "п'ятдесят два",
"value": 52
},
{ "type": "Text", "substring": "року", "value": "року" }
]
}


Як бачите, SpeechMarkup як би «розмічає» вихідний текст даними, які може знайти, і повертає в тому ж порядку, в якому вони йдуть в тексті.

Тобто, наше додаток може відправити рядок і отримати назад звичайний JSON, де кожна сутність має свій тип і певний формат, незалежний від мови вихідного запиту! Як написано в документації по REST API SpeechMarkup, на даний момент підтримуються сутності типу дат, часу, чисел, міст та ПІБ. Ну а все інше позначається як звичайний текст.

Кастомні сутностіСервіс з'явився нещодавно, але в планах надати користувачам сервісу створювати свої власні сутності і логіку їх перетворення в дані потрібного формату.


Важливо відзначити, що SpeechMarkup не працює з контекстом запиту. Іншими словами, це завдання конкрентного сервісу інтерпретувати дані, отримані з тексту. Тобто, якщо вашого сервісу не цікаві, скажімо, сутності ПІБ, то він може ігнорувати їх розмітку і працювати з ними як із звичайною рядком, якщо вона йому потрібна. Як це відбувається — покажу на прикладі.

Простий приклад застосування

В якості прикладу використання API візьмемо демо-проект, реалізує функціональність сервісу нагадувань. Звичайно, використовувати REST API може будь-який додаток на будь-якій платформі, написаний будь-якою мовою програмування, т. до. все, що потрібно, — це відправити HTTP запит з текстом і кількома параметрами і отримати назад JSON. У даному прикладі ми використовуємо JavaScript.

Отже, що робить наш тестовий сервіс нагадувань? Зберігає нагадування. Все, що потрібно від користувача — це ввести текст, який потім буде інтерпретований, і якщо в ньому є всі дані, то він перетвориться в напоминалку. Якщо в тексті є чиєсь ім'я, то воно додатково підсвічується в елементі списку. Можна спробувати поклікати на приклади.

Давайте подивимося на ту частину JavaScript коду, яка надсилає текст запиту і отримує назад відповідь, з якого конструює елемент списку з даними про дату, час і тексті нагадування.

Відправка тексту з параметрами
$('#form').bind('submit', function(event) {
event.preventDefault();
var val = $.trim(text.val());
if (val) {
var date = new Date();
$.ajax({
url: 'http://markup.dusi.mobi/api/text',
type: 'GET',
data: {text: val, timestamp: date.getTime(), offset: date.getTimezoneOffset()},
success: onResult
});
}
return false;
});


Тут все просто. Коли користувач надсилає форму, беремо значення поля з текстом і відправляємо його методом GET на markup.dusi.mobi/api/text
Ще 2 додаткові параметри потрібні для правильного перетворення дат і часу з тексту на стороні сервера SpeechMarkup. Це параметр timestamp, який представляє собою поточну дату-час клієнта в мілісекундах, і параметр offset, що містить зміщення часу UTC у хвилинах. Їх важливо вказувати, оскільки інакше сервер SpeechMarkup не дізнається, що для клієнта означає, наприклад, «через 5 хвилин».

А ось так виглядає код, який обробляє відповідь
function onResult(data) {
var resp = JSON.parse(data);
var item = createItem(resp);
if (!item.text) {
warning$.text('А що нагадати?');
} else if (!item.time) {
warning$.text('А скільки нагадати?');
} else {
warning$.empty();
if (!item.date) {
item.datetime = moment();
if (item.time.value.hour < item.datetime.hour()) {
if (!item.time.value.part && item.time.value.hour < 12 && item.time.value.hour + 12 > item.datetime.hour()) {
item.time.value.hour += 12;
} else {
item.datetime.add(1, 'd');
}
}
item.datetime.hour(item.time.value.hour).minute(item.time.value.minute);
} else {
item.datetime = moment([item.date.value.year, item.date.value.month, item.date.value.day, 
item.time.value.hour, item.time.value.minute]);
}
items.push(item);
appendItem(item, items.length - 1);
text.val(");
}
}


Так як ми працюємо з датами і часом, то зручно скористатися бібліотекою Moment.js.
Тут трохи більше коду, але він теж простий, і що найголовніше — він не оперує текстом, не парсити його, а працює з вже готовими даними, які сформував SpeechMarkup.

У цьому коді ми намагаємося за наявними даними сконструювати нагадування. А саме, якщо не вказаний текст або час, то сказати про це. А якщо все є крім дати, то зрозуміти з вказаного часу, на яку дату створити нагадування.

На початку методу ви бачили виклик createItem, який з відповіді збирає об'єкт для маніпуляцій. Ось його код
function createItem(resp) {
var tokens = resp.tokens;
var item = {text: tokens.length > 0 ? " : resp.string};
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i];
switch (token.type) {
case 'Person': item.text = $.trim(item.text + ' ' + '<span class="label label-warning">' + token.substring + '</span>');
break;
case 'Date': item.date ? item.text = $.trim(item.text + ' ' + token.substring) : item.date = token;
break;
case 'Time': item.time ? item.text = $.trim(item.text + ' ' + token.substring) : item.time = token;
break;
default: item.text = $.trim(item.text + ' ' + token.substring);
}
}
return item;
}


Власне це та частина, яка розбирає матч JSON від сервера і або додає якісь сутності до тексту нагадування, або до дати або часу.
Щоб повністю зрозуміти, що таке token чи substring, пройдемося трохи по API SpeechMarkup.

API SpeechMarkup
Як ми вже бачили, SpeechMarkup приймає на вхід рядок і кілька додаткових параметрів, а на виході віддає JSON з вихідної рядком (поле string) масивом знайдених сутностей (поле tokens). Якщо масив порожній, значить специфічних сутностей не знайдено і все є звичайним текстом (не забуваємо, що SpeechMarkup працює з певним набором сутностей, які незабаром можна буде доповнювати своїми власними).

Кожен token (сутність) — це об'єкт, в якому вказується тип сутності поле type), частина рядка, до якої вона відноситься (substring) і перетворене кінцеве языконезависимое значення (value). Для типу Text це поле містить саму підрядок.
Також може бути присутнім необов'язкове поле formatted для компактного подання даних. Наприклад, дата буде записана в форматі «DD.MM.РРРР», час — «HH:mm:ss», а тип Person — у вигляді «Прізвище Ім'я по Батькові».

Кожен тип сутності має свій формат значення в полі value. Для дат це об'єкт з полями day, month і year. Для часу — hour, minute, second.
Для міст це не об'єкт, а масив (оскільки існує багато міст з однаковою назвою). У кожному місті є координати, чисельність населення, код країни і стандартне назву.
В сутності типу Person (ПІБ) є поля firstName, surName та patrName, деякі з яких можуть бути відсутніми, якщо користувач вказав, наприклад, тільки ім'я.

Спираючись на ці дані, можна йти по токенам по порядку (т. к. вони йдуть точно в тому порядку, в якому зазначено в початковому тексті) і в залежності від типу сутності і його значення, застосовувати ту чи іншу логіку.
Якщо в тексті зустрічається декілька раз, то все окрім першого додаються до тексту. Те ж і з датами. Якщо в тексті є ім'я, то воно додатково виділяється в тексті.

У підсумку
SpeechMarkup пропонує безкоштовний API для розмітки сутностей в запитах на природному мовою, що дозволяє вашому додатку інтерпретувати в тому числі і мова, і звичайний текстовий enter. З часом користувачі API також зможуть створювати свої власні сутності і логіку їх перетворення в дані, що дозволить створювати обробники більш специфічних запитів.

Ось кілька посилань, які допоможуть дізнатися про проект більше і бути в курсі нововведень:
Сайт проекту SpeechMarkup
Документація на GitHub
Співтовариство розробників Google+

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.