1 引言

在上一篇文章中,笔者详细介绍了在文本处理过程中如何通过torch.vocab来快速根据原始语料构建我们所需要的词表。在接下来的这篇文章中,笔者就来详细介绍一下如何在上一步的基础上快速构建Pytorch中的DataLoader,以便于后续网络的训练。

下面,笔者将会详细介绍在两种场景下文本数据集的构建过程。第一种场景是输入为序列输出为标签,也就是类似于文本分类数据集;第二种场景就是输入输出均序列,例如翻译模型,或者文本生成模型等。

2 构建类文本分类数据集

对于构建类似文本分类的数据集来说,总体上可以分为4个步骤:①构建字典;②将文本中的每一个词(字)转换为Token序列;③对不同长度的样本序列按照某个标准进行padding处理;④构建DataLoader类。现在假设我们有如下格式的原始数据:

其中文本与标签之间通过一个控制符进行分割,下面我们开始来一步步构建数据集。

2.1 构建字典

由于这部分内容在上一篇文章中已经具体介绍过,所以这里直接贴出代码即可,如下:

在完成上述过程后,我们将得到一个Vocab类的实例化对象,通过它便可以得到最终生成的字典:

此时,我们就需要定义一个类,并在类的初始化过程中根据训练语料完成字典的构建,代码如下:

2.2 转换为Token序列

在得到构建的字典后,便可以通过如下函数来将训练集、验证集和测试集转换成Token序列:

在上述代码中,其中第9行用来保存当前数据中最长样本的长度,在后续padding时会用到;第13-14行是先将原始文本序列tokenize,然后再转换成每个词(字)对应的token;第18行将返回包含所有样本的一个列表,以及当前语料中最长样本的长度。

例如如下两行样本

在经过该函数处理后得到的结果为

2.3 padding处理

从上面的输出结果也可以看到,对于不同的样本来说其对应的长度通常来说都是不同的。但是在将数据输入到相应模型时却需要保持同样的长度,因此在这里我们就需要对Token序列化后的样本进行padding处理,具体代码如下:

上述代码是根据torch.nn.utils.rnn中的pad_sequence函数修改而来,增加了可以指定一个全局最大长度的参数max_len。在经过pad_sequence函数处理后,所有的样本就会保持同样的长度。例如上面的tokenize后的结果在经过padding处理后将变为

即第2个样本的末尾padding了两个1。

2.4 构建DataLoader迭代器

在经过前面的一系列处理后,我们便可以通过如下代码来构建DataLoader迭代器:

在上述代码中,第1-13行便是用来构造最后需要返回的DataLoader迭代器;而第15-25行则是自定义一个函数来对每个batch中的样本进行处理,该函数将作为一个参数传入到类DataLoader中。同时,由于在DataLoader中是对每一个batch的数据进行处理,所以,当max_len=None时这就意味着上面的pad_sequence操作最终表现出来的结果就是不同的样本,padding后在同一个batch中长度是一样的,而在不同的batch之间可能是不一样的。因为此时pad_sequence是以一个batch中最长的样本为标准对其它样本进行padding。当max_len = 'same'时,最终表现出来的结果就是,所有样本在padding后的长度都等于训练集中最长样本的长度。

最终,在定义完成类LoadSentenceClassificationDataset后,便可以通过如下方式进行使用:

当然,暂时不想理解代码的朋友,可以直接将原始数据整理成上述一样的格式然后导入类LoadSentenceClassificationDataset使用即可(下载地址见文末[1])。

3 构建类翻译模型数据集

通常,在NLP中还有一类任务就是模型的输入和输出均为序列的形式,这就需要在训练的过程中同时将这两部分输入到模型中。例如在翻译模型的训练过程中,需要同时将原始序列和目标序列一同输入到网络模型里。对于构建这种类似翻译模型的数据集总体上同样也可以采用上面的4个步骤:①构建字典;②将文本中的每一个词(字)转换为Token序列;③对不同长度的样本序列按照某个标准进行padding处理;④构建DataLoader类。只是在每一步中都需要分别对原始序列和目标序列进行处理

现在假设我们有如下格式的平行语料数据:

从上面的语料可以看出,这是一个用于训练翻译模型的数据,原始序列为德语,目标序列为英语。下面我们开始来一步步的构建数据集。

3.1 构建字典

在构建字典的过程中,整体上与2.1节内容中的一样,只是在这里需要同时对原始序列和目标序列分别构建一个字典。具体代码如下所示:

在上述代码中,第1-3行为自定义的一个tokenizer;虽然上述两种语料可以直接通过空格来对每个词进行分割,但是还需要做的就是在单词和符号之间加上一个空格,以便把符号分割出来。第5-12行定义的build_vocab函数还是同之前的一样,没有发生改变。

在完成上述过程后,我们将得到两个Vocab类的实例化对象。

一个为原始序列的字典:

一个为目标序列的字典:

此时,我们就需要定义一个类,并在类的初始化过程中根据训练语料完成字典的构建,代码如下:

3.2 转换为Token序列

在得到构建的字典后,便可以通过如下函数来将训练集、验证集和测试集转换成Token序列:

在上述代码中,第11-4行分别用来将原始序列和目标序列转换为对应词表中的Token形式。在处理完成后,就会得到类似如下的结果:

其中左边的一列就是原始序列的Token形式,右边一列就是目标序列的Token形式,每一行构成一个样本。

3.3 padding处理

同样,从上面的输出结果可以看到,无论是对于原始序列来说还是目标序列来说,在不同的样本中其对应长度都不尽相同。但是在将数据输入到相应模型时却需要保持同样的长度,因此在这里我们就需要对Token序列化后的样本进行padding处理。同时需要注意的是,一般在这种生成模型中,模型在训练过程中只需要保证同一个batch中所有的原始序列等长,所有的目标序列等长,也就是说不需要在整个数据集中所有样本都保证等长。

因此,在实际处理过程中无论是原始序列还是目标序列都会以每个batch中最长的样本为标准对其它样本进行padding,具体代码如下:

在上述代码中,第6-7行用来在目标序列的首尾加上特定的起止符;第9-10行则是分别对一个batch中的原始序列和目标序列以各自当中最长的样本为标准进行padding(这里的pad_sequence导入自torch.nn.utils.rnn)。

3.4 构建DataLoader迭代器

在经过前面的一系列处理后,我们便可以通过如下代码来构建DataLoader迭代器:

最终,在定义完成类LoadEnglishGermanDataset后,便可以通过如下方式进行使用:

运行结果为:

从上述结果可以看出,对于同一个batch来说,原始序列的长度都相同,目标序列的长度也都相同。最后,如果暂时不想理解代码的朋友,可以直接将原始数据整理成上述一样的格式,然后导入类LoadEnglishGermanDataset使用即可。

4 总结

在这篇文章中,笔者首先介绍了如何快速的构建类似文本分类模型中的DataLoader迭代器,包括词表字典的构建、序列转换为Token、对不同长度的样本进行padding等;然后介绍了如何构建类似翻译模型中的数据迭代器,并且也对其用法进行了示例。

本次内容就到此结束,感谢您的阅读!如果你觉得上述内容对你有所帮助,欢迎分享至一位你的朋友!若有任何疑问与建议,请添加笔者微信nulls8或加群进行交流。青山不改,绿水长流,我们月来客栈见!

引用

[1] 完整代码:https://github.com/moon-hotel/DeepLearningWithMe