捋一捋Unified Language Model Pre-training for Natural Language Understanding and Generation

論文地址:https://arxiv.org/pdf/1905.03197.pdf

1.這是個什麼東西?

首先我想盡量用比較通俗的方式和語言去描述一下我理解的這個UNILM模型,如果有哪些不太專業嚴謹的地方各位看官可以留言給我我再改.

其實看完這篇論文以後有兩個地方讓我情緒產生了比較激烈的波動:

1.當然第一個就是這個模型的結構(如下,後面詳細介紹),很有創意,當然最後的結果也被證實了的確很厲害,不僅在NLU(自然語言理解)上相較於bert有提升,在bert表現的不是那麼好的NLG(自然語言生成)方面表現的那也是相當的好,在:CNN/DailyMail abstractive summarization , Gigaword abstractive summarization  ,CoQA generative question answering,SQuAD question generation,DSTC7 document-grounded dialog response generation.等五個NLG數據集上都拿到了state of art,並且在GLUE benchmark,SQuAD 2.0和CoQA上都超越了bert,具體的對比情況可以看論文的第三部分,反正就是最終效果很NB就是了.

2.第二點讓我比較激動的就是這真不是個普通人能玩兒的起的東西,他的預訓練模型一共跑了770000步,在8片Nvidia Telsa V100 32GB 顯卡上跑10000步需要7個小時,算下來77 * 7 = 539個小時= 22天半,啊,每一秒都是金錢在燃燒的味道. 而且他微調(Fine-tuning)也推薦使用2到4片Nvidia Telsa V100 32GB, 我覺得這就勸退了大部分想試一試的人(然而嘗試了一下,batch_size調到32在1080上也是能跑的,用了三片1080,跑他提供的第一個demo大概三分鐘一個epoch), 而且現在也沒有中文的預訓練模型,我猜測這就是爲啥雖然他效果很好,但是熱度不比bert的原因吧(雖然bert也是挺貴,不過微調的話1080也是可以用的),畢竟bert提供的預訓練模型種類更多,大多數人也能玩兒的起. 希望UNILM也能在後續出一些低配版,讓我這種窮人也能嚐嚐鮮.

 

說了這麼多廢話,那他到底是個什麼東西呢? 看這篇論文的名字, 翻譯過來就是 : 一個可以用來做自然語言理解和自然語言生成的統一預訓練模型, 說白了也和bert啊 ,elmo啊之類的一樣, 但是人家更優秀的一個地方就是既能用在自然語言理解上, 又能用在自然語言生成上, 不像bert在自然語言生成上不太行(因爲他是雙向的), 這個UNILM在各個方面都很能打,那他爲什麼這麼秀呢 ? 接下來咱們來一起捋一捋.

 

2.汝何秀?

首先無可爭議的是預訓練語言模型在自然語言處理任務上的成果是優秀的,有目共睹的,爲啥呢,因爲咱們的文章語句天生就是蘊含着語法語義的訓練資源,預訓練語言模型在海量的語料數據中遨遊學習,所以他能夠基於足夠多的上下文文本表徵來預測目標token的語義信息,俗話說 : 熟讀詩書三百首,不會寫來也會抄麼, 我覺得說的在理, 看咱們先人在多少年前就領悟了非監督學習的真諦!

首先,這個UNILM和bert一樣,都可以微調(fine-tuned)來適用於各種各樣的下游任務,但是不像bert多被用在自然語言理解上,UNILM可以通過配置的方式來使用不同的自注意力的mask機制(看上圖),爲不同類型的語言模型聚合上下文,從而既可以被用在自然語言理解任務上,又可以被用在自然語言生成任務上.

論文說了,UNILM主要有三個優點:
1、他統一的預訓練過程,使用一個transformer語言模型囊括了了不同類型的語言模型的的參數和結構(bi, left-t-right,right-t-left,seq-t-seq),從而不需要分開訓練多個語言模型.

2、因爲他囊括了多種語言模型參數和結構呀,所以參數共享使得他學習到的文本表徵更加通用化了,他會針對不同的語言建模目標進行聯合優化,上下文信息會以不同的方式去使用,所以可以減少自然語言任務訓練中的過擬合.

3、因爲UNILM有seq-to-seq的用法,所以他天生適用自然語言生成任務,比如摘要提取,問題生成.

 

說了那麼多優點,咱們接下來一起看一下這個“統一語言模型”是怎麼做預訓練的吧!

 

還是看這個圖,

2.1.首先看輸入

首先我們看到了他的輸入是 [x1,x2,x3....xn],整個輸入字符串也可以有兩種格式,一個句子-單項語言模型用(unidirectional LMs) 或者一對句子,給雙向(bidirectional LM)或者seq-t-seq用,每個x就是一個英文單詞兒或者說是一個漢字,總之就是基本語言單位,然後和bert一樣一樣的,每個基本語言單位的向量表示都包括了三個部分,分別是字符表示向量,位置表示向量,句子序號表示向量.

然後在輸入開頭加入[SOS] token(start-of-sequence), 表示句子輸入開始啦,然後在每一句末尾加[EOS] token(end-of-sequence),這個標籤不僅在自然語言理解任務中表示句子邊界,還在自然語言生成任務中標示着到這個點兒了就該結束解碼過程了,讓模型學習該怎麼停止,還有就是他的英文處理的時候參照bert,(比如會把playing切成兩個詞play和##ing) , 中文? UNILM好像還沒中文的預訓練模型, 現在好像就出了一個預訓練模型, 用的bert-large的參數初始化的.

2.2.然後看一下主要模型結構(敲黑板,這裏要考)

他的主要模型結構是一個多層的transformer,看上圖,把輸入的字符串處理妥當,轉成向量表示以後交給第一個transformer,然後第一個transformer一頓料理以後給下一層transformer,可以表示爲以下這個公式:

H(l) = Transformer-l(H(l−1)), l ∈ [1, L]        當前層的輸出等於上一層的輸出輸入進當前層的transformer後產生的結果

在每一層transformer中,我們用多個自注意力頭(self-attention heads)來聚合上一層transformer的輸出,其中每一個自注意力頭的操作步驟可以表示爲以下這幾個公式(別慌,這公式不難懂)

瞭解bert的同學是不是看着感覺很熟悉? 根本不用講嘛一眼就看懂了. 對嘛 , 本來unilm用的也是transformer, 能有多大的不同呢是吧, 如果不太瞭解bert和transformer的同學可以先去熟悉以下 , 學習路線推薦 attention -> transformer -> bert .傳送門:

Self-Attention與Transformer : https://zhuanlan.zhihu.com/p/47282410

bert : https://zhuanlan.zhihu.com/p/46652512  

其實這些一搜一大堆的啦,畢竟熱度還是比較高的,有時間的話可以直接看那兩篇論文

Attention Is All You Need https://arxiv.org/pdf/1706.03762.pdf

BERT: https://arxiv.org/pdf/1810.04805.pdf

(這兩篇我覺得網上解讀的比較好的已經比較多了,就不捋了)

好了,言歸正傳,那到這裏我就不多解釋QKV以及縮放因子dk了, 我們可以看到unilm在這裏創新的一點就是第二個公式,那這是個什麼東西呢? 大家還要看那個結構圖來理解

 

是不是對照着這個圖一看,有種一下子就明白了的感覺? 是的, 這一個公式也很好理解, 這個M 就代表Mask Matrix , 意思很直白,就是對於當前的token來講, 哪些token他可見, 哪些token他不可見 , 可見的部分就在Q乘以K的轉置除根號dk以後得到的矩陣的相應位置上加0 ,也就是不做任何改變, 不可見的部分就是在相應位置加上個負無窮大 , 就表示對當前token不可見.

就拿第一個Bidirectional LM來說,  每一個token都可見其完整的上下文(就像地圖全開一樣)

第二個單方向的 LM, 每個token只能看見其(左/右)單方向的信息(有戰爭迷霧,走過的地方纔能看見)

seq-t-seq LM , 作爲source的句子裏地圖全開了, 作爲target的句子裏只能看見左面的也就是已經生成的.

 

2.3.看一看unilm怎麼做完形

咱們都知道, 預訓練的語言模型是不需要標註的,他會隨機的遮住一些基本的語言單位 ,也就是把一些字符替換成[MASK]這個特殊token, 然後讓模型通過上下文信息去猜這個蓋住的是什麼字, 然後和真實的做對比求loss, 然後照着loss降低的方向去優化模型參數.

因爲unilm他本質上存在四種不同的語言模型結構嘛.所以他共有四種完形填空的方式,接下來咱們一一道來.

Unidirectional LM : 單向的語言模型, 包括從左到右和從右到左,這裏就單說從左到右, 在考慮每個基本字符單位的向量表示的時候,只考慮他本身及本身左邊的基本字符單位, 打個比方 “我 是 大 [MASK] 哥 呀” ,這句話, 使用單向語言模型的時候 , 我們在預測被遮住的mask的時候所能使用的上下文信息只有 “我 是 大 [MASK]” , 就是隻有MASK和他左邊的所有基本字符單位, 那他是怎麼實現的呢? 大家往上看 , 對, 就是上一個圖片 , left-to-right LM對應的那個矩陣 , 看見沒, 對角線往左下包括對角線都是白色的 , 就代表了Mask Matrix這些位置是0 ,就是不做改變(回想一下上上圖的公式2) , 灰黑色的塊兒就代表這裏是無窮小,就是在Q乘以K的轉置並縮放以後的結果響應位置上加上無窮小,就是變不可見了唄.

Bidirectional LM : 雙向的語言模型可以在每一次預測mask的時候都獲得完整的上下文信息,兩個方向的所有的基本字符單位信息都會編碼進他的上下文信息中 , 相比於單向的語言模型 , 他這樣做肯定可以會有更好的上下文表示(但是這樣就搞不好生成任務了,爲啥 ? 你還沒生成後面的內容呢,就要用後面的內容表徵這個基本字符單位的向量了,能掐還是會算啊?) . 還用上個栗子  “我 是 大 [MASK] 哥 呀” 這句話 (當然兩句話也行,反正就是全局視野,都互相可見), 整個的一句話都會參與進mask的預測過程. 還是擡頭看圖片 , 全是白框框,就代表這時候整個mask矩陣都是0 , 所有的表徵都相互可見 . 

Sequence-to-Sequence LM : 我們都知道 , seq-to-seq的場景中語料是一對兒, 有個source有個target , 在source裏的基本字符單位對互相都可見 , 但是在target裏面的基本字符單位只能見到自己和自己左邊的基本字符單位 , 舉個栗子來說的話 ,比如 “[SOS]你 瞅 啥 [EOS] 瞅 你 咋 滴 [EOS]” 這句話如果喂進去的話(source是你瞅啥,target是瞅你咋滴) , 那source的[SOS], 你 , 瞅 , 啥 ,[EOS]他們五個互相可見 , target 裏的 “咋” 就只能看見source裏的所有字符和自己以及自己左邊的倆字兒 : 瞅 , 你 .咋 . 還是擡頭看麼 , 圖也畫的很清楚了 , s1是source , s2是target. 

 

2.4.更具體一些?

 

那我們現在知道了他一共有三種不同的完形填空手段, 那這是一個模型呀 , 怎麼用這三種完形填空的手段呢? 在論文中作者是這樣描述的 , 在一個訓練batch中 , 三分之一的時間用Bidirectional LM ,三分之一的時間用Sequence-to-Sequence LM, 然後從左到右的單向和從右到左的單向各佔六分之一的時間 . 同時爲了和bert[large]進行公平比較, 他的模型結構和bert[large]相同, 激活參數用的gelu, 具體來說就是24層的transformer , 每層1024個節點, 使用了16個自注意力頭 ,大概一共340M左右,他的參數初始化自bert[large],然後使用的English Wikipedia2 和 BookCorpus 進行的模型的預訓練 , 詞彙量大小是28996個 , 語料最大長度是512 , 他隨機選取mask的機率是15%,在這些選出的mask中, 有80%的是直接替換成[MASK]字符, 有10%的是替換成隨機基本字符單位,剩下的10%不動他, 讓他保持原樣. 此外,還有一個手段, 就是在替換mask時, 80%的機率是隻替換一個基本字符單位 , 還有20%的機率是替換一個二元組或者三元組.

更細緻的訓練參數就不說了,大家感興趣可以看論文的2.4最後一段 , 包括一些更詳細的預訓練參數和他的訓練配置.

 

2.5. 如何微調?

這裏就大致插一嘴 , 具體有想法的還是得去看代碼 , 粗略的跑了一下 ,按指示來可以跑

自然語言理解任務比如文本分類,就和bert用法一樣, 把UNILM當成一個雙向的transformer編碼器, 後面直接跟個softmax就行.

自然語言生成任務比如seq-to-seq, 就和他預訓練的時候差不多 , 都是隨機mask然後預測麼 , [EOS]也是可以mask的,讓模型學學啥時候該結束生成.

 

3.關於環境的心路歷程

首先聲明一下,我是新手,所以搞這個環境搞了一整天,如果是老鳥的話我覺得這裏可以跳過去.

系統是16.04.1-Ubuntu , 顯卡是GTX 1080 ,

本來系統上裝的nvidia驅動是387的,cuda是9.0 

但是這個UNILM需要的cuda10纔行 , cuda比較低的話會在開始跑epoch的時候core dumped , 而且pytorch不能高過1.2.0

他推薦的是使用docker-gpu來搞, 這個也好整, 可自行谷歌必應百度

那就聽人家的話用docker-gpu吧 , 但是雖然docker-gpu裏可以用不同版本的cuda和cudnn , 但是顯卡驅動得裝在容器外吧,

所以我就遇到了第一個難題 , 387的顯卡驅動顯然帶動不了cuda10 ,然而unilm要求的環境就是cuda10.0-cudnn7.5,

沒辦法,卸載nvidia驅動吧, 但是服務器又不是我一人兒用是不是,還得保證機器上裝的cuda是9吧,

據我一通操作之後,頻繁碰壁之後, 總結出以下升級顯卡驅動,系統cuda版本照舊的操作步驟(可以在docker裏用高版本的cuda)

1.執行 sudo apt-get purge nvidia* 卸載nvidia驅動

2. 如果你之前是有一套舊的nvidia驅動和cuda的話這時候他會提示你還有一堆東西沒用了,要你執行一個好像autoremove的命令,這個你得聽話執行,要不然之後會有問題,我就是沒執行這個命令浪費了好長時間

3.到你的cuda目錄裏的bin目錄下找卸載腳本, 把cuda給幹掉

4.好了,這個時候咱們的環境應該就是乾淨, 接下來有兩種方式可以安裝顯卡驅動, 分別是apt-get和ubuntu-drivers , 後者會自動安裝最新的,有人說是用前者會有問題,但是因爲我不想裝最新的,就使用了apt-get,最後看來也沒出啥問題,兩種安裝方法分別是:

sudo apt-get upgrade

sudo apt-get update

sudo apt-get install nvidia-418(看你想要什麼版本了)

ubuntu-drivers devices

sudo ubuntu-drivers autoinstall

 

5.然後現在我們就有了顯卡驅動了,該安裝cuda了,cuda的安裝可以去nvidia官網上下文件,也可以直接用apt-get,兩種方法我都試過了,然而去官網上下載run文件安裝讓我始終沒有辦法在nvidia-418下用上cuda9,這種方式自行谷歌必應百度吧,不多說了.然而apt-get install cuda-9-0 當時嘗試的也不行, 正當我要崩潰回退nvidia版本的時候發現了個帖子:

https://devtalk.nvidia.com/default/topic/1042638/linux/is-nvidia-driver-410-57-incompatible-with-cuda-9-0/

啊,這個時候不能裝cuda,要裝cuda-toolkit

簡直就是提壺灌頂 , 瀟灑的敲下 apt-get install cuda-toolkit-9-0 ,

完美收官

6.安裝cudnn也就不用多說了吧,網上一堆堆教程,不用在csdn上花幣下載,直接去nvidia官網註冊個賬號下載就行,不算慢

7.然後你就可以用他的github上的教程了, 傳送門: https://github.com/microsoft/unilm

8.然而當你開始快樂的跑demo的時候是不是發現他竟然還要下載東西? 而且很慢 ! 這個時候你可以直接去下載下來bert-larger模型,放在本地,然後在這裏把路徑改成你的本地路徑,就OK了,當然tokenization.py裏也有這麼個類似的vocab,不過這個小,整不整無所謂.

 

4.如何微調和使用?

這個,就不放在這裏說了吧, 如果有需要的話再加一篇代碼分析, 不過沒有中文的預訓練模型 ,unilm感覺還不太具備很好的適用性 , 暫時先不去擼他的代碼了就

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章