Torchtext 使用方法

Torchtext 使用方法

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 下载数据集 常用数据集的链接为:[数据集](https://www.kaggle.com/c/movie-review-sentiment-analysis-kernels-only/data)。简单注册登录即可下载(train.tsc; test.tsv)。也可以仿照此数据集的格式使用自己已有的数据集。接下来导入数据并查看:
  • 导入数据并查看
## 导入相关包
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行


Torchtext 使用方法_第1张图片

  • 划分验证集(下载的数据不包含验证集,可以从训练集划出来一部分当做验证集)。
# 从训练集划分出测试集

train, val = train_test_split(train, test_size=0.2)
print(len(train))
train.to_csv("./data/train.csv", index=False)
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

# 定义Field
import jieba

def tokenizer(text):    # 可以自己定义分词器,比如jieba分词。也可以在里面添加数据清洗工作
    "分词操作,可以用jieba"
    return [wd for wd in jieba.cut(text, cut_all=False)]

"""
field在默认的情况下都期望一个输入是一组单词的序列,并且将单词映射成整数。
这个映射被称为vocab。如果一个field已经被数字化了并且不需要被序列化,
可以将参数设置为use_vocab=False以及sequential=False。
"""
# 定义停用词
stopwords = open('./data/stopwords.txt', encoding='utf-8').read().strip().split('\n')

LABEL = data.Field(sequential=False, use_vocab=False)

TEXT = data.Field(sequential=True, tokenize=tokenizer, lower=True, stop_words=stop_words)

2.4 定义Dataset

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

# 定义Dataset
"""
我们不需要 'PhraseId' 和 'SentenceId'这两列, 所以我们给他们的field传递 None
如果你的数据有列名,如我们这里的'Phrase','Sentiment',...
设置skip_header=True,不然它会把列名也当一个数据处理。
我们需要把 ‘Phrase’,'Sentiment'列按照Field进行处理,即('Phrase', TEXT), ('Sentiment', LABEL)
注意:fields设置列的顺序要与原数据列的顺序一样。
"""

train, val = data.TabularDataset.splits(
    path='./data', train='train.csv', validation='val.csv', format='csv', skip_header=True,
    fields=[('PhraseId', None), ('SentenceId', None), ('Phrase', TEXT), ('Sentiment', LABEL)]
)

test = data.TabularDataset('./data/test.tsv', format='tsv', skip_header=True, 
                           fields=[('PhraseId', None), ('SentenceId', None), ('Phrase', TEXT)])

# 查看生成的dataset
print(len(train),train[2].Phrase, train[2].Sentiment)

# (一行是一个example类型)
#['remains', ' ', 'oddly', ' ', 'detached'] 1



2.5 定义Vocab

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

# 建立vocab(不需要加载预训练的词向量)
TEXT.build_vocab(train, val)
LABEL.build_vocab(train, val)

# 建立vocab(加载预训练的词向量,如果路径没有该词向量,会自动下载)
TEXT.build_vocab(train, vectors='./data/glove.6B.100d')#, max_size=30000)
# 当 corpus 中有的 token 在 vectors 中不存在时 的初始化方式.
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的迭代器为模型提供输入。

# 构造迭代器
'''
sort_key指在一个batch内根据文本长度进行排序。
'''
train_iter = data.BucketIterator(train, batch_size=128, sort_key=lambda x: len(x.Phrase), 
                                 shuffle=True,device=DEVICE)

val_iter = data.BucketIterator(val, batch_size=128, sort_key=lambda x: len(x.Phrase), 
                                 shuffle=True,device=DEVICE)

# 在 test_iter , sort一定要设置成 False, 要不然会被 torchtext 搞乱样本顺序
test_iter = data.Iterator(dataset=test, batch_size=128, train=False,
                          sort=False, device=DEVICE)

# 查看trainiter一个batch
batch = next(iter(train_iter))
data = batch.Phrase
label = batch.Sentiment
print(data.shape)
print(batch.Phrase)

结果为:

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

2.6 总体代码

import jieba
import torch
from torchtext import data, datasets
from torchtext.vocab import Vectors
from torch.nn import init
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
from sklearn.model_selection import train_test_split
import pandas as pd

DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")

data = pd.read_csv('train.tsv', sep='\t')
test = pd.read_csv('test.tsv', sep='\t')

# create train and validation set 
train, val = train_test_split(data, test_size=0.2)
train.to_csv("train.csv", index=False)
val.to_csv("val.csv", index=False)

spacy_en = spacy.load('en')

def tokenizer(text): # create a tokenizer function
    return [wd for wd in jieba.cut(text, cut_all=False)]
# Field
TEXT = data.Field(sequential=True, tokenize=tokenizer, lower=True)
LABEL = data.Field(sequential=False, use_vocab=False)

# Dataset
train,val = data.TabularDataset.splits(
        path='.', train='train.csv',validation='val.csv', format='csv',skip_header=True,
        fields=[('PhraseId',None),('SentenceId',None),('Phrase', TEXT), ('Sentiment', LABEL)])

test = data.TabularDataset('test.tsv', format='tsv',skip_header=True,
        fields=[('PhraseId',None),('SentenceId',None),('Phrase', TEXT)])
# build vocab
TEXT.build_vocab(train, vectors='glove.6B.100d')#, max_size=30000)
TEXT.vocab.vectors.unk_init = init.xavier_uniform

# Iterator
train_iter = data.BucketIterator(train, batch_size=128, sort_key=lambda x: len(x.Phrase), 
                                 shuffle=True,device=DEVICE)

val_iter = data.BucketIterator(val, batch_size=128, sort_key=lambda x: len(x.Phrase), 
                                 shuffle=True,device=DEVICE)

# 在 test_iter , sort一定要设置成 False, 要不然会被 torchtext 搞乱样本顺序
test_iter = data.Iterator(dataset=test, batch_size=128, train=False,
                          sort=False, device=DEVICE)
"""
由于目的是学习torchtext的使用,所以只定义了一个简单模型
"""
len_vocab = len(TEXT.vocab)

class Enet(nn.Module):
    def __init__(self):
        super(Enet, self).__init__()
        self.embedding = nn.Embedding(len_vocab,100)
        self.lstm = nn.LSTM(100,128,3,batch_first=True)#,bidirectional=True)
        self.linear = nn.Linear(128,5)
        
    def forward(self, x):
        batch_size,seq_num = x.shape
        vec = self.embedding(x)
        out, (hn, cn) = self.lstm(vec)
        out = self.linear(out[:,-1,:])
        out = F.softmax(out,-1)
        return out


model = Enet()
"""
将前面生成的词向量矩阵拷贝到模型的embedding层
这样就自动的可以将输入的word index转为词向量
如果没有使用预训练词向量,name就用随机生成的,会跟着模型进行更新
vocab_size是所用词的总数,embedding_dim是预设的词向量维度。
model.embedding = nn.Embedding(vocab_size, embedding_dim)
"""
model.embedding.weight.data.copy_(TEXT.vocab.vectors)   
model.to(DEVICE)




# 训练
optimizer = optim.Adam(model.parameters())#,lr=0.000001)

n_epoch = 20

best_val_acc = 0

for epoch in range(n_epoch):

    for batch_idx, batch in enumerate(train_iter):
        data = batch.Phrase
        target = batch.Sentiment
        target = torch.sparse.torch.eye(5).index_select(dim=0, index=target.cpu().data)
        target = target.to(DEVICE)
        data = data.permute(1,0)
        optimizer.zero_grad()

        out = model(data)
        loss = -target*torch.log(out)-(1-target)*torch.log(1-out)
        loss = loss.sum(-1).mean()

        loss.backward()
        optimizer.step()

        if (batch_idx+1) %200 == 0:
            _,y_pre = torch.max(out,-1)
            acc = torch.mean((torch.tensor(y_pre == batch.Sentiment,dtype=torch.float)))
            print('epoch: %d \t batch_idx : %d \t loss: %.4f \t train acc: %.4f'
                  %(epoch,batch_idx,loss,acc))
    
    val_accs = []
    for batch_idx, batch in enumerate(val_iter):
        data = batch.Phrase
        target = batch.Sentiment
        target = torch.sparse.torch.eye(5).index_select(dim=0, index=target.cpu().data)
        target = target.to(DEVICE)
        data = data.permute(1,0)
        out = model(data)
        
        _,y_pre = torch.max(out,-1)
        acc = torch.mean((torch.tensor(y_pre == batch.Sentiment,dtype=torch.float)))
        val_accs.append(acc)
    
    acc = np.array(val_accs).mean()
    if acc > best_val_acc:
        print('val acc : %.4f > %.4f saving model'%(acc,best_val_acc))
        torch.save(model.state_dict(), 'params.pkl')
        best_val_acc = acc
    print('val acc: %.4f'%(acc))

你可能感兴趣的:(NLP,pytorch,深度学习,Pytorch,torchtext)