Распознавание речи на STM32F4-Discovery

Автор работы: Пользователь скрыл имя, 11 Октября 2013 в 17:10, курсовая работа

Краткое описание

Также стоит отметить статью Мел-кепстральные коэффициенты (MFCC) и распознавание речи и выполненную на её основе работу по идентификации человека по голосу: Кто там? — Идентификация человека по голосу.
В данной работе предлагается простой алгоритм (и его реализация на C++) системы распознавания речи по короткому словарю, основанный на анализе статистического распределения мел-кепстральных коэффициентов (Mel-frequency cepstrum coefficients, MFCC).

Прикрепленные файлы: 1 файл

Пояснювальна записка .doc

— 447.00 Кб (Скачать документ)


                                                                Вступление 
 
В последнее время наблюдается значительный рост интереса к технологиям, связанным с распознаванием речи. Можно назвать несколько причин этого роста, в частности, значительное рост вычислительных возможностей и обучающего материала. На хабрахаре пользователем domage был опубликован целый цикл статей по основам технологий распознавания речи. Также стоит отметить статью Мел-кепстральные коэффициенты (MFCC) и распознавание речи и выполненную на её основе работу по идентификации человека по голосу: Кто там? — Идентификация человека по голосу.  
В данной работе предлагается простой алгоритм (и его реализация на C++) системы распознавания речи по короткому словарю, основанный на анализе статистического распределения мел-кепстральных коэффициентов (Mel-frequency cepstrum coefficients, MFCC). 
 
 

  • Постановка задачи

  •  
    Существуют множество методов  распознавания речи, в подавляющем  большинстве случаев они основаны на методах статистического анализа  и теории вероятностей (Hidden Markov Model, Gaussian Mixture Model и т.п.). Как известно, компания google предоставляет бесплатный сервис по распознаванию коротких речевых сообщений. На основе этого сервиса было даже предложено распознавание речи при помощи микроконтроллера: Распознавание речи на STM32F4-Discovery . Однако, возникает вопрос: есть ли возможность сделать свою систему распознавания речи, пусть даже на довольно ограниченном по размеру словаре, без использования «внешних» сервисов, при этом чтобы она работала быстро и с приемлемым качеством? 
     

  • Основная идея

  •  
    Итак, для распознавания речи будем  использовать MFCC. Чтобы не вдаваться  в подробности скажу, что относиться к ним стоит лишь как к некоторому фильтру, на входе которого — фонограмма, на выходе — набор векторов (коэффициенты), который мы и будем распознавать как некоторое слово или набор слов. Справедливости ради стоит отметить, что существуют множество других акустических признаков, использующихся для распознавания речи: Perceptual linear predictive (PLP), Linear prediction cepstral coefficient (LPCC), Linear frequency cepstral coefficients (LFCC).  
    Основная идея заключается в использовании линейного дискриминантного анализа для идентификации слова. Однако, он применим лишь для векторов одинаковой размерности. Т.к. слова могут быть различной длины, возникает вопрос: каким образом преобразовать последовательность произвольного числа MFCC-векторов в вектор фиксированной размерности?  
    Можно поступить следующим образом: находить места «сгущения» распределения этих векторов и в качества результирующего вектора брать конкатенацию векторов, являющихся центрами «сгущений». Такой конкатенированный вектор будем называть супервектором средних, а сами центры — средними значениями. При этом в качестве «отправной точки» будем использовать супервектор средних, полученный на всех MFCC-векторах всей базы обучения. Преобразовав таким образом последовательность MFCC-векторов в один супервектор средних фиксированной размерности, мы можем применять различные методы классификации.  
    Очевиден принципиальный недостаток такого подхода: не учитывается динамика распределения MFCC-признаков по времени, следовательно, система априори не способна различать, к примеру, слова «главрыба» и «абырвалг», т.к. общее распределение MFCC-векторов таких слов будет примерно одинаковым (соответственно, центры «сгущений» будут совпадать). 
     

  • Описание алгоритма

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

    1. Находим супервектор средних для всей базы обучения при помощи алгоритма K-средних.  
      Пример работы алгоритма K-средних для K=10 представлен на рисунке:  
        
      где большие красные квадраты и есть искомые средние значения.
    2. Для каждого файла базы находим собственные средние значения по формуле:  
      Mk = a * Mk0 + (1 — a) * Mk', k = 1:K  
      где Mk0 — среднее значение, найденное в п.1,  
      Mk' — среднее значение, полученное в результате применения одной итерации алгоритма K-средних для MFCC-векторов файла с использованием в качестве начального значения Mk0,  
      a = R/(R + Nk), где R — коэффициент «чувствительности», Nk — число MFCC-векторов, соответствующие среднему значению Mk'.  
      Найденные таким образом средние значения будем называть адапритованными средними значениями.  
      Пример адаптированных средних значений для файла представлен на рисунке:  
    3. Имея теперь вместо исходных фонограмм адаптированные супервектора средних, проводим LDA для N классов (каждый класс соответствует одному слову).  
      В результате мы должны получить матрицу, состоящую из векторов нового базиса, при проекции на который исходные адаптированные супервектора средних должны достаточно хорошо разделяться. Пример для N=4:  
    4. Проецируем все адаптированные супервектора средних на новый базис и находим средние значения и СКО (среднее квадратичное отклонение) проекций для каждого класса.
    5. Для определения принадлежности тестовой фонограммы тому или иному классу (т.е. распознавания), выполняем для неё пп. 2 и 4, далее находим расстояния полученной проекции до средних значений всех классов (можно дополнительно нормировать их на соответствующее СКО). Минимальное расстояние и будет соответствовать классу, к которому принадлежит тестовая фонограмма.

     
     

  • Реализация

  •  
     
    Создание собственной системы  распознавания слов состоит из следующих  этапов: 

    1. Запись фонограмм для обучения и тестирования  
      Для записи можно воспользоваться любой программой, умеющей записывать звук и сохранять его в формате WAVE. Я рекомендую использовать бесплатную программу Audacity.  
      Разработанная система не умеет выделять речевые сегменты, поэтому при записи нужно стараться, чтобы в фонограмме присутствовала только речь. Чем качественнее используется микрофон, тем качественнее получается система. Записывать необходимо в моно-режиме с частотой дискретизации 16000.
    2. Построение MFCC-векторов  
      Для построения MFCC-векторов можно использовать бесплатную библиотеку SPro 5.0. Я взял на себя ответственность, немного перебрал эту библиотеку, исправил парочку ошибок и сделал сборку программы sfbcep.exe под windows (см. папку ../spro-5.0). 32-разрядная версия этой программы лежит в папке ../tools. Для построения MFCC-векторов я использовал следующие параметры: 
    3. sfbcep.exe --format=wave --sample-rate=16000 --mel --freq-min=0 --freq-max=8000 --fft-length=256 --length=16.0 --shift=10.0 --num-ceps=13 [входной WAVE-файл] [выходной файл с MFCC-векторами]

     

    1. Обучение и тестирование системы 
      Для обучения и тестирования системы я написал программу wrsystem на языке C++. Полный исходный код находится в папке ../wrsystem. 32-разрядную версию этой программы можно взять в папке ../tools.  
      Релизация алгоритма LDA была позаимствована из библиотеки ALGLIB.  
      Программа wrsystem имеет два режима работы: обучение (в случае наличия параметра --learn) и тестирование. Эта программа принимает на вход три основных параметра:  
      — Путь к файлу с описанием базы обучения (тестирования) (параметр --base). Пример файла с описанием базы лежит в папке ../base, также описание формата можно посмотреть, запустив программу с параметром --help.  
      — Путь к бинарному файлу, хранящему результат обучения системы (параметр --system). В режиме обучения этот файл создается, в режиме тестирования — считывается.  
      — Путь к файлу, в который записывается результаты тестирования системы на указанной базе: матрица перепутывания и значение WER (Word Error Rate) (параметр --test_results).

     
     

  • Результаты экспериментов

  •  
    В качестве эксперимента я создал систему, которая умеет распознавать 14 слов, записанных моим голосом. Для обучения системы я записал каждое слово 4-5 раз, а для тестирования — 7 раз. Итого база обучения содержит 63 файла, а база тестирования — 98. Использовались следующие параметры при обучении:  

    • Количество средних значений: 10
    • Коэффициент «чувствительности» при адаптации: 20
    • Размерность проекции: 20
    • Использование нормализации на СКО: отсутствует

     
    Результат тестирования на базе обучения показал уровень ошибки распознавания  слов (WER) 1,6%, а на базе тестирования 5,1%. 
     
                                       На что стоит обратить внимание

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

     

    #include "WordRecognizer.h"

     

    #include <exception>

    #include <iostream>

    #include <fstream>

    #include <string>

    #include <vector>

     

    #include <float.h>

    #include <math.h>

     

    using namespace std;

    using namespace WordRecognizer;

     

    //! Реализация интерфейса IVectorsSet с хранением всех данных  в оперативной памяти

    class MemoryVectorsSet : public IVectorsSet

    {

    private:

    vector<vector<float> > m_data;

    int m_dim;

     

    public:

    MemoryVectorsSet() : m_dim(0)

    {

    }

     

    void SetDim(const int &_dim)

    {

    if (_dim < 0)

    throw exception("MemoryVectorsSet::SetDim: _dim < 0");

    m_dim = _dim;

    }

     

    const int GetDim() const

    {

    return m_dim;

    }

     

    void SetSize(const int &_size)

    {

    if (_size < 0)

    throw exception("MemoryVectorsSet::SetSize: _size < 0");

    m_data.resize(_size);

    }

     

    const int GetSize() const

    {

    return (const int)m_data.size();

    }

     

    void SetVectorsNum(const int &_indx, const int &_vectorsNum)

    {

    if (_indx >= GetSize())

    throw exception("MemoryVectorsSet::SetVectorsNum: _indx >= size");

    if (m_dim <= 0)

    throw exception("MemoryVectorsSet::SetVectorsNum: m_dim <= 0");

    m_data[_indx].resize(_vectorsNum * m_dim);

    memset(&m_data[_indx][0], 0, sizeof(float) * _vectorsNum * m_dim);

    }

     

    void GetVectors(

    const int  &_indx,

    int    &_vectorsNum,

    const float* &_vectors) const

    {

    if (_indx >= GetSize())

    throw exception("MemoryVectorsSet::GetVectors: _indx >= size");

    if (m_dim <= 0)

    throw exception("MemoryVectorsSet::GetVectors: m_dim <= 0");

    _vectorsNum = (int)m_data[_indx].size() / m_dim;

    _vectors = (_vectorsNum > 0) ? &m_data[_indx][0] : NULL;

    }

     

    void GetVectors(

    const int &_indx,

    int   &_vectorsNum,

    float*  &_vectors)

    {

    const float* pVectors;

    GetVectors(_indx, _vectorsNum, pVectors);

    _vectors = const_cast<float*>(pVectors);

    }

     

     

    };

     

    //! Класс-оболочка для упрощения  работы с системой распознавания  слов

    class WordRecognizerWrapper

    {

    public:

    vector<vector<string> > m_files;

    vector<string> m_wordsBase;

    MemoryVectorsSet m_mfcc;

     

    int m_dim;

    int meansNum;

    vector<float> m_means;

     

    float m_adaptWeight;

    MemoryVectorsSet m_adaptMeans;

     

    int m_LDASize;

    vector<float> m_LDAMatrix;

    vector<float> m_mean;

     

    MemoryVectorsSet m_projections;

     

    vector<string> m_learnWords;

    vector<float> m_wordsMean;

    vector<float> m_wordsSigma;

     

    bool m_baseOK;

    bool m_meansOK;

    bool m_adaptMeansOK;

    bool m_LDAOK;

    bool m_projectionsOK;

    bool m_compareSystemOK;

     

    public:

    WordRecognizerWrapper() : m_baseOK(false), m_meansOK(false), m_adaptMeansOK(false), m_LDAOK(false),

    m_projectionsOK(false), m_compareSystemOK(false), meansNum(0), m_adaptWeight(50.), m_LDASize(0)

    {

    }

     

    ~WordRecognizerWrapper()

    {

    }

     

    void LoadBase(const char* _baseFile)

    {

    string fileName(_baseFile);

    string path = fileName.substr(0, fileName.find_last_of("/\\"));

    path += "/";

    ifstream baseInfo(_baseFile);

    if (!baseInfo.is_open())

    throw exception("can't open base file");

     

    const int maxStr = 5000;

    char tmpStr[maxStr];

    int wordsNum = 0;

     

    baseInfo.getline(tmpStr, maxStr);

    wordsNum = atoi(tmpStr);

    if (wordsNum <= 0)

    throw exception("words num <= 0");

     

    m_wordsBase.resize(wordsNum);

    m_files.resize(wordsNum);

     

    int filesNum = 0;

    int filesCount = 0;

     

    for (int i = 0; i < wordsNum; i++)

    {

    baseInfo.getline(tmpStr, maxStr);

    m_wordsBase[i] = tmpStr;

    if (m_wordsBase[i].length() == 0)

    ThrowFormatException("word[%d] name length == 0", i);

    baseInfo.getline(tmpStr, maxStr);

    filesNum = atoi(tmpStr);

    if (filesNum <= 0)

    ThrowFormatException("files num for word %s <= 0", m_wordsBase[i].c_str());

    m_files[i].resize(filesNum);

    filesCount += filesNum;

    for (int j = 0; j < filesNum; j++)

    {

    if (baseInfo.eof())

    ThrowFormatException("unexpected end of file within word %s", m_wordsBase[i].c_str());

    baseInfo.getline(tmpStr, maxStr);

    m_files[i][j] = tmpStr;

    if (m_files[i][j].length() == 0)

    ThrowFormatException("file name [%d] length for word %s == 0", j, m_wordsBase[i].c_str());

    }

    }

    baseInfo.close();

     

    m_mfcc.SetSize(filesCount);

    filesCount = 0;

    for (int i = 0; i < wordsNum; i++)

    {

    for (size_t j = 0; j < m_files[i].size(); j++)

    {

    ifstream fileMFCC((path + m_files[i][j]).c_str(), ios_base::in | ios_base::binary);

    if (!fileMFCC.is_open())

    ThrowFormatException("can't open file %s", (path + m_files[i][j]).c_str());

     

    // Read SPRO file

    unsigned short dim = 0;

    long flag = 0;

    float freq = 0;

     

    fileMFCC.seekg(0, ios_base::end);

    size_t fileSize = (size_t)fileMFCC.tellg();

    fileMFCC.seekg(0, ios_base::beg);

    if (fileSize < 10)

    ThrowFormatException("can't read file %s", (path + m_files[i][j]).c_str());

     

    fileMFCC.read((char*)&dim, sizeof(dim));

    fileMFCC.read((char*)&flag, sizeof(flag));

    Информация о работе Распознавание речи на STM32F4-Discovery