使用ULMFiT和Python中的fastai库的文本分类(NLP)教程
自然语言处理(NLP)在当今世界中最重要的研究领域之一,并且在过去十年中出现了惊人的兴趣增长。NLP的基础知识广为人知,易于掌握。但是当文本数据变得庞大且非结构化时,事情开始变得棘手了。
这就是深度学习的意义所在。是的,我说的是针对NLP任务的深度学习——这是一种相对较少有人涉足的方法。已经证明深度学习(DL)在像图像检测、分类和分割之类的计算机视觉任务中是有用的,但是对于传统的机器学习(ML)技术来说,像文本生成和分类这样的NLP应用被认为是适合的。
正如您将在本文中看到的那样,深度学习肯定会对NLP产生非常积极的影响。我们将重点关注迁移学习的概念,以及我们如何利用它在NLP中利用流行的fastai库构建令人难以置信的精确模型。我将在此过程中向您介绍ULMFiT框架。
注意 - 本文假设你基本熟悉神经网络,深度学习和迁移学习。
迁移学习的优势
深度学习的最大挑战是训练模型的大量数据要求。很难找到如此巨大尺寸的数据集,而且准备这样的数据集的成本太高。
另一个障碍是运行高级深度学习算法所需的GPU成本很高。
值得庆幸的是,我们可以使用经过预训练的最先进的深度学习模型,并对它们进行调整以便为我们工作。这被称为迁移学习。它不像从头开始训练深度学习模型那样占用资源,即使在少量训练数据上也能产生不错的结果。
NLP中的预训练模型
经过预先训练的模型通过提供现有的框架帮助数据科学家开始解决一个新问题。您不必总是从头开始构建深度学习模型,这些预训练模型已经被证明在计算机视觉领域是真正有效和有用的。
他们的成功通常归功于Imagenet数据集。它有超过1400万张带标签的图像,超过100万张图像附带有边界框。该数据集于2009年首次发布,从此成为有史以来最受欢迎的图像数据集之一。它在计算机视觉的深度学习研究方面取得了一些突破,迁移学习就是其中之一。
然而,在NLP中,迁移学习并不那么成功(与计算机视觉相比)。当然,我们有预训练过的word嵌入,如word2vec,GloVe和fastText,但它们主要用于初始化神经网络的第一层。模型的其余部分仍然需要从头开始训练,并且需要大量示例才能产生良好的性能。
在这种情况下我们真正需要什么呢?与前面提到的计算机视觉模型一样,我们需要一个预训练的NLP模型,可以对其进行微调并在不同的文本数据集上使用。预训练自然语言模型的竞争者之一是用于文本分类的通用语言模型微调,即ULMFiT(Imagenet dataset [cs.CL] https://arxiv.org/abs/1801.06146)。
它是如何工作的呢?它的应用程序有多广泛?我们怎样才能让它在Python中运行?在本文的其余部分,我们将通过解决文本分类问题并检查其执行情况来对ULMFiT进行测试。
ULMFiT概述
由fast.ai的Jeremy Howard和NUI Galway Insight Center的Sebastian Ruder提出,ULMFiT本质上是一种为任何NLP任务实现迁移学习并取得优异成果的方法。所有这一切,无需从头开始训练模型。
ULMFiT使用以下新技术实现最先进的结果:
- Discriminative fine-tuning
- Slanted triangular learning rates
- Gradual unfreezing
该方法涉及将在Wikitext 103数据集(https://einstein.ai/research/the-wikitext-long-term-dependency-language-modeling-dataset)上训练的预训练语言模型(LM)微调到新数据集,使得它不会忘记它先前学习的内容。
语言建模可以看作是NLP中Imagenet的对等物。它捕获语言的一般属性,并提供大量数据,这些数据可以提供给其他下游NLP任务。这就是为什么选择语言建模作为ULMFiT的源任务。
我强烈建议你阅读最初的ULMFiT 论文(https://arxiv.org/abs/1801.06146),了解更多关于它是如何工作的,杰里米和塞巴斯蒂安推导它的方式,并解析其他有趣的细节。
问题陈述
我们的目标是微调预训练的模型,并将其用于新数据集的文本分类。我们将在此过程中实施ULMFiT。这里有趣的是这个新数据的大小非常小(<1000个标记实例)。从头开始训练的神经网络模型会过度拟合这样一个小数据集。我想看看ULMFiT是否像论文中承诺的那样出色地完成了这项任务。
数据集:我们将使用sklearn.datasets中提供的20个新闻组数据集。顾名思义,它包含来自20个不同新闻组的文本文档。
导入所需的Python库
# import libraries import fastai from fastai import * from fastai.text import * import pandas as pd import numpy as np from functools import partial import io import os
导入数据集
from sklearn.datasets import fetch_20newsgroups dataset = fetch_20newsgroups(shuffle=True, random_state=1, remove= ('headers', 'footers', 'quotes')) documents = dataset.data
让我们创建一个由文本文档及其相应标签(新闻组名称)组成的数据框。
df = pd.DataFrame({'label':dataset.target, 'text':dataset.data}) df.shape
(11314, 2)
我们通过仅选择数据集中存在的20个标签中的2个来将其转换为二元分类问题。我们将分别选择对应于'comp.graphics'和'rec.sport.hockey'的标签1和10。
df = df[df['label'].isin([1,10])] df = df.reset_index(drop = True)
让我们快速浏览一下目标分布
df['label'].value_counts()
分布看起来很均匀。在这种情况下,准确性将是一个很好的评估指标。
数据预处理
向模型提供干净的数据总是一个很好的实践,尤其是当数据以非结构化文本的形式出现时。让我们通过只保留字母并删除其他所有内容来清理文本。
df['text'] = df['text'].str.replace("[^a-zA-Z]", " ")
现在,我们将从文本数据中删除stopwords 。如果你以前从未使用过stopwords ,那么你必须从nltk包下载它们,如下所示:
import nltk nltk.download('stopwords') from nltk.corpus import stopwords stop_words = stopwords.words('english') # tokenization tokenized_doc = df['text'].apply(lambda x: x.split()) # remove stop-words tokenized_doc = tokenized_doc.apply(lambda x:[item for item in x if item not in stop_words]) # de-tokenization detokenized_doc = [] for i in range(len(df)): t =' '.join(tokenized_doc[i]) detokenized_doc.append(t) df['text'] = detokenized_doc
现在让我们将清理后的数据集拆分为60:40的训练和验证集。
from sklearn.model_selection import train_test_split # split data into training and validation set df_trn, df_val = train_test_split(df, stratify = df['label'], test_size = 0.4, random_state = 12) df_trn.shape, df_val.shape
((710, 2), (474, 2))
在继续之前,我们需要分别为语言模型和分类模型准备数据。使用fastai库可以非常轻松地完成此操作:
# Language model data data_lm = TextLMDataBunch.from_df(train_df = df_trn, valid_df = df_val, path = "") # Classifier model data data_clas = TextClasDataBunch.from_df(path = "", train_df = df_trn, valid_df = df_val, vocab=data_lm.train_ds.vocab, bs=32)
微调预训练模型并进行预测
我们可以使用data_lm我们之前创建的对象来微调预先训练的语言模型。我们可以创建一个学习者对象,“学习”,直接创建模型,下载预先训练的权重,并准备好进行微调:
learn = language_model_learner(data_lm, pretrained_model=URLs.WT103, drop_mult=0.7)
one cycle和cyclic momentum 使模型能够以更高的学习速率进行训练,并更快地收敛。“one cycle”策略提供了某种形式的regularisation。我们不会深入讨论它是如何工作的,因为本文是关于学习实现的。
learn.fit_one_cycle(1, 1e-2)
我们将保存此编码器以便稍后用于分类。
learn.save_encoder('ft_enc')
现在,让我们使用前面创建的data_clas对象,使用微调的编码器构建分类器。
learn = text_classifier_learner(data_clas, drop_mult=0.7) learn.load_encoder('ft_enc')
我们将再次尝试拟合我们的模型。
learn.fit_one_cycle(1, 1e-2)
我们的准确度有了惊人的提高,甚至验证损失也远低于训练损失。它在小型数据集上表现非常出色。您甚至可以使用以下代码获取学习者对象的验证集的预测:
# get predictions preds, targets = learn.get_preds() predictions = np.argmax(preds, axis = 1) pd.crosstab(predictions, targets)
最后
我希望这篇文章对你有帮助。使用fastai库在ULMFiT中还有很多东西需要探索,我鼓励大家去看看。