使⽤pytorch和torchtext进⾏⽂本分类的实例
⽂本分类是NLP领域的较为容易的⼊门问题,本⽂记录我⾃⼰在做⽂本分类任务以及复现相关论⽂时的基本流程,绝⼤部分操作都使⽤了torch和torchtext两个库。
1. ⽂本数据预处理
⾸先数据存储在三个csv⽂件中,分别是train.csv,valid.csv,test.csv,第⼀列存储的是⽂本数据,例如情感分类问题经常是⽤户的评论review,例如imdb或者amazon数据集。第⼆列是情感极性polarity,N分类问题的话就有N个值,假设值得范围是0~N-1。
下⾯是很常见的⽂本预处理流程,英⽂⽂本的话不需要分词,直接按空格split就⾏了,这⾥只会主要说说第4点。
1、去除⾮⽂本部分
2、分词
3、去除停⽤词
4、对英⽂单词进⾏词⼲提取(stemming)和词型还原(lemmatization)
5、转为⼩写
6、特征处理
Bag of Words
Tf-idf
N-gram
Word2vec
词⼲提取和词型还原
from nltk.stem import SnowballStemmer
stemmer = SnowballStemmer("english") # 选择语⾔
from nltk.stem import WordNetLemmatizer
wnl = WordNetLemmatizer()
SnowballStemmer较为激进,转换有可能出现错误,这⾥较为推荐使⽤WordNetLemmatizer,它⼀般只在⾮常肯定的情况下才进⾏转换,否则会返回原来的单词。
stemmer.stem('knives')
# knive
wnl.lemmatize('knives')
# knife
因为我没有系统学习和研究过NLTK的代码,所以就不多说了,有兴趣的可以⾃⼰去阅读NLTK的源码。
2. 使⽤torchtext加载⽂本数据
本节主要是⽤的模块是torchtext⾥的data模块,处理的数据同上⼀节所描述。
⾸先定义⼀个tokenizer⽤来处理⽂本,⽐如分词,⼩写化,如果你已经根据上⼀节的词⼲提取和词型还原的⽅法处理过⽂本⾥的每⼀个单词后可以直接分词就够了。
tokenize = lambda x: x.split()
或者也可以更保险点,使⽤spacy库,不过就肯定更耗费时间了。
import spacy
spacy_en = spacy.load('en')
def tokenizer(text):
return [ for toke in kenizer(text)]
然后要定义Field,⾄于Field是啥,你可以简单地把它理解为⼀个能够加载、预处理和存储⽂本数据和标签的对象。我们可以⽤它根据训练数据来建⽴词表,加载预训练的Glove词向量等等。
def DataLoader():
tokenize = lambda x: x.split()
# ⽤户评论,include_lengths设为True是为了⽅便之后使⽤torch的pack_padded_sequence
REVIEW = data.Field(sequential=True,tokenize=tokenize, include_lengths=True)
# 情感极性
POLARITY = data.LabelField(sequential=False, use_vocab=False, dtype = torch.long)
# 假如train.csv⽂件并不是只有两列,⽐如1、3列是review和polarity,2列是我们不需要的数据,
# 那么就要添加⼀个全是None的元组, fields列表存储的Field的顺序必须和csv⽂件中每⼀列的顺序对应,
# 否则review可能就加载到polarity Field⾥去了
fields = [('review', REVIEW), (None, None), ('polarity', POLARITY)]
# 加载train,valid,test数据
train_data, valid_data, test_data = data.TabularDataset.splits(
path = 'amazon',
train = 'train.csv',
validation = 'valid.csv',
sort of torch翻译test = 'test.csv',
format = 'csv',
fields = fields,
skip_header = False # 是否跳过⽂件的第⼀⾏
)
return REVIEW, POLARITY, train_data
加载完数据可以开始建词表。如果本地没有预训练的词向量⽂件,在运⾏下⾯的代码时会⾃动下载到当前⽂件夹下
的'.vector_cache'⽂件夹内,如果本地已经下好了,可以⽤Vectors指定⽂件名name,路径cache,还可以使⽤Glove。
from torchtext.vocab import Vectors, Glove
import torch
REVIEW, POLARITY, train_data = DataLoader()
# vectors = Vectors(name='glove.', cache='.vector_cache')
REVIEW.build_vocab(train_data, # 建词表是⽤训练集建,不要⽤验证集和测试集
max_size=400000, # 单词表容量
vectors='glove.6B.300d', # 还有'glove.840B.300d'已经很多可以选
unk_init=al_ # 初始化train_data中不存在预训练词向量词表中的单词
)
# print(REVIEW.st_common(20)) 数据集⾥最常出现的20个单词
# print(REVIEW.vocab.itos[:10])  列表 index to word
# print(REVIEW.vocab.stoi)    字典 word to index
接着就是把预训练词向量加载到model的embedding weight⾥去了。
pretrained_embeddings = REVIEW.vocab.vectors
UNK_IDX = REVIEW.vocab.stoi[REVIEW.unk_token]
PAD_IDX = REVIEW.vocab.stoi[REVIEW.pad_token]
# 因为预训练的权重的unk和pad的词向量不是在我们的数据集语料上训练得到的,所以最好置零
然后⽤torchtext的迭代器来批量加载数据,torchtext.data⾥的BucketIterator⾮常好⽤,它可以把长度相近的⽂本数据尽量都放到⼀个batch⾥,这样最⼤程度地减少padding,数据就少了很多⽆意义的0,也减少了矩阵计算量,也许还能对最终准确度有帮助(误)?我凭直觉猜的,没有做实验对⽐过,但是⾄少能加速训练迭代应该是没有疑问的,如果哪天我有钱了买了台好点的服务器做完实验再来补充。
sort_within_batch设为True的话,⼀个batch内的数据就会按sort_key的排列规则降序排列,sort_key是排列的规则,这⾥使⽤的是review的长度,即每条⽤户评论所包含的单词数量。
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits(
(train_data, valid_data, test_data),
batch_size=32,
sort_within_batch=True,
sort_key = lambda x:view),
device=torch.device('cpu'))
最后就是加载数据喂给模型了。
for batch in train_iterator:
# 因为REVIEW Field的inclue_lengths为True,所以还会包含⼀个句⼦长度的Tensor
review, review_len = view
# review.size = (seq_length, batch_size) , review_len.size = (batch_size, )
polarity = batch.polarity
# polarity.size = (batch_size, )
predictions = model(review, review_lengths)
loss = criterion(predictions, polarity) # criterion = nn.CrossEntropyLoss()
3. 使⽤pytorch写⼀个LSTM情感分类器
下⾯是我简略写的⼀个模型,仅供参考
as nn
functional as F
import pack_padded_sequence
import torch
class LSTM(nn.Module):
def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim,
n_layers, bidirectional, dropout, pad_idx):
super(LSTM, self).__init__()
self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers=n_layers,
bidirectional=bidirectional, dropout=dropout)
self.Ws = nn.Parameter(torch.Tensor(hidden_dim, output_dim))
self.bs = nn.s((output_dim, )))
nn.init.uniform_(self.Ws, -0.1, 0.1)
nn.init.uniform_(self.bs, -0.1, 0.1)
self.dropout = nn.Dropout(p=0.5)
def forward(self, x, x_len):
x = bedding(x)
x = pack_padded_sequence(x, x_len)
H, (h_n, c_n) = self.lstm(x)
h_n = self.dropout(h_n)
h_n = torch.squeeze(h_n)
res = torch.matmul(h_n, self.Ws) + self.bs
y = F.softmax(res, dim=1)
# y.size(batch_size, output_dim)
return y
训练函数
def train(model, iterator, optimizer, criterion):
epoch_loss = 0
num_sample = 0
correct = 0
for batch in iterator:
<_grad()
review, review_lengths = view
polarity = batch.polarity
predictions = model(review, review_lengths)
correct += torch.sum(torch.argmax(preds, dim=1) == polarity)
loss = criterion(predictions, polarity)
loss.backward()
epoch_loss += loss.item()
num_sample += len(batch)
optimizer.step()
return epoch_loss / num_sample, correct.float() / num_sample
if __name__ == '__main__':
for epoch in range(N_EPOCHS):
train_loss, acc = train(model, train_iter, optimizer, criterion)
print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {acc* 100:.2f}%')
注意事项和遇到的⼀些坑
⽂本情感分类需不需要去除停⽤词?
应该是不⽤的,否则acc有可能下降。
data.TabularDataset.splits虽然好⽤,但是如果你只想加载训练集,这时候如果直接不给validation和test参数赋值,那么其他代码和原来⼀样,⽐如这样
train_data = data.TabularDataset.splits(
path = '',
train = 'train.csv',
format = 'csv',
fields = fields,
skip_header = False # 是否跳过⽂件的第⼀⾏
)
那么底下你⼀定会报错,因为data.TabularDataset.splits返回的是⼀个元组,也就是如果是训练验证测试三个⽂件都给了函数,就返回(train_data, valid_data, test_data),这时候你⽤三个变量去接受函数返
回值当然没问题,元组会⾃动拆包。
当只给函数⼀个⽂件train.csv时,函数返回的是(train_data)⽽⾮train_data,因此正确的写法应该如下
train_data = data.TabularDataset.splits(
path = '',
train = 'train.csv',
format = 'csv',
fields = fields,
skip_header = False # 是否跳过⽂件的第⼀⾏
)[0] # 注意这⾥的切⽚,选择元组的第⼀个也是唯⼀⼀个元素赋给train_data
同理data.BucketIterator.splits也有相同的问题,它不但返回的是元组,它的参数datasets要求也是以元组形式,即(train_data, valid_data, test_data)进⾏赋值,否则在下⾯的运⾏中也会出现各种各样奇怪的问题。
如果你要⽣成两个及以上的迭代器,那么没问题,直接照上⾯写就完事了。
如果你只要⽣成train_iterator,那么正确的写法应该是下⾯这样
train_iter = data.BucketIterator(
train_data,
batch_size=32,
sort_key=lambda x:view),
sort_within_batch=True,
shuffle=True # 训练集需要shuffle,但因为验证测试集不需要
# 可以⽣成验证和测试集的迭代器直接⽤data.iterator.Iterator类就⾜够了
)
出现的问题 x = pack_padded_sequence(x, x_len) 当数据集有长度为0的句⼦时, 就会后⾯报错
Adagrad效果⽐Adam好的多
4. 总结
不仅仅是NLP领域,在各⼤顶会中,越来越多的学者选择使⽤Pytorch⽽⾮TensorFlow,主要原因就是因为它的易⽤
性,torchtext和pytorch搭配起来是⾮常⽅便的NLP⼯具,可以⼤⼤缩短⽂本预处理,加载数据的时间。
我本⼈之前⽤过tf 1.x以及keras,最终拥抱了Pytorch,也是因为它与Numpy极其类似的⽤法,更Pythonic的代码,清晰的源码让我在遇到bug时能⼀步⼀步到问题所在,动态图让⼈能随时看到输出的Tensor的全部信息,这些都是Pytorch的优势。
现在tf 2.0也在不断改进,有⼈笑称tf越来越像pytorch了,其实pytorch也在不断向tf学习,在⼯业界,tf仍然处于王者地位,不知道未来pytorch能不能在⼯业界也与tf平分秋⾊,甚⾄更胜⼀筹呢?
以上这篇使⽤pytorch和torchtext进⾏⽂本分类的实例就是⼩编分享给⼤家的全部内容了,希望能给⼤家⼀个参考,也希望⼤家多多⽀持。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。