Робот, который таки ответит на вопрос о погоде в Токио (на самом деле — нет, но уже близко)

в 9:40, , рубрики: watson, машинное обучение

Собственно, после одного из недавних постов @IBM возникла идея скрестить ежа с ужом Dialog с Natural Language Classifier. Причём тут Токио? А при наличии возможности определить его как сущность типа «город» в dialog и сохранить в профиле для обработки. Впрочем, именно получения погоды под катом не будет. Однако, по идее, можно прицепить обработку соответствующей «команды».

Перед началом работы понадобится зарегистрироваться в Bluemix, создать приложение и получить учётные данные для Dialog и Natural Language Classifer. Само же приложение может быть локальным.

Код полученной библиотеки (на Kotlin, должно легко сочетаться с Java и, вероятно, другими языками на платформе JVM) — тут. Бинарная же сборка — тут (кстати, куда её лучше забросить? CVS всё же про исходники). Вкратце, основные моменты:

  • Сценарий описывается XML-файлом (как «надмножество» Dialog-й разметки). Классы, соответственно, определены в нём же.
  • После классификации наиболее подходящий класс будет передан перед пользовательским вводом в Dialog (например — «isn't it too cold in Moscow» — «temperature isn't it too cold in Moscow»). Есть возможность подмены «команды» в выводе результатом её выполнения. Например, [Text:$City] заменится на содержимое переменной City (впрочем, нужна реализация команды — стандартных нет).

Пример кода сценария показан далее. Обратите внимание — ссылки на классы в input/grammar оформлены как [имяКласса]:

Много XML

<?xml version="1.0" encoding="UTF-8"?>
<dialog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="WatsonDialogDocument_1.0.xsd">
<flow>
<folder label="Main">
<output>
<prompt selectionType="RANDOM">
<item>Hello, I'm Watson-based chatbot and can tell you about weather.</item>
</prompt>
<goto ref="introSearch" />
</output>
<output>
<prompt selectionType="RANDOM">
<item>Goodbye.</item>
</prompt>
<getUserInput id="introSearch">
<search ref="introSearchFolder" />
<default>
<output>
<prompt selectionType="RANDOM">
<item>I don't understand you.</item>
</prompt>
</output>
</default>
</getUserInput>
</output>
</folder>
<folder label="Library">
<folder id="introSearchFolder" label="Live Content">
<input>
<grammar>
<item>[temperature] *</item>
</grammar>

<input>
<grammar>
<item>$ (City)={City}</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>I processed your request. Temperature in [Text:$City] is [Temperature:$City]</item>
</prompt>
</output>
</input>
<input>
<grammar>
<item>*</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>Send a city name.</item>
</prompt>
<getUserInput id="temperatureCitySelectionInput">
<search ref="temperatureCitySelectionSearch" />
<default>
<output>
<prompt selectionType="RANDOM">
<item>I can't process this request.</item>
</prompt>
</output>
</default>
</getUserInput>
</output>
</input>
</input>

<input>
<grammar>
<item>[conditions] *</item>
</grammar>

<input>
<grammar>
<item>$ (City)={City}</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>I processed your request. Conditions in [Text:$City] is [Conditions:$City]</item>
</prompt>
</output>
</input>
<input>
<grammar>
<item>*</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>Send a city name.</item>
</prompt>
<getUserInput id="conditionsCitySelectionInput">
<search ref="conditionsCitySelectionSearch" />
<default>
<output>
<prompt selectionType="RANDOM">
<item>I can't process your request</item>
</prompt>
</output>
</default>
</getUserInput>
</output>
</input>
</input>
</folder>

<folder id="temperatureCitySelectionSearch" label="Live Content">
<input>
<grammar>
<item>$ (City)={City}</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>I processed your request. Temperature in [Text:$City] is [Temperature:$City]</item>
</prompt>
</output>
</input>
</folder>

<folder id="conditionsCitySelectionSearch" label="Live Content">
<input>
<grammar>
<item>$ (City)={City}</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>I processed your request. Conditions in [Text:$City] is [Conditions:$City]</item>
</prompt>
</output>
</input>
</folder>
</folder>
<folder label="Global" />
<folder label="Concepts" />
</flow>
<entities>
<entity name="City">
<value name="Tokyo" value="Tokyo">
<grammar>
<item>Tokyo</item>
</grammar>
</value>
<value name="Ottawa" value="Ottawa">
<grammar>
<item>Ottawa</item>
</grammar>
</value>
<value name="Moscow" value="Moscow">
<grammar>
<item>Moscow</item>
</grammar>
</value>
</entity>
</entities>
<variables>
<var_folder name="Home">
<var name="City" type="TEXT" />
</var_folder>
</variables>
<settings />
<specialSettings />
<classes>
<class name="conditions">
<item>Is it windy?</item>
<item>Will it rain today?</item>
<item>What are the chances for rain?</item>
<item>Will we get snow?</item>
<item>Are we expecting sunny?</item>
<item>Is it overcast?</item>
<item>Will it be cloudy?</item>
<item>How much rain will fall today?</item>
<item>How much snow are we expecting?</item>
<item>Is it windy outside?</item>
<item>How much snow do we expect?</item>
<item>Is the forecast calling for snow today?</item>
<item>Will we see some sun?</item>
<item>When will the rain subside?</item>
<item>Is it cloudy?</item>
<item>Is it sunny now?</item>
<item>Will it rain?</item>
<item>Will we have much snow?</item>
<item>Are the winds dangerous?</item>
<item>What is the expected snowfall today?</item>
<item>Will it be dry?</item>
<item>Will it be breezy?</item>
<item>Will it be humid?</item>
<item>What is today's expected humidity?</item>
<item>Will the blizzard hit us?</item>
<item>Is it drizzling?</item>
</class>
<class name="temperature">
<item>How hot is it today?</item>
<item>Is it hot outside?</item>
<item>Will it be uncomfortably hot?</item>
<item>Will it be sweltering?</item>
<item>How cold is it today?</item>
<item>Is it cold outside?</item>
<item>Will it be uncomfortably cold?</item>
<item>Will it be frigid?</item>
<item>What is the expected high for today?</item>
<item>What is the expected temperature?</item>
<item>Will high temperatures be dangerous?</item>
<item>Is it dangerously cold?</item>
<item>When will the heat subside?</item>
<item>Is it hot?</item>
<item>Is it cold?</item>
<item>How cold is it now?</item>
<item>Will we have a cold day today?</item>
<item>When will the cold subside?</item>
<item>What highs are we expecting?</item>
<item>What lows are we expecting?</item>
<item>Is it warm?</item>
<item>Is it chilly?</item>
<item>What's the current temp in Celsius?</item>
<item>What is the temperature in Fahrenheit?</item>
</class>
</classes>
</dialog>

Как видно — в сценарии определены классы [temperature, conditions] и тип сущности City с возможными значениями [Tokyo, Ottava, Moscow].

Пример кода, который запустит обучение и выдаст «вводное» сообщение по его завершению:

Исходник

package com.alex4321.botdemo

import com.alex4321.bot.*

fun main(args: Array<String>) {
    if (args.size != 7 && args.size != 6) {
        println("Need arguments [path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, dialogID, nlcID] or " +
                "[path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, robotName]")
        return
    }
    val path = args[0]
    val dialogUsername = args[1]
    val dialogPassword = args[2]
    val nlcUsername = args[3]
    val nlcPassword = args[4]
    val robotName = if (args.size == 6) {
        args[5]
    } else {
        ""
    }
    val dialogId = if (args.size == 7) {
        args[5]
    } else {
        ""
    }
    val nlcId = if (args.size == 7) {
        args[6]
    } else {
        ""
    }
    val new = dialogId == "" && nlcId == ""
    val code = WatsonProgramPath(path).read()
    val program = WatsonNlcDialogProgram(code)
    val auth = WatsonRobotAuth(dialogUsername, dialogPassword, nlcUsername, nlcPassword)
    val robot = WatsonRobot(program, dialogId, nlcId, auth)
    if (new) {
        robot.train(robotName)
        print("Dialog ID : ")
        println(robot.dialogID)
        print("NLC ID : ")
        println(robot.nlcID)
        println("Training")
        while (! robot.available) {
            Thread.sleep(10000)
        }
    }
    println("Ready")
    val conversation = robot.converse()
    val intro =conversation.intro()
    println(intro.response)
    println(intro.profile)
}

Для начала работы нужно передать аргументы:

  • path — путь к разметке сценария
  • dialogUsername, dialogPassword, nlcUsername, nlcPassword — см. ваши данные для доступа в Bluemix
  • robotName — уникальное наименование. Используется для именования диалогов и классификаторов

После запуска и обучения (не самый быстрый процесс, да) получим что-то типа:

Dialog ID : 93791233-53fb-42c7-8581-6bc2bb761e6a
NLC ID : 2374f9x68-nlc-7617
Training
Ready
[Hello, I'm Watson-based chatbot and can tell you about weather.]
{}

Добавим пользовательский ввод:

Исходник

package com.alex4321.botdemo

import com.alex4321.bot.*

fun main(args: Array<String>) {
    if (args.size != 7 && args.size != 6) {
        println("Need arguments [path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, dialogID, nlcID] or " +
                "[path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, robotName]")
        return
    }
    val path = args[0]
    val dialogUsername = args[1]
    val dialogPassword = args[2]
    val nlcUsername = args[3]
    val nlcPassword = args[4]
    val robotName = if (args.size == 6) {
        args[5]
    } else {
        ""
    }
    val dialogId = if (args.size == 7) {
        args[5]
    } else {
        ""
    }
    val nlcId = if (args.size == 7) {
        args[6]
    } else {
        ""
    }
    val new = dialogId == "" && nlcId == ""
    val code = WatsonProgramPath(path).read()
    val program = WatsonNlcDialogProgram(code)
    val auth = WatsonRobotAuth(dialogUsername, dialogPassword, nlcUsername, nlcPassword)
    val robot = WatsonRobot(program, dialogId, nlcId, auth)
    if (new) {
        robot.train(robotName)
        print("Dialog ID : ")
        println(robot.dialogID)
        print("NLC ID : ")
        println(robot.nlcID)
        println("Training")
        while (! robot.available) {
            Thread.sleep(10000)
        }
    }
    println("Ready")
    val conversation = robot.converse()
    val intro = conversation.intro()
    println(intro.response)
    println(intro.profile)
    while(true) {
        val input = readLine() as String
        if (input == "exit") {
            break
        }
        val answer = conversation.answer(input)
        println(answer.response)
        println(answer.profile)
    }
}

В результате — что-то типа такого:

Ready
[Hello, I'm Watson-based chatbot and can tell you about weather.]
{}
Isn't it too cold in Moscow?
[I processed your request. Temperature in Text is Temperature, , Goodbye.]
{City=Moscow}

Или

Ready
[Hello, I'm Watson-based chatbot and can tell you about weather.]
{}
Isn't it cold now?
[Send a city name.]{}
Moscow
[I processed your request. Temperature in Text is Temperature.]
{City=Moscow}

Ну и собственно обработка команд. Обработчики имеют тип BiFunction<List, String, String>. На входе — полученный при разборе аргументов список и пользовательский ввод. На выходе — текст, полученный при обработке команды. Например:

Ещё исходник

package com.alex4321.botdemo

import com.alex4321.bot.*
import java.util.function.BiFunction

fun main(args: Array<String>) {
    CommandHandler.register("Text", BiFunction { args, input -> args[0] })
    if (args.size != 7 && args.size != 6) {
        println("Need arguments [path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, dialogID, nlcID] or " +
                "[path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, robotName]")
        return
    }
    val path = args[0]
    val dialogUsername = args[1]
    val dialogPassword = args[2]
    val nlcUsername = args[3]
    val nlcPassword = args[4]
    val robotName = if (args.size == 6) {
        args[5]
    } else {
        ""
    }
    val dialogId = if (args.size == 7) {
        args[5]
    } else {
        ""
    }
    val nlcId = if (args.size == 7) {
        args[6]
    } else {
        ""
    }
    val new = dialogId == "" && nlcId == ""
    val code = WatsonProgramPath(path).read()
    val program = WatsonNlcDialogProgram(code)
    val auth = WatsonRobotAuth(dialogUsername, dialogPassword, nlcUsername, nlcPassword)
    val robot = WatsonRobot(program, dialogId, nlcId, auth)
    if (new) {
        robot.train(robotName)
        print("Dialog ID : ")
        println(robot.dialogID)
        print("NLC ID : ")
        println(robot.nlcID)
        println("Training")
        while (! robot.available) {
            Thread.sleep(10000)
        }
    }
    println("Ready")
    val conversation = robot.converse()
    val intro = conversation.intro()
    println(intro.response)
    println(intro.profile)
    while(true) {
        val input = readLine() as String
        if (input == "exit") {
            break
        }
        val answer = conversation.answer(input)
        println(answer.response)
        println(answer.profile)
    }
}

Теперь вывод выглядит так:

Ready
[Hello, I'm Watson-based chatbot and can tell you about weather.]
{}
Isn't it too cold in Moscow?
[I processed your request. Temperature in Moscow is Temperature, , Goodbye.]
{City=Moscow}

P.S.: и да, код явно не лучший, согласен. Жду критики.

Автор: alex4321

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js