cmvn : 计算mean, variance, count of frame这三个量。上次并没有详细分析计算cmvn的细节,这里把这个坑填了。
启动准备
tools/compute_cmvn_stats.py
python -m ipdb tools/compute_cmvn_stats.py \
--num_workers 0 --train_config $train_config \
--in_scp $wave_data/$train_set/wav.scp_$minsec \
--out_cmvn $wave_data/$train_set/global_cmvn相关的输入的参数为:
Namespace(in_scp='data/train/wav.scp_0.1', log_interval=1000, num_workers=0, 【这是为了方便ipdb调试,普通情况下可以设置为16。。。】 out_cmvn='data/train/global_cmvn', train_config='conf/train_conformer_bidecoder_large.yaml')
第一个是关于wav的,csj这里,按照>=0.1s过滤之后,剩余932,577行。每一行的内容是:
S04F1603_00000.308_00005.321
/workspace/asr/wenet/examples/csj/s0/data/wav/S04F1603.wav_00000.308_00005.321.wav两列,左边是id,右边是wav文件的绝对路径。第四个是输出的global_cmvn文件。第五个是配置文件。读取之后,得到的是:
ipdb> configs
{'encoder':'conformer','encoder_conf': {'output_size':512,'attention_heads':8,
'linear_units':2048,'num_blocks':12,'dropout_rate':0.1,'positional_dropout_rate':0.1,
'attention_dropout_rate':0.1,'input_layer':'conv2d','normalize_before':True,
'cnn_module_kernel':31,'use_cnn_module':True,'activation_type':'swish',
'pos_enc_layer_type':'rel_pos','selfattention_layer_type':'rel_selfattn',
'cnn_module_norm':'layer_norm'},'decoder':'bitransformer','decoder_conf':
{'attention_heads':8,'linear_units':2048,'num_blocks':3,'r_num_blocks':3,
'dropout_rate':0.1,'positional_dropout_rate':0.1,'self_attention_dropout_rate':0.1,
'src_attention_dropout_rate':0.1},'model_conf': {'ctc_weight':0.3,'lsm_weight':0.1,
'length_normalized_loss':False,'reverse_weight':0.3},'dataset_conf': {'filter_conf':
{'max_length':2000,'min_length':50,'token_max_length':400,'token_min_length':1,
'min_output_input_ratio':0.05,'max_output_input_ratio':10.0},'resample_conf':
{'resample_rate':16000},'speed_perturb':True,'fbank_conf': {'num_mel_bins':80,
'frame_shift':10,'frame_length':25,'dither':1.0},'spec_aug':True,'spec_aug_conf':
{'num_t_mask':3,'num_f_mask':2,'max_t':50,'max_f':10},'shuffle':True,
'shuffle_conf': {'shuffle_size':1500},'sort':True,'sort_conf': {'sort_size':500},
'batch_conf': {'batch_type':'static','batch_size':12}},'grad_clip':5,'accum_grad':4,
'max_epoch':200,'log_interval':100,'optim':'adam','optim_conf': {'lr':0.002},
'scheduler':'warmuplr','scheduler_conf': {'warmup_steps':50000}}重要的是:
feat_dim = configs['dataset_conf']['fbank_conf']['num_mel_bins'] # 80 dimensions主要流程

然后是最核心的对于一个batch计算相关的mean, variance和frame数量的代码:

上面的脑图有几个点,其一,waveform * (1<<15),相当于把取值放大了32768倍。例如:
ipdb> waveform, sample_rate
(tensor([[-0.0012, -0.0011, -0.0011, ..., -0.0007, 0.0002, 0.0010]]), 16000)
ipdb> waveform.shape
torch.Size([1, 28592])
ipdb> 1 << 15
32768
ipdb> n
> /workspace/asr/wenet/examples/csj/s0/tools/compute_cmvn_stats.py(51)__call__()
50 waveform = waveform * (1 << 15)
---> 51 if self.resample_rate != 0 and self.resample_rate != sample_rate:
52 resample_rate = self.resample_rate
ipdb> waveform
tensor([[-38., -36., -36., ..., -22., 5., 32.]])其二,调用kaldi.fbank的输出,mat的shape为[177, 80],取值例如:
ipdb> mat.shape
torch.Size([177, 80])
ipdb> mat
tensor([[11.1986, 12.1797, 12.5807, ..., 19.6952, 19.5846, 17.6198],
[11.2527, 11.5830, 12.3206, ..., 20.5928, 19.4825, 16.8303],
[10.2722, 8.4219, 11.0196, ..., 17.4477, 15.4190, 12.8465],
...,
[10.9639, 11.8858, 10.9370, ..., 11.4633, 11.9100, 10.6619],
[10.2542, 11.2394, 10.0596, ..., 12.2695, 12.2623, 10.2948],
[ 9.0662, 9.5829, 9.5333, ..., 12.2994, 11.4364, 10.0366]])其三,获取mean_stat之后,其实是简单相加(80个维度),取值例如:
--->61 mean_stat += torch.sum(mat, axis=0)
ipdb>mean_stat
tensor([1668.3152, 1652.7643, 1792.3387, 2019.7705, 2261.6038, 2389.7666,
...,
2553.3887, 2138.4426])其四,获取var_stat的是:
> /workspace/asr/wenet/examples/csj/s0/tools/compute_cmvn_stats.py(63)__call__()
62 var_stat += torch.sum(torch.square(mat), axis=0)
---> 63 number += mat.shape[0]
64 return number, mean_stat, var_stat
ipdb> var_stat
tensor([16120.1914, 15886.4385, 18786.2188, 24513.6582, 31858.9766, 35987.9102,
...,
38383.5391, 27215.1484])例如,其中一个值的由来为:
ipdb> torch.square(mat)[:,0]
tensor([125.4096, 126.6239, 105.5190, 138.9763, 142.1694, 167.2804, 134.3045,
...,
71.5625, 75.5625, 85.0866, 82.4624, 72.6762, 104.7350, 120.2076,
105.1492, 82.1951])
ipdb> sum(torch.square(mat)[:,0])recall主流程
上面计算之后,其实得到的是(累计和):
mean,(1,80),80维度的向量,类似:
, 其中N= frame count,
是一个frame的80个维度的向量;variance, (1, 80),类似:
, 其中N= frame count,
是一个frame的80个维度的向量;
但看i=0的时候的计算:
ipdb> p count
176228620
ipdb> n
> /workspace/asr/wenet/wenet/utils/cmvn.py(38)_load_json_cmvn()
37 for i in range(len(means)):
---> 38 means[i] /= count
# count=N, means[i]除以N之后, 就是这个维度的均值了(一共80个维度)
39 variance[i] = variance[i] / count - means[i] * means[i]
---> 40 if variance[i] < 1.0e-20:
41 variance[i] = 1.0e-20
---> 42 variance[i] = 1.0 / math.sqrt(variance[i])
43 cmvn = np.array([means, variance])然后,上面执行的是:

至此,cmvn就算搞定了。

然后是参数的规模:
135M参数
the number of model params: 135,154,810 = 135M参数。

系统会先保存一份当前model,然后读取给定的checkpoint(如果有的话);并读取该checkpoint下达到的如下信息:
{'cv_loss': 2.504995254788169, 'epoch': 119, 'lr': 0.0009465458080551907, 'step': 225100}当前start_epoch=120,因为之前的checkpoint是到epoch=119。
optimizer
往后比较重要的是优化器的构造:

ipdb> optimizer
Adam (
Parameter Group 0
amsgrad: False
betas: (0.9, 0.999)
eps: 1e-08
lr: 0.002
weight_decay: 0
)其次是关于学习率的规划器:

得到的是:
WarmupLR(warmup_steps=50000)data_loader: one batch
【wenet/dataset/processor.py里面的方法是核心】
root@1032f8d48655:/workspace/asr/wenet/examples/csj/s0#
vi /opt/conda/lib/python3.8/site-packages/torch/utils/data/dataloader.py +522
num_workers=0
prefetch, default=2
class Processor's __iter__, append "import ipdb; ipdb.set_trace()"
回顾一下之前的分析,
11个关口:
dataset=DataList(lists, shuffle=True, partition=True)
processor.parse_raw
processor.tokenize [切词]
processor.filter
processor.resample
processor.speed_perturb
processor.compute_fbank [计算filter banks]
processor.spec_aug [spectrum augment-谱增强]
processor.shuffle
processor.sort
processor.batch
processor.padding
padding: 11
ipdb>self.f,self.args,self.kw
(,(),{}) 
例如:feats补0.0000
ipdb>padded_feats
tensor([[[ 6.9371, 7.1257, 7.7550, ..., 11.1890, 11.4491, 9.2112],
[11.5080, 12.6101, 12.9887, ..., 12.0408, 12.3439, 10.0229],
[12.9132, 14.4461, 15.3878, ..., 11.3328, 11.0440, 9.5172],
...,
[ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000]],
...
[[ 8.3853, 9.5206, 10.3255, ..., 14.2314, 14.4264, 12.7104],
[10.3463, 9.3440, 9.7137, ..., 15.6107, 15.8510, 13.8745],
[11.0108, 11.0644, 12.7715, ..., 15.2525, 15.1387, 13.0166],
...,
...,
[ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000, ..., 0.0000, 0.0000, 0.0000]]])labels补-1
ipdb>padding_labels
tensor([[1554, 4019, 4112, -1, -1, -1, -1],
[2248, 1741, 1867, -1, -1, -1, -1],
[1885, 2161, 1741, 1908, 1609, 1548, -1],
[1516, 1845, 2161, -1, -1, -1, -1],
[5366, 2616, 1741, -1, -1, -1, -1],
[1762, 1527, 1548, 5375, 1816, -1, -1],
[3183, 1527, 1885, 1670, -1, -1, -1],
[1715, 1737, 1527, 1548, 1908, 1845, 1575],
[1677, 1952, 1741, -1, -1, -1, -1],
[1554, 1609, 1952, 1867, -1, -1, -1],
[3310, 3296, 1741, 1867, -1, -1, -1],
[2307, 1554, 1870, -1, -1, -1, -1]])batch: 10
接着调用:
ipdb> self.source, self.f, self.args, self.kw
(,
, (), {'batch_type': 'static', 'batch_size': 12}) static_batch,从data中取batch_size=12个数据,并发送出来。

sort: 9
ipdb> self.source, self.f, self.args, self.kw
(,
, (), {'sort_size': 500}) 根据feature length排序,shuffle之后,batch之前。从而可以把utts的相同length的样本,放入一个batch。
sort_size < shuffle_size

shuffle: 8
ipdb> self.source, self.f, self.args, self.kw
(,
, (), {'shuffle_size': 1500}) 10000个数据,搞一次shuffle,重新洗牌

spec_aug: 7
声谱增强!
ipdb> self.source, self.f, self.args, self.kw
(,
, (),
{'num_t_mask': 3, 'num_f_mask': 2, 'max_t': 50, 'max_f': 10}) time mask, y[start:end, :]=0, start.time, end.time. length=random.[1, 50]
freq mask, y[:, start:end]=0, start.freq, end.freq, length=random.[1, 10]
随机删除一些输入,属于人工增加一些噪声。。。
sample['feat'] 是对这个的处理。
80维度的梅尔谱。

compute_fbank: 6
ipdb> self.source, self.f, self.args, self.kw
(,
, (),
{'num_mel_bins': 80, 'frame_shift': 10,
'frame_length': 25, 'dither': 1.0}) 调用的是kaldi.fbank
sample里面有,'sample_rate', 'wav' = waveform, 'key', 'label',
fbank:
waveform
num_mel_bins=80
frame_length
frame_shift
dither
energy_floor
sample_frequency=sample_rate
yield, key=sample['key'], label=sample['label'], feat=mat

speed_perturb: 5
ipdb> self.source, self.f, self.args, self.kw
(,
, (), {}) speeds=[0.9, 1.0, 1.1]
改变wav的“语速”。
wav, _ = torchaudio.sox_effects.apply_effects_tensor(
waveform,
sample_rate,
[['speed', str(speed)], ['rate', str(sample_rate)]])
通过这个方法,就把原始语音的语速给修改了。

resample: 4
ipdb> self.source, self.f, self.args, self.kw
(,
, (), {'resample_rate': 16000}) 只有当新指定的sample_rate和原来不相同的时候,这个才有效:
sample['wav'] = torchaudio.transforms.Resample(
orig_freq=sample_rate,
new_freq = resample_rate) (waveform)

filter: 3
ipdb> self.source, self.f, self.args, self.kw
(,
, (),
{'max_length': 2000, 'min_length': 50, 'token_max_length': 400,
'token_min_length': 1, 'min_output_input_ratio': 0.05,
'max_output_input_ratio': 10.0}) min-length, 是关于wav的长度的;min=50 frames = 0.5 seconds
token-max-length, 400
token-min-length,是关于text的长度的;1
min-output-input-ratio= len(sample['label']) / num_frames,不能相差太大。。。
max-output-input-ratio
{'filter_conf':
{'max_length': 2000,
'min_length': 50,
'token_max_length': 400,
'token_min_length': 1,
'min_output_input_ratio': 0.05,
'max_output_input_ratio': 10.0},
三个过滤策略。
tokenize: 2
ipdb> self.source, self.f, self.args, self.kw
(,
,
({'': 0, '': 1, '...'': 5501},
None, None, False), {}) 是基于tokenizer来对文本序列切词的
bpe_model = None,所以是unigram分词(类似于character-based)

parse_raw: 1
ipdb> self.source, self.f, self.args, self.kw
(,
, (), {}) 读取raw wav,返回
key=key, (wav.id)
txt = txt,文本,原始json里面的文本
wav = waveform,这是从torchaudio.load(wav_file)得到的。
sample_rate = sample_rate,也是torchaudio.load(wav_file)得到的。
先执行这一步!
一行数据类似:
{'src':
'{"key": "S05F1072_00343.690_00344.688",
"wav":
"/workspace/asr/wenet/examples/csj/s0/data/wav/S05F1072.wav_00343.690_00344.688.wav",
"txt": "で"}',
'rank': 0,
'world_size': 1,
'worker_id': 0,
'num_workers': 1}脑图为:

batch 搞定了。
[wenet/dataset/processor.py]
待续了,总算把最重要的数据准备阶段看清了。
