预训练机制(2)~Attention mechanism、Transformer

目录

1. Attention Mechanism

1.1 Background

1.1.1 时代背景

1.1.2 模型背景

1.2 Attention Algorithm

1.3 Scaled Dot-Product Attention 

1.3.1 attention scale缩放:

1.4 Self-Attention 自注意力机制

1.4.1 Q、K、V的获取

1.4.2 matmul for similarity

1.4.3 softmax + scale

1.4.4 matmul for new vector z1

1.4.5 self-attention summary

1.4.6 Attention和Self-Attention区别

1.4.7 Selft-attention和RNNs的区别

1.4.8 masked self-attention 实现-1

1.4.9 self-attention 实现-2

1.5 Masked Self-Attention

1.6 Multi-head Self-Attention (MHA)

1.6.1 multi-head self-attention algorithm

1.6.2 multi-head self-attention意义

1.6.3 pytorch实现 multi-head attention

1.7 Positional Encoding

1.7.1 positional encoding algorithm

1.7.2 positional encoding 实现

2. Transformer(Encoder-Decoder Architecture)

2.1 Transformer 编码器 Encoder 

2.1.1 Encoder 总结

2.2 Transformer 解码器 Decoder

2.2.1 为什么Decoder需要做mask

2.2.2 为什么Encoder给予Decoders的是K、V矩阵 

2.3 Transformer 动态流程

2.4 Transformer疑问解答

2.4.1 为什么用张量tensor?

2.4.2 词向量input必须是one-hot吗?换成word2vec可以提升模型performance吗?

2.4.3 transformer强大之处

2.5 pytorch 实现Transformer

extension:  python面向对象--封装(class类、__init__初始化函数)

2.5.1 残差函数add、归一化Norm

2.5.2 Feed Forward layer(FFN)

2.5.3 Linear层 + Softmax层

2.5.4 masked multi-head self-attention

2.5.5 构建编码器 encoder

2.5.6 解码器 decoder

2.5.7 整体Transformer

2.6 Huggingface transformers package

2.6.1 background

2.6.2 AutoClass介绍

2.6.3 分词器对象 AutoTokenizer

2.6.4 配置文件 AutoConfig

2.6.5 创建预训练模型 AutoModel

2.7 “调包侠”实现transformer、bert

2.7.1 “调包侠”实现transformer

2.7.2 “调包侠”实现bert

2.8 bert project application

参考
​​​​​​​


1. Attention Mechanism

1.1 Background

1.1.1 时代背景

理论基础:类似于CNN中"感受野"receptive field的思想,attention mechanism从全部到只关注重点。

 大数据背景,什么数据都有,重要的,不重要的。但model很难自己区分重要的内容和不重要的内容,--> attention mechanism.

1.1.2 模型背景

problem:在机器翻译时,RNN(极限50词)、LSTM(极限200词)存在长序列依赖问题long-term dependecies、无法做并行计算

solution:self-attention,突破RNNs等序列sequence模型的固有缺陷,捕获长距离信息依赖,生成具有句法特征和语义特征的新向量。

  • 句法特征。
  • 语义特征。

1.2 Attention Algorithm

本质:QKV加权求和。

权重矩阵Wq * () = Q ; Wk * () = K; Wv * () = V,这才是attention计算中的qkv

q1通过与k1计算相似度s1,

  • Note that,qk相乘最终结果得到一个一维向量!!虽然某些attention codes中直接将k定义为一个参数nn.Parameter(torch.Tensor(attn_size, 1)),但qk相乘的最终结果是要得到一个一维相似度向量,所以将k定义为参数不影响计算。
  • KV一般同源
  • 从dict数据结构理解,键值对key-value同源,query和key向量积得到相似度,然后用这个相似度矩阵来对value做加权求和!

softmax后得到注意力概率a1,即k1代表的单词(KV一般同源)在q1单词新的向量表示中的占比,最后实现是通过加权求和,q1新的向量表示z1=a1*v1 + a2*v2。即长距离强相关的单词信息融入到q1新向量表示中,克服了RNN长距离相关单词在q1新向量表示中信息丢失的问题

self-attention: QKV相乘(QKV同源),QK相乘得到相似度A,AV相乘得到注意力值Z。

10 Transformer 之 Self-Attention(自注意力机制)_哔哩哔哩_bilibili

Q, K = k_{1}, k_{2},...,k_{n},我们一般使用点乘的方式。

1. 通过点乘的方式计算Q和K里的每一个元素的相似度,就可以拿到Q和k1的相似值s1,Q和k2的相似值s2,Q和kn的相似值sn。

2. 做一层softmax(s1, s2,...,sn),就可以得到概率(a1, a2,...,an)。进而就可以找出哪个对Q而言更重要

3. 还得进行一个汇总,即加权求和的过程。(a1,a2,...,an) * (v1,v2,...,vn) = (a1*v1 + a2*v2 + ...+ an*vn)  a1是一个数值0.3, 0.4等、v1是一个向量哈

这样的话,就得到了一个新的矩阵V'。

一般K=V,在transformer里,K可以!= V,但是K和V之间一定具有某种联系,这样QK的点乘才能指导V哪些重要,哪些不重要。

1.3 Scaled Dot-Product Attention 

  1. QK相乘求相似度,做一个scale放缩(未来做softmax时概率分布避免出现极端情况)
  2. 然后,做softmax得到概率
  3. 新的向量表示了K和V(K==V),然后这种表示还暗含了Q的信息(于Q而言,K里面重要信息)

1.3.1 attention scale缩放:\sqrt{d_{k}}

\sqrt{d_{k}}是K的维度

\sqrt{d_{k}}为了防止因为数据太过离散而导致softmax计算出来的概率不稳定

softmax:

51, 49 --> 0.51, 0.4

80, 20 --> 0.999, 0.0001

a1和a2之间的差额越大,这个概率就越离谱。因为softmax在梯度更新时的缺陷。

\sqrt{d_{k}}:80/8, 20/8 --> 0.9, 0.1

1.4 Self-Attention 自注意力机制

self-attention的关键在于,不仅仅是K≈V≈Q来源于同一个X,这三者是同源的

通过X找到X里面的关键点

1.4.1 Q、K、V的获取

并不是K=V=Q=X,而是通过三个参数W_{Q}, W_{K}, W_{V}

Q = X * W^{Q}; K = X * W^{K}; V = X * W^{V}

V = {v1, v2,...vn} 

1.4.2 matmul for similarity

  

1.4.3 softmax + scale

 

1.4.4 matmul for new vector z1

z1表示的就是thinking新的向量表示。

  • 对于thinking,初始词向量为x1
  • 现在通过thinking machines这句话去查询这句话里每一个单词和thinking之间的相似度。

新的z1依然是thinking的词向量表示,只不过这个词向量的表示蕴含了thinking machines这句话对于thinking而言哪个更重要的信息。

1.4.5 self-attention summary

 

1.4.6 Attention和Self-Attention区别

注意力机制attention是一个很宽泛的概念,QKV相乘就是注意力,但是它没有规定QKV怎么来的。

  • 通过一个查询变量Q,去找到V里面比较重要的东西。
  • 假设K==V,然后QK相乘求相似度A,然后AV相乘得到注意力值Z,这个Z就是V的另一种形式表示。
  • Q可以是任何一种东西,V也是任何一个东西,K往往和V同源。
  • 它没有规定QKV怎么来,只规定QKV怎么做

自注意力机制self-attention是注意力机制的一个子集

  • 本质上QKV可以看作是相等的,同源,来源于同一个X
  • 对于一个词向量,做的是空间上的对应。乘上参数矩阵,做线性变换,依然代表X
  • 词向量是不固定的,目前没有任何模型可以说一定能生活准备代表某个单词的词向量

扩展:交叉注意力机制

Q和K不同源,但是K和V同源,同源代表着一定意义上的相等

QKV不固定,你自己定义QKV,可以自己赋予新的名字(e.g. 小猫注意力),但attention计算的本质不变

1.4.7 Selft-attention和RNNs的区别

RNN、LSTM存在长序列依赖问题,无法做并行

self-attention得到新的词向量具有句法特征和语义特征(表征更完善) 

  • 句法特征

self-attention会和句子中的每一个单词做注意力计算,解决了长序列依赖问题,无论句子多长,第一个单词和最后一个单词都能产生联系。

  • 语义特征

 

不做注意力,its的词向量就是单纯的its,没有任何附加信息。

也就是说,its有law这层意思,而通过注意力机制得到的its词向量,则会包含一定的laws信息

self-attention计算量太大,计算复杂度O(n^{2})

1.4.8 masked self-attention 实现-1

import torch.nn.functional as F

def self-attention(query, key, value, dropout=None, mask=None):
    d_k = query.size(-1)
    scores = torch.matmul(query, key.transpose(-1, -2)) / math.sqrt(d_k)
    # mask的操作在QK之后,softmax之前
    if mask is not None:
        mask.cuda()
        scores = scores.masked_fill(mask == 0, -1e9)
    self_attn = F.softmax(scores, dim=-1)
    if dropout is not None:
        self_attn = dropout(self_attn)
    return torch.matmul(self_attn, value), self_attn

1.4.9 self-attention 实现-2

import torch
import torch.nn as nn
 
class SelfAttention(nn.Module):
    def __init__(self, d_model, nhead):
        super(SelfAttention, self).__init__()
        self.d_model = d_model
        self.nhead = nhead
        self.query_linear = nn.Linear(d_model, d_model)
        self.key_linear = nn.Linear(d_model, d_model)
        self.value_linear = nn.Linear(d_model, d_model)
        self.softmax = nn.Softmax(dim=-1)
 
    def forward(self, input):
        # input: (batch_size, sequence_length, d_model)
        query = self.query_linear(input)
        key = self.key_linear(input)
        value = self.value_linear(input)
        
        # compute attention scores
        attention_scores = torch.matmul(query, key.transpose(-2, -1))
        attention_scores = attention_scores / torch.sqrt(torch.tensor(self.d_model, dtype=attention_scores.dtype))
        
        # apply softmax to get attention weights
        attention_weights = self.softmax(attention_scores)
        
        # compute the final attention output
        attention_output = torch.matmul(attention_weights, value)
        return attention_output

1.5 Masked Self-Attention

掩码自注意力

why要在self-attention上做这个改进?

生成模型,在生成单词时,是一个一个生成的。

当我们做生成任务时,我们也想对生成的这个单词做注意力计算但是,生成的句子是一个一个单词生成的。很简单,不能用未来的预期结果预测现在的

e.g. I have a dream

1. I  第一次注意力计算,只有I

2. I have  第二次注意力计算,只有I和have

3. I have a 

4. I have a dream

5. I have a dream <eos>

掩码自注意力机制

自注意力机制明确知道这句话有多少个单词,并且一次给足,就相当于预知未来指导现在穿越、降维打击?就很扯。。而masked掩码是分批次给,最后一次才给足。

  • 掩码后1

  •  掩码后2

1.6 Multi-head Self-Attention (MHA)

multi-head attention concanation 拼接方式其实有两种

  • 将input_dimensin/heads_num = dimension_subhead, e.g. 256/8=32,然后将32-d embedding 输入到MHA. 经过拼接后,32+32+...+32=256,再经过projection Wo: 256 ->256. 先拼接再映射!input->W_q1W_q2-> d_q1=32, d_q2=32->d_out=64
  • 将input_dimension分别输入到 MHA中,然后做一个变换unsqueeze,(batch_size, input_dim) -> (batch_size, n_heads, input_dim/n_heads),然后对each head做projection Wo,将映射后的所有head dim concatenation. 先映射再拼接!input->W_q->d_q=64 -> d_q1=32, d_q2=32 -> d_out=64

https://proceedings.neurips.cc/paper/2017/file/3f5ee243547dee91fbd053c1c4a845aa-Paper.pdf

self-attention是attention的一个具体做法。给定一个x,通过self-attention model,得到一个z,这个z就是对x的新的表征(词向量),z这个词向量相比较x拥有了句法特征和语义特征。

z相比较x有了提升,通过multi-head self-attention得到的z',相比较z又有了进一步的提升!

multi-head,多头,就是多次,一般用h=8表示。

attention: 输入x,乘以参数矩阵Wq, Wk, Wv(one linear transfermation),得到Query,Key,Value,然后输入到放缩点积attention。
multi-head, 多头,x分为heads份,维度=d_model/heads,表示并行parallel做多次。而且每次线性变换的w是不一样的。然后将h次的scaled dot-product attention结果进行拼接,
虽然拼接后d_output维度为d_model,但还要再进行一次线性变换(参数矩阵矩阵为Wo=d_model*d_model),这才是multi-head attention最终的输出结果的结果

可以允许模型在不同的表示子空间里学习到相关的信息

  • 拼接方式1:dimension / heads_num 

1.6.1 multi-head self-attention algorithm

1. 对于X,我们不是直接拿X得到Z,而是把X分成了8块(8头),经过8次线性变换(8个不同的Wq,Wk,Wv),得到z0-z7。Note that embed_dim will be split across num_heads (i.e. each head will have dimension embed_dim // num_heads).

MultiheadAttention — PyTorch 2.0 documentation

8个不同的w会将(x0,x1,...,x7)映射到8个不同的子空间进行attention计算。

2. 然后把z0-z7拼接起来,再做一次线性变换(改变维度)得到z。

1.6.2 multi-head self-attention意义

机器学习本质:y=wx+b,线性变换。

深度学习:y=σ(wx+b),非线性。改变空间上的位置坐标,让一个维度空间上不合理的点变得合理。

非线性变换的本质:改变空间上的位置坐标,任何一个点都可以在维度空间上找到,通过某个手段,让一个不合理的点(位置不合理)变得合理。

multi-head self-attention把X切分成8块,这样一个原先在一个位置上的X,去了空间上的8个子空间subspaces,找到更合理的位置z'。

1.6.3 pytorch实现 multi-head attention

__init__()函数,声明初始化变量、模块modules和函数functions

forward函数,放入输入数据input vector,传出输出output。

X输入的第一个维度是batch,因为训练模型时一个批度一个批度的输入数据。

这里的全连接层进行线性变换,取代w矩阵的功能,原本应该是8组不同的w矩阵,这里用一个大的全连接层进行变换,然后通过维度变换转换成8个头的形式,输入到self-attention模块里。

比如,[batch, 32, 512]  # 每个batch里有32个词,每个词有512维。

将其切分成[batch, 8, 32, 64]  # 将512维切分成8个头、64维

import math

import torch
import torch.nn as nn
import torch.nn.functional as F

def self_attention(query, key, value, dropout=None, mask=None):
    d_k = query.size(-1)
    scores = torch.matmul(query, key.transpose(-1, -2)) / math.sqrt(d_k)
    # mask的操作在QK之后,softmax之前
    if mask is not None:
        mask.cuda()
        scores = scores.masked_fill(mask == 0, -1e9)
    self_attn = F.softmax(scores, dim=-1)
    if dropout is not None:
        self_attn = dropout(self_attn)
    return torch.matmul(self_attn, value), self_attn

class MultiHeadAttention(nn.Module):
    # 声明初始化变量和函数functions
    def __init__(self, head, d_model, dropout=0.1):
        super(MultiHeadAttention, self).__init__()
        '''
        :param head: 头数,默认8
        :param d_model: 输入的维度
        :param query: Q
        :param key: K
        :param value: V
        '''
        assert (d_model % head ==0)  # 取余
        self.d_k = d_model // head  # 将输入X平均分成8个头
        self.head = head
        self.d_model = d_model
        # 自注意力机制QKV同源,线性变换
        self.linear_query = nn.Linear(d_model, d_model)  # linear线性层(input, output),中间暗含着w矩阵
        self.linear_key = nn.Linear(d_model, d_model)
        self.linear_value = nn.Linear(d_model, d_model)

        self.linear_out = nn.Linear(d_model, d_model)
        self.dropout = nn.Dropout(p=dropout)
        self.attn = None  # 用于接收self-attention function 返回的attn 概率

    # forward函数传入input vector,传出输出
    def forward(self, query, key, value, mask=None):
        if mask is not None:
            # 多头注意力机制的线性变换层是4维,是把query[batch_size, frame_num, d_model]变成[batch_size, -1, head, d_k]
            # 在1,2维交换变成[batch_size, head, -1, d_k],所以mask要在第一维添加一维,与后面的self attention计算维度一样
            mask = mask.unsqueeze(1)
        # 输入的size(0)是batch,[batch,]
        n_batch = query.size(0)
        # 多头需要对输入X切分成多头, query==key==value. [batch, 32, 512] -> [batch, 8, 32, 64]
        query = self.linear_query(query).view(n_batch, -1, self.head, self.d_k).transpose(1, 2)  # [batch, 8, 32, 64]
        key = self.linear_key(key).view(n_batch, -1, self.head, self.d_k).transpose(1, 2)
        value = self.linear_value(value).view(n_batch, -1, self.head, self.d_k).transpose(1, 2)  # []

        x, self.attn = self_attention(query, key, value, dropout=self.dropout, mask=mask)
        # 变成三维,或者说是concat head. [batch, 8, 32, 64] -> [batch, 32, 512]
        x = x.transpose(1, 2).contiguous().view(n_batch, -1, self.head * self.d_k)

        return self.linear_out(x)

1.7 Positional Encoding

 Attention mechanism解决了长序列依赖问题、可以并行。

缺点:计算成本太大。

体现不出词与词之间存在的顺序关系order,位置编码的问题。

RNN、LSTM是包含词顺序关系的。self-attention对于每个词都是无位置关系的,把每个词的顺序打乱,得到的注意力值依然不变

加上一个positional encoding

通过t1告诉你,x1在前面,x2在x1后面 

1.7.1 positional encoding algorithm

pos代表input embedding在整体句子中的位置i代表embedding维度里元素的位置,即2i表示维度里的偶数项,用了sin函数;2i+1表示维度里的奇数项,用了cos函数

sin和cos函数存在和差化积公式

即sin(pos+k)=sin(pos)*cos(k) + cos(pos)*sin(k),cos(pos+k)=cos(pos)*cos(k) - sin(pos)*sin(k)

pos+k是pos位置与k位置的位置向量线性组合,2i--偶数维度用sin函数,2i+1--奇数维度用cos函数这样的线性组合意味着位置向量中蕴含了相对位置信息

e.g. 我爱你,现在做第三个词“你”的位置编码

pos + k = 3 = 1+2

3 = 1+2 --> 1*2+2*1

当变成你爱我,依然做第三个词“我”的位置编码

pos + k = 3 = 1+2 -->1*2+2*1

虽然都是1*2+2*1,但前面包含的单词位置信息不一样了,“我爱”、“你爱”,得到的位置编码不一样了。

实际应用中,transformer中positional encoding效果并不理想 --> bert中使用自学习的一种方式

1.7.2 positional encoding 实现

偶数项用sin函数,奇数项用cos函数,词向量维度一定是偶数维

输入X的embedding和positional encoding没有关系,比如:“我爱你”、“你爱我”,无论第一个词是啥,它的positional encoding都是一样的。

class PositionalEncoding(nn.Module):
    def __init__(self, dim, dropout, max_len=5000):
        super(PositionalEncoding, self).__init__()
        # 偶数项用sin函数,奇数项用cos函数,位置一定是成双成对的
        if dim % 2 != 0:
            raise ValueError("Cannot use sin/cos positional encoding with"
                             "odd dim (got dim={:d})".format(dim))
        """
        构建位置编码pe
        pe公式为:PE(pos, 2i/2i+1) = sin/cos(pos/10000^{2i/d_{model})
        """
        # max_len是解码器生成句子的最长的长度,即生成初始化一个为空的postional encoding vector
        pe = torch.zeros(max_len, dim)  
        position = torch.arange(0, max_len).unsqueeze(1)
        div_term = torch.exp((torch.arange(0, dim, 2, dtype=torch.float) *
                              -(math.log(10000.0) / dim)))
        pe[:, 0::2] = torch.sin(position.float() * div_term)  # pe第一维表示多少个词
        pe[:, 1::2] = torch.cos(position.float() * div_term)
        pe = pe.unsqueeze(1)
        self.register_buffer('pe', pe)
        self.drop_out = nn.Dropout(p=dropout)
        self.dim = dim

    def forward(self, emb, step=None):

        emb = emb * math.sqrt(self.dim)
        if step is None:
            # X的embedding vector和positional vector没有关系
            emb = emb + self.pe[:emb.size(0)]  # 表示第几个词的embedding
        else:
            emb = emb + self.pe[step]
        emb = self.drop_out(emb)
        return emb

2. Transformer(Encoder-Decoder Architecture)

 

transformer其实就是attention的一个堆叠,分成两部分:EncoderDecoder

Nx(乘)的意思是,transformer编码器里面又有N个小编码器(默认N=6)。通过6个编码器,对词向量一步又一步的强化(增强)详见下节

seq2seq模型,序列(编码器encoder)到序列(解码器decoder),比如:一句话到一句话。

  • 编码器encoder:把输入变成一个词向量(Self-Attention)
  • 解码器decoder:得到编码器输出的词向量后,生成翻译结果

机器翻译流程(transformer)

给定一个输入,给出一个输出(输出是输入的翻译结果)

“我是一个学生” --> (通过transformer) I am a student

了解transformer,就是了解transformer里的小的编码器(Encoder)和小的解码器(Decoder)。

FFN(feed Forward Network):前馈神经网络,本质就是w2(σ(w1x+b1)) + b2

编码器包括两个子层:self-attention、Feed Forward

每一个子层的传输过程都会有一个(残差网络 + 归一化)

2.1 Transformer 编码器 Encoder 

thinking --> 得到green x1(词向量,可以通过one-hot、word2vec得到) + positional encoding,给x1赋予位置信息 --> 黄色x1

-->输入到Self-Attention子层中,做注意力机制(x1、x2拼接起来的一句话做),得到z1(x1与x1、x2拼接起来的句子做了自注意力机制的词向量,表征的仍然是thinking),也就是说z1拥有了位置属性、句法特征、语义特征。

--> 残差网络Add(避免梯度消失)

“梯度消失”,self-attention里面有很多wx+b,比如w3(w2(w1+b1)+b2)+b3,如果w1, w2, w3特别小,0.00...1,三者联乘,w结果基本为0,x就相当于没了 <--三阶函数的梯度是二阶,二阶函数的梯度是一阶

残差网络:加上x,让x梯度求导后不会没了。w3(w2(w1+b1)+b2)+b3 + x

--> LayerNorm归一化

做标准化,限制区间,避免梯度爆炸(如果w1,w2,w3较大)在数据挖掘中,如果某一特征太过离散,数值分布在几十到几千之间,对参数w带来挑战,影响模型效果

--> 得到深粉色z1

--> Feed Forward(前面每一步走在做线性变换wx+b,线性变化的叠加永远都是线性变化(线性变换就是在空间中放缩平移),激活函数非线性的提出是划时代的,通过feed forward中的Relu做多次非线性变换,这样的空间变换可以无线拟合空间中任何一种状态了),得到r1(是thinking新的表征)

2.1.1 Encoder 总结

Encoder就是在做词向量,这不过这个词向量更加优秀。因为它每一步很合理,很完美,一比之下,word2vec就是渣渣! 

让这个词向量能够更加准确的表示这个单词、这句话 

?未来有一个东西专门做词向量,而不是像transformer一样定位在机器翻译。

Bert

2.2 Transformer 解码器 Decoder

编码器 Encoder:转换成词向量。Encoder就是让计算机能够合理地认识人类世界客观存在的一些东西。(这个词向量不可能是100%正确的,是不确定的,只是尽可能精确的

解码器会接收解码器生成的词向量,然后通过这个词向量去生成翻译结果。

2.2.1 为什么Decoder需要做mask

解码器的self-attention编码已经生成的单词,即masked self-attention。这是为了解决训练阶段和测试阶段不匹配

假如目标词“我是一个学生”,

训练阶段:人工标注机器翻译语料--<I'm a student, 我是一个学生>是已知的,decoder self-attention需要对“我是一个学生”做masked计算。

  • 如果不做masked,每次训练阶段,decoder都会获得全部的信息。
  • 如果做masked,self-attention第一次对“我”做计算;第二次对“我是”做计算,...,masked是为了训练阶段与测试阶段预测未知目标词行为保持一致

测试阶段:目标翻译词是未知的,无法直接拿未知的翻译结果输入decoder

        1. 目标词未知,假如目标词是“我是一个老师”,self-attention第一次对“我”做计算。

        2. 第二次对“我是”做计算

        3. .....

测试阶段:encoder每生成一个词,decoder就获得多一点

2.2.2 为什么Encoder给予Decoders的是K、V矩阵 

--> Encoder-Decoder Attention中QKV不同源,交叉注意力计算

        Q来自Decoder,K=V来自Encoder,Q通过对全信息KV进行查询,后面经过linear维度变换和softmax概率计算,得出下一个词。

--> Linear层映射为词表的维度。因为线性变化wx+b,神经元个数即embedding维度

--> softmax得到最大概率的单词

Q是查询变量,K=V是源语句,通过部分(生成的词)去全部(源语句)的里面挑重点。 如果反过来,Q是源语句,KV是已经生成的词,那么源语句去已经生成词里找下一个翻译词的信息,这根本是找不到的! 

解决了以前seq2seq框架的问题(lstm做编码器,lstm做解码器,这种方法去生成词,每一次生成词,都是通过C的全部信息去生成)

2.3 Transformer 动态流程

  • 生成一个词

  •  生成全部单词

2.4 Transformer疑问解答

2.4.1 为什么用张量tensor?

因为向量是2维,张量tensor是3维以上

2.4.2 词向量input必须是one-hot吗?换成word2vec可以提升模型performance吗?

词向量不一定必须是one-hot,可以使word2vec、ELMo、Bert等其他词向量,但这不会改变模型效果,因为BP阶段会不断改变向量对应权重w,最终达到optimal,唯一区别在于BP时更新幅度和收敛熟读不一样。

e.g. 初始权重w0=5,optimal wf=10。one-hot编码不完善,需要更新的次数多一点,而bert经过预训练,可能需要的epoches更少,直接从7开始BP。

所以,input embedding可以随机初始一个向量,transformer会反向更新,它一定能将帮你更新到一个确切的位置。-->让AI领域发生翻天覆地的变化。

2.4.3 transformer强大之处

transformer的核心是self-attention、multi-head attention

生成式语言模型的核心是masked multi-head attention

lstm其实也有上述特性,但是它们模型不能做得很大、没有用self-attention;复杂的数据、大型的数据、大量的数据、没有标签的数据,lstm model无能为力

transformer、bert 有强大的特征提取能力,不论是文本还是视频!!!

2.5 pytorch 实现Transformer

自己写transformer没有任何实际意义!!

这里只是为了熟悉transformer代码结构

实际应用请直接做调包侠!! 

extension:  python面向对象--封装(class类、__init__初始化函数)

面向对象,即java的编程思想。一个数据类型抽象化创建为对象(class),这个对象拥有name和property两种属性,面向对象思想编程有三大特点:

  • 数据封装。在于复用
  • 继承
  • 多态

python编程基础(五): 面向对象--封装、继承_天狼啸月1990的博客-CSDN博客_自行封装ready.py

init函数声明初始变量和函数,

forward函数使用init变量、输入x和输出y

当前可以直接在forward函数中声明变量,但这就写死了后面应用修改太麻烦!!!

e.g. 

l1 = LayerNorm()

....

l1(128)

l1(128)

l1(128)

------------------------------------------------

l1 = LayerNorm(128)

....

l1()

l1()

l1()

2.5.1 残差函数add、归一化Norm

首先有一个norm函数

class LayerNorm(nn.Module):

    def __init__(self, feature, eps=1e-6):
        """
        param feature: self-attention的x的大小
        param eps:做平滑
        """
        super(LayerNorm, self).__init__()
        self.a_2 = nn.Parameter(torch.ones(feature))
        self.b_2 = nn.Parameter(torch.zeros(feature))
        self.eps = eps

   def forward(self, x):
        mean = x.mean(-1, keepdim=True)
        std = x.std(-1, keepdim=True)
        return self.a_2 * (x-mean)/(std+self.eps) + self.b_2

 然后norm里面做残差,会输入(x和淡粉色z1,残差值),输出一个紫粉色的z1 

class SublayerConnection(nn.Module):
    """
    这不仅仅做了残差,这是把残差和layernorm一起做了
    """
    def __init__(self, size, dropout=0.1):
        super(SublayerConnection, self).__init__()
        # 第一步做 layernorm
        self.layer_norm = LayerNorm(size)
        # 第二步做 dropout,随机置0,防止过拟合
        self.dropout = nn.Dropout(p=dropout)

    def forward(self, x, sublayer):
        """
        param x: self-attention input
        param sublayer: self-attention output
        """
        return self.dropout(self.layer_norm(x + sublayer(x)))

2.5.2 Feed Forward layer(FFN)

layer_norm: 对X做标准化,深度学习中的常规操作。 

w2(relu(w1x+b1))+b2。如果对线性变换或线性回归比较熟悉的话,b1和b2有时会放到w1和w2里面,默认X多增加了[1,1,1,...]这一维

class PositionWiseFeedForward(nn.Module):

    '''
    layer_norm: 对X做标准化
    FFN: w2(relu(w1x+b1))+b2
    '''
    def __init__(self, d_model, d_ff, dropout=0.1):
        super(PositionWiseFeedForward, self).__init__()
        self.w_1 = nn.Linear(d_model, d_ff)
        self.w_2 = nn.Linear(d_ff, d_model)
        self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)
        self.dropout_1 = nn.Dropout(dropout)
        self.relu = nn.ReLU()
        self.dropout_2 = nn.Dropout(dropout)

    def forward(self, x):
        inter = self.dropout_1(self.relu(self.w_1(self.layer_norm)))
        output = self.dropout_2(self.w_2(inter))
        return output

2.5.3 Linear层 + Softmax层

class Generator(nn.Module):

    def __init__(self, d_model, vocab_size):
        super(Generator, self).__init__()
        self.linear = nn.Linear(d_model, vocab_size)

    def forward(self, x):
        return F.log_softmax(self.linear(x), dim=-1)

2.5.4 masked multi-head self-attention

只是对multi-head self-attention的输入做了一个masking操作

# masking multi-head attention
def pad_mask(src, r2l_trg, trg):
    if trg is not None:  # trg 是标签y
        # 把trg丢到subsequent_mask,对它进行掩码处理,改变它的形状,只能看前几个
        trg_mask = subsequent_mask(trg.size(1)).type_as(src_image_mask.data)

# 掩码处理 masking, 即生成一个上三角矩阵,只给你看到生成词的信息
def subsequent_mask(size):
    """
    mask out subsequent positions.
    """
    attn_shape = (1, size, size)
    mask = np.triu(np.ones(attn_shape), k=1).astype('uint8')
    return (torch.from_numpy(mask) == 0).cuda()

2.5.5 构建编码器 encoder

# 这里是一层编码器
class EncoderLayer(nn.Module):
    def __init__(self, size, attn, feed_forward, dropout=0.1):  # 实例化出来的attn, feed_forward层
        super(EncoderLayer, self).__init__()
        self.attn = attn
        self.feed_forward = feed_forward
        # 克隆,module复制n次。transformer编码器encoder里有2个残差连接
        self.sublayer_connection = clones(SublayerConnection(size, dropout), 2)

    def forward(self, x, mask):
        x = self.sublayer_connection[0](x, lambda x: self.attn(x, x, x, mask))  # forward前向传播把数据流串起来
        return self.sublayer_connection[1](x, self.feed_forward)

# transformer encoder里面有6个编码器encoderlayer
class Encoder(nn.Module):
    # encoder_layer = EncoderLayer()
    def __init__(self, n, encoder_layer):
        super(Encoder, self).__init__()
        self.encoder_layer = clones(encoder_layer, n)  # 克隆n次,transformer里面是6层

    def forward(self, x, src_mask):
        for layer in self.encoder_layer:  # 编码器前向传播,前向传播一次得到新的x,再放入下一层编码器,共有6层
            x = layer(x, src_mask)
        return x

2.5.6 解码器 decoder

# 首先构建一层解码器
class DecoderLayer(nn.Module):

    def __init__(self, size, attn, feed_forward, sublayer_num, dropout=0.1):
        super(DecoderLayer, self).__init__()
        self.attn = attn  # 这样的写好处是,没有写死。可以根据传入参数,确定是multi-head self-attention还是 masked multi-head self-attention
        self.feed_forward = feed_forward
        self.sublayer_connection = clones(SublayerConnection(size, dropout), sublayer_num)

    def forward(self, x, memory, src_mask, trg_mask, r2l_memory=None, r2l_trg_mask=None):
        x = self.sublayer_connection[0](x, lambda x: self.attn(x, x, x, trg_mask))
        x = self.sublayer_connection[1](x, lambda x: self.attn(x, memory, memory, src_mask))  # memory是编码器输出
        # 双向解码器
        # if r2l_memory is not None:
        #     x = self.sublayer_connection[-2](x, lambda x: self.attn(x, r2l_memory, r2l_memory, r2l_trg_mask))

        return self.sublayer_connection[-1](x, self.feed_forward)

# 再构建6层解码器
class R2L_Decoder(nn.Module):  #  双向解码器--正向解码器

    def __init__(self, n ,decoder_layer):
        super(R2L_Decoder, self).__init__()
        self.decoder_layer = clones(decoder_layer, n)

    def forward(self, x, memory, src_mask, r2l_trg_mask):
        for layer in self.decoder_layer:
            x = layer(x, memory, src_mask, r2l_trg_mask)
        return x

# # 双向解码器--反向解码器
# class L2R_Decoder(nn.Module):
#
#     def __init__(self, n, decoder_layer):
#         super(L2R_Decoder, self).__init__()
#         self.decoder_layer = clones(decoder_layer, n)
#
#     def forward(self, x, memory, src_mask, trg_mask, r2l_memory, r2l_trg_mask):
#         for layer in self.decoder_layer:
#             x = layer(x, memory, src_mask, trg_mask, r2l_memory, r2l_trg_mask)
#         return x

2.5.7 整体Transformer

__init__,初始化一些小的结构layer

class ABDTransformer(nn.Module):

    def __init__(self, vocab, d_feat, d_model, d_ff, n_heads, n_layers, dropout, feature_mode,
                 device='cuda', n_heads_big=128):
        super(ABDTransformer, self).__init__()
        self.vocab = vocab  # 词表大小
        self.device = device  # 是否使用GPU
        self.feature_mode = feature_mode  # 多模态

        c = copy.deepcopy()

        # attn_no_heads = MultiHeadAttention(0, d_model, dropout)  # 无头multi-head attention = 一层注意力

        attn = MultiHeadAttention(n_heads, d_model, dropout)

        # attn_big = MultiHeadAttention(n_heads_big, d_model, dropout)  # 大头 multi-head attention

        feed_forward = PositionWiseFeedForward(d_model, d_ff)

        if feature_mode == 'one':
            self.src_embed = FeatEmbedding(d_feat, d_model, dropout)  # 视频嵌入
        elif feature_mode == 'two':
            pass
        elif feature_mode == 'three':
            pass
        elif feature_mode == 'four':
            pass

        self.trg_embed = TextEmbedding(vocab.n_vocabs, d_model)
        self.pos_embed = PositionalEncoding(d_model, dropout)

        # 初始化编码器
        self.encoder = Encoder(n_layers, EncoderLayer(d_model, c(attn), c(feed_forward), dropout))
        #  初始化一个正向解码器
        self.l2r_decoder = L2R_Decoder(n_layers, DecoderLayer(d_model, c(attn), c(feed_forward),
                                                              sublayer_num=4, dropout=dropout))
        # 初始化一个反向解码器
        # self.r2l_decoder = R2L_Decoder(n_layers, DecoderLayer(d_model, c(attn), c(feed_forward),
        #                                                       sublayer_num=4, dropout=dropout))
        # 生成器generator,就是linear + softmax layer
        self.generator = Generator(d_model, vocab.n_vocabs)

    # 单模态encode
    def encode(self, src, src_mask, feature_mode_two=False):
        x1 = self.image_src_embed(src[0])
        x1 = self.pos_embed(x1)
        x1 = self.encoder_big(x1, src_mask[0])  # 将 X embedding + positional encoding 输入编码器

    # 正向解码器 decode
    def l2r_decode(self, trg, memory, src_mask, trg_mask, r2l_memory, r2l_trg_mask):
        x = self.trg_embed(x)  # 解码器输入
        x = self.pos_embed(x)
        return self.l2r_decoder(x, memory, src_mask, trg_mask, r2l_memory, r2l_trg_mask)

    # # 反向解码器
    # def r2l_decode(self, trg, memory, src_mask, trg_mask, r2l_memory, r2l_trg_mask):
    #     pass

    # 构建forward函数
    def forward(self, src, r2l_trg, trg, mask):
        src_mask, r2l_pad_mask, r2l_trg_mask, trg_mask = mask

        if self.feature_mode == 'two' or 'three' or 'four':

            encoding_outputs = self.encode(src, enc_src_mask)

            l2r_outputs = self.l2r_encode(trg, encoding_outputs, dec_src_mask[1], trg_mask, r2l_outputs, r2l_pad_mask)
        else:
            raise "没有输出"

        # r2l_pred = self.generator(r2l_outputs)
        l2r_pred = self.generator(l2r_outputs)

2.6 Huggingface transformers package

2.6.1 background

Transformer,由于其优越的性能表现,在工业界使用的越来越广泛,同时,配合迁移学习理论,越来越多的Transformer预训练模型和源码库逐渐开源,Huggingface就是其中做的最为出色的一家机构。Huggingface是一家在NLP社区做出杰出贡献的纽约创业公司,其所提供的大量预训练模型和代码等资源被广泛的应用于学术研究当中Huggingface所开源的Transformers package提供了数以千计针对于各种任务的预训练模型模型,开发者可以根据自身的需要,选择模型进行训练或微调,也可阅读api文档和源码, 快速开发新模型。

pip install transformers

2.6.2 AutoClass介绍

problem:transformers package中有上百个算法,有bert model对应的BertModel类,有bart model对应的BartModel,当我们使用对应的pre-trained model是,都必须先找到对应类名,然后进行实例化,非常麻烦!

transformers库提供了AutoClass高级对象,只需要知道预训练模型的名称name所在目录dir,就可以快速便捷create pre-trained model

AutoClass类只能通过from_pretrained()方法创建模型。比如:

from transformers import AutoModel

model = AutoModel.from_pretrained("./models/bert-base-chinese")
print(type(model))

2.6.3 分词器对象 AutoTokenizer

Huggingface的transformers库中提供了高级API对象--AutoTokenizer,用来加载预训练模型的分词器。

Note that: 预训练模型分词器和模型model是配套使用的!

比如:我们使用的预训练模型时"bert-base-chinese",那么,预先加载的分词器也必须使用"bert-base-chinese"

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("bert-base-chinese")
sentence = "床前明月光"
tokenizer(sentence)
-----------------------------------------------------
{'input_ids': [101, 2414, 1184, 3209, 3299, 1045, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1]}

tokenizer内部还提供其他丰富的参数用于实现多种功能:

tokenizer(
    ["床前明月光", "床前明月光,疑是地上霜。"],
    padding=True,   # 长度不足max_length时是否进行填充
    truncation=True,  # 长度超过max_length时是否进行截断
    max_length=10,
    return_tensors="pt",  #指定返回数据类型,pt:pytorch的张量,tf:TensorFlow的张量
)

2.6.4 配置文件 AutoConfig

problem: 每一类算法模型的框架结构不一样、超参数配置也不一样。如果每次加载预训练模型,都要用户手动找到对应的配置项,就很麻烦

所以AutoClass提供了专门的配置管理入口--AutoConfig

from transformers import AutoConfig
config = AutoConfig.from_pretrained("./models/bert-base-chinese")

通过config实例,我们可以对配置项进行修改。

config.num_hidden_layers=5
print(config)

修改之后的参数,如果后续需要再次使用,可以保存到本地。

config.save_pretrained("./models/bert-base-chinese")

2.6.5 创建预训练模型 AutoModel

通过AutoModel类,创建pre-trained model最简单的方法就是直接传入模型名称或本地路径。

from transformers import AutoModel
model = AutoModel.from_pretrained("./models/bert-base-chinese")

# 也可以在加载模型时,指定配置项实例
model = AutoModel.from_pretrained("./models/bert-base-chinese", config=config)

2.7 “调包侠”实现transformer、bert

2.7.1 “调包侠”实现transformer

2.7.2 “调包侠”实现bert

# -----------------------------------------Bert embeddings-----------------
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')
bert_model = AutoModel.from_pretrained('bert-base-uncased')

df['bert_embeddings'] = df.text.apply(lambda x: bert_model(**tokenizer(x, return_tensors='pt'))[0][0][0][:128])

df_bert = df[['event_id','bert_embeddings']]

2.8 bert project application

https://www.cnblogs.com/chenhuabin/p/16997607.html

  • 自定义数据集
  • 自定义网络
  • 训练 training
  • run model

参考

推荐:  

小白友好,简单直白,逻辑性强,但有好好几个点讲错了,-_-||,比如多头和解码器QKV部分

66 使用注意力机制的seq2seq【动手学深度学习v2】_哔哩哔哩_bilibili

(我不推荐李沐大神的attention课程,仁者见仁智者见智。专家是指能把复杂数学概念直白讲解到老农民都能听懂的程度,虽然李沐已经简化了数学公式,还是对小白不友好;认真用心程度低,虽然沐神是卡耐基的博士,但cs高度不代表课件用心投入程度高,即便是沐神,认真投入度低,事情做出来也不好;依托RNN讲解self-attention,不理解机器翻译seq-2-seq,听attention云里雾里的。)

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Transformer 架构的训练模型主要分为两个阶段:训练和微调。其中,训练阶段主要包括两个任务:掩码语言模型(Masked Language Model,MLM)和下一句测(Next Sentence Prediction,NSP)。微调阶段主要针对具体的自然语言处理任务进行模型微调。 下面是 Transformer 架构的训练模型的详细结构说明: 1. 输入嵌入层(Input Embedding Layer):将输入的文本转换为向量表示,通常使用词嵌入(Word Embedding)技术。 2. Transformer 编码器(Transformer Encoder):包含多个相同的编码器层,每个编码器层包含多头自注意力机制(Multi-Head Self-Attention Mechanism)和前馈神经网络(Feedforward Neural Network)两个子层。 3. Transformer 解码器(Transformer Decoder,只适用于 GPT 类型的模型):包含多个相同的解码器层,每个解码器层包含多头自注意力机制、多头注意力机制(Multi-Head Attention Mechanism)和前馈神经网络三个子层。 4. 自注意力机制(Self-Attention Mechanism):用于在不损失序列信息的情况下,对输入序列中的每个位置进行加权处理,以获得更好的序列特征表示。 5. 多头自注意力机制(Multi-Head Self-Attention Mechanism):将自注意力机制分为多个头,分别计算不同的注意力权重,最后将结果拼接在一起,以获得更好的特征表示。 6. 多头注意力机制(Multi-Head Attention Mechanism,只适用于解码器):将输入序列和输出序列分别进行自注意力计算和注意力计算,以获得更好的特征表示。 7. 前馈神经网络(Feedforward Neural Network):用于对特征表示进行非线性变换和降维,以获得更好的特征表示。 8. 输出层(Output Layer):将特征表示转换为对应的输出结果,如文本分类、序列生成等。 总的来说,Transformer 架构的训练模型采用了自注意力机制和多头注意力机制等技术,能够有效地捕捉输入文本的序列信息,从而获得更好的特征表示。同时,该模型的结构非常灵活,可以通过增加或删除编码器层和解码器层等方式进行调整,以适应不同的自然语言处理任务。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天狼啸月1990

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值