Распознавание речи на 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).
В последнее время наблюдается значительный рост интереса к технологиям, связанным с распознаванием речи. Можно назвать несколько причин этого роста, в частности, значительное рост вычислительных возможностей и обучающего материала. На хабрахаре пользователем domage был опубликован целый цикл статей по основам технологий распознавания речи. Также стоит отметить статью Мел-кепстральные коэффициенты (MFCC) и распознавание речи и выполненную на её основе работу по идентификации человека по голосу: Кто там? — Идентификация человека по голосу.
В данной работе предлагается простой алгоритм (и его реализация на C++) системы распознавания речи по короткому словарю, основанный на анализе статистического распределения мел-кепстральных коэффициентов (Mel-frequency cepstrum coefficients, MFCC).
Постановка задачи
Существуют множество методов
распознавания речи, в подавляющем
большинстве случаев они
Основная идея
Итак, для распознавания речи будем
использовать MFCC. Чтобы не вдаваться
в подробности скажу, что относиться
к ним стоит лишь как к некоторому
фильтру, на входе которого — фонограмма, на выходе — набор векторов
(коэффициенты), который мы и будем распознавать
как некоторое слово или набор слов. Справедливости
ради стоит отметить, что существуют множество
других акустических признаков, использующихся
для распознавания речи: Perceptual linear predictive
(PLP), Linear prediction cepstral coefficient (LPCC), Linear frequency
cepstral coefficients (LFCC).
Основная идея заключается в использовании линейного дискриминантного
анализа для идентификации слова. Однако, он применим
лишь для векторов одинаковой размерности.
Т.к. слова могут быть различной длины,
возникает вопрос: каким образом преобразовать
последовательность произвольного числа
MFCC-векторов в вектор фиксированной размерности?
Можно поступить следующим образом: находить
места «сгущения» распределения этих
векторов и в качества результирующего
вектора брать конкатенацию векторов,
являющихся центрами «сгущений». Такой
конкатенированный вектор будем называть
супервектором средних, а сами центры
— средними значениями. При этом в качестве
«отправной точки» будем использовать
супервектор средних, полученный на всех
MFCC-векторах всей базы обучения. Преобразовав
таким образом последовательность MFCC-векторов
в один супервектор средних фиксированной
размерности, мы можем применять различные
методы классификации.
Очевиден принципиальный недостаток такого
подхода: не учитывается динамика распределения
MFCC-признаков по времени, следовательно,
система априори не способна различать,
к примеру, слова «главрыба» и «абырвалг»,
т.к. общее распределение MFCC-векторов таких
слов будет примерно одинаковым (соответственно,
центры «сгущений» будут совпадать).
Описание алгоритма
В качестве базы обучения будем использовать множество файлов, каждый из которых
представляет собой набор MFCC-векторов,
полученных из фонограммы с записью того
или иного слова. При этом файлы с записью
одного и того же слова должны быть объединены
в одну группу.
Вот как выглядит распределение первых
двух компонент MFCC-векторов всей базы
обучения:
Алгоритм состоит из следующих этапов:
- Находим супервектор средних для всей базы обучения при помощи алгоритма K-средних.
Пример работы алгоритма K-средних для K=10 представлен на рисунке:
где большие красные квадраты и есть искомые средние значения. - Для каждого файла базы находим собственные средние значения по формуле:
Mk = a * Mk0 + (1 — a) * Mk', k = 1:K
где Mk0 — среднее значение, найденное в п.1,
Mk' — среднее значение, полученное в результате применения одной итерации алгоритма K-средних для MFCC-векторов файла с использованием в качестве начального значения Mk0,
a = R/(R + Nk), где R — коэффициент «чувствительности», Nk — число MFCC-векторов, соответствующие среднему значению Mk'.
Найденные таким образом средние значения будем называть адапритованными средними значениями.
Пример адаптированных средних значений для файла представлен на рисунке:
- Имея теперь вместо исходных фонограмм адаптированные супервектора средних, проводим LDA для N классов (каждый класс соответствует одному слову).
В результате мы должны получить матрицу, состоящую из векторов нового базиса, при проекции на который исходные адаптированные супервектора средних должны достаточно хорошо разделяться. Пример для N=4:
- Проецируем все адаптированные супервектора средних на новый базис и находим средние значения и СКО (среднее квадратичное отклонение) проекций для каждого класса.
- Для определения принадлежности тестовой фонограммы тому или иному классу (т.е. распознавания), выполняем для неё пп. 2 и 4, далее находим расстояния полученной проекции до средних значений всех классов (можно дополнительно нормировать их на соответствующее СКО). Минимальное расстояние и будет соответствовать классу, к которому принадлежит тестовая фонограмма.
Реализация
Создание собственной системы
распознавания слов состоит из следующих
этапов:
- Запись фонограмм для обучения и тестирования
Для записи можно воспользоваться любой программой, умеющей записывать звук и сохранять его в формате WAVE. Я рекомендую использовать бесплатную программу Audacity.
Разработанная система не умеет выделять речевые сегменты, поэтому при записи нужно стараться, чтобы в фонограмме присутствовала только речь. Чем качественнее используется микрофон, тем качественнее получается система. Записывать необходимо в моно-режиме с частотой дискретизации 16000. - Построение MFCC-векторов
Для построения MFCC-векторов можно использовать бесплатную библиотеку SPro 5.0. Я взял на себя ответственность, немного перебрал эту библиотеку, исправил парочку ошибок и сделал сборку программы sfbcep.exe под windows (см. папку ../spro-5.0). 32-разрядная версия этой программы лежит в папке ../tools. Для построения MFCC-векторов я использовал следующие параметры:
- 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-векторами]
- Обучение и тестирование системы
Для обучения и тестирования системы я написал программу 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::
m_dim = _dim;
}
const int GetDim() const
{
return m_dim;
}
void SetSize(const int &_size)
{
if (_size < 0)
throw exception("MemoryVectorsSet::
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::
if (m_dim <= 0)
throw exception("MemoryVectorsSet::
m_data[_indx].resize(_
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::
if (m_dim <= 0)
throw exception("MemoryVectorsSet::
_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("
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));