Начну свою первую статью с небольшой предыстории. К моменту когда все началось, я уже на протяжении 7 лет участвовал в научном проекте, целью которого была разработка семантической технологии проектирования интеллектуальных систем. А началось все с прочтения одной замечетельной статьи (спасибо vovochkin) во второй половине 2015 года. Именно тогда я понял, что разрабатываемая нами технология хорошо подходит под решение задач в области интернета вещей. Это был первый фактор который привел меня к текущему проекту. Вторым фактором было то, что мне сильно нравился фильм «Железный человек» и я сильно хотел иметь своего «Джарвиса» у себя дома.




Несколько месяцев планирования и чтения привели меня к следующим задачам, которые предстояло решить:


Немного теории


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

Вся система строится вокруг базы знаний (далее буду использовать сокращение БЗ). Знания в ней представлены в виде семантической сети (более подробно про язык представления знаний можно почитать в статье). Я же приведу пару простых примеров. Которые просто позволят понимать дальнейший текст и изображения. В своих примерах я буду использовать два языка: графический — SCg и текстовый — SCs

Для начала надо усвоить как задается отношение «is a». Все достаточно просто. На изображении слева указан факт, что яблоко и ананас — это фрукты. Для этого мы просто создали узел обозначающий множество всех яблок — apple и добавили входящую дугу принадлежности из узла обозначающего множество всех фруктов — fruit. Тоже самое мы сделали и с ананасом.

В принципе на основе отношения «is a» строится все хранение в БЗ, включая и любые другие отношения. Для примера рассмотрим как в БЗ указывается изображение объекта или класса объектов. На данной картинке видно, что мы ввели множество обозначающее все экземпляры отношения «быть изображением чего-то» и в него добавляем все дуги обозначающие бинарные связки.

Теперь немного поговорим про то как происходит обработка хранимых в знаний. Для этого используется агентно-ориентированный подход. Важный принцип, что агенты могут взаимодействовать между собой только через БЗ (меняя ее состояние). Сразу приведу ссылку на документацию по разработке агентов на языке C++. Взаимодействие между агентами реализовано с помощью специализированного языка команд-запросов (документация по нему в процессе разработки). Здесь я приведу один пример запроса (читайте данное изображение по примеру двух прошлых, используя «is a» отношение):

Это самый простой пример запроса, погоды в Минске. На данном изображении можно увидеть узел, который обозначет экземпляр запроса и входит во множество command и command_find_weather, где последний определяет тип запроса. По сути запрос — это множество элементами которого являются его аргументы.

Каждый агент в БЗ имеет свою спецификацию, где указан ожидаемый результат, условие инициирования и другие свойства агента. Одним из таких свойств является — событие при котором он инициируется. В данный момент доступны следующие виды таких событий: удаление(создание) входящей(выходящей) дуги, удаление элемента, изменение содержимого ссылки. Другими словами мы подписываем агента на событие в базе знаний и когда оно происходит агент инициируется. Для команд таким событием является добавление экземпляра запроса во множество command_initiated. Когда это происходит все агенты подписанные на это событие инициируются. После этого лишь агенты, которые умеют обрабатывать данный тип запроса, продолжают работать.



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

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

Описание получилось достаточно большим хотя я старался его сделать минимальным и понятным. На любые вопросы по нему я готов ответить подробно в комментариях или же при необходимости сделать отдельную статью с детальным описанием и примерами. Но мы двигаемся дальше.

Доработки ядра


К моменту когда я начал работу над «Джарвисом» наше ядро находилось в достаточно стабильном и рабочем состоянии, но разработка агентов была возможна лишь с использованием языка Си, в репозитории еще остались примеры таких агентов.

Первое что необходимо было сделать — это упростить их разработку. Поэтому было решено написать С++ библиотеку, которая брала на себя множество рутинных задач. На тот момент я работал разработчиком игр на Unreal Engine 4 и поэтому решил, что удобно было бы использовать генератор кода и реализовал такую возможность.

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

class AMyAgent : public ScAgentAction
{
 SC_CLASS(Agent, CmdClass("command_my"))
 SC_GENERATED_BODY()
};

Реализация этого агента заключалась в реализации одной функции:

SC_AGENT_ACTION_IMPLEMENTATION(AMyAgent)
{
  // implement agent logic there
  return SC_RESULT_OK;
}

Более детально о реализации агентов можно почитать в документации. Примеры реализации агентов с новой библиотекой можно посмотреть тут (они временно переехали в мой закрытый репозиторий, но скоро они снова будут открыты).

Второе что необходимо было сделать — это многопоточный запуск агентов. Ранее для обработки событий в БЗ использовался один поток, который последовательно запускал агенты из очереди. Сейчас используются все доступные ядра в системе. Сразу скажу, что было очень сложно обеспечить асинхронный доступ к БЗ от множества агентов в разных потоках. Описание этого механизма наверное — это отдельная статья, которую я могу написать, если возникнет такая потребность. Тут я остановлюсь лишь на паре нюансов:

  • работа с элементами, которые хранятся в памяти реализована с помощью lock-free подхода. Я не силен в классификации таких подходов, но думаю что у меня реализация «Без блокировок», когда хотя бы один агент (чаще их больше одного или почти все) продвигается вперед;

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

Третье на что я натолкнулся, что большинство сервисов Amazon, Google и т.д. имеют готовые API на разных языках, но не для С++. Поэтому было решено сделать возможность запускать Python код внутри С++. Реализовано это все с помощью Boost-Python (используя Python 3, вот тут я уже могу много кому помочь с этим).

Помимо описанных вещей была написана документация (ссылки давались уже). Вся библиотека с++ покрыта unit-тестами. На все это ушло около года и все это делалось параллельно с остальными вещами и делается до сих пор.

Визуальная модель


Визуальная модель разрабатывалась для отработки концепции и отладки базовых вещей. Видео небольшого Proof of concept в этой модели:



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

Голосовой интерфейс


В раннем прототипе я реализовал голосовой ввод с помощью Android API, а получаемый текст разбирал с помощью api.ai, который возвращал мне класс запроса для инициирования и его параметры. В таком или немного измененном виде он существует и сейчас. Но разговор мы поведем в другом русле — как этот механизм реализован с помощью агентов.

Если вспомнить про сравнение общения между агентами с форумом, то можно описать этот механизм следующим логом (где A<имя_агента> — это агенты, решающие разные задачи; user — это пользователь):

user: яблоко это фрукт
ADialogueProcessMessage: сделайте анализ входного текста "яблоко это фрукт"
AApiAiParseUserTextAgent: что за объект "яблоко"?
AResolveAddr: адрес элемента "яблоко" - 3452
AApiAiParseUserTextAgent: что за объект "фрукт"?
AResolveAddr: адрес элемента "фрукт" - 3443
AApiAiParseUserTextAgent: нужно добавить элемент 3452 во множество 3443
AAddIntoSet: сделано
ADialogueProcessMessageAgent: сгенерируйте текст на языке пользователя для указанного запроса
AGenCmdTextResult: сгенерируйте текст на русском языке по шаблону "..."
AGenText: готово - "Теперь буду знать"

Вот что происходит под капотом, при решении такой простой задачи. Но это еще не все, далее нам необходимо ввести еще два понятия. Кроме агентов, которые я уже описал (реагируют на изменение БЗ и меняют ее состояние), есть еще два вида агентов:

  • эффекторные — это агенты, которые на изменение в БЗ делают изменение во внешней среде. Другими словами отвечают за вывод информации: экран, манипулятор и т. д.
  • рецепторные — это агенты, которые на изменения во внешней среде, делают изменения в БЗ. Отвечают за ввод информации: устройства ввода, датчики и т. д.

Видео работы эффекторных агентов


На видео можно увидеть, как лампа (куб) включается, когда узел, обозначающий её, добавляют во множество включенных устройств. И наоборот, когда её оттуда удаляют, то она выключается. Тоже самое происходит и с краном.

Когда агенты разобрали запрос пользователя и сформировали на него ответ на том же языке, то этот ответ добавляется в множество обозначающее диалог с ним (пользователем). В этот момент один из «эффекторных» агентов выводит этот ответ пользователю в виде строки. Вот пример работы на видео:



Отдельно, пользовательский интерфейс делает запрос на генерацию речи по тексту. Агент формирует ее в виде звукового файла (OGG) используя сервис ivona.

Что дальше?


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

У данного подхода есть потенциал:

  • добавление нового функционала не затрагивает уже работающий. К примеру, когда мне понадобилось сделать планировщик задач (для запуска задач в определенное время или периодически), то для этого понадобился лишь 1 агент, который всего лишь добавляет уже сформированный запрос во множество инициированных в указанное время. Получается что любой запрос, который доступен на языке запросов может быть запланирован. Например: "«напомни позвонить жене»"; «выключи свет в 10»; «включи телевизор в 11.05»; ...;

  • в данной статье я не описывал, но существует поиск по шаблонам, на базе которого можно делать логический вывод (и уже делали тестовые решатели задач по геометрии и физике);

  • в качестве анализатора входного текста планируется использовать Cloud Natural Language API, что позволит увеличить качестве языкового интерфейса;

На этом наверное я закончу свою первую статью, в которой постарался описать основные принципы и что было сделано по моей реализации «умного дома», «умного дворецкого» или «Джарвиса» (кому как удобно). Не питаю иллюзий, что материал получился хорошим, поэтому надеюсь на обратную связь. Спасибо всем прочитавшим.
Поделиться с друзьями
-->

Комментарии (20)


  1. 17th
    09.05.2017 17:42

    Я что-то неправильно понял, или вы изобрели функциональный язык программирования в картинках?


    1. DenisKoronchik
      09.05.2017 20:27

      В данных примерах по сути так и есть. Основная задача — сделать данный язык универсальным и ограниченным. Чтобы все, что запрашивает пользователь (на любом языке, с любым диалектом) можно было свести к этому языку. А все агенты просто общаются между собой с его помощью. Можно назвать его внутренним языком системы. Пока сложно себе представить нечто более универсальное, но я надеюсь этого дело дойдет.


  1. x893
    09.05.2017 19:35

    Что то мне это напомнило
    http://www.state-machine.com/qm/


  1. niksan3d
    09.05.2017 20:28

    Современные системы управления с использованием ии мне напоминают известную цитату с баша:
    XXX: понедельник
    I-Bot Translate 39M2: Monday
    XXX: вторник
    I-Bot Translate 39M2: Tuesday
    XXX: среда
    I-Bot Translate 39M2: environment
    XXX: среда
    I-Bot Translate 39M2: environment
    XXX: день недели, сука… среда!
    I-Bot Translate 39M2: day of week, bitch… environment!


    1. DenisKoronchik
      09.05.2017 20:31

      Все верно. На уровне БЗ среда (как день недели) и среда (как окружение), имеют два разных узла (две разные сущности). Проблема чуть выше, как понять про какую среду идет речь. Я пока не имею цели решить данную проблему, пусть её решают профи в естественно языковых интерфейсах. Просто в проекте есть инфраструктура, когда я текущий resolver понятий заменю на лучший (это выключить текущий агент и включить другой).


      1. ACPrikh
        10.05.2017 11:20

        Дельфи в свое время имел замечательный хэлп, так как имел «See Also». И можно было «думать около». Или в Вики «имеются другие значения».
        В топологическом смысле надо иметь не дерево, а сеть. Плюс диалог с юзером для уточнения (прохождения сложной топологии).


  1. neadekvatnij
    09.05.2017 20:51

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


  1. DenisKoronchik
    09.05.2017 20:53

    Я когда начинал работу над «умным домом», прочитал одну фразу, которая просто предопределила с чего стоит начинать: "Чтобы создать систему, способную действовать и размышлять, надо сперва создать систему, которая умеет взаимодействовать и понимать" — Адам Чайер (руководитель разработки SIRI)


  1. Godless
    10.05.2017 00:13

    как физически вы хватаете голос? микрофоны по всему жилищу?


    1. DenisKoronchik
      10.05.2017 00:39

      Сейчас пока телефон, уже в процессе прототип на часы (Android). Распознавание текста там уже есть, надо лишь отправить его в БЗ. Часы сейчас хороши, так как можно по ним сразу персонифицировать запросы.


      1. Godless
        10.05.2017 09:07

        Понятно, спасибо


  1. ondister
    10.05.2017 07:22

    Так ведь не в голосовом интерфейсе дело. Штука в том, что создается сложная модель физического умного умного дома в виде семантической сети.


  1. win0err
    10.05.2017 10:10

    Подскажите что-нибудь интересное про МАС. Желательно, чтобы ещё и примеры клёвые были


    1. DenisKoronchik
      10.05.2017 10:13

      Извините меня, но я не понимаю что вы имеете ввиду под MAC? Да еще и с примерами. Можете уточнить про какой из MAC-ов вы ведете речь? Спасибо


      1. win0err
        10.05.2017 10:30

        Естественно, многоагентные системы


        1. DenisKoronchik
          10.05.2017 12:24

          Я даже не могу Вам порекомендовать конкретную литературу сейчас. Читал я её еще только когда мы начинали делать технологию это 6-7 лет назад. Наверное для общих понятий подойдет вот эта ссылка. И наверное вот такая еще статья.
          А вот список библиотек и софта.


  1. eao197
    10.05.2017 11:45

    Интересно, а почему вы начали делать свою реализацию агентов на C++, а не взяли какую-то из уже существующих реализаций той же Модели Акторов для C++?


    1. DenisKoronchik
      10.05.2017 12:21

      Технология, которая лежит в основе использует свой формализм SC-код, который можно реализовать как угодно. Идея в том, что если все записать с его помощью (а у нас даже есть язык который можно интерпретировать SCp), то необходимо реализовать лишь интерпретацию этого формализма в виде программной или аппаратной реализации.

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

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


      1. eao197
        10.05.2017 12:29

        то необходимо реализовать лишь интерпретацию этого формализма в виде программной или аппаратной реализации

        Собственно, тогда вообще два вопроса возникают:

        1. Чем «SC-код» отличается от уже известных моделей вроде Actor Model или CSP? И почему именно SC-код был нужен.

        2. Даже если вам нужна была реализация SC-кода, то вы могли делать ее не с нуля, а взяв за основу что-то готовое. Тем самым минимизировав затраты на разработку все системы. Поскольку какие-то вещи, вроде поддержки многопоточности, вы бы переиспользовали, а не создавали заново. Отсюда и вопрос: почему не было взято-то что-то готовое?


        1. DenisKoronchik
          10.05.2017 13:40

          1. SC-код это формализм, способ представления информации. Если проводить грубую аналогию, то это способ кодирования информации (сейчас мы используем бинарный 0 и 1 повсеместно). SC-код позволяет записать любую информацию, при этом еще сохранить семантику. Другими словами SC-код — это язык представления информации (знаний), но не модель многоагентной обработки знаний. Дальше над SC-кодом синтаксис которого прост надстраиваются разные языки представления знаний. Каждый из них — это по сути набор правил как и с помощью каких отношений представляются знания по теории множеств, алгебре, геометрии, физике и т. д. Так строится база знаний. Среди множества этих языков есть и язык который я описывал статье (команд-запросов). Один из таких языков описывает агентов.

          2. Так как SC-код — это способ представления знаний (абстрактный, а значит мог быть реализован большим количеством способов), то и смотрели на графовые БД. Но на тот момент ничего подходящего не нашлось. Но даже сейчас можно ядро заменить (хранение) на графовую БД, и просто оставить тоже АПИ оно на чистом си, все остальное будет через это АПИ работать с любым хранилищем. Что касается агентов, то имеющиеся библиотеки вряд ли нам помогли бы, так как все равно пришлось бы реализовывать очередь событий из памяти их вызов обработчиков.

          Еще раз повторюсь, когда дойдет дело до распределенного хранилища, то скорее всего мы будем что-то такое использовать. Но на текущий момент в этом нет необходимости. На вызов параллельных агентов было потрачено около 30 часов, не так уж и много.