Drive your career as React Developer with Symphony Solutions!
×Закрыть

Осваиваем Kaldi на примере wake-up-words детектора

Всем привет! Сегодня я расскажу, как можно использовать фреймворк Kaldi для поиска ключевого слова в устной речи (wake-up-word detection).

На рынке существуют самые разные решения для систем, которые могут выводить устройство из спящего режима по ключевому слову. Основное требования к таким системам — минимальное потребление ресурсов, так как запускаются они непосредственно на устройствах, а не в облаке, и работают 24/7.

Один из самых простых способов решения такой задачи — это использование автоматической системы распознавания речи (ASR). Большинство ASR-систем включают в себя акустическую и языковую модели. Эти модели обучаются независимо друг от друга. Акустическая модель преобразовывает звук в фонемы, а языковая — составляет из последовательности фонем слова и предложения. Сейчас активно развиваются end-to-end системы, которые объединяют обе эти модели.

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

В обучающих целях используем малую версию известного ASR датасета LibriSpeech (openslr.org). Нам не нужны значительные вычислительные ресурсы, так как обучающий датасет содержит около 5 часов аудио, а тестовый — всего 2 часа.

Установить Kaldi можно следуя данной инструкции (шаг с установкой IRSTLM можно сейчас пропустить), также нужно установить flac.

Подготовка датасетов

В первую очередь настроим наше рабочее пространство. Перейдем в Kaldi директорию, из которой будем запускать все скрипты:

cd ~/kaldi/prod/egs/mini_librispeech/s5; . ./path.sh; train_cmd=run.pl

Создадим директории, в которых будем хранить результаты наших экспериментов и обучающие корпуса:

dir=~/kostya/hot_word
mkdir -p $dir
mkdir -p $dir/corpus

Скачаем train и dev корпуса, а также языковую модель c openslr.org:

data_url=www.openslr.org/resources/31
lm_url=www.openslr.org/resources/11

for part in dev-clean-2 train-clean-5; do
  local/download_and_untar.sh $dir/corpus $data_url $part
done

local/download_lm.sh $lm_url $dir/corpus $dir/local/lm

На данном этапе train и dev корпуса находятся в $dir/corpus/LibriSpeech и разбиты на множество подпапок. В каждой есть аудиофрагменты и транскрипции к ним.

Для дальнейшей работы структурируем данные в соответствии с требованиями Kaldi. Для этого уже есть готовый скрипт:

for part in dev-clean-2 train-clean-5; do
    # use underscore-separated names in data directories.
    local/data_prep.sh $dir/corpus/LibriSpeech/$part $dir/$(echo $part | sed s/-/_/g)
done

Таким образом, train и dev теперь находятся в $dir/train_clean_5 и $dir/dev_clean_2 соответственно.

То же самое касается и языковой модели:

local/prepare_dict.sh --stage 3 --nj 30 --cmd "$train_cmd" \
    $dir/local/lm $dir/local/lm $dir/local/dict

utils/prepare_lang.sh $dir/local/dict \
    "<UNK>" $dir/local/lang_tmp $dir/lang

Так как мы не используем полноценную языковую модель, а создаем свою, нам понадобится только лексикон ($dir/local/dict/lexicon.txt) и список используемых фонем ($dir/lang/phones.txt).

Теперь посчитаем мел-частотные кепстральные коэффициенты (MFCC). Они будут входными данными для акустической модели. Также нам нужно аудиосигнал нормализовать (cmvn). Чтоб сгенерировать MFCC, аудиосигнал разбивается на фреймы длительностью 0,01 с. Для каждого фрейма нам нужно создать соответствующие коэффициенты (обычно их 13). В итоге из каждого аудиосигнала получаем последовательность фреймов из 13 коэффициентов:

for part in dev_clean_2 train_clean_5; do
    steps/make_mfcc.sh --cmd "$train_cmd" --nj 10 $dir/$part
    steps/compute_cmvn_stats.sh $dir/$part
done

Акустическая модель

Теперь обучим акустическую модель. Как я уже говорил, мы будем использовать HMM. Процесс обучения состоит из нескольких этапов, на каждом из которых модель усложняется.

Первая модель, так называемая монофоническая, наиболее простая. Она учится предсказывать отдельные фонемы для каждого фрейма, на которые мы только что разбили входной аудиосигнал. На первой итерации обучения фонемы, которые соответствуют словам из текста, распределяются равномерно по всей длине аудиосигнала. На следующих итерациях уточняется принадлежность каждой фонемы к тому или иному фрейму:

steps/train_mono.sh --boost-silence 1.25 --nj 5 --cmd "$train_cmd" \
    $dir/train_clean_5 $dir/lang $dir/exp/mono

Поздравляю, мы получили нашу первую акустическую модель! Она находится в файле $dir/exp/mono/final.mdl. Теперь используем её для чтоб точнее локализовать фонемы в аудиосигнале. Это упростит обучение последующей модели. Такой процесс называется aligning:

steps/align_si.sh --boost-silence 1.25 --nj 5 --cmd "$train_cmd" \
    $dir/train_clean_5 $dir/lang $dir/exp/mono $dir/exp/mono_ali_train_clean_5

Результаты работы скрипта находятся в $dir/exp/mono_ali_train_clean_5, где каждому фрейму входного сигнала поставлена соответствующая ему фонема.

Для следующей акустической модели увеличим размерность вектора входных переменных с помощью дельта и дельта-дельта коэффициенты. Они считаются усреднением MFCC для соседних фреймов. Так как одна и та же фонема может произносится по-разному в зависимости от предыдущей и следующей фонемы, мы будем использовать трифоны. Каждая трифона состоит из трех фонем — центральной, левой и правой.

steps/train_deltas.sh --boost-silence 1.25 --cmd "$train_cmd" \
    2000 10000 $dir/train_clean_5 $dir/lang $dir/exp/mono_ali_train_clean_5 $dir/exp/tri1

Повторим aligning процедуру используя новую модель:

steps/align_si.sh --nj 5 --cmd "$train_cmd" \
    $dir/train_clean_5 $dir/lang $dir/exp/tri1 $dir/exp/tri1_ali_train_clean_5

Следующий этап — это обучение акустической модели. Мы используем LDA-MLLT преобразования к входным переменным. Их основная цель — это декорреляция входных данных.

steps/train_lda_mllt.sh --cmd "$train_cmd" \
    --splice-opts "--left-context=3 --right-context=3" 2500 15000 \
    $dir/train_clean_5 $dir/lang $dir/exp/tri1_ali_train_clean_5 $dir/exp/tri2b

Последний раз применяем aligning процедуру к обучающим данным:

steps/align_si.sh  --nj 5 --cmd "$train_cmd" --use-graphs true \
    $dir/train_clean_5 $dir/lang $dir/exp/tri2b $dir/exp/tri2b_ali_train_clean_5

На последнем этапе мы обучим модель с адаптацией к диктору (speaker adaptation technique — SAT):

steps/train_sat.sh --cmd "$train_cmd" 2500 15000 \
    $dir/train_clean_5 $dir/lang $dir/exp/tri2b_ali_train_clean_5 $dir/exp/tri3b

Все, акустическая модель готова хранится в файле $dir/exp/tri3b/final.mdl. Теперь перейдем к языковой модели.

Языковая модель

При помощи SRILM можно сгенерировать языковую модель на основе корпуса текстов. В нашем случае мы ограничимся простой моделью, состоящей из нескольких терминов:

  1. <s>, </s> — технические. Они обозначают начало и конец предложения;
  2. <UNK> — термин для замены всех слов, кроме искомого;
  3. NOTHING — то самое слово, которое мы будем детектировать.

После того как мы определились с терминами, необходимо задать логарифмические вероятности каждого из них. Для <s>, </s> возьмем −99, что соответствует практически нулевой вероятности их появления. Для <UNK> возьмем −0.0004, а для NOTHING поставим −8, поскольку NOTHING должно встречаться намного реже по сравнению с остальными словами. Меняя эти значения, мы можем регулировать количество ложных срабатываний или пропусков.

Теперь мы готовы генерировать языковую модель:

cat <<EOL |gzip -c >$dir/lang/lm.gz
\data\\
ngram 1=4

\1-grams:
-0.0004 <UNK>
-8 NOTHING
-99 <s>
-99 </s>

\end\\
EOL

Готовую модель сразу преобразуем для работы с Kaldi:

utils/format_lm.sh $dir/lang $dir/lang/lm.gz \
$dir/local/dict/lexicon.txt $dir/lang

Теперь нам необходимо сгенерировать граф для декодирования. Его задача объединить распределение вероятностей фонем для каждого аудиофрейма с языковой моделью:

utils/mkgraph.sh $dir/lang $dir/exp/tri3b \
    $dir/exp/tri3b/graph

Граф для декодирования находится в папке $dir/exp/tri3b/graph и называется HCLG.fst.

У нас уже есть акустическая модель и граф. Мы можем начать тестирование нашей системы обнаружения слова «NOTHING». Для этого декодируем наш dev датасет:

steps/decode_fmllr.sh --nj 10 --cmd "$train_cmd" \
                          $dir/exp/tri3b/graph \
                          $dir/dev_clean_2 \
                          $dir/exp/tri3b/decode_dev_clean_2

В результате декодирования мы получаем т.н. латтисы. Они содержат множество альтернативных последовательностей слов для аудио. Чтоб получить результаты в читаемом формате, сгенерируем CTM файл (time-marked conversation):

steps/get_ctm.sh $dir/dev_clean_2 \
$dir/exp/tri3b/graph \
$dir/exp/tri3b/decode_dev_clean_2

И посмотрим на него:

head $dir/exp/tri3b/decode_dev_clean_2/score_8/dev_clean_2.ctm

1272-135031-0000 1 0.420 0.970 <UNK> 
1272-135031-0000 1 1.390 0.600 <UNK> 
1272-135031-0000 1 2.050 0.280 <UNK> 
1272-135031-0000 1 2.330 0.390 <UNK> 
1272-135031-0000 1 3.360 0.950 <UNK> 
1272-135031-0000 1 4.380 1.980 <UNK> 
1272-135031-0000 1 6.430 0.400 <UNK> 
1272-135031-0000 1 7.360 0.180 <UNK> 
1272-135031-0000 1 7.540 1.630 <UNK> 
1272-135031-0000 1 9.290 0.590 <UNK> 

Здесь мы видим id аудиофрагмента, номер канала, относительное время начала, длительность слова и само слово. Давайте посмотрим сколько раз было обнаружено наше ключевое слово «NOTHING»:

grep 'NOTHING'  $dir/exp/tri3b/decode_dev_clean_2/score_8/dev_clean_2.ctm
1462-170142-0017 1 0.600 0.370 NOTHING 
1462-170142-0019 1 3.600 0.420 NOTHING 
1462-170142-0031 1 2.460 0.420 NOTHING 
1462-170142-0037 1 3.300 0.530 NOTHING 
1462-170145-0007 1 5.500 0.580 NOTHING 
1988-147956-0026 1 2.410 0.420 NOTHING 
2035-147961-0031 1 2.470 0.580 NOTHING 
2035-152373-0018 1 0.480 0.350 NOTHING 
2428-83699-0005 1 0.450 0.270 NOTHING 
2428-83699-0042 1 2.510 0.410 NOTHING 
251-136532-0016 1 8.060 0.770 NOTHING 
3576-138058-0026 1 3.510 0.520 NOTHING 
5338-284437-0024 1 0.920 0.390 NOTHING 
5895-34622-0002 1 1.920 0.540 NOTHING 
6319-57405-0002 1 4.430 0.470 NOTHING 
777-126732-0006 1 1.680 0.350 NOTHING 
84-121550-0001 1 6.670 0.440 NOTHING 
84-121550-0034 1 7.100 0.490 NOTHING 

Теперь оценим качество результатов обнаружения. Для этого нам применим aligning к test датасету. В результате получим время для каждого слова в тексте:

steps/align_fmllr.sh --nj 10 --cmd "$train_cmd" \
$dir/dev_clean_2 $dir/lang $dir/exp/tri3b $dir/exp/tri3b_ali_dev_clean_2

Генерируем CTM файл

steps/get_train_ctm.sh $dir/dev_clean_2 $dir/lang $dir/exp/tri3b_ali_dev_clean_2

И ищем слово «NOTHING»

grep 'NOTHING' $dir/exp/tri3b_ali_dev_clean_2/ctm 
1272-135031-0010 1 1.500 0.340 NOTHING 
1462-170142-0017 1 0.590 0.370 NOTHING 
1462-170142-0031 1 2.500 0.370 NOTHING 
1462-170145-0007 1 5.530 0.540 NOTHING 
1988-147956-0026 1 2.390 0.420 NOTHING 
2035-147961-0031 1 2.450 0.590 NOTHING 
2035-152373-0018 1 0.490 0.390 NOTHING 
2412-153948-0000 1 2.630 0.340 NOTHING 
2412-153948-0006 1 7.660 0.300 NOTHING 
2428-83699-0005 1 0.450 0.300 NOTHING 
2428-83699-0005 1 3.960 0.230 NOTHING 
2428-83699-0042 1 2.500 0.420 NOTHING 
3536-23268-0014 1 1.740 0.360 NOTHING 
5338-284437-0016 1 0.660 0.340 NOTHING 
5338-284437-0024 1 0.910 0.400 NOTHING 
5895-34622-0002 1 1.930 0.530 NOTHING 
6241-61946-0007 1 5.030 0.300 NOTHING 
6295-244435-0040 1 0.880 0.380 NOTHING 
6319-57405-0002 1 4.350 0.540 NOTHING 
777-126732-0040 1 4.010 0.320 NOTHING 
777-126732-0079 1 7.470 0.340 NOTHING 
7976-110523-0020 1 1.450 0.320 NOTHING 
84-121550-0001 1 6.670 0.430 NOTHING 
84-121550-0034 1 7.090 0.490 NOTHING

Выводы

Сравнив два CTM файла, получаем следующие результаты детектирования нашего ключевого слова:

  • правильное обнаружение (TP) — 13,
  • пропуск (FN) — 11,
  • ложное обнаружение (FP) — 5.

В результате у нас много пропусков и ложных обнаружений. Все потому что мы использовали акустическую модель на основе HMM и обучали ее на очень маленьком датасете (5 часов аудио). Для сравнения одна из акустических моделей на нейронных сетях, которая обучалась нами в Verbit на нескольких тысячах часов аудио, показала стопроцентное обнаружение ключевого слова на этом датасете. Узнать больше вы сможете 19-го октября на конференции Data Science UA.

LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Подписаться на комментарии