大模型交叉研讨课-学习笔记2

Attention(在RNN中的应用)

注意力机制是为了解决encoder和decoder之间存在的信息瓶颈问题而提出的。因为decoder需要获取到encoder阶段的最后一个隐藏状态向量,然后用这个向量去一层一层迭代decoder,而最后一层隐藏向量或许没有能力包含输入的全部信息,因此存在信息瓶颈。

Attention计算步骤

  • encoder的隐藏状态 h 1 , h 2 … , h N ∈ R h h_1, h_2 \ldots, h_N \in \mathbb{R}^h h1,h2,hNRh
  • decoder在时间步 t t t的隐藏状态 s t ∈ R h s_t \in \mathbb{R}^h stRh
  • 计算当前时间步的注意力分数 e t = [ s t T h 1 , … , s t T h N ] ∈ R N e^t=\left[s_t^T h_1, \ldots, s_t^T h_N\right] \in \mathbb{R}^N et=[stTh1,,stThN]RN
  • 用softmax计算注意力分布。
    与RNN的主要区别在于,RNN直接将decoder的结果作为预测词向量,而加入注意力机制后,需要用decoder的 s t s_t st结合encoder的隐向量点积计算注意力分数(比较常见的一种计算方式),使得模型能关注到最大可能该关注的encoder的隐向量 h i h_i hi,然后softmax得到注意力分布 α 1 \alpha^1 α1,最终用注意力分布与encoder的隐向量加权求和得到一个输出向量,再将这个输出向量 o 1 o_1 o1与decoder的隐向量 s 1 s_1 s1进行拼接得到最终预测词的向量。
    在这里插入图片描述
    其中每个时间步decoder的隐向量都会作为输入传入decoder的下一个隐向量,直到最后一个时间步结束。
    在这里插入图片描述

数学表示

query向量对应上述的decoder的隐向量,value向量对应encoder的隐向量,其中给定一个query向量和一系列value向量,attention的本质就是根据这个query向量对value向量加权平均。
在这里插入图片描述

计算注意力分数

  • 点积(dot-produce)的方式:当encoder和decoder的隐向量维度相同时。
    e i = s T h i ∈ R e_i=s^T h_i \in \mathbb{R} ei=sThiR
  • multiplicative attention:当encoder和decoder隐向量维度不一样时,需要在中间乘一个权重矩阵使得它们能够相乘,最后得到一个标量。
    e i = s T W h i ∈ R , W ∈ R d 2 × d 1 e_i=s^T W h_i \in \mathbb{R}, \quad W \in \mathbb{R}^{d_2 \times d_1} ei=sTWhiR,WRd2×d1
  • additive attention:使用一个前馈神经网络将encoder和decoder的隐向量变成一个标量。 W 1 W_1 W1 W 2 W_2 W2是两个权重矩阵, v T ∈ R d 3 v^T \in \mathbb{R}^{d_3} vTRd3是一个权重向量。其中 W 1 ∈ R d 3 × d 1 , W 2 ∈ R d 3 × d 2 W_1 \in \mathbb{R}^{d_3 \times d_1}, W_2 \in \mathbb{R}^{d_3 \times d_2} W1Rd3×d1,W2Rd3×d2 t a n h tanh tanh是一个激活函数。
    e i = v T tanh ⁡ ( W 1 h i + W 2 s ) ∈ R e_i=v^T \tanh \left(W_1 h_i+W_2 s\right) \in \mathbb{R} ei=vTtanh(W1hi+W2s)R

Attention解决的问题

  • RNN中信息瓶颈问题
  • RNN中梯度消失问题:在encoder和decoder之间提供一种直接连接的方式,防止梯度在RNN中传播过长而消失。
  • 给神经网络提供了一定可解释性。

Transformer

总体结构:encoder+decoder
输入层:byte pair encoding (BPE) + 位置编码
模型:多个encoder和decoder的堆叠,每个encoder或decoder结构相同,只是参数不同。
输出层:经过线性变换和softmax输出词的概率分布。
损失函数:交叉熵损失。
在这里插入图片描述

输入层

Byte Pair Encoding(BPE)

  • 词表由语料库中所有出现的字母开始。如下图语料库中low频率为5;lower频率为2;newest频率为6;widest频率为3。初始词表为组成这些词的字母集合{l,o,w,e,r,n,s,t,i,d}.
  • 找到出现频率最高的n-gram,并将他们替换原词表中的字母。n=2时,“es”出现频率最高为9,因此把“es”加入词表,而由于“s”不会再单独出现,于是在词表中删除,以此类推。
  • 直到词表达到预期大小。最终词表中的每个结果对应一个token。
    在这里插入图片描述
    BPE解决了OOV(out of vocabulary)问题,因为原本按照空格切分的方法很难穷举完语料库中所有单词,而BPE使用subword unit能表示更多的词。

positional encoding

Transformer与RNN不同,RNN是通过从左到右的方式进行编码从而保留了句子中的位置信息,如果没有位置编码,Transformer将不能捕获词之间的位置信息。因为Transformer block(每一个encoder或decoder叫做encoder/decoder block)对不同位置的相同词不敏感。

不同位置的词表示不同:
P E ( pos  , 2 i ) = sin ⁡ (  pos  / 1000 0 2 i / d ) P E ( pos  2 i + 1 ) = cos ⁡ (  pos  / 1000 0 2 i / d ) \begin{aligned} & P E_{(\text {pos }, 2 i)}=\sin \left(\text { pos } / 10000^{2 i / d}\right) \\ & P E_{(\text {pos } 2 i+1)}=\cos \left(\text { pos } / 10000^{2 i / d}\right) \end{aligned} PE(pos ,2i)=sin( pos /100002i/d)PE(pos 2i+1)=cos( pos /100002i/d)
其中$pos 当前 t o k e n 在句子中的位置, 当前token在句子中的位置, 当前token在句子中的位置,i 是 e m b e d d i n g 的 i n d e x , 是embedding的index, embeddingindexd$是BPE结果(词表构建完之后,输入样本能直接从词表得到一个向量)的维度。
最终输入层的结果为BPE+Position Encoding

Transformer block

encoder由多头注意力层和前馈网络(本质上是一个带激活函数的两层MLP(多层感知机,即多层神经网络)全连接)组成。
在这里插入图片描述
其中运用到了残差链接(将输入和输出直接相加缓解梯度消失。图中直接指向Add&Norm的线。)和正则化(将输入向量变为均值为0,方差为1的一个分布。图中的Add&Norm)两个技巧,用于缓解梯度消失或爆炸问题。

Attention(在Transfomer中的应用)

输入:

  1. 给定一个query向量和key-value对向量的集合
  2. query和key向量维度都是 d k d_k dk
  3. value向量维度为 d v d_v dv
    输出:
  4. values向量的权重之和
  5. 通过query和key的点积计算每一个value的权重。
    A ( q , K , V ) = ∑ e q ⋅ k i ∑ j e q ⋅ k j v i A(q, K, V)=\sum \frac{e^{q \cdot k_i}}{\sum_j e^{q \cdot k_j}} v_i A(q,K,V)=jeqkjeqkivi
  6. 通过softmax计算注意力分布。这里用矩阵 Q Q Q表示多个query。
    A ( Q , K , V ) = softmax ⁡ ( Q K T ) V A(Q, K, V)=\operatorname{softmax}\left(Q K^T\right) V A(Q,K,V)=softmax(QKT)V
    在这里插入图片描述
    上述做法随着 d k d_k dk的增大, q T ⋅ k q^T \cdot k qTk的方差将会增加,也就是说得到的分布将会更加尖锐,有某个地方可能为1,其余地方趋近于0。这会导致梯度越来越小,为了解决这个问题需要加入scale(缩放)过程。 A ( Q , K , V ) = softmax ⁡ ( Q K T d k ) V A(Q, K, V)=\operatorname{softmax}\left(\frac{Q K^T}{\sqrt{d_k}}\right) V A(Q,K,V)=softmax(dk QKT)V
    在这里插入图片描述
self-attention

让词向量自己选择需要关注哪些token。Q、K、V都是通过词向量乘上一个变换矩阵得的。对于第一层block来说,词向量是BPE和位置编码之后的结果对于非第一层block来说,词向量是上一层block的输出。
在这里插入图片描述

Multi-head Attention

由相同的结构自注意力一致,只是参数不同。每个注意力头都有自己的 W q 、 W k 、 W v W_q、W_k、W_v WqWkWv
对于 h h h个头,有如下表示:
 head  i = A ( Q W i Q , K W i K , V W i K )  MultiHead  ( Q , K , V ) = Concat ⁡ (  head  1 , … ,  head  h ) W O \begin{aligned} & \text { head }_i=\mathrm{A}\left(Q W_i^Q, K W_i^K, V W_i^K\right) \\ & \text { MultiHead }(Q, K, V)=\operatorname{Concat}\left(\text { head }_1, \ldots, \text { head }_h\right) W^O \end{aligned}  head i=A(QWiQ,KWiK,VWiK) MultiHead (Q,K,V)=Concat( head 1,, head h)WO
其中 W O W^O WO表示线性层的权重矩阵。
decoer与上述encoder过程基本一致(decoder需要遵循从左到右的生成的方式,不要参考后面的信息),不同点有两个:

  1. 在多头注意力上加了Mask(通过限制 q × k q \times k q×k的结果中上三角部分全为负无穷,经过softmax,这些位置的概率会变为0),使得词只能看到先前的词。
  2. 这里的多头注意力(第二个)的输入中, K 、 V K、V KV向量来自于最后一层encoder的输出, Q Q Q向量才是来自于decoder(类似于RNN中的应用)。
    在这里插入图片描述
    在这里插入图片描述

Transformer的优缺点

优点

  • 是一个强有力的模型,并已被证实对许多NLP任务是有效的。
  • 适合并行计算,能更好的利用GPU资源。
  • 证明了attention机制的有效性
  • 为一些NLP的SOTA带来灵感,例如bert、gpt
    缺点
  • 结构难以优化,对参数比较敏感。对超参数、优化器的选择可能对模型性能造成很大影响。
  • 每一层的复杂度为 O ( n 2 ) O(n^2) O(n2) n n n代表处理文本的长度,通常限制为512。

预训练语言模型(PLM)

语言模型

任务:给定部分词预测下一个词。 P ( w n ∣ w 1 , w 2 , ⋯   , w n − 1 ) P\left(w_n \mid w_1, w_2, \cdots, w_{n-1}\right) P(wnw1,w2,,wn1)(能迁移到别的NLP任务),常见的有Word2vec、GPT、Bert…
语言模型包含大量语言理解的知识,例如语言知识和事实知识;并且语言模型只需要纯文本进行训练,不需要人工标注。

预训练语言模型

预训练语言模型分为两种范式:

  • 基于特征的范式:直接将PLMs的输出作为下游任务的输入,例如特征提取。最具代表性的是Word2vec
  • 微调的范式:语言模型也会作为下游任务的模型,并且会更新他们的参数。最具代表性的是Bert。

GPT

GPT是第一个基于Transformer的预训练语言模型。它利用了Transformer+left-to-right LM的方式,并在下游任务进行微调。
GPT用12层的Transformer的decoder在无监督预料上进行训练,然后在下游任务,例如分类任务上进行预测。
在这里插入图片描述

GPT-2

与GPT相比,提升了transformer的参数量,使用更大规模的预训练语料训练了40GB的文本。可以零样本学习。
GPT系列效果好的主要原因是:

  1. 模型从大规模的预训练语料中学习
  2. 使用了高效的transformer的decoder

Bert

在bert之前的LM是单向的,要么只用左边要么只用右边的内容,但是语言理解通常是双向的,既要考虑左边也要考虑右边的信息。
而语言模型设计为单向的原因是:

  1. 单项的模型能生成更好的概率分布。
  2. 双向的模型可能产生信息泄露问题。例如下图中在预测“a”时,模型会看到这个词,模型就不学习和推理,直接shortcut出需要预测的词。
    在这里插入图片描述
    Bert为了解决信息泄露问题,提出了掩码语言模型,这也是Bert的预训练任务之一,。Bert采用随机mask15%的词,在最后一层让模型还原被mask掉的词。
    在这里插入图片描述
    而使用mask策略会引起mask的token在微调时没有见过,会导致预训练和微调时的差异,模型可能会只关注mask的token。为了解决这个问题,针对15%的mask的词,分为3种小策略:
  3. 80%的时间,会直接用[MASK]替代需要被掩码的词。
  4. 10%的时间,随机用一个词来替代需要被掩码的词。
  5. 10%的时间,保持不变。
    Bert的另一个预训练任务是下一个句子预测,能够学习句子间的关系。 利用标签来确定两个句子是否相邻。
    在这里插入图片描述
    Bert的输入:起始[CLS]和结束[SEP]都是两个特殊标签,Segment Embedding在预训练时就是两个句子是否相邻的embedding。最后将三个部分的embedding加起来作为输入。
    在这里插入图片描述

RoBERTa

由于mask引起的问题,导致模型中有效学习的只有15%。
对Bert进行改进:提升BERT的鲁棒性。

  • 动态的mask
  • 模型的输入格式
  • 下一个句子预测是否必要
  • 用更大的batch size效果更好
  • 文本编码(encoding)

PLM Family

在这里插入图片描述

Trsnformer实战

Pipeline接口

# 使用pipeline,直接调用一个PLM,没有微调
from transformers import pipeline
# 直接传需要做的任务,transformer会给你找一个相应任务的plm,会自动下载。
classifier = pipeline('sentiment-analysis')
# 传入数据
cls = classifier('I love you!')
# 输出:[{'label': 'POSITIVE', 'score': 0.9998782873153687}]
print(cls)

在PLM上用自己数据进行fine-tune

在PLMs上微调的步骤通常是:
例子是用SST-2数据集做情感分析任务。

  • 加载数据集和评价指标,
from datasets import load_dataset, load_metric
import numpy as np
# 加载GLUE中的sst2
data = load_dataset("glue", "sst2")
metric = load_metric("glue", "sst2")

# 随机生成ground-truth和预测的结果,用于演示metric的使用
fake_preds = np.random.randint(0, 2, size=(64,))
fake_labels = np.random.randint(0, 2, size=(64,))
# metric的使用.输出为:{'accuracy': 0.5}
metric.compute(predictions=fake_preds, references=fake_labels)
  • 对数据进行tokenize
from transformers import AutoTokenizer
# 对数据进行tokenization,使用的是bert的base版本,uncased代表不区分大小小,全转为小写。
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
'''
tokenize"I love you!"的结果:
     {'input_ids': [101, 1045, 2293, 2017, 999, 102], 
      'token_type_ids': [0, 0, 0, 0, 0, 0], 
      'attention_mask': [1, 1, 1, 1, 1, 1]}
input_ids代表每个token对应词表中的id,
token_type_ids代表两个句子是否相邻,这里全0是因为我们只有一个句子.
若有两个句子,第一个句子的token全为0,第二个句子全为1.
attention_mask代表需要模型去attention的tokens,该例子中
全为1,如果有补全,补全的位置应该置为0.
'''
tokenizer("I love you!")
  • 利用tokenizer对数据进行预处理
# 利用tokenizer对数据进行预处理
def preprocess_function(example):
	# 传入truncation,将长度超过512的句子截断
    return tokenizer(example['sentence'], truncation=True)
# 用sst2的训练集中前5个样例进行验证预处理函数
'''
输出结果:
{'input_ids': [[101, 5342, 2047, 3595, 8496, 2013, 1996, 18643, 3197, 102],
           [101, 3397, 2053, 15966, 1010, 2069, 4450, 2098, 18201, 2015, 102], 
           [101, 2008, 7459, 2049, 3494, 1998, 10639, 2015, 2242, 2738, 3376, 2055, 2529, 3267, 102], 
           [101, 3464, 12580, 8510, 2000, 3961, 1996, 2168, 2802, 102], 
           [101, 2006, 1996, 5409, 7195, 1011, 1997, 1011, 1996, 1011, 11265, 17811, 18856, 17322, 2015, 1996, 16587, 2071, 2852, 24225, 2039, 102]],
'token_type_ids': [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
           [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], 
'attention_mask': [[1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 
           [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]}

'''
preprocess_function(data['train'][:5])

# 用到我们自己数据集上,用批处理的方式。map函数会将preprocess_function映射到每一个数据样本,map迭代一轮是1000次。
encoded_data = data.map(preprocess_function, batched=True)

  • 加载模型
# 导入自己需要用到的模型
from transformers import AutoModelForSequenceClassification
# 载入模型,num_labels代表分类标签的种类,根据数据集来给定。
model = AutoModelForSequenceClassification.from_pretrained('bert-base-uncased', num_lables=2)
  • 使用Trainer类来进行微调
# 导入trainer参数这个包
from transformers import TrainingArguments
# 导入训练器
from transformers import Trainer

# 定义一个函数,告诉trainer怎么计算指标。即根据哪个指标来选取模型作为我们的最终模型。
def compute_metrics(eval_preds):
    logits, labels = eval_preds  # labels:[batch_size,]
    # 将概率最大的类别作为预测结果
    predictions = np.argmax(logits, axis=1)  # predictions:[batch_size,num_labels]
    return metric.compute(predictions=predictions, references=labels)

# 构建要传给trainer的参数字典
batch_size = 16
args = TrainingArguments(
    'bert-base-uncased-finetuned-sst2',# 本次训练的名称
    evaluation_strategy='epoch',# 每个epoch结束评价一次
    save_strategy='epoch',# 每个epoch结束保存一个checkpoint
    learning_rate=2e-5,# 学习率
    per_device_train_batch_size=batch_size,# 训练时,每个gpu上的batch_size,一次性训练多少个样例
    per_gpu_eval_batch_size=batch_size,
    num_train_epochs=5,# 训练的epoch数量
    weight_decay=0.01,
    load_best_model_at_end=True,# 训练结束后加载过程中效果最好的checkpoint
    metric_for_best_model='accuracy'# 以准确率作为指标
)

# 初始化trainer
trainer = Trainer(
    model,
    args,
    train_dataset=encoded_data['train'],
    eval_dataset=encoded_data['validation'],
    tokenizer=tokenizer,
    compute_metrics=compute_metrics
)
# 开始训练
trainer.train()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值