作者 | Yasufumi TANIGUCHI
来源 | Medium
编辑 | 代码医生团队
对于NLP任务,可能需要在预处理中标记文本或构建词汇表。可能已经体验到预处理代码与桌面一样混乱。如这就是为什么创建LineFlow来缓解痛苦!它将使“桌面”尽可能干净。真正的代码如何?看看下图。预处理包括标记化,构建词汇表和索引。
https://github.com/tofunlp/lineflow
左边部分是来自PyTorch官方示例存储库的示例代码,它对文本数据进行常见的预处理。右边部分用LineFlow编写,以实现完全相同的处理。了解LineFlow如何减轻痛苦。可以从此链接查看完整代码。
https://gist.github.com/yasufumy/ba73b587bd3c516b66fb94b3a90bac71
在这篇文章中,将详细解释右边的代码并展示 LineFlow 的用法。开始一个干净的“桌面”生活!
1.加载文本数据
加载文本数据由上面代码的第8行完成。稍后会解释一下这张地图。lf.TextDataset 将文本文件的路径作为参数并加载它。
dataset = lf.TextDataset(path, encoding='utf-8').map(...)
lf.TextDataset 期望的数据格式是每行对应于一个数据。如果文本数据满足此条件,则可以加载任何类型的文本数据。
加载后,它将文本数据转换为列表。列表中的项目对应于文本数据中的行。请看下图。这是直观的形象 lf.TextDataset。该d图中表示dataset的代码。
LineFlow已经提供了一些公开可用的数据集。所以可以立即使用它。可以在此处查看提供的数据集。
https://github.com/tofunlp/lineflow#datasets
2.标记化
文本标记化也由第8行完成。map将作为参数传递的处理应用于文本数据的每一行。
dataset = lf.TextDataset(...).map(lambda x: x.split() + ['<eos>'])
请看下图。这是直观的形象 lf.TextDataset.map。该d图中表示 dataset 的代码。
深入了解下面的实际处理。
lambda x: x.split() + ['<eos>']
在这里,将文本数据中的每一行用空格分割为标记,然后添加<eos>到这些标记的末尾。按照WikiText官方页面中的处理方式进行操作。
在这个时候,str.split 用于标记化。可以使用其他标记化方法,如 spaCy,StanfordNLP 和 Bling Fire 等。例如如果想使用 Bling Fire ,将获得以下代码。
from blingfire import text_to_words
d = lf.TextDataset('/path/to/your/text')
d.map(text_to_words).map(str.split)
此外,只要处理将每行文本数据作为参数,就可以进行任何想要的处理。例如,可以计算令牌的数量。在以下代码中,标记的数量在第二个元素中定义。
d = lf.TextDataset('/path/to/text')
d.map(tokenize).map(lambda x: (x, len(x)))
当想要为注意机制或LSTM制作掩码时,这个处理很有用。
3.索引
索引从第9行到第12行完成。这些行如下图所示。在这个代码块中,构建了词汇表和索引。按顺序看看这些。
for word in dataset.flat_map(lambda x: x):
self.dictionary.add_word(word)
return torch.LongTensor(dataset.flat_map(...))
首先,将看到构建词汇表的障碍。在下面的代码块中,构建了词汇表。flat_map 将作为参数传递的处理应用于数据中的每一行,然后将其展平。所以将获得个人令牌
dataset.flat_map(lambda x: x)。
for word in dataset.flat_map(lambda x: x):
self.dictionary.add_word(word)
请看下图。这是直观的形象dataset.flat_map(lambda x: x)。该d图中表示dataset的代码。
flat_map 有点令人困惑,但它等于以下代码。
from itertools import chain
chain.from_iterable(map(lambda x: x, dataset))
dataset.flat_map(lambda x: x) # same as above
通过使用提取每个标记后 flat_map,传递 self.dictionary.add_word 构建词汇表的标记。
self.dictionary.add_word(word)
接下来,将看到索引的代码块。索引由以下块完成。在这里还使用flat_map索引每个标记并展平它。这是因为PyTorch的例子需要扁平标记的张量。
dataset.flat_map(
[lambda x: self.dictionary.word2idx[token] for token in x)])
请看下图。这是直观的形象 dataset.flat_map(indexer)。该 d 图中表示 dataset 的代码。
此代码等于以下代码。
from itertools import chain
chain.from_iterable(map(indexer, dataset))
dataset.flat_map(indexer) # same as above
最后,将其包装torch.LongTensor以使其tensor。完成了加载文本数据。
return torch.LongTensor(dataset.flat_map(...))
可以查看到目前为止已经看到的完整代码。
import os
import torch
import lineflow as lf
class Dictionary(object):
def __init__(self):
self.word2idx = {}
self.idx2word = []
def add_word(self, word):
if word not in self.word2idx:
self.idx2word.append(word)
self.word2idx[word] = len(self.idx2word) - 1
return self.word2idx[word]
def __len__(self):
return len(self.idx2word)
class Corpus(object):
def __init__(self, path):
self.dictionary = Dictionary()
self.train = self.tokenize(os.path.join(path, 'train.txt'))
self.valid = self.tokenize(os.path.join(path, 'valid.txt'))
self.test = self.tokenize(os.path.join(path, 'test.txt'))
def tokenize(self, path):
assert os.path.exists(path)
dataset = lf.TextDataset(path, encoding='utf-8').map(lambda x: x.split() + ['<eos>'])
for word in dataset.flat_map(lambda x: x):
self.dictionary.add_word(word)
return torch.LongTensor(dataset.flat_map(
lambda x: [self.dictionary.word2idx[token] for token in x]))
这就是解释的全部内容。LineFlow通过矢量化文本数据来完成less循环和较少嵌套的代码。可以使用Python的map完全相同。但 LineFlow 提供了可读和干净的代码,因为它构建了像管道(Fluent Interface)这样的处理。
如果喜欢LineFlow并想了解更多信息,请访问下面的存储库。
https://github.com/tofunlp/lineflow/tree/master/examples?source=post_page-----1caf7851125e----------------------