пятница, 25 сентября 2015 г.

Говорящий бот с псевдоинтеллектом

Надо же, практически год назад я тут писал пост... Все ж в нашем мире любой процесс имеет циклическую природу... Ладно, не будем зацикливаться ;)

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


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


Сейчас, как всегда, немного теории и немного истории. Но, уверяю вас, это будет познавательно и немного забавно, так что не думаю, что заскучаете. Итак, кто из вас помнит нудные речи на всяческих пленумах, собраниях и заседаниях? Впрочем, современные совещания в корпоративных океанах также могут вполне пользоваться предложенной дедовской технологией.

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

Я вот тут положу скан такой таблицы (да-да, они реально существовали!), попробуйте сами, это, по крайней мере, довольно весело ;)


Несколькими годами ранее на Плюке Менькой был сделан LSL-бот "Эд", который проигрывал аудиофразы в случайном порядке при возникновении события столкновения с кем-либо и нудел, я вас заверяю, на отлично; было очень похоже на реального Эда в тот период.

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

Вот что я еще хочу сказать, дорогие читатели - в инженерном искусстве важно найти кратчайший путь решения задачи для достижения технической цели. Это очень важно помнить при выборе инструмента, при помощи которого вы хотите ее достичь. В данном случае речь идет о том, какой язык при этом выбрать. Но на самом деле это совершенно не важно до тех пор, пока не конкретизирована цель. Предложенный сценарий несложно реализовать, используя лишь LSL и подход Меньки к вопросу. А можно все написать самому "с нуля" (привет, Вэб!) на C/C++, включая бота и войс для него. Этот путь интересен и познавателен и я, кстати, ходил по нему :D

Поэтому сейчас предложу короткий - недаром я выше заметил, как можно использовать подобное решение. Поэтому выбор мой пал на несколько странный, как может показаться, инструмент - потоковый стример LiquidSoap и его скрипт-язык. Сам по себе стример и является этим самым скрипт-языком, который позволяет работать со звуковыми потоками, как с переменными, со всеми вытекающими отсюда прелестями и удобностями.

Кроме того, сам стример кроссплатформенный, что позволяет широко использовать подобные решения. Поэтому я не буду углубляться в особенности установки - считаем, что у вас есть и работоспособный бот с войсом и установленный на ту же систему LiquidSoap, а вывод с него настроен как вход для войса.

В первую очередь необходимо сформировать базу фраз. Если вы делаете автодиджея или рекламного агента, то рекомендую сесть и написать не менее ста предложений примерно одинаковой длины. Затем разбить их на словосочетания и разместить из в таблице. Затем, пробуя таблицу, что-то подкорректировать.

А затем записать уже голосом, проходя таблицу подряд сверху до низу. Обязательно засеките среднее время звучания предложения, это будет важно! Далее вы разрезаете вашу запись на аудиофразы, при этом не обрезайте тишину - она необходима для правильного восприятия, просто постарайтесь резать посередине.

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

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

Вот теперь можно перейти к коде этого марлезонского балета - вдохнуть разум и сознание в эту кучку цифрового песка. Ведь чем мы занимаемся? Големов строим! Пусть и виртуальных... ;)

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

Итак, сперва сделаем источники:

phrases1 = playlist(reload = 300, "/home/androbot/phrases1")
phrases2 = playlist(reload = 300, "/home/androbot/phrases2")
phrases3 = playlist(reload = 300, "/home/androbot/phrases3")
phrases4 = playlist(reload = 300, "/home/androbot/phrases4")
То есть мы сказали LiquidSoap'у, что нужно рассматривать папки, как плейлисты, а параметр "reload" говорит о том, что каждые 300 секунд нужно перечитывать список файлов. Это позволит вам добавлять новые фразы без перезапуска основной системы - частенько это бывает важно.

Перемешаем всё:

phrases1 = random([phrases1])
phrases2 = random([phrases2])
phrases3 = random([phrases3])
phrases4 = random([phrases4])

К слову сказать, по умолчанию плейлист и так рандомизирован, но и LiquidSoap штука сама по себе настолько магическая, что перемешать еще раз не помешает, простите за каламбур ;)

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

Создаем очередь:

proposal = rotate(track_sensitive = true, weights = [1,1,1,1], [phrase1, phrase2, phrase3, phrase4])

Вообще, конечно, этот оператор не для этого, но в данном случае он нам формирует последовательную очередь из четырех треков четырех источников с ожиданием концов треков. Кому интересно подробнее - в документации всё есть ;)

В принципе, всё, если сейчас отдать переменную proposal проигрываться, мы получим постоянный бубнеж.

Делаем некие магические пассы (документация!):

voice = mksafe(proposal)

И отдаем в звуковую карту напрямую (я это делаю при помощи pulseaudio, у вас может быть что-то другое, это при установке выясните):

output.pulseaudio(device = "Androbot", voice)

Все, можно запускать и наслаждаться непрерывной речью. Кстати, вот она, мечта одной моей учительницы из детства - робот, который бы нам, детям-дебилам, долдонил бы одно и то же, не прерываясь ни на секунду. Чтобы с этим явлением побороться, нужно добавить еще один оператор, который управляет временем.

Помните, я говорил, что нужно запомнить среднее время звучания предложения? Основываясь на этих данных мы прикинем, когда нашему боту говорить и сколько, скажем... Скажем, в рамках часа - ведь база у нас невелика. Допустим, он должен говорить каждые двадцать минут, сначала две минуты, потом три минуты, потом одну с половиной минуты. Ну и допустим, что одно предложение у нас занимает 10 секунд.

voice = fallback(track_sensitive=false, [
        switch(track_sensitive=true, [
                ({(00m05s-02m05s) or (20m20s-22m20s) or (53m40s-54m40s)}, proposal)
        ]), blank()
])

Я также позволю себе не вдаваться в подробности работы этой конструкции - важно лишь понять, как работает синтаксис указания времени. В данном случае в потоке у нас будет либо тишина, либо, если время (глобальное) попадает в указанные интервалы, бот будет говорить. У нас он заговорит в 5 секунд после начала каждого часа, потом в 20 минут 20 секунд и в 53 минуты 40 секунд. Вы можете задавать свои интервалы, разделять предложения паузами, для этого вам и нужно знать, сколько длиться предложение - тут дело тонкой настройки бота, которое напрямую зависит от ваших требований и вашей базы.

Ну, а дальше, все просто, мы это уже делали раньше:

voice = mksafe(proposal)
output.pulseaudio(device = "Androbot", voice)

Всё! Когда наступит указанное время, бот заговорит человечьим голосом. Конечно, этого всего еще мало для полноценного автоматического диджея, но никто же не обещал, что я раскрою все секреты, верно? ;)

До новых встреч, девочки и мальчики.