預計閱讀時間:8分鐘

ALBERT使用層級引數共享對BERT進行壓縮,這個理論上是非常好理解的,但實現上卻有一些細節需要考慮。走讀albert pytorch版本的原始碼之時,覺得寫得挺優雅,而且靈活,遂記錄在此。

閱讀本文前,可能需要先了解一些Albert基礎知識以及Pytorch框架。

文字參考的原始碼版本

Albert 原始碼整體和bert程式碼差異很小,為了實現靈活的引數共享,作者提出了一個Group的概念。 原始碼中將每一層都分到一個指定Group之中,一個Group包含了多個相鄰的層,

同一個Group裡面的層是引數共享的

,這個group個數由

num_hidden_groups

引數決定,預設為1。即所有的層share同一個Transformer權重。

如num_hidden_groups 為2,

num_hidden_layers

為12,那麼層分為兩組。1~6層是第一組,7~12是第二組。

如num_hidden_groups 為3,

num_hidden_layers

為12 ,那麼層分為三組。1~4為第一組,5~8為第二組,9~12為第三組。

以此類推。。。

Albert Pytorch 原始碼解讀:共享實現方法

Layers Group Split。 相同顏色的layer引數共享

層索引

layer_idx

和組索引

group_idx

由程式碼計算得出:

group_idx = int(layer_idx / num_hidden_layers * num_hidden_groups)

對於group編號較低的組,學習低抽象的知識,group編號較高的組,學習相對較高抽象的知識,這個是make sense的。透過設定

num_hidden_groups

num_hidden_layers

可以靈活設定模型深度和共享的程度。

可見group在原始碼中是一個比較重要的型別,其由 AlbertGroup類實現。AlbertTransformer 在初始化之時,預先例項化了

num_hidden_groups

個AlbertGroup,這個AlbertGroup代表新的一層引數,裡面還有一些細節,後文會描述。

self

group

=

nn

ModuleList

([

AlbertGroup

config

for

_

in

range

config

num_hidden_groups

)])

當AlbertTransformer計算到某一層時,直接在group列表中找到對應的AlbertGroup去forward,因此在梯度backward的時候,梯度的變化也會傳遞到對應的AlbertGroup,這樣就實現了多級引數共享的引數更新。

AlbertTransformer 完整程式碼:

class

AlbertTransformer

nn

Module

):

def

__init__

self

config

):

super

AlbertTransformer

self

__init__

()

self

output_attentions

=

config

output_attentions

self

output_hidden_states

=

config

output_hidden_states

self

num_hidden_layers

=

config

num_hidden_layers

self

num_hidden_groups

=

config

num_hidden_groups

self

group

=

nn

ModuleList

([

AlbertGroup

config

for

_

in

range

config

num_hidden_groups

)])

def

forward

self

hidden_states

attention_mask

head_mask

):

all_hidden_states

=

()

all_attentions

=

()

for

layer_idx

in

range

self

num_hidden_layers

):

if

self

output_hidden_states

and

layer_idx

==

0

all_hidden_states

=

all_hidden_states

+

hidden_states

,)

group_idx

=

int

layer_idx

/

self

num_hidden_layers

*

self

num_hidden_groups

layer_module

=

self

group

group_idx

layer_outputs

=

layer_module

hidden_states

attention_mask

head_mask

layer_idx

])

hidden_states

=

layer_outputs

0

][

-

1

if

self

output_attentions

all_attentions

=

all_attentions

+

layer_outputs

1

if

self

output_hidden_states

all_hidden_states

=

all_hidden_states

+

layer_outputs

0

outputs

=

hidden_states

,)

if

self

output_hidden_states

outputs

=

outputs

+

all_hidden_states

,)

if

self

output_attentions

outputs

=

outputs

+

all_attentions

,)

其他就是些很常規的程式碼了,和BERT原始碼基本類似。

除了對layers進行分組之外

group的內部也分了層級

AlbertGroup 完整程式碼:

class

AlbertGroup

nn

Module

):

def

__init__

self

config

):

super

AlbertGroup

self

__init__

()

self

inner_group_num

=

config

inner_group_num

self

inner_group

=

nn

ModuleList

([

AlbertLayer

config

for

_

in

range

config

inner_group_num

)])

def

forward

self

hidden_states

attention_mask

head_mask

):

layer_attentions

=

()

layer_hidden_states

=

()

for

inner_group_idx

in

range

self

inner_group_num

):

layer_module

=

self

inner_group

inner_group_idx

layer_outputs

=

layer_module

hidden_states

attention_mask

head_mask

hidden_states

=

layer_outputs

0

layer_attentions

=

layer_attentions

+

layer_outputs

1

],)

layer_hidden_states

=

layer_hidden_states

+

hidden_states

,)

return

layer_hidden_states

layer_attentions

AlbertGroup 內部的層級由

inner_group_num

引數確定,預設為1。其內部處理的邏輯也很簡單,即forward多層的AlbertLayer,這個AlbertLayer就代表著一個block。

由此可見,假設一個block的引數量為 m。則實際的encoder的引數量K為:

K = m * inner_group_num * num_hidden_groups

inner_group_num和num_hidden_groups預設均為1。大多數預訓練模型是基於預設引數訓練的,所以這兩個引數一般也不會改動。除非需要嘗試調整共享程度進行重新預訓練。

除了Group分組共享外albert 還可以調整 block內部的共享方式

分為三種

all : ffn和attention都共享

ffn :ffn共享

attention: attention共享

對於不同的內部共享,在初始化Module時將

不共享

元件例項化 inner_group_num * num_hidden_groups個 儲存在ModuleList之中,在forward時,按照索引定位到指定元件即可。

本文參考的程式碼並沒有實現內部共享,感興趣的讀者可以嘗試寫寫。

用碎片的時間,來總結學習

文章系原創,轉載請說明出處