
上一篇说到了第四点,接下来继续分析整个模型中数据格式的变化。
上次说到了每一条短音频的数据被提取到特征之后,变成一个item返回给dataset的getitem函数,从而构成了一个迭代器,在训练的过程中依次输出给model,但是问题是之前说过model获取数据的形式是按批次的(batch_size个为一组),那么这个工作是在哪里做的呢?
然后就介绍一下采样器(Sampler),同样的Sampler也是一个迭代器,例如在一个dataset中一共有n个batch,每个batch有batch_size个item,那么Sampler做的事情就是如何选出这个n-batch的数据,并且batch_size内如何排列的。同为迭代器的形式,为后面生成可遍历数据集提供了方便,Sampler类实现的大致结构如下:
from torch.utils.data import Sampler
class SomeSampler(Sampler):
def __init__(self, dataset, batch_size, start_index):
self.list = [ids[i:i+batch_size] for i in range(0, len(dataset), batch_size)]
def __len__(self):
return len(self.list)- self.start_index
def set_epoch(self, epoch):
self.epoch = epoch
def __iter__(self):
g = torch.Generator().manual_seed(self.epoch)
indices = (torch.randperm(len(self.list)-self.start_index, generator=g))
.add(self.start_index)
.tolist()
for x in indices:
batch_ids = self.list[x]
np.random.shuffle(batch_ids)
yield batch_ids
可以看出,iter函数返回的每一个batch的item的序号都是打乱的,在前期我们手机大量数据的时候可能会有相似的或者相同数据源的在清单文件的相邻位置,如果按照录入的顺序进行训练,一个batch之中的数据可能非常相似,就会对魔性的训练造成影响。
得到了Sampler和Dataset就可以得到真正在for循环中输入给model的数据了,关系如下:
train_loader = SomeDataLoader(dataset=train_dataset,batch_sampler=train_sampler)
一开始我们说过,数据才是我们真正关注的,此时的数据虽然在数量上、批次(batch)上符合我们的要求,但是本项目中输入模型的数据增加了音频特征的label的shapes,而且原始音频数据特征的第二维度长度各不相同,统一shape才能输入到模型中(以batch为单位),这些就依靠dataLoader的collate_fn参数来实现,他本身的定义是如何取样本,
官网的定义是这样的
collate_fn (callable, optional) – merges a list of samples to form a mini-batch of Tensor(s). Used when using batched loading from a map-style dataset.
但是本项目中就在这个函数中对数据做了一次整理。dataLoader的函数参数是这样的:
class torch.utils.data.DataLoader(
dataset,
batch_size=1,
shuffle=False,
sampler=None,
batch_sampler=None,
num_workers=0,
collate_fn=<function default_collate>,
pin_memory=False,
drop_last=False,
timeout=0,
worker_init_fn=None)
本项目中collate_fn的具体逻辑是:
def _collate_fn(batch):
def func(p):
return p[0].size(1)
batch = sorted(batch, key=lambda sample: sample[0].size(1), reverse=True)
longest_sample = max(batch, key=func)[0]
freq_size = longest_sample.size(0)
minibatch_size = len(batch)
max_seqlength = longest_sample.size(1)
inputs = torch.zeros(minibatch_size, 1, freq_size, max_seqlength)
input_percentages = torch.FloatTensor(minibatch_size)
target_sizes = torch.IntTensor(minibatch_size)
targets = []
for x in range(minibatch_size):
sample = batch[x]
tensor = sample[0]
target = sample[1]
seq_length = tensor.size(1)
inputs[x][0].narrow(1, 0, seq_length).copy_(tensor)
input_percentages[x] = seq_length / float(max_seqlength)
target_sizes[x] = len(target)
targets.extend(target)
targets = torch.IntTensor(targets)
return inputs, targets, input_percentages, target_sizes
return的这四个变量根据后面训练或者预测的需求去确定就可以。
看一下具体的逻辑吧,主要还是关于数据处理。输入参数batch中包含两个数据,(spect, transcribe),就是音频特征数据和语音内容。batch按照音频特征数据二维度的长度进行的升序排列然后取得了长度最大的item(longest_sample),一个batch的长度(minibatch_size),新建空的矩阵,形状就是(minibatchsize, 1, freq_size, max_seq_length),也就是我们之前说过的(16, 1, 161, xx)了,这里的161表征一种频率特性,把batch的内容处理之后放入这个新的四维矩阵,把tensor放到形状设定好的zeros矩阵inputs中就可以达到补齐的效果了。
所以这个时候的数据变化是
dataLoader----(16, 1, 161, 1000)
5、模型中的数据处理
上面4介绍的数据就是真正输入模型的数据了,数据首先经过卷积层,这一层的参数成了hard code,代码如下
self.conv = MaskConv(nn.Sequential(
nn.Conv2d(1, 32, kernel_size=(41, 11), stride=(2, 2), padding=(20, 5)),
nn.BatchNorm2d(32),
nn.Hardtanh(0, 20, inplace=True),
nn.Conv2d(32, 32, kernel_size=(21, 11), stride=(2, 1), padding=(10, 5)),
nn.BatchNorm2d(32),
nn.Hardtanh(0, 20, inplace=True)
))
模型的卷积核设置的确实比较大,这会让模型的参数增加,一般的卷积核可以是(3, 3) (5, 5)的,考虑到数据模型的原始数据shape比较大,所以这里做这个处理也就不奇怪了,根据官网给的计算公式(如下)

可以计算出得到的数据形状
Conv2D----(16, 32, 41, 500)
接下来的循环卷积层我们选择lstm性能相对较好,可以做上下文的关联,设置双向的卷积层就可以让上下文相互感知了,这里默认按照这种情况分析。在设计网络模型的时候涉及到模型之间的数据shape转换,因为不同的网络层都有自己的对数据格式的要求,比如官网中lstm对数据格式的要求是
(seq_len, batch, input_size)
那么在输入之前要进行的两步操作就不难理解了
sizes = x.size()
x = x.view(sizes[0], sizes[1] * sizes[2], sizes[3]) # Collapse feature dimension
x = x.transpose(1, 2).transpose(0, 1).contiguous() # TxNxH
先观察一下lstm的参数
input_size=input_size, hidden_size=hidden_size,
bidirectional=bidirectional, bias=True
输出数据的形状受到hidden_size的影响,所以想用lstm形成循环的多层就要保证输入和输出的shape一致,这里先用一个lstm把输出转成hidden_size的形状,然后把这些数据循环输入。对于双向的设置输出形状是(seq_len, batch, num_directions * hidden_size),所以用一个相加的操作把他搞定
x = x.view(x.size(0), x.size(1), 2, -1).sum(2).view(x.size(0), x.size(1), -1)
LSTM----(500, 16, 1024)
这时再transpose一下让batch_size恢复原位
transpose----(16, 500, 1024)
经过上面的过程接下来就是线性层了,实际上整个模型还是相当于一个大型的分类器,如果具有上下文关联的能力就更强了
Linear----(16, 500, 8000)
在训练的过程中不会增加softmax层,可以理解为这个softmax后面也会作为模型预测的一部发挥他的作用
softmax不会改变数据的形状,原样输出,但是8000维数据会被处理成概率的形式,并且相加之和为1,公式如下

模型这部分的数据分析就说的差不多了,后面继续补充关于预测过程的内容吧❥(^_-)
网友评论