迁移学习在自然语言处理中的应用

通用语言建模

symbolic image of natural language processing

图片来源:OpenAI

在相当长的时间里,人们一直将迁移学习应用于计算机视觉 (CV) 领域,而且最近几年也取得了显著的成果。执行一些任务时,我们甚至能超越人类准确度水平。最近,很少能看到无需预训练权重就能输出顶尖结果的模型实施情况。事实上,人们生产它们时,通常使用迁移学习或某种微调的方法。迁移学习在计算机视觉领域产生了巨大影响,为该领域的发展作出了巨大贡献。

到目前为止,迁移学习仅限于计算机视觉,但最新研究表明,迁移学习的影响几乎无处不在,其中包括自然语言处理 (NLP) 和强化学习 (RL)。最近的几篇论文表明,迁移学习和微调在 NLP 中的使用效果不错。

最近,OpenAI 还举办了强化学习竞赛 retro 竞赛,参赛者的挑战是创建代理,在玩游戏时不能接触环境,而是利用迁移学习进行训练。现在,我们可以充分挖掘这种方法的潜能。

training diagram

图片来源:OpenAI Retro 竞赛

图 1.利用过去的经验学习新事物(强化学习中的新环境)

此前的研究涉及计算机视觉领域中的增量学习,对模型进行了概括,因为这是确保神经网络的学习保持稳定的最重要因素之一。一篇旨在以此为基础的论文是《面向文本分类的通用语言模型微调》

文本分类是 NLP 的重要组成部分,它与现实生活场景密切相关,例如机器人、语音助理、欺诈或垃圾邮件检测、文档分类等等。由于我们处理的是语言模型,因此这项技术的用途十分广泛,几乎可以扩展至所有任务。本文的作者进行的是文本分类。到目前为止,大部分学术研究仍然用嵌入来训练类似 word2vec 和 GloVe 这样的模型。

嵌入的局限性

词嵌入是单词的密集表示。嵌入通过转换为张量的真实数字完成,这些数字将被输入到模型中。在模型中,需要用特定顺序(状态化)排序,这样模型才能学会词语和语境之间的语法和语义关系。

data visualizations

图 2.不同数据类型的可视化

进行可视化时,语义相近的单词彼此之间的嵌入也会更加紧密,这样每个单词都会有不同的向量表示。

词汇表中不常见词

处理数据集时,我们通常会遇到生僻词,因为内存中保存的词汇量有限。

Tokenization example

图 3.令牌化。这些词都在词汇表中,而且都是通用词汇,但如果用嵌入,就无法有效地处理类似这样的令牌。

对出现频率较小的词语,模型很难弄清楚它的词义,因此创建了一个词汇表来解决这一问题。Word2vec 无法处理未知单词。如果模型不认识这个单词,就无法确切地构造它的向量,因此它必须随机地进行初始化。关于嵌入,常见的问题有:

处理共享表示

这种表示的另一个不足之处是子词之间没有共享表示。英语中的前缀和后缀通常会为所有词添加一个共同的含义(比如“better”和“faster”中的比较级 -er)。由于每个向量是独立的,因此不能完全理解词语之间的语义关系。

共现统计

分布式词向量模型能够捕获词语中共现统计的某些方面。在单词共现数量上训练的嵌入可以捕获语义字之间的相似性,所以可以根据词义相似性任务进行评估。

如果某种特定语言模型采用 char-based 输入,但这种输入无法从预训练中受益,那么就需要进行随机嵌入。

支持新语言

如果遇到其他语言,使用嵌入将无法确定模型的稳定性。使用新语言时,需要使用新的嵌入矩阵,但这些矩阵无法从参数共享中受益,因此模型不能执行跨语言任务。

嵌入可以串级,但仍然必须从头开始训练模型;预训练的嵌入将被视为固定参数。这样的模型在增量学习中没有任何作用。

计算机视觉已经表明,hypercolumn 和其他常用的训练方法相比,并不实用。在 CV 中,像素的超列 (hypercolumn) 是该像素上所有卷积网络单元的激活矢量。

diagram of Hypercolumns in ConvNets

图 4.卷积网络中的超列 (图片来源)

平均随机梯度法 (ASGD) 权重下降的长短期记忆网络 (AWD-LSTM)

在本研究中使用这种模型的想法主要来源于文章:《正规化和优化 LSTM 语言模型》。它使用权重下降的 LSTM,LSTM 将有关 hidden-to-hidden 权重的 DropConnect 用作循环正则化形式。DropConnect 是 Hinton 提出的一种名为 Dropout 的泛化,它在神经网络内部对大型完全连接层进行正规化。

使用 Dropout 进行训练时,随机选择的激活子集在每层都设置为零。DropConnect 将网络中随机选择的权重子集设置为零。因此,每个单元都接收来自上一层随机单元子集的输入。

Dropout network diagram

Dropout 网络

DropConnect network diagram

DropConnect 网络

图 5.Dropout 和 DropConnect 之间的差异

在 LSTM 中,通过在 hidden-to-hidden 权重矩阵上使用 DropConnect,即 [Ui , Uf , Uo , Uc ],可以避免 LSTM 循环连接出现过度拟合。这一正则化技术还可以防止其他循环神经网络单元的循环权重矩阵出现过度拟合。

常用的值为:

dropouts = np.array([0.4,0.5,0.05,0.3,0.1]) x 0.5

这里 x 0.5 是一个超参数,虽然阵列内部的比率已经达到很好的平衡,但可能仍然需要 0.5 的调整。

由于同样的权重可在多个时间步重复使用,因此相同下降权重会在整个来回过程中保持下降。结果和变分 dropout 非常像,它除了应用于循环权重外,还可将相同的 dropout 掩码应用于 LSTM 中的循环连接。DropConnect 也可以用于 LSTM [Wi , Wf , Wo ] 的非循环权重。

通用语言模型

一个包含调优的 dropout 参数的三层 LSTM (AWD-LSTM) 架构,其表现要优于使用其他训练方法训练的其他文本分类任务。由于原始预训练模型是在 wiki-text103 上训练,而且我们要处理的数据集是 IMDb 影评,因此我们使用了三种技术来防止忘记何时执行微调。

三角变化学习率 (STLR)

最开始我使用的是 Adam 优化算法和权重衰减,但自适应优化器有局限性。如果模型在鞍点卡住,同时生成的梯度很小,那么模型很难生成足够的梯度走出非凸区域。

Leslie Smith 提出的周期性学习率解决了这个问题。使用循环学习率 (CLR) 之后,精确度 (CMC) 提高了 10%。有关更多信息,请参阅文章:《用于训练神经网络的周期性学习率》

学习速率决定了在目前的权重上应用多少损失梯度,以使其朝损失的方向移动。这种方法和支持热启动的随机梯度很像。带重启的随机梯度下降 (SGDR) 方法曾用作退火策略。

learning rate diagram

图 6.周期性学习率

简单地说,选择起始学习率和学习率调度程序可能比较困难,因为有时无法分辨哪种更好。

每个参数都可以使用自适应学习速率。像 Adam、Adagrad 和 RMSprop 这样的优化器都可以适应每种训练中的参数的学习率。

文章《用于训练神经网络的周期性学习率》用简洁优雅的方式解决了许多常见问题。

周期性学习率 (CLR) 为学习率创建了上限和下限。它可以与自适应学习方法结合使用,但和 SGDR 比较相似,而且所需计算成本较低。

如果在鞍点卡住,可以使用较高的学习率让模型走出这一区域,但如果位置过低(在后期我们需要降低学习率),那么传统改变学习率的方法将无法生成足够的梯度,使其处于平稳区域(非凸)。

non convex function graph

图 7.非凸函数

周期性的更高的学习率可以更平稳、更快地穿过此区域。

最佳学习率 (LR) 将处于最上限和最下限之间。

maximum/minimum bound diagram

图 8.周期性学习率创建的限值

用这种方式改变 LR 可以确保在必要时解决这一问题。

因此,有了迁移学习后,倘若模型面向静态任务 A 训练,它的任务就是提高任务 B 的性能。语言模型拥有 CV 中分类模型在 NLP 环境下拥有的所有功能:它了解语言、理解层次关系、能够控制长期相关性,还可进行情绪分析。

和 CV 一样,面向文本分类的通用语言模型微调 (ULMfit) 也包含三个阶段。

Three stages of ULMFit diagram

图 9.ULMFit 的三个阶段

第一阶段,LM 预训练 (a),语言模型在通用数据集上训练,从中学习语言的一般特征,并了解词语之间的语义关系。和 ImageNet 一样,该模型使用 wikitext103(103 M 个令牌)。

第二阶段,LM 微调 (b),使用判别式微调和使用三角变化学习率 (STLR) 的骨干(基本模型)需要进行微调,从而使模型学习特定于任务的特征。

第三阶段,分类器微调 (c),对分类器进行调整,以对使用逐渐解锁和 STLR 的目标任务进行微调,从而维持低级表示并适应高级表示。

总之,ULMfit 可以看作是模型的主干,上面(头部)添加了一个分类器。它使用在通用领域语料库上训练过的预训练模型。(通常必须仔细检查研究人员处理的数据集,以便尽量减少域间隙。)稍后,可以使用上面提到的技术对目标任务进行微调,以达到文本分类的最佳性能水平。

ULMfit 可解决的问题

这种方法非常通用,因为它并不特定于数据集。它可以处理任何长度的文档和数据集。它使用单一架构(在本案例中为 AWD-LSTM,和 CV 中的 ResNets 一样)。它不需要设计定制特性就能兼容其他任务。它不需要任何其他文档来跨某些域发挥功能。

在必要时用注意和添加跳跃连接 (skip connection),还可以进一步提升该模型的性能。

判别式微调

每个神经网络层都捕获不同的信息。在 CV 中,初始层捕获广泛的、明显的、丰富的特征。随着深度的加大,它们尝试捕获特定于任务的复杂特征。利用相同的原理,这种方法会提议以不同的方式对语言模型的不同层进行微调。为此,每一层的学习率都不相同。这样人们就可以决定如何更新每一层的参数。

参数 θ 将分成一系列参数,并成为第 l 层的参数 equation,同时学习率也可以进行同样的操作 equation。之后可通过判别式微调来运行随机梯度下降:

equation

针对任务特定权重的分类器微调

我们增加了两个额外的线性模块。每个模块都使用批量归一化和较低的 dropout 值。(批量归一化可产生正则化效果。)在模块之间,修正线性单位 (ReLU) 用作激活函数,然后对数传递给 softmax,从而通过目标类输出概率分布。这些分类器层不会从预训练中延续任何内容;而是从头开始训练。在模块之前,会对上一个隐藏层进行池化,然后输入到第一个线性层中。

trn_ds = TextDataset(trn_clas, trn_labels)
val_ds = TextDataset(val_clas, val_labels)
trn_samp = SortishSampler(trn_clas, key=lambda x: len(trn_clas[x]), bs=bs//2)
val_samp = SortSampler(val_clas, key=lambda x: len(val_clas[x]))
trn_dl = DataLoader(trn_ds, bs//2, transpose=True, num_workers=1, pad_idx=1, sampler=trn_samp)
val_dl = DataLoader(val_ds, bs, transpose=True, num_workers=1, pad_idx=1, sampler=val_samp)
md = ModelData(PATH, trn_dl, val_dl)
dropouts = np.array([0.4,0.5,0.05,0.3,0.4])*0.5
m = get_rnn_classifer(bptt, 20*70, c, vs, emb_sz=em_sz, n_hid=nh, n_layers=nl, pad_token=1,
          layers=[em_sz*3, 50, c], drop=[dropouts[4], 0.1],
          dropouti=dropouts[0], wdrop=dropouts[1], dropoute=dropouts[2], dropouth=dropouts[3])

代码片段:带有 FastAI API 的 Pytorch(分类器训练)

Concat 池

注意循环模型的状态通常很重要,并且要保留有用的状态,释放那些无用的状态,因为内存中的状态有限,无法通过更新门进行更新。但通过 LSTM 模型生成的上一个隐藏状态包含大量信息,而且必须通过隐藏状态保存这些权重。为此,我们将通过尽可能多的时间步将上一个时间步的隐藏状态与隐藏状态的最大和均值池化的表示串级起来,这样它可以方便地适应 GPU 内存。

equation

trn_dl = DataLoader(trn_ds, bs//2, transpose=True, num_workers=1, pad_idx=1, sampler=trn_samp)
val_dl = DataLoader(val_ds, bs, transpose=True, num_workers=1, pad_idx=1, sampler=val_samp)
md = ModelData(PATH, trn_dl, val_dl)

训练分类器(逐渐解锁)

直接对分类器进行微调,会导致出现严重的遗忘。慢慢地微调会导致过度拟合和收敛。建议不要一次微调所有层,而是一次微调一个层(一次解锁一些层)。由于最后一层拥有大致的领域知识,因此最后一层之后进行解锁,然后我们可以在一次迭代中微调之前冻结的层。下一个较低的冻结层被解锁,重复进行这一过程,直到所有层都被微调并看到收敛。

文本分类的基于时间的反向传播 (BPTT)

在 RNN 中经常使用基于时间的反向传播 (BPTT) 来对数据进行排序。BPTT 通过展开所有时步来完成。每个时步包含一个输入、一个网络副本和一个输出。每个时步都会计算并累积网络生成的错误。网络回滚,并通过梯度下降更新权重。

diagram of BPTT

图 10.基于时间的反向传播 (BPTT)

使用上一批的最终状态对模型进行初始化。并跟踪均值和最大池的隐藏状态。在内核中,反向传播使用可变长度序列。以下是 PyTorch* 中使用的 Sampler 片段:

class SortishSampler(Sampler):
    def __init__(self, data_source, key, bs):
        self.data_source,self.key,self.bs = data_source,key,bs
    def __len__(self): return len(self.data_source)
    def __iter__(self):
        idxs = np.random.permutation(len(self.data_source))
        sz = self.bs*50
        ck_idx = [idxs[i:i+sz] for i in range(0, len(idxs), sz)]
        sort_idx = np.concatenate([sorted(s, key=self.key, reverse=True) for s in ck_idx])
        sz = self.bs
        ck_idx = [sort_idx[i:i+sz] for i in range(0, len(sort_idx), sz)]
        max_ck = np.argmax([self.key(ck[0]) for ck in ck_idx])  # find the chunk with the largest key,
        ck_idx[0],ck_idx[max_ck] = ck_idx[max_ck],ck_idx[0]     # then make sure it goes first.
        sort_idx = np.concatenate(np.random.permutation(ck_idx[1:]))
        sort_idx = np.concatenate((ck_idx[0], sort_idx))
        return iter(sort_idx)

因此,Sampler 返回一个迭代器(一个可以迭代的简单对象)。它在大小大致相同的随机排序批次中遍历数据。在第一次调用过程中,使用最大的序列,从而支持相应的内存分配排序。

所使用的技术

项目主要使用英特尔® AIDevCloud 完成令牌化。执行该任务时还(结合超线程,fast.ai 支持)使用了 spaCy*,因为它的计算成本很高。广泛的培训逐步进行,主要部分使用英特尔® 至强® 可扩展处理器集群完成,其余部分使用 NVIDIA GeForce* GTX 1080 图形处理单元完成。

结果

在 NLP 研究中,相比依赖词嵌入或某种形式的迁移学习的其他方法,这种方法的效果更好。在逐级解锁并用(上述讨论的)新方法训练分类器之后,仅仅 4 个 epoch 就轻松达到了 94.4 的准确度,击败了当今其他最高准确度。

表 1.使用 ULMFit 进行文本分类的损失和准确度

Epoch

训练损失

验证损失

准确度

1

0.210046

0.202856

0.942858

2

0.212139

0.149009

0.943746

3

0.21163

0.186739

0.946553

4

0.186233

0.1508

0.945218

5

0.176255

0.1504472

0.947985

6

0.198024

0.146215

0.948345

Validation error rate charts

图 11.验证错误率

所使用的英特尔开发工具

该项目在英特尔 AI DevCloudDevCloud(采用英特尔至强可扩展处理器)上使用 Jupyter* 笔记本编写代码并实现可视化。英特尔® 人工智能研究院论坛的信息也用于借助英特尔至强处理器进行优化。所使用的代码来源于 GitHub* 存储库 Jeremy Howard Fast.ai 初步实施。访问此处,了解有关本架构的部分优化调整。

加入英特尔® 人工智能研究院

注册英特尔人工智能研究院,访问必要的学习材料、社区、工具和技术,促进人工智能开发。申请成为人工智能校园大使,与其他学生数据科学家和开发人员分享专业知识。

通过 TwitterGitHub 或这个 网站,联系英特尔® 人工智能校园大使 Prajjwal Bhargava。

有关编译器优化的更完整信息,请参阅优化通知