74FAST.AI 深度学习实践课程--FAST.AI 文本分类实践

FAST.AI 文本分类实践

除了前面介绍的图像分类,深度学习的另一个主要应用场景是自然语言处理。自然语言处理的研究对象是语言文本,而研究任务主要集中在:文本分类、摘要提取、情感分析、机器翻译、自动问答等方面。除了前面我们已经学习的计算机视觉任务模块 fastai.vision,FAST.AI 也有专门针对自然语言处理 fastai.text 模块。本次实验中,我们将学习 FAST.AI 在自然语言处理任务中的应用方法和技巧。

fastai.text 模块

FAST.AI 中用于自然语言处理的相关类和方法都位于 fastai.text 模块下方。其中:
text.transform:包含所有用于预处理数据的类和方法,例如字典编码。
text.data:包含的定义 TextDataBunch 的类和方法。
text.learner:包含快速创建语言模型或 RNN 分类器的相关类和方法。

事实上,通过学习 FAST.AI 针对计算机视觉任务设计的 fastai.vision 模块,你会发现 fastai.text 模块在接口设计和命名上与其非常相似。所以,接下来,我们将选择一个简单的文本分类示例,学习使用 FAST.AI 来完成文本分类任务。

文本分类

文本分类是自然语言处理最常见也是最简单的应用场景。文本分类就是将多个文档按某种属性来进行划分。例如,图书馆会把人文社科这一类的书籍放到一个区域,把科学技术类的书籍放到另一个区域,这样既方便馆内工作人员整理,也方便读者查阅。再比如说电子邮箱常用的垃圾邮件分类功能,也是一个常用的文本分类应用场景。
本次实验,我们选择 IMDB 数据集进行演示。前面的实验中,我们已经了解到 IMDB 数据集实际上是 IMDB 网站中的电影评论,每个样本拥有积极或消极标签。IMDB 总共包含 100,000 条评论,其中 25,000 条样本为训练集,另外 25,000 条则被标记为验证集。剩余的 50,000 条是未标记数据。
数据处理
首先,我们下载由 FAST.AI 整理好的 IMDB 数据集。由于原数据集太大,所以实验选择 IMDB_SAMPLE 精简数据集进行加载,该数据集只包括 1000 条采样样本,其中 20% 为验证集。

from fastai.text import URLs, untar_data

imdb_path = untar_data(URLs.IMDB_SAMPLE)
imdb_path.ls()

如果你了解 Pandas 的使用,那么我们可以直接读取该 CSV 文件进行预览。

import pandas as pd

imdb_df = pd.read_csv(imdb_path/'texts.csv')
imdb_df.head()

可以看到,整理好的 CSV 文件中包含 3 列,分别是标签 label,文本内容 text,和 is_valid 标记是否为验证数据。此时,我们使用 fastai.text.TextDataBunch 将文本数据读取为 DataBunch 对象,这一点和之前图像数据的处理过程非常相似。

from fastai.text import TextDataBunch

imdb_data = TextDataBunch.from_csv(imdb_path, 'texts.csv')
imdb_data.show_batch()

你会发现,FAST.AI 会自动依据 is_valid 标记将数据样本读取为训练集和验证集。这一点非常关键,后续你在处理自己的数据集时应该明白该标记设置的含义。
Tokenization
如果你仔细观察上方 TextDataBunch 的输出,会觉得有一些怪异,其中出现了很多以 xx 开头的标记。事实上,FAST.AI 在将文本处理成 TextDataBunch 时会自动执行 Tokenization 过程。Tokenization 在处理中文时被称为「分词」,而英语文本本身由单词构成,所以 Tokenization 的过程会针对其中一些特征进行标记,例如:
将 didn't 这类英文缩写分解成 did 和 n't 两部分。
对包含的 HTML 文档内容进行清洁。
将大写字母全部转化为小写字母。
加入以 xx 开头的特殊标记,xxunk 表示未知单词,xxbos 表示文本开头,xxup 表示下一个单词在原文本中为大写字母。

更多关于 Tokenization 的处理过程和步骤,请阅读 官方文档
为了更加直观看出 Tokenization 的过程,我们使用单个样本进行对照演示。

example_text = imdb_df.iloc[1][1]  # 取出单个样本
example_text

from fastai.text import Tokenizer, SpacyTokenizer

# 调用 FAST.AI 默认 spaCy Tokenizer 进行处理
tokenizer = Tokenizer()
' '.join(tokenizer.process_text(example_text, SpacyTokenizer('en')))

FAST.AI 执行 Tokenization 后是以列表形式储存数据,上面为了演示方便使用 join 操作进行了句子还原。由于 FAST.AI 默认使用 spaCy 文本处理工具,所以并不支持针对中文文本的 Tokenization 过程。中文文本分词,必须用到像 jieba 这类中文分词工具进行单独处理。
Numericalization
在执行 Tokenization 时,还会执行 Numericalization 操作,Numericalization 意思是将词处理成用数字表示的字典。机器学习算法只能处理数值,无法直接理解文本。所以,我们往往会将分词之后的单词按出现频次进行标记,例如 the 这个单词出现频次最多,那么标记为序号 1,以此类推。FAST.AI 默认只保留出现频次最高的 60000 个词汇,剩下出现次数较少的就会以 xxunk 标记,也就代表不认识该单词。
我们可以读取 TextDataBunch 中的 vocab 字典:

imdb_data.vocab.itos

同时,为了更直观看出 Numericalization 前后结果,这里使用单个样本对比观察:

imdb_data.train_ds[0][0].text  # 输出训练集中第一条样本

imdb_data.train_ds[0][0].data  # 训练集中第一条样本对应的向量

你可以对照上方字典来观察文本和向量元素之间的对应关系。
所以,由此可见 FAST.AI 使用的方便程度。正常情况下我们在对文本预处理过程中的分词和向量化过程,FAST.AI 使用 TextDataBunch 单个操作进行了封装,一次性将文本转换为能够用于训练模型的向量。
建模评估
完成以上步骤之后,接下来就可以开始训练文本分类模型。FAST.AI 提供了专门用于文本分类的接口 text_classifier_learner,与前面图片分类相似,我们使用该类定义一个 Learner 最终完成模型训练。
一般情况下,文本分类较常使用到 RNN 循环神经网络模型。FAST.AI 在 text.models 中提供了多个经过实验反复验证的预训练模型方便调用,例如 AWD_LSTM,Transformer,TransformerXL 等。相关网络的原理需要你参考论文进行学习。接下来,我们使用 AWD_LSTM 模型结构完成文本分类任务。由于原 AWD_LSTM 预训练模型,速度很慢,实验首先从蓝桥云课镜像服务器下载该模型。

# 由于原 AWD_LSTM 预训练模型,速度很慢,实验首先从蓝桥云课镜像服务器下载该模型。直接运行即可。
!wget -nc "https://labfile.oss.aliyuncs.com/courses/1445/wt103-fwd.tgz" -P /root/.fastai/models
from fastai.text.models import AWD_LSTM
from fastai.text import text_classifier_learner

# 定义一个小 batch_size 的 TextDataBunch
imdb_data_lite = TextDataBunch.from_csv(imdb_path, 'texts.csv')
# 定义 Learner
learner = text_classifier_learner(imdb_data_lite, AWD_LSTM)

如果代码报网络错误,可能是因为有模型更新的原因,而官方模型又挂载在外网,反复重试一下可以解决。你可以通过提交课程问题告诉我们,我们会及时更新新的模型镜像。

learner.fit(3)

等待训练完成即可,效果并不太好。原因主要有 2 点,首先,预训练模型 AWD_LSTM 是基于维基百科语料进行训练的,和 IMDB 语料相差较大,我们并没有进行微调。此外,实验使用到的数据实在太少。

语言模型

为了使得文本分类的效果更好,很多时候会先训练新的语言模型。语言模型是在不考虑标签的基础上,使用无监督学习方法来完成训练。基于训练好的语言模型,我们可以直接预测和生成新的句子。
训练语言模型前,需要重新制作专用的 DataBunch 数据,你需要使用到 TextLMDataBunch 方法。过程和之前处理 TextDataBunch 是非常相似,示例如下:

from fastai.text import TextLMDataBunch

imdb_data_lm = TextLMDataBunch.from_csv(imdb_path, 'texts.csv')
imdb_data_lm.show_batch()

你可以看到,输出的数据不再含有标签,只包括文本内容。
接下来,我们使用 FAST.AI 用于语言模型训练的方法 fastai.text.language_model_learner 开始训练过程。该方法使用和上方的分类过程几乎一致。

from fastai.text import language_model_learner

learner_lm = language_model_learner(imdb_data_lm, AWD_LSTM)
learner_lm.fit(3)

等待训练完成之后,我们可以使用模型进行预测。语言模型可以预测完整的句子,而不是分类标签。例如,我们指定句子的开头和长度,使用训练好的模型来补充完整的句子。

TEXT = "I loved that movie because"  # 指定句子的开头
N_WORDS = 40  # 指定句子长度
N_SENTENCES = 5  # 指定预测句子数量

print("\n".join(learner_lm.predict(TEXT, N_WORDS, temperature=0.75)
                for _ in range(N_SENTENCES)))

最终,模型将会给出不同的预测结果。语言模型训练往往需要大量的数据语料,所以上方的句子可能读起来并不通顺。很多时候,我们会使用 BERT,XLNet 等在大规模语料上训练好的语言模型,这些模型使用到 TB 级的数据进行训练。
此时,我们使用 .save_encoder 保存学习器中的编码器部分,其主要负责创建和更新 RNN 网络中的隐藏状态。

learner_lm.save_encoder('fine_tuned_encoder')

接下来,我们使用保存好的编码器来重新完成分类模型训练过程。这里,我们选择上方语言模型的生成的 vocab 来定义新的 TextDataBunch 对象。

imdb_data_ft = TextDataBunch.from_csv(imdb_path, 'texts.csv', vocab=imdb_data_lm.vocab)
imdb_data_ft

训练新的文本分类器,调用语言模型提供的编码器对模型进行微调。

learner_ft = text_classifier_learner(imdb_data_ft, AWD_LSTM)
learner_ft.load_encoder('fine_tuned_encoder')
learner_ft.fit(3)

最终,你会发现效果会比上方直接训练要好一些。但受限于迭代次数和语料大小,提升程度并不是非常显著。
与图像分类相似,FAST.AI 也提供的 fastai.text.TextClassificationInterpretation 方法来对结果进行进一步分析。我们可以通过 interp.plot_confusion_matrix 直接绘制出真实标签和预测标签之间的混淆矩阵。

from fastai.text import TextClassificationInterpretation

# 载入学习器
interp = TextClassificationInterpretation.from_learner(learner_ft)
interp.plot_confusion_matrix(figsize=(5, 5), dpi=100)

通过混淆矩阵,我们可以看出被预测错误最多的类别,从而来调整输入数据。同样,我们可以输出损失最大的样本进行人工查看。

interp.show_top_losses(5)

IMDB 文本分类挑战

前面的挑战中,我们使用 IMDB_SAMPLE 数据集训练了文本分类和语言模型。由于 IMDB_SAMPLE 仅有 1000 条数据,模型的表现并不算好。本次挑战中,请使用完整的 IMDB 尝试训练出更好的文本分类和语言模型。为了提升训练速度,本次挑战同样配置了 GPU 环境。
首先,我们从蓝桥云课服务器下载完整的 IMDB 数据集,并使用 FAST.AI 读取路径。该数据集来源于 FAST.AI,蓝桥云课仅完成了镜像托管,以便于提高数据下载速度。

from fastai.datasets import untar_data, URLs, download_data

download_data("http://labfile.oss.aliyuncs.com/courses/1445/imdb")
imdb_path = untar_data(URLs.IMDB)
imdb_path.ls()
# 由于原 AWD_LSTM 预训练模型,速度很慢,实验首先从蓝桥云课镜像服务器下载该模型。直接运行即可。
!wget -nc "https://labfile.oss.aliyuncs.com/courses/1445/wt103-fwd.tgz" -P /root/.fastai/models

数据路径中,train 为 25000 条有标签的训练数据,test 为 25000 条有标签验证数据,unsup 为 50000 条未标注数据。其他文件可以不予考虑。接下来,请使用完整的 IMDB 数据训练一个文本分类模型,并得到验证数据集上的准确度结果。
首先,我们使用 50000 条未标注数据训练语言模型。这里,挑战告诉大家一种自定义程度更高的划分方法,通过 TextList 而不是 TextDataBunch 将数据处理成自己想要的 DataBunch。

from fastai.text import TextList

imdb_data_lm = (TextList.from_folder(imdb_path)
                # 过滤我们需要的文件夹
                .filter_by_folder(include=['unsup'])
                # 随机划分一定百分比数据到验证集
                .split_by_rand_pct(0.1)
                # 指定标签类型为语言模型
                .label_for_lm()
                # 处理成 DataBunch
                .databunch())
imdb_data_lm

接下来,请补充训练语言模型的代码,并保存相应的编码器。

from fastai.text.models import AWD_LSTM
from fastai.text import language_model_learner

learner_lm = language_model_learner(imdb_data_lm, AWD_LSTM)
learner_lm.fit(1)
learner_lm.save_encoder('fine_tuned_encoder')

我们可以生成以 I loved that movie because 开头的语句,并与前面实验对比效果。

TEXT = "I loved that movie because"  # 指定句子的开头
N_WORDS = 40  # 指定句子长度
N_SENTENCES = 5  # 指定预测句子数量

print("\n".join(learner_lm.predict(TEXT, N_WORDS, temperature=0.75)
                for _ in range(N_SENTENCES)))

接下来,我们开始训练分类模型。由于 train 和 test 各有 25000 标记数据,实际上我们无需 25000 条数据用于验证。所以,在此希望能合并数据后进行重新划分。
下面,我们使用 TextList 读取全部标注数据,并划分其中 10% 用于验证。相关代码的作用已进行了注释。

imdb_data = (TextList.from_folder(imdb_path, vocab=imdb_data_lm.vocab)
             # 过滤我们需要的两个文件夹
             .filter_by_folder(include=['train', 'test'])
             # 0.1 划为验证集
             .split_by_rand_pct(0.1)
             # 指定标签类型
             .label_from_folder(classes=['neg', 'pos'])
             # 处理成 DataBunch
             .databunch())
imdb_data

此时,我们得到了包含 45000 条训练数据和 5000 条验证数据的 DataBunch 对象。
接下来,请使用 FAST.AI 提供的文本分类方法,针对上述数据集进行分类。你可以参考官方文档或其他资料自由选择模型,需保证最终验证集分类准确度高于 80% 即可。

from fastai.text import text_classifier_learner
# 定义 Learner
learner_ft = text_classifier_learner(imdb_data, AWD_LSTM)
learner_ft.load_encoder('fine_tuned_encoder')
learner_ft.fit(5)
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容