使用pytorch和torchtext进行文本分类的实例


Posted in Python onJanuary 08, 2020

文本分类是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 [toke.text for toke in spacy_en.tokenizer(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',
      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.6B.300d.txt', cache='.vector_cache')
REVIEW.build_vocab(train_data, # 建词表是用训练集建,不要用验证集和测试集
     max_size=400000, # 单词表容量
     vectors='glove.6B.300d', # 还有'glove.840B.300d'已经很多可以选
     unk_init=torch.Tensor.normal_ # 初始化train_data中不存在预训练词向量词表中的单词
)

# print(REVIEW.vocab.freqs.most_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
model.embedding.weight.data.copy_(pretrained_embeddings)
UNK_IDX = REVIEW.vocab.stoi[REVIEW.unk_token]
PAD_IDX = REVIEW.vocab.stoi[REVIEW.pad_token]
# 因为预训练的权重的unk和pad的词向量不是在我们的数据集语料上训练得到的,所以最好置零
model.embedding.weight.data[UNK_IDX] = torch.zeros(EMBEDDING_DIM)
model.embedding.weight.data[PAD_IDX] = torch.zeros(EMBEDDING_DIM)

然后用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:len(x.review),
            device=torch.device('cpu'))

最后就是加载数据喂给模型了。

for batch in train_iterator:
 # 因为REVIEW Field的inclue_lengths为True,所以还会包含一个句子长度的Tensor
 review, review_len = batch.review 
 # 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情感分类器

下面是我简略写的一个模型,仅供参考

import torch.nn as nn
import torch.nn.functional as F
from torch.nn.utils.rnn 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.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx=pad_idx)
  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.Parameter(torch.zeros((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 = self.embedding(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

 model.train()
 for batch in iterator:
  optimizer.zero_grad()
  review, review_lengths = batch.review
  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:len(x.review),
   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进行文本分类的实例就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python collections模块实例讲解
Apr 07 Python
Python警察与小偷的实现之一客户端与服务端通信实例
Oct 09 Python
Python cookbook(数据结构与算法)从任意长度的可迭代对象中分解元素操作示例
Feb 13 Python
Python实现string字符串连接的方法总结【8种方式】
Jul 06 Python
配置 Pycharm 默认 Test runner 的图文教程
Nov 30 Python
python 函数中的内置函数及用法详解
Jul 02 Python
python3发送邮件需要经过代理服务器的示例代码
Jul 25 Python
Python字符串中添加、插入特定字符的方法
Sep 10 Python
详解Python3.8+PyQt5+pyqt5-tools+Pycharm配置详细教程
Nov 02 Python
Python的信号库Blinker用法详解
Dec 31 Python
Python爬虫之自动爬取某车之家各车销售数据
Jun 02 Python
python程序的组织结构详解
Dec 06 Python
python爬虫爬取监控教务系统的思路详解
Jan 08 #Python
Pytorch实现基于CharRNN的文本分类与生成示例
Jan 08 #Python
python实现单目标、多目标、多尺度、自定义特征的KCF跟踪算法(实例代码)
Jan 08 #Python
Pytorch实现神经网络的分类方式
Jan 08 #Python
python 爬取古诗文存入mysql数据库的方法
Jan 08 #Python
基于python3抓取pinpoint应用信息入库
Jan 08 #Python
Python PyInstaller安装和使用教程详解
Jan 08 #Python
You might like
把PHP安装为Apache DSO
2006/10/09 PHP
不用数据库的多用户文件自由上传投票系统(1)
2006/10/09 PHP
php网页后退不再出现过期
2007/03/08 PHP
PHP目录与文件操作技巧总结(创建,删除,遍历,读写,修改等)
2016/09/11 PHP
PHP获取当前执行php文件名的代码
2017/03/02 PHP
Ubuntu 16.04中Laravel5.4升级到5.6的步骤
2018/12/07 PHP
jQuery中的pushStack实现原理和应用实例
2015/02/03 Javascript
JavaScript使用concat连接数组的方法
2015/04/06 Javascript
jQuery关键词说明插件cluetip使用指南
2015/04/21 Javascript
jQuery基于扩展简单实现倒计时功能的方法
2016/05/14 Javascript
jquery实现文字单行横移或翻转(上下、左右跳转)
2017/01/08 Javascript
JS简单获取当前日期和农历日期的方法
2017/04/17 Javascript
详谈AngularJs 控制器、数据绑定、作用域
2017/07/09 Javascript
React Native中Mobx的使用方法详解
2018/12/04 Javascript
Vue CLI 2.x搭建vue(目录最全分析)
2019/02/27 Javascript
Python(Tornado)模拟登录小米抢手机
2013/11/12 Python
python解决Fedora解压zip时中文乱码的方法
2016/09/18 Python
分分钟入门python语言
2018/03/20 Python
python 定时任务去检测服务器端口是否通的实例
2019/01/26 Python
python定义类self用法实例解析
2020/01/22 Python
浅谈Python的方法解析顺序(MRO)
2020/03/05 Python
详解Ubuntu环境下部署Django+uwsgi+nginx总结
2020/04/02 Python
浅谈Python中threading join和setDaemon用法及区别说明
2020/05/02 Python
Python实现Excel自动分组合并单元格
2021/02/22 Python
一份全面的PHP面试问题考卷
2012/07/15 面试题
Does C# support multiple inheritance? (C#支持多重继承吗)
2012/01/04 面试题
几个Linux面试题笔试题
2016/08/01 面试题
人力资源专员自我评价怎么写
2013/09/19 职场文书
计算机专业大学生的自我评价
2013/11/14 职场文书
股份转让协议书
2014/04/12 职场文书
2015年少先队活动总结
2015/03/25 职场文书
2015暑期爱心支教策划书
2015/07/14 职场文书
高三教师工作总结2015
2015/07/21 职场文书
利用python Pandas实现批量拆分Excel与合并Excel
2021/05/23 Python
关于maven依赖 ${xxx.version}报错问题
2022/01/18 Java/Android
python双向链表实例详解
2022/05/25 Python