transformer详解

一、transformer简介

Transformer最早由Ashish Vaswani等人在论文<>[1]中提出,是一个基于注意力机制的网络主干结构,如下图,左边是编码器,右边是解码器

Transformer的意义体现在它的长距离依赖关系处理和并行计算,而这两点都离不开其提出的自注意力机制。
首先,Transformer引入的自注意力机制能够有效捕捉序列信息中长距离依赖关系,相比于以往的RNNs,它在处理长序列时的表现更好。
而自注意力机制的另一个特点时允许模型并行计算,无需RNN一样t步骤的计算必须依赖t-1步骤的结果,因此Transformer结构让模型的计算效率更高,加速训练和推理速度。

二、什么是自注意力机制

传统的Attention机制在一般任务的Encoder-Decoder model中,输入Source和输出Target内容是不一样的,比如对于英-中机器翻译来说,Source是英文句子,Target是对应的翻译出的中文句子,Attention机制发生在Target的元素Query和Source中的所有元素之间。简单的讲就是Attention机制中的权重的计算需要Target来参与的,即在Encoder-Decoder model中Attention权值的计算不仅需要Encoder中的隐状态而且还需要Decoder 中的隐状态。

而Self Attention顾名思义,指的不是Target和Source之间的Attention机制,而是Source内部元素之间或者Target内部元素之间发生的Attention机制,也可以理解为Target=Source这种特殊情况下的注意力计算机制。例如在Transformer中在计算权重参数时将文字向量转成对应的KQV,只需要在Source处进行对应的矩阵操作,用不到Target中的信息。

自注意力机制(Self-Attention)的核心是 Query(Q)、Key(K)、Value(V)这三个概念。这三个概念都来自于信息检索领域,其中,Query 是我们的查询,Key 是我们要匹配的目标,Value 是我们想要的结果。

注意力公式为:

对于每一个输入的句子X={x1,x2,x3,…} 每个单词x都会通过线性变换(矩阵运算)生成对应的q,k,v。 q和k的转置相乘再经过softmax。其实相当于softmax(X.XT),计算两个都由X变换而来的矩阵的内积,其实就是计算每个单词和同一句话中其他词的相关性(在注意力机制中,序列上的每个单词都会和其它所有单词做一次点积计算注意力得分,这种注意力机制中单词之间的交互是强制的不受距离影响的,所以可以解决长距离依赖问题)。经过softmax(X.XT)归一化的权重,再次乘以X变化而来的V,就相当于加权求和了,这就得到了一个经过自注意力机制后的新向量(源自引用文章[1])。

以下我们假设q=k=v,即先不加入线性变化的权重,计算流程如下:

  1. 首先我们计算每个单词和其转置矩阵相乘,获得X.XT
  1. 将其经过softmax
  1. 将注意力得分分别加到原始向量上面去,得到新的向量

标准的self-attention计算过程如下:

三、gpt2模型系列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
import transformers
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
import numpy as np

# from transformers import GPT2Tokenizer, GPT2LMHeadModel

device = ( "cuda" if torch.cuda.is_available() else "cpu" )

tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")# , force_download=True

if device == "cuda":
model = model.cuda()


prompt = "how old are you"
inputs = tokenizer(prompt, return_tensors="pt").to(device)

######################## 单次推理 ###################
# # 1. 贪婪采样,取最大概率token
# print('input_ids=',inputs)
# y = model.forward(inputs['input_ids'])
# next_token_id = int(np.argmax(y.logits[0][-1].detach().cpu().numpy()))
# print("next_token_id=",next_token_id)
# next_token = tokenizer.convert_ids_to_tokens(next_token_id)
# print("output word =",prompt + next_token)


####################### generate生成50个 ################################
# 使用KV-cache进行生成
output = model.generate(inputs['input_ids'], max_length=10, use_cache=True)
# 解码生成的序列
generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
print(generated_text)


# ###################### 手动实现最大长度推理 ########################################
# # 设置生成参数
# max_length = 50
# temperature = 1.0
# # 手动实现文本生成
# generated_ids = inputs['input_ids']
# for _ in range(max_length - inputs['input_ids'].size(1)):
# # 使用模型的前向传递生成下一个 token 的 logits
# outputs = model(generated_ids)
# next_token_logits = outputs.logits[:, -1, :] / temperature
# # 使用贪婪搜索选择概率最高的 token
# next_token_id = torch.argmax(next_token_logits, dim=-1).unsqueeze(-1)
# # 将生成的 token 添加到序列中
# generated_ids = torch.cat([generated_ids, next_token_id], dim=-1)
# # 将生成的 token 序列转换回文本
# generated_text = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
# print("generated_text=",generated_text)





# ###################### 手动实现最大长度推理并保存kv-cache ########################################
# # 设置生成参数
# max_length = 20
# temperature = 1.0
# # 手动实现文本生成
# generated_ids = inputs['input_ids']
# print(f'generated_ids={generated_ids[:, -1:]}')
# past_key_values = None
# for _ in range(max_length - inputs['input_ids'].size(1)):
# if past_key_values is None:
# # 初始生成时,传递整个prompt
# outputs = model(generated_ids, past_key_values=past_key_values, use_cache=True)
# else:
# # 使用模型的前向传递生成下一个 token 的 logits
# outputs = model(generated_ids[:, -1:], past_key_values=past_key_values, use_cache=True)
# next_token_logits = outputs.logits[:, -1, :] / temperature
# past_key_values = outputs.past_key_values
# # 遍历 past_key_values 中的每一层
# for layer_idx, layer in enumerate(past_key_values):
# # layer 是一个包含两个张量 (key, value) 的元组
# key, value = layer

# # 打印每一层的 key 和 value 的 shape
# print(f"Layer {layer_idx}:")
# print(f" Key shape: {key.shape}")
# print(f" Value shape: {value.shape}")
# break


# # 使用贪婪搜索选择概率最高的 token
# next_token_id = torch.argmax(next_token_logits, dim=-1).unsqueeze(-1)
# # 将生成的 token 添加到序列中
# generated_ids = torch.cat([generated_ids, next_token_id], dim=-1)
# # 如果生成了终止 token,则停止生成
# if next_token_id.item() == tokenizer.eos_token_id:
# break
# # 将生成的 token 序列转换回文本
# generated_text = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
# print(f"generated_text={generated_text}")

三、llama结构

链接

  1. 超详细图解Self-Attention https://zhuanlan.zhihu.com/p/410776234
  2. 如何最简单、通俗地理解Transformer https://www.zhihu.com/question/445556653/answer/3254012065
  3. 熬了一晚上,我从零实现了Transformer模型,把代码讲给你听 https://zhuanlan.zhihu.com/p/411311520
  4. 三万字最全解析!从零实现Transformer https://zhuanlan.zhihu.com/p/648127076
  5. 我们从零开始用pytorch搭建Transformer模型 https://www.zhihu.com/question/445556653/answer/3145761470
  6. transformers可视化https://github.com/poloclub/transformer-explainer