Как да изградим разговорна AI система от край до край, използвайки поведенчески дървета

В основата си AI проектите могат да бъдат изобразени като обикновен тръбопровод от няколко строителни елемента. Диаграмата по-горе обяснява, че доста добре: Неструктурираното съдържание, обикновено в огромни количества данни, влиза отляво и се подава в AI класификатори. Тези предварително обучени машинно или дълбоко изучаващи модели разделят житото от плявата и намаляват входните данни до няколко числови или низови стойности на изхода.

Например, мега пикселите и цветовете в изображението се свеждат до етикет: това е жираф. Или зебра. При аудио милиони вълнови честоти произвеждат изречение чрез модели на реч към текст. И в разговорния AI това изречение може да бъде намалено допълнително до няколко низа, представящи намерението на говорещия и субектите в изреченията.

След като входът бъде разпознат, трябва да направим нещата и да генерираме някакъв смислен резултат. Например автомобил, разпознат твърде близо, трябва да завърти колелото в автономна кола. Искането за резервиране на полет трябва да доведе до някои заявки към база данни на RESTFul и POST обаждания и да издаде потвърждение или отказ на потребителя.

Последната част, показана на диаграмата като базирана на правила логика, е неразделна част от всяка AI система и няма промяна в погледа към това. Обикновено се прави чрез кодиране, хиляди и хиляди редове код - ако това е сериозна система - или няколко скрипта, ако е играчка за играчки.

Поведенчески дървета

Дърво на поведението е парадигма за програмиране, появила се във видеоигри, за да създаде подобни на човека поведения в герои, които не играят. Те формират отличен визуален език, с който софтуерен архитект, младши разработчик и дори некодер, технически дизайнер може да създаде сложни скриптове. В действителност, тъй като дървесните поведения (BT) позволяват логически операции като AND и OR, цикли и условия, всяка програма, която може да бъде създадена с код, може да бъде създадена с BTs.

Servo е AI разговорна рамка с отворен код, изградена на основата на рамка на дърво на поведението на JavaScript, наречена Behavior3. Той е проектиран да направи необходимата оркестрация на входове и изходи за разговорни AI системи. Това се нарича рамка с нисък код: трябва само да кодирате малко и повечето от задачите могат да се извършват във визуалния редактор.

Това не е твоята обичайна играчка за начинаещи: тя е проектирана да бъде разширена с помощта на реални файлове и класове на JS изходни файлове и класове и да спазва всички екипни методологии за управление на проекти. Освен това, той е подходящ за екипи, които увеличават размера си, позволявайки въвеждането и повторната употреба на нови модули чрез абстрахирани и отделени подпроцеси.

Аз съм основният разработчик на Servo. След 30-годишно кодиране, усещайки болката при дълго забавени проекти и гледам как наследствените системи се разбиват под собствената си тежест, исках да постигна максимална гъвкавост с минимално кодиране. Тук ще обясня магията, която може да се направи, когато човек комбинира дърветата на поведението с NLU / NLP двигателите, използвайки Servo и Wit.ai.

Всеки разработчик може да се възползва от този урок, но най-добре е, ако сте разработчик с опит в изграждането на чат или гласови ботове и имате знания за работа с NLU / NLP двигатели като LUIS, Wit.AI, Lex (Alexa двигателя) или Dialogflow. Ако не го направите, това е наред, но ще разгледам някои теми накратко.

Ако искате да научите за NLU и NLP двигателите, в интернет има отлични ресурси - просто потърсете „Урок за ума“. Ако искате да научите как да изградите асистент в тежка категория, тогава просто продължете да четете.

Първи стъпки със Servo

Няма да навлизам в подробности за инсталирането на Servo тук, но просто казвам, че да започнете със Servo е наистина лесно. Можете да прочетете за това тук. По същество вие клонирате репото на Github, npm-инсталирате го според readme и го стартирате локално. След това всеки нов проект би ви стартирал с малък готов бот, за да постигнете нещата:

Всеки нов проект има начално дърво

Тук можете да забележите зелените шестоъгълници, наречени „чит-чат“, „отмяна“ и други. В края на тази статия ще имате ясна представа какво представляват и как работят. Но първо, да се справим с първите предизвикателства.

Изграждане на НЛУ модел

Нека да поговорим за изграждането на банков асистент и по-конкретно за този, който работи за отдела за парични преводи. Ако беше уеб приложение, щяхме да имаме формуляр с няколко полета, сред които най-важни са сумата и акаунтът на получателя (наричан също бенефициент). Нека ги използваме тук за този урок. Всъщност, когато използваме NLU двигатели, все още можем да мислим за него като форми, като полетата вече се наричат ​​образувания (също слотове, в Alexa lingo). NLU двигателите също произвеждат намерение, което може да се разглежда като името на формата, която ще насочи асистента до областта на тази функционалност на намерението на потребителя.

Трябва да обучим двигателя на НЛУ с няколко изречения, като например:

  • „Бих искал да изпратя малко пари“
  • „Бих искал да изпратя 100 долара“
  • „Моля, преведете $ 490 в сметка № 01–10099988“

И за тях трябва да кажем на двигателя да изведе следното:

  • намерение на TransferIntent за такива изречения
  • Остроумие / число за сумата
  • AccountNumberEntity за сметка на бенефициента

Нека направим това на Wit.ai. Отново няма да вляза в урок за ума - има много ръководства. Servo се предлага с общ модел Wit, който можете да вземете от Github на Servo тук. След това отворете своето собствено приложение Wit и го импортирайте.

Създадох субект със свободен текст за номерата на сметките (тъй като номерът на акаунта може да включва други символи), както и остроумие / числово образувание за сумата. Открих, че съставните образувания също работят доста добре, въпреки че се нуждаят от известно обучение. За простота, за номера на сметки, обучих модела да бъде #, последван от 8 цифри.

По принцип винаги е по-добре да експериментирате с различни модели. В нашия случай може да получим две числа в едно и също изречение (номер на сметка и сума) и се нуждаем от начин да ги разделим, така че е най-добре, ако става дума за две различни имена на субекти. Но можете да опитате и други видове: ИИ все още е много емпирична наука ...

След това го обучихме с няколко изречения и оставихме Wit да изгради модела за банков превод. За удобство го добавих тук и също така настроих цялото ръководство за ръководство за банков бот да идва заедно с предварително заредените примери.

Обучение на вит с примерни изречения за банков превод

Последно, трябва да свържем НЛУ с асистента. Отидете на Настройки в Wit и копирайте маркера за достъп. Трябва да го залепим в свойствата на корена на нашето дърво. Правим това, като отваряме редактора на Servo, избираме корен, отваряме свойствата му и го поставяме под nlu. Както можете да видите, Servo поддържа многоезични асистенти и различни двигатели на NLU:

Стартирайте Bank Assistant

Сега можем да се обърнем към Servo. Трябва да конструираме малко дърво с въпрос за всяко образувание и намерение.

Като напомняне, основните правила на Servo поведение дървета са следните

  1. Основният цикъл на дървото изпълнява корена непрекъснато
  2. Всеки възел изпълнява своите деца
  3. Възел AskAndMap (възел „Възраст“ в диаграмата по-горе) извежда въпрос към потребителя и чака отговор
  4. След като се появи отговор, потокът се препраща към съответното дете според намерението и субектите, които двигателят на NLU го е дал

Нека първо променим основния, най-горния въпрос от „Възраст?“ В „Какво бихте искали да направите?“. Също така, нека изтрием първото (тоест най-лявото) дете и неговите възли, тъй като ние повече няма да ги използваме:

Въведете въпроса за първоначален асистент

Защо виждаме червените тирета около възела? Задръжте курсора на мишката върху него и ще видите грешката:

Броят на броя на контекстите трябва да е равен на броя на децата

Ще поправим това след минута.

Сега нека изградим трансферния поток. Ще приемем, че след като потребителят каже „Бих искал да превеждам пари“, искаме да се спуснем в първото, най-ляво дете. За това ще изберем възела „Как мога да ти помогна“ и ще влезем в неговите свойства. Там променете първия контекст, за да имате намерение на „TransferIntent“:

Това ще причини всяко изречение, което Вит определя, че има TransferIntent, да бъде насочено там.

Картографиране на образувание

Сега, след като НЛУ разпознае намерението ни да прехвърляме пари, трябва да получим всички различни „полета“ или образувания. Нека добавим възел за сумата:

Добавихме възел AskAndMap и зададохме подкана към въпрос за сумата. Променихме и заглавието му - винаги е добра практика. И накрая, не забравяйте да запазите работата си с бутона Save или Ctrl-S.

Можете също да забележите, че червеното предупреждение изчезна от възела Как мога да ви помогна.

И накрая, нека добавим числова единица към един от дочерните контексти на възела Amount и да картографираме стойността в поле, наречено сума.

„Контексти“: [
 {
   „Образувания“: [
    {
      „ContextFieldName“: „сума“,
      „Име на субект“: „номер“,
      „Очаквана стойност“: „“,
      „EntitIndex“: 0
    }
   ]
 }

Всичко това изглежда много просто и е: ако потребителят каже нещо като „Трябва да изпратя малко пари“, той ще бъде попитан: „Каква е сумата?“. След като те въведат сумата, номерът ще бъде извлечен от НЛУ и ще бъде картографиран към контекстната сума в Серво. След това можем да го използваме по-късно в играта. Визуално потокът започна от корена:

И асистентът би поискал:

"Как мога да ти помогна?"

Ако потребителят отговори:

„Бих искал да преведа малко пари“

NLU двигателят ще изведе TransferIntent и потокът ще продължи надолу по течението в контекста, който идентифицира - най-лявото дете - и зададе следващия въпрос, относно сумата:

Но какво ще стане, ако потребителят не въведе сума?

Строителни помощници

Възлите AskAndMap поддържат друг тип контекстно дете, наречен Helper. Този контекст е избран, когато потребителят отговори на нещо, което не може да бъде отнесено към друг контекст. Нека добавим един в нашата Каква е сумата AskAndMap:

"контекст": [
   {
      "субекти": [
         {
            "ContextFieldName": "количество",
            "EntityName": "номер",
            "ExpectedValue": "",
            "EntityIndex": 0
         }
      ]
   }
   {
      "Помощник": вярно
   }
]

Нека сега добавим най-подходящото дете с помощ за съобщение. Нещо като:

Разбира се, за AskAndMap може да има само едно дете контекст на помощник.

Човек би могъл да си представи пример за потока:

Потребител: „Бих искал да преведа малко пари“

Асистент: „Каква е сумата?“

Потребител: „Мислиш, че ще знам. Но не съм сигурен"

Асистент: „Моля, предоставете сумата за превод“

Това изглежда просто: очевидно асистентът не е разбрал „Мислиш, че щях да знам. Но не съм сигурен “и продължи с помощното съобщение„ Моля, предоставете сумата за превод “.

Но всъщност, ако стартирате бота, след последния ред ще получите изненадващо изречение:

Потребител: „Мислиш, че ще знам. Но не съм сигурен"

Асистент: „Моля, предоставете сумата за превод“

Асистент: „Как мога да ви помогна?“

Какво е станало тук? Откъде дойде „Как мога да ти помогна?“

Ето и потока. Помощният възел каза своята линия и върна SUCCESS на своя родител, AskAndMap. Това от своя страна също връща УСПЕХ и така нататък, докато бъде достигнат корена. В този момент цялото дърво беше рестартирано и получаваме първоначалния Как мога да ви помогна? “

Така че, за да избегнем това, трябва да поставим цикъл пред AskAndMap, така че той да не се върне, докато наистина успее. Това се прави с нещо, наречено декоратор.

Добавяне на повторителен декоратор

Дърветата на поведението прилагат контури, използвайки декоратори, които са възли, които имат един родител и едно дете. Изобразени като ромб ⧫, тук ще използваме декоратора RepeatUntillSuccess, за да циклираме AskAndMap, докато не бъде завършен успешно. Получаването на помощно съобщение не би го завършило, така че трябва да върнем FAILURE след съобщението за помощ. Правим това чрез секвениране на възел Failer веднага след съобщението. Като цяло, това е декорацията, която добавяме към конструкцията AskAndMap:

Сега е моментът да добавите следващия възел, който да картографира номера на акаунта на бенефициента. Отново, доста направо: както преди, ние добавяме AskAndMap с въпроса за номера на акаунта и карта от accountNumberEntity към член на accountNumber в контекста. Ние го определяме като дете на декоратор на RepeatUntilSuccess и дете-помощник, което обяснява какво е необходимо за това образувание.

След това трябва да добавим действителната бизнес логика, за да извършим прехвърлянето. Това вероятно би означавало няколко обаждания по API с събраните субекти. Бихме симулирали това със съобщение: ще прехвърлим $ X в сметка #Y. За това е необходимо да плъзнете в GeneralMessage като първо дете на акаунтаNumberEntity и да направите неговите свойства, както следва:

"Дебъгване-дневник": "",
 "RunningTimeoutSec": 600,
 "MaxRetriesNumber": 5,
 "ReplayActionOnReturnFromContextSwitch": вярно,
 "Изглед": фалшиви,
 "Бърз": [
 „На път за прехвърляне <% = context.amount%> към сметка <% = context.accountNumber%>»
],
...

Ето как изглежда дървото сега:

Дървото идва със Серво. Файловете са под сървър / convocode / анонимен / чернови / bank-bot.

Изпълнение и тестване

Нека да тестваме бота и да видим какво се случва с различни входове. Щракнете върху раздела Debugger, след това бутона за възпроизвеждане ▶ ️. От дясната ръка симулаторът ще изскочи:

Можете да въведете изречение като:

Бих искал да изпратя пари.

На това ще се отговори, както се очаква, с

"Каква е сумата?"

И можете да вложите сумата и да продължите.

Но какво ще кажем, ако кажем

Бих искал да изпратя $ 14141 ??

Тествайте го и ще видите как асистентът прескача въпроса за сумата направо към номера на сметката:

„Какъв е номерът на сметката?“

Сега, нека направим живота си още по-труден:

Бих искал да изпратя пари по сметка № 87654321

Доста приятно, че иска само сумата. Кажете, че въвеждате $ 3400, след това ще пропусне номера на сметката (тъй като вече го знае) в окончателното изречение за потвърждение:

Предстои да прехвърлим 3400 в сметка # 87654321.

Как знае да прави цялата тази магия?

Контекстният поток

Servo идва оборудван с мощен набор от алгоритми за разпознаване на контекст, който му помага да направи всичко това. Това, което се случи тук, показва малко от това. Нека вземем последния пример. След като асистентът попита:

"Как мога да ти помогна?"

И потребителят отговори:

Бих искал да изпратя пари по сметка № 87654321

NLU двигателят извежда TransferIntent и потокът продължава надолу към следващия въпрос, относно сумата:

Но NLU също върна акаунтNumberEntity! Така че преди да се спусне, това образувание се записва в контекста „Как мога да ви помогна“. И всеки AskAndMap дефинира свой собствен контекст.

Това всъщност е важна забележка, така че ще го повторя: всеки AskAndMap определя своя собствен контекст.

Във всеки момент от потока, когато се споменава образувание, Servo търси назад (четете: нагоре) в разговора, за да го намери. Ако не е, ще го поиска.

Така след въвеждането на сумата, след като продължим към възела с номера на сметката, Servo открива, че accountNumberEntity вече беше споменат и го използва.

Между другото, процес с подобни характеристики се случва и когато стигнем до последния потвърждаващ възел на GeneralMessage. Подканеното гласи:

Предстои да прехвърлите <% = context.amount%> към сметка <% = context.accountNumber%>

За да разреши това, Servo търси в контекстното дърво, за да намери нужните обекти или членове на контекста.

Това напомня ли ви за нещо? Хората, запознати с наследственото прототипично JavaScript, биха видели, че основно използва същия дизайн. В Servo реализирахме това, тъй като се нуждаем от повече контрол върху променливите. Но винаги е интересно да се види как обектно-ориентираните концепции в действителност се прилагат към реалния живот, естествените разговори.

Но какво ще стане, ако потребителят попита нещо много по-несвързано, като:

„Колко пари имам в сметката си?“

Или още повече

„Кой си ти, за бога?“

На което да отговаря ботът:

„Аз съм помощник по изкуствен интелект, създаден от Servo Labs.“

Whaaaaaaaaaat ?? Откъде идва това?

Контекст и под-дървета

Почти всички структурни дизайни, които архитектите използват за изграждане на управляеми големи системи, могат да бъдат разделени в една от двете категории:

  • Повторна употреба
  • Modularize

Ако Servo трябва да застане като инфраструктура на големи AI системи, той трябва да осигури някакъв механизъм, който позволява на разработчиците да постигнат тези цели. И там играят под-дървета.

Споменахме преди зелените шестоъгълници:

Това е под-дърво. Щракнете двукратно върху него и ще въведете ново дърво с това име. За да създадете ново под дърво, задръжте курсора на мишката върху Дърветата в левия прозорец и изберете Ново:

Ще се появи дърво с уникално GUID име. Променете името му на нещо смислено и го изградете, като използвате всеки възел от левия прозорец. След като сте го изградили, можете да го плъзнете, пуснете и свържете във всяка точка на всяко друго дърво (включително и себе си, между другото, но бъдете много внимателни за това). Тъй като под-дърветата могат да имат много листни възли, можете да ги свържете само като листа.

Какво се случи, когато потребителят попита асистента „Кой си ти“?

Първо, NLU, вече обучен за подобни въпроси, върна WhoAreYouIntent. Тогава търсенето на контекста беше активирано. Ако разговорът се намираше някъде по средата на разговор за трансфер, търсенето продължи нагоре, опитвайки се да намери контекст с WhoAreYouIntent. Този контекст е открит: той се намира на четвъртия контекст в раздела Как мога да ви помогна. Токът след това е пренасочен там, което означава, че този маршрут е направен активен маршрут. Потокът тук продължи надолу по течението в поддървото на чат-чата, отговори на въпроса, върна се с УСПЕХ и маршрутизацията беше върната в предишния контекст, прехвърлящия.

Тук научихме нещо всъщност много важно. Разговорът тече надолу, но се търси контекст. Никога не забравяй това:

Разговорът тече надолу, но се търси контекст

Свързване с месинджър клиент

Досега ние използвахме вътрешния симулатор и отстраняване на грешки като наш клиент за съобщения. Нека свържем нашия малък помощник с истински пратеник във Facebook. Има една голяма важна промяна в кореновите свойства на нашето дърво и това е да промените името на канала от канала по подразбиране „chatsim“ на „facebook“:

"канали": "Фейсбук"

От страна на Facebook това са основните стъпки на високо ниво, които човек трябва да предприеме:

  1. Отворете страница във Facebook под акаунта си във Facebook
  2. Създайте ново приложение в Facebook в центъра за разработчици на Facebook
  3. Добавете функционалност на пратеник към приложението си
  4. Абонирайте се за приложението, за да слушате събития в страницата
  5. Задайте адреса за обратно извикване на асистента като уебхоката за публикуване. Servo винаги публикува своя бот с формата на

/ запис / <идентификатор на канала> / <име на асистент>

Така че за асистент на банков бот, работещ на www.mydomain.com, адресът ще бъде:

HTTPS: // / влизане / еб / банка-бота

Трябва да го зададете в секцията за абонамент на страници на приложението Facebook, в портала на програмиста. Трябва да изберете най-малко съобщения, съобщения_постове и да сравните маркера за потвърждение с маркера за потвърждение, който сте задали в кореновите свойства на бота:

Между другото, https://serveo.net е страхотна система за тунелиране (друга алтернатива е ngrok), ако разработвате своите помощници, като мен, в localhost.

На свойствата на асистентския корен задайте същия маркер за проверка и го публикуйте отново:

"facebook": {
    "validationToken": "mytoken123",
    "accessToken": "<знак тук>"
  }

Маркерът за достъп също трябва да бъде зададен, взето от областта на съобщенията на Facebook:

Последно, изберете страница, за да абонирате уебхоук за събитията на страницата:

и ... след като свържете всички тези краища, най-сетне трябва да имате пълна, оркестрирана, разговорна система от край до край!

Свързване на Backends

Връзките в реалния живот варират, но за щастие повечето от тях в наши дни се осъществяват с помощта на RESTFul API. За тях вижте документацията за RetrieveJSONAction и PostAction. След извличане на данни или получаване на отговор, той се задава в поле на паметта (контекст / глобален / променлив). Вероятно бихте искали да го попитате. Това се прави с помощта на ArrayQueryAction, който реализира в памет памет, подобен на монго. За директни заявки на MongoDB използвайте действието MongoQuery.

В обобщение

Servo е IDE и рамка с отворен код, която използва търсене за разпознаване на контекст, за да постави потребителя на правилния разговор и да изведе правилните въпроси. Научихме как да конструираме обикновен разговор и как да увием такива разговори в поддървеси за разединяване и повторна употреба. Servo има много други функции, които си струва да проучите, сред които бихте могли да намерите

  • Съединители към Facebook, Alexa, Twilio и Angular
  • Съединители към бази данни MongoDB, Couchbase и LokiJS
  • Сбруя за тестване на автоматичен разговор
  • Отладчик за разговор
  • Повече действия, условия и декоратори
  • Механизми за контрол на потока
  • Полево задаване и сравнение
  • Контекстна манипулация
  • утвърждаване
  • Заявки, подобни на монго в паметта
  • И всяко персонализирано действие, което измислите

Не се колебайте да го проверите и да зададете въпроси във форума на Github или на моето име @lmessinger Github. Наслади се!