Categories
程式開發

LineFlow開源:比PyTorch簡潔數倍,適用任何框架的NLP數據集處理程序


一般來講,用 PyTorch 處理自然語言比較繁瑣。於是,國外一位開發者Yasufumi TANIGUCHI開發了 LineFlow,為了盡可能減輕編碼的痛苦,並保證完成同樣的任務。 Yasufumi TANIGUCHI表示,LineFlow 要比 PyTorch 簡潔數倍,讓我們來看看 LineFlow 究竟能簡潔到什麼地步?

對自然語言處理任務來說,你可能需要在預處理中對文本進行詞法分析或構建詞彙表。因為這個過程非常痛苦,所以我創建了LineFlow ,盡可能讓整個過程乾淨整潔。真正的代碼看起來是什麼樣子?請看下面的圖,預處理包括詞法分析、詞彙表構建和索引。

LineFlow開源:比PyTorch簡潔數倍,適用任何框架的NLP數據集處理程序 1

左邊部分是來自 PyTorch 官方示例倉庫的示例代碼,它對文本數據進行常見的預處理。右邊部分是用 LineFolw 編寫的,實現了完全相同的處理。看完對比之後,你應該明白 LineFlow 是如何減輕痛苦的。要查看完整的代碼,可以訪問此鏈接

在本文中,我將詳細解釋上圖右邊部分的代碼,並講解 LineFlow 的用法。

加載文本數據

文本數據的加載,是通過上面代碼中的第 8 行完成的,我稍後會詳細解釋這個 map。lf.TextDataset 將文本文件的路徑作為參數並進行加載。

dataset = lf.TextDataset(path, encoding='utf-8').map(...)

lf.TextDataset 要求的數據格式是每行對應一個數據。如果文本數據滿足此條件,則可以加載任何類型的文本數據。

LineFlow開源:比PyTorch簡潔數倍,適用任何框架的NLP數據集處理程序 2LineFlow開源:比PyTorch簡潔數倍,適用任何框架的NLP數據集處理程序 3

加載之後,它將文本數據轉換為列表。列表中的項對應於文本數據中的行。 請看下圖,這是 lf.TextDataset 的直觀圖像。圖中的 d 代表代碼中的 dataset

LineFlow開源:比PyTorch簡潔數倍,適用任何框架的NLP數據集處理程序 4

LineFlow 已經提供了一些公開可用的數據集。所以你可以馬上使用它。要查看提供的數據集,請訪問此鏈接

2. 標記化

文本標記化也是通過第 8 行完成的。map將作為參數傳遞的處理應用到文本數據的每一行。

dataset = lf.TextDataset(...).map(lambda x: x.split() + (''))

請看下圖。這是 lf.TextDataset.map 的直觀圖像。圖中的 d 代表代碼中的 dataset

LineFlow開源:比PyTorch簡潔數倍,適用任何框架的NLP數據集處理程序 5

讓我們深入了解下面的實際處理過程。

lambda x: x.split() + ('')

我們將文本數據中的每一行按空格拆分為標記,然後將 添加到這些標記的末尾。我們遵循 WikiText 官方頁面上的處理方式。

此時,我們使用 str.split 進行標記化。我們可以使用其他的標記化方法,如 spaCyStanfordNLPBling 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)))

當我們想要製作用於注意力機製或長短期記憶網絡的掩碼時,這種處理就很有用。

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`。

LineFlow開源:比PyTorch簡潔數倍,適用任何框架的NLP數據集處理程序 6

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

LineFlow開源:比PyTorch簡潔數倍,適用任何框架的NLP數據集處理程序 7

此代碼等同於以下代碼。

>>> from itertools import chain
>>> chain.from_iterable(map(indexer, dataset))
>>>
>>> dataset.flat_map(indexer) # same as above

最後,我們用 torch.LongTensor 將它包起來,把它變成張量。至此就完成了文本數據的加載。

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() + (''))
        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 通過對文本數據進行向量化來完成較少的循環和嵌套代碼。我們可以使用 Python 的 map 來完成同樣的工作。但是,LineFlow 為我們提供了可讀的、乾淨的代碼,因為它像管道(Fluent Interface)一樣構建了處理過程。

如果你喜歡 LineFlow,並想了解更多信息,請訪問 LineFlow 在 GitHub 的倉庫

作者介紹:

Yasufumi TANIGUCHI,軟件工程師,對自然語言處理有著濃厚的興趣。本文最初發表於 Medium 博客,經原作者 Yasufumi TANIGUCHI 授權,InfoQ 中文站翻譯並分享。

原文鏈接:

https://towardsdatascience.com/lineflow-introduction-1caf7851125e