
目前的代码位置:
> /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是原来的四倍了!】

[wenet/transformer/asr_model.py]
输入参数,上面的feats, feats_lengths, target, target_lengths。
输出参数:loss, loss_att, loss_ctc三种loss。

[wenet/transformer/encoder.py]
主要forward函数是在BaseEncoder类的forward函数中实现了。
【注意】
class ConformerEncoder中没有自己的forward函数!!!
encoder层逻辑脑图:

主要逻辑包括:
【都是处理输入wav的】
按照frame长度搞个mask;
依据cmvn来“归一化”,x = (x-mean)*istd
调用embed【wave是用两个conv2d来卷;然后加入绝对位置编码】
调用12层ConformerEncoderLayer的forward
最后一次layer normalization
结果返回
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

这个方法的返回是类似:
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

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:
conv,
out,
以及position encoding。


conv里面有两个conv2d的卷积,会在时域和频域上(time/frame, freq)卷起来。
两次卷积之后,形状从(12=batch-size, 1, 57=# frames, 80=# fbanks)变成了(12, 512, 13, 19)
其中:
12=batch.size
512=channel.number
13=frame/time direction dimension
19=freq direction dimension。
然后是变换一下,x变成(12, 13, 512*19) -> self.out -> [12, 13, 512]。这里面的self.out是一个线性层,从9728维度映射到512维度。
这里面还是使用了绝对位置编码,self.pe:

看下脑图:

没有看到相对位置的啊。。。待定了。
【挖坑】
mask的后处理:

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')
-->最后两个不要,然后隔一行删除一个:
[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]
就是最后的结果了。
进入最最核心的一个类了。
[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。
前两个是:【执行顺序是:从上到下!】

后面两个是:

最后还追加了一个norm_final,是第五个Layer Normalization函数。
1 FFN-马卡龙
和原始论文一样:


上面是第一个模块的细节,FFN-马卡龙。里面有两个线性层,一个非线性激活函数,SiLU,以及一个dropout。
silu(x)=x∗σ(x),where σ(x) is the logistic sigmoid.

这个非线性激活函数可以参考:
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的脑图:

这是一个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


计算attention scores所涉及的代码:
一个是wav vector的自注意力,一个是wav和位置之间的注意力:

2.3 attention

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

3 conv模块
实现逻辑和原始论文,(几乎)一毛一样!!!
唯一的不同是:在1D depthwise Conv后面,论文中写的是BatchNorm,但是代码中是LayerNorm!

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

具体为:

4. FFN

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

待续了。
