gitweixin
  • 首页
  • 小程序代码
    • 资讯读书
    • 工具类
    • O2O
    • 地图定位
    • 社交
    • 行业软件
    • 电商类
    • 互联网类
    • 企业类
    • UI控件
  • 大数据开发
    • Hadoop
    • Spark
    • Hbase
    • Elasticsearch
    • Kafka
    • Flink
    • 数据仓库
    • 数据挖掘
    • flume
    • Kafka
    • Hive
    • shardingsphere
    • solr
  • 开发博客
    • Android
    • php
    • python
    • 运维
    • 技术架构
    • 数据库
  • 程序员网赚
  • bug清单
  • 量化投资
  • 在线查询工具
    • 去行号
    • 在线时间戳转换工具
    • 免费图片批量修改尺寸在线工具
    • SVG转JPG在线工具

分类归档深度学习

精品微信小程序开发门户,代码全部亲测可用

  • 首页   /  人工智能
  • 分类归档: "深度学习"
  • ( 页面2 )
深度学习 3月 27,2022

什么是BERT模型和作用?

Google 最近进行了一项重要的算法更新,称为 Google BERT,以更好地理解搜索并为更自然的语言查询生成结果。算法更新还将为他们的人工智能技术提供自然语言和搜索上下文。每天数十亿次的搜索将有助于增强 Google 的 AI 功能,从而改善搜索结果、提高对语音搜索的理解,并帮助 Google 更好地了解消费者行为。

向 Google BERT 打个招呼!

BERT 是谷歌自 2015 年推出 RankBrain 以来最大的搜索算法。事实上,谷歌表示这次更新代表了“过去五年来最大的飞跃,也是搜索领域最大的飞跃之一。” BERT 通过了解用户在更具会话结构的查询中的意图,使搜索更加集中。

让我们更好地了解 BERT,并了解它如何帮助优化您的搜索。

什么是 BERT?
BERT 是一种人工智能 (AI) 系统,代表 Transformers 的双向编码器表示。这种搜索进步是谷歌对转换器研究的结果,转换器是处理与句子中所有其他单词相关的单词的模型,而不是按顺序一个接一个地处理。简而言之,此更新侧重于短语而不是简单的单词。

在排名结果方面,BERT 将影响十分之一的搜索查询。此算法更新也被应用于帮助为世界各地的人们提供更好的搜索。通过从一种语言中学习,相关结果可以应用于许多其他语言。 Google 正在许多国家/地区使用 BERT 模型来改进片段,支持韩语、印地语和葡萄牙语等 70 多种语言。

BERT+
然而,BERT 不仅仅是一种搜索算法。它也是一个机器学习自然语言处理框架、一个不断发展的计算效率工具,以及一个开源研究项目和学术论文,于 2018 年 10 月首次发表,名称为 BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding。

怎么运作
BERT 的美妙之处在于,无论单词的拼写方式或它们在查询中的顺序,它都能计算出您的搜索并显示相关信息。 BERT 能够基于句子中的整个单词集而不是传统的单词序列来训练语言模型,例如从左到右或从左到右和从右到左的组合。谷歌现在可以解决由许多具有多种含义的单词组成的模棱两可的短语。

此外,日常语言中存在细微差别,计算机并不完全理解人类的行为方式。因此,当搜索包含一个短语时,BERT 将解释它并根据句子的创建方式和发音给出结果。这很重要,因为即使是最简单的短语与单数单词相比也可能具有完全不同的含义。例如,在“纽约到洛杉矶”和“四分之一到九点”这样的短语中,“到”这个词有不同的含义,这可能会导致搜索引擎混淆。 BERT 区分这些细微差别以促进更相关的搜索。

RankBrain 仍在努力
RankBrain 是谷歌第一个用于理解查询的人工智能方法。它同时查看搜索和谷歌索引中的网页内容,以更好地理解单词的含义。 BERT 不会取代 RankBrain,它是更好地理解内容、自然语言和查询的扩展。 RankBrain 仍将被使用,但当 Google 认为在 BERT 的帮助下更适合查询时,搜索将使用新模型。似乎这句谚语是真的……两种搜索算法比一种更好!

更智能的搜索结果
作为谷歌最新的算法更新,BERT 通过更好地理解自然语言来影响搜索,尤其是在会话短语中。 BERT 将影响大约 10% 的查询以及自然排名和精选片段。所以这对谷歌……和我们所有人来说都是一件大事。有这么多问题,找到与我们的“正常”短语查询匹配的相关结果肯定会让我们的搜索体验更加轻松。搜索愉快!

作者 east
深度学习 3月 27,2022

深入了解 BERT 模型的代码-分解 Hugging Face Bert 实现

已经有很多关于如何从头开始创建简化的 Bert 模型及其工作原理的教程。 在本文中,我们将做一些稍微不同的事情——我们通过 BERT 的实际 Hugging face 实现分解其所有组件。


介绍
在过去的几年里,Transformer 模型彻底改变了 NLP 领域。 BERT (Bidirectional Encoder Representations from Transformers) 是最成功的 Transformer 之一——由于与 LSTM 的递归结构不同,通过注意力机制和训练时间更好地理解了上下文,它在性能上都优于以前的 SOTA 模型(如 LSTM), BERT 是可并行的。
现在不用再等了,让我们深入研究代码,看看它是如何工作的。 首先我们加载 Bert 模型并输出 BertModel 架构:

# with bertviz package we can output attentions and hidden states 
from bertviz.transformers_neuron_view import BertModel, BertConfig
from transformers import BertTokenizer

max_length = 256
config = BertConfig.from_pretrained("bert-base-cased", output_attentions=True, output_hidden_states=True, return_dict=True)
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
config.max_position_embeddings = max_length

model = BertModel(config)
model = model.eval()

display(model)
# output : 

BertModel(
  (embeddings): BertEmbeddings(
    (word_embeddings): Embedding(30522, 768, padding_idx=0)
    (position_embeddings): Embedding(256, 768)
    (token_type_embeddings): Embedding(2, 768)
    (LayerNorm): BertLayerNorm()
    (dropout): Dropout(p=0.1, inplace=False)
  )
  (encoder): BertEncoder(
    (layer): ModuleList(
      (0): BertLayer(
        (attention): BertAttention(
          (self): BertSelfAttention(
            (query): Linear(in_features=768, out_features=768, bias=True)
            (key): Linear(in_features=768, out_features=768, bias=True)
            (value): Linear(in_features=768, out_features=768, bias=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (output): BertSelfOutput(
            (dense): Linear(in_features=768, out_features=768, bias=True)
            (LayerNorm): BertLayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
        (intermediate): BertIntermediate(
          (dense): Linear(in_features=768, out_features=3072, bias=True)
        )
        (output): BertOutput(
          (dense): Linear(in_features=3072, out_features=768, bias=True)
          (LayerNorm): BertLayerNorm()
          (dropout): Dropout(p=0.1, inplace=False)
        )
      )
      (1): BertLayer(
        (attention): BertAttention(
          (self): BertSelfAttention(
            (query): Linear(in_features=768, out_features=768, bias=True)
            (key): Linear(in_features=768, out_features=768, bias=True)
            (value): Linear(in_features=768, out_features=768, bias=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (output): BertSelfOutput(
            (dense): Linear(in_features=768, out_features=768, bias=True)
            (LayerNorm): BertLayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
        (intermediate): BertIntermediate(
          (dense): Linear(in_features=768, out_features=3072, bias=True)
        )
        (output): BertOutput(
          (dense): Linear(in_features=3072, out_features=768, bias=True)
          (LayerNorm): BertLayerNorm()
          (dropout): Dropout(p=0.1, inplace=False)
        )
      )
      
      ......

      (11): BertLayer(
        (attention): BertAttention(
          (self): BertSelfAttention(
            (query): Linear(in_features=768, out_features=768, bias=True)
            (key): Linear(in_features=768, out_features=768, bias=True)
            (value): Linear(in_features=768, out_features=768, bias=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
          (output): BertSelfOutput(
            (dense): Linear(in_features=768, out_features=768, bias=True)
            (LayerNorm): BertLayerNorm()
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
        (intermediate): BertIntermediate(
          (dense): Linear(in_features=768, out_features=3072, bias=True)
        )
        (output): BertOutput(
          (dense): Linear(in_features=3072, out_features=768, bias=True)
          (LayerNorm): BertLayerNorm()
          (dropout): Dropout(p=0.1, inplace=False)
        )
      )
    )
  )
  (pooler): BertPooler(
    (dense): Linear(in_features=768, out_features=768, bias=True)
    (activation): Tanh()
  )
)

我们分别分析了 3 个部分:Embeddings、具有 12 个重复 Bert 层的 Encoder 和 Pooler。 最终我们将添加一个分类层。
伯特嵌入:
从原始文本开始,首先要做的是将我们的句子拆分为标记,然后我们可以将其传递给 BertEmbeddings。 我们使用基于 WordPiece 的 BertTokenizer——子词标记化可训练算法,有助于平衡词汇量和词汇量外的单词。 看不见的词被分成子词,这些子词是在分词器的训练阶段派生的(这里有更多详细信息)。 现在让我们从 20newsgroups 数据集中导入几个句子并标记它们

from sklearn.datasets import fetch_20newsgroups
newsgroups_train = fetch_20newsgroups(subset='train')
inputs_tests = tokenizer(newsgroups_train['data'][:3], truncation=True, padding=True, max_length=max_length, return_tensors='pt')

一旦句子被分割成标记,我们就会为每个标记分配一个具有代表性的数字向量,该向量在 n 维空间中表示该标记。每个维度都包含该单词的一些信息,因此如果我们假设特征是 Wealth、Gender、Cuddly,则模型在训练嵌入层之后,将使用以下 3 维向量表示例如单词 king:(0.98, 1, 0.01)和 cat (0.02, 0.5, 1)。然后我们可以使用这些向量来计算单词之间的相似度(使用余弦距离)并做许多其他事情。
注意:实际上,我们无法得出这些特征名称的真正含义,但以这种方式思考它们有助于获得更清晰的画面。
所以 word_embeddings 在这种情况下是一个形状矩阵 (30522, 768),其中第一个维度是词汇维度,而第二个维度是嵌入维度,即我们用来表示一个单词的特征的数量。对于 base-bert,它是 768,对于更大的型号,它会增加。一般来说,嵌入维度越高,我们可以更好地表示某些单词——这在一定程度上是正确的,在某些时候增加维度不会大大提高模型的准确性,而计算复杂度却可以。

model.embeddings.word_embeddings.weight.shape
output: torch.Size([30522, 768])

需要 position_embeddings 是因为,与 LSTM 模型不同,例如 LSTM 模型顺序处理令牌,因此通过构造具有每个令牌的顺序信息,Bert 模型并行处理令牌并合并每个令牌的位置信息,我们需要从 position_embeddings 矩阵添加此信息 . 它的形状是 (256, 768),其中前者表示最大句子长度,而后者是词嵌入的特征维度——因此根据每个标记的位置,我们检索相关向量。 在这种情况下,我们可以看到这个矩阵是学习的,但还有其他实现是使用正弦和余弦构建的。

model.embeddings.position_embeddings.weight.shapeoutput: torch.Size([256, 768])

token_type_embeddings 在这里是“冗余的”,来自 Bert 训练任务,其中评估了两个句子之间的语义相似性——需要这种嵌入来区分第一句和第二句。 我们不需要它,因为我们只有一个用于分类任务的输入句子。
一旦我们为句子中的每个单词提取单词嵌入、位置嵌入和类型嵌入,我们只需将它们相加即可得到完整的句子嵌入。 所以对于第一句话,它将是:

f1 = torch.index_select(model.embeddings.word_embeddings.weight, 0, inputs_tests['input_ids'][0])  # words embeddings
  + torch.index_select(model.embeddings.position_embeddings.weight, 0, torch.tensor(range(inputs_tests['input_ids'][0].size(0))).long()) \ # pos embeddings
 + torch.index_select(model.embeddings.token_type_embeddings.weight, 0, inputs_tests['token_type_ids'][0]) # token embeddings

对于我们的 3 个句子的 mini-batch,我们可以通过以下方式获取它们:

n_batch = 3
shape_embs = (inputs_tests['input_ids'].shape) + (model.embeddings.word_embeddings.weight.shape[1], )
w_embs_batch = torch.index_select(model.embeddings.word_embeddings.weight, 0, inputs_tests['input_ids'].reshape(1,-1).squeeze(0)).reshape(shape_embs)
pos_embs_batch = torch.index_select(model.embeddings.position_embeddings.weight, 0, 
                                    torch.tensor(range(inputs_tests['input_ids'][1].size(0))).repeat(1, n_batch).squeeze(0)).reshape(shape_embs)
type_embs_batch = torch.index_select(model.embeddings.token_type_embeddings.weight, 0, 
                                     inputs_tests['token_type_ids'].reshape(1,-1).squeeze(0)).reshape(shape_embs)
batch_all_embs = w_embs_batch + pos_embs_batch + type_embs_batch
batch_all_embs.shape # (batch_size, n_words, embedding dim)

接下来我们有一个 LayerNorm 步骤,它可以帮助模型更快地训练和更好地泛化。 我们通过令牌的均值嵌入和标准差对每个令牌的嵌入进行标准化,使其具有零均值和单位方差。 然后,我们应用经过训练的权重和偏差向量,以便可以将其转换为具有不同的均值和方差,以便训练期间的模型可以自动适应。 因为我们独立于其他示例计算不同示例的均值和标准差,所以它与批量归一化不同,后者的归一化是跨批次维度的,因此取决于批次中的其他示例。

# single example normalization
ex1 = f1[0, :]
ex1_mean = ex1.mean()
ex1_std = (ex1 - ex1_mean).pow(2).mean()
norm_example = ((ex1- ex1_mean)/torch.sqrt(ex1_std + 1e-12))
norm_example_centered = model.embeddings.LayerNorm.weight * norm_example + model.embeddings.LayerNorm.bias


def layer_norm(x, w, b):
    mean_x = x.mean(-1, keepdim=True)
    std_x = (x - mean_x).pow(2).mean(-1, keepdim=True)
    x_std = (x - mean_x) / torch.sqrt(std_x + 1e-12)
    shifted_x = w * x_std + b
    return shifted_x
  
# batch normalization
norm_embs = layer_norm(batch_all_embs, model.embeddings.LayerNorm.weight, model.embeddings.LayerNorm.bias

让我们最后应用 Dropout,我们用零替换一些具有一定 dropout 概率的值。 Dropout 有助于减少过度拟合,因为我们随机阻止来自某些神经元的信号,因此网络需要找到其他路径来减少损失函数,因此它学会了如何更好地泛化而不是依赖某些路径。 我们还可以将 dropout 视为一种模型集成技术,因为在每一步的训练过程中,我们随机停用某些神经元,最终形成“不同”的网络,最终在评估期间集成这些神经元。
注意:因为我们将模型设置为评估模式,我们将忽略所有的 dropout 层,它们仅在训练期间使用。 为了完整起见,我们仍将其包括在内。

norm_embs_dropout = model.embeddings.dropout(norm_embs)

我们可以检查我们是否获得了与模型相同的结果:

embs_model = model.embeddings(inputs_tests[‘input_ids’], inputs_tests[‘token_type_ids’])
torch.allclose(embs_model, norm_embs, atol=1e-06) # True

编码器
编码器是最神奇的地方。有 12 个 BertLayers,前一个的输出被馈送到下一个。这是使用注意力来创建与上下文相关的原始嵌入的不同表示的地方。在 BertLayer 中,我们首先尝试理解 BertAttention——在导出每个单词的嵌入之后,Bert 使用 3 个矩阵——Key、Query 和 Value,来计算注意力分数,并根据句子中的其他单词导出单词嵌入的新值;通过这种方式,Bert 是上下文感知的,每个单词的嵌入而不是固定的,上下文独立是基于句子中的其他单词推导出来的,并且在为某个单词推导新嵌入时其他单词的重要性由注意力分数表示。为了导出每个单词的查询和键向量,我们需要将其嵌入乘以经过训练的矩阵(查询和键是分开的)。例如,要导出第一句的第一个词的查询向量:

att_head_size = int(model.config.hidden_size/model.config.num_attention_heads)
n_att_heads = model.config.num_attention_heads
norm_embs[0][0, :] @ model.encoder.layer[0].attention.self.query.weight.T[:, :att_head_size] + \
                      model.encoder.layer[0].attention.self.query.bias[:att_head_size]

我们可以注意到,在整个查询和关键矩阵中,我们只选择了前 64 个 (=att_head_size) 列(原因将在稍后说明)——这是转换后单词的新嵌入维度,它小于原始嵌入 维度 768。这样做是为了减少计算负担,但实际上更长的嵌入可能会带来更好的性能。 实际上,这是降低复杂性和提高性能之间的权衡。
现在我们可以推导出整个句子的 Query 和 Key 矩阵:

Q_first_head = norm_embs[0] @ model.encoder.layer[0].attention.self.query.weight.T[:, :att_head_size] + \
               model.encoder.layer[0].attention.self.query.bias[:att_head_size] 
K_first_head = norm_embs[0] @ model.encoder.layer[0].attention.self.key.weight.T[:, :att_head_size] + \
               model.encoder.layer[0].attention.self.key.bias[:att_head_size]

为了计算注意力分数,我们将 Query 矩阵乘以 Key 矩阵,并将其标准化为新嵌入维度的平方根 (=64=att_head_size)。 我们还添加了一个修改后的注意力掩码。 初始注意掩码 (inputs[‘attention_mask’][0]) 是一个 1 和 0 的张量,其中 1 表示该位置有一个标记,0 表示它是一个填充标记。
如果我们从 1 中减去注意力掩码并将其乘以一个高负数,当我们应用 SoftMax 时,我们实际上将那些负值发送到零,然后根据其他值推导出概率。 让我们看下面的例子:
如果我们有一个 3 个标记 + 2 个填充的句子,我们会得到以下注意力掩码:[0,0,0, -10000, -10000]
让我们应用 SoftMax 函数:

torch.nn.functional.softmax(torch.tensor([0,0,0, -10000, -10000]).float())# tensor([0.3333, 0.3333, 0.3333, 0.0000, 0.0000])mod_attention = (1.0 – inputs[‘attention_mask’][[0]]) * -10000.0attention_scores = torch.nn.Softmax(dim=-1)((Q_first_head @ K_first_head.T)/ math.sqrt(att_head_size) + mod_attention)

让我们检查一下我们得到的注意力分数是否与我们从模型中得到的相同。 我们可以使用以下代码从模型中获取注意力分数:

as we defined output_attentions=True, output_hidden_states=True, return_dict=True we will get last_hidden_state, pooler_output, hidden_states for each layer and attentions for each layer
out_view = model(**inputs_tests)

out_view 包含:
last_hidden_state (batch_size, sequence_length, hidden_size) : 最后一个 BertLayer 输出的隐藏状态
pooler_output (batch_size, hidden_size) : Pooler 层的输出
hidden_states (batch_size, sequence_length, hidden_size):模型在每个 BertLayer 输出的隐藏状态加上初始嵌入
注意(batch_size、num_heads、sequence_length、sequence_length):每个 BertLayer 一个。 注意力 SoftMax 后的注意力权重

torch.allclose(attention_scores, out_view[-1][0][‘attn’][0, 0, :, :], atol=1e-06)) # True
print(attention_scores[0, :])
tensor([1.0590e-04, 2.1429e-03, .... , 4.8982e-05], grad_fn=<SliceBackward>)

注意分数矩阵的第一行表示,要为第一个标记创建新嵌入,我们需要注意权重 = 1.0590e-04 的第一个标记(对自身),权重 = 2.1429e-03 的第二个标记 等等。 换句话说,如果我们将这些分数乘以其他标记的向量嵌入,我们会得出第一个标记的新表示,但是,我们将使用下面计算的值矩阵,而不是实际使用嵌入。
值矩阵的推导方式与查询和键矩阵相同:

V_first_head = norm_embs[0] @ model.encoder.layer[0].attention.self.value.weight.T[:, :att_head_size] + \
              model.encoder.layer[0].attention.self.value.bias[:att_head_size]

然后我们将这些值乘以注意力分数以获得新的上下文感知词表示

new_embed_1 = (attention_scores @ V_first_head)

现在您可能想知道,为什么我们要从张量中选择前 64 个 (=att_head_size) 元素。 好吧,我们上面计算的是 Bert 注意力层的一个头,但实际上有 12 个。 这些注意力头中的每一个都会创建不同的单词表示(new_embed_1 矩阵),例如,给定以下句子“ I like to eat pizza in the Italian restaurants ”,在第一个头中,“pizza”一词可能主要关注前一个单词 ,单词本身以及后面的单词和剩余单词的注意力将接近于零。 在下一个头中,它可能会关注所有动词(like 和 eat),并以这种方式捕捉与第一个头不同的关系。
现在,我们可以以矩阵形式将它们一起推导,而不是单独推导每个头部:

Q = norm_embs @ model.encoder.layer[0].attention.self.query.weight.T + model.encoder.layer[0].attention.self.query.bias
K = norm_embs @ model.encoder.layer[0].attention.self.key.weight.T + model.encoder.layer[0].attention.self.key.bias
V = norm_embs @ model.encoder.layer[0].attention.self.value.weight.T + model.encoder.layer[0].attention.self.value.bias
new_x_shape = Q.size()[:-1] + (n_att_heads, att_head_size)
new_x_shape # torch.Size([3, 55, 12, 64])
Q_reshaped = Q.view(*new_x_shape)
K_reshaped = K.view(*new_x_shape)
V_reshaped = V.view(*new_x_shape)
att_scores = (Q_reshaped.permute(0, 2, 1, 3) @ K_reshaped.permute(0, 2, 1, 3).transpose(-1, -2))
att_scores = (att_scores/ math.sqrt(att_head_size)) + extended_attention_mask
attention_probs = torch.nn.Softmax(dim=-1)(att_scores)

第一个例子和第一个 head 的注意力和我们之前推导出的一样:

example = 0
head = 0
torch.allclose(attention_scores, attention_probs[example][head]) # True

我们现在将 12 个头的结果连接起来,并将它们传递给我们已经在嵌入部分中看到的一堆线性层、归一化层和 dropout,以获得第一层的编码器结果。

att_heads = []
for i in range(12):
  att_heads.append(attention_probs[0][i] @ V_reshaped[0, : , i, :])
output_dense = torch.cat(att_heads, 1) @ model.encoder.layer[0].attention.output.dense.weight.T + \
               model.encoder.layer[0].attention.output.dense.bias
output_layernorm = layer_norm(output_dense + norm_embs[0], 
                              model.encoder.layer[0].attention.output.LayerNorm.weight, 
                              model.encoder.layer[0].attention.output.LayerNorm.bias)
interm_dense = torch.nn.functional.gelu(output_layernorm @ model.encoder.layer[0].intermediate.dense.weight.T + \
                                        model.encoder.layer[0].intermediate.dense.bias)
out_dense = interm_dense @ model.encoder.layer[0].output.dense.weight.T + model.encoder.layer[0].output.dense.bias
out_layernorm  = layer_norm(out_dense + output_layernorm, 
                            model.encoder.layer[0].output.LayerNorm.weight, 
                            model.encoder.layer[0].output.LayerNorm.bias)

output_dense 我们只是通过线性层传递连接的注意力结果。然后我们需要进行归一化,但我们可以看到,我们不是立即对 output_dense 进行归一化,而是首先将其与我们的初始嵌入相加——这称为残差连接。当我们增加神经网络的深度时,即堆叠越来越多的层时,我们会遇到梯度消失/爆炸的问题,当梯度消失的情况下,模型无法再学习,因为传播的梯度接近于零初始层停止改变权重并改进。当权重因极端更新而最终爆炸(趋于无穷大)而无法稳定时,梯度爆炸的相反问题。现在,正确初始化权重和归一化有助于解决这个问题,但观察到的是,即使网络变得更加稳定,性能也会随着优化的困难而下降。添加这些残差连接有助于提高性能,即使我们不断增加深度,网络也变得更容易优化。 out_layernorm 中也使用了残差连接,它实际上是第一个 BertLayer 的输出。最后要注意的是,当我们计算 interterm_dense 时,在将 AttentionLayer 的输出传递到线性层之后,会应用非线性 GeLU 激活函数。 GeLU 表示为:

查看图表我们可以看到,如果由公式 max(input, 0) 给出的 ReLU 在正域中是单调的、凸的和线性的,那么 GeLU 在正域中是非单调的、非凸的和非线性的 正域,因此可以逼近更容易复杂的函数。

我们现在已经成功地复制了整个 BertLayer。 该层的输出(与初始嵌入的形状相同)进入下一个 BertLayer,依此类推。 总共有 12 个 BertLayers。 因此,将所有这些放在一起,我们可以从编码器中获得所有 3 个示例的最终结果:

n_batch = 3
tot_n_layers = 12
tot_n_heads = 12
shape_embs = (inputs_tests['input_ids'].shape) + (model.embeddings.word_embeddings.weight.shape[1], )
w_embs_batch = torch.index_select(model.embeddings.word_embeddings.weight, 
                                  0, inputs_tests['input_ids'].reshape(1,-1).squeeze(0)).reshape(shape_embs)
pos_embs_batch = torch.index_select(model.embeddings.position_embeddings.weight, 0, 
                                    torch.tensor(range(inputs_tests['input_ids'][1].size(0))).repeat(1, n_batch).squeeze(0)).reshape(shape_embs)
type_embs_batch = torch.index_select(model.embeddings.token_type_embeddings.weight, 0, 
                                     inputs_tests['token_type_ids'].reshape(1,-1).squeeze(0)).reshape(shape_embs)
batch_all_embs = w_embs_batch + pos_embs_batch + type_embs_batch
normalized_embs = layer_norm(batch_all_embs, model.embeddings.LayerNorm.weight, model.embeddings.LayerNorm.bias)
extended_attention_mask = inputs['attention_mask'].unsqueeze(1).unsqueeze(2)
extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0
for layer_n in range(tot_n_layers):
 if layer_n == 0:
   # compute Q, K and V matrices
   Q = normalized_embs @ model.encoder.layer[layer_n].attention.self.query.weight.T + \ 
                         model.encoder.layer[layer_n].attention.self.query.bias
   K = normalized_embs @ model.encoder.layer[layer_n].attention.self.key.weight.T + \
                         model.encoder.layer[layer_n].attention.self.key.bias
   V = normalized_embs @ model.encoder.layer[layer_n].attention.self.value.weight.T + \
                         model.encoder.layer[layer_n].attention.self.value.bias
   # reshape
   new_x_shape = Q.size()[:-1] + (n_att_heads, att_head_size)
   Q_reshaped = Q.view(*new_x_shape)
   K_reshaped = K.view(*new_x_shape)
   V_reshaped = V.view(*new_x_shape)
   # compute attention probabilities
   att_scores = (Q_reshaped.permute(0, 2, 1, 3) @ K_reshaped.permute(0, 2, 1, 3).transpose(-1, -2))
   att_scores = (att_scores/ math.sqrt(att_head_size)) + extended_attention_mask
   attention_probs = torch.nn.Softmax(dim=-1)(att_scores)
   # concatenate attention heads
   att_heads = []
   for i in range(tot_n_heads):
    att_heads.append(attention_probs[:, i] @ V_reshaped[:, : , i, :])

   output_dense = torch.cat(att_heads, 2) @ model.encoder.layer[layer_n].attention.output.dense.weight.T + \
                                            model.encoder.layer[layer_n].attention.output.dense.bias
   # normalization + residual connection
   output_layernorm = layer_norm(output_dense + normalized_embs, 
                                 model.encoder.layer[layer_n].attention.output.LayerNorm.weight,
                                 model.encoder.layer[layer_n].attention.output.LayerNorm.bias)
   # linear layer + non linear gelu activation
   interm_dense = torch.nn.functional.gelu(output_layernorm @ model.encoder.layer[layer_n].intermediate.dense.weight.T + \
                                           model.encoder.layer[layer_n].intermediate.dense.bias)
   # linear layer
   out_dense = interm_dense @ model.encoder.layer[layer_n].output.dense.weight.T + model.encoder.layer[layer_n].output.dense.bias
   # normalization + residual connection
   out_layernorm = layer_norm(out_dense + output_layernorm, 
                              model.encoder.layer[layer_n].output.LayerNorm.weight, 
                              model.encoder.layer[layer_n].output.LayerNorm.bias)
 else:
   # compute Q, K and V matrices
   Q = out_layernorm @ model.encoder.layer[layer_n].attention.self.query.weight.T + \
                              model.encoder.layer[layer_n].attention.self.query.bias
   K = out_layernorm @ model.encoder.layer[layer_n].attention.self.key.weight.T + \
                              model.encoder.layer[layer_n].attention.self.key.bias
   V = out_layernorm @ model.encoder.layer[layer_n].attention.self.value.weight.T + \
                              model.encoder.layer[layer_n].attention.self.value.bias
   # reshape
   Q_reshaped = Q.view(*new_x_shape)
   K_reshaped = K.view(*new_x_shape)
   V_reshaped = V.view(*new_x_shape)
   # compute attention probabilities
   att_scores = (Q_reshaped.permute(0, 2, 1, 3) @ K_reshaped.permute(0, 2, 1, 3).transpose(-1, -2))
   att_scores = (att_scores/ math.sqrt(att_head_size)) + extended_attention_mask
   attention_probs = torch.nn.Softmax(dim=-1)(att_scores)
   # concatenate attention heads
   att_heads = []
   for i in range(tot_n_heads):
    att_heads.append(attention_probs[:, i] @ V_reshaped[:, : , i, :])

   output_dense = torch.cat(att_heads, 2) @ model.encoder.layer[layer_n].attention.output.dense.weight.T + \
                                            model.encoder.layer[layer_n].attention.output.dense.bias
   # normalization + residual connection
   output_layernorm = layer_norm(output_dense + out_layernorm, 
                                 model.encoder.layer[layer_n].attention.output.LayerNorm.weight, 
                                 model.encoder.layer[layer_n].attention.output.LayerNorm.bias)

   # linear layer + non linear gelu activation
   interm_dense = torch.nn.functional.gelu(output_layernorm @ model.encoder.layer[layer_n].intermediate.dense.weight.T + \
                                                              model.encoder.layer[layer_n].intermediate.dense.bias)
   # linear layer
   out_dense = interm_dense @ model.encoder.layer[layer_n].output.dense.weight.T + model.encoder.layer[layer_n].output.dense.bias
   # normalization + residual connection
   out_layernorm = layer_norm(out_dense + output_layernorm, 
                              model.encoder.layer[layer_n].output.LayerNorm.weight, 
                              model.encoder.layer[layer_n].output.LayerNorm.bias)

注意 out_layernorm – 每层的输出如何被馈送到下一层。
我们可以看到这与 out_view 中的结果相同


torch.allclose(out_view[-2][-1], out_layernorm, atol=1e-05) # True


Pooler
现在我们可以获取最后一个 BertLayer 的第一个令牌输出,即 [CLS],将其通过一个线性层并应用一个 Tanh 激活函数来获得池化输出。使用第一个标记进行分类的原因来自于模型是如何被训练为 Bert state 的作者的:
每个序列的第一个标记始终是一个特殊的分类标记 ([CLS])。与该标记对应的最终隐藏状态用作分类任务的聚合序列表示。


out_pooler = torch.nn.functional.tanh(out_layernorm[:, 0] @ model.pooler.dense.weight.T + model.pooler.dense.bias)


分类器
最后,我们创建一个简单的类,它将是一个简单的线性层,但您可以向它添加一个 dropout 和其他东西。我们在这里假设一个二元分类问题(output_dim=2),但它可以是任何维度的。

from torch import nn
class Classifier(nn.Module):
    
    def __init__(self, output_dim=2):
        super(Classifier, self).__init__()
        self.classifier = nn.Linear(model.config.hidden_size, output_dim, bias=True)
    
    def forward(self, x):
        return self.classifier(x)
classif = Classifier()
classif(out_pooler)
tensor([[-0.2918, -0.5782],
        [ 0.2494, -0.1955],
        [ 0.1814,  0.3971]], grad_fn=<AddmmBackward>)

引用:

 
https://arxiv.org/pdf/1606.08415v3.pdf
https://arxiv.org/pdf/1810.04805.pdf
https://jalammar.github.io/illustrated-transformer/
https://github.com/huggingface/transformers/

作者 east

上一 1 2

关注公众号“大模型全栈程序员”回复“小程序”获取1000个小程序打包源码。回复”chatgpt”获取免注册可用chatgpt。回复“大数据”获取多本大数据电子书

标签

AIGC AI创作 bert chatgpt github GPT-3 gpt3 GTP-3 hive mysql O2O tensorflow UI控件 不含后台 交流 共享经济 出行 图像 地图定位 外卖 多媒体 娱乐 小程序 布局 带后台完整项目 开源项目 搜索 支付 效率 教育 日历 机器学习 深度学习 物流 用户系统 电商 画图 画布(canvas) 社交 签到 联网 读书 资讯 阅读 预订

官方QQ群

小程序开发群:74052405

大数据开发群: 952493060

近期文章

  • 详解Python当中的pip常用命令
  • AUTOSAR如何在多个供应商交付的配置中避免ARXML不兼容?
  • C++thread pool(线程池)设计应关注哪些扩展性问题?
  • 各类MCAL(Microcontroller Abstraction Layer)如何与AUTOSAR工具链解耦?
  • 如何设计AUTOSAR中的“域控制器”以支持未来扩展?
  • C++ 中避免悬挂引用的企业策略有哪些?
  • 嵌入式电机:如何在低速和高负载状态下保持FOC(Field-Oriented Control)算法的电流控制稳定?
  • C++如何在插件式架构中使用反射实现模块隔离?
  • C++如何追踪内存泄漏(valgrind/ASan等)并定位到业务代码?
  • C++大型系统中如何组织头文件和依赖树?

文章归档

  • 2025年6月
  • 2025年5月
  • 2025年4月
  • 2025年3月
  • 2025年2月
  • 2025年1月
  • 2024年12月
  • 2024年11月
  • 2024年10月
  • 2024年9月
  • 2024年8月
  • 2024年7月
  • 2024年6月
  • 2024年5月
  • 2024年4月
  • 2024年3月
  • 2023年11月
  • 2023年10月
  • 2023年9月
  • 2023年8月
  • 2023年7月
  • 2023年6月
  • 2023年5月
  • 2023年4月
  • 2023年3月
  • 2023年1月
  • 2022年11月
  • 2022年10月
  • 2022年9月
  • 2022年8月
  • 2022年7月
  • 2022年6月
  • 2022年5月
  • 2022年4月
  • 2022年3月
  • 2022年2月
  • 2022年1月
  • 2021年12月
  • 2021年11月
  • 2021年9月
  • 2021年8月
  • 2021年7月
  • 2021年6月
  • 2021年5月
  • 2021年4月
  • 2021年3月
  • 2021年2月
  • 2021年1月
  • 2020年12月
  • 2020年11月
  • 2020年10月
  • 2020年9月
  • 2020年8月
  • 2020年7月
  • 2020年6月
  • 2020年5月
  • 2020年4月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年3月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年7月
  • 2018年6月

分类目录

  • Android (73)
  • bug清单 (79)
  • C++ (34)
  • Fuchsia (15)
  • php (4)
  • python (43)
  • sklearn (1)
  • 云计算 (20)
  • 人工智能 (61)
    • chatgpt (21)
      • 提示词 (6)
    • Keras (1)
    • Tensorflow (3)
    • 大模型 (1)
    • 智能体 (4)
    • 深度学习 (14)
  • 储能 (44)
  • 前端 (4)
  • 大数据开发 (488)
    • CDH (6)
    • datax (4)
    • doris (30)
    • Elasticsearch (15)
    • Flink (78)
    • flume (7)
    • Hadoop (19)
    • Hbase (23)
    • Hive (40)
    • Impala (2)
    • Java (71)
    • Kafka (10)
    • neo4j (5)
    • shardingsphere (6)
    • solr (5)
    • Spark (99)
    • spring (11)
    • 数据仓库 (9)
    • 数据挖掘 (7)
    • 海豚调度器 (10)
    • 运维 (34)
      • Docker (3)
  • 小游戏代码 (1)
  • 小程序代码 (139)
    • O2O (16)
    • UI控件 (5)
    • 互联网类 (23)
    • 企业类 (6)
    • 地图定位 (9)
    • 多媒体 (6)
    • 工具类 (25)
    • 电商类 (22)
    • 社交 (7)
    • 行业软件 (7)
    • 资讯读书 (11)
  • 嵌入式 (70)
    • autosar (63)
    • RTOS (1)
    • 总线 (1)
  • 开发博客 (16)
    • Harmony (9)
  • 技术架构 (6)
  • 数据库 (32)
    • mongodb (1)
    • mysql (13)
    • pgsql (2)
    • redis (1)
    • tdengine (4)
  • 未分类 (6)
  • 程序员网赚 (20)
    • 广告联盟 (3)
    • 私域流量 (5)
    • 自媒体 (5)
  • 量化投资 (4)
  • 面试 (14)

功能

  • 登录
  • 文章RSS
  • 评论RSS
  • WordPress.org

All Rights Reserved by Gitweixin.本站收集网友上传代码, 如有侵犯版权,请发邮件联系yiyuyos@gmail.com删除.