Заменяем Google Assistant на нейросеть Порфирьевич и троллим Алису

  • Tutorial

Вы уже видели, что вытворяет нейросеть Порфирьевич? Она дописывает текст к любой вашей фразе. И действительно забавные штуки получаются, потому что обучена она на книгах Достоевского, Толстого, Пушкина, Булгакова, Гоголя и Пелевина.

«Озвучить все это дело голосом Левитана — получился бы отличный заменитель гугловского ассистента к новогоднему застолью...» — подумал я. И решил не откладывать это мероприятие на посленовогогода (а то ведь сами понимаете).

Под катом — весь процесс создания опенсорсного голосового ассистента Порфирьевич на исключительно опенсорсном фреймворке Aimybox, и его запуск вместо штатного Google ассистента. Ну и заодно Алису потроллить можно.

Если хочется сразу попробовать все это в деле, то можно установить последний релиз отсюда

Порфирьевич API


На хабре недавно вышла статья, в которой рассказывается про то, как Михаил Гранкин научил нейросеть GPT-2 дописывать «осмысленные» тексты к любой фразе. Получилось забавно. Вдобавок, доступ к нейросети открыт через обычный HTTP API.

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



Лимитов на этом API нет, но разработчик все же рекомендует скачать модель Порфирьевича и развернуть на своем сервере.


Aimybox SDK


API Порфирьевича у нас есть! Теперь нужно было запилить собственно голосовое приложение для смартфонов, которое бы распознавало и синтезировало речь.

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

Можно склонировть к себе сэмпловое приложение и на его базе создать проект.
Кстати, недавно появилась первая версия iOS SDK, так что все то же самое можно повторить и для iOS.
Aimybox умеет работать с любым диалоговым движком, например Aimylogic, Rasa или Dialogflow, и в том числе позволяет реализовать подключение к любому другому. В нашем случае нужно напрямую слать запросы на API Порфирьевича и парсить ответы. Для этого необходимо всего ничего — написать соответствующие классы Request, Response и реализацию самого DialogAPI, которая собственно шлет запросы и парсит ответы.

Распознавание и синтез


На любом Андроид-девайсе из коробки работает бесплатное распознавание и синтез речи от Google, так что за это можно было не волноваться. Вот только голос, конечно, хотелось поменять на что-то более подходящее литературному слогу Порфирьевича…

Голос Левитана


Раньше у Яндекс Speechkit-a (облачного решения для распознавания и синтеза речи) был замечательный голос «Левитан», как нельзя лучше подходящий к нашему проекту. Сейчас все переехало в Яндекс Облако, все стало платным (без всяких пробных 10к запросов), поэтому пришлось искать альтернативы.

Есть такой прекрасный сервис для тестирования навыков для Алисы — station.aimylogic.com. На нем можно выбрать голос, которым синтезируются ответы и потестировать свой навык прямо в браузере. Смотрим в его API и находим endpoint, на который идут запросы на синтез речи



Тут видно, что если подставить в этом URL в параметр speaker значение levitan, то можно получить желаемый результат. Это нам и нужно!

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

Заменяем Google Assistant на Порфирьевича


Запускать нашего голосового Порфирьевича можно просто открыв приложение и тапнув по кнопке микрофона. Но куда приятнее запускать его вместо штатного гугловского ассистента, потому что многие смартфоны имеют встроенную поддержку ассистента (например, на моем Oneplus 7 можно зажать кнопку включения, чтобы активировать ассистента с любого экрана).

Для этого нужно лишь прописать в AndroidManifest еще один интент-фильтр android.intent.action.ASSIST, проставить флаг android:launchMode=«singleTop» и в MainActivity реализовать метод onNewIntent, в котором запускать распознавание речи, когда приложение вызывается в режиме ассистента.

    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        if (intent?.action == Intent.ACTION_ASSIST) {
            val aimybox = (application as PorfirApplication).aimybox
            val channel = aimybox.stateChannel.openSubscription()
            launch {
                channel.consume {
                    aimybox.startRecognition()
                }
            }.invokeOnCompletion { channel.cancel() }
        }
    }

В настройках смартфона нужно выбрать Порфирьевича в качестве ассистента по умолчанию. Настройки -> Приложения -> Приложения по умолчанию

Разукрашиваем


В файлах styles.xml и strings.xml можно изменить цвета стандартного GUI Aimybox и заодно указать стартовую фразу, которая будет отображаться на экране при запуске нашего ассистента.

Запускаем


Собственно все! После запуска можно наблюдать вот такие перлы



Если положить рядом другой смартфон с Яндекс Алисой и запустить на ней болталку, то можно получить местами «вменяемый» диалог двух не вполне здоровых людей



Что дальше?


Вот такие идеи приходят пока в голову

  • На главном экране приложения отображать историю ответов Порфирьевича с возможностью шеринга.
  • Сделать экран с настройками — режим диалога, длина ответа, выбор голоса и тп

Если у вас есть идеи получше или нашли багу — то велком в Github, где и лежит теперь весь код проекта. Также можно поконтрибьютить (код пописать).

Полезные ссылки


Похожие публикации

AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 22

  • НЛО прилетело и опубликовало эту надпись здесь
      +10
      настроить Порфирьевича на автоответчик назойливым рекламным звонкам, а то отвлекают почем зря, а так отдохнуть можно будет с юмором
        0
        уже хочу
          0
          Да, у меня тоже такая идея возникала, насчет создания чат-бота секретаря… особенно после того, как мне по-наглому позвонил чат-бот от Тинькофф-банка и представился Игорем)
            +1
            ИИИИИИИгорь)))… есть еще всякие Романы и т.п… а вообще много реальных задач которые даже нужно поручить скриптам с автоответчиками, но все руки не доходят хотябы тестовый вариант реализовать…
              0

              Да уж. Эльдар сделал Ииигоря до того как это стало мэйнстримом ))

            0
            Да, было бы круто =)
            +3
            Порфирьич -win!
            Алиса — lose, со словами «Ой, фсё!»!
              0

              Видимо не за горами время, когда условная Алиса будет спрашивать толерантного совета у условного Порфирьича, как ей правильно подкатить к условной Siri (Алексе или Кортане). И тогда психоаналитики и другие психологи станут не нужны ;-)

                +2
                Было бы неплохо вырезать начало фразы Алисы из озвучки ответа. Стало бы еще задорнее и интересней имхо.
                  0

                  Порфирьевич именно дописывает фразу, сказанную человеком (или нечеловеком). Поэтому вырезать не получится — иначе предложение Порфирьевича будет неправильным.

                    0
                    Да его предложения и так не особо-то интересны и/или остроумны, если честно.
                      +2

                        –2

                        Они интересны настолько, насколько интересно начало вашего рассказа.


                        Ведь нейросети нужно вникнуть в смысл происходящего.

                          0
                          Сужу по приведенному видео. Чего-то забавного/интересного я там не вижу. Возможно, есть где-то примеры действительно остроумных «дописываний», но сомнительно.
                            0
                            Об «остроумности» пока речи не идёт) Интересно уже, что иногда получается какое-то логичное продолжение фразы… пускай и не с первого раза. Пара примеров:

                            «Да его предложения и так не особо-то интересны и/или остроумны, если честно.» — сказал Хэйнхайн. — «Но хотя они и нелепы и глупы», добавил он снова, — «в них есть что-то необычное»

                            Сужу по приведенному видео. Чего-то забавного/интересного я там не вижу. Возможно, есть где-то примеры действительно остроумных «дописываний», но сомнительно. Так что пиши-ка лучше ты, доктор Габдрахиров. Я потом подумаю, что с ними делать…
                    0

                    Клево. Возможно, что нибудь ещё интересное из этого получится.


                    А я на днях эту же нейронку прям в Алису запихал (линк). Правда, API с нейронкой не укладывается в отведенные навыку три секунды, и приходиться костылять и просить юзеров вводить капчу :)

                      0
                      Немного оффтопик. Давно у меня блуждает идея написать расширение к браузеру, которое на новостных сайтах будет автоматически дописывать ко всем заголовкам новостей желтушные окончания вроде:
                      — "… и попала на видео"
                      — "… и сгорел от стыда"
                      — "… и опозорились"
                      — "… и был пристыжен"
                      — "… и была высмеяна в соцсетях"
                      и так далее.
                      Может кто-то имеет хороший опыт работы с нейронками и знает, как это делается быстро?
                        0

                        Это делается без нейронки.

                          0
                          Конечно можно и без. Только в любом случае нужен анализ текста, чтобы определять род, склонение, отсутствие уже существующих окончаний и так далее. Мне кажется тут есть место и для варианта с нейронками.
                          +2
                          Думаю, это действительно достаточно хорошо решается даже простыми эвристиками и NLP *pасчехляет pymorphy2*

                          • Самая красивая женщина в мире показала фото в бикини и приспущенных шортах и попала на видео
                          • Boeing рекордно провалился в борьбе с Airbus и был высмеян в соцсетях
                          • В России ответили на территориальные претензии Эстонии и были высмеяны в соцсетях
                          • ГИБДД изменит практическую часть экзамена на права и опозорится
                          • Двое россиян прыгнули с парашютом с 24 этажа и были пристыжены
                          • Норвежская биатлонистка объяснила традицию «сильно напиваться» в новогоднюю ночь и немедленно выпила
                          • Депутат-единоросс расстрелял двух собак и был высмеян в соцсетях

                          Код
                          import pymorphy2
                          import random
                          from pymorphy2.tagset import OpencorporaTag as Tag
                          
                          class Inflectable: 
                              def __init__(self, value, allowed_gramemmes=set()):
                                  self.value = value
                                  self.allowed_gramemmes = allowed_gramemmes
                          
                              def inflect(self, morph, grammemes):        
                                  if self.allowed_gramemmes:
                                      grammemes = grammemes.intersection(self.allowed_gramemmes)
                                  return morph.parse(self.value)[0].inflect(grammemes)
                          
                          headings = [
                              'Самая красивая женщина в мире показала фото в бикини и приспущенных шортах',
                              'Boeing рекордно провалился в борьбе с Airbus',
                              'В России ответили на территориальные претензии Эстонии',
                              'ГИБДД изменит практическую часть экзамена на права',
                              'Двое россиян прыгнули с парашютом с 24 этажа',
                              'Норвежская биатлонистка объяснила традицию «сильно напиваться» в новогоднюю ночь',
                              'Депутат-единоросс расстрелял двух собак',    
                          ]
                          
                          endings = [
                              ("и", Inflectable("попал"), "на видео"),
                              ("и", Inflectable("сгорел"), "от стыда"),
                              ("и", Inflectable("опозорился")),
                              ("и", Inflectable("был"), Inflectable("пристыжён", Tag.NUMBERS.union(Tag.GENDERS))),
                              ("и", Inflectable("был"), Inflectable("высмеян", Tag.NUMBERS.union(Tag.GENDERS)), "в соцсетях"),
                              ("и", "немедленно", Inflectable("выпил")),
                          ]
                          
                          def guess_inflect(morph, words):
                              for word in words:
                                  p = morph.parse(word)[0]
                                  if 'VERB' in p.tag:
                                      inflect = {p.tag.gender, p.tag.number, p.tag.tense, p.tag.person}
                                      inflect.remove(None)
                                      return inflect
                          
                          def inflect_ending(morph, words, inflect):
                              result = []
                              for word in words:
                                  if(isinstance(word, Inflectable)):
                                      result.append(word.inflect(morph, inflect).word)
                                  else:
                                      result.append(word)
                              return ' '.join(result)
                          
                          
                          morph = pymorphy2.MorphAnalyzer()
                          for heading in headings:    
                              inf = guess_inflect(morph, heading.split(' '))
                              if not inf:
                                  continue
                              ending = inflect_ending(morph, random.choice(endings), inf)
                              print(heading + ' ' + ending)
                          


                          Для JS тут на хабре тоже что-то подобное было.
                            0
                            Вы мой герой! Отличный пример, спасибо! Будет теперь с чем поразвлекаться!

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

                        Самое читаемое