乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

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

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

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

      更多內(nèi)容建議參考:

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

      前言

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

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

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

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

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

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

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

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

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

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

      Note: 使用者應(yīng)該事先安裝好 k2icefall、lhotse、sherpa。

      第一步:數(shù)據(jù)準(zhǔn)備和處理

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

      準(zhǔn)備音頻文件并進(jìn)行特征提取

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

      下載并解壓數(shù)據(jù)

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

      >> 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 中包含了音頻文件路徑和相關(guān)的監(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''怎么樣這些日子住得還習(xí)慣吧'
                      },
                      {
                          'sid''Y0000000000_--5llN02F84_S00002',
                          'confidence'1.0,
                          'begin_time'25.0,
                          'end_time'26.28,
                          'subsets': [
                              'L'
                          ],
                          'text''挺好的'

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

      利用 lhotse 生成 manifests

      關(guān)于 lhotse 是如何將原始數(shù)據(jù)處理成 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 文件進(jìn)行查看, 其中:

      wenetspeech_recordings_S.jsonl.gz:

      Image

      wenetspeech_supervisions_S.jsonl.gz:

      Image

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

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

      計(jì)算、提取和貯存音頻特征

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

      python3 ./local/preprocess_wenetspeech.py

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

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

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

      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

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

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

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

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

      構(gòu)建語言建模文件

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

      規(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 的形式如下:

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

      這里我們用 jieba 對中文句子進(jìn)行分詞,可參考文件 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 的形式如下:

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

      words_no_ids.txt 的形式如下:

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

      阿Q
      阿阿虎
      阿阿離
      阿阿瑪
      阿阿毛
      阿阿強(qiáng)
      阿阿淑
      阿安
      ............

      words.txt 的形式如下:

      ............
      阿 225
      阿Q 226
      阿阿虎 227
      阿阿離 228
      阿阿瑪 229
      阿阿毛 230
      阿阿強(qiáng) 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ù)據(jù)集來說,其基本思路也是類似的。在數(shù)據(jù)準(zhǔn)備和處理階段,我們主要做兩件事情:準(zhǔn)備音頻文件并進(jìn)行特征提取、構(gòu)建語言建模文件。

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

      第二步:模型訓(xùn)練和測試

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

      文件準(zhǔn)備

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

      mkdir pruned_transducer_stateless2
      cd pruned_transducer_stateless2

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

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

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

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

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

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

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

      數(shù)據(jù)讀?。?/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
          ............

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

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

      數(shù)據(jù)加載

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

      首先,這里以 egs/librispeech/ASR/pruned_transducer_stateless2/asr_datamodule.py[19] 為基礎(chǔ),在這個(gè)上面進(jìn)行修改:

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

      其次,修改函數(shù)類的名稱,如這里將 LibriSpeechAsrDataModule 修改為 WenetSpeechAsrDataModule ,并讀取第一步中生成的 jsonl.gz 格式的訓(xùn)練測試文件。本示例中,第一步生成了 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
          ............

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

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

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

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

      ............
      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

      根據(jù)上面的統(tǒng)計(jì)結(jié)果,我們在 train.py 中設(shè)置了樣本的最大時(shí)長為 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)
          ............

      模型訓(xùn)練

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

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

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

      在這個(gè)示例中,我們用 WenetSpeech 中 L subset 訓(xùn)練集來進(jìn)行訓(xùn)練,并綜合考慮該數(shù)據(jù)集的規(guī)模和我們的算力條件,訓(xùn)練參數(shù)設(shè)置和運(yùn)行指令如下(沒出現(xiàn)的參數(shù)表示使用默認(rèn)的參數(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

      到這里,如果能看到訓(xùn)練過程中的 loss 記錄的輸出,則說明訓(xùn)練已經(jīng)成功開始了。

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

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

      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

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

      另外,我們也不用從第一個(gè) batch 進(jìn)行迭代訓(xùn)練,因?yàn)椴蓸悠髦斜4媪说?batch 數(shù),我們可以設(shè)置參數(shù) --start-batch xxx, 使得我們可以從某一個(gè) epoch 的某個(gè) batch 處開始訓(xùn)練,這大大節(jié)省了訓(xùn)練時(shí)間和計(jì)算資源,尤其是在訓(xùn)練大規(guī)模數(shù)據(jù)集時(shí)。

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

      當(dāng)訓(xùn)練完畢以后,我們可以得到相關(guān)的訓(xù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 記錄查看網(wǎng)址復(fù)制到本地瀏覽器的網(wǎng)址欄中即可查看。如在本示例中,我們將 https://v/experiment/wM4ZUNtASRavJx79EOYYcg/ 復(fù)制到本地瀏覽器的網(wǎng)址欄中,損失函數(shù)的 tensorboard 記錄如下:

      Image

      (PS: 讀者可從上圖發(fā)現(xiàn),筆者在訓(xùn)練 WenetSpeech L subset 時(shí),也因?yàn)槟承┰蛑袛嗔擞?xùn)練,但是,icefall 中人性化的接續(xù)訓(xùn)練操作讓筆者避免了從零開始訓(xùn)練,并且前后兩個(gè)訓(xùn)練階段的 losslearning rate 曲線還連接地如此完美。)

      解碼測試

      當(dāng)模型訓(xùn)練完畢,我們就可以進(jìn)行解碼測試了。

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

      這里為了在測試過程中更快速地加載數(shù)據(jù),我們將測試數(shù)據(jù)導(dǎo)出為 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)
          ............

      同時(shí),在 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)合調(diào)試 decode.py 和 asr_datamodule.py, 解碼過程能正常加載數(shù)據(jù)即可。

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

      這里,因?yàn)榻卧臄?shù)量非常多(5500+),導(dǎo)致解碼速度非常慢,所以,筆者不建議使用 beam_search 的解碼方式。

      在本示例中,如果使用 greedy_search 進(jìn)行解碼,我們的解碼指令如下 ( 關(guān)于如何使用其他的解碼方式,讀者可以自行參考 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

      運(yùn)行上述指令進(jìn)行解碼,在終端將會(huì)展示如下內(nèi)容(部分):

      ............
      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
      ............

      這里,讀者可能還有一個(gè)疑問,如何選取合適的 epochavg 參數(shù),以保證平均模型的性能最佳呢?這里我們通過遍歷所有的 epoch 和 avg 組合來搜索最好的平均模型,可以使用如下指令得到所有可能的平均模型的性能,然后進(jìn)行找到最好的解碼結(jié)果所對應(yīng)的平均模型的 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

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

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

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

      第三步:服務(wù)端部署演示

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

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

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

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

      將訓(xùn)練好的模型編譯為 TorchScript 代碼

      這里,我們使用 torch.jit.script 對模型進(jìn)行編譯,使得 nn.Module 形式的模型在生產(chǎn)環(huán)境下變得可用,具體的代碼實(shí)現(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

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

      服務(wù)器終端運(yùn)行

      本示例中,我們的模型是中文非流式的,所以我們選擇非流式模式來運(yùn)行指令,同時(shí),我們需要選擇在上述步驟中生成的 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,這里的端口也不是固定的,讀者可以根據(jù)自己的實(shí)際情況進(jìn)行修改,如6007等。但是,修改本端口的同時(shí),必須要在 sherpa/bin/web/js 中對 offline_record.jsstreaming_record.js中的端口進(jìn)行同步修改,以保證 web 的數(shù)據(jù)和 server 的數(shù)據(jù)可以互通。

      與此同時(shí),我們還需要在服務(wù)器終端另開一個(gè)窗口開啟 web 網(wǎng)頁端服務(wù),指令如下:

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

      本地 web 端測試使用

      在服務(wù)器端運(yùn)行相關(guān)功能的調(diào)用指令后,為了有更好的 ASR 交互體驗(yàn),我們還需要將服務(wù)器端的 web 網(wǎng)頁端服務(wù)進(jìn)行本地化,所以使用 ssh 來連接本地端口和服務(wù)器上的端口:

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

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

      我們選擇 Offline-Record,并打開麥克風(fēng),即可錄音識別了。筆者的一個(gè)識別結(jié)果如下圖所示:

      Image

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

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

      總結(jié)

      在本文中,筆者試圖以 WenetSpeech 的 pruned transducer stateless2 recipe 構(gòu)建、訓(xùn)練、部署的全流程為線索,貫通 k2、icefall、lhotse、sherpa四個(gè)獨(dú)立子項(xiàng)目, 將新一代 Kaldi 框架的數(shù)據(jù)準(zhǔn)備和處理、模型訓(xùn)練和測試、服務(wù)端部署演示等流程一體化地全景展示出來,形成一個(gè)簡易的教程,希望能夠更好地幫助讀者認(rèn)識和使用新一代 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

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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多