Bert Encoder和Transformer Encoder有什么不同

前言:本篇文章主要从代码实现角度研究 Bert Encoder和Transformer Encoder 有什么不同?应该可以帮助你:

  • 深入了解Bert Encoder 的结构实现
  • 深入了解Transformer Encoder的结构实现

本篇文章不涉及对注意力机制实现的代码研究。

注:本篇文章所得出的结论和其它文章略有不同,有可能是本人代码理解上存在问题,但是又没有找到更多的文章加以验证,并且代码也检查过多遍。

观点不太一致的文章:bert-pytorch版源码详细解读_bert pytorch源码-CSDN博客 这篇文章中,存在 “这个和我之前看的transformers的残差连接层差别还挺大的,所以并不完全和transformers的encoder部分结构一致。” 但是我的分析是:代码实现上不太一样,但是本质上没啥不同,只是Bert Encoder在Attention之后多了一层Linear。具体分析过程和结论可以阅读如下文章。

如有错误或问题,请在评论区回复。

1、研究目标

这里主要的观察对象是BertModel中Bert Encoder是如何构造的?从Bert Tensorflow源码,以及transformers库中源码去看。

然后再看TransformerEncoder是如何构造的?从pytorch内置的transformer模块去看。

最后再对比不同。

2、tensorflow中BertModel主要代码如下

class BertModel(object):def __init__(...):...得到了self.embedding_output以及attention_mask# transformer_model就代表了Bert Encoder层的所有操作self.all_encoder_layers = transformer_model(input_tensor=self.embedding_output, attention_mask=attention_mask,...)# 这里all_encoder_layers[-1]是取最后一层encoder的输出self.sequence_output = self.all_encoder_layers[-1]...pooler层,对 sequence_output中的first_token_tensor,即CLS对应的表示向量,进行dense+tanh操作with tf.variable_scope("pooler"):first_token_tensor = tf.squeeze(self.sequence_output[:, 0:1, :], axis=1)self.pooled_output = tf.layers.dense(first_token_tensor,config.hidden_size,activation=tf.tanh,kernel_initializer=create_initializer(config.initializer_range))def transformer_model(input_tensor, attention_mask=None,...):...for layer_idx in range(num_hidden_layers):# 如下(1)(2)(3)就是每一层Bert Encoder包含的结构和操作with tf.variable_scope("layer_%d" % layer_idx):# (1)attention层:主要包含两个操作,获取attention_output,对attention_output进行dense + dropout + layer_normwith tf.variable_scope("attention"):# (1.1)通过attention_layer获得 attention_outputattention_output# (1.2)output层:attention_output需要经过dense + dropout + layer_norm操作with tf.variable_scope("output"):attention_output = tf.layers.dense(attention_output,hidden_size,...)attention_output = dropout(attention_output, hidden_dropout_prob)# “attention_output + layer_input” 表示 残差连接操作attention_output = layer_norm(attention_output + layer_input)# (2)intermediate中间层:对attention_output进行dense+激活(GELU)with tf.variable_scope("intermediate"):intermediate_output = tf.layers.dense(attention_output,intermediate_size,activation=intermediate_act_fn,)# (3)output层:对intermediater_out进行dense + dropout + layer_normwith tf.variable_scope("output"):layer_output = tf.layers.dense(intermediate_output,hidden_size,kernel_initializer=create_initializer(initializer_range))layer_output = dropout(layer_output, hidden_dropout_prob)# "layer_output + attention_output"是残差连接操作layer_output = layer_norm(layer_output + attention_output)all_layer_outputs.append(layer_output)

3、pytorch的transformers库中的BertModel主要代码;

  • 其中BertEncoder对应要研究的目标
class BertModel(BertPreTrainedModel):def __init__(self, config, add_pooling_layer=True):self.embeddings = BertEmbeddings(config)self.encoder = BertEncoder(config)self.pooler = BertPooler(config) if add_pooling_layer else Nonedef forward(...):# 这是嵌入层操作embedding_output = self.embeddings(input_ids=input_ids,position_ids=position_ids,token_type_ids=token_type_ids,...)# 这是BertEncoder层的操作encoder_outputs = self.encoder(embedding_output,attention_mask=extended_attention_mask,...)# 这里encoder_outputs是一个对象,encoder_outputs[0]是指最后一层Encoder(BertLayer)输出sequence_output = encoder_outputs[0]# self.pooler操作是BertPooler层操作,是先取first_token_tensor(即CLS对应的表示向量),然后进行dense+tanh操作# 通常pooled_output用于做下游分类任务pooled_output = self.pooler(sequence_output) if self.pooler is not None else Noneclass BertEncoder(nn.Module):def __init__(self, config):...self.layer = nn.ModuleList([BertLayer(config) for _ in range(config.num_hidden_layers)])...def forward(...):for i, layer_module in enumerate(self.layer):# 元组的append做法,将每一层的hidden_states保存到all_hidden_states;# 第一个hidden_states是BertEncoder的输入,后面的都是每一个BertLayer的输出if output_hidden_states:all_hidden_states = all_hidden_states + (hidden_states,)...# 执行BertLayer的forward方法,包含BertAttention层 + BertIntermediate中间层 + BertOutput层layer_outputs = layer_module(...)# 当前BertLayer的输出hidden_states = layer_outputs[0]# 添加到all_hidden_states元组中if output_hidden_states:all_hidden_states = all_hidden_states + (hidden_states,)class BertLayer(nn.Module):def __init__(self, config):self.attention = BertAttention(config)self.intermediate = BertIntermediate(config)self.output = BertOutput(config)def forward(...):# (1)Attention是指BertAttention# BertAttention包含:BertSelfAttention + BertSelfOutput# BertSelfAttention包括计算Attention+Dropout# BertSelfOutput包含:dense+dropout+LayerNorm,LayerNorm之前会进行残差连接self_attention_outputs = self.attention(...)# self_attention_outputs是一个元组,取[0]获取当前BertLayer中的Attention层的输出attention_output = self_attention_outputs[0]# (2)BertIntermediate中间层包含:dense+gelu激活# (3)BertOutput层包含:dense+dropout+LayerNorm,LayerNorm之前会进行残差连接# feed_forward_chunk的操作是:BertIntermediate(attention_output) + BertOutput(intermediate_output, attention_output)# BertIntermediate(attention_output)是:dense+gelu激活# BertOutput(intermediate_output, attention_output)是:dense+dropout+LayerNorm;# 其中LayerNorm(intermediate_output + attention_output)中的“intermediate_output + attention_output”是残差连接操作layer_output = apply_chunking_to_forward(self.feed_forward_chunk, ..., attention_output)

4、pytorch中内置的transformer的TransformerEncoderLayer主要代码

  • torch.nn.modules.transformer.TransformerEncoderLayer
class TransformerEncoderLayer(Module):'''Args:d_model: the number of expected features in the input (required).nhead: the number of heads in the multiheadattention models (required).dim_feedforward: the dimension of the feedforward network model (default=2048).dropout: the dropout value (default=0.1).activation: the activation function of intermediate layer, relu or gelu (default=relu).Examples::>>> encoder_layer = nn.TransformerEncoderLayer(d_model=512, nhead=8)>>> src = torch.rand(10, 32, 512)>>> out = encoder_layer(src)'''def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1, activation="relu"):super(TransformerEncoderLayer, self).__init__()self.self_attn = MultiheadAttention(d_model, nhead, dropout=dropout)# Implementation of Feedforward modelself.linear1 = Linear(d_model, dim_feedforward)self.dropout = Dropout(dropout)self.linear2 = Linear(dim_feedforward, d_model)self.norm1 = LayerNorm(d_model)self.norm2 = LayerNorm(d_model)self.dropout1 = Dropout(dropout)self.dropout2 = Dropout(dropout)self.activation = _get_activation_fn(activation)def forward(...):# 过程:# (1)MultiheadAttention操作:src2 = self.self_attn# (2)Dropout操作:self.dropout1(src2)# (3)残差连接:src = src + self.dropout1(src2)# (4)LayerNorm操作:src = self.norm1(src)# 如下是FeedForword:做两次线性变换,为了更深入的提取特征# (5)Linear操作:src = self.linear1(src)# (6)RELU激活(默认RELU)操作:self.activation(self.linear1(src))# (7)Dropout操作:self.dropout(self.activation(self.linear1(src)))# (8)Linear操作:src2 = self.linear2(...)# (9)Dropout操作:self.dropout2(src2)# (10)残差连接:src = src + self.dropout2(src2)# (11)LayerNorm操作:src = self.norm2(src)src2 = self.self_attn(src, src, src, attn_mask=src_mask,key_padding_mask=src_key_padding_mask)[0]src = src + self.dropout1(src2)src = self.norm1(src)src2 = self.linear2(self.dropout(self.activation(self.linear1(src))))src = src + self.dropout2(src2)src = self.norm2(src)return src

5、区别总结

        Transformer Encoder的结构如上图所示,代码也基本和上图描述的一致,不过代码中在Multi-Head Attention和Feed Forward之后都存在一个Dropout操作。(可以认为每层网络之后都会接一个Dropout层,是作为网络模块的一部分)

可以将Transformer Encoder过程表述为:

(1)MultiheadAttention + Dropout + 残差连接 + LayerNorm

(2)FeedForword(Linear + RELU + Dropout + Linear + Dropout) + 残差连接 + LayerNorm;Transformer默认的隐含层激活函数是RELU;

可以将 Bert Encoder过程表述为:

(1)BertSelfAttention: MultiheadAttention + Dropout

(2)BertSelfOutput:Linear+ Dropout + 残差连接 + LayerNorm; 注意:这里的残差连接是作用在BertSelfAttention的输入上,不是Linear的输入。

(3)BertIntermediate:Linear + GELU激活

(4)BertOutput:Linear + Dropout + 残差连接 + LayerNorm;注意:这里的残差连接是作用在BertIntermediate的输入上,不是Linear的输入;

进一步,把(1)(2)合并,(3)(4)合并:

(1)MultiheadAttention + Dropout + Linear + Dropout + 残差连接 + LayerNorm

(2)FeedForword(Linear + GELU激活 + Linear + Dropout) + 残差连接 + LayerNorm;Bert默认的隐含层激活函数是GELU;

所以,Bert Encoder和Transformer Encoder最大的区别是,Bert Encoder在做完Attention计算后,还会用一个线性层去提取特征,然后才进行残差连接。其次,是FeedForword中的默认激活函数不同。Bert Encoder图结构如下:

Bert 为什么要这么做?或许是多一个线性层,特征提取能力更强,模型表征能力更好。

GELU和RELU:GELU是RELU的改进版,效果更好。

Reference

  • GeLU、ReLU函数学习_gelu和relu-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/718831.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解:npm升级到pnpm对比优化点!!

npm3之前 依赖树层级过深,导致依赖路径过长并且相同依赖模块会被重复安装,占用电脑磁盘空间 npm3之后 修改为扁平化处理 算法复杂存在多项目间依赖相同副本的情况导致没有明确被依赖的包也可以直接引用,管理复杂 pnpm node_modules改成非扁平化结构&a…

微软37页论文逆向工程Sora,得到了哪些结论?

一篇论文回顾 Sora 文生视频技术的背景、技术和应用。 追赶 Sora,成为了很多科技公司当下阶段的新目标。研究者们好奇的是:Sora 是如何被 OpenAI 发掘出来的?未来又有哪些演进和应用方向? Sora 的技术报告披露了一些技术细节&…

找专业人士编辑百度百科,避免审核问题

对于想在百度百科上创建词条或修改现有词条的网友,可能会有一些疑问,比如找第三方代不通过审核创建百度百科。那么,创建百度百科需要多少钱呢?让我们仔细看看。 百度百科创建服务一直存在。为了节省时间,很多人选择专业…

java八股文复习-----2024/03/03

1.接口和抽象类的区别 相似点: (1)接口和抽象类都不能被实例化 (2)实现接口或继承抽象类的普通子类都必须实现这些抽象方法 不同点: (1)抽象类可以包含普通方法和代码块&#x…

【JavaEE进阶】使用云服务器实现Linux环境搭建

文章目录 🍃前言🌲Linux背景知识🚩Linux 是什么?🚩Linux发行版🚩Linux的优势 🎄Linux环境搭建🚩环境的搭建方式🚩使用云服务器 🎋使用终端软件连接到Linux&am…

【Python】进阶学习:pandas--read_csv()用法详解

🚀【Python】进阶学习:pandas–read_csv()用法详解🚀 🌈 个人主页:高斯小哥 🔥 高质量专栏:Matplotlib之旅:零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教…

如何自学python

Python是一种高级编程语言,它具有简单易学、可读性强、可移植性好、功能丰富等优点,因此在许多领域都被广泛使用,如科学计算、数据分析、人工智能、Web开发、游戏开发等等。 Python具有丰富的标准库和第三方库,可以帮助程序员快速开发功能强大的应用程序。同时,Python也具…

如何做代币分析:以 LEO 币为例

作者: lesleyfootprint.network 编译:cicifootprint.network 数据源:LEO 代币仪表板 (仅包括以太坊数据) 在加密货币和数字资产领域,代币分析起着至关重要的作用。代币分析指的是深入研究与代币相关的数…

电能表检定装置

电能表检定装置产品概述 KDZD3030C三相便携式校验装置采用铝合金机箱,配合可拆卸铝合金挂表架,适用于1.0级及以下各种三相电能表的检验。 ​武汉凯迪正大电能表检定装置功能特点 1.仪器特点 1)采用表源一体结构,内置0.05级三相宽量程多功能…

网络编程:TCP机械臂,UDP文件传输

1.TCP机械臂测试 程序代码&#xff1a; 1 #include<myhead.h>2 #define SER_IP "192.168.126.112" //服务器IP3 #define SER_PORT 8888 //服务器端口号4 5 #define CLI_IP "192.168.126.121" //客户端IP6 #define CLI_PORT 9999 //…

LeetCode 刷题 [C++] 第763题.划分字母区间

题目描述 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。 注意&#xff0c;划分结果需要满足&#xff1a;将所有划分结果按顺序连接&#xff0c;得到的字符串仍然是 s 。 返回一个表示每个字符串片段的长度的列表。 …

看看技术大佬是如何把ls命令玩到飞起

关注公众号&#xff1a;“DevOps实战派”&#xff0c;获取更多DevOps和运维的精彩内容。 Linux中一个基本命令是ls&#xff0c;没有这个命令&#xff0c;我们会在浏览目录条目时会遇到困难。 ls命令用于列出文件和目录&#xff0c;默认上&#xff0c;它会列出当前目录的内容。…

Synchronized方法锁、对象锁、类锁区别

synchronized&#xff0c;这个东西我们一般称之为”同步锁“&#xff0c;他在修饰代码块的时候需要传入一个引用对象作为“锁”的对象。 在修饰方法的时候&#xff0c;默认是当前对象作为锁的对象在修饰类时&#xff0c;默认是当前类的Class对象作为所的对象 故存在着方法锁、…

【MySQL】事务管理 -- 详解

一、前言 CURD 不加控制&#xff0c;会有什么问题&#xff1f; CURD 满足什么属性&#xff0c;能解决上述问题&#xff1f; 买票的过程得是原子的。买票应该不能受互相的影响。买完票应该要永久有效。买前和买后都要是确定的状态。 什么是事务&#xff1f; 事务就是一组 DML…

网络编程作业day3

项目作业1&#xff1a;TCP机械臂测试 客户端操作代码&#xff1a; /*机械臂客户端控制代码*/ #include <myhead.h>#define SER_IP "192.168.125.176" //机械臂服务器IP地址 #define SER_PORT 8888 //机械臂服务器端口号 #define CLI_IP "…

Vue 项目重复点击菜单刷新当前页面

需求&#xff1a;“在当前页面点击当前页面对应的菜单时&#xff0c;也能刷新页面。” 由于 Vue 项目的路由机制是路由不变的情况下&#xff0c;对应的组件是不重新渲染的。所以重复点击菜单不会改变路由&#xff0c;然后页面就无法刷新了。 方案一 在vue项目中&#xff0c;…

深入了解 JavaScript 混淆加密和环境检测

JavaScript混淆加密是一种通过修改代码结构和命名约定来增加代码的复杂性&#xff0c;使其难以被理解和逆向工程的技术。在这篇文章中&#xff0c;我们将深入探讨JS混淆加密的一些逻辑&#xff0c;并介绍如何通过环境检测来提高代码的安全性。我们将使用案例代码演示这些概念。…

List集合按中文拼音排序,或按自己想要顺序的调整排序

1.你要按拼音排序&#xff08;字母同音依次比后面字母&#xff09; //集合按中文拼音排序Collections.sort(collect,new Comparator() {Overridepublic int compare(Object o1, Object o2) {return chineseCompare(o1,o2);}});//排序方法private static int chineseCompare(Obj…

STM32用标准库编写按键控制LED灯的proteus仿真

首先打开proteus仿真软件&#xff0c;绘制电路图&#xff1a; 或是下载我已经建立好的工程修改&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1Nx5p3Tif6eHBIVkcPfsj9w?pwd1234 提取码&#xff1a;1234 第一步复制整个工程文件夹&#xff0c;就不用重新配置的辛苦…

论文阅读:2017MobileNet V1谷歌轻量化卷积神经网络

拓展&#xff1a;贾扬清&#xff1a;深度学习框架caffe&#xff08;Convolutional Architecture for Fast Feature Embedding&#xff09; 主要贡献&#xff1a; 深度可分离卷积&#xff08;Depthwise separable convolution&#xff09;逐点卷积&#xff08;Pointwise convo…