久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

圖解Transformer完整版(從輸入開始一步一步演示了數(shù)據(jù)的流動(dòng)過程訓(xùn)練過程是非常有用的知識(shí))

 山峰云繞 2023-08-21 發(fā)布于貴州

  (從輸入開始一步一步演示了數(shù)據(jù)的流動(dòng)過程)訓(xùn)練過程,,這也是非常有用的知識(shí)


↑↑↑關(guān)注后"星標(biāo)"Datawhale
每日干貨 & 每月組隊(duì)學(xué)習(xí),,不錯(cuò)過
 Datawhale干貨 
譯者:張賢,,哈爾濱工程大學(xué),,Datawhale原創(chuàng)作者
本文約16000字,是NLP專欄第一篇,,建議收藏閱讀
審稿人:Jepson,Datawhale成員,,畢業(yè)于中國(guó)科學(xué)院,目前在騰訊從事推薦算法工作,。

結(jié)構(gòu)總覽

前言

https://mp.weixin.qq.com/s?__biz=MzIyNjM2MzQyNg==&mid=2247538397&idx=1&sn=0e534e86c3810f1bc8b72c862e0390a7&chksm=e8738790df040e867e9b23bacd16fc2a539aa38da1e1a76e6cb8207fb67d2447f0f057a3962a&scene=21#wechat_redirect

本文翻譯自http://jalammar./illustrated-transformer,,是筆者看過的把 Transformer 講解得最好的文章。這篇文章從輸入開始,,一步一步演示了數(shù)據(jù)在 Transformer 中的流動(dòng)過程,。由于看過一些中文翻譯的文章,感覺不夠好,,所以我自己翻譯了一個(gè)版本,,在一些難以直譯的地方,我加入了一些原文沒有的文字說明,,來(lái)更好地解釋概念,。另外,我添加了一些簡(jiǎn)單的代碼,,實(shí)現(xiàn)了一個(gè)基本的 Self Attention 以及 multi-head attention 的矩陣運(yùn)算,。

Transformer 依賴于 Self Attention 的知識(shí)。Attention 是一種在深度學(xué)習(xí)中廣泛使用的方法,,Attention的思想提升了機(jī)器翻譯的效果,。如果你還沒學(xué)習(xí) Attention,請(qǐng)查看這篇 Attention 的精彩講解:https://zhuanlan.zhihu.com/p/265182368,。

2017 年,,Google 提出了 Transformer 模型,用 Self Attention 的結(jié)構(gòu),,取代了以往 NLP 任務(wù)中的 RNN 網(wǎng)絡(luò)結(jié)構(gòu),,在 WMT 2014 Englishto-GermanWMT 2014 English-to-French兩個(gè)機(jī)器翻譯任務(wù)上都取得了當(dāng)時(shí) SOTA 的效果。

這個(gè)模型的其中一個(gè)優(yōu)點(diǎn),,就是使得模型訓(xùn)練過程能夠并行計(jì)算,。在 RNN 中,每一個(gè) time step 的計(jì)算都依賴于上一個(gè) time step 的輸出,,這就使得所有的 time step 必須串行化,,無(wú)法并行計(jì)算,如下圖所示,。

而在 Transformer 中,,所有 time step 的數(shù)據(jù),都是經(jīng)過 Self Attention 計(jì)算,,使得整個(gè)運(yùn)算過程可以并行化計(jì)算,。

這篇文章的目的是從上到下,一步一步拆解 Transformer 的各種概念,希望有助于初學(xué)者更加容易地理解 Transformer 到底是什么,。

Transformer 使用了 Seq2Seq任務(wù)中常用的結(jié)構(gòu)——包括兩個(gè)部分:Encoder 和 Decoder。一般的結(jié)構(gòu)圖,,都是像下面這樣,。

如果你看到上圖不知所措,不要擔(dān)心,,下面我們來(lái)一步步拆解 Transformer,。

一、從整體宏觀來(lái)理解 Transformer

首先,,我們將整個(gè)模型視為黑盒,。在機(jī)器翻譯任務(wù)中,接收一種語(yǔ)言的句子作為輸入,,然后將其翻譯成其他語(yǔ)言輸出,。

中間部分的 Transformer 可以拆分為 2 部分:左邊是編碼部分(encoding component),右邊是解碼部分(decoding component),。

其中編碼部分是多層的編碼器(Encoder)組成(Transformer 的論文中使用了 6 層編碼器,,這里的層數(shù) 6 并不是固定的,你也可以根據(jù)實(shí)驗(yàn)效果來(lái)修改層數(shù)),。同理,,解碼部分也是由多層的解碼器(Decoder)組成(論文里也使用了 6 層的解碼器)。

每一個(gè)編碼器 在結(jié)構(gòu)上都是一樣的,,但它們的權(quán)重參數(shù)是不同的,。每一個(gè)編碼器里面,可以分為 2 層

  • Self-Attention Layer
  • Feed Forward Neural Network(前饋神經(jīng)網(wǎng)絡(luò),,縮寫為 FFNN)

輸入編碼器的文本數(shù)據(jù),,首先會(huì)經(jīng)過一個(gè) Self Attention 層,這個(gè)層處理一個(gè)詞的時(shí)候,,不僅會(huì)使用這個(gè)詞本身的信息,,也會(huì)使用句子中其他詞的信息(你可以類比為:當(dāng)我們翻譯一個(gè)詞的時(shí)候,不僅會(huì)只關(guān)注當(dāng)前的詞,,也會(huì)關(guān)注這個(gè)詞的上下文的其他詞的信息),。本文后面將會(huì)詳細(xì)介紹 Self Attention 的內(nèi)部結(jié)構(gòu)。

接下來(lái),,Self Attention 層的輸出會(huì)經(jīng)過前饋神經(jīng)網(wǎng)絡(luò),。

同理,解碼器也具有這兩層,,但是這兩層中間還插入了一個(gè) Encoder-Decoder Attention 層,,這個(gè)層能幫助解碼器聚焦于輸入句子的相關(guān)部分(類似于 seq2seq 模型 中的 Attention)。

二、從細(xì)節(jié)來(lái)理解 Transformer

上面,,我們從宏觀理解了 Transformer 的主要部分,。下面,我們來(lái)看輸入的張量數(shù)據(jù),,在 Transformer 中運(yùn)算最終得到輸出的過程,。

2.1 Transformer 的輸入

和通常的 NLP 任務(wù)一樣,我們首先會(huì)使用詞嵌入算法(embedding algorithm),,將每個(gè)詞轉(zhuǎn)換為一個(gè)詞向量,。實(shí)際中向量一般是 256 或者 512 維。為了簡(jiǎn)化起見,,這里將每個(gè)詞的轉(zhuǎn)換為一個(gè) 4 維的詞向量,。

那么整個(gè)輸入的句子是一個(gè)向量列表,其中有 3 個(gè)詞向量,。在實(shí)際中,,每個(gè)句子的長(zhǎng)度不一樣,我們會(huì)取一個(gè)適當(dāng)?shù)闹?,作為向量列表的長(zhǎng)度,。如果一個(gè)句子達(dá)不到這個(gè)長(zhǎng)度,那么就填充全為 0 的詞向量,;如果句子超出這個(gè)長(zhǎng)度,,則做截?cái)唷>渥娱L(zhǎng)度是一個(gè)超參數(shù),,通常是訓(xùn)練集中的句子的最大長(zhǎng)度,,你可以嘗試不同長(zhǎng)度的效果。

編碼器(Encoder)接收的輸入都是一個(gè)向量列表,,輸出也是大小同樣的向量列表,,然后接著輸入下一個(gè)編碼器。

第一個(gè)編碼器的輸入是詞向量,,而后面的編碼器的輸入是上一個(gè)編碼器的輸出,。

下面,我們來(lái)看這個(gè)向量列表在編碼器里面是如何流動(dòng)的,。

這里我們可以注意到 Transformer 的一個(gè)重要特性:每個(gè)位置的詞向量經(jīng)過編碼器都有自己?jiǎn)为?dú)的路徑,。具體來(lái)說,在 Self Attention 層中,,這些路徑之間是有依賴關(guān)系的,;而在 Feed Forward (前饋神經(jīng)網(wǎng)絡(luò))層中,這些路徑之間是沒有依賴關(guān)系的,。因此這些詞向量在經(jīng)過 Feed Forward 層中可以并行計(jì)算(這句話會(huì)造成困擾,,我認(rèn)為在 Self Attention 層中,,也能并行計(jì)算,沒有必要單獨(dú)說 Feed Forward 層也可以并行計(jì)算),。

下面我們用一個(gè)更短的句子,,來(lái)說明數(shù)據(jù)在編碼器的編碼過程。

2.2 Encoder(編碼器)

上面我們提到,,一個(gè)編碼器接收的輸入是一個(gè)向量列表,,它會(huì)把向量列表輸入到 Self Attention 層,然后經(jīng)過 feed-forward neural network (前饋神經(jīng)網(wǎng)絡(luò))層,,最后得到輸出,傳入下一個(gè)編碼器,。

每個(gè)位置的詞都經(jīng)過 Self Attention 層,,得到的每個(gè)輸出向量都單獨(dú)經(jīng)過前饋神經(jīng)網(wǎng)絡(luò)層,每個(gè)向量經(jīng)過的前饋神經(jīng)網(wǎng)絡(luò)都是一樣的

三,、 Self-Attention 整體理解

別被“Self-Attention”這么高大上的詞給唬住了,,乍一聽好像每個(gè)人都應(yīng)該對(duì)這個(gè)詞熟悉一樣。但我在讀論文《Attention is All You Need》 之前就沒有聽過這個(gè)詞,。下面來(lái)分析 Self-Attention 的具體機(jī)制,。

假設(shè)我們想要翻譯的句子是:

The animal didn't cross the street because it was too tired

這個(gè)句子中的 it 是一個(gè)指代詞,那么 it 指的是什么呢,?它是指animal還是street,?這個(gè)問題對(duì)人來(lái)說,是很簡(jiǎn)單的,,但是對(duì)算法來(lái)說并不是那么容易,。

當(dāng)模型在處理(翻譯)it 的時(shí)候,Self Attention機(jī)制能夠讓模型把itanimal關(guān)聯(lián)起來(lái),。

同理,,當(dāng)模型處理句子中的每個(gè)詞時(shí),Self Attention機(jī)制使得模型不僅能夠關(guān)注這個(gè)位置的詞,,而且能夠關(guān)注句子中其他位置的詞,,作為輔助線索,進(jìn)而可以更好地編碼當(dāng)前位置的詞,。

如果你熟悉 RNN,,回憶一下:RNN 在處理一個(gè)詞時(shí),會(huì)考慮前面?zhèn)鬟^來(lái)的hidden state,,而hidden state就包含了前面的詞的信息,。而 Transformer 使用Self Attention機(jī)制,會(huì)把其他單詞的理解融入處理當(dāng)前的單詞,。

當(dāng)我們?cè)诘谖鍖泳幋a器中(編碼部分中的最后一層編碼器)編碼“it”時(shí),,有一部分注意力集中在“The animal”上,并且把這兩個(gè)詞的信息融合到了"it"這個(gè)單詞中。

你可以查看 【Tensor2Tensor notebook】,。在這個(gè) notebook 里,,你可以加載 Transformer 模型,并通過交互式的可視化,,來(lái)理解 Self Attention,。

四、Self-Attention 的細(xì)節(jié)

4.1 計(jì)算Query 向量,,Key 向量,,Value 向量

下面我們先看下如何使用向量來(lái)計(jì)算 Self Attention,然后再看下如何使用矩陣來(lái)實(shí)現(xiàn) Self Attention,。(矩陣運(yùn)算的方式,,使得 Self Attention 的計(jì)算能夠并行化,這也是 Self Attention 最終的實(shí)現(xiàn)方式),。

計(jì)算 Self Attention 的第 1 步是:對(duì)輸入編碼器的每個(gè)詞向量,,都創(chuàng)建 3 個(gè)向量,分別是:Query 向量,,Key 向量,,Value 向量。這 3 個(gè)向量是詞向量分別和 3 個(gè)矩陣相乘得到的,,而這個(gè)矩陣是我們要學(xué)習(xí)的參數(shù),。

注意,這 3 個(gè)新得到的向量一般比原來(lái)的詞向量的長(zhǎng)度更小,。假設(shè)這 3 個(gè)向量的長(zhǎng)度是 ,,而原始的詞向量或者最終輸出的向量的長(zhǎng)度是 512(這 3 個(gè)向量的長(zhǎng)度,和最終輸出的向量長(zhǎng)度,,是有倍數(shù)關(guān)系的),。關(guān)于 Multi-head Attention,后面會(huì)給出實(shí)際代碼,。這里為了簡(jiǎn)化,,假設(shè)只有一個(gè) head 的 Self-Attention。

上圖中,,有兩個(gè)詞向量:Thinking 的詞向量 x1 和 Machines 的詞向量 x2,。以 x1 為例,X1 乘以 WQ 得到 q1,,q1 就是 X1 對(duì)應(yīng)的 Query 向量,。同理,X1 乘以 WK 得到 k1,,k1 是 X1 對(duì)應(yīng)的 Key 向量,;X1 乘以 WV 得到 v1,,v1 是 X1 對(duì)應(yīng)的 Value 向量。

Query 向量,,Key 向量,,Value 向量是什么含義呢?

其實(shí)它們就是 3 個(gè)向量,,給它們加上一個(gè)名稱,,可以讓我們更好地理解 Self-Attention 的計(jì)算過程和邏輯含義。繼續(xù)往下讀,,你會(huì)知道 attention 是如何計(jì)算出來(lái)的,,Query 向量,Key 向量,,Value 向量又分別扮演了什么角色,。

4.2 計(jì)算 Attention Score(注意力分?jǐn)?shù))

第 2 步,是計(jì)算 Attention Score(注意力分?jǐn)?shù)),。假設(shè)我們現(xiàn)在計(jì)算第一個(gè)詞 Thinking 的 Attention Score(注意力分?jǐn)?shù)),需要根據(jù) Thinking 這個(gè)詞,,對(duì)句子中的其他每個(gè)詞都計(jì)算一個(gè)分?jǐn)?shù),。這些分?jǐn)?shù)決定了我們?cè)诰幋aThinking這個(gè)詞時(shí),需要對(duì)句子中其他位置的每個(gè)詞放置多少的注意力,。

這些分?jǐn)?shù),,是通過計(jì)算 "Thinking" 對(duì)應(yīng)的 Query 向量和其他位置的每個(gè)詞的 Key 向量的點(diǎn)積,而得到的,。如果我們計(jì)算句子中第一個(gè)位置單詞的 Attention Score(注意力分?jǐn)?shù)),,那么第一個(gè)分?jǐn)?shù)就是 q1 和 k1 的內(nèi)積,第二個(gè)分?jǐn)?shù)就是 q1 和 k2 的點(diǎn)積,。

第 3 步就是把每個(gè)分?jǐn)?shù)除以  是 Key 向量的長(zhǎng)度),。你也可以除以其他數(shù),除以一個(gè)數(shù)是為了在反向傳播時(shí),,求取梯度更加穩(wěn)定,。

第 4 步,接著把這些分?jǐn)?shù)經(jīng)過一個(gè) Softmax 層,,Softmax可以將分?jǐn)?shù)歸一化,,這樣使得分?jǐn)?shù)都是正數(shù)并且加起來(lái)等于 1。

這些分?jǐn)?shù)決定了在編碼當(dāng)前位置(這里的例子是第一個(gè)位置)的詞時(shí),,對(duì)所有位置的詞分別有多少的注意力,。很明顯,在上圖的例子中,,當(dāng)前位置(這里的例子是第一個(gè)位置)的詞會(huì)有最高的分?jǐn)?shù),,但有時(shí),,關(guān)注到其他位置上相關(guān)的詞也很有用。

第 5 步,,得到每個(gè)位置的分?jǐn)?shù)后,,將每個(gè)分?jǐn)?shù)分別與每個(gè) Value 向量相乘。這種做法背后的直覺理解就是:對(duì)于分?jǐn)?shù)高的位置,,相乘后的值就越大,,我們把更多的注意力放到了它們身上;對(duì)于分?jǐn)?shù)低的位置,,相乘后的值就越小,,這些位置的詞可能是相關(guān)性不大的,這樣我們就忽略了這些位置的詞,。

第 6 步是把上一步得到的向量相加,,就得到了 Self Attention 層在這個(gè)位置(這里的例子是第一個(gè)位置)的輸出。

上面這張圖,,包含了 Self Attention 的全過程,,最終得到的當(dāng)前位置(這里的例子是第一個(gè)位置)的向量會(huì)輸入到前饋神經(jīng)網(wǎng)絡(luò)。但這樣每次只能計(jì)算一個(gè)位置的輸出向量,,在實(shí)際的代碼實(shí)現(xiàn)中,,Self Attention 的計(jì)算過程是使用矩陣來(lái)實(shí)現(xiàn)的,這樣可以加速計(jì)算,,一次就得到所有位置的輸出向量,。下面讓我們來(lái)看,如何使用矩陣來(lái)計(jì)算所有位置的輸出向量,。

五,、使用矩陣計(jì)算 Self-Attention

第一步是計(jì)算 Query,Key,,Value 的矩陣,。首先,我們把所有詞向量放到一個(gè)矩陣 X 中,,然后分別和 3 個(gè)權(quán)重矩陣,, 相乘,,得到 Q,,K,V 矩陣,。

矩陣 X 中的每一行,,表示句子中的每一個(gè)詞的詞向量,長(zhǎng)度是 512,。Q,,K,,V 矩陣中的每一行表示 Query 向量,Key 向量,,Value 向量,,向量長(zhǎng)度是 64。

接著,,由于我們使用了矩陣來(lái)計(jì)算,,我們可以把上面的第 2 步到第 6 步壓縮為一步,直接得到 Self Attention 的輸出,。

六,、多頭注意力機(jī)制(multi-head attention)

Transformer 的論文通過增加多頭注意力機(jī)制(一組注意力稱為一個(gè) attention head),進(jìn)一步完善了 Self Attention 層,。這種機(jī)制從如下兩個(gè)方面增強(qiáng)了 attention 層的能力:

  1. 它擴(kuò)展了模型關(guān)注不同位置的能力,。在上面的例子中,第一個(gè)位置的輸出 z1 包含了句子中其他每個(gè)位置的很小一部分信息,,但 z1 可能主要是由第一個(gè)位置的信息決定的,。當(dāng)我們翻譯句子:The animal didn’t cross the street because it was too tired時(shí),我們想讓機(jī)器知道其中的it指代的是什么,。這時(shí),,多頭注意力機(jī)制會(huì)有幫助。

  2. 多頭注意力機(jī)制賦予 attention 層多個(gè)“子表示空間”,。下面我們會(huì)看到,多頭注意力機(jī)制會(huì)有多組  的權(quán)重矩陣(在 Transformer 的論文中,使用了 8 組注意力(attention heads),。因此,,接下來(lái)我也是用 8 組注意力頭 (attention heads))。每一組注意力的 的權(quán)重矩陣都是隨機(jī)初始化的。經(jīng)過訓(xùn)練之后,,每一組注意力可以看作是把輸入的向量映射到一個(gè)”子表示空間“,。


在多頭注意力機(jī)制中,我們?yōu)槊拷M注意力維護(hù)單獨(dú)的 WQ, WK, WV 權(quán)重矩陣,。將輸入 X 和每組注意力的WQ, WK, WV 相乘,,得到 8 組 Q, K, V 矩陣。

接著,,我們把每組 K, Q, V 計(jì)算得到每組的 Z 矩陣,,就得到 8 個(gè) Z 矩陣,。

接下來(lái)就有點(diǎn)麻煩了,因?yàn)榍梆伾窠?jīng)網(wǎng)絡(luò)層接收的是 1 個(gè)矩陣(其中每行的向量表示一個(gè)詞),,而不是 8 個(gè)矩陣,。所以我們需要一種方法,把 8 個(gè)矩陣整合為一個(gè)矩陣,。

怎么才能做到呢,?我們把矩陣拼接起來(lái),然后和另一個(gè)權(quán)重矩陣  相乘,。

  1. 把 8 個(gè)矩陣 {Z0,Z1...,Z7} 拼接起來(lái)
  2. 把拼接后的矩陣和 WO 權(quán)重矩陣相乘
  3. 得到最終的矩陣 Z,,這個(gè)矩陣包含了所有 attention heads(注意力頭) 的信息。這個(gè)矩陣會(huì)輸入到 FFNN (Feed Forward Neural Network)層,。

這就是多頭注意力的全部?jī)?nèi)容,。我知道,在上面的講解中,,出現(xiàn)了相當(dāng)多的矩陣,。下面我把所有的內(nèi)容都放到一張圖中,這樣你可以總攬全局,,在這張圖中看到所有的內(nèi)容,。

既然我們已經(jīng)談到了多頭注意力,現(xiàn)在讓我們重新回顧之前的翻譯例子,,看下當(dāng)我們編碼單詞it時(shí),,不同的 attention heads (注意力頭)關(guān)注的是什么部分。

當(dāng)我們編碼單詞"it"時(shí),,其中一個(gè) attention head (注意力頭)最關(guān)注的是"the animal",,另外一個(gè) attention head 關(guān)注的是"tired"。因此在某種意義上,,"it"在模型中的表示,,融合了"animal"和"word"的部分表達(dá)。

然而,,當(dāng)我們把所有 attention heads(注意力頭) 都在圖上畫出來(lái)時(shí),,多頭注意力又變得難以解釋了。

七,、代碼實(shí)現(xiàn)矩陣計(jì)算 Attention

下面我們是用代碼來(lái)演示,,如何使用矩陣計(jì)算 attention,。首先使用 PyTorch 庫(kù)提供的函數(shù)實(shí)現(xiàn),,然后自己再實(shí)現(xiàn),。

7.1 使用 PyTorch 庫(kù)的實(shí)現(xiàn)

PyTorch 提供了 MultiheadAttention 來(lái)實(shí)現(xiàn) attention 的計(jì)算。

torch.nn.MultiheadAttention(embed_dim, num_heads, dropout=0.0, bias=True, add_bias_kv=False, add_zero_attn=False, kdim=None, vdim=None)

參數(shù)說明如下:

  • embed_dim:最終輸出的 K,、Q,、V 矩陣的維度,,這個(gè)維度需要和詞向量的維度一樣

  • num_heads:設(shè)置多頭注意力的數(shù)量。如果設(shè)置為 1,,那么只使用一組注意力,。如果設(shè)置為其他數(shù)值,那么 num_heads 的值需要能夠被 embed_dim 整除

  • dropout:這個(gè) dropout 加在 attention score 后面

現(xiàn)在來(lái)解釋一下,,為什么  num_heads 的值需要能夠被 embed_dim 整除,。這是為了把詞的隱向量長(zhǎng)度平分到每一組,這樣多組注意力也能夠放到一個(gè)矩陣?yán)?,從而并行?jì)算多頭注意力,。

例如,我們前面說到,,8 組注意力可以得到 8 組 Z 矩陣,,然后把這些矩陣拼接起來(lái),得到最終的輸出,。如果最終輸出的每個(gè)詞的向量維度是 512,,那么每組注意力的向量維度應(yīng)該是 

如果不能夠整除,,那么這些向量的長(zhǎng)度就無(wú)法平均分配,。

下面的會(huì)有代碼示例,如何使用矩陣實(shí)現(xiàn)多組注意力的并行計(jì)算,。

定義 MultiheadAttention 的對(duì)象后,,調(diào)用時(shí)傳入的參數(shù)如下。

forward(query, key, value, key_padding_mask=None, need_weights=True, attn_mask=None)
  • query:對(duì)應(yīng)于 Key 矩陣,,形狀是 (L,N,E) ,。其中 L 是輸出序列長(zhǎng)度,N 是 batch size,,E 是詞向量的維度

  • key:對(duì)應(yīng)于 Key 矩陣,,形狀是 (S,N,E) ,。其中 S 是輸入序列長(zhǎng)度,,N 是 batch size,E 是詞向量的維度

  • value:對(duì)應(yīng)于 Value 矩陣,,形狀是 (S,N,E) ,。其中 S 是輸入序列長(zhǎng)度,N 是 batch size,,E 是詞向量的維度

  • key_padding_mask:如果提供了這個(gè)參數(shù),,那么計(jì)算 attention score 時(shí),忽略 Key 矩陣中某些 padding 元素,,不參與計(jì)算 attention,。形狀是 (N,S),。其中 N 是 batch size,S 是輸入序列長(zhǎng)度,。

    • 如果 key_padding_mask 是 ByteTensor,,那么非 0 元素對(duì)應(yīng)的位置會(huì)被忽略
    • 如果 key_padding_mask 是 BoolTensor,那么  True 對(duì)應(yīng)的位置會(huì)被忽略
  • attn_mask:計(jì)算輸出時(shí),,忽略某些位置,。形狀可以是 2D  (L,S),或者 3D (N?numheads,L,S),。其中 L 是輸出序列長(zhǎng)度,,S 是輸入序列長(zhǎng)度,N 是 batch size,。

    • 如果 attn_mask 是 ByteTensor,,那么非 0 元素對(duì)應(yīng)的位置會(huì)被忽略
    • 如果 attn_mask 是 BoolTensor,那么  True 對(duì)應(yīng)的位置會(huì)被忽略

需要注意的是:在前面的講解中,,我們的 K,、Q、V 矩陣的序列長(zhǎng)度都是一樣的,。但是在實(shí)際中,,K、V 矩陣的序列長(zhǎng)度是一樣的,,而 Q 矩陣的序列長(zhǎng)度可以不一樣,。

這種情況發(fā)生在:在解碼器部分的Encoder-Decoder Attention層中,Q 矩陣是來(lái)自解碼器下層,,而 K,、V 矩陣則是來(lái)自編碼器的輸出。


在完成了編碼(encoding)階段之后,,我們開始解碼(decoding)階段,。解碼(decoding )階段的每一個(gè)時(shí)間步都輸出一個(gè)翻譯后的單詞(這里的例子是英語(yǔ)翻譯)。

輸出是:

  • attn_output:形狀是 (L,N,E)
  • attn_output_weights:形狀是 (N,L,S)

代碼示例如下:

## nn.MultiheadAttention 輸入第0維為length
# batch_size 為 64,,有 12 個(gè)詞,,每個(gè)詞的 Query 向量是 300 維
query = torch.rand(12,64,300)
# batch_size 為 64,有 10 個(gè)詞,,每個(gè)詞的 Key 向量是 300 維
key = torch.rand(10,64,300)
# batch_size 為 64,,有 10 個(gè)詞,每個(gè)詞的 Value 向量是 300 維
value= torch.rand(10,64,300)

embed_dim = 299
num_heads = 1
# 輸出是 (attn_output, attn_output_weights)
multihead_attn = nn.MultiheadAttention(embed_dim, num_heads)
attn_output = multihead_attn(query, key, value)[0]
# output: torch.Size([12, 64, 300])
# batch_size 為 64,,有 12 個(gè)詞,,每個(gè)詞的向量是 300 維
print(attn_output.shape)

7.2 手動(dòng)實(shí)現(xiàn)計(jì)算 Attention

在 PyTorch 提供的 MultiheadAttention  中,第 1 維是句子長(zhǎng)度,第 2 維是 batch size,。這里我們的代碼實(shí)現(xiàn)中,,第 1 維是 batch size,第 2 維是句子長(zhǎng)度,。代碼里也包括:如何用矩陣實(shí)現(xiàn)多組注意力的并行計(jì)算,。代碼中已經(jīng)有詳細(xì)注釋和說明。

class MultiheadAttention(nn.Module):
# n_heads:多頭注意力的數(shù)量
# hid_dim:每個(gè)詞輸出的向量維度
def __init__(self, hid_dim, n_heads, dropout):
super(MultiheadAttention, self).__init__()
self.hid_dim = hid_dim
self.n_heads = n_heads

# 強(qiáng)制 hid_dim 必須整除 h
assert hid_dim % n_heads == 0
# 定義 W_q 矩陣
self.w_q = nn.Linear(hid_dim, hid_dim)
# 定義 W_k 矩陣
self.w_k = nn.Linear(hid_dim, hid_dim)
# 定義 W_v 矩陣
self.w_v = nn.Linear(hid_dim, hid_dim)
self.fc = nn.Linear(hid_dim, hid_dim)
self.do = nn.Dropout(dropout)
# 縮放
self.scale = torch.sqrt(torch.FloatTensor([hid_dim // n_heads]))

def forward(self, query, key, value, mask=None):
# K: [64,10,300], batch_size 為 64,,有 12 個(gè)詞,,每個(gè)詞的 Query 向量是 300 維
# V: [64,10,300], batch_size 為 64,有 10 個(gè)詞,,每個(gè)詞的 Query 向量是 300 維
# Q: [64,12,300], batch_size 為 64,,有 10 個(gè)詞,每個(gè)詞的 Query 向量是 300 維
bsz = query.shape[0]
Q = self.w_q(query)
K = self.w_k(key)
V = self.w_v(value)
# 這里把 K Q V 矩陣拆分為多組注意力,,變成了一個(gè) 4 維的矩陣
# 最后一維就是是用 self.hid_dim // self.n_heads 來(lái)得到的,,表示每組注意力的向量長(zhǎng)度, 每個(gè) head 的向量長(zhǎng)度是:300/6=50
# 64 表示 batch size,6 表示有 6組注意力,,10 表示有 10 詞,,50 表示每組注意力的詞的向量長(zhǎng)度
# K: [64,10,300] 拆分多組注意力 -> [64,10,6,50] 轉(zhuǎn)置得到 -> [64,6,10,50]
# V: [64,10,300] 拆分多組注意力 -> [64,10,6,50] 轉(zhuǎn)置得到 -> [64,6,10,50]
# Q: [64,12,300] 拆分多組注意力 -> [64,12,6,50] 轉(zhuǎn)置得到 -> [64,6,12,50]
# 轉(zhuǎn)置是為了把注意力的數(shù)量 6 放到前面,把 10 和 50 放到后面,,方便下面計(jì)算
Q = Q.view(bsz, -1, self.n_heads, self.hid_dim //
self.n_heads).permute(0, 2, 1, 3)
K = K.view(bsz, -1, self.n_heads, self.hid_dim //
self.n_heads).permute(0, 2, 1, 3)
V = V.view(bsz, -1, self.n_heads, self.hid_dim //
self.n_heads).permute(0, 2, 1, 3)

# 第 1 步:Q 乘以 K的轉(zhuǎn)置,,除以scale
# [64,6,12,50] * [64,6,50,10] = [64,6,12,10]
# attention:[64,6,12,10]
attention = torch.matmul(Q, K.permute(0, 1, 3, 2)) / self.scale

# 把 mask 不為空,那么就把 mask 為 0 的位置的 attention 分?jǐn)?shù)設(shè)置為 -1e10
if mask is not None:
attention = attention.masked_fill(mask == 0, -1e10)

# 第 2 步:計(jì)算上一步結(jié)果的 softmax,,再經(jīng)過 dropout,,得到 attention。
# 注意,,這里是對(duì)最后一維做 softmax,,也就是在輸入序列的維度做 softmax
# attention: [64,6,12,10]
attention = self.do(torch.softmax(attention, dim=-1))

# 第三步,attention結(jié)果與V相乘,,得到多頭注意力的結(jié)果
# [64,6,12,10] * [64,6,10,50] = [64,6,12,50]
# x: [64,6,12,50]
x = torch.matmul(attention, V)

# 因?yàn)?query 有 12 個(gè)詞,,所以把 12 放到前面,把 5 和 60 放到后面,,方便下面拼接多組的結(jié)果
# x: [64,6,12,50] 轉(zhuǎn)置-> [64,12,6,50]
x = x.permute(0, 2, 1, 3).contiguous()
# 這里的矩陣轉(zhuǎn)換就是:把多組注意力的結(jié)果拼接起來(lái)
# 最終結(jié)果就是 [64,12,300]
# x: [64,12,6,50] -> [64,12,300]
x = x.view(bsz, -1, self.n_heads * (self.hid_dim // self.n_heads))
x = self.fc(x)
return x


# batch_size 為 64,,有 12 個(gè)詞,每個(gè)詞的 Query 向量是 300 維
query = torch.rand(64, 12, 300)
# batch_size 為 64,,有 12 個(gè)詞,,每個(gè)詞的 Key 向量是 300 維
key = torch.rand(64, 10, 300)
# batch_size 為 64,,有 10 個(gè)詞,,每個(gè)詞的 Value 向量是 300 維
value = torch.rand(64, 10, 300)
attention = MultiheadAttention(hid_dim=300, n_heads=6, dropout=0.1)
output = attention(query, key, value)
## output: torch.Size([64, 12, 300])
print(output.shape)

7.3 關(guān)鍵代碼

其中用矩陣實(shí)現(xiàn)多頭注意力的關(guān)鍵代碼如下所示, K、Q,、V 矩陣拆分為多組注意力,,變成了一個(gè) 4 維的矩陣。

        # 這里把 K Q V 矩陣拆分為多組注意力,,變成了一個(gè) 4 維的矩陣
# 最后一維就是是用 self.hid_dim // self.n_heads 來(lái)得到的,,表示每組注意力的向量長(zhǎng)度, 每個(gè) head 的向量長(zhǎng)度是:300/6=50
# 64 表示 batch size,6 表示有 6組注意力,,10 表示有 10 個(gè)詞,,50 表示每組注意力的詞的向量長(zhǎng)度
# K: [64,10,300] 拆分多組注意力 -> [64,10,6,50] 轉(zhuǎn)置得到 -> [64,6,10,50]
# V: [64,10,300] 拆分多組注意力 -> [64,10,6,50] 轉(zhuǎn)置得到 -> [64,6,10,50]
# Q: [64,12,300] 拆分多組注意力 -> [64,12,6,50] 轉(zhuǎn)置得到 -> [64,6,12,50]
# 轉(zhuǎn)置是為了把注意力的數(shù)量 6 放到前面,把 10 和 50 放到后面,,方便下面計(jì)算
Q = Q.view(bsz, -1, self.n_heads, self.hid_dim //
self.n_heads).permute(0, 2, 1, 3)
K = K.view(bsz, -1, self.n_heads, self.hid_dim //
self.n_heads).permute(0, 2, 1, 3)
V = V.view(bsz, -1, self.n_heads, self.hid_dim //
self.n_heads).permute(0, 2, 1, 3)
經(jīng)過 attention 計(jì)算得到 x 的形狀是 `[64,12,6,50]`,,64 表示 batch size,6 表示有 6組注意力,,10 表示有 10 個(gè)詞,,50 表示每組注意力的詞的向量長(zhǎng)度。把這個(gè)矩陣轉(zhuǎn)換為 `[64,12,300]`的矩陣,,就是相當(dāng)于把多組注意力的結(jié)果拼接起來(lái),。
e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e e ee

這里的矩陣轉(zhuǎn)換就是:把多組注意力的結(jié)果拼接起來(lái),最終結(jié)果就是 [64,12,300],,x: [64,12,6,50] -> [64,12,300]
x = x.view(bsz, -1, self.n_heads * (self.hid_dim // self.n_heads))

八,、使用位置編碼來(lái)表示序列的順序

到目前為止,我們闡述的模型中缺失了一個(gè)東西,,那就是表示序列中單詞順序的方法,。

為了解決這個(gè)問題,Transformer 模型對(duì)每個(gè)輸入的向量都添加了一個(gè)向量,。這些向量遵循模型學(xué)習(xí)到的特定模式,,有助于確定每個(gè)單詞的位置,或者句子中不同單詞之間的距離,。這種做法背后的直覺是:將這些表示位置的向量添加到詞向量中,,得到了新的向量,這些新向量映射到 Q/K/V,,然后計(jì)算點(diǎn)積得到 attention 時(shí),,可以提供有意義的信息。

為了讓模型了解單詞的順序,,我們添加了帶有位置編碼的向量--這些向量的值遵循特定的模式,。

如果我們假設(shè)詞向量的維度是 4,那么帶有位置編碼的向量可能如下所示:

上圖為帶有位置編碼的向量長(zhǎng)度為 4 的例子,。

那么帶有位置編碼的向量到底遵循什么模式,?

在下圖中,,每一行表示一個(gè)帶有位置編碼的向量。所以,,第一行對(duì)應(yīng)于序列中第一個(gè)單詞的位置編碼向量,。每一行都包含 512 個(gè)值,每個(gè)值的范圍在 -1 和 1 之間,。我對(duì)這些向量進(jìn)行了涂色可視化,,你可以從中看到向量遵循的模式。

這是一個(gè)真實(shí)的例子,,包含了 20 個(gè)詞,,每個(gè)詞向量的維度是 512。你可以看到,,它看起來(lái)像從中間一分為二,。這是因?yàn)樽蟀氩糠值闹凳怯?sine 函數(shù)產(chǎn)生的,而右半部分的值是由 cosine 函數(shù)產(chǎn)生的,,然后將他們拼接起來(lái),,得到每個(gè)位置編碼向量。

你可以在get_timing_signal_1d()上查看生成位置編碼的代碼,。這種方法來(lái)自于Tranformer2Transformer 的實(shí)現(xiàn),。

而論文中的方法和上面圖中的稍有不同,它不是直接拼接兩個(gè)向量,,而是將兩個(gè)向量交織在一起,。如下圖所示。

此為生成位置編碼的公式,,在 Transformer 論文的 3.5 節(jié)中有詳細(xì)說明,。

這不是唯一一種生成位置編碼的方法。但這種方法的優(yōu)點(diǎn)是:可以擴(kuò)展到未知的序列長(zhǎng)度,。例如:當(dāng)我們的模型需要翻譯一個(gè)句子,,而這個(gè)句子的長(zhǎng)度大于訓(xùn)練集中所有句子的長(zhǎng)度,這時(shí),,這種位置編碼的方法也可以生成一樣長(zhǎng)的位置編碼向量,。

九、殘差連接

在我們繼續(xù)講解之前,,編碼器結(jié)構(gòu)中有一個(gè)需要注意的細(xì)節(jié)是:編碼器的每個(gè)子層(Self Attention 層和 FFNN)都有一個(gè)殘差連接和層標(biāo)準(zhǔn)化(layer-normalization),。

將 Self-Attention 層的層標(biāo)準(zhǔn)化(layer-normalization)和向量都進(jìn)行可視化,如下所示:

在解碼器的子層里面也有層標(biāo)準(zhǔn)化(layer-normalization),。假設(shè)一個(gè) Transformer 是由 2 層編碼器和兩層解碼器組成的,,如下圖所示。

十,、Decoder(解碼器)

現(xiàn)在我們已經(jīng)介紹了解碼器中的大部分概念,,我們也基本知道了解碼器的原理?,F(xiàn)在讓我們來(lái)看下, 編碼器和解碼器是如何協(xié)同工作的,。

上面說了,編碼器一般有多層,,第一個(gè)編碼器的輸入是一個(gè)序列,,最后一個(gè)編碼器輸出是一組注意力向量 K 和 V。這些注意力向量將會(huì)輸入到每個(gè)解碼器的Encoder-Decoder Attention層,,這有助于解碼器把注意力集中中輸入序列的合適位置,。

在完成了編碼(encoding)階段之后,我們開始解碼(decoding)階段,。解碼(decoding )階段的每一個(gè)時(shí)間步都輸出一個(gè)翻譯后的單詞(這里的例子是英語(yǔ)翻譯),。

接下來(lái)會(huì)重復(fù)這個(gè)過程,直到輸出一個(gè)結(jié)束符,,Transformer 就完成了所有的輸出,。每一步的輸出都會(huì)在下一個(gè)時(shí)間步輸入到下面的第一個(gè)解碼器。Decoder 就像 Encoder 那樣,,從下往上一層一層地輸出結(jié)果,。正對(duì)如編碼器的輸入所做的處理,我們把解碼器的輸入向量,,也加上位置編碼向量,,來(lái)指示每個(gè)詞的位置。

解碼器中的 Self Attention 層,,和編碼器中的 Self Attention 層不太一樣:在解碼器里,,Self Attention 層只允許關(guān)注到輸出序列中早于當(dāng)前位置之前的單詞。具體做法是:在 Self Attention 分?jǐn)?shù)經(jīng)過 Softmax 層之前,,屏蔽當(dāng)前位置之后的那些位置,。

Encoder-Decoder Attention層的原理和多頭注意力(multiheaded Self Attention)機(jī)制類似,不同之處是:Encoder-Decoder Attention層是使用前一層的輸出來(lái)構(gòu)造 Query 矩陣,,而 Key 矩陣和 Value 矩陣來(lái)自于解碼器最終的輸出,。

十一、 最后的線性層和 Softmax 層

Decoder 最終的輸出是一個(gè)向量,,其中每個(gè)元素是浮點(diǎn)數(shù),。我們?cè)趺窗堰@個(gè)向量轉(zhuǎn)換為單詞呢?這是由 Softmax 層后面的線性層來(lái)完成的,。

線性層就是一個(gè)普通的全連接神經(jīng)網(wǎng)絡(luò),,可以把解碼器輸出的向量,映射到一個(gè)更長(zhǎng)的向量,,這個(gè)向量稱為 logits 向量,。

現(xiàn)在假設(shè)我們的模型有 10000 個(gè)英語(yǔ)單詞(模型的輸出詞匯表),,這些單詞是從訓(xùn)練集中學(xué)到的。因此 logits 向量有 10000 個(gè)數(shù)字,,每個(gè)數(shù)表示一個(gè)單詞的分?jǐn)?shù),。我們就是這樣去理解線性層的輸出。

然后,,Softmax 層會(huì)把這些分?jǐn)?shù)轉(zhuǎn)換為概率(把所有的分?jǐn)?shù)轉(zhuǎn)換為正數(shù),,并且加起來(lái)等于 1)。然后選擇最高概率的那個(gè)數(shù)字對(duì)應(yīng)的詞,,就是這個(gè)時(shí)間步的輸出單詞,。

在上圖中,最下面的向量,,就是編碼器的輸出,,這個(gè)向量輸入到線性層和 Softmax 層,最終得到輸出的詞,。

十二,、 Transformer 的訓(xùn)練過程

現(xiàn)在我們已經(jīng)了解了 Transformer 的前向傳播過程,下面講講 Transformer 的訓(xùn)練過程,,這也是非常有用的知識(shí),。

在訓(xùn)練過程中,模型會(huì)經(jīng)過上面講的所有前向傳播的步驟,。但是,,當(dāng)我們?cè)谝粋€(gè)標(biāo)注好的數(shù)據(jù)集上訓(xùn)練這個(gè)模型的時(shí)候,我們可以對(duì)比模型的輸出和真實(shí)的標(biāo)簽,。

為了可視化這個(gè)對(duì)比,,讓我們假設(shè)輸出詞匯表只包含 6 個(gè)單詞(“a”, “am”, “i”, “thanks”, “student”, and “<eos>”(“<eos>”表示句子末尾))。

我們模型的輸出詞匯表,,是在訓(xùn)練之前的數(shù)據(jù)預(yù)處理階段構(gòu)造的,。當(dāng)我們確定了輸出詞匯表,我們可以用向量來(lái)表示詞匯表中的每個(gè)單詞,。這個(gè)表示方法也稱為  one-hot encoding,。例如,我們可以把單詞 “am” 用下面的向量來(lái)表示:

介紹了訓(xùn)練過程,,我們接著討論模型的損失函數(shù),,這我們?cè)谟?xùn)練時(shí)需要優(yōu)化的目標(biāo),通過優(yōu)化這個(gè)目標(biāo)來(lái)得到一個(gè)訓(xùn)練好的,、非常精確的模型,。

十三、 損失函數(shù)

用一個(gè)簡(jiǎn)單的例子來(lái)說明訓(xùn)練過程,,比如:把“merci”翻譯為“thanks”,。

這意味著我們希望模型最終輸出的概率分布,,會(huì)指向單詞 ”thanks“(在“thanks”這個(gè)詞的概率最高)。但模型還沒訓(xùn)練好,,它輸出的概率分布可能和我們希望的概率分布相差甚遠(yuǎn),。

由于模型的參數(shù)都是隨機(jī)初始化的。模型在每個(gè)詞輸出的概率都是隨機(jī)的,。我們可以把這個(gè)概率和正確的輸出概率做對(duì)比,,然后使用反向傳播來(lái)調(diào)整模型的權(quán)重,使得輸出的概率分布更加接近震數(shù)輸出,。

那我們要怎么比較兩個(gè)概率分布呢,?我們可以簡(jiǎn)單地用一個(gè)概率分布減去另一個(gè)概率分布,。關(guān)于更多細(xì)節(jié),,你可以查看交叉熵(cross-entropy)]和KL 散度(Kullback–Leibler divergence)的相關(guān)概念。

但上面的例子是經(jīng)過簡(jiǎn)化的,,因?yàn)槲覀兊木渥又挥幸粋€(gè)單詞,。在實(shí)際中,我們使用的句子不只有一個(gè)單詞,。例如--輸入是:“je suis étudiant” ,,輸出是:“i am a student”。這意味著,,我們的模型需要輸出多個(gè)概率分布,,滿足如下條件:

  • 每個(gè)概率分布都是一個(gè)向量,長(zhǎng)度是 vocab_size(我們的例子中,,向量長(zhǎng)度是 6,,但實(shí)際中更可能是 30000 或者 50000)
  • 第一個(gè)概率分布中,最高概率對(duì)應(yīng)的單詞是 i
  • 第二個(gè)概率分布中,,最高概率對(duì)應(yīng)的單詞是 am
  • 以此類推,,直到第 5 個(gè)概率分布中,最高概率對(duì)應(yīng)的單詞是 <eos>,,表示沒有下一個(gè)單詞了

我們用例子中的句子訓(xùn)練模型,,希望產(chǎn)生圖中所示的概率分布

我們的模型在一個(gè)足夠大的數(shù)據(jù)集上,經(jīng)過足夠長(zhǎng)時(shí)間的訓(xùn)練后,,希望輸出的概率分布如下圖所示:

希望經(jīng)過訓(xùn)練,,模型會(huì)輸出我們希望的正確翻譯。當(dāng)然,,如果你要翻譯的句子是訓(xùn)練集中的一部分,,那輸出的結(jié)果并不能說明什么。我們希望的是模型在沒見過的句子上也能夠準(zhǔn)確翻譯,。需要注意的是:概率分布向量中,,每個(gè)位置都會(huì)有一點(diǎn)概率,,即使這個(gè)位置不是輸出對(duì)應(yīng)的單詞--這是 Softmax 中一個(gè)很有用的特性,有助于幫助訓(xùn)練過程,。

現(xiàn)在,,由于模型每個(gè)時(shí)間步只產(chǎn)生一個(gè)輸出,我們可以認(rèn)為:模型是從概率分布中選擇概率最大的詞,,并且丟棄其他詞,。這種方法叫做貪婪解碼(greedy decoding)。另一種方法是每個(gè)時(shí)間步保留兩個(gè)最高概率的輸出詞,,然后在下一個(gè)時(shí)間步,,重復(fù)執(zhí)行這個(gè)過程:假設(shè)第一個(gè)位置概率最高的兩個(gè)輸出的詞是”I“和”a“,這兩個(gè)詞都保留,,然后根據(jù)第一個(gè)詞計(jì)算第二個(gè)位置的詞的概率分布,,再取出 2 個(gè)概率最高的詞,對(duì)于第二個(gè)位置和第三個(gè)位置,,我們也重復(fù)這個(gè)過程,。這種方法稱為集束搜索(beam search),在我們的例子中,,beam_size 的值是 2(含義是:在所有時(shí)間步,,我們保留兩個(gè)最高概率),top_beams 的值也是 2(表示我們最終會(huì)返回兩個(gè)翻譯的結(jié)果),。beam_size  和 top_beams 都是你可以在實(shí)驗(yàn)中嘗試的超參數(shù),。

更進(jìn)一步理解

我希望上面講的內(nèi)容,可以幫助你理解 Transformer 中的主要概念,。如果你想更深一步地理解,,我建議你可以參考下面這些:

  • 閱讀 Transformer 的論文:
    《Attention Is All You Need》
    鏈接地址:https:///abs/1706.03762
  • 閱讀Transformer 的博客文章:
    《Transformer: A Novel Neural Network Architecture for Language Understanding》
    鏈接地址:https://ai./2017/08/transformer-novel-neural-network.html
  • 閱讀《Tensor2Tensor announcement》
    鏈接地址:https://ai./2017/06/accelerating-deep-learning-research.html
  • 觀看視頻 【?ukasz Kaiser’s talk】來(lái)理解模型和其中的細(xì)節(jié)
    鏈接地址:https://www./watch?v=rBCqOTEfxvg
  • 運(yùn)行這份代碼:【Jupyter Notebook provided as part of the Tensor2Tensor repo】
    鏈接地址:https://colab.research.google.com/github/tensorflow/tensor2tensor/blob/master/tensor2tensor/notebooks/hello_t2t.ipynb。
  • 查看這個(gè)項(xiàng)目:【Tensor2Tensor repo】
    鏈接地址:https://github.com/tensorflow/tensor2tensor
“干貨學(xué)習(xí),,點(diǎn)三連

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式,、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多