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.pswp.cn/news/718831.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/718831.shtml
英文地址,請注明出處:http://en.pswp.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…

深入理解TCP/IP協議:互聯網通信的核心

深入理解TCP/IP協議:互聯網通信的核心 在數字化時代,TCP/IP協議是支撐全球互聯網通信的基石。它不僅負責數據的傳輸和路由,還確保了信息傳遞的準確性和完整性。本文將深入探討TCP/IP協議的工作原理、結構以及它在網絡編程中的應用。 TCP/IP…

【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…

【java】使用七牛云上傳文件

注冊七牛云 - 小王小王ii - 博客園 (cnblogs.com) 1.依賴 <dependencies><dependency><groupId>com.qiniu</groupId><artifactId>qiniu-java-sdk</artifactId><version>7.2.7</version></dependency><dependency>…