Всем привет!

Возвращаюсь к теме применения нейронных сетей в личных целях. На этот раз будем запускать долгий проект, по созданию голосового ассистента (ГА). Создать свою Алису или Siri довольно просто, есть уже много статей на Хабр (и не только), которые подробно описывают основные принципы, но чтобы было действительно профессионально и интересно мы углубимся в эту тему и «прикрутим» нейронные сети к нашему ГА. И в первой части начнем с того, что научим нашего голосового ассистента распознавать человека, который в данный момент пользуется компьютером.

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

Первую часть нашего большого проекта поделим на несколько этапов:

  1. Создадим основную базу ГА, прикрутим функционал по общению с пользователем и несколько вспомогательных фичей для проверки работоспособности ГА.

  2. Напишем и обучим нейронную сеть для распознавания лиц.

  3. Совместим первый и второй пункт.

Итак, можем приступать!


Этап 1 - Фрося

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

#Импорт необходимых бибилиотек
import speech_recognition as sr
import pyttsx3
import pyaudio
import sounddevice as sd
import os
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0'
import pyautogui
import requests
import cv2
import time
import transliterate #Из кириллицы в латиницу

Во-первых, необходимо придумать имя нашему голосовому ассистенту. Моего ГА зовут Фрося, в честь своенравной, но очень милой кошки моих друзей.

Фрося
Фрося

Дальше научим нашу Фросю говорить:

# Функция голоса Фроси
def Frosia_speak(Frosia_audio):
    # Инициализируем Фросю
    start = pyttsx3.init()
    start.say(Frosia_audio)
    start.runAndWait()

И воспринимать команды:

# Функция команды пользователя
def listen_command():
    # Инициализируем распознование речи
    r = sr.Recognizer()

    #Пауза, после которой начнётся запись голоса
    r.pause_threshold = 0.5
    print('Говорите')
    #Запускаем микрофон
    with sr.Microphone() as mic:
        #Лишний шум источник микрофон,
        r.adjust_for_ambient_noise(source=mic, duration=0.5)
        try:
            #Запрос голосом с микрофона, timeout - время пока Фрося ждет отклика,
            audio = r.listen(source=mic, timeout=3)
            #Распознаем речь в текст с помощью google (требуется интернет)
            user_command = r.recognize_google(audio_data=audio, language='ru-RU').lower()
        except:
            message = 'Не поняла вас. Повторите, пожалуйста'
            Frosia_speak(message)
            user_command = listen_command()
    return user_command

Хотя тут всё просто (описано в коде), но на самом деле это основа взаимодействия с Фросей. Как видите, необходимо подключение к интернету для распознавания речи, это гугловский функционал, но подойдет любой другой. Мы делаем по принципу: «чем проще и доступнее, тем лучше» (по крайней мере, для текущей задачи).

С помощью функции listen_command мы говорим команду и ГА будет на неё реагировать. Для примера напишем функцию запроса прогноза погоды. Сама функция:

#Функция погоды
def weather():
    Frosia_speak('Назовите город')
    city = listen_command()
    url = 'https://api.openweathermap.org/data/2.5/weather?q=' + city + '&units=metric&lang=ru&appid=79d1ca96933b0328e1c7e3e7a26cb347'
    weather_data = requests.get(url).json()
    temperature = round(weather_data['main']['temp'])
    temperature_feels = round(weather_data['main']['feels_like'])
    Frosia_speak(f'Сейчас в городе {city} температура {temperature}, ощущается как {temperature_feels}')

И то, что будет делать Фрося в случае получения команды «Погода»:

def Frosia_actions(comm):
    if comm == 'погода':
        weather()
        Frosia_speak('Что нибудь ещё?')
        comm = listen_command()
        Frosia_actions(comm)
    else:
        Frosia_speak('До свидания, кожанный ***!')

Через elif расширяем функционал нашего ГА, пишем какие угодно функции по управлению ПК и любому другому взаимодействию! 

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

Для тех же, кто хочет прикрутить нейроки и сделать нашу Фросю чуточку умнее, двигаемся дальше!


Этап 2 - Нейронная сеть

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

Общая задача этапа 2 будет звучать так: Необходимо сделать модель нейронной сети мультиклассовой классификации. Всё будем делать на tensotflow (некоторые компании в тестовых заданиях требует решение на PyTorch, но у нас сейчас другая цель, поэтому возьмем, что попроще).

Для любой нейронной сети в первую очередь необходимы данные. Пускай наш Голосовой Ассистент сделает 25 фотографий нового пользователя (спереди, слева, справа итд).

# Делаем фото функция
def make_photo(u_path):
    cap = cv2.VideoCapture(0)
    # "Прогреваем" камеру, чтобы снимок не был тёмным
    for i in range(5):
        cap.read()
    Frosia_speak('Пожалуйста, смотрите ровно в камеру 5 секунда, слегка наклоняя голову')
…

Фрося будет говорить, что нужно делать.

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

data_image_gen = ImageDataGenerator(preprocessing_function=preprocess_input,
                                   rotation_range=40, # поворот
                                   shear_range=0.2, # сдвиг
                                   zoom_range=0.2, # увеличение
                                   horizontal_flip=True, # зеркальный поворот
                                   fill_mode="nearest", #Заполняем пробелы
                                   validation_split=0.2)

Вот, что по итогу будет подаваться  нашей нейронке:

Как видите, в процессе фотографирования Фрося просит поворачивать и наклонять голову чтобы датасет был более разнообразным.
Как видите, в процессе фотографирования Фрося просит поворачивать и наклонять голову чтобы датасет был более разнообразным.

Теперь можно начинать формировать архитектуру нейронной сети. Это будет простая custom архитектура. Сейчас мы пытаемся определить структуру работы, тип архитектуры не так важен, можно провести исследования, сравнивая и подбирая наилучшую, но в данном случае ограничимся самой простой версией (экспериментировать никто не запрещает, когда всё будет готово, можно улучшать качество до бесконечности). Особо не будем фантазировать, сделаем несколько светок и пулинг. Обратим внимание на добавление слоя Dropout для предотвращения переобучения модели.

#Модель
model_user_Face = tf.keras.Sequential([tf.keras.layers.ZeroPadding2D((1,1),input_shape=(224,224, 3)),
                                           tf.keras.layers.Convolution2D(64, (3, 3), activation='relu'),
                                           tf.keras.layers.ZeroPadding2D((1,1)),
                                           tf.keras.layers.Convolution2D(64, (3, 3), activation='relu'),
                                           tf.keras.layers.MaxPooling2D((2,2), strides=(2,2)),
...

В конце я накатил на нашу нейронку веса VGGFaces (легко находятся в свободном доступе в интернете) для лучшего обучения модели:

#загрузим веса VGGFace
model_user_Face.load_weights('vgg_face_weights.h5')

Так, теперь, когда у нас всё готово – запустим обучение. Постараемся минимизировать время, так как обучение будет проходить каждый раз, когда перед нашей Фросей будет возникать новое лицо и ей нужно будет дообучиться на новых данных.

После 35 эпох обучение можно останавливать
После 35 эпох обучение можно останавливать

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

В идеале конечно прикрутить сюда каскады Хаара, или YOLO для определения BoundingBox-a лица, но пока что качество модели нас удовлетворяет (оставим этот момент на будущий этап разработки, сейчас определим базу).

Сохраним нашу модель и можно попробовать протестировать.

faces_model_base_user_Face.save(dir_path + f'checkpoint_best_user_count_{n_faces}.h5')

Следите за версиями tf (чтобы загрузить в скрипт ГА веса модели необходимо, чтобы версии tensorflow во время сохранения и загрузки совпадали)


Этап 3 - Голосовой Ассистент

Итак, что мы имеем: 

Говорящая машина – есть

Мозги, отвечающие за узнавание лиц – есть

Теперь надо это подружить друг с другом. Работать это будет так: Как только запускается скрипт Фроси (можно настроить автоматический запуск при включении компьютера), то будет сразу делаться фотография текущего пользователя:

Фото меня, если вы ещё не поняли как я выгляжу
Фото меня, если вы ещё не поняли как я выгляжу

Далее Фрося будет закидывать фотку в загруженную модель нс и смотреть с какой вероятностью перед ней сидит человек, который ранее уже участвовал в обучении Фроси (поставим порог узнавания равным 75%). Если это новый человек, то Фрося предложит познакомиться и сделать фото нового пользователя и далее обучит нейронную сеть с учётом новых данных. Всё, человек находится в памяти нашей Фроси, и теперь она его узнает в следующий раз. Тут можно ограничить количество пользователей, чтобы не терять в качестве, но это всё уже “докручивается” по мере эксплуатации.

И теперь можно сделать возможность доступа определенных людей к определенным действиям и папкам на компьютере. То есть, по сути, мы сделали аналог FaceID. Ну и теперь всё ограничивается вашей фантазией, научив нашу Фросю «видеть» и «понимать», что происходит перед экраном, можно обучить её множеству интересных функций. В мыслях были идеи сделать возможность управления ПК с помощью жестов рук, например, «поднять открытую ладонь – свернуть все окна», «показать большой палец – выключить компьютер» и так далее. Практической пользы от этого особо нет, но для общего развития это было бы интересно реализовать.

Кому интересно, по ссылке вы можете скачать стикерпак замечательной кошки Фроси - https://t.me/addstickers/BestFrosyaNegodyajka

Теперь дело за вами, вы можете расширять и углублять своего голосового ассистента как угодно. Лично я планирую сделать ещё один интересный функционал для Фроси, какой именно я обязательно расскажу в следующей статье. Но об этом чуть позже…

Всем добра!

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


  1. Question_man
    07.07.2024 18:18

    Сколько данных в итоговом датасете? По 25 фоток на человека? Какой аккураси на тестовых фотках? Что-то не внушают доверия такие мощные колебания лоссов.


  1. Pol1mus
    07.07.2024 18:18

    Голосовой ассистент с распознаванием по.. лицу. Почему не по голосу?


    1. IamSVP
      07.07.2024 18:18

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


  1. IamSVP
    07.07.2024 18:18

    А где дропаут?
    А где дропаут?

    Кажется, что статья немного сыровата... Не говоря уже о том, что на изобр. лицо занимает ну процентов 20, т.е. слишком большой вклад фона