1 背景
傑出的科學家和工程師們一直在努力地給機器賦予自然交流的能力,語音識別就是其中的一個重要環節。人類對語音識別技術的研究從上世紀 50 年代開始就未曾停止。在長期的探索中,一次次重大的技術突破逐漸讓語音識別技術進入我們的日常生活。今天的 ASR 技術水平是前所未有的。高效能的語音識別給我們帶來了更多的生活體驗,我們擁有了可以對話的智慧數字助手;它也在逐步改善相關領域的生產力水平。
和很多偉大技術的應用一樣,語音識別技術的背後也是很多模組的組合。對其實現流程的改進往往會從一定程度上節省開發成本,並且加快技術迭代的速度。Pytorch-Kaldi 的出現就是基於這樣的動力。
1.1 語音識別系統的組成
圖 1. 語音識別系統的結構
一個典型的語音識別系統如圖 1 所示。它包含 4 個組成部分:訊號處理和特徵提取、聲學模型、語言模型和解碼搜尋。聲學模型是其中的核心部分,我們可以把它理解為說話聲音和語言發音之間的對映。而訊號處理和特徵提取就是用於構建聲學模型的材料了,它主要依靠數字訊號處理相關的技術。語言模型則可以被看作是語言先驗知識。語音識別的最終結果就是在聲學模型得分和語言模型得分上進行搜尋得到的。具體的內容這裡不做展開。
在語音識別技術的發展史上,深度學習絕對是極具影響力的。可以說,沒有對深度學習的引入,就不會有今天如此先進的語音識別引擎。
1.2 業界的基本現狀
一個成功的語音識別系統真的是很難離開一眾優秀開源框架的支撐,比如:HTK,Julius,CMU-Sphinx,PWTH-ASR,LIA-ASR 以及 Kaldi。後來居上的 Kaldi 獨領風騷,擁有活躍的技術社群,被廣泛的應用在語音識別技術的研究和系統開發中。據筆者瞭解,很多國內語音公司的語音識別系統也有著對 Kaldi 或多或少的依賴。圖 2 是在本文寫作的時,GitHub 上 Kaldi 專案的「盛景」。
圖 2. kaldi-asr
但是,Kaldi 也有不盡如人意的地方,它依賴大量的指令碼語言,而且核心演算法使用 C++編寫的,對聲學模型的更新就不是一件容易的事情了,尤其是在需要改變各種神經網路的結構時。即便是擁有豐富經驗的工程師,在除錯的時候也會經歷巨大的痛苦。當然,儘管如此,Kaldi 還是一項偉大的工作。
有問題存在,便有了改進的需要。Yoshua Bengio 團隊成員 Mirco Ravanelli 等人開發了一個試圖繼承 Kaldi 的效率和 PyTorch 的靈活性的開源框架——PyTorch-Kaldi。相關的論文已經在 ICASSP 2019 上發表了,論文標題如圖 3 所示。
圖 3. PyTorch-Kaldi 論文首頁
1.3 Why pytorch-kaldi?
正如論文提到的一句話,「The PyTorch-Kaldi project aims to bridge the gap between Kaldi and PyTorch」,PyTorch-Kaldi 就是為了彌補 PyTorch 和 Kaldi 之間的鴻溝。在 PyTorch 中實現聲學模型,在 Kaldi 中執行特徵提取、標籤/對齊計算和解碼。這也再次從側面證明了 PyTorch 作為一個深度學習框架所具有的的卓越的靈活性和便利性。事實上,很多人都認為 PyTorch 比 TensorFlow 更加適合做研究工作。本文的第二部分將會重點介紹一下 PyTorch-Kaldi 開源工具。
2 PyTorch-Kaldi 簡介
PyTorch-Kaldi 專案的結構如圖 4 所示。正如前面所提到的,在這個專案中,PyTorch 和 Kaldi 在專案中的分工是比較明確的。主指令碼 run_exp.py(後面稱其為主指令碼)是用 Python 寫的,它負責管理 ASR 系統的所有階段,包括特徵和標籤提取、訓練、驗證、解碼和打分。目前版本(v0.2)的 PyTorch-Kaldi 實現了混合 DNN-HMM 的語音識別器。
圖 4. PyTorch-Kaldi 專案結構
2.1 配置檔案
主指令碼以 INI 格式的配置檔案為輸入,這個配置檔案在專案文件中有著很詳細的描述。配置檔案主要由以下 5 部分內容組成。
[exp]:這部分指定了一些高階資訊,例如用於實驗的資料夾、訓練迭代次數、隨機數種子,它也允許使用者指定實驗是在 CPU/GPU 或者多 GPU 上進行,下面是一個例子:
[exp]
cmd =
run_nn_script = run_nn
out_folder = exp/TIMIT_liGRU_fmllr
seed = 4234
use_cuda = True
multi_gpu = False
save_gpumem = False
n_epochs_tr = 24
[dataset]:這部分指定了特徵和標籤。包括它們的儲存路徑、視窗的特徵以及資料集被分割成塊的數量。下面是一個例子:
[dataset1]
data_name = TIMIT_tr
fea = fea_name=mfcc
fea_lst=quick_test/data/train/feats_mfcc.scp
fea_opts=apply-cmvn --utt2spk=ark:quick_test/data/train/utt2spk ark:quick_test/mfcc/train_cmvn_speaker.ark ark:- ark:- | add-deltas --delta-order=2
ark:- ark:- |
cw_left=0
cw_right=0
fea_name=fbank
fea_lst=quick_test/data/train/feats_fbank.scp
fea_opts=apply-cmvn --utt2spk=ark:quick_test/data/train/utt2spk ark:quick_test/fbank/cmvn_train.ark
ark:- ark:- | add-deltas --delta-order=0 ark:- ark:- |
cw_left=0 cw_right=0
fea_name=fmllr fea_lst=quick_test/data/train/feats_fmllr.scp
fea_opts=apply-cmvn --utt2spk=ark:quick_test/data/train/utt2spk
ark:quick_test/data-fmllr-tri3/train/train_cmvn.ark
ark:- ark:- | add-deltas --delta-order=0
ark:- ark:- |
cw_left=0
cw_right=0
lab = lab_name=lab_cd
lab_folder=quick_test/dnn4_pretrain-dbn_dnn_ali
lab_opts=ali-to-pdf
lab_count_file=auto
lab_data_folder=quick_test/data/train/
lab_graph=quick_test/graph
lab_name=lab_mono
lab_folder=quick_test/dnn4_pretrain-dbn_dnn_ali
lab_opts=ali-to-phones --per-frame=true
lab_count_file=none
lab_data_folder=quick_test/data/train/
lab_graph=quick_test/graph
n_chunks = 5
[architecture] 這部分描述神經網路模型,下面是一個例子:
2.2 語音特徵
[model]:這部分定義了神經網路的結合方式,下面是一個例子:
[model]
model_proto = proto/model.proto
model = out_dnn1=compute(liGRU_layers,fmllr)
out_dnn2=compute(MLP_layers,out_dnn1)
out_dnn3=compute(MLP_layers2,out_dnn1)
loss_mono=cost_nll(out_dnn3,lab_mono)
loss_mono_w=mult_constant(loss_mono,1.0)
loss_cd=cost_nll(out_dnn2,lab_cd)
loss_final=sum(loss_cd,loss_mono_w)
err_final=cost_err(out_dnn2,lab_cd)
[forward]
forward_out = out_dnn2
normalize_posteriors = True
normalize_with_counts_from = lab_cd
save_out_file = False
require_decoding = True
[decoding]:這部分定義瞭解碼引數,下面是一個例子:
[decoding]
decoding_script_folder = kaldi_decoding_scripts/
decoding_script = decode_dnn.sh
decoding_proto = proto/decoding.proto
min_active = 200
max_active = 7000
max_mem = 5000000 0
beam = 13.0
latbeam = 8.0
acwt = 0.2
max_arcs = -1
skip_scoring = false
scoring_script = local/score.sh
scoring_opts = "--min-lmwt 1 --max-lmwt 10"
norm_vars = False
2.2 語音特徵和標籤
語音特徵是使用 Kaldi 中的原生 C++庫進行提取的。PyTorch-Kaldi 的一個特點就是可以管理多個語音特徵流,使用者在實際開發的過程中可以定義使用多個特徵引數組合的模型。
用於訓練聲學模型的主要特徵來自於語音特徵和上下文無關的音素序列,這是透過 Kaldi 的音素決策樹生成的。
2.3 chunk 和 minibatch 的組成
PyTorch-Kaldi 將資料集分成了多個塊,每個塊中的樣本都是從整個語料庫中隨機抽樣得到的。然後再訓練的過程中每次迭代只使用一個小批次的資料,這也是神經網路最佳化的常用方法。
不過,小批次資料的聚集方式是由神經網路的結構決定的,對於普通的前饋模型而言,隨機選擇資料就行。但是對於 RNN 這類網路而言,minibatch 則必須由完整的句子組成。
2.4 DNN 聲學模型、解碼和打分
PyTorch-Kaldi 已經定義好了一些先進的神經網路模型,目前支援標準的 MLP、CNN、RNN、LSTM、GRU、Light GRU 等。實際上這部分就是神經網路模型的訓練和最佳化。
在進行基於 HMM 的解碼之前,聲學模型產生的聲學後驗機率與其先驗機率進行歸一化之後便和語言模型生成的語言機率,常用的語言模型就是 n-gram 模型。然後使用波束搜尋演算法得到語音訊號中的單詞序列。最後使用 NIST SCTK 工具計算字錯率(WER)。
原論文的實驗部分展示了使用 PyTorch-Kaldi 進行的多組語音識別相關的實驗,在一些資料集和任務上面還達到了目前最高的水平。
3 總結
就其整體架構和 Mirco Ravanelli 等人表現出來的「野心」來看,PyTorch-Kaldi 的潛力是比較大的。專案文件中關於下一個版本的描述是這樣寫的:「The architecture of the toolkit will be more modular and flexible. Beyond speech recognition, the new toolkit will be suitable for other applications such as speaker recognition, speech enhancement, speech separation, etc.」。當然,這只是一個工具而已,如果沒有對語音識別技術的深刻理解,肯定是做不出更好東西的。許願:有更多的人力和資源積極地投入到這個領域,幫助讓 PyTorch-Kaldi 變得更好,或者打造出全新的比 PyTorch-Kaldi 更好的工具。
參考資料
[1] M. Ravanelli, T. Parcollet and Y. Bengio, "The Pytorch-kaldi Speech Recognition Toolkit," ICASSP 2019 - 2019 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP), Brighton, United Kingdom, 2019, pp. 6465-6469.doi: 10.1109/ICASSP.2019.8683713
[2] D. Yu and L. Deng, Automatic Speech Recognition – A Deep Learning Approach, Springer, 2015.
[3] Kaldi 文件(kaldi-asr.org/doc/)(http://kaldi-asr.org/doc/%EF%BC%89)
[4] PyTorch-Kaldi Github 倉庫(https://github.com/mravanelli/pytorch-kaldi)(https://github.com/mravanelli/pytorch-kaldi%EF%BC%89)
[5] 王贇. 語音識別技術的前世今生(https://www.zhihu.com/lives/843853238078963712%EF%BC%89)(https://www.zhihu.com/lives/843853238078963712%EF%BC%89%EF%BC%89)