大语言模型LLMs技术精粹,Transformer模型架构全解析:三生万物——且看AI江湖基石
1. 新消息
New Bing中的聊天,在2023年2月17日对用户使用进行了限制,每次会话 5 次聊天轮次限制,每天总共 50 次。这个限制引起了广泛的批评。在21日,New Bing将每个会话的聊天轮数增加到 6 次,并将每天的聊天总数增加到 60 次。并宣布计划很快将每日聊天上限提高到 100 次。[1]从广泛的讨论来看,New Bing无法很好地解决胡说八道、可信以及负面情绪等问题。另外,可能成本上也是巨大的,对微软来说也是一个巨大的负担。这两点我多次在公开或非公开的讨论中都是特别强调的:
一方面,大模型拥有非常巨量的信息,既能够对齐工作上的需要,在对齐人们的情绪方面也同样非常强大。如果对聊天机器人本身不做限制的话,它会让人产生幻觉,编造情绪,放大负面情绪,甚至会引起悲剧;
另一方面,目前来说成本巨大,这个成本不仅仅是做出一个类似 ChatGPT 这样的大模型和系统,也包括之后每一次使用的成本。如果考虑 ROI 的话,极大概率是不合算的。
关于第一点,可参阅:
大语言模型LLMs技术精粹总纲:重剑无锋,大巧不工——且看AI江湖刀剑争锋的源流 提到的 New Bing对用户示爱,并试图说服用户离婚的新闻[2]‘’
用户认为New Bing充满着错误、怪异和令人毛骨悚然的回复,并且开始在语言上伤害人们,甚至说出“尔若不先加害于吾,吾不至殃及尔。(I will not harm you unless you harm me first)”之类的话[3];
去年6月份 Google工程师Blake Lemoine认为 LaMDA模型具有人格的事件,认为 应当赋予LaMDA人的身份,并公开了聊天的全文。[4]
2. 以人为本 AGI
“以人为本AGI” 就是以人类为中心的通用人工智能,也就是说人工智能是以服务人类为目的的,有名的“机器人三定律”说的也是这个。这个定义在前面几篇文章中重复了好几次了,最近有将其简化为“人本AGI” 或“人本智能体”。值得欣慰的是,这个定义与最近(2月24日) OpenAI在其官网发布的《Planning for AGI and beyond》[5]中的精神极为一致,比如“吾辈欲以人工智能为使,推动人类极致繁荣于宇宙之中(We want AGI to empower humanity to maximally flourish in the universe)”等。进而,“人本AGI”的公式为:
神经网络大模型 ⊕ 知识图谱 ⊕ 强化学习=人本AGI
其中,⊕表示了某种组合/融合的方法,并且:
神经网络大模型:连接主义发展至今的代表性成果,随着 AGI 的发展,大语言模型未必会是最终形态,比如多模态跨模态的神经网络大模型;
知识图谱 :符号主义发展至今的代表性成果,随着 AGI 的发展,知识图谱本身也需要不断发展,目前这种知识图谱未必是最终形态,比如拥有更强表达能力、知识计算的计算、推理和规划能力的知识图谱;
强化学习:行为主义发展至今的代表性成果,随着 AGI 的发展,强化学习本身也会不断发展,PPO 未必是最佳的形式。
人工智能发展至今,已经出现了能够将神经网络大模型、知识图谱 、强化学习三者融合的系统(产品),而这已经隐隐散发出一点 AGI 的曙光,或称之为婴儿期的AGI,或称之为 AGI 的幼芽。未来,通用人工智能的进一步发展,必将使得曙光上升为朝阳,婴儿茁壮成长成青壮年,幼芽长成苍天大树。那时,人本 AGI或将为人类来带来思维革命。这种变革可能从改造自然到改造自身,其影响深远可能远超此前。也许,我们的后辈的形态,也是我们现在所无法想象的。
3. 变换器网络
图1是过去5年大模型“狂飙”的成果,而这一切的基础则是变换器网络(Transformer)。本文参考珠峰书《知识图谱:认知智能理论与实战》[6]有关于变换器网络的解析的内容,结合《Attention is all you need》[7]论文,全面解析架构,并给出代码实践。以目前来看,不管是大模型、AGI 还是 AIGC,深入技术的话,都离不开对变换器网络的理解。变换器网络是一种使用注意力机制处理输入序列(例如文本或语音)的深度神经网络架构。其关键创新是使用了多头自注意力机制,使模型能够在处理过程中不同时刻关注输入序列的不同部分。变换器网络发布伊始就表现牛逼,在机器翻译、语言建模和问答等任务上达到了当时的 SOTA。并在随后,成为了预训练大语言模型的基础架构,同时在语音、图像、图分析等几乎所有人工智能子领域攻城掠寨,成为了当今深度学习领域所有网络结构中事实上的王者。下图是变换器网络的架构,下文详细解析。
4. 多头自注意力机制
首先是掩码多头自注意力机制(Masked Multi Self Attention),其核心是如下图的自注意力计算。图3展示了自注意力的具体计算方法,其中
直观来说,
图4 自注意力的可视化
图4是自注意力的可视化,是基于 “bert-base-chinese”模型的数据,输入文本是两个句子,上句是“知识图谱:认知智能理论与实战”,下句是“详细解析数十个知识图谱前沿算法”。因为 BERT 是双向模型,所以注意力会弥漫到文本序列的过去与未来。后文在介绍 GPT 系列模型的时候,我们再来看自回归语言模型中的自注意力的表现,届时可以和上图进行比较。从上图可以看出,在第0层的第10个头,“知”字注意力集中在“认”上。在另一个头,关注的点会有所不同,比如第8头则关注了更多样的内容,包括“识”、“论”等。另外,不同层的关注点也不一样,一般底层偏向于词,高层偏向与语义,并且跨度也会更大。这能够直观地理解,网络能够同时关注嵌入空间的不同子空间。这有助于模型捕捉输入序列不同部分之间的更复杂依赖关系。
图5 自注意力计算过程的可视化
在了解了注意力本身之后,多头注意力机制将“多头”和“自注意力”两种方法组合,其计算过程如图3-17所示。
根据上图,结合论文,基于PyTorch 来实现多头注意力的计算,代码如下。
import torch
import torch.nn as nn
import torch.nn.functional as F
class MultiHeadAttention(nn.Module):
def __init__(self, embed_dim, num_heads):
super(MultiHeadAttention, self).__init__()
self.embed_dim = embed_dim
self.num_heads = num_heads
self.head_dim = embed_dim // num_heads
self.query_proj = nn.Linear(embed_dim, embed_dim, bias=False)
self.key_proj = nn.Linear(embed_dim, embed_dim, bias=False)
self.value_proj = nn.Linear(embed_dim, embed_dim, bias=False)
self.out_proj = nn.Linear(embed_dim, embed_dim, bias=False)
def forward(self, query, key, value, mask=None):
batch_size = query.size(0)
# Project the inputs
query = self.query_proj(query)
key = self.key_proj(key)
value = self.value_proj(value)
# Split into multiple heads
query = query.view(batch_size, -1, self.num_heads, self.head_dim)
key = key.view(batch_size, -1, self.num_heads, self.head_dim)
value = value.view(batch_size, -1, self.num_heads, self.head_dim)
# Transpose the dimensions for batch matrix multiplication
query = query.transpose(1, 2)
key = key.transpose(1, 2)
value = value.transpose(1, 2)
# Compute the dot product attention scores
scores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(self.head_dim)
# Apply the mask (if provided)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# Apply the softmax function to get attention weights
weights = F.softmax(scores, dim=-1)
# Compute the weighted sum of the values
attn_output = torch.matmul(weights, value)
# Merge the heads back together
attn_output = attn_output.transpose(1, 2).contiguous()
attn_output = attn_output.view(batch_size, -1, self.embed_dim)
# Project the output
attn_output = self.out_proj(attn_output)
return attn_output, weights
上述代码定义了一个 MultiHeadAttention 类,其中 embed_dim 表示输入Q、K 和 V 的隐向量维度,num_heads是头的个数。如果提供掩码,可以对某些信息进行掩蔽。在 GPT系列模型中,掩码是对未来的信息进行掩盖,避免了信息的左向流动,也就是解码器的结构。在BERT中使用的是编码器结构,是双向语言模型,则不对其进行掩蔽。而在变换器网络本身,则都用到了。
基于变换器网络延伸出的许多其他模型,比如后面会专文介绍的稀疏变换器网络,就是用了不同的掩码来实现。
5. 编码器
在实现了多头自注意力后,编码器的实现就很简单了,编码器的逻辑如下图所示。
从图7可以看出,编码器多头自注意力和前馈网络组成。多头自注意力算出来后,与输入的隐向量相加,即残差飞线。然后使用层归一化(Layer Norm)进行归一化。此后,使用Feed Forward网络进行进一步的编码学习,然后又是一个残差飞线和层归一化。残差飞线和层归一化是非常经典的用于提高训练期间梯度流的效果的方法。
用 Pytorch 将其组合起来非常简单,代码如下:
import torch
import torch.nn as nn
class EncoderLayer(nn.Module):
def __init__(self, embed_dim, num_heads, ff_dim):
super(EncoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(embed_dim, num_heads)
self.ffn = nn.Sequential(
nn.Linear(embed_dim, ff_dim),
nn.ReLU(),
nn.Linear(ff_dim, embed_dim)
)
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
self.dropout = nn.Dropout(p=0.1)
def forward(self, x, mask=None):
# Self-attention
attn_output, _ = self.self_attn(x, x, x, mask)
# Apply residual connection and layer normalization
x = self.norm1(x + self.dropout(attn_output))
# Feed-forward network
ffn_output = self.ffn(x)
# Apply residual connection and layer normalization
x = self.norm2(x + self.dropout(ffn_output))
return x
值得一提的是,对于大模型来说,这里的 Feed Forward网络可能发挥这非常关键的作用,后面拟专文介绍。
6. 解码器
解码器的架构如下图所示:
图8 解码器架构
与编码器类似,用 Pytorch 实现解码器也不难,其示例代码如下:
import torch
import torch.nn as nn
class DecoderLayer(nn.Module):
def __init__(self, embed_dim, num_heads, ff_dim):
super(DecoderLayer, self).__init__()
self.self_attn = MultiHeadAttention(embed_dim, num_heads)
self.enc_dec_attn = MultiHeadAttention(embed_dim, num_heads)
self.ffn = nn.Sequential(
nn.Linear(embed_dim, ff_dim),
nn.ReLU(),
nn.Linear(ff_dim, embed_dim)
)
self.norm1 = nn.LayerNorm(embed_dim)
self.norm2 = nn.LayerNorm(embed_dim)
self.norm3 = nn.LayerNorm(embed_dim)
self.dropout = nn.Dropout(p=0.1)
def forward(self, x, enc_output, self_mask=None, enc_mask=None):
# Self-attention
self_attn_output, _ = self.self_attn(x, x, x, self_mask)
# Apply residual connection and layer normalization
x = self.norm1(x + self.dropout(self_attn_output))
# Encoder-decoder attention
enc_dec_attn_output, _ = self.enc_dec_attn(x, enc_output, enc_output, enc_mask)
# Apply residual connection and layer normalization
x = self.norm2(x + self.dropout(enc_dec_attn_output))
# Feed-forward network
ffn_output = self.ffn(x)
# Apply residual connection and layer normalization
x = self.norm3(x + self.dropout(ffn_output))
return x
这里要说明的是,变换器网络本身是序列到序列的网络,解码器的输入中还包括编码器的输出,故而相对来说更复杂些。如 GPT 系列无监督大模型所使用的“解码器”还更简单,在后面我们对 GPT 全解析中,会进一步介绍。
7. 位置编码
由于变换器网络不使用递归或卷积,不像LSTM 等网络一样自带序列属性。因此需要一种方式来将每个子单元在序列中的位置信息纳入考虑。这是通过位置编码实现的,它将一个向量添加到每个嵌入中,编码其位置信息。位置编码的公式如下所示,非常简单。
根据公式实现了位置编码部分,代码如下:
import torch
import torch.nn as nn
class PositionalEncoding(nn.Module):
def __init__(self, max_seq_len, embed_dim, device=None):
super(PositionalEncoding, self).__init__()
self.embed_dim = embed_dim
self.pos_encoding = self.compute_positional_encoding(max_seq_len, embed_dim, device)
self.pos_encoding.requires_grad = False
def forward(self, x):
# Add the positional encoding to the input tensor
seq_length = x.size(1)
x = x + self.pos_encoding[:seq_length, :]
return x
def compute_positional_encoding(self, max_seq_len, embed_dim, devide):
pos_encoding = torch.zeros(max_seq_len, embed_dim)
# 计算公式如上图所示
pos = torch.arange(0, max_seq_len).unsqueeze(1)
div_term = 10000.0** torch.arange(0, embed_dim, 2) * 2 / embed_dim
pos_encoding[:, 0::2] = torch.sin(pos / div_term)
pos_encoding[:, 1::2] = torch.cos(pos / div_term)
if devide:
pos_encoding = pos_encoding.to(devide)
return pos_encoding
8. 变换器网络代码实现
到此,实现一个变换器网络代码就非常简单了。其步骤包括:
嵌入层
位置编码:
编码器和解码器
参照前面的变换器网络的架构图,使用 Pytorch 编码实现也不难,示例代码如下:
import torch
import torch.nn as nn
class Transformer(nn.Module):
def __init__(self, input_vocab_size, output_vocab_size, max_seq_len,
embed_dim, num_heads, ff_dim, num_layers, device):
super(Transformer, self).__init__()
self.embedding = nn.Embedding(input_vocab_size, embed_dim)
self.pos_encoding = PositionalEncoding(max_seq_len, embed_dim, device)
self.encoder_layers = nn.ModuleList([
EncoderLayer(embed_dim, num_heads, ff_dim) for _ in range(num_layers)
])
self.decoder_layers = nn.ModuleList([
DecoderLayer(embed_dim, num_heads, ff_dim) for _ in range(num_layers)
])
self.fc = nn.Linear(embed_dim, output_vocab_size)
def forward(self, src, tgt, src_mask=None):
if src_mask is None:
src_mask = self.get_src_mask(src)
tgt_mask = self.get_tgt_mask(tgt)
memory = self.encode(src, src_mask)
output = self.decode(tgt, memory, src_mask, tgt_mask)
output = self.fc(output)
return output
def encode(self, src, src_mask):
x = self.embedding(src)
x = self.pos_encoding(x)
for layer in self.encoder_layers:
x = layer(x, src_mask)
return x
def decode(self, tgt, memory, src_mask, tgt_mask):
x = self.embedding(tgt)
x = self.pos_encoding(x)
for layer in self.decoder_layers:
x = layer(x, memory, src_mask, tgt_mask)
return x
def get_src_mask(self, src):
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)
return src_mask.to(torch.bool).to(src.device)
def get_tgt_mask(self, tgt):
tgt_pad_mask = (tgt != 0).unsqueeze(1).unsqueeze(2)
tgt_length = tgt.shape[1]
tgt_sub_mask = torch.tril(torch.ones((tgt_length, tgt_length), dtype=torch.bool))
tgt_mask = tgt_pad_mask & tgt_sub_mask.to(tgt.device)
return tgt_mask.to(torch.bool)
9. 总结
变换器网络Transformer诞生于2017年,因其强大的性能,已经彻底改变了自然语言处理以至于所有人工智能各细分领域。而在去年年底出现了 ChatGPT ,爆火出圈,甚至引起AGI 即将来临的大讨论。而这一切,都起源于变换器网络。同时,在未来,要做出自己的 ChatGPT 或者类似的 AGI,学习和理解变换器网络必不可少。希望本文能够帮助你理解和使用。
有任何问题和建议,也可以在评论中讨论。
10. 参考文献
[1]The new Bing and Edge - Increasing Limits on Chat Sessions. bing.com. https://blogs.bing.com/search/february-2023/The-new-Bing-and-Edge-Increasing-Limits-on-Chat-Sessions. 2023.
[2]A Conversation With Bing’s Chatbot Left Me Deeply Unsettled. The New York Times. https://www.nytimes.com/2023/02/16/technology/bing-chatbot-microsoft-chatgpt.html. 2023.
[3]Bing:“I will not harm you unless you harm me first”. Simonwillison Blog. https://simonwillison.net/2023/Feb/15/bing/. 2023.
[4]Is LaMDA Sentient? — an Interview. https://cajundiscordian.medium.com/is-lamda-sentient-an-interview-ea64d916d917. 2022
[5]Planning for AGI and beyond. OpenAI. https://openai.com/blog/planning-for-agi-and-beyond/. 2023.
[6]王文广. 知识图谱:认知智能理论与实战[M] //电子工业出版社, 2022
[7] Ashish Vaswani, Noam Shazeer, Niki Parmar et al. Attention is all you need.[C] //In advances in neural information processing systems. 2017. P5998-6008.