Осваиваем Kaldi на примере wake-up-words детектора
Усі статті, обговорення, новини про AI — в одному місці. Підписуйтеся на DOU | AI!
Всем привет! Сегодня я расскажу, как можно использовать фреймворк 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 можно сгенерировать языковую модель на основе корпуса текстов. В нашем случае мы ограничимся простой моделью, состоящей из нескольких терминов:
- <s>, </s> — технические. Они обозначают начало и конец предложения;
- <UNK> — термин для замены всех слов, кроме искомого;
- 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 на нескольких тысячах часов аудио, показала стопроцентное обнаружение ключевого слова на этом датасете. Узнать больше вы сможете
Немає коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів