在數(shù)據(jù)處理的時(shí)候文本數(shù)據(jù)處理是一件比較麻煩的事情,如果使用手工方式去進(jìn)行處理的話會(huì)浪費(fèi)很多人力。在pytorch中有一個(gè)torchtext工具,可以很方便快速的進(jìn)行torchtext進(jìn)行數(shù)據(jù)處理,那么torchtext怎么進(jìn)行文本數(shù)據(jù)處理呢?接下來(lái)這篇文章告訴你。
前言
用深度學(xué)習(xí)做nlp也有一段時(shí)間了,熟悉這塊內(nèi)容的同學(xué)都知道,實(shí)踐算法的時(shí)候,寫(xiě)模型是個(gè)簡(jiǎn)單的事,最麻煩的是數(shù)據(jù)處理,數(shù)據(jù)處理不僅會(huì)浪費(fèi)我們大部分時(shí)間,而且會(huì)消耗很大的計(jì)算資源,浪費(fèi)人力物力。
今年開(kāi)始接觸pytorch,簡(jiǎn)潔的API,動(dòng)態(tài)圖,更加靈活的編寫(xiě)模式,諸多優(yōu)點(diǎn)不用多說(shuō)。
最近嘗試使用torchtext工具,這里想先說(shuō)明的是,torchtext并不是pytorch所獨(dú)有的,使用其它深度學(xué)習(xí)框架,torchtext仍然可以使用。
但是比較麻煩的是,并沒(méi)有很好很全面的torchtext教程,給同學(xué)們?nèi)腴T(mén)造成了一定麻煩,這也是我寫(xiě)這篇文章的目的。
首先整體介紹一下torchtext的組件
torchtext包含以下組件:
Field
:主要包含以下數(shù)據(jù)預(yù)處理的配置信息,比如指定分詞方法,是否轉(zhuǎn)成小寫(xiě),起始字符,結(jié)束字符,補(bǔ)全字符以及詞典等等
Dataset
:繼承自pytorch的Dataset,用于加載數(shù)據(jù),提供了TabularDataset可以指點(diǎn)路徑,格式,F(xiàn)ield信息就可以方便的完成數(shù)據(jù)加載。同時(shí)torchtext還提供預(yù)先構(gòu)建的常用數(shù)據(jù)集的Dataset對(duì)象,可以直接加載使用,splits方法可以同時(shí)加載訓(xùn)練集,驗(yàn)證集和測(cè)試集。
Iterator
: 主要是數(shù)據(jù)輸出的模型的迭代器,可以支持batch定制
1. Field
Field 包含一寫(xiě)文本處理的通用參數(shù)的設(shè)置,同時(shí)還包含一個(gè)詞典對(duì)象,可以把文本數(shù)據(jù)表示成數(shù)字類型,進(jìn)而可以把文本表示成需要的tensor類型
以下是Field對(duì)象包含的參數(shù):
sequential: 是否把數(shù)據(jù)表示成序列,如果是False, 不能使用分詞 默認(rèn)值: True.
use_vocab: 是否使用詞典對(duì)象. 如果是False 數(shù)據(jù)的類型必須已經(jīng)是數(shù)值類型. 默認(rèn)值: True.
init_token: 每一條數(shù)據(jù)的起始字符 默認(rèn)值: None.
eos_token: 每條數(shù)據(jù)的結(jié)尾字符 默認(rèn)值: None.
fix_length: 修改每條數(shù)據(jù)的長(zhǎng)度為該值,不夠的用pad_token補(bǔ)全. 默認(rèn)值: None.
tensor_type: 把數(shù)據(jù)轉(zhuǎn)換成的tensor類型 默認(rèn)值: torch.LongTensor.
preprocessing:在分詞之后和數(shù)值化之前使用的管道 默認(rèn)值: None.
postprocessing: 數(shù)值化之后和轉(zhuǎn)化成tensor之前使用的管道默認(rèn)值: None.
lower: 是否把數(shù)據(jù)轉(zhuǎn)化為小寫(xiě) 默認(rèn)值: False.
tokenize: 分詞函數(shù). 默認(rèn)值: str.split.
include_lengths: 是否返回一個(gè)已經(jīng)補(bǔ)全的最小batch的元組和和一個(gè)包含每條數(shù)據(jù)長(zhǎng)度的列表 . 默認(rèn)值: False.
batch_first: Whether to produce tensors with the batch dimension first. 默認(rèn)值: False.
pad_token: 用于補(bǔ)全的字符. 默認(rèn)值: "<pad>".
unk_token: 不存在詞典里的字符. 默認(rèn)值: "<unk>".
pad_first: 是否補(bǔ)全第一個(gè)字符. 默認(rèn)值: False.
重要的幾個(gè)方法:
pad(minibatch): 在一個(gè)batch對(duì)齊每條數(shù)據(jù)
build_vocab(): 建立詞典
numericalize(): 把文本數(shù)據(jù)數(shù)值化,返回tensor
簡(jiǎn)單的栗子如下,建一個(gè)Field對(duì)象
TEXT = data.Field(tokenize=data.get_tokenizer('spacy'),
init_token='<SOS>', eos_token='<EOS>',lower=True)
2.Dataset
torchtext的Dataset是繼承自pytorch的Dataset,提供了一個(gè)可以下載壓縮數(shù)據(jù)并解壓的方法(支持.zip, .gz, .tgz)
splits方法可以同時(shí)讀取訓(xùn)練集,驗(yàn)證集,測(cè)試集
TabularDataset可以很方便的讀取CSV, TSV, or JSON格式的文件,例子如下:
train, val, test = data.TabularDataset.splits(
path='./data/', train='train.tsv',
validation='val.tsv', test='test.tsv', format='tsv',
fields=[('Text', TEXT), ('Label', LABEL)])
加載數(shù)據(jù)后可以建立詞典,建立詞典的時(shí)候可以使用與訓(xùn)練的word vector
TEXT.build_vocab(train, vectors="glove.6B.100d")
3. Iterator
Iterator是torchtext到模型的輸出,它提供了我們對(duì)數(shù)據(jù)的一般處理方式,比如打亂,排序,等等,可以動(dòng)態(tài)修改batch大小,這里也有splits方法 可以同時(shí)輸出訓(xùn)練集,驗(yàn)證集,測(cè)試集
參數(shù)如下:
dataset: 加載的數(shù)據(jù)集
batch_size: Batch 大小.
batch_size_fn: 產(chǎn)生動(dòng)態(tài)的batch大小 的函數(shù)
sort_key: 排序的key
train: 是否是一個(gè)訓(xùn)練集
repeat: 是否在不同epoch中重復(fù)迭代
shuffle: 是否打亂數(shù)據(jù)
sort: 是否對(duì)數(shù)據(jù)進(jìn)行排序
sort_within_batch: batch內(nèi)部是否排序
device: 建立batch的設(shè)備 -1:CPU ;0,1 ...:對(duì)應(yīng)的GPU
使用方式如下:
train_iter, val_iter, test_iter = data.Iterator.splits(
(train, val, test), sort_key=lambda x: len(x.Text),
batch_sizes=(32, 256, 256), device=-1)
4.其他
torchtext提供常用文本數(shù)據(jù)集,并可以直接加載使用:
train,val,test = datasets.WikiText2.splits(text_field=TEXT)
現(xiàn)在包含的數(shù)據(jù)集包括:
Sentiment analysis: SST and IMDb
Question classification: TREC
Entailment: SNLI
Language modeling: WikiText-2
Machine translation: Multi30k, IWSLT, WMT14
完整例子如下,短短幾行就把詞典和數(shù)據(jù)batch做好了。
import spacy
import torch
from torchtext import data, datasets
spacy_en = spacy.load('en')
def tokenizer(text): # create a tokenizer function
return [tok.text for tok in spacy_en.tokenizer(text)]
TEXT = data.Field(sequential=True, tokenize=tokenizer, lower=True, fix_length=150)
LABEL = data.Field(sequential=False, use_vocab=False)
train, val, test = data.TabularDataset.splits(
path='./data/', train='train.tsv',
validation='val.tsv', test='test.tsv', format='tsv',
fields=[('Text', TEXT), ('Label', LABEL)])
TEXT.build_vocab(train, vectors="glove.6B.100d")
train_iter, val_iter, test_iter = data.Iterator.splits(
(train, val, test), sort_key=lambda x: len(x.Text),
batch_sizes=(32, 256, 256), device=-1)
vocab = TEXT.vocab
補(bǔ)充:使用TorchText處理我們自己的數(shù)據(jù)集
TorchText可以讀取三種數(shù)據(jù)格式:json, tsv (tab separated values 制表分隔值)和csv(comma separated values 逗號(hào)分隔值)。
處理JSON數(shù)據(jù)
從json開(kāi)始,你的數(shù)據(jù)必須是json行格式,也就是說(shuō),它必須是這樣的:
{"name": "John", "location": "United Kingdom", "age": 42, "quote": ["i", "love", "the", "united kingdom"]}
{"name": "Mary", "location": "United States", "age": 36, "quote": ["i", "want", "more", "telescopes"]}
也就是說(shuō),每一行都是一個(gè)json對(duì)象。data/trian.json為例。
然后我們定義字段:
from torchtext import data
from torchtext import datasets
NAME = data.Field()
SAYING = data.Field()
PLACE = data.Field()
接下來(lái),我們必須告訴TorchText哪個(gè)字段應(yīng)用于json對(duì)象的哪個(gè)元素。
對(duì)于json數(shù)據(jù),我們必須創(chuàng)建一個(gè)字典:
鍵與json對(duì)象的鍵匹配
值為元組,其中:
第一個(gè)元素成為batch對(duì)象的屬性名
第二個(gè)元素是字段的名稱
一些注意事項(xiàng):
fields字典中鍵的順序并不重要,只要它的鍵與json數(shù)據(jù)鍵匹配即可。
字段名不必與json對(duì)象中的鍵匹配,例如,我們使用PLACE來(lái)表示“l(fā)ocation”字段。
當(dāng)處理json數(shù)據(jù)時(shí),并不是所有的鍵都必須使用,例如,我們沒(méi)有使用“age”字段。
同樣,如果json字段的值是一個(gè)字符串,那么將應(yīng)用字段標(biāo)記化(默認(rèn)情況下是將字符串按空格分隔),然而,如果值是一個(gè)列表,則不應(yīng)用標(biāo)記化。通常情況下,將數(shù)據(jù)標(biāo)記為一個(gè)列表是一個(gè)好主意,這節(jié)省了時(shí)間,因?yàn)槟槐氐却齌orchText來(lái)做這件事。
json字段的值不必是相同的類型。有些例子的“引號(hào)”可以是字符串,有些則是列表。標(biāo)記化將只應(yīng)用于那些以“引號(hào)”表示字符串的字符串。
如果你正在使用一個(gè)json字段,每個(gè)例子必須有一個(gè)該字段的實(shí)例,例如在這個(gè)例子中所有的例子必須有一個(gè)name,location和quote。但是,由于我們沒(méi)有使用age字段,因此示例中沒(méi)有age字段也沒(méi)有關(guān)系。
fields = {'name': ('n', NAME), 'location': ('p', PLACE), 'quote': ('s', SAYING)}
現(xiàn)在,在訓(xùn)練循環(huán)中,我們可以通過(guò)數(shù)據(jù)迭代器進(jìn)行迭代并且通過(guò)batch.n訪問(wèn)name,通過(guò)batch.p訪問(wèn)location,通過(guò)batch.s訪問(wèn)quote。
然后我們使用TabularDataset.splits函數(shù)創(chuàng)建我們的數(shù)據(jù)集(train_data和test_data)
path參數(shù)指定兩個(gè)數(shù)據(jù)集中共同的頂級(jí)文件夾,train和test參數(shù)指定每個(gè)數(shù)據(jù)集的文件名,例如,這里的train數(shù)據(jù)集位于data/train.json。
我們告訴函數(shù)我們正在使用json數(shù)據(jù),并將前面定義的fields字典傳遞給它。
train_data, test_data = data.TabularDataset.splits(
path = 'data',
train = 'train.json',
test = 'test.json',
format = 'json',
fields = fields
)
如果已經(jīng)有驗(yàn)證數(shù)據(jù)集,則可以將其路徑作為validation 參數(shù)傳遞。
train_data, valid_data, test_data = data.TabularDataset.splits(
path = 'data',
train = 'train.json',
validation = 'valid.json',
test = 'test.json',
format = 'json',
fields = fields
)
然后,我們可以查看一個(gè)示例,以確保它已經(jīng)正確地工作。
請(qǐng)注意字段名(n、p和s)是如何與fields字典中定義的內(nèi)容匹配的。
還請(qǐng)注意p中的單詞“United Kingdom”是如何被標(biāo)記化分開(kāi)的,而s中的“United Kingdom”則沒(méi)有。這是由于前面提到的原因,TorchText假設(shè)任何作為列表的json字段都已經(jīng)被標(biāo)記化了,并且不再應(yīng)用進(jìn)一步的標(biāo)記化。
print(vars(train_data[0]))
{'n': ['John'], 'p': ['United', 'Kingdom'], 's': ['i', 'love', 'the', 'united kingdom']}
現(xiàn)在我們可以使用train_data、test_data和valid_data來(lái)構(gòu)建詞匯表并創(chuàng)建迭代器。我們可以使用batch.n, batch.p and batch.s訪問(wèn)分別表示names、places和sayings的所有屬性。
處理CSV/TSV數(shù)據(jù)
csv和tsv非常相似,只是csv的元素用逗號(hào)分隔,而tsv用制表符分隔。
用上面的例子,我們的tsv數(shù)據(jù)將會(huì)是:
name location age quote
John United Kingdom 42 i love the united kingdom
Mary United States 36 i want more telescopes
也就是說(shuō),每一行的元素都由制表符分隔,每行有一個(gè)示例。第一行通常是標(biāo)題(即每個(gè)列的名稱),但你的數(shù)據(jù)也可能沒(méi)有標(biāo)題。
tsv或csv數(shù)據(jù)中不能有列表。
字段的定義方式與json稍有不同?,F(xiàn)在我們使用一個(gè)元組列表,其中每個(gè)元素也是一個(gè)元組。這些內(nèi)部元組的第一個(gè)元素將成為batch對(duì)象的屬性名,第二個(gè)元素是字段名。
與json數(shù)據(jù)不同,元組必須與tsv數(shù)據(jù)中的順序相同。因此,當(dāng)跳過(guò)一列數(shù)據(jù)時(shí),需要使用一個(gè)none元組,如果沒(méi)有,那么我們的SAYING字段將應(yīng)用到tsv數(shù)據(jù)的age列,而quote列將不會(huì)被使用。
但是,如果您只想使用name和age列,您可以只使用兩個(gè)元組,因?yàn)樗鼈兪乔皟蓚€(gè)列。
我們更改TabularDataset以讀取正確的.tsv文件,并將format參數(shù)更改為'tsv'。
如果你的數(shù)據(jù)有一個(gè)標(biāo)題頭,我們的數(shù)據(jù)就有,它必須通過(guò)傳遞skip_header = True來(lái)跳過(guò)。如果沒(méi)有,TorchText會(huì)認(rèn)為頭部是一個(gè)例子。默認(rèn)情況下,skip_header為False。
fields = [('n', NAME), ('p', PLACE), (None, None), ('s', SAYING)]
train_data, valid_data, test_data = data.TabularDataset.splits(
path = 'data',
train = 'train.tsv',
validation = 'valid.tsv',
test = 'test.tsv',
format = 'tsv',
fields = fields,
skip_header = True
)
print(vars(train_data[0]))
{'n': ['John'], 'p': ['United', 'Kingdom'], 's': ['i', 'love', 'the', 'united', 'kingdom']}
最后,我們將討論csv文件。
這與tsv文件幾乎完全相同,只是格式參數(shù)設(shè)置為“csv”。
fields = [('n', NAME), ('p', PLACE), (None, None), ('s', SAYING)] train_data, valid_data, test_data = data.TabularDataset.splits( path = 'data', train = 'train.csv', validation = 'valid.csv', test = 'test.csv', format = 'csv', fields = fields, skip_header = True )
print(vars(train_data[0]))
{'n': ['John'], 'p': ['United', 'Kingdom'], 's': ['i', 'love', 'the', 'united', 'kingdom']}
為什么JSON好于CSV/TSV?
csv或tsv數(shù)據(jù)無(wú)法存儲(chǔ)列表。這意味著數(shù)據(jù)不能被標(biāo)記化,因此每次運(yùn)行通過(guò)TorchText讀取數(shù)據(jù)的Python腳本時(shí),數(shù)據(jù)都必須被標(biāo)記化。使用高級(jí)的標(biāo)記器,如spaCy標(biāo)記器,需要不可忽略的大量時(shí)間。因此,最好是對(duì)數(shù)據(jù)集進(jìn)行標(biāo)記并以json行格式存儲(chǔ)它們。
如果tsv數(shù)據(jù)中出現(xiàn)制表符,或csv數(shù)據(jù)中出現(xiàn)逗號(hào),TorchText會(huì)認(rèn)為它們是列之間的分隔符。這將導(dǎo)致數(shù)據(jù)被錯(cuò)誤地解析。最糟糕的是,TorchText不會(huì)提醒你這一點(diǎn),因?yàn)樗鼰o(wú)法分辨字段中的制表符/逗號(hào)和作為分隔符的制表符/逗號(hào)之間的區(qū)別。由于json數(shù)據(jù)本質(zhì)上是一個(gè)字典,您可以通過(guò)它的鍵訪問(wèn)字段中的數(shù)據(jù),所以不必?fù)?dān)心“surprise”分隔符。
迭代器
使用上面的任何數(shù)據(jù)集,我們就可以構(gòu)建詞匯表并創(chuàng)建迭代器。
NAME.build_vocab(train_data) SAYING.build_vocab(train_data) PLACE.build_vocab(train_data)
然后,我們可以在定義批處理大小和設(shè)備后,創(chuàng)建迭代器。
默認(rèn)情況下,訓(xùn)練數(shù)據(jù)在每個(gè)epoch進(jìn)行洗牌,但驗(yàn)證/測(cè)試數(shù)據(jù)是排序的。然而,TorchText不知道該用什么來(lái)排序我們的數(shù)據(jù),如果我們不告訴它,它就會(huì)拋出錯(cuò)誤。
有兩種方法來(lái)處理這個(gè)問(wèn)題,你可以通過(guò)傳遞sort = False來(lái)告訴迭代器不要對(duì)驗(yàn)證/測(cè)試數(shù)據(jù)進(jìn)行排序,或者你可以通過(guò)傳遞sort_key來(lái)告訴迭代器如何對(duì)數(shù)據(jù)進(jìn)行排序。sort key是一個(gè)函數(shù),它返回一個(gè)用于對(duì)數(shù)據(jù)進(jìn)行排序的鍵。例如,lambda x: x.s將根據(jù)它們的s屬性(即它們的quote)對(duì)示例進(jìn)行排序。理想情況下,您希望使用sort key,因?yàn)锽ucketIterator將能夠?qū)κ纠M(jìn)行排序,然后最小化每個(gè)批處理中的填充量。
然后,我們可以遍歷迭代器來(lái)獲得批量數(shù)據(jù)。注意,默認(rèn)情況下TorchText的批處理維度是在第二維。
import torch
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
BATCH_SIZE = 1
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits(
(train_data, valid_data, test_data),
sort = False, #don't sort test/validation data
batch_size=BATCH_SIZE,
device=device)
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits(
(train_data, valid_data, test_data),
sort_key = lambda x: x.s, #sort by s attribute (quote)
batch_size=BATCH_SIZE,
device=device)
print('Train:')
for batch in train_iterator:
print(batch)
print('Valid:')
for batch in valid_iterator:
print(batch)
print('Test:')
for batch in test_iterator:
print(batch)
Train: [torchtext.data.batch.Batch of size 1] [.n]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.p]:[torch.cuda.LongTensor of size 2x1 (GPU 0)] [.s]:[torch.cuda.LongTensor of size 5x1 (GPU 0)] [torchtext.data.batch.Batch of size 1] [.n]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.p]:[torch.cuda.LongTensor of size 2x1 (GPU 0)] [.s]:[torch.cuda.LongTensor of size 4x1 (GPU 0)] Valid: [torchtext.data.batch.Batch of size 1] [.n]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.p]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.s]:[torch.cuda.LongTensor of size 2x1 (GPU 0)] [torchtext.data.batch.Batch of size 1] [.n]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.p]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.s]:[torch.cuda.LongTensor of size 4x1 (GPU 0)] Test: [torchtext.data.batch.Batch of size 1] [.n]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.p]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.s]:[torch.cuda.LongTensor of size 3x1 (GPU 0)] [torchtext.data.batch.Batch of size 1] [.n]:[torch.cuda.LongTensor of size 1x1 (GPU 0)] [.p]:[torch.cuda.LongTensor of size 2x1 (GPU 0)] [.s]:[torch.cuda.LongTensor of size 3x1 (GPU 0)]
以上就是torchtext怎么進(jìn)行文本數(shù)據(jù)處理的全部?jī)?nèi)容,希望能給大家一個(gè)參考,也希望大家多多支持W3Cschool。