Создание простого интерактивного помощника


Привет, Хабр! Вдохновившись последними достижениями в области прикладного ИИ и личных помощников (Siri, Google Now и им подобные), я решил написать под себя помощника, пусть и не такого продвинутого как коммерческие аналоги, зато со своим блекджеком и своей изюминкой.

Знакомьтесь, Сузи





Главная изюминка моего псевдо-ИИ заключается в очень простой реализации и быстром обучении большому количеству фраз. Достигается это следующим образом:



Логика Сузи



В папке с исполняемым файлом есть (на данный момент) три файла: sinonims.txt, faq.txt, и phrases.txt. Они заполняются мною, в дальнейшем планирую добавить функцию заполнения голосом и/или какое ни будь подобие самообучения.

sinonims.txt
— база исходных слов их замен
привет=hi
здравствуй=hi
хай=hi

дела=state
жизнь=state
...


faq.txt
— база вопрос-ответ. Ответ может содержать текст, команду или переменную
*hi*={&hi}, {%name}
*как*state*={&state}

*off*debug*={@dbg0}{&ok}

В правой части содержится маска, под которую подставляется текст с синонимами, в левой ответ. Спец символы "@", "%" и "&" указывают на то, что нужно вставить вместо конструкции — команду, переменную или фразу соответственно.

phrases.txt
— база частых фраз в нескольких вариантах
hi1=здравствуйте
hi2=приветствую

state1=хорошо
state2=нормально
state3=все в норме
...


Если есть желание по заполнять базы — пишите в комментариях, я выложу базы, исходники, exeшник.

Пример обработки запроса

Исходный текст: привет, как дела -> Очищаем строку, заменяем синонимы по словарю: hi как state -> Смотрим под какие маски он попадает: *hi* и *как*state* -> Парсим ответ: заменяем {&hi} и {&state} случайным образом на варианты, присутствующие в базе фраз, а {%name} на переменную curusr -> Результат: приветствую, Сэр все в норме (Вы, наверное, могли подумать, что Сузи не очень грамотная, но грамотность ей не нужна, в моих планах прикрутить к ней распознавание речи и TTS, а в этом случае ей не понадобится ставить знаки препинания и писать слова с заглавных букв)



Внутренности Сузи



Процедура инициализации
procedure init;
var
  path: string;
begin
  canspeak := true;
  path := extractfilepath(application.ExeName) + '\brains\'; //Путь к базам
  faq := tstringlist.Create; // Создание и загрузка массивов данных
  words := tstringlist.Create;
  words.LoadFromFile(path + 'phrases.txt');
  faq.LoadFromFile(path + 'faq.txt');
  sins := tstringlist.Create;
  sins.LoadFromFile(path + 'sinonims.txt');
  curusr := 'dysha'; // Текущий пользователь
  name := sino(curusr); // Имя из логина по базе синонимов
  say('Инициализация завершена');
  say('Моя база знаний на данный момент позволяет распознавать ' +
    inttostr(faq.Count * sins.Count) + ' выражений и выводить ' +
    inttostr(faq.Count * words.Count));
end;


Процедура парсинга
procedure parce(s: string);
var
  p, o, i, t, i1: Integer;
  t1: string;
  str: tstringlist;
begin
  str := tstringlist.Create;
  said := false;
  s := ansilowercase(s); // Очистка строки
  trim(s);
  stringtowords(s, str); 
  s := '';
  for i := 0 to str.Count - 1 do // Цикл замены слов на синонимы
    s := s + sino(str[i]) + ' ';
  delete(s, length(s), 1);
  s := answ(s); // Парсинг синонимов на предмет команд или переменных, они там встречаются, например, я={%cname}
  d(s); // Отладочный вывод
  tparce := s;
  rootcmdparce; // Поиск в тексте внутренних команд
  faqparce; // Поиск ответа по маске
end;


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

Заключение


Планов относительно Сузи у меня много, я хочу добавить к ней распознавание и синтезирование речи (но это занятие долгое, а времени мало — сессия, студенты и им сочувствующие поймут), возможность запоминать то, что говорит оператор, анализировать это, сохранять напоминания, заметки. Всю эту систему я хочу поставить на отдельную машину, благо на даче их валяется куча, прикрутить хорошую колонку к ней, микрофон, датчик движения, релешки через com-порт, чтоб я мог зайти в комнату и сказать: «Сузи, закрой дверь, вруби кондиционер и поставь мою любимую песню» или что то в этом роде… Ах, как это круто, черт побери.

Пока искал картинку для топика, наткнулся на это, кто знает, тот поймет.


UPD
Выкладываю исходники с exeшником: docs.google.com/file/d/0B1vVuifL615WVzNmQllOUGEwd00/edit?usp=sharing. Если найдутся те, кто до заполнит базы, буду очень благодарен, если Вы мне их скинете. За возможное наличие быдлокода прошу понять и простить.
Share post

Similar posts

Comments 31

    +3
    В оригинале, кстати, она EDI, просто аббревиатуру так на русский перевели.
    Это я к надписи «Suzie» в заголовке программы.
      0
      Знаю, сам играл в английской озвучке. Просто сузи произносить удобнее.
      0
      Спасибо за статью!
      Конечно, с Delphi и распознаванием/синтезированием речи будут проблемы. Разве что использование внешних ресурсов может помочь.
      Посмотрите всё-таки в сторону какой-то более разумной обработки фраз, например, добавьте логику(типа не — отрицание следующей части). И, пожалуйста, выложите код, вам это ничего не стоит, а статью бы дополнило.
      P.S. parse пишется через s
        0
        Хорошо, сейчас выложу, только базы от нехороших слов почищу, а то получится как с IBM Watson. Насчет parse, да, надо бы исправить. Распознавание не обязательно использовать через Delphi-библиотеки, поскольку Сузи будет стоять на отдельной машине, можно просто запускать хоть отдельное приложение для распознавания и воровать у него из текстовых поле распознанный текст через WinAPI.
        +1
        я хочу добавить к ней распознавание и синтезирование речи (но это занятие долгое, а времени мало — сессия, студенты и им сочувствующие поймут)

        Используйте гугловское, хотя бы для начала
          0
          Буду копаться с ней на даче летом, там интернетом не очень, так бы я к ней еще бы и Google Knowledge Graph прикрутил бы. Упор делается на полную автономность, да и какая радость от общения, если собеседник отвечает через несколько секунд после запроса.
          +2
          О, уже и карму сливают. Добро пожаловать на Хабр, называется.
            0
            Проходи не стесняйся=))
            +3
            Помнится во времена BBS была такая программа как pSys — чат якобы с настоящим оператором BBS. Своего рода тоже псевдособеседник.

            Логов с разговорами с ним в сети много, вот например — dunenet.ru/texts/9_100996.html
              0
              Простите за возможно тупой вопрос, но что такое BBS?
                +1
                Bulletin Board System

                ru.wikipedia.org/wiki/BBS
                lurkmore.to/BBS

                Кроме кучи частных и не очень BBS — была еще правительственная сеть стандарта x25 (РОСПАК), к ней был ограниченный бесплатный доступ по модему и куча «дырок» позволявших «юзать» в хвост и гриву :) с целью качать прон и творить другие подростковые штуки
                  0
                  Прон со скоростью < 4кбайт/c? мне кажется это оооочень проблематично. А насчсет pSys чего то он слишком умный для 96го… Хотя он и местами фейлит адово.
                    +1
                    :) Ну дык прон тогдашний был 320х200 и 16 цветов, в основном формата GIF.

                    А pSys очень многих обманывал, угу, каждый 3-ий наверно далеко не сразу соображал с кем он чатится.
                      0
                      С тех пор мало что изменилось.
                      И сейчас боты в чатах водятся, и не каждый третий сообразит что это бот.
                      Например сайт стримов sc2tv.
              +1
              Хорошая штука. Советую глянуть в сторону уже готовых словарей синонимов, а то и тезаурусов. Для английского есть WordNet , мощная база сгруппированных по смыслу словарных облаков.
              Также советую подключить Oracle Text. Всё равно, наверное, захотите хранить диалоги пользователей со Сьюзи. Чем тут может быть полезен Text?

              Да хотя бы поддержкой словоформ. К тому же , уже есть поддержка нескольких десятков (!) языков.
              select * from v$version
              / 
              select value from v$nls_parameters 
              where  parameter = 'NLS_CHARACTERSET'
              / 
              DROP TABLE news
              / 
              EXEC CTX_DDL.DROP_PREFERENCE ('my_wordlist')
              EXEC CTX_DDL.DROP_PREFERENCE ('global_lexer')
              EXEC CTX_DDL.DROP_PREFERENCE ('english_LEXER')
              EXEC CTX_DDL.DROP_PREFERENCE ('german_LEXER')
              EXEC CTX_DDL.DROP_PREFERENCE ('russian_LEXER')
              CREATE TABLE news
                (pkey           NUMBER,
                 lang           VARCHAR2 (2),
                 short_content  CLOB)
              / 
              INSERT ALL
              INTO news (pkey, lang, short_content)
                VALUES  (1, 'en', 'I drive a bike.')
              INTO news (pkey, lang, short_content)
                VALUES  (2, 'en', 'I drove a bike.')
              INTO news (pkey, lang, short_content)
                VALUES  (3, 'en', 'I have driven a bike.')
              INTO news (pkey, lang, short_content)
                VALUES  (4, 'en', 'I always drive a car')
              INTO news (pkey, lang, short_content)
                VALUES  (5, 'en', 'This is nothing')
              INTO news (pkey, lang, short_content)
                VALUES  (6, 'de', 'Ich fahre ein Fahrrad.')
              INTO news (pkey, lang, short_content)
                VALUES  (7, 'de', 'Ich fuhr ein Fahrrad.')
              INTO news (pkey, lang, short_content)
                VALUES  (8, 'de', 'Ich habe ein Fahrrad gefahren.')
              INTO news (pkey, lang, short_content)
                VALUES  (9, 'de', 'Ich habe immer ein Auto fahren.')
              INTO news (pkey, lang, short_content)
                VALUES  (10, 'de', 'Es ist nichts.')
              INTO news (pkey, lang, short_content)  VALUES  (11, 'ru', 'читал')
              INTO news (pkey, lang, short_content)  VALUES  (12, 'ru', 'читаю')
              INTO news (pkey, lang, short_content)  VALUES  (13, 'ru', 'читали')
              INTO news (pkey, lang, short_content)  VALUES  (14, 'ru', 'читать')
              INTO news (pkey, lang, short_content)  VALUES  (15, 'ru', 'читаем')
              INTO news (pkey, lang, short_content)  VALUES  (16, 'ru', 'читаешь')  
              SELECT * FROM DUAL
              / 
              BEGIN
                -- word list:
                ctx_ddl.create_preference ('my_wordlist',   'basic_wordlist');
                ctx_ddl.set_attribute     ('my_wordlist',   'stemmer',            'auto');
                -- english lexer:
                ctx_ddl.create_preference ('english_lexer', 'basic_lexer');
                -- russian lexer:
              --  ctx_ddl.create_preference ('russian_lexer', 'basic_lexer');  
                --ctx_ddl.set_attribute('russian_lexer', 'INDEX_STEMS','YES');
                -- german lexer:
                ctx_ddl.create_preference ('german_lexer',  'basic_lexer');
                ctx_ddl.set_attribute     ('german_lexer',  'composite',          'german');
                ctx_ddl.set_attribute     ('german_lexer',  'alternate_spelling', 'german');
                ctx_ddl.set_attribute     ('german_lexer',  'mixed_case',          'no');
                ctx_ddl.set_attribute     ('german_lexer',  'base_letter',         'yes');
                -- multi_lexer:
                ctx_ddl.create_preference ('global_lexer',  'multi_lexer');
                ctx_ddl.add_sub_lexer     ('global_lexer',  'default',             'english_lexer');
                ctx_ddl.add_sub_lexer     ('global_lexer',  'german',              'german_lexer');
                --ctx_ddl.add_sub_lexer     ('global_lexer',  'russian',              'russian_lexer');
              END;
              / 
              --drop index search_news;
              create index search_news 
              on news (short_content) 
              indextype is ctxsys.context 
              parameters 
                ('lexer            global_lexer
                  language column  lang
                  wordlist         my_wordlist')
              / 
              EXEC DBMS_STATS.GATHER_TABLE_STATS (USER, 'NEWS')
              COLUMN short_content FORMAT A30
              ALTER SESSION SET NLS_LANGUAGE = 'AMERICAN'
              / 
              set timing on
              select * from news 
              where  contains (short_content, '$drive') > 0
              / 
              select * from news 
              where  contains (short_content, '$drove') > 0
              / 
              ALTER SESSION SET NLS_LANGUAGE = 'GERMAN'
              / 
              select * from news 
              where  contains (short_content, '$fahr') > 0
              / 
              select * from news 
              where  contains (short_content, '$fuhr') > 0
              / 
              
              ALTER SESSION SET NLS_LANGUAGE = 'RUSSIAN'
              / 
              select * from news 
              where  contains (short_content, '$читать') > 0
              / 
              select * from news 
              where  contains (short_content, '$читали') > 0
              / 
              

              Фишка тезаурусов Оракла ещё и в том, что там существуют уровни обобщения, и при должном программировании Ваша система сможет сообразить, что собака=млекопитающее=животное, например, при вопросе «ты своё животное покормить не забыла?» :-)
              Также было бы интересно, если б «девушка» могла читать новостные ленты и «быть в курсе» трендов ))
              Ну, типа, ты ей «Сердюков», а она тебе «Васильева», или «Коррупция».
              Здесь могут помочь функции Gist, About, Themes, которые попытаются большой текст ужать до нескольких ключевых предложений.
              Кстати, что насчёт опечаток? При текущем подходе, если слово набрано с опечаткой, диалога вообще не будет полноценного. Можно писать свой fuzzy matcher, например использовав дистанцию Левенштейна, можно использовать оракловую конструкцию textquery progression:

                                                     
              create table mybooks (title varchar2(20), author varchar2(20));
              
              insert into mybooks values ('Consider the Lillies', 'Ian Crichton Smith');
              insert into mybooks values ('Sphere',               'Michael Crichton');
              insert into mybooks values ('Stupid White Men',     'Michael Moore');
              insert into mybooks values ('Lonely Day',           'Michaela Criton');
              insert into mybooks values ('How to Teach Poetry',  'Michaela Morgan');
              
              create index auth_idx on mybooks (author) indextype is ctxsys.context;
              
              SELECT score(1), title, author FROM mybooks WHERE CONTAINS (author, '
              <query>
                 <textquery>
                   <progression>
                     <seq>michael crichton</seq>
                     <seq>?michael ?crichton</seq>
                     <seq>michael OR crichton</seq>
                     <seq>?michael OR ?crichton</seq>
                   </progression>
                 </textquery>
              </query>', 1) > 0 ORDER BY score(1) DESC;
              
                                                  
              
              The output of this query is:
              
                                                     
               SCORE(1) TITLE          AUTHOR
              ---------- -------------------- --------------------
                      76 Sphere               Michael Crichton
                      51 Lonely Day           Michaela Criton
                      26 Stupid White Men     Michael Moore
                      26 Consider the Lillies Ian Crichton Smith
                       1 How to Teach Poetry  Michaela Morgan
              

              Ну, это вот навскидку. Так-то тема интересная :-)
                0
                Спасибо большое, завтра изучу, сейчас уже мозги не варят.
                  0
                  Насчет опечаток, опять же, после подключения к голосовлму распознаванию можно уже не волноваться насчет опечаток, там может только слово целиком неверно распознаться, а от этого эже не спасешься. А вообще проблема опечаток решается добавлением нечеткого сравнения на стадии замены на синонимы. Насчет тезаруса с разными уровнями конкретизации да, мой подход в этом плане очень слаб, нет никакой эвристики и зависимости от контекста. А Oracle Text обязательно гляну, вы меня чертовски заинтересовали.
                    0
                    А, то есть основной упор будет на голосовую часть. Тогда есть смысл встроить распознавание голосов разных пользователей.
                    Товарищ сейчас занимается этой темой, для диплома, есть несколько неплохих статей.
                    Тут как раз используется Дельфи
                    Ну и парочка матлабовых:
                      0
                      Писать распознавание с нуля у меня мои познания в вышмате не потянут, 1 курс все таки еще. Вопрос, а Вы знаете готовые оффлайновые русскоязычные программы(или библиотеки) для распознавания речи?
                      0
                      И еще вопрос, а Oracle Text, это, как я понял, облачный сервис?
                        0
                        Нет. Это часть СУБД Oracle.
                  0
                  Забыл совсем. Как вы формировали правила наподобие *как*state*={&state} в faq.txt, вручную?
                  Хорошо бы добавить модуль обучения, который будет делать частотный анализ живых текстов и автоматически извлекать подобные правила…
                  Только вот где брать такие тексты в формате вопрос-ответ. Какой-нить мессенджер или форум бы взять да проанализировать только относительно короткие пАры…
                  Хм, а ведь есть mail.ru ответы и тому подобные сервисы…
                  Ну и в идеале хочется, чтобы виртуальный собеседник в процессе беседы уточнял контекст и общался с «живым» на одном языке: с деятелем культуры — высоким стилем, с учёным — на сухом точном языке фактов, с юной блондинкой — со смайликами и всякими женскими штучками, с гопником — на фене )
                    0
                    Да, сам, вручную, ну, впринципе, если забить словарь фраз и синонимов и погонять его по чатам, то можно научить. А насчет стиля общения — да, в принципе, тоже не проблема, просто подключать разные словари фраз. У меня в планах есть идея создавать для каждого отдельного юзера ini-файл со всеми его предпочтениями и тд.
                    0
                    I am Commander Shepard and this is my favourite assistant on Habrahabr!
                      0
                      Плюс за EDI и минус за то, что картинка с ней размером всего 415 x 717 весит 662 КБ.
                        0
                        Прошу прощения, не подумал, сейчас и правда поменьше сделаю.
                        0
                        Крутое пике — огонь =)
                          0
                          У меня похожий проект, но на C++, полное голосовое управление. Базы нормальные есть. Сейчас работаю над NLP.
                          s017.radikal.ru/i436/1306/84/5e77a6475d2d.jpg

                          Only users with full accounts can post comments. Log in, please.