使用PyTorch建立你的第一个文本分类模型
概述
- 学习如何使用PyTorch执行文本分类
- 理解解决文本分类时所涉及的要点
- 学习使用包填充(Pack Padding)特性
介绍
我总是使用最先进的架构来在一些比赛提交模型结果。得益于PyTorch、Keras和TensorFlow等深度学习框架,实现最先进的体系结构变得非常容易。这些框架提供了一种简单的方法来实现复杂的模型体系结构和算法,而只需要很少的概念知识和代码技能。简而言之,它们是数据科学社区的一座金矿!
在本文中,我们将使用PyTorch,它以其快速的计算能力而闻名。因此,在本文中,我们将介绍解决文本分类问题的关键点。然后我们将在PyTorch中实现第一个文本分类器!
目录
- 为什么使用PyTorch进行文本分类?
- 处理词汇表外单词
- 处理可变长度序列
- 包装器和预训练模型
- 理解问题
- 实现文本分类
为什么使用PyTorch进行文本分类?
在深入研究技术概念之前,让我们先快速熟悉一下将要使用的框架——PyTorch。PyTorch的基本单位是张量,类似于python中的“numpy”数组。使用PyTorch有很多好处,但最重要的两个是:
- 动态网络——运行时架构的变化
- 跨gpu的分布式训练
我敢肯定你想知道——为什么我们要使用PyTorch来处理文本数据?让我们讨论一下PyTorch的一些令人难以置信的特性,这些特性使它不同于其他框架,特别是在处理文本数据时。
1. 处理词汇表外单词
文本分类模型根据固定的词汇量进行训练。但在推理过程中,我们可能会遇到一些词汇表中没有的词。这些词汇被称为词汇量外单词(Out of Vocabulary),大多数深度学习框架缺乏处理词汇量不足的能力。这是一个关键的问题,甚至可能导致信息的丢失。
为了处理词汇量不足的单词,PyTorch支持一个很好的功能,它用未知的token替换训练数据中的稀有单词。这反过来又帮助我们解决了词汇量不足的问题。
除了处理词汇之外,PyTorch还有一个可以处理可变长度序列的特性!
2. 处理可变长度序列
你听说过循环神经网络是如何处理可变长度序列的吗?有没有想过如何实现它?PyTorch提供了一个有用的特性“填充序列”(Packed Padding sequence),它实现了动态循环神经网络。
填充是在句首或句尾添加一个称为填充标记的额外标记的过程。由于每个句子中的单词数量不同,我们通过添加填充标记将可变长度的输入句子转换为具有相同长度的句子。
填充是必须的,因为大多数框架支持静态网络,即架构在整个模型训练过程中保持不变。虽然填充解决了可变长度序列的问题,但是这种思想还有另一个问题——体系结构现在像处理任何其他信息/数据一样处理这些填充标记。让我用一个简单的图表来解释一下
正如你在下图中所看到的,在生成输出时还使用了最后一个元素,即padding标记。这是由PyTorch中的填充序列来处理的。
压缩填充会对填充标记忽略输入时间步。这些值不输入给循环神经网络,这帮助我们建立动态循环神经网络。
3.包装器和预训练模型
最新的模型架构状态正在为PyTorch框架发布。Hugging Face发布Transformers,其中提供超过32个自然语言理解生成的最新架构!
不仅如此,PyTorch还为文本到语音、对象检测等任务提供了预训练模型,这些任务可以在几行代码内执行。
不可思议,不是吗?这些是PyTorch的一些非常有用的特性。现在让我们使用PyTorch解决一个文本分类问题。
理解问题陈述
作为本文的一部分,我们将研究一个非常有趣的问题。
Quora希望在他们的平台上追踪不真诚的问题,以便让用户在分享知识的同时感到安全。在这种情况下,一个不真诚的问题被定义为一个旨在发表声明的问题,而不是寻找有用的答案。为了进一步分析这个问题,这里有一些特征可以表明一个特定的问题是不真诚的:
- 语气非中性
- 是贬低还是煽动性的
- 没有现实根据
- 使用性内容(乱伦、兽交、恋童癖)来达到令人震惊的效果,而不是寻求真正的答案
训练数据包括被询问的问题,以及一个表示是否被识别为不真诚的标记(target = 1)。标签包含一些噪音,即它们不能保证是完美的。我们的任务是识别某个问题是否“不真诚”。你可以从这里下载数据集。
https://drive.google.com/file/d/1fcip8PgsrX7m4AFgvUPLaac5pZ79mpwX/view?usp=drive_open
现在是使用PyTorch编写我们自己的文本分类模型的时候了。
实现文本分类
让我们首先导入构建模型所需的所有必要库。下面是我们将使用的包/库的简要概述
- Torch包用于定义张量和张量上的数学运算
- torchtext是PyTorch中的一个自然语言处理(NLP)库。这个库包含预处理文本的脚本和一些流行的NLP数据集的源。
#导入库
import torch
#处理数据
from torchtext import data
为了使结果可重复,我指定了种子值。由于深度学习模型在执行时由于其随机性可能会产生不同的结果,因此指定种子值是很重要的。
#产生同样的结果
SEED = 2019
#Torch
torch.manual_seed(SEED)
#Cuda 算法
torch.backends.cudnn.deterministic = True
预处理数据:
现在,让我们看看如何使用字段对象对文本进行预处理。字段对象有两种不同的类型——field和LabelField。让我们快速了解一下两者之间的区别
- field:数据模块中的字段对象用于为数据集中的每一列指定预处理步骤。
- LabelField: LabelField对象是Field对象的一个特例,它只用于分类任务。它的惟一用途是默认将unk_token和sequential设置为None。
在我们使用field之前,让我们看看field的不同参数和它们的用途。
field的参数:
- Tokenize:指定标记句子的方法,即将句子分词。我正在使用spacy分词器,因为它使用了新的分词算法
- Lower:将文本转换为小写
batch_first:输入和输出的第一个维度总是批处理大小
接下来,我们将创建一个元组列表,其中每个元组中的第一个值包含一个列名,第二个值是上面定义的字段对象。此外,我们将按照csv列的顺序排列每个元组,并指定为(None,None)以忽略csv文件中的列。
让我们只读需要的列-问题和标签
fields = [(None, None), ('text',TEXT),('label', LABEL)]
在下面的代码块中,我通过定义字段对象加载了自定义数据集。
#载入自定义数据集
training_data=data.TabularDataset(path = 'quora.csv',format = 'csv',fields = fields,skip_header = True)
print(vars(training_data.examples[0]))
现在,让我们将数据集分为训练和验证数据
import random
train_data, valid_data = training_data.split(split_ratio=0.3, random_state = random.seed(SEED))
准备输入和输出序列:
下一步是为文本构建词汇表,并将它们转换为整数序列。词汇表包含了整篇文章中出现的词汇。每个唯一的单词都有一个索引。下面列出了相同的参数
参数:
- min_freq:忽略词汇表中频率小于指定频率的单词,并将其映射到未知标记。
- 两个特殊的标记(称为unknown和padding)将被添加到词汇表中
- unknown标记用于处理词汇表中的单词
- padding标记用于生成相同长度的输入序列
让我们构建词汇表,并使用预训练好的嵌入来初始化单词。如果希望随机初始化嵌入,请忽略vectors参数。
#初始化glove embeddings
TEXT.build_vocab(train_data,min_freq=3,vectors = "glove.6B.100d")
LABEL.build_vocab(train_data)
print("Size of TEXT vocabulary:",len(TEXT.vocab))
print("Size of LABEL vocabulary:",len(LABEL.vocab))
print(TEXT.vocab.freqs.most_common(10))
print(TEXT.vocab.stoi)
现在我们准备批训练模型。BucketIterator以需要最小填充量的方式形成批。
#检查cuda是否可用
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#设置batch大小
BATCH_SIZE = 64
#载入迭代器
train_iterator, valid_iterator = data.BucketIterator.splits(
(train_data, valid_data),
batch_size = BATCH_SIZE,
sort_key = lambda x: len(x.text),
sort_within_batch=True,
device = device)
模型架构
现在是定义体系结构来解决二分类问题的时候了。torch中的神经网络模块是所有模型的基础模型。这意味着每个模型都必须是nn模块的子类。
我在这里定义了两个函数:init和forward。让我来解释一下这两个函数的用例
- Init:每当创建类的实例时,都会自动调用Init函数。因此,它被称为构造函数。传递给类的参数由构造函数初始化。我们将定义将在模型中使用的所有层
- Forward: Forward函数定义输入的前向传播。
最后,让我们详细了解用于构建体系结构的不同层及其参数
嵌入层:嵌入对于任何与NLP相关的任务都是非常重要的,因为它以向量格式表示一个单词。嵌入层创建一个查找表,其中每一行表示一个单词的嵌入。嵌入层将整数序列转换成向量表示。这里是嵌入层两个最重要的参数-
- num_embeddings:字典中的单词数量
- embedding_dim:单词的维度
LSTM: LSTM是RNN的一个变体,能够捕获长期依赖项。遵循你应该熟悉的LSTM的一些重要参数。以下是这一层的参数:
- input_size:输入的维度
- hidden_size:隐藏节点的数量
- num_layers:要堆叠的层数
- batch_first:如果为真,则输入和输出张量以(batch, seq, feature)的形式提供。
- dropout:如果非零,则在除最后一层外的每一LSTM层的输出上引入一个dropout层,dropout概率等于dropout。默认值:0
- bidirection:如果为真,则引入双向LSTM
线性层:线性层是指Dense层。这里的两个重要参数如下:
- in_features:输入的特征数量
- out_features:隐藏层的节点数量
包填充:如前所述,包填充用于定义动态循环神经网络。如果没有填充包,填充输入也由rnn处理,并返回填充元素的隐状态。这是一个非常棒的包装器,它不显示填充的输入。它只是忽略这些值并返回未填充元素的隐藏状态。
现在我们已经很好地理解了架构的所有块,让我们来看代码!
我将从定义架构的所有层开始:
import torch.nn as nn
class classifier(nn.Module):
#定义所有层
def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim, n_layers,
bidirectional, dropout):
super().__init__()
#embedding 层
self.embedding = nn.Embedding(vocab_size, embedding_dim)
#lstm 层
self.lstm = nn.LSTM(embedding_dim,
hidden_dim,
num_layers=n_layers,
bidirectional=bidirectional,
dropout=dropout,
batch_first=True)
#全连接层
self.fc = nn.Linear(hidden_dim * 2, output_dim)
#激活函数
self.act = nn.Sigmoid()
def forward(self, text, text_lengths):
#text = [batch size,sent_length]
embedded = self.embedding(text)
#embedded = [batch size, sent_len, emb dim]
packed_embedded = nn.utils.rnn.pack_padded_sequence(embedded, text_lengths,batch_first=True)
packed_output, (hidden, cell) = self.lstm(packed_embedded)
#hidden = [batch size, num layers * num directions,hid dim]
#cell = [batch size, num layers * num directions,hid dim]
#连接最后的正向和反向隐状态
hidden = torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim = 1)
#hidden = [batch size, hid dim * num directions]
dense_outputs=self.fc(hidden)
#激活
outputs=self.act(dense_outputs)
return outputs
下一步是定义超参数并实例化模型。下面是相同的代码块:
#定义超参数
size_of_vocab = len(TEXT.vocab)
embedding_dim = 100
num_hidden_nodes = 32
num_output_nodes = 1
num_layers = 2
bidirection = True
dropout = 0.2
#实例化模型
model = classifier(size_of_vocab, embedding_dim, num_hidden_nodes,num_output_nodes, num_layers,
bidirectional = True, dropout = dropout)
让我们看看模型摘要,并使用预先训练好的嵌入来初始化嵌入层
#模型体系
print(model)
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f'The model has {count_parameters(model):,} trainable parameters')
#初始化预训练embedding
pretrained_embeddings = TEXT.vocab.vectors
model.embedding.weight.data.copy_(pretrained_embeddings)
print(pretrained_embeddings.shape)
这里我已经为模型定义了优化器,损失和度量:
import torch.optim as optim
#定义优化器和损失
optimizer = optim.Adam(model.parameters())
criterion = nn.BCELoss()
#定义度量
def binary_accuracy(preds, y):
#四舍五入到最接近的整数
rounded_preds = torch.round(preds)
correct = (rounded_preds == y).float()
acc = correct.sum() / len(correct)
return acc
#如果cuda可用
model = model.to(device)
criterion = criterion.to(device)
构建模型分为两个阶段:
- 训练阶段:model.train()将模型设置在训练阶段,并激活dropout层。
- 推理阶段:model.eval()将模型设置在评估阶段,并停用dropout层。
下面是定义用于训练模型的函数的代码块
def train(model, iterator, optimizer, criterion):
#初始化
epoch_loss = 0
epoch_acc = 0
#设置为训练模式
model.train()
for batch in iterator:
#在每一个batch后设置0梯度
optimizer.zero_grad()
text, text_lengths = batch.text
#转换成一维张量
predictions = model(text, text_lengths).squeeze()
#计算损失
loss = criterion(predictions, batch.label)
#计算二分类精度
acc = binary_accuracy(predictions, batch.label)
#反向传播损耗并计算梯度
loss.backward()
#更新权重
optimizer.step()
#损失和精度
epoch_loss += loss.item()
epoch_acc += acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
我们有一个函数来训练模型,但我们也需要一个函数来评估模型。让我们这样做
def evaluate(model, iterator, criterion):
#初始化
epoch_loss = 0
epoch_acc = 0
#停用dropout层
model.eval()
#取消autograd
with torch.no_grad():
for batch in iterator:
text, text_lengths = batch.text
#转换为一维张量
predictions = model(text, text_lengths).squeeze()
#计算损失和准确性
loss = criterion(predictions, batch.label)
acc = binary_accuracy(predictions, batch.label)
#跟踪损失和准确性
epoch_loss += loss.item()
epoch_acc += acc.item()
return epoch_loss / len(iterator), epoch_acc / len(iterator)
最后,我们将对模型进行若干个epoch的训练,并在每个epoch保存最佳模型。
N_EPOCHS = 5
best_valid_loss = float('inf')
for epoch in range(N_EPOCHS):
#训练模型
train_loss, train_acc = train(model, train_iterator, optimizer, criterion)
#评估模型
valid_loss, valid_acc = evaluate(model, valid_iterator, criterion)
#保存最佳模型
if valid_loss < best_valid_loss:
best_valid_loss = valid_loss
torch.save(model.state_dict(), 'saved_weights.pt')
print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}%')
print(f'\t Val. Loss: {valid_loss:.3f} | Val. Acc: {valid_acc*100:.2f}%')
让我们加载最佳模型并定义接受用户定义的输入并进行预测的推理函数
#载入权重
path='/content/saved_weights.pt'
model.load_state_dict(torch.load(path));
model.eval();
#推理
import spacy
nlp = spacy.load('en')
def predict(model, sentence):
tokenized = [tok.text for tok in nlp.tokenizer(sentence)] #标记句子
indexed = [TEXT.vocab.stoi[t] for t in tokenized] #转换为整数序列
length = [len(indexed)]
tensor = torch.LongTensor(indexed).to(device) #转换为tensor
tensor = tensor.unsqueeze(1).T
length_tensor = torch.LongTensor(length) #转换为tensor
prediction = model(tensor, length_tensor) #预测
return prediction.item()
让我们用这个模型来预测几个问题:
#作出预测
predict(model, "Are there any sports that you don't like?")
#不真诚的问题
predict(model, "Why Indian girls go crazy about marrying Shri. Rahul Gandhi ji?")
结尾
我们已经看到了如何在PyTorch中构建自己的文本分类模型,并了解了包填充的重要性。
你可以尝试使用调试LSTM模型的超参数,并尝试进一步提高准确性。一些要调优的超参数可以是LSTM层的数量、每个LSTM单元中的隐藏单元的数量等等。
欢迎关注磐创博客资源汇总站:
http://docs.panchuang.net/
欢迎关注PyTorch官方中文教程站:
http://pytorch.panchuang.net/
OpenCV中文官方文档:
http://woshicver.com/
使用PyTorch建立你的第一个文本分类模型的更多相关文章
- NLP学习(2)----文本分类模型
实战:https://github.com/jiangxinyang227/NLP-Project 一.简介: 1.传统的文本分类方法:[人工特征工程+浅层分类模型] (1)文本预处理: ①(中文) ...
- 深度学习之文本分类模型-前馈神经网络(Feed-Forward Neural Networks)
目录 DAN(Deep Average Network) Fasttext fasttext文本分类 fasttext的n-gram模型 Doc2vec DAN(Deep Average Networ ...
- CNN 文本分类模型优化经验——关键点:加卷积层和FC可以提高精度,在FC前加BN可以加快收敛,有时候可以提高精度,FC后加dropout,conv_1d的input维度加大可以提高精度,但是到256会出现OOM。
network = tflearn.input_data(shape=[None, max_len], name='input') network = tflearn.embedding(networ ...
- 139、TensorFlow Serving 实现模型的部署(二) TextCnn文本分类模型
昨晚终于实现了Tensorflow模型的部署 使用TensorFlow Serving 1.使用Docker 获取Tensorflow Serving的镜像,Docker在国内的需要将镜像的Repos ...
- 用深度学习(CNN RNN Attention)解决大规模文本分类问题 - 综述和实践
https://zhuanlan.zhihu.com/p/25928551 近来在同时做一个应用深度学习解决淘宝商品的类目预测问题的项目,恰好硕士毕业时论文题目便是文本分类问题,趁此机会总结下文本分类 ...
- [转] 用深度学习(CNN RNN Attention)解决大规模文本分类问题 - 综述和实践
转自知乎上看到的一篇很棒的文章:用深度学习(CNN RNN Attention)解决大规模文本分类问题 - 综述和实践 近来在同时做一个应用深度学习解决淘宝商品的类目预测问题的项目,恰好硕士毕业时论文 ...
- 使用libsvm实现文本分类
@Hcy(黄灿奕) 文本分类,首先它是分类问题,应该对应着分类过程的两个重要的步骤,一个是使用训练数据集训练分类器,另一个就是使用测试数据集来评价分类器的分类精度.然而,作为文本分类,它还具有文本这样 ...
- NLP大赛冠军总结:300万知乎多标签文本分类任务(附深度学习源码)
NLP大赛冠军总结:300万知乎多标签文本分类任务(附深度学习源码) 七月,酷暑难耐,认识的几位同学参加知乎看山杯,均取得不错的排名.当时天池AI医疗大赛初赛结束,官方正在为复赛进行平台调 ...
- Bert文本分类实践(一):实现一个简单的分类模型
写在前面 文本分类是nlp中一个非常重要的任务,也是非常适合入坑nlp的第一个完整项目.虽然文本分类看似简单,但里面的门道好多好多,作者水平有限,只能将平时用到的方法和trick在此做个记录和分享,希 ...
随机推荐
- 最强加密算法?AES加解密算法Matlab和Verilog实现
目录 背景 AES加密的几种模式 基本运算 AES加密原理 Matlab实现 Verilog实现 Testbench 此本文首发于公众号[两猿社],重点讲述了AES加密算法的加密模式和原理,用MATL ...
- USB小白学习之路(11) Cy7c68013A驱动电路设计注意事项(转)
Cy7c68013A驱动电路设计注意事项 转自:http://group.chinaaet.com/116/79029#0-tsina-1-71467-397232819ff9a47a7b7e80a4 ...
- [ASP.NET Core 3框架揭秘] 服务承载系统[1]: 承载长时间运行的服务[上篇]
借助.NET Core提供的承载(Hosting)系统,我们可以将任意一个或者多个长时间运行(Long-Running)的服务寄宿或者承载于托管进程中.ASP.NET Core应用仅仅是该承载系统的一 ...
- LeetCode 题解 | 1. 两数之和
题目描述: 给定一个整数数组和一个目标值,找出数组中和为目标值的两个数. 你可以假设每个输入只对应一种答案,且同样的元素不能被重复利用. 示例: 给定 nums = [2, 7, 11, 15], t ...
- 93-time模块
目录 time模块 一.time模块 1.1 时间戳 1.2 格式化时间 1.3 结构化时间 1.4 不同格式时间之间的转换 1.5 其它用法 time模块 一.time模块 import time ...
- Grafana使用总结
最近工作需求学习了下grafana,根据创建的几个dashboard简要记录下创建过程. 本次使用了grafana做可视化展示,data source使用的rds是postgresql和时序数据库in ...
- Fedora CoreOS 非LInux专业安装文章第一手
开篇一张图 Docker基本知识掌握后,又学习了"专有的系统平台",CoreOS; 之前一直Windows,学习Docker的同时练习了好多Linux知识,全是江湖路数,打个不同就 ...
- Day06 - Fetch、filter、正则表达式实现快速古诗匹配
Day06 - Fetch.filter.正则表达式实现快速古诗匹配 作者:©liyuechun 简介:JavaScript30 是 Wes Bos 推出的一个 30 天挑战.项目免费提供了 30 个 ...
- pc端适配移动端
pc端和移动端共用一套代码 1. 允许网页宽度自动调整 在网页代码的头部,加入一行viewport元标签 <meta name="viewport" content=&quo ...
- Bootstrap4 正式发布
历经三年开发,前端框架Bootstrap 4正式发布了.然而今天的Web世界已经和当初Mark Otto发布Bootstrap时的情况大为不同,一些开发者由此质疑它的更新是否还有意义 1.V4版本的主 ...