91视频免费?看_蜜芽MY188精品TV在线观看_国产免费无遮挡在线观看视频_深夜国产_亚洲精品欧洲精品_欧美黑人粗暴多交

徐土豆
認證:優質創作者
所在專題目錄 查看專題
Transformer代碼隨記
視頻分析與多模態融合之一,為什么需要多模態融合
WenLan 2.0:一種不依賴Object Detection的大規模圖文匹配預訓練模型 & 數據+算力=大力出奇跡
圖文多模態語義融合前的語義對齊——一種單雙混合塔多模態模型
在多模態模型訓練時,如何合適地融合單模態損失
FILIP: 一種基于交互的細粒度圖文預訓練模型
作者動態 更多
給定計算預算下的最佳LLM模型尺寸與預訓練數據量分配
05-19 09:33
大模型推理時的尺度擴展定律
05-18 10:32
世界多胞體與世界模型
05-13 09:42
獎勵模型中的尺度擴展定律和獎勵劫持
05-12 08:41
MeCo——給預訓練數據增加源信息,就能減少33%的訓練量并且提升效果
05-08 09:13

Transformer代碼隨記

本文基于paddlepaddle的ernie中的Transformer代碼[5]進行分析。Transformer的Encoder部分主要由多頭注意力(Multi-Head Attention)和FFN組成,如Fig 1.1所示。

Fig 1.1 Transformer的encoder由多頭注意力模塊和FFN模塊組成。其中的輸入Inputs是文本字符串經過令牌化處理(tokenizing)之后的id,這個過程需要在詞表(vocabulary)中查找對應字符得到,詞表的節選如:

[PAD]	0
[CLS]	1
[SEP]	2
[MASK]	3
,	4
的	5
、	6
一	7
人	8
有	9
是	10
在	11
...

令牌化處理除了查表,之前還可能需要進行切詞,控制字符特殊處理等,不過我們暫時不考慮這些。通過查表后可以將字符轉換成為id。

最終得到的token id如同:

[1 1429 34 65 87 679 90 944 2 1429 134 74 262 82 54 542 698 14 65 90 34 504 67 2 ]

然后根據這個id逐個在embedding表中進行查表,這里的word embedding和word2vec之類的關系不太大(除了最后一步查表的步驟之外),比如一個詞表由10000個字符(包括了控制字符和特殊字符,以及模型相關的特殊字符比如[CLS],[SEP],[MASK]等),那么該embedding表 ,此處的是embedding的維度,按照ID的數值從第i行表中抽出作為embedding。對于位置編碼(position embedding)和分段編碼(sentence embedding)而言,也是如此在對應的embedding表中查找,如以下代碼所示:

self.word_emb = nn.Embedding(d_vocab,d_emb,
        weight_attr=P.ParamAttr(
            name=append_name(name, 'word_embedding'),
            initializer=initializer))
self.pos_emb = nn.Embedding(d_pos,d_emb,
        weight_attr=P.ParamAttr(
            name=append_name(name, 'pos_embedding'),
            initializer=initializer))
self.sent_emb = nn.Embedding(d_sent,d_emb,
        weight_attr=P.ParamAttr(
            name=append_name(name, 'sent_embedding'),
            initializer=initializer))
...
src_embedded = self.word_emb(src_ids)
pos_embedded = self.pos_emb(pos_ids)
sent_embedded = self.sent_emb(sent_ids)
embedded = src_embedded + pos_embedded + sent_embedded

如同論文所說的,后續將src_embedded,pos_embedded,sent_embedded相加得到最后的embedding。

Fig 1.2 將token,sentense,和position的embedding進行逐元素相加。

然而,在batch size大于1時,需要根據整個batch中長度最長的序列作為標注,記為max_length,然后對其他序列進行填充(padding),一般是填充[PAD]字符。為了讓自注意力忽略填充[PAD]的部分,在前向計算時候需要加上mask對填充部分進行屏蔽,一般將填充部分的mask設為0,而有效部分的mask作為1。記mask為,其中M為序列長度,那么有(1.1)式子,對應代碼如下所示。

attn_bias = (1. - attn_bias) * -10000.0
attn_bias = attn_bias.unsqueeze(1).tile([1, self.n_head, 1, 1])  # avoid broadcast =_=

此處將-10000視為是負無窮大,認為是序列的無效部分,那么可以看出當的元素為0時候,attn_bias=-10000表示該位置的字符無效,反之則attn_bias=0,該位的字符有效。Paddle的Transformer實現按照論文中說的,在多頭注意力層添加了這個attn_bias從而實現了避免填充部分干擾的問題。如以下代碼所示。

class AttentionLayer(nn.Layer):
    def __init__(self, cfg, name=None):
        super(AttentionLayer, self).__init__()
        initializer = nn.initializer.TruncatedNormal(
            std=cfg['initializer_range'])
        d_model = cfg['hidden_size']
        n_head = cfg['num_attention_heads']
        assert d_model % n_head == 0
        d_model_q = cfg.get('query_hidden_size_per_head',
                            d_model // n_head) * n_head
        d_model_v = cfg.get('value_hidden_size_per_head',
                            d_model // n_head) * n_head
        self.n_head = n_head
        self.d_key = d_model_q // n_head
        self.q = _build_linear(d_model, d_model_q,
                               append_name(name, 'query_fc'), initializer)
        self.k = _build_linear(d_model, d_model_q,
                               append_name(name, 'key_fc'), initializer)
        self.v = _build_linear(d_model, d_model_v,
                               append_name(name, 'value_fc'), initializer)
        self.o = _build_linear(d_model_v, d_model,
                               append_name(name, 'output_fc'), initializer)
        self.dropout = nn.Dropout(p=cfg['attention_probs_dropout_prob'])

    def forward(self, queries, keys, values, attn_bias, past_cache):
        assert len(queries.shape) == len(keys.shape) == len(values.shape) == 3
        #bsz, q_len, q_dim = queries.shape
        #bsz, k_len, k_dim = keys.shape
        #bsz, v_len, v_dim = values.shape
        #assert k_len == v_len

        q = self.q(queries)
        k = self.k(keys)
        v = self.v(values)

        cache = (k, v)
        if past_cache is not None:
            cached_k, cached_v = past_cache
            k = P.concat([cached_k, k], 1)
            v = P.concat([cached_v, v], 1)

        q = q.reshape(
            [0, 0, self.n_head, q.shape[-1] // self.n_head]).transpose(
                [0, 2, 1, 3])  #[batch, head, seq, dim]
        k = k.reshape(
            [0, 0, self.n_head, k.shape[-1] // self.n_head]).transpose(
                [0, 2, 1, 3])  #[batch, head, seq, dim]
        v = v.reshape(
            [0, 0, self.n_head, v.shape[-1] // self.n_head]).transpose(
                [0, 2, 1, 3])  #[batch, head, seq, dim]

        q = q.scale(self.d_key**-0.5)
        score = q.matmul(k, transpose_y=True)
        if attn_bias is not None:
            score += attn_bias
        score = F.softmax(score)
        score = self.dropout(score)

        out = score.matmul(v).transpose([0, 2, 1, 3])
        out = out.reshape([0, 0, out.shape[2] * out.shape[3]])
        out = self.o(out)
        return out, cache

其中在過softmax層之前將scoreattn_bias相加,此時自注意力的公式變為:

其中的經過歸一化,因此范圍在[ 0 , 1 ],而attn_bias的范圍是{-10000,0},因此在attn_bias=-10000的時候,的值很小,在經過了softmax之后將近為0,也意味著該token無效,一般用于屏蔽padding字符使用;而當attn_bias=0是,相當與該項不存在,不會造成屏蔽效果。從中我們看到,attn_bias的存在,或者進一步說mask的存在是對填充字符進行屏蔽用的,防止填充字符干擾到了自注意力結果。

無論是從原理上還是從FFN層的代碼來看,FFN的參數都和輸入序列長度無關。輸入長度在Transformer中會影響到的是位置編碼,因此在ViT [6]中會對位置編碼進行插值。

class PositionwiseFeedForwardLayer(nn.Layer):
    def __init__(self, cfg, name=None):
        super(PositionwiseFeedForwardLayer, self).__init__()
        initializer = nn.initializer.TruncatedNormal(
            std=cfg['initializer_range'])
        d_model = cfg['hidden_size']
        d_ffn = cfg.get('intermediate_size', 4 * d_model)
        self.act = ACT_DICT[cfg['hidden_act']]()
        self.i = _build_linear(
            d_model,
            d_ffn,
            append_name(name, 'fc_0'),
            initializer, )
        self.o = _build_linear(d_ffn, d_model,
                               append_name(name, 'fc_1'), initializer)
        prob = cfg.get('intermediate_dropout_prob', 0.)
        self.dropout = nn.Dropout(p=prob)

    def forward(self, inputs):
        hidden = self.act(self.i(inputs))
        hidden = self.dropout(hidden)
        out = self.o(hidden)
        return out

在代碼的多頭注意力這一塊,論文中采用的結構圖如Fig 1.3所示,如果不采用多頭注意力,只有一頭注意力的話,那么首先將Query,Key和Value從d_model映射到d_model_q,d_model_k,d_model_v,然后進行后續的自注意計算。但是如果采用了多頭注意力,那么理論上會將d_model_x平均劃分為d_model_x/n_head ,稱之為split_multi_head ,然后在每一頭上由d_model映射到d_model_x/n_head后,再在每一頭上進行自注意力計算,最后合并多頭注意力的計算結果(combine_multi_head)。

Fig 1.3 論文中對多頭注意力結構的示意圖。

然而在實際代碼中,通常不會采用這種直白(但是低效)的方式組織多頭注意力,如以上的代碼片段所示,在實現上還是直接將d_model維度直接映射到了d_model_x維度,然后通過reshape進行劃分,如以下代碼段所示。

q = q.reshape([0, 0, self.n_head, q.shape[-1] // self.n_head]).transpose(
              [0, 2, 1, 3])  #[batch, head, seq, dim]

也就是說,如Fig 1.4所示,其中d代表映射前的維度,n代表的是序列的長度,而D代表映射后的維度,那么通過reshape可以劃分每個頭,如綠色和粉色所示,每段長度即是D/k,k是頭數。

Fig 1.4 在代碼實現上實際采取的方式。

Reference

[1]. Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N Gomez, ?ukasz Kaiser, and Illia Polosukhin. Attention is all you need. In NIPS, 2017

[2]. Devlin, Jacob, Ming-Wei Chang, Kenton Lee, and Kristina Toutanova. “Bert: Pre-training of deep bidirectional transformers for language understanding.” arXiv preprint arXiv:1810.04805 (2018).

[3]. Sun, Yu, Shuohuan Wang, Yukun Li, Shikun Feng, Xuyi Chen, Han Zhang, Xin Tian, Danxiang Zhu, Hao Tian, and Hua Wu. “Ernie: Enhanced representation through knowledge integration.” arXiv preprint arXiv:1904.09223 (2019).

[4]. https://fesian.blog.csdn.net/article/details/116031656

[5]. https://github.com/PaddlePaddle/ERNIE

[6]. Dosovitskiy, Alexey, Lucas Beyer, Alexander Kolesnikov, Dirk Weissenborn, Xiaohua Zhai, Thomas Unterthiner, Mostafa Dehghani et al. “An image is worth 16x16 words: Transformers for image recognition at scale.” arXiv preprint arXiv:2010.11929 (2020).

聲明:本內容為作者獨立觀點,不代表電子星球立場。未經允許不得轉載。授權事宜與稿件投訴,請聯系:editor@netbroad.com
覺得內容不錯的朋友,別忘了一鍵三連哦!
贊 2
收藏 3
關注 52
成為作者 賺取收益
全部留言
0/200
成為第一個和作者交流的人吧
主站蜘蛛池模板: 印江| 朝阳区| 河南省| 赞皇县| 涟水县| 绿春县| 京山县| 利津县| 钟山县| 疏附县| 德州市| 鄂尔多斯市| 阳新县| 西藏| 蒲城县| 正镶白旗| 新泰市| 新野县| 水城县| 文安县| 千阳县| 勐海县| 乌鲁木齐市| 闵行区| 哈密市| 桂平市| 福泉市| 沾化县| 孝义市| 本溪市| 比如县| 乌鲁木齐县| 新沂市| 丹东市| 峨山| 措勤县| 都江堰市| 化隆| 乃东县| 灵山县| 巴楚县|