06_迁移学习

1 迁移学习

早期 NLP 领域针对每个具体任务单独构建并从头训练模型,这种方式不仅严重依赖高质量的标注数据,且模型之间无法共享语言知识,导致训练成本高昂、泛化能力差,

迁移学习是机器学习的一种思想或者说方法论,核心是将一个模型在一个任务中学到的知识(特征、权重等),应用到另一个任务中

在过渡阶段,Word2Vec、GloVe 和 fastText 都是早期类似迁移学习的思想,核心是基于特征的迁移。先在大规模文本上预训练生成静态词向量,将其捕捉到的语义信息作为查找表初始化下游模型的嵌入层。虽然复用了词汇层面的知识,但难以解决多义词和复杂语境的问题。

现在主流的迁移学习方式是 预训练 + 微调

  • 预训练(Pre-training):利用海量无标注数据进行自监督学习,训练一个通用的语言模型(如 BERT, GPT, T5),使其掌握词法、句法及上下文语义等通用语言规律。
  • 微调(Fine-tuning):在通用模型的基础上,使用少量特定任务的标注数据,对模型参数进行微小的调整与适配,使其迅速掌握特定领域的业务逻辑。

这种模式就像先读完大学(预训练),再进行岗前培训(微调)一样,极大地降低了下游任务的数据门槛和训练成本。

2 fastText

2.1 概述

fastText 主要用于两个核心任务:

  1. 高效的文本分类(Text Classification)
    • 模型结构简单:使用简单的浅层神经网络。
    • 训练速度快:使用层次 Softmax 进行优化,可以在数十亿词汇的语料库上几分钟内完成训练。
    • 性能优异:尽管模型简单,但在文本分类任务上的表现可以与深度学习模型相媲美。
  2. 学习词向量(Word Embeddings)
    • 子词 (Subword) 信息:fastText 最大的特色。它不是直接将一个完整的词映射为一个向量,而是将一个词分解成多个 n-gram 字符序列。
      • 处理未登录词 (Out-of-Vocabulary, OOV): 即使一个词没有出现在训练集中,只要它的子词 n-gram 出现过,fastText 仍然可以根据这些子词向量来构建该词的向量表示。
      • 处理词形变化: 对于像 “run” 和 “running” 这样有词形变化的词,它们会共享一些 n-gram 子词,从而使得它们的词向量更加接近。

2.2 优化技术

如果样本有 10 万个词,标准 Softmax 在训练每个样本时都需要做 10 万次指数和加法运算,然后对这 10 万个词进行多元交叉熵损失计算,效率非常低下。

为了加速训练,Word2Vec 和 fastText 引入了层次 Softmax 和负采样:

  • 层次 Softmax:将模型的输出层变成了一个哈夫曼树,只需要做十几次二分类任务。
  • 负采样:不再计算所有类别,而是变成了一个简单的分类器,只需要计算 1 个正样本和几个负样本的得分,损失函数是变体。

通常二者是二选一的,层次 Softmax 由于树结构的优势,通常更适用于文本分类,而负采样训练的词向量效果更好。

2.2.1 层次Softmax

层次 Softmax 的将全量查找转换为树结构查找,从而大大提高效率。

  1. 首先使用所有词构建哈夫曼树,每个词都是树的一个节点,出现频率高的词离根结点更近,频率低的词离根节点更远。
  2. 将问题转换为一系列二分类问题,最终节点的概率值为路径上节点的概率乘积。

计算量从标准 Softmax 的 O(V) 降为 O(logV)。

2.2.2 负采样

核心思想其实非常简单,模型学习正例和几个负例就够了,其他的负例可以忽略。

就像我们教小孩辨认大象,只需要给小孩子看大象的照片(正例),再随便看几个别的东西(负例),告诉他这不是大象就够了,而不需要指着世界上其他的所有东西一个一个告诉他这不是大象。

比如当前词表有 10 万词,当前预测目标是 “man” 这一个词,如果采用负采样,不会对这 10 万个词都计算损失并更新参数,而是

  1. 选取 1 个正例(“man”)
  2. 从词表随机选取 k 个负例
  3. 最大化正例的概率,最小化这 k 个负例的概率

这就把一个巨大的多分类问题,简化成了 k+1 的简单分类问题,k 通常取 5~20。

实际上不是完全的随机抽,而是高频词被抽到的概率更高。

2.3 文本分类

文本分类是将文档(例如帖子,产品评论等)分配给一个或多个类别。

  • 二分类:通常是对立的,比如好评差评
  • 单标签多分类:每条文本只能属于一个类别,比如判断人名属于哪个国家
  • 多标签多分类:每条文本可以属于多个类别,比如帖子分类,可能既属于科技,又属于美食
  1. 获取数据
1
2
3
with open('data/cooking_train.txt', 'r') as f:
for i in range(5):
print(f.readline(), end='')
__label__sauce __label__cheese how much does potato starch affect a cheese sauce recipe ? 
__label__food-safety __label__acidity dangerous pathogens capable of growing in acidic environments
__label__cast-iron __label__stove how do i cover up the white spots on my cast iron stove ? 
__label__restaurant michelin three star restaurant; but if the chef is not there
__label__knife-skills __label__dicing without knife skills ,  how can i quickly and accurately dice vegetables ? 

每段文本可以有多个标签,所有标签均以前缀__label__开头,这是 fastText 识别标签的格式,标签之后的一段话就是文本信息。

  1. 模型训练
1
2
3
4
5
6
7
8
9
import fasttext

model = fasttext.train_supervised(
input='data/cooking_train.txt',
epoch=100, # 增加训练轮数
lr=1, # 增大学习率,加快收敛
wordNgrams=3, # 使用 3-gram,提升模型理解能力
loss='hs', # 使用层次 Softmax,性能小幅下降,但显著提升训练效率
)
Read 0M words
Number of words:  8952
Number of labels: 735
Progress: 100.0% words/sec/thread: 2192661 lr:  0.000000 avg.loss:  1.477953 ETA:   0h 0m 0s
  1. 模型预测
1
2
3
# 输入一段文本进行预测
model.predict('Michelin Three Star Restaurant; but if the chef is not there')
# 预测结果是二元组,分别是预测标签和预测概率
(('__label__restaurant',), array([0.83001912]))
  1. 模型评估
1
2
model.test('data/cooking_valid.txt')
# 预测结果分别是 验证集数量, Precision, Recall
(3000, 0.605, 0.26164047859305173)

手动调节超参数费时费力,fastText 提供了自动调节超参数的功能。自动调节超参数时,有以下参数可以调节:

  • autotuneValidationFile: 用于验证的文件路径。
  • autotuneDuration: 最大训练时间(秒),默认为 300 秒。
  • autotuneModelSize: 模型大小(small, medium, large)。
1
2
3
4
5
model = fasttext.train_supervised(
input='data/cooking_train.txt',
autotuneValidationFile='data/cooking_valid.txt',
autotuneDuration=60
)
Progress: 100.0% Trials:   10 Best score:  0.334910 ETA:   0h 0m 0s
Training again with best arguments
Read 0M words
Number of words:  8952
Number of labels: 735
Progress: 100.0% words/sec/thread:   23056 lr:  0.000000 avg.loss:  6.539281 ETA:   0h 0m 0s
1
model.test('data/cooking_valid.txt')
(3000, 0.5483333333333333, 0.23713420787083753)

使用 softmax 只能最大化一个标签,针对多标签分类任务,更好的方式是为每个标签使用独立的二分类器作为输出层结构,可以使用 ova——— one vs all,同时训练多个二分类模型。

1
2
3
4
5
6
7
model = fasttext.train_supervised(
input='data/cooking_train.txt',
epoch=150, # 增加训练轮数
lr=0.3, # 二分类任务不宜使用过大的学习率
wordNgrams=3, # 使用 3-gram,提升模型理解能力
loss='ova', # 使用多个二分类
)
Read 0M words
Number of words:  8952
Number of labels: 735
Progress: 100.0% words/sec/thread:  185397 lr:  0.000000 avg.loss:  0.985614 ETA:   0h 0m 0s

预测时还有一些参数可以指定:

  • k:返回最可能的 k 个标签,-1 指返回所有标签
  • threshold:过滤概率大于指定阈值的标签
1
2
3
4
5
model.predict(
text='how much does potato starch affect a cheese sauce recipe ?',
k=-1,
threshold=0.5
)
(('__label__sauce', '__label__cheese'), array([0.99947423, 0.99859965]))
1
2
3
4
5
model.test(
path='data/cooking_valid.txt',
k=-1,
threshold=0.5
)
(3000, 0.6963788300835655, 0.21623180049012541)
  1. 模型保存与加载
1
2
3
4
5
# 保存模型
model.save_model('model/cooking_model.bin')

# 加载模型
model = fasttext.load_model('model/cooking_model.bin')

2.4 训练词向量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 训练模型
model = fasttext.train_unsupervised(
'corpus.txt', # 语料路径
model='cbow', # 模型类型
dim=100, # 词向量维度
epoch=5, # 训练轮数
lr=0.1, # 学习率
thread=6 # 线程数

# 获取词向量
model.get_word_vector('中国')

# 查找最相似的词
model.get_nearest_neighbors('中国')

3 预训练模型分类

几乎所有的预训练模型都在 Transformer 基础上构建,根据结构差异,大致可以分为三类:

4 GPT

GPT(Generative Pre-trained Transformer)核心思想是通过大规模无监督语料进行生成式语言建模预训练,训练模型根据上文预测下一个词,从而让模型学习自然语言的通用语法、语义和上下文依赖能力。完成预训练后,再通过微调适应具体的下游任务。

4.1 核心结构

  • 输入嵌入层(Text & Position Embedding):同样由词嵌入和位置编码组成,不同点在于 GPT 的位置编码采用可学习的位置嵌入,模型可以在训练过程中自动优化位置参数。
    • Text Embedding(词嵌入):将输入的文本序列映射为向量表示。
    • Position Embedding(位置嵌入):将位置信息映射为向量表示。
    • 每个 token 的表示为词嵌入和位置编码的和,维度为 768。
  • 解码器:由 12 个解码器层堆叠而成,由于取消了解码器,因此不需要编码器-解码器注意力层,每个解码器层包括两个子层:
    • 掩码多头自注意力:使用 12 个注意力头
    • 前馈神经网络
  • 输出层:根据任务不同,可以接入不同的任务头
    • Text Prediction(文本预测):输出是经过 softmax 之后,得到词表大小的概率分布,用于下一个词的生成,预训练阶段使用的就是这个任务头
    • Task Classifier(任务分类):微调阶段使用,通过提取特定位置的表示对文本进行分类,比如情感极性分类,话题识别等。

4.2 预训练

GPT 的预训练阶段采用生成式语言建模(Generative Language Modeling)作为训练目标。具体来说就是基于已观察到的上文,预测当前词的位置应出现的词,在这个过程中学习自然语言的统计规律与上下文依赖关系。

这种自回归语言建模方式不依赖人工标注,训练样本可以直接从原始文本中自动构建,极大地降低了构建数据的成本。

4.3 微调

在完成无监督的预训练之后,需要针对下游任务进行微调,核心是在保留预训练语言建模能力的基础上,利用有监督数据对整个模型进行端到端优化,从而实现知识迁移。

  • 输出:在输出端引入线性层,根据下游任务的需要,将 GPT 的隐状态映射为标签或具体值。
  • 输入:在输入端统一输入的格式,方便 GPT 使用连续文本自回归生成。主要是开始标记 Start、结束标记 Extract和隔断标记 Delim

由于 GPT 是基于自回归的,只有最后一个词包含整句话的完整语义,因此在进行句级分类时,通常取最后一个位置,也就是 Extract 对应的输出送入线性层。

GPT 在保留预训练模型结构和参数的基础上,仅添加极少量新参数(如线性层),便可高效完成从语言建模到多种下游任务的迁移。

5 BERT

BERT(Bidirectional Encoder Representations from Transformers)核心在于使用了编码器结构,由于是没有掩码的双向自注意力机制,每个 token 都融合了上下文的所有信息,因此能获得比 GPT 更加准确丰富的语义信息,BERT 发布后曾在各项基准测试中霸榜。

预训练完成得到每个 token 的表示后,通过微调适应下游任务。由于 BERT 更加侧重自然语言理解,因此广泛应用在文本分类、序列标注、句子匹配等场景。

5.1 核心结构

BERT 仅仅使用 Transformer 的编码器结构,不像 GPT 那样可以自回归生成,BERT 进行了一些非常巧妙的设计,稍后介绍。

  • 输入嵌入层:BERT 在 GPT 的基础上,额外添加了一个句嵌入向量。
    • Token Embedding:词向量表示。
    • Position Embedding:位置向量表示,也是可学习的向量。
    • Segment Embedding:区分句子对任务的两个句子,各用一个可学习向量表示。

  • 编码器:结构与 Transformer 编码器相同,具体参数有两个版本。
    • Bert-base:对标 GPT,12 层,12 个注意力头,模型维度 768。
    • Bert-large:24 层,16 个注意力头,模型维度 1024。
  • 输出层:根据下游任务不同,接入不同任务头。
    • 词级(Token-Level)任务:如实体识别,使用每个位置的表示。
    • 句级(Sequence-Level)任务:如文本分类,使用特殊 token 的输出表示。一般是使用 CLS,在序列开头专门汇总整个序列的语义信息。

5.2 预训练

BERT 的解码器结构,使其不能像 GPT 那样可以在自回归中,通过上文预测当前词的方式训练模型,使其学习自然语言规律。因此 BERT 精心设计了两个核心任务,分别用于学习词级语义和句间逻辑关系

  • 掩码语言模型(Masked Language Modeling, MLM)
  • 下一句预测(Next Sentence Prediction, NSP)

5.2.1 MLM

在此之前的传统语言模型,都是采用 left-to-right,或者 left-to-right + right-to-left 结合的方式,但这种单向或拼接的方式提取语义能力有限。BERT 提出了深度双向表达模型(deep bidirectional representation),也就是通过掩码语言模型训练。

在训练过程中,BERT 会随机遮盖输入序列中 15% 的 token,训练模型通过上下文预测被遮盖的 token。而在这 15% 遮盖的 token 中,还采用了 811 策略:

  • 80% 的 token 替换为 [MASK]
  • 10% 的 token 替换为随机词
  • 10% 的 token 保持原词

这种机制下,模型既能看到上文,也能看到下文,真正实现了深度双向表达。

模型在训练时,并不知道哪些词是要预测的,哪些词是原始的,哪些词是被遮罩的,哪些词是替换为其他单词的。在这种高度不确定中,使模型能够更快地学习自然语言规律,并且由于总遮盖只有 15%,也不会破坏语言原本的结构。

5.2.2 NSP

在 NLP 中有一类句子对问题,比如 QA(Quention-Answer), NLI(Natural Language Inference), 需要模型能够很好的理解两个句子之间的关系。为了提升模型理解句间关系的能力,BERT 引入了 NSP 任务。

设计非常简单,输入句子对(A, B), 模型来预测句子 B 是不是句子 A 的真实后续句。

  • 50% 的训练样本 B 是 A 的真实后续句(正样本)
  • 50% 的训练样本 B 是随机抽取的句子(负样本)

5.2.3 预训练过程

BERT 在预训练时,输入两个句子对,并随机遮盖,在开头加上 CLS 特殊标记,在句子间和结尾加上 SEP 标记。分别计算 MLM 和 NSP 的损失并求和,可以同时优化这两个任务目标,学习词级语义和句间逻辑关系。

5.3 微调

BERT 在预训练完成后,微调时模型主体结构保持不变,在顶部根据特定下游任务添加一个输出层,使用下游具体的标注数据进行训练。

输入格式和预训练阶段基本一致,开头添加CLS,句间和结尾添加SEP,不同点在于使用哪些位置的表示,经过怎样的输出层映射。

BERT 在原论文中展示了下图四类微调方式:

    1. 句子对分类任务:
    • 输入:[CLS]句子1[SEP]句子2[SEP]
    • 输出:使用CLS作为句表示作为整个序列的表示,接入线性层做具体的分类。
      • MNLI:Multi-Genre Natural Language Inference,多类别句子蕴含判断
      • QQP: Quora Question Pairs,问句语义重复判断
      • QNLI: Question Natural Language Inference,判断句子是否为问题的答
      • STS-B: Semantic Textual Similarity Benchmark,语义相似度回归
      • MRPC: Microsoft Research Paraphrase Corpus,句子复述判断
      • RTE: Recognizing Textual Entailment,二分类蕴含判断
      • SWAG: Situations With Adversarial Generations,多项选择填句任务
    1. 单句分类任务
    • 输入:[CLS]句子[SEP]
    • 输出:同样使用CLS作为句表示作为整个序列的表示,接入线性层做具体的分类。
      • SST-2: Stanford Sentiment Treebank (binary),情感极性判断(二分类)
      • CoLA: Corpus of Linguistic Acceptability,语法可接受性判断(二分类)
    1. 问答任务
    • 输入:[CLS]问题[SEP]段落[SEP]
    • 输出:使用每个位置的输出,分别预测它们作为答案起始位置和结束位置的概率,最终确定答案在原始段落中的起始位置和结束位置,抽取连续的文本作为答案。
      • SQuAD v1.1:Stanford Question Answering Dataset 抽取式问答(起止定位)
    1. 单句标注任务
    • 输入:[CLS]文本[SEP]
    • 输出:接入线性层,对每个位置的输出分别预测类别。
      • NER:Named Entity Recognition,命名实体识别

6 T5

T5(Text-to-Text Transfer Transformer)使用 Transformer 完整的编码器-解码器架构,核心思想是将所有自然语言处理任务统一表示为文本到文本的转换问题(Text-to-Text Framework),这一思想使得 T5 可以使用同一模型架构和预训练机制完成多种任务。

下图展示了翻译任务、语法判断、句子相似度、摘要任务,都是输入文本,模型输出文本。

T5 的核心结构和 Transformer 一致,不再赘述。

6.1 预训练

T5 的作者在论文中对比了多种预训练策略(如类似 BERT 的 MLM、类似 GPT 的 CLM 等),最终发现效果最好的是一种被称为 Span-Corruption(基于片段的去噪) 的目标。基本上可以认为是一种填空任务,但比 BERT 的单字填空更高级。相比与 BERT 的每次只 MASK 一个,T5 每次 MASK 一个片段,使模型学习片段内的依赖关系和更长的上下文语义。

  1. 随机掩码(Masking):模型会随机选中输入文本中的连续片段(Spans)进行掩盖。
  2. 哨兵标记(Sentinel Tokens):被掩盖的片段会被替换为一个唯一的哨兵标记(Sentinel Token),通常表示为 , 等。
  3. 生成目标(Target): 模型需要生成被掩盖的片段内容,每个片段前加上对应的哨兵标记,并在最后加上结束标记。

这种架构既保留了编码器的双向建模能力,又提供了编码器的生成式学习信号,使模型能够更轻松地适配下游任务。

6.2 微调

T5 最具革命性的地方在于它的微调方式。在 BERT 时代,针对不同的任务,我们需要在模型顶部添加不同的任务头(Task Head),比如一个全连接层用于分类。

但在 T5 中,不需要改变模型结构。由于所有任务都是“输入文本 -> 输出文本”形式,T5 只需要在输入文本前添加一个 文本提示(Prompt/Prefix) 来告诉模型当前要做什么任务。

比如:

  • 机器翻译:“translate English to Chinese: That is cool.” -> “那很酷。”
  • 情感分类:“sentiment: This movie is so bad.” -> “negative”
  • 文本摘要:“summarize: 长文本” -> “摘要”

当然提示可以不是上述单词,也可以有很多别的,不过输出都有一个共同点,那就是输出即标签,而不是概率分布,并且同一个模型可以同时通过多任务学习掌握上述所有能力。


06_迁移学习
https://zhubaoduo.com/2024/08/24/大模型开发/06_自然语言处理/06_迁移学习/
作者
baoduozhu
发布于
2024年8月24日
许可协议