小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

如何基于新一代 Kaldi 框架快速搭建服務端 ASR 系統(tǒng)

 520jefferson 2022-07-07 發(fā)布于北京
來源丨新一代Kaldi

本文將介紹如何基于新一代 Kaldi 框架快速搭建一個服務端的 ASR 系統(tǒng),包括數(shù)據準備、模型訓練測試、服務端部署運行。

更多內容建議參考:

  • k2[1]
  • icefall[2]
  • lhotse[3]
  • sherpa[4]

前言

距離新一代 Kaldi 開源框架的正式發(fā)布已經有一段時間了。截至目前,框架基本的四梁八柱都已經立起來了。那么,如何用它快速搭建一個 ASR 系統(tǒng)呢?

閱讀過前面幾期公眾文的讀者可能都知道新一代 Kaldi 框架主要包含了四個不同的子項目:k2、icefall、lhotse、sherpa。其中,k2 是核心算法庫;icefall 是數(shù)據集訓練測試示例腳本;lhotse 是語音數(shù)據處理工具集;sherpa 是服務端框架,四個子項目共同構成了新一代 Kaldi 框架。

另一方面,截至目前,新一代 Kaldi 框架在很多公開數(shù)據集上都獲得了很有競爭力的識別結果,在 WenetSpeech 和 GigaSpeech 上甚至都獲得了 SOTA 的性能。

看到這,相信很多小伙伴都已經摩拳擦掌、躍躍欲試了。那么本文的目標就是試圖貫通新一代 Kaldi 的四個子項目,為快速搭建一個服務端的 ASR 系統(tǒng)提供一個簡易的教程。希望看完本文的小伙伴都能順利搭建出自己的 ASR 系統(tǒng)。

三步搭建 ASR 服務端系統(tǒng)

本文主要介紹如何從原始數(shù)據下載處理、模型訓練測試、到得到一個服務端 ASR 系統(tǒng)的過程,根據功能,分為三步:

  • 數(shù)據準備和處理
  • 模型訓練和測試
  • 服務端部署演示

本文介紹的 ASR 系統(tǒng)是基于 RNN-T 框架且不涉及外加的語言模型。所以,本文將不涉及 WFST 等語言模型的內容,如后期有需要,會在后面的文章中另行講述。

為了更加形象、具體地描述這個過程,本文以構建一個基于 WenetSpeech 數(shù)據集訓練的 pruned transducer stateless2[5] recipe 為例,希望盡可能為讀者詳細地描述這一過程,也希望讀者在本文的基礎上能夠無障礙地遷移到其他數(shù)據集的處理、訓練和部署使用上去。

本文描述的過程和展示的代碼更多的是為了描述功能,而非詳細的實現(xiàn)過程。詳細的實現(xiàn)代碼請讀者自行參考 egs/wenetspeech/ASR[6]

Note: 使用者應該事先安裝好 k2、icefalllhotse、sherpa

第一步:數(shù)據準備和處理

對于數(shù)據準備和處理部分,所有的運行指令都集成在文件 prepare.sh[7] 中,主要的作用可以總結為兩個:準備音頻文件并進行特征提取、構建語言建模文件。

準備音頻文件并進行特征提取

(注:在這里我們也用了 musan 數(shù)據集對訓練數(shù)據進行增廣,具體的可以參考 prepare.sh[8] 中對 musan 處理和使用的相關指令,這里不針對介紹。)

下載并解壓數(shù)據

為了統(tǒng)一文件名,這里將數(shù)據包文件名變?yōu)?WenetSpeech, 其中 audio 包含了所有訓練和測試的音頻數(shù)據

>> tree download/WenetSpeech -L 1
download/WenetSpeech
├── audio
├── TERMS_OF_ACCESS
└── WenetSpeech.json

>> tree download/WenetSpeech/audio -L 1
download/WenetSpeech/audio
├── dev
├── test_meeting
├── test_net
└── train

WenetSpeech.json 中包含了音頻文件路徑和相關的監(jiān)督信息,我們可以查看 WenetSpeech.json 文件,部分信息如下所示:

    'audios': [
        {
            'aid''Y0000000000_--5llN02F84',
            'duration'2494.57,
            'md5''48af998ec7dab6964386c3522386fa4b',
            'path''audio/train/youtube/B00000/Y0000000000_--5llN02F84.opus',
            'source''youtube',
            'tags': [
                'drama'
            ],
            'url''https://www./watch?v=--5llN02F84',
            'segments': [
                {
                    'sid''Y0000000000_--5llN02F84_S00000',
                    'confidence'1.0,
                    'begin_time'20.08,
                    'end_time'24.4,
                    'subsets': [
                        'L'
                    ],
                    'text''怎么樣這些日子住得還習慣吧'
                },
                {
                    'sid''Y0000000000_--5llN02F84_S00002',
                    'confidence'1.0,
                    'begin_time'25.0,
                    'end_time'26.28,
                    'subsets': [
                        'L'
                    ],
                    'text''挺好的'

(注:WenetSpeech 中文數(shù)據集中包含了 S,M,L 三個不同規(guī)模的訓練數(shù)據集)

利用 lhotse 生成 manifests

關于 lhotse 是如何將原始數(shù)據處理成 jsonl.gz 格式文件的,這里可以參考文件wenet_speech.py[9], 其主要功能是生成 recordingssupervisionsjsonl.gz 格式文件

>> lhotse prepare wenet-speech download/WenetSpeech data/manifests -j 15
>> tree data/manifests -L 1
├── wenetspeech_recordings_DEV.jsonl.gz
├── wenetspeech_recordings_L.jsonl.gz
├── wenetspeech_recordings_M.jsonl.gz
├── wenetspeech_recordings_S.jsonl.gz
├── wenetspeech_recordings_TEST_MEETING.jsonl.gz
├── wenetspeech_recordings_TEST_NET.jsonl.gz
├── wenetspeech_supervisions_DEV.jsonl.gz
├── wenetspeech_supervisions_L.jsonl.gz
├── wenetspeech_supervisions_M.jsonl.gz
├── wenetspeech_supervisions_S.jsonl.gz
├── wenetspeech_supervisions_TEST_MEETING.jsonl.gz
└── wenetspeech_supervisions_TEST_NET.jsonl.gz

這里,可用 vimrecordingssupervisionsjsonl.gz 文件進行查看, 其中:

wenetspeech_recordings_S.jsonl.gz:

Image

wenetspeech_supervisions_S.jsonl.gz:

Image

由上面兩幅圖可知,recordings 用于描述音頻文件信息,包含了音頻樣本的 id、具體路徑、通道、采樣率、子樣本數(shù)和時長等。supervisions 用于記錄監(jiān)督信息,包含了音頻樣本對應的 id、起始時間、時長、通道、文本和語言類型等。

接下來,我們將對音頻數(shù)據提取特征。

計算、提取和貯存音頻特征

首先,對數(shù)據進行預處理,包括對文本進行標準化和對音頻進行時域上的增廣,可參考文件 preprocess_wenetspeech.py[10]。

python3 ./local/preprocess_wenetspeech.py

其次,將數(shù)據集切片并對每個切片數(shù)據集進行特征提取。可參考文件  compute_fbank_wenetspeech_splits.py[11]。

(注:這里的切片是為了可以開啟多個進程同時對大規(guī)模數(shù)據集進行特征提取,提高效率。如果數(shù)據集比較小,對數(shù)據進行切片處理不是必須的。)

# 這里的 L 也可修改為 M 或 S, 表示訓練數(shù)據子集

lhotse split 1000 ./data/fbank/cuts_L_raw.jsonl.gz data/fbank/L_split_1000

python3 ./local/compute_fbank_wenetspeech_splits.py \
    --training-subset L \
    --num-workers 20 \
    --batch-duration 600 \
    --start 0 \
    --num-splits 1000

最后,待提取完每個切片數(shù)據集的特征后,將所有切片數(shù)據集的特征數(shù)據合并成一個總的特征數(shù)據集:

# 這里的 L 也可修改為 M 或 S, 表示訓練數(shù)據子集

pieces=$(find data/fbank/L_split_1000 -name 'cuts_L.*.jsonl.gz')
lhotse combine $pieces data/fbank/cuts_L.jsonl.gz

至此,我們基本完成了音頻文件的準備和特征提取。接下來,我們將構建語言建模文件。

構建語言建模文件

RNN-T 模型框架中,我們實際需要的用于訓練和測試的建模文件有 tokens.txt、words.txtLinv.pt 。我們按照如下步驟構建語言建模文件:

規(guī)范化文本并生成 text

在這一步驟中,規(guī)范文本的函數(shù)文件可參考 text2token.py[12]。

# Note: in Linux, you can install jq with the following command:
# 1. wget -O jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64
# 2. chmod +x ./jq
# 3. cp jq /usr/bin

gunzip -c data/manifests/wenetspeech_supervisions_L.jsonl.gz \
      | jq 'text' | sed 's/'//g' \
      | ./local/text2token.py -t 'char' > data/lang_char/text

text 的形式如下:

 怎么樣這些日子住得還習慣吧
 挺好的
 對了美靜這段日子經常不和我們一起用餐
 是不是對我回來有什么想法啊
 哪有的事啊
 她這兩天挺累的身體也不太舒服
 我讓她多睡一會那就好如果要是覺得不方便
 我就搬出去住
 ............
分詞并生成 words.txt

這里我們用 jieba 對中文句子進行分詞,可參考文件 text2segments.py[13] 。

python3 ./local/text2segments.py \
    --input-file data/lang_char/text \
    --output-file data/lang_char/text_words_segmentation

cat data/lang_char/text_words_segmentation | sed 's/ /\n/g' \
    | sort -u | sed '/^$/d' | uniq > data/lang_char/words_no_ids.txt

python3 ./local/prepare_words.py \
    --input-file data/lang_char/words_no_ids.txt \
    --output-file data/lang_char/words.txt

text_words_segmentation 的形式如下:

  怎么樣 這些 日子 住 得 還 習慣 吧
  挺 好 的
  對 了 美靜 這段 日子 經常 不 和 我們 一起 用餐
  是不是 對 我 回來 有 什么 想法 啊
  哪有 的 事 啊
  她 這 兩天 挺累 的 身體 也 不 太 舒服
  我 讓 她 多 睡 一會 那就好 如果 要是 覺得 不 方便
  我 就 搬出去 住
  ............

words_no_ids.txt 的形式如下:

............

阿Q
阿阿虎
阿阿離
阿阿瑪
阿阿毛
阿阿強
阿阿淑
阿安
............

words.txt 的形式如下:

............
阿 225
阿Q 226
阿阿虎 227
阿阿離 228
阿阿瑪 229
阿阿毛 230
阿阿強 231
阿阿淑 232
阿安 233
............
生成 tokens.txt 和 lexicon.txt

這里生成 tokens.txt 和 lexicon.txt 的函數(shù)文件可參考 prepare_char.py[14] 。

python3 ./local/prepare_char.py \
    --lang-dir data/lang_char

tokens.txt 的形式如下:

<blk> 0
<sos/eos> 1
<unk> 2
怎 3
么 4
樣 5
這 6
些 7
日 8
子 9
............

lexicon.txt 的形式如下:

............
X光 X 光
X光線 X 光 線
X射線 X 射 線
Y Y
YC Y C
YS Y S
YY Y Y
Z Z
ZO Z O
ZSU Z S U
○ ○
一 一
一一 一 一
一一二 一 一 二
一一例 一 一 例
............

至此,第一步全部完成。對于不同數(shù)據集來說,其基本思路也是類似的。在數(shù)據準備和處理階段,我們主要做兩件事情:準備音頻文件并進行特征提取構建語言建模文件。

這里我們使用的范例是中文漢語,建模單元是字。在英文數(shù)據中,我們一般用 BPE 作為建模單元,具體的可參考 egs/librispeech/ASR/prepare.sh[15] 。

第二步:模型訓練和測試

在完成第一步的基礎上,我們可以進入到第二步,即模型的訓練和測試了。這里,我們根據操作流程和功能,將第二步劃分為更加具體的幾步:文件準備、數(shù)據加載、模型訓練、解碼測試。

文件準備

首先,創(chuàng)建 pruned_transducer_stateless2 的文件夾。

mkdir pruned_transducer_stateless2
cd pruned_transducer_stateless2

其次,我們需要準備數(shù)據讀取、模型、訓練、測試、模型導出等腳本文件。在這里,我們在 egs/librispeech/ASR/pruned_transducer_stateless2[16] 的基礎上創(chuàng)建我們需要的文件。

對于公共的腳本文件(即不需要修改的文件),我們可以用軟鏈接直接復制過來,如:

ln -s ../../../librispeech/ASR/pruned_transducer_stateless2/conformer.py .

其他相同文件的操作類似。另外,讀者也可以使用自己的模型,替換本框架內提供的模型文件即可。

對于不同的腳本文件(即因為數(shù)據集或者語言不同而需要修改的文件),我們先從 egs/librispeech/ASR/pruned_transducer_stateless2 中復制過來,然后再進行小范圍的修改,如:

cp -r ../../../librispeech/ASR/pruned_transducer_stateless2/train.py .

在本示例中,我們需要對 train.py 中的數(shù)據讀取、graph_compiler(圖編譯器)及 vocab_size 的獲取等部分進行修改,如(截取部分代碼,便于讀者直觀認識):

數(shù)據讀?。?/p>

    ............
    from asr_datamodule import WenetSpeechAsrDataModule
    ............
    wenetspeech = WenetSpeechAsrDataModule(args)

    train_cuts = wenetspeech.train_cuts()
    valid_cuts = wenetspeech.valid_cuts()
    ............

graph_compiler:

    ............
    y = graph_compiler.texts_to_ids(texts)
    if type(y) == list:
        y = k2.RaggedTensor(y).to(device)
    else:
        y = y.to(device)
    ............
    lexicon = Lexicon(params.lang_dir)
    graph_compiler = CharCtcTrainingGraphCompiler(
        lexicon=lexicon,
        device=device,
    )
    ............

vocab_size 的獲取:

    ............
    params.blank_id = lexicon.token_table['<blk>']
    params.vocab_size = max(lexicon.tokens) + 1
    ............

更加詳細的修改后的 train.py 可參考 egs/wenetspeech/ASR/pruned_transducer_stateless2/train.py[17] 。其他 decode.py、pretrained.py、export.py 等需要修改的文件也可以參照上述進行類似的修改和調整。

(注:在準備文件時,應該遵循相同的文件不重復造輪子、不同的文件盡量小改、缺少的文件自己造的原則。icefall 中大多數(shù)函數(shù)和功能文件在很多數(shù)據集上都進行了測試和驗證,都是可以直接遷移使用的。)

數(shù)據加載

實際上,對于數(shù)據加載這一步,也可以視為文件準備的一部分,即修改文件 asr_datamodule.py[18],但是考慮到不同數(shù)據集的 asr_datamodule.py 都不一樣,所以這里單獨拿出來講述。

首先,這里以 egs/librispeech/ASR/pruned_transducer_stateless2/asr_datamodule.py[19] 為基礎,在這個上面進行修改:

cp -r ../../../librispeech/ASR/pruned_transducer_stateless2/asr_datamodule.py .

其次,修改函數(shù)類的名稱,如這里將 LibriSpeechAsrDataModule 修改為 WenetSpeechAsrDataModule ,并讀取第一步中生成的 jsonl.gz 格式的訓練測試文件。本示例中,第一步生成了 data/fbank/cuts_L.jsonl.gz,我們用 load_manifest_lazy 讀取它:

    ............
        group.add_argument(
            '--training-subset',
            type=str,
            default='L',
            help='The training subset for using',
        )
    ............
    @lru_cache()
    def train_cuts(self) -> CutSet:
        logging.info('About to get train cuts')
        cuts_train = load_manifest_lazy(
            self.args.manifest_dir
            / f'cuts_{self.args.training_subset}.jsonl.gz'
        )
        return cuts_train
    ............

其他的訓練測試集的 jsonl.gz 文件讀取和上述類似。另外,對于 train_dataloaders、valid_dataloaderstest_dataloaders 等幾個函數(shù)基本是不需要修改的,如有需要,調整其中的具體參數(shù)即可。

最后,調整修改后的 asr_datamodule.pytrain.py 聯(lián)合調試,把 WenetSpeechAsrDataModule 導入到 train.py,運行它,如果在數(shù)據讀取和加載過程中不報錯,那么數(shù)據加載部分就完成了。

另外,在數(shù)據加載的過程中,我們也有必要對數(shù)據樣本的時長進行統(tǒng)計,并過濾一些過短、過長且占比極小的樣本,這樣可以使我們的訓練過程更加穩(wěn)定。

在本示例中,我們對 WenetSpeech 的樣本進行了時長統(tǒng)計(L 數(shù)據集太大,這里沒有對它進行統(tǒng)計),具體的可參考 display_manifest_statistics.py[20],統(tǒng)計的部分結果如下:

............
Starting display the statistics for ./data/fbank/cuts_M.jsonl.gz
Cuts count: 4543341
Total duration (hours): 3021.1
Speech duration (hours): 3021.1 (100.0%)
***
Duration statistics (seconds):
mean    2.4
std     1.6
min     0.2
25%     1.4
50%     2.0
75%     2.9
99%     8.0
99.5%   8.8
99.9%   12.1
max     405.1
............
Starting display the statistics for ./data/fbank/cuts_TEST_NET.jsonl.gz
Cuts count: 24774
Total duration (hours): 23.1
Speech duration (hours): 23.1 (100.0%)
***
Duration statistics (seconds):
mean    3.4
std     2.6
min     0.1
25%     1.4
50%     2.4
75%     4.8
99%     13.1
99.5%   14.5
99.9%   18.5
max     33.3

根據上面的統(tǒng)計結果,我們在 train.py 中設置了樣本的最大時長為 15.0 seconds:

    ............
    def remove_short_and_long_utt(c: Cut):
        # Keep only utterances with duration between 1 second and 15.0 seconds
        #
        # Caution: There is a reason to select 15.0 here. Please see
        # ../local/display_manifest_statistics.py
        #
        # You should use ../local/display_manifest_statistics.py to get
        # an utterance duration distribution for your dataset to select
        # the threshold
        return 1.0 <= c.duration <= 15.0

    train_cuts = train_cuts.filter(remove_short_and_long_utt)
    ............

模型訓練

在完成相關必要文件準備和數(shù)據加載成功的基礎上,我們可以開始進行模型的訓練了。

在訓練之前,我們需要根據訓練數(shù)據的規(guī)模和我們的算力條件(比如 GPU 顯卡的型號、GPU 顯卡的數(shù)量、每個卡的顯存大小等)去調整相關的參數(shù)。

這里,我們將主要介紹幾個比較關鍵的參數(shù),其中,world-size 表示并行計算的 GPU 數(shù)量,max-duration 表示每個 batch 中所有音頻樣本的最大時長之和,num-epochs 表示訓練的 epochs 數(shù),valid-interval 表示在驗證集上計算 loss 的 iterations 間隔,model-warm-step 表示模型熱啟動的 iterations 數(shù),use-fp16 表示是否用16位的浮點數(shù)進行訓練等,其他參數(shù)可以參考 train.py[21] 具體的參數(shù)解釋和說明。

在這個示例中,我們用 WenetSpeech 中 L subset 訓練集來進行訓練,并綜合考慮該數(shù)據集的規(guī)模和我們的算力條件,訓練參數(shù)設置和運行指令如下(沒出現(xiàn)的參數(shù)表示使用默認的參數(shù)值):

export CUDA_VISIBLE_DEVICES='0,1,2,3,4,5,6,7'

python3 pruned_transducer_stateless2/train.py \
  --lang-dir data/lang_char \
  --exp-dir pruned_transducer_stateless2/exp \
  --world-size 8 \
  --num-epochs 15 \
  --start-epoch 0 \
  --max-duration 180 \
  --valid-interval 3000 \
  --model-warm-step 3000 \
  --save-every-n 8000 \
  --training-subset L

到這里,如果能看到訓練過程中的 loss 記錄的輸出,則說明訓練已經成功開始了。

另外,如果在訓練過程中,出現(xiàn)了 Out of Memory 的報錯信息導致訓練中止,可以嘗試使用更小一些的 max-duration 值。如果還有其他的報錯導致訓練中止,一方面希望讀者可以靈活地根據實際情況修改或調整某些參數(shù),另一方面,讀者可以在相關討論群或者在icefall 上通過 issuespull request 等形式進行反饋。

如果程序在中途中止訓練,我們也不必從頭開始訓練,可以通過加載保存的某個 epoch-X.ptcheckpoint-X.pt 模型文件(包含了模型參數(shù)、采樣器和學習率等參數(shù))繼續(xù)訓練,如加載 epoch-3.pt 的模型文件繼續(xù)訓練:

export CUDA_VISIBLE_DEVICES='0,1,2,3,4,5,6,7'

python3 pruned_transducer_stateless2/train.py \
  --lang-dir data/lang_char \
  --exp-dir pruned_transducer_stateless2/exp \
  --world-size 8 \
  --num-epochs 15 \
  --start-batch 3 \
  --max-duration 180 \
  --valid-interval 3000 \
  --model-warm-step 3000 \
  --save-every-n 8000 \
  --training-subset L

這樣即使程序中斷了,我們也不用從零開始訓練模型。

另外,我們也不用從第一個 batch 進行迭代訓練,因為采樣器中保存了迭代的 batch 數(shù),我們可以設置參數(shù) --start-batch xxx, 使得我們可以從某一個 epoch 的某個 batch 處開始訓練,這大大節(jié)省了訓練時間和計算資源,尤其是在訓練大規(guī)模數(shù)據集時。

在 icefall 中,還有更多類似這樣人性化的訓練設置,等待大家去發(fā)現(xiàn)和使用。

當訓練完畢以后,我們可以得到相關的訓練 log 文件和 tensorboard 損失記錄,可以在終端使用如下指令:

cd pruned_transducer_stateless2/exp

tensorboard dev upload --logdir tensorboard

如在使用上述指令之后,我們可以在終端看到如下信息:

............
To stop uploading, press Ctrl-C.

New experiment created. View your TensorBoard at: https://v/experiment/wM4ZUNtASRavJx79EOYYcg/

[2022-06-30T15:49:38] Started scanning logdir.
Uploading 4542 scalars...
............

將上述顯示的 tensorboard 記錄查看網址復制到本地瀏覽器的網址欄中即可查看。如在本示例中,我們將 https://v/experiment/wM4ZUNtASRavJx79EOYYcg/ 復制到本地瀏覽器的網址欄中,損失函數(shù)的 tensorboard 記錄如下:

Image

(PS: 讀者可從上圖發(fā)現(xiàn),筆者在訓練 WenetSpeech L subset 時,也因為某些原因中斷了訓練,但是,icefall 中人性化的接續(xù)訓練操作讓筆者避免了從零開始訓練,并且前后兩個訓練階段的 losslearning rate 曲線還連接地如此完美。)

解碼測試

當模型訓練完畢,我們就可以進行解碼測試了。

在運行解碼測試的指令之前,我們依然需要對 decode.py 進行如文件準備過程中對 train.py 相似位置的修改和調整,這里將不具體講述,修改后的文件可參考 decode.py[22]。

這里為了在測試過程中更快速地加載數(shù)據,我們將測試數(shù)據導出為 webdataset 要求的形式(注:這一步不是必須的,如果測試過程中速度比較快,這一步可以省略),操作如下:

    ............
    # Note: Please use 'pip install webdataset==0.1.103'
    # for installing the webdataset.
    import glob
    import os

    from lhotse import CutSet
    from lhotse.dataset.webdataset import export_to_webdataset

    wenetspeech = WenetSpeechAsrDataModule(args)

    dev = 'dev'
    ............

    if not os.path.exists(f'{dev}/shared-0.tar'):
        os.makedirs(dev)
        dev_cuts = wenetspeech.valid_cuts()
        export_to_webdataset(
            dev_cuts,
            output_path=f'{dev}/shared-%d.tar',
            shard_size=300,
        )
    ............
    dev_shards = [
        str(path)
        for path in sorted(glob.glob(os.path.join(dev, 'shared-*.tar')))
    ]
    cuts_dev_webdataset = CutSet.from_webdataset(
        dev_shards,
        split_by_worker=True,
        split_by_node=True,
        shuffle_shards=True,
    )
    ............
    dev_dl = wenetspeech.valid_dataloaders(cuts_dev_webdataset)
    ............

同時,在 asr_datamodule.py 中修改 test_dataloader 函數(shù),修改如下(注:這一步不是必須的,如果測試過程中速度比較快,這一步可以省略):

        ............
        from lhotse.dataset.iterable_dataset import IterableDatasetWrapper

        test_iter_dataset = IterableDatasetWrapper(
            dataset=test,
            sampler=sampler,
        )
        test_dl = DataLoader(
            test_iter_dataset,
            batch_size=None,
            num_workers=self.args.num_workers,
        )
        return test_dl

待修改完畢,聯(lián)合調試 decode.py 和 asr_datamodule.py, 解碼過程能正常加載數(shù)據即可。

在進行解碼測試時,icefall 為我們提供了四種解碼方式:greedy_search、beam_search、modified_beam_searchfast_beam_search,更為具體實現(xiàn)方式,可參考文件 beam_search.py[23]

這里,因為建模單元的數(shù)量非常多(5500+),導致解碼速度非常慢,所以,筆者不建議使用 beam_search 的解碼方式。

在本示例中,如果使用 greedy_search 進行解碼,我們的解碼指令如下 ( 關于如何使用其他的解碼方式,讀者可以自行參考 decode.py):

export CUDA_VISIBLE_DEVICES='0'
python pruned_transducer_stateless2/decode.py \
        --epoch 10 \
        --avg 2 \
        --exp-dir ./pruned_transducer_stateless2/exp \
        --lang-dir data/lang_char \
        --max-duration 100 \
        --decoding-method greedy_search

運行上述指令進行解碼,在終端將會展示如下內容(部分):

............
2022-06-30 16:58:17,232 INFO [decode.py:487] About to create model
2022-06-30 16:58:17,759 INFO [decode.py:508] averaging ['pruned_transducer_stateless2/exp/epoch-9.pt''pruned_transducer_stateless2/exp/epoch-10.pt']
............
2022-06-30 16:58:42,260 INFO [decode.py:393] batch 0/?, cuts processed until now is 104
2022-06-30 16:59:41,290 INFO [decode.py:393] batch 100/?, cuts processed until now is 13200
2022-06-30 17:00:35,961 INFO [decode.py:393] batch 200/?, cuts processed until now is 27146
2022-06-30 17:00:38,370 INFO [decode.py:410] The transcripts are stored in pruned_transducer_stateless2/exp/greedy_search/recogs-DEV-greedy_search-epoch-10-avg-2-context-2-max-sym-per-frame-1.txt
2022-06-30 17:00:39,129 INFO [utils.py:410] [DEV-greedy_search] %WER 7.80% [51556 / 660996, 6272 ins, 18888 del, 26396 sub ]
2022-06-30 17:00:41,084 INFO [decode.py:423] Wrote detailed error stats to pruned_transducer_stateless2/exp/greedy_search/errs-DEV-greedy_search-epoch-10-avg-2-context-2-max-sym-per-frame-1.txt
2022-06-30 17:00:41,092 INFO [decode.py:440]
For DEV, WER of different settings are:
greedy_search   7.8     best for DEV
............

這里,讀者可能還有一個疑問,如何選取合適的 epochavg 參數(shù),以保證平均模型的性能最佳呢?這里我們通過遍歷所有的 epoch 和 avg 組合來搜索最好的平均模型,可以使用如下指令得到所有可能的平均模型的性能,然后進行找到最好的解碼結果所對應的平均模型的 epoch 和 avg 即可,如:

export CUDA_VISIBLE_DEVICES='0'
num_epochs=15
for ((i=$num_epochs; i>=0; i--));
do
    for ((j=1; j<=$i; j++));
    do
        python3 pruned_transducer_stateless2/decode.py \
            --exp-dir ./pruned_transducer_stateless2/exp \
            --lang-dir data/lang_char \
            --epoch $i \
            --avg $j \
            --max-duration 100 \
            --decoding-method greedy_search
    done
done

以上方法僅供讀者參考,讀者可根據自己的實際情況進行修改和調整。目前,icefall 也提供了一種新的平均模型參數(shù)的方法,性能更好,這里將不作細述,有興趣可以參考文件 decode.py[24] 中的參數(shù) --use-averaged-model。

至此,解碼測試就完成了。使用者也可以通過查看 egs/pruned_transducer_stateless2/exp/greedy_searchrecogs-*.txt、errs-*.txtwer-*.txt 等文件,看看每個樣本的具體解碼結果和最終解碼性能。

本示例中,筆者的訓練模型和測試結果可以參考 icefall_asr_wenetspeech_pruned_transducer_stateless2[25],讀者可以在 icefall_asr_wenetspeech_pruned_transducer_stateless2_colab_demo[26] 上直接運行和測試提供的模型,這些僅供讀者參考。

第三步:服務端部署演示

在順利完成第一步和第二步之后,我們就可以得到訓練模型和測試結果了。

接下來,筆者將講述如何利用 sherpa 框架把訓練得到的模型部署到服務端,筆者強烈建議讀者參考和閱讀 sherpa使用文檔[27],該框架還在不斷地更新和優(yōu)化中,感興趣的讀者可以保持關注并參與到開發(fā)中來。

本示例中,我們用的 sherpa 版本為 sherpa-for-wenetspeech-pruned-rnnt2[28]。

為了將整個過程描述地更加清晰,筆者同樣將第三步細分為以下幾步:將訓練好的模型編譯為 TorchScript 代碼、服務器終端運行本地 web 端測試使用。

將訓練好的模型編譯為 TorchScript 代碼

這里,我們使用 torch.jit.script 對模型進行編譯,使得 nn.Module 形式的模型在生產環(huán)境下變得可用,具體的代碼實現(xiàn)可參考文件 export.py[29],操作指令如下:

python3 pruned_transducer_stateless2/export.py \
    --exp-dir ./pruned_transducer_stateless2/exp \
    --lang-dir data/lang_char \
    --epoch 10 \
    --avg 2 \
    --jit True

運行上述指令,我們可以在 egs/wenetspeech/ASR/pruned_transducer_stateless2/exp 中得到一個 cpu_jit.pt 的文件,這是我們在 sherpa 框架里將要使用的模型文件。

服務器終端運行

本示例中,我們的模型是中文非流式的,所以我們選擇非流式模式來運行指令,同時,我們需要選擇在上述步驟中生成的 cpu_jit.pttokens.txt

python3 sherpa/bin/conformer_rnnt/offline_server.py \
    --port 6006 \
    --num-device 1 \
    --max-batch-size 10 \
    --max-wait-ms 5 \
    --max-active-connections 500 \
    --feature-extractor-pool-size 5 \
    --nn-pool-size 1 \
    --nn-model-filename ~/icefall/egs/wenetspeech/ASR/pruned_transducer_stateless2/exp/cpu_jit.pt \
    --token-filename ~/icefall/egs/wenetspeech/ASR/data/lang_char/tokens.txt

注:在上述指令的參數(shù)中,port 為6006,這里的端口也不是固定的,讀者可以根據自己的實際情況進行修改,如6007等。但是,修改本端口的同時,必須要在 sherpa/bin/web/js 中對 offline_record.jsstreaming_record.js中的端口進行同步修改,以保證 web 的數(shù)據和 server 的數(shù)據可以互通。

與此同時,我們還需要在服務器終端另開一個窗口開啟 web 網頁端服務,指令如下:

cd sherpa/bin/web
python3 -m http.server 6008

本地 web 端測試使用

在服務器端運行相關功能的調用指令后,為了有更好的 ASR 交互體驗,我們還需要將服務器端的 web 網頁端服務進行本地化,所以使用 ssh 來連接本地端口和服務器上的端口:

ssh -R 6006:localhost:6006 -R 6008:localhost:6008 local_username@local_ip

接下來,我們可以在本地瀏覽器的網址欄輸入:localhost:6008,我們將可以看到如下頁面:Image

我們選擇 Offline-Record,并打開麥克風,即可錄音識別了。筆者的一個識別結果如下圖所示:

Image

到這里,從數(shù)據準備和處理、模型訓練和測試、服務端部署演示等三步就基本完成了。

新一代 Kaldi 語音識別開源框架還在快速地迭代和發(fā)展之中,本文所展示的只是其中極少的一部分內容,筆者在本文中也只是粗淺地概述了它的部分使用流程,更多詳細具體的細節(jié),希望讀者能夠自己去探索和發(fā)現(xiàn)。

總結

在本文中,筆者試圖以 WenetSpeech 的 pruned transducer stateless2 recipe 構建、訓練、部署的全流程為線索,貫通 k2、icefall、lhotse、sherpa四個獨立子項目, 將新一代 Kaldi 框架的數(shù)據準備和處理、模型訓練和測試、服務端部署演示等流程一體化地全景展示出來,形成一個簡易的教程,希望能夠更好地幫助讀者認識和使用新一代 Kaldi 語音識別開源框架,真正做到上手即用。

參考資料

[1]

k2: https://github.com/k2-fsa/k2

[2]

icefall: https://github.com/k2-fsa/icefall

[3]

lhotse: https://github.com/lhotse-speech/lhotse

[4]

sherpa: https://github.com/k2-fsa/sherpa

[5]

pruned transducer stateless2 recipe: https://github.com/k2-fsa/icefall/tree/master/egs/wenetspeech/ASR

[6]

pruned transducer stateless2 recipe: https://github.com/k2-fsa/icefall/tree/master/egs/wenetspeech/ASR

[7]

prepare.sh: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/prepare.sh

[8]

prepare.sh: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/prepare.sh

[9]

wenet_speech.py: https://github.com/lhotse-speech/lhotse/blob/master/lhotse/recipes/wenet_speech.py

[10]

preprocess_wenetspeech.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/local/preprocess_wenetspeech.py

[11]

compute_fbank_wenetspeech_splits.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/local/compute_fbank_wenetspeech_splits.py

[12]

text2token.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/local/text2token.py

[13]

text2segments.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/local/text2segments.py

[14]

prepare_char.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/local/prepare_char.py

[15]

egs/librispeech/ASR/prepare.sh: https://github.com/k2-fsa/icefall/tree/master/egs/librispeech/ASR

[16]

egs/librispeech/ASR/pruned_transducer_stateless2: https://github.com/k2-fsa/icefall/tree/master/egs/librispeech/ASR/pruned_transducer_stateless2

[17]

egs/wenetspeech/ASR/pruned_transducer_stateless2/train.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/pruned_transducer_stateless2/train.py

[18]

asr_datamodule.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/pruned_transducer_stateless2/asr_datamodule.py

[19]

egs/librispeech/ASR/pruned_transducer_stateless2/asr_datamodule.py: https://github.com/k2-fsa/icefall/blob/master/egs/librispeech/ASR/pruned_transducer_stateless2/asr_datamodule.py

[20]

display_manifest_statistics.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/local/display_manifest_statistics.py,

[21]

train.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/pruned_transducer_stateless2/train.py

[22]

decode.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/pruned_transducer_stateless2/decode.py

[23]

beam_search.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/pruned_transducer_stateless2/train.py

[24]

decode.py: https://github.com/k2-fsa/icefall/blob/master/egs/librispeech/ASR/pruned_transducer_stateless5/train.py

[25]

icefall_asr_wenetspeech_pruned_transducer_stateless2: https:///luomingshuang/icefall_asr_wenetspeech_pruned_transducer_stateless2

[26]

icefall_asr_wenetspeech_pruned_transducer_stateless2_colab_demo: https://colab.research.google.com/drive/1EV4e1CHa1GZgEF-bZgizqI9RyFFehIiN?usp=sharing

[27]

sherpa使用文檔: https://k2-fsa./sherpa/

[28]

sherpa-for-wenetspeech-pruned-rnnt2: https://github.com/k2-fsa/sherpa/tree/9da5b0779ad6758bf3150e1267399fafcdef4c67

[29]

export.py: https://github.com/k2-fsa/icefall/blob/master/egs/wenetspeech/ASR/pruned_transducer_stateless2/export.py

Image

Image

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約