目前的位置
开始训练!

一个batch已经组装好了,开始进入训练

目前的代码位置:

> /workspace/asr/wenet/wenet/utils/executor.py(43)train()
42                 import ipdb; ipdb.set_trace()
---> 43                 key, feats, target, feats_lengths, target_lengths = batch
44                 feats = feats.to(device)

简单说一下

一个batch的内容:

  • key=['S11F1534_00079.407_00079.992', ..., 'S00M0505_00042.444_00042.951'] 是当前一个batch中12个wav/text对的id;

  • feats.shape=[12, 51, 80],其中12=batch.size, 51=sequence length (# of frames), 80 = 80维度的梅尔谱(fbank)

  • target.shape=[12, 5],其中12=batch.size, 5=target text 序列长度;

  • feats_lengths=tensor([51, 51, 50, 49, 49, 48, 47, 47, 47, 47, 46, 44], dtype=torch.int32),12个输入wav的# of frames,记录这个目标是为了source input wav的padding;

  • target_lengths=tensor([4, 3, 3, 3, 3, 4, 3, 3, 5, 3, 3, 5], dtype=torch.int32),目标文本序列中的unigram的数量。

除了key,其他四个张量都要放入gpu中。

准备进入forward函数!

这里面用到了accum_grad=4这个参数,类似于说,每隔四次,才用叠加之后的梯度,更新参数的值!

【相当于batch-size是原来的四倍了!】


准备进入forward

forward: class ASRModel

[wenet/transformer/asr_model.py]

输入参数,上面的feats, feats_lengths, target, target_lengths。

输出参数:loss, loss_att, loss_ctc三种loss。


ASRModel中的forward函数涉及的几个逻辑分支

encoder:forward

[wenet/transformer/encoder.py]

主要forward函数是在BaseEncoder类的forward函数中实现了。

【注意】

class ConformerEncoder中没有自己的forward函数!!!

encoder层逻辑脑图:

encoder的forward函数的主要逻辑

主要逻辑包括:

【都是处理输入wav的】

  1. 按照frame长度搞个mask;

  2. 依据cmvn来“归一化”,x = (x-mean)*istd

  3. 调用embed【wave是用两个conv2d来卷;然后加入绝对位置编码】

  4. 调用12层ConformerEncoderLayer的forward

  5. 最后一次layer normalization

  6. 结果返回

ipdb的坑

鉴于asr model被torch.jit.scripts给封装了,所以在每个class的forward函数里面如果设置:

import ipdb; ipdb.set_trace()

的话,会报错。。。

即使是分开,在文件的开头import ipdb;然后在forward函数内部加入ipdb.set_trace()的话,也不行。。。

解决方法为:

> /opt/conda/lib/python3.8/site-packages/torch/nn/modules/module.py(1111)_call_impl()
1110             import ipdb; ipdb.set_trace()
-> 1111             return forward_call(*input, **kwargs)
1112         # Do not call functions when jit is used

即在torch的module.py里面,在forward_call之前,加入一样ipdb追踪,这样,到了这个位置之后,直接按下s,就到了对应的class的forward函数了!

构建输入frame长度的mask


一个batch内部,frame的个数不是都一样的,所以需要搞一个mask,标明每个序列的长度

这个方法的返回是类似:

True,  True,  True,  True,  True,  True,  True],
[False, False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False, False, False,
False, False, False, False,  True,  True,  True,  True,  True,  True,
True,  True,  True,  True,  True,  True,  True]], device='cuda:0')
ipdb> lengths
tensor([57, 55, 54, 54, 53, 53, 49, 49, 47, 46, 46, 44], device='cuda:0',
dtype=torch.int32)

最后一个序列是44个frames,从而它对应的mask里面有57-44=13个'True'。

然后返回之后,被~了一下,就反过来了:

[[ True,  True,  True,  True,  True,  True,  True,  True,  True,  True,
True,  True,  True,  True,  True,  True,  True,  True,  True,  True,
True,  True,  True,  True,  True,  True,  True,  True,  True,  True,
True,  True,  True,  True,  True,  True,  True,  True,  True,  True,
True,  True,  True,  True, False, False, False, False, False, False,
False, False, False, False, False, False, False]]], device='cuda:0')

cmvn正则化(归一化)

[wenet/transformer/cmvn.py]

类似于

x = (x-mean)/std

其中mean=均值,std=标准方差:

istd = inverse std = 1/std


对输入mel (80 fbanks)进行归一化处理

src embed

下面开始对输入frame seq的embedding了,使用的self.embed的架构为:

ipdb> self.embed
Conv2dSubsampling4(
(conv): Sequential(
(0): Conv2d(1, 512, kernel_size=(3, 3), stride=(2, 2))
(1): ReLU()
(2): Conv2d(512, 512, kernel_size=(3, 3), stride=(2, 2))
(3): ReLU()
)
(out): Sequential(
(0): Linear(in_features=9728, out_features=512, bias=True)
)
(pos_enc): RelPositionalEncoding(
(dropout): Dropout(p=0.1, inplace=False)
)
)

上面的embed内部包括了三个sub layers:

  1. conv,

  2. out,

  3. 以及position encoding。


forward函数,使用三个部分来构造embedding

embedding涉及到三个部分

conv里面有两个conv2d的卷积,会在时域和频域上(time/frame, freq)卷起来。

两次卷积之后,形状从(12=batch-size, 1, 57=# frames, 80=# fbanks)变成了(12, 512, 13, 19)

其中:

  1. 12=batch.size

  2. 512=channel.number

  3. 13=frame/time direction dimension

  4. 19=freq direction dimension。

然后是变换一下,x变成(12, 13, 512*19) -> self.out -> [12, 13, 512]。这里面的self.out是一个线性层,从9728维度映射到512维度。

这里面还是使用了绝对位置编码,self.pe:


关于RelPositionalEncoding的class的截屏

看下脑图:


positional encoding相关的,里面貌似还是用的绝对位置编码相关的tensor...

没有看到相对位置的啊。。。待定了。

【挖坑】

mask的后处理:


mask也搞了两次stride=2的截取,shape从[12, 1, 57]变成了[12, 1, 13]了。

mask[:, :, :-2:2]举例子:

ipdb> x_mask[:,:,:-2:2][:,:,:-2:2][-1,-1,:]

tensor([ True, True, True, True, True, True, True, True, True, True,

True, False, False], device='cuda:0')

ipdb> x_mask[-1, -1, :]

tensor([ True, True, True, True, True, True, True, True, True, True,

True, True, True, True, True, True, True, True, True, True,

True, True, True, True, True, True, True, True, True, True,

True, True, True, True, True, True, True, True, True, True,

True, True, True, True, False, False, False, False, False, False,

False, False, False, False, False, False, False], device='cuda:0')

tensor([True, True,True, True,True, True,True, True,True, True,
True, True,True, True,True, True,True, True,True, True,
True, True,True, True,True, True,True, True,True, True,
True, True,True, True,True, True,True, True,True, True,
True, True,True, True,False, False,False, False,False, False,
False, False,False, False,False, False, False], device='cuda:0')

-->最后两个不要,然后隔一行删除一个:

[t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, t, f, f, f,

f, f, f, ]

-->--> 最后两个不要,然后隔一个删除一个:

[t, t, t, t, t, t, t, t, t, t, t, f, f]

就是最后的结果了。

ConformerEncoderLayer: forward

进入最最核心的一个类了。

[wenet/transformer/encoder_layer.py]

回顾一下其定义:

ConformerEncoderLayer(

(self_attn): RelPositionMultiHeadedAttention(
(linear_q): Linear(in_features=512, out_features=512, bias=True)
(linear_k): Linear(in_features=512, out_features=512, bias=True)
(linear_v): Linear(in_features=512, out_features=512, bias=True)
(linear_out): Linear(in_features=512, out_features=512, bias=True)
(dropout): Dropout(p=0.1, inplace=False)
(linear_pos): Linear(in_features=512, out_features=512, bias=False)
)


(feed_forward): PositionwiseFeedForward(
(w_1): Linear(in_features=512, out_features=2048, bias=True)
(activation): SiLU()
(dropout): Dropout(p=0.1, inplace=False)
(w_2): Linear(in_features=2048, out_features=512, bias=True)
)


(feed_forward_macaron): PositionwiseFeedForward(
(w_1): Linear(in_features=512, out_features=2048, bias=True)
(activation): SiLU()
(dropout): Dropout(p=0.1, inplace=False)
(w_2): Linear(in_features=2048, out_features=512, bias=True)
)


(conv_module): ConvolutionModule(
(pointwise_conv1): Conv1d(512, 1024, kernel_size=(1,), stride=(1,))
(depthwise_conv): Conv1d(512, 512, kernel_size=(31,), stride=(1,), padding=(15,), groups=512)
(norm): LayerNorm((512,), eps=1e-05, elementwise_affine=True)
(pointwise_conv2): Conv1d(512, 512, kernel_size=(1,), stride=(1,))
(activation): SiLU()
)


(norm_ff): LayerNorm((512,), eps=1e-12, elementwise_affine=True)
(norm_mha): LayerNorm((512,), eps=1e-12, elementwise_affine=True)
(norm_ff_macaron): LayerNorm((512,), eps=1e-12, elementwise_affine=True)
(norm_conv): LayerNorm((512,), eps=1e-12, elementwise_affine=True)
(norm_final): LayerNorm((512,), eps=1e-12, elementwise_affine=True)


(dropout): Dropout(p=0.1, inplace=False)
(concat_linear): Linear(in_features=1024, out_features=512, bias=True)

输入参数


输入参数示例

ipdb的坑

ipdb> q.shape, k.shape, v.shape
The program finished and will be restarted
> /workspace/asr/wenet/examples/csj/s0/wenet/bin/train.py(15)()
14
---> 15 from __future__ import print_function
16

只要是打入q...,ipdb就自动退出重启。。。太尴尬了。

已经不小心中招。。。两次。。。【一共是n次。。。n>5,非常耽误时间。。。ipdb 这坨。。。】


四大模块

一个ConformerEncoderLayer里面包括四个模块,FFN, self-attention, conv, FFN。

前两个是:【执行顺序是:从上到下!】


一个ConformerEncoderLayer中的前两个模块,FFN和self-attn

后面两个是:


一个ConformerEncoderLayer中的后两个模块,conv_module和ffn

最后还追加了一个norm_final,是第五个Layer Normalization函数。

1 FFN-马卡龙

和原始论文一样:


原始论文中的FFN

第一个模块,FFN-马卡龙

上面是第一个模块的细节,FFN-马卡龙。里面有两个线性层,一个非线性激活函数,SiLU,以及一个dropout。

silu(x)=x∗σ(x),where σ(x) is the logistic sigmoid.


SiLU函数的图像

这个非线性激活函数可以参考:

SiLU - PyTorch 1.11.0 documentation

https://link.zhihu.com/?target=https%3A//pytorch.org/docs/stable/generated/torch.nn.SiLU.html

【感觉还是直接放URL,比放卡片容易理解一些。。。】

2 attention

这块,也是基本很好地保留了原始的论文的架构:


原始论文中的注意力模块

看下第二个模块attention的脑图:


self-attention模块的细节脑图

这是一个class RelPositionMultiHeadedAttention的对象。

三个重要的点:

其一,self.forward_qkv,主要是用线性层来封装Q, K, V;

其二,scores = (matrix_ac + matrix_bd)/sqrt(d_k)

其三,self.forward_attention,是类似softmax(QK^T/sqrt(d_k)) V这样的计算。

分别看一下:

2.1 qkv

其一的细节:


这块就是三个线性层变换,这个没啥难度

其二的细节:

2.2 scores


带有位置信息的scores (attention scores)


这里有两个特殊的张量:pos_bias_u, pos_bias_v,其形状都是(8, 64)

计算attention scores所涉及的代码:

一个是wav vector的自注意力,一个是wav和位置之间的注意力:


RelPositionMultiHeadedAttention中的主要逻辑!

2.3 attention


forward_attention涉及的逻辑

下面是特殊的,softmax之后,masked_fill的效果:


执行softmax之后的归一化得分attention scores

3 conv模块

实现逻辑和原始论文,(几乎)一毛一样!!!

唯一的不同是:在1D depthwise Conv后面,论文中写的是BatchNorm,但是代码中是LayerNorm!

原始论文的卷积模块!

这里面使用了三个卷积层:


卷积模块里面三个卷积层

具体为:


卷积模块的细节

4. FFN


第二个FFN的细节,FFN结束之后,外边还有一个norm_final(它不属于FFN)

套回到Conformer原来的论文就是:


conformer encoder的forward流程

待续了。