GitHub:https://github.com/Xianchao-Wu/wekws是2022年5月31号fork的,目标还是逐行基于脑图来学习分析代码。

简介

WeKws = wenet + keyword spotting

生产优先和生产就绪的端到端“关键字发现”(关键词识别,唤醒词识别)工具包。

该工具包的目标是...

小尺寸(small footprint)关键字发现 (keyword spotting - KWS),唤醒词 (wake-up word - WuW) 检测是物联网 (IoT) 设备中典型且重要的模块。
它为用户提供了一种通过”免提“(hands-free)体验控制物联网设备的方式。
WuW检测系统通常在IoT设备上本地持久运行,需要:
  1. 低功耗、

  2. 模型参数少、

  3. 计算复杂度低,并且

  4. 以流的方式检测预定义的关键字,即

  5. 需要低延迟。

典型场景

WeKws将支持以下唤醒词的典型应用:
单唤醒词,single wake-up word
多个唤醒词,multiple wake-up words
可定制的唤醒词,customizable wake-up word(s)
个性化唤醒词(personalized wake-up word),即唤醒词检测(wake-up word detection)与声纹(voiceprint)相结合
准备工作

一些安装包:

root@fdb35a512cab:/workspace/asr/wekws# 
more requirements.txt
flake8==3.8.2
pyyaml>=5.1
tensorboard
tensorboardX
matplotlib
onnxruntime


数据下载

用了如下这个:openslr.org/87/
目前的路径:【个人是在nvidia nemo的docker里面搞的,可以用WeKws建议的虚拟环境。这个因人而异了。】


数据下载:
root@fdb35a512cab:/workspace/asr/wekws/examples/hi_xiaowen/s0# vi local/mobvoi_data_download.sh
这个就是用wget下载一下两个文件,然后用tar解压一下。


配置一下路径:



run.sh的部分截屏,核心入口脚本


stage=0数据准备

正好复用上面的截屏。stage=0的情况。

老样子,在这个local/prepare_data.py的前面增加:
python -m ipdb local/prepare_data.py ... 从而开启欢乐的逐行调试旅程~~
【注意:】我这里stage=0, stop_stage=0,为的就是一点点把这个run.sh的主要功能给学习一下。

数据准备阶段


相关的json数据:


例如,
p_train.json
的内容:

p_train.json

脑图

整体逻辑比较简单了:

打开json文件,抽取其中的部分信息,然后构造出来wav.scp文件和text文件


继续看:


逐行写到wav.scp和text
如此,得到的两个文件是:

得到p_train文件夹下面的text和wav.scp这两个文件
这是个双层循环:
train, dev, test,以及p, n,所以会有:
p_train
p_dev
p_test
以及
n_train
n_dev
n_test
这六个文件夹(中间结果)。
后续 p_train, n_train ---> train;
p_dev, n_dev ---> dev;
p_test, n_test ---> test。

得到的文件为:


stage=1 cmvn等

cmvn


stage=1的两个大的逻辑
上面的cmvn的细节,这块就不仔细介绍了,因为之前有关于它的详细介绍:
ASR工业级代码-WeNet代码逐行分析-2-batch装填
ASR工业级代码-WeNet代码逐行分析-1-模型初始化


得到的global_cmvn为:


wav to duration

wav_to_duration.sh这个脚本,调用tools/wav2dur.py,其中使用的是torchaudio来为每个wav计算其长度信息:

上面的是wav_to_duration.sh脚本,下面的是wav2dur.py代码


计算duration之后,得到的结果类似:


make_list.py


就是把几个文件的结果统一放到一个地方:


如此,就得到了三个集合的data.list:


stage=2 train

正式进入training:


为了可以使用ipdb,我这里对原来的run.sh的脚本进行了一些修改:


修改过后的,可以逐行调试的train.py


另外一个修改是:


暂时先不用prefetch!


注意:

gpus="0",我只用了一个gpu;

config=conf/ds_tcn.yaml

走起!


命令行参数 args=


config文件读取

然后是读取configs 配置文件里面的信息:


格式不太好,来个截屏:


tcn模型的配置文件


来个权宜之计:


姑且先拍两个TODO


看一些脑图吧:

main()脑图1

大概的逻辑,读取参数,读取配置文件,构造train dataset,构造valid dataset,然后是初始化模型。


构造datasets,data-loader,以及初始化模型

main()脑图2

构造executor,

构造参数优化器optimizer,

构造learning rate的scheduler,

以及对每个epoch循环,进行train, valid,保存checkpoint的各项操作。


挑选重要的几个,说一下吧。


网络的构造

backbone

ipdb>self.networkModuleList(
  (0): DsCnnBlock(
   (quant): QuantStub()
   (dequant): DeQuantStub()
   (cnn): Sequential(
       (0): Conv1d(256,256, kernel_size=(8,), stride=(1,), groups=256)
       (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
       (2): ReLU()
       (3): Conv1d(256,256, kernel_size=(1,), stride=(1,))
       (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
       (5): ReLU()
       (6): Dropout(p=0.1, inplace=False)
    )
   )
  (1): DsCnnBlock(
    (quant): QuantStub()
    (dequant): DeQuantStub()
    (cnn): Sequential(
      (0): Conv1d(256,256, kernel_size=(8,), stride=(1,), dilation=(2,), groups=256)
      (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, 
    track_running_stats=True)
     (2): ReLU()
     (3): Conv1d(256,256, kernel_size=(1,), stride=(1,))
     (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, 
      track_running_stats=True)
     (5): ReLU()
     (6): Dropout(p=0.1, inplace=False)
   )
  )
  (2): DsCnnBlock(
    (quant): QuantStub()
    (dequant): DeQuantStub()
    (cnn): Sequential(
        (0): Conv1d(256,256, kernel_size=(8,), stride=(1,), dilation=
  (4,), groups=256)
              (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, 
  track_running_stats=True)
    (2): ReLU()
    (3): Conv1d(256,256, kernel_size=(1,), stride=(1,))
    (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
    (5): ReLU()
    (6): Dropout(p=0.1, inplace=False)
    )
  )
  (3): DsCnnBlock(
    (quant): QuantStub()
    (dequant): DeQuantStub()
    (cnn): Sequential(
       (0): Conv1d(256,256, kernel_size=
   (8,), stride=(1,), dilation=(8,), groups=256)
       (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, 
  track_running_stats=True)
   (2): ReLU()
   (3): Conv1d(256,256, kernel_size=(1,), stride=(1,))
   (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, 
   track_running_stats=True)
   (5): ReLU()
   (6): Dropout(p=0.1, inplace=False)
  )
 ))


上面就是tcn, 时序卷积网络的构造,组装之后,就是

kws_model:

KWSModel(
  (global_cmvn): GlobalCMVN()
  (preprocessing): LinearSubsampling1(
    (out): Sequential(
      (0): Linear(in_features=40, out_features=256, bias=True)
      (1): ReLU()
    )
    (quant): QuantStub()
    (dequant): DeQuantStub()
  )
  (backbone): TCN(
    (network): ModuleList(
     (0): DsCnnBlock(
       (quant): QuantStub()
       (dequant): DeQuantStub()
       (cnn): Sequential(
              (0): Conv1d(256,256, kernel_size=(8,), stride=(1,), 
groups=256)
              (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, 
track_running_stats=True)
              (2): ReLU()
              (3): Conv1d(256,256, kernel_size=(1,), stride=(1,))
              (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, 
   track_running_stats=True)
              (5): ReLU()
              (6): Dropout(p=0.1, inplace=False)
          )
         )
      (1): DsCnnBlock(
        (quant): QuantStub()
        (dequant): DeQuantStub()
        (cnn): Sequential(
           (0): Conv1d(256,256, kernel_size=(8,), stride=(1,), dilation=(2,), groups=256)
           (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
           (2): ReLU()
           (3): Conv1d(256,256, kernel_size=(1,), stride=(1,))
           (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
           (5): ReLU()
           (6): Dropout(p=0.1, inplace=False)
           )
          )
       (2): DsCnnBlock(
         (quant): QuantStub()
         (dequant): DeQuantStub()
         (cnn): Sequential(
            (0): Conv1d(256,256, kernel_size=(8,), stride=(1,), dilation=(4,), groups=256)
            (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (2): ReLU()
            (3): Conv1d(256,256, kernel_size=(1,), stride=(1,))
            (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
            (5): ReLU()
            (6): Dropout(p=0.1, inplace=False)
          )
        )
       (3): DsCnnBlock(
        (quant): QuantStub()
        (dequant): DeQuantStub()
        (cnn): Sequential(
          (0): Conv1d(256,256, kernel_size=(8,), stride=(1,), dilation=(8,), groups=256)
          (1): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (2): ReLU()
          (3): Conv1d(256,256, kernel_size=(1,), stride=(1,))
          (4): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
          (5): ReLU()
          (6): Dropout(p=0.1, inplace=False)
          )
         )
        )
       )
      (classifier): LinearClassifier(
        (linear): Linear(in_features=256, out_features=2, bias=True)
        (quant): QuantStub()
        (dequant): DeQuantStub()
       )
        (activation): Sigmoid())


linear subsampling


下面的是linear subsampling之后的self.dequant(x)的前后对比:


模型初始化

基本是老样子:

global_cmvn

linear subsampling 线性子采样

backbone (tcn, 时间卷积)


模型初始化

对于linear subsampling

是把40 -> 256,40是输入的fbanks的维度。


前处理和backbone的细节


TCN细节

四层TCN blocks


TCN-第0个模块

一个i=0的模块里面,有两个卷积,以及两个batch norm,后面追加的是relu和dropout。

TCN-第1个模块


一个i=1的模块里面,有两个卷积,以及两个batch norm,后面追加的是relu和dropout。不同的是dilation = 2,膨胀了!


TCN-第2个模块


一个i=2的模块里面,有两个卷积,以及两个batch norm,后面追加的是relu和dropout。不同的是dilation = 4,继续膨胀了!


TCN-第3个模块


一个i=3的模块里面,有两个卷积,以及两个batch norm,后面追加的是relu和dropout。不同的是dilation = 8,继续膨胀了!


classifier

分类器

这个输出的维度=2=唤醒词的数量。



最终模型构造
传入的几个参数最重要!!!


细节

preprocessing, classifier, activation


preprocessing, classifier, activation的网络细节


backbone细节:


四层DsCnnBlock而已,没啥特别的,感觉有些借鉴了wavenet的“膨胀卷积”的概念。即dilation=1,2,4,8。


先到这里,待续。