1. Torchtext 介绍

众所周知,Pytorch是现今非常流形的深度学习框架。而Torchtext是一个非官方的、为Pytorch提供文本数据处理的库。在自然语言处理尤其是RNN、LSTM等模型的应用方面具有重要意义。虽然torchtext主要是为Pytorch提供服务的,但是也可以用于其他框架比如Tensorflow、Keras等。 本文主要内容有:

  • 获取文本数据
  • 使用torchtext建立语料库
  • 使用torchtext建立索引库(word2index; index2word; word2vector)
  • 使用torchtext建立迭代器供深度模型训练使用。

    2. Torchtext的总体流程

    2.1 torchtext主要包含三个组件。

  • Field:主要包含以下数据预处理的配置信息,比如指定分词方法,是否转成小写,起始字符,结束字符,补全字符以及词典等。

  • Dataset:继承自pytorch的Dataset,用于加载数据,通过给TabularDataset 提供路径,格式,Field信息就可以方便的完成数据加载。同时torchtext还提供预先构建的常用数据集的Dataset对象,可以直接加载使用,splits方法可以同时加载训练集,验证集和测试集。
  • Iterator : 主要是数据输出的迭代器,输出batch用来分批次训练模型。


2.2 下载数据集

常用数据集的链接为:数据集。简单注册登录即可下载(train.tsc; test.tsv)。也可以仿照此数据集的格式使用自己已有的数据集。接下来导入数据并查看:

  • 导入数据并查看 ```python

    导入相关包

    import pandas as pd # pandas包,适合导入csv,tsv等数据 import torch from torchtext import data, datasets # 需要从torchtext导入data,datasets from torchtext.vocab import Vectors from torch.nn import init from sklearn.model_selection import train_test_split import jieba # 用来预处理文本(分词等) DEVICE = torch.device(“cuda” if torch.cuda.is_available() else “cpu”) # 选择Gpu或Cpu

查看文件 (./data是存放文件的路径)

train = pd.read_csv(‘./data/train.tsv’, sep=’\t’)
test = pd.read_csv(‘./data/test.tsv’, sep=’\t’) print(train.head(5)) # 查看前5行

  1. ![](https://cdn.nlark.com/yuque/0/2020/webp/606030/1590494610968-e32fc9f2-03f1-4b35-8dc2-a406e8cd8908.webp#align=left&display=inline&height=186&margin=%5Bobject%20Object%5D&originHeight=186&originWidth=522&size=0&status=done&style=none&width=522)
  2. - 划分验证集(下载的数据不包含验证集,可以从训练集划出来一部分当做验证集)。
  3. ```python
  4. # 从训练集划分出测试集
  5. train, val = train_test_split(train, test_size=0.2)
  6. print(len(train))
  7. train.to_csv("./data/train.csv", index=False)
  8. val.to_csv("./data/val.csv", index=False)

2.3 定义Field

Torchtext采用了一种声明式的方法来加载数据:你来告诉Torchtext你希望的数据是什么样子的,剩下的由torchtext来处理。下面是Field包含的一些参数:
sequential:是否把数据表示成序列,如果是False, 不能使用分词 默认值: True.
use_vocab: 是否使用词典对象. 如果是False 数据的类型必须已经是数值类型. 默认值: True.
init_token: 每一条数据的起始字符 默认值: None.
eos_token: 每条数据的结尾字符 默认值: None.
fix_length: 修改每条数据的长度为该值,不够的用pad_token补全. 默认值: None.
tensor_type: 把数据转换成的tensor类型 默认值: torch.LongTensor.
preprocessing:在分词之后和数值化之前使用的管道 默认值: None.
postprocessing: 数值化之后和转化成tensor之前使用的管道默认值: None.
lower: 是否把数据转化为小写 默认值: False.
tokenize: 分词函数. 默认值: str.split.
include_lengths: 是否返回一个已经补全的最小batch的元组和和一个包含每条数据长度的列表 . 默认值: False.
batch_first: Whether to produce tensors with the batch dimension first. 默认值: False.
pad_token: 用于补全的字符. 默认值: “”.
unk_token: 不存在词典里的字符. 默认值: “”.
pad_first: 是否补全第一个字符. 默认值: False.
stop_words: Tokens to discard during the preprocessing step. Default: None
重要的几个方法:
pad(minibatch): 在一个batch对齐每条数据
build_vocab(): 建立词典
numericalize(): 把文本数据数值化,返回tensor

  1. # 定义Field
  2. import jieba
  3. def tokenizer(text): # 可以自己定义分词器,比如jieba分词。也可以在里面添加数据清洗工作
  4. "分词操作,可以用jieba"
  5. return [wd for wd in jieba.cut(text, cut_all=False)]
  6. """
  7. field在默认的情况下都期望一个输入是一组单词的序列,并且将单词映射成整数。
  8. 这个映射被称为vocab。如果一个field已经被数字化了并且不需要被序列化,
  9. 可以将参数设置为use_vocab=False以及sequential=False。
  10. """
  11. # 定义停用词
  12. stopwords = open('./data/stopwords.txt', encoding='utf-8').read().strip().split('\n')
  13. LABEL = data.Field(sequential=False, use_vocab=False)
  14. TEXT = data.Field(sequential=True, tokenize=tokenizer, lower=True, stop_words=stop_words)

2.4 定义Dataset

Dataset可以处理很多格式(tsv,csv,json…)的数据,具体参考datasets。当给定原始数据时,Field知道要做什么。Dataset要告诉Field它需要处理哪些数据。

  1. # 定义Dataset
  2. """
  3. 我们不需要 'PhraseId' 和 'SentenceId'这两列, 所以我们给他们的field传递 None
  4. 如果你的数据有列名,如我们这里的'Phrase','Sentiment',...
  5. 设置skip_header=True,不然它会把列名也当一个数据处理。
  6. 我们需要把 ‘Phrase’,'Sentiment'列按照Field进行处理,即('Phrase', TEXT), ('Sentiment', LABEL)
  7. 注意:fields设置列的顺序要与原数据列的顺序一样。
  8. """
  9. train, val = data.TabularDataset.splits(
  10. path='./data', train='train.csv', validation='val.csv', format='csv', skip_header=True,
  11. fields=[('PhraseId', None), ('SentenceId', None), ('Phrase', TEXT), ('Sentiment', LABEL)]
  12. )
  13. test = data.TabularDataset('./data/test.tsv', format='tsv', skip_header=True,
  14. fields=[('PhraseId', None), ('SentenceId', None), ('Phrase', TEXT)])
  15. # 查看生成的dataset
  16. print(len(train),train[2].Phrase, train[2].Sentiment)
  17. #<torchtext.data.example.Example object at 0x000001B94E965208> (一行是一个example类型)
  18. #['remains', ' ', 'oddly', ' ', 'detached'] 1

2.5 定义Vocab

我们可以看到第6行的输入,它是一个Example对象。Example对象绑定了一行中的所有属性,可以看到,句子已经被分词了,但是没有转化为数字。这是因为我们还没有建立vocab,我们将在下一步建立vocab。

  1. # 建立vocab(不需要加载预训练的词向量)
  2. TEXT.build_vocab(train, val)
  3. LABEL.build_vocab(train, val)
  4. # 建立vocab(加载预训练的词向量,如果路径没有该词向量,会自动下载)
  5. TEXT.build_vocab(train, vectors='./data/glove.6B.100d')#, max_size=30000)
  6. # 当 corpus 中有的 token 在 vectors 中不存在时 的初始化方式.
  7. TEXT.vocab.vectors.unk_init = init.xavier_uniform

现在每个词都对应一个索引值。如果你加载了词向量,你的每个词还会对应一个词向量。在后续RNN或LSTM模型中,可以直接获取该词嵌入向量。如果没加载,现在只有词索引而没有词向量,后续RNN或LSTM会随机生成一个词嵌入矩阵(self.embedding = nn.Embedding(vocab_size, embedding_dim))。这部分后续会介绍。

2.5 构造迭代器

我们日常使用pytorch训练网络时,每次训练都是输入一个batch。torchtext可以构造一个batch的迭代器为模型提供输入。

  1. # 构造迭代器
  2. '''
  3. sort_key指在一个batch内根据文本长度进行排序。
  4. '''
  5. train_iter = data.BucketIterator(train, batch_size=128, sort_key=lambda x: len(x.Phrase),
  6. shuffle=True,device=DEVICE)
  7. val_iter = data.BucketIterator(val, batch_size=128, sort_key=lambda x: len(x.Phrase),
  8. shuffle=True,device=DEVICE)
  9. # 在 test_iter , sort一定要设置成 False, 要不然会被 torchtext 搞乱样本顺序
  10. test_iter = data.Iterator(dataset=test, batch_size=128, train=False,
  11. sort=False, device=DEVICE)
  12. # 查看trainiter一个batch
  13. batch = next(iter(train_iter))
  14. data = batch.Phrase
  15. label = batch.Sentiment
  16. print(data.shape)
  17. print(batch.Phrase)

结果为:

  1. torch.Size([95, 128])
  2. '''
  3. batch大小128,文本长度95(每个batch内的样本长度一样,但是每个batch的文本长度不一样).
  4. 如果在Field设置参数fix_length=100。那么所有数据的文本长度
  5. 都会成100(少的pad,多的截断)。
  6. '''
  7. tensor([[ 18, 494, 19, ..., 12363, 71, 20],
  8. [ 2, 2, 2, ..., 2, 2, 2],
  9. [ 15, 28, 11483, ..., 285, 1273, 796],
  10. ...,
  11. [ 1, 1, 1, ..., 1, 1, 1],
  12. [ 1, 1, 1, ..., 1, 1, 1],
  13. [ 1, 1, 1, ..., 1, 1, 1]], device='cuda:0')

2.6 总体代码

  1. import jieba
  2. import torch
  3. from torchtext import data, datasets
  4. from torchtext.vocab import Vectors
  5. from torch.nn import init
  6. import torch.nn as nn
  7. import torch.nn.functional as F
  8. import torch.optim as optim
  9. import numpy as np
  10. from sklearn.model_selection import train_test_split
  11. import pandas as pd
  12. DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  13. data = pd.read_csv('train.tsv', sep='\t')
  14. test = pd.read_csv('test.tsv', sep='\t')
  15. # create train and validation set
  16. train, val = train_test_split(data, test_size=0.2)
  17. train.to_csv("train.csv", index=False)
  18. val.to_csv("val.csv", index=False)
  19. spacy_en = spacy.load('en')
  20. def tokenizer(text): # create a tokenizer function
  21. return [wd for wd in jieba.cut(text, cut_all=False)]
  22. # Field
  23. TEXT = data.Field(sequential=True, tokenize=tokenizer, lower=True)
  24. LABEL = data.Field(sequential=False, use_vocab=False)
  25. # Dataset
  26. train,val = data.TabularDataset.splits(
  27. path='.', train='train.csv',validation='val.csv', format='csv',skip_header=True,
  28. fields=[('PhraseId',None),('SentenceId',None),('Phrase', TEXT), ('Sentiment', LABEL)])
  29. test = data.TabularDataset('test.tsv', format='tsv',skip_header=True,
  30. fields=[('PhraseId',None),('SentenceId',None),('Phrase', TEXT)])
  31. # build vocab
  32. TEXT.build_vocab(train, vectors='glove.6B.100d')#, max_size=30000)
  33. TEXT.vocab.vectors.unk_init = init.xavier_uniform
  34. # Iterator
  35. train_iter = data.BucketIterator(train, batch_size=128, sort_key=lambda x: len(x.Phrase),
  36. shuffle=True,device=DEVICE)
  37. val_iter = data.BucketIterator(val, batch_size=128, sort_key=lambda x: len(x.Phrase),
  38. shuffle=True,device=DEVICE)
  39. # 在 test_iter , sort一定要设置成 False, 要不然会被 torchtext 搞乱样本顺序
  40. test_iter = data.Iterator(dataset=test, batch_size=128, train=False,
  41. sort=False, device=DEVICE)
  42. """
  43. 由于目的是学习torchtext的使用,所以只定义了一个简单模型
  44. """
  45. len_vocab = len(TEXT.vocab)
  46. class Enet(nn.Module):
  47. def __init__(self):
  48. super(Enet, self).__init__()
  49. self.embedding = nn.Embedding(len_vocab,100)
  50. self.lstm = nn.LSTM(100,128,3,batch_first=True)#,bidirectional=True)
  51. self.linear = nn.Linear(128,5)
  52. def forward(self, x):
  53. batch_size,seq_num = x.shape
  54. vec = self.embedding(x)
  55. out, (hn, cn) = self.lstm(vec)
  56. out = self.linear(out[:,-1,:])
  57. out = F.softmax(out,-1)
  58. return out
  59. model = Enet()
  60. """
  61. 将前面生成的词向量矩阵拷贝到模型的embedding层
  62. 这样就自动的可以将输入的word index转为词向量
  63. 如果没有使用预训练词向量,name就用随机生成的,会跟着模型进行更新
  64. vocab_size是所用词的总数,embedding_dim是预设的词向量维度。
  65. model.embedding = nn.Embedding(vocab_size, embedding_dim)
  66. """
  67. model.embedding.weight.data.copy_(TEXT.vocab.vectors)
  68. model.to(DEVICE)
  69. # 训练
  70. optimizer = optim.Adam(model.parameters())#,lr=0.000001)
  71. n_epoch = 20
  72. best_val_acc = 0
  73. for epoch in range(n_epoch):
  74. for batch_idx, batch in enumerate(train_iter):
  75. data = batch.Phrase
  76. target = batch.Sentiment
  77. target = torch.sparse.torch.eye(5).index_select(dim=0, index=target.cpu().data)
  78. target = target.to(DEVICE)
  79. data = data.permute(1,0)
  80. optimizer.zero_grad()
  81. out = model(data)
  82. loss = -target*torch.log(out)-(1-target)*torch.log(1-out)
  83. loss = loss.sum(-1).mean()
  84. loss.backward()
  85. optimizer.step()
  86. if (batch_idx+1) %200 == 0:
  87. _,y_pre = torch.max(out,-1)
  88. acc = torch.mean((torch.tensor(y_pre == batch.Sentiment,dtype=torch.float)))
  89. print('epoch: %d \t batch_idx : %d \t loss: %.4f \t train acc: %.4f'
  90. %(epoch,batch_idx,loss,acc))
  91. val_accs = []
  92. for batch_idx, batch in enumerate(val_iter):
  93. data = batch.Phrase
  94. target = batch.Sentiment
  95. target = torch.sparse.torch.eye(5).index_select(dim=0, index=target.cpu().data)
  96. target = target.to(DEVICE)
  97. data = data.permute(1,0)
  98. out = model(data)
  99. _,y_pre = torch.max(out,-1)
  100. acc = torch.mean((torch.tensor(y_pre == batch.Sentiment,dtype=torch.float)))
  101. val_accs.append(acc)
  102. acc = np.array(val_accs).mean()
  103. if acc > best_val_acc:
  104. print('val acc : %.4f > %.4f saving model'%(acc,best_val_acc))
  105. torch.save(model.state_dict(), 'params.pkl')
  106. best_val_acc = acc
  107. print('val acc: %.4f'%(acc))