数据空间
您当前的位置: 首页 /数据知识

独家 :教你用Pytorch建立你的第一个文本分类模型!

发布时间:[2020-04-29] 来源:数据派THU
点击量:

 一、为什么用PyTorch来解决文本分类问题?


在我们深入专业概念前,我们先快速熟悉一下PyTorch这个框架。PyTorch的基本数据单元是Tensor,类似于python中的numpy数列。使用PyTorch的最重要的两个优点是:

 

  • 动态的网络——训练过程中网络结构可以变化

  • 多GPU分布式训练

 


我肯定你在想-为什么我们要用PyTorch处理文本数据?接下来我们讨论一下PyTorch的一些令人难以置信的特性,这些特性使它不同于其他框架,特别是在处理文本数据时。
 
1. 处理Out of Vocabulary words问题

文本分类模型是在固定数据大小的文本数据集上进行训练的。但是对于推理问题,可能会遇到有些词并不涵盖在词汇集内,这些词叫做Out of Vocabulary words。忽略Out of Vocabulary words可能会导致信息丢失,因此这是一个重要的问题。
 
为了解决这个问题,PyTorch支持把训练数据集中的稀有词替换为unknown token,帮助我们处理Out of Vocabulary words问题。
 
另外,PyTorch还提供了处理变长序列的方法。
 
2. 处理变长文本序列

是否听过,循环神经网络用来解决变长序列的问题,有没有疑惑它是怎么实现的?PyTorch带来了很有用的'Packed Padding sequence',来实现动态循环神经网络。
 
Padding(填充)是在句子的开头或者结尾填充额外的token的过程。由于每个句子的词的数量不同,我们把长度不同的句子输入,增加padding tokens,扩充以使得句子等长。
 
由于大部分的框架支持的是静态网络,也就是说在模型训练过程中,模型框架是不变的,因此padding是必要的。尽管padding解决了变长序列的问题,但也带来了新的问题——padding token是增加了新的信息/数据,下面我用一个简单的图来做解释。
 
下边这个图中,最后一个单词表示的是一个padding token,但它也在产生输出的过程里发生了作用。这个问题可以交给pytorch中的Packed Padding sequence(压缩填充序列)来处理。

 

1.jpg

 


压缩填充会忽略掉padding token的部分。这些值,永远不会出现在循环神经网络的训练过程中,可以帮助我们建立动态的循环神经网络。

 

2.jpg

 


3. 包装和预训练的模型

Pytorch正在推出先进的框架。Hugging Face 公司的Transformers库提供了超过32个先进的框架用于自然语言理解和自然语言生成。
 
除此之外,pytorch还提供了一些预训练的模型,可以用很少的代码去解决文本到语言、目标检测等问题。
 
是不是感觉到难以置信?这些是PyTorch的一些非常有用的特性。现在让我们使用PyTorch解决文本分类问题。
 
二、了解问题的场景

作为本文的一部分,我们将研究一个非常有趣的问题。
 
Quora希望在他们的平台上跟踪“不真诚”的问题,以便让用户在分享知识的同时感到安全。在这种情况下,一个不真诚的问题被定义为一个旨在陈述而不是寻求有用答案的问题。为了进一步分解这一点,这里有一些特征可以表明某个特定问题是不真诚的:

 

  • 具有非中性音调;

  • 贬低或煽动;    

  • 不是基于现实;

  • 利用性(乱伦、兽交、恋童癖)来获得震惊的价值,而不是寻求真正的答案。

 


训练集包括以上被问的问题,以及一个标志它是否是不真诚的标签(target=1表示不真诚)。基本事实中存在噪声,也就是说,训练集并不是完美的。我们的任务是识别一个给定的问题是否是“不真诚的”。
 
现在是时候用PyTorch编写我们自己的文本分类模型了。
 
三、实现文本分类

首先导入所有建模需要的包。这里有一个简单的关于我们要用到的包的概览:

 

  • Torch包是用来定义tensor和tensor上的数学运算;

  • TorchText包是PyTorch中NLP的库,包含一些预处理的脚本和常见的NLP数据集。

 


为了使结果可复现,我指定了种子值。由于深度学习模型的随机性,在执行时可能会产生不同的结果,因此指定种子值非常重要。
 

 

  • 数据预处理:

 


现在我们来看,如何用field(域)来做文本的预处理。这里有两种域对象——Field和LabelField。我们来快速理解一下它们的区别:

 

  • Field:data模块的Field对象,用于描述数据集中每一列的预处理步骤;

  • LabelField:LabelField是Field对象只用于分类任务的特例。只用于设置unk_token和sequential,默认都为None。

 


在使用Field之前,看一下它的不同参数和用途:

 

  • Tokenize:定义分词方法,这里我们用spacy tokenizer,因为它用了新的分词算法;

  • Lower:把文本转化成小写;

  • Batch_first:输入和输出的第一个维度一般都是batch size。

 


然后,创建元组构成的列表,每个元组都包含一个列名,第二个值是field对象。另外,按照csv文件中列的顺序,来排列元组,当我们忽略一列的时候,用(None,None)表示。
 
读入必要的列——问题和标签。
 
  •  
fields = [(None, None), ('text',TEXT),('label', LABEL)]

这个代码块中,我通过定义field对象加载了自定义数据集。              
 
现在让我们将数据集分成训练和验证数据
 

 

  • 准备输入和输出序列:

 


下一步是建立文本的vocabulary并把它们转化为整数序列。Vocabulary包含整个文本中的所有的独立的词,每一个词都分配一个索引。下面是参数:

 

  • min_freq:当vocabulary中的词的频率低于这个参数值的时候把这个词映射为unknown token;

  • 两个特殊的token,一个unknown tokenpadding token加到vocabulary中:Unknown token是用于处理Out Of Vocabulary words;Padding token是把输入序列变为等长的。

     

 

 
我们建立起vocabulary,用预训练好的词嵌入来初始化单词成向量。如果你想随机初始化词嵌入,可以忽略向量的参数。
 
接下来,准备训练模型的batch。BucketIterator以需要最小填充量的方式形成批次。
 

 

  • 模型架构

 


现在需要定义模型的架构来解决这个二分类问题。Torch中的nn模块,是一个所有模型的基础模型。也就是说,每个模型都必须是nn模块的子类。

我定义了两个函数,init和forward。我来解释一下这两个函数的应用场景。

 

  • init:初始化类的实例时,init函数自动被调用。因此,它也叫做构造函数。类的参数需要在构造函数中初始化,我们需要定义模型需要用到的层;

  • forward:forward函数定义了inputs前向传播的计算步骤。

 


最后,我们理解一下各层的细节问题和参数。
 
嵌入层:对于任何NLP相关的任务,词嵌入都很重要,因为它可以应用数字形式表示一个单词。嵌入层得到一个查询表,其中每一行代表一个词嵌入。嵌入层可以把表示文本的整数序列转化为稠密向量矩阵。嵌入层的两个重要的参数:

 

  • num_embeddings:查询表中,单词的的个数;

  • embedding_dim:表示一个单词的向量维度。

 


LSTM:LSTM是RNN的一个变体,可以学习长的依赖关系。下面列举了LSTM的一些你应该了解的重要参数:

 

  • input_size:输入向量的维度;

  • hidden_size:隐藏层节点的个数;

  • num_layers:网络中的层数;

  • batch_first:如果等于True,输入输出的tensor的形式都是(batch, seq, feature);

  • dropout:默认值是0,如果设为非0,每个LSTM层输出结果都会进到dropout层,以dropout参数值的概率删掉对应比例的神经元;

  • bidirection:如果为True, LSTM是双向的。

 


Linear Layer:线性层指的是稠密层,有两个重要参数:

 

  • in_features:输入特征的个数;

  • out_features:隐藏层节点个数。

 


压缩填充:上文已经讨论过,压缩填充用于动态循环神经网络。如果不采用压缩填充的话,填充后的输入,rnn也会处理padding输入,并返回padded元素的hidden state。但压缩填充是一个很棒的包装,它不显示填充的输入。它直接忽略填充部分并返回非填充元素部分的hidden state。
 
现在我们已经对这个架构中的所有板块都有了了解,现在可以去看代码了!
 
下一步是定义超参、初始化模型。
 
让我们看看模型摘要,并使用预训练的词嵌入初始化嵌入层。
 
在这里,我定义了模型的优化器、损失和度量:
 
建模的两个阶段:

 

  • 训练阶段:model.train() 设置了模型进入训练,并激活dropout层;

  • 预测阶段:model.eval() 开始模型的评估阶段并关闭dropout层。

 


接下来是定义用于训练模型的函数的代码块。

所以我们有一个函数来训练模型,但是我们也需要一个函数来评估模型。我们来吧 !
  
最后,我们将对模型进行一定数量的训练,并保存每个时期的最佳模型。
 
让我们加载最佳模型并定义一个推理函数,它接受用户定义的输入并进行预测太神了!让我们用这个模型来预测几个问题。
 
小结

我们已经看到了如何在PyTorch中构建自己的文本分类模型,并了解了压缩填充的重要性。您可以随意使用长短期模型的超参数,如隐藏节点数、隐藏层数等,以进一步提高性能。

 

 

中翰软件:专注数据治理15年(http://www.jobhand.cn


发表评论 共有条评论
用户名: 密码:
匿名发表