huggingface NLP工具包教程1:Transformers模型
原文:TRANSFORMER MODELS
本課程會通過 Hugging Face 生態系統中的一些工具包,包括 Transformers, Datasets, Tokenizers, Accelerate 和 Hugging Face Hub。
課程簡介如下:
- 第 1 章至第 4 章將介紹 Transformers 庫。包括 Transformer 模型的工作原理,如何使用 Hugging Face Hub 中的模型,如何在數據集上對其進行微調,并在 Hub 上分享自己的模型。
- 第 5 章至第 8 章介紹了 Datasets 庫和 Tokenizer 庫,這都是 NLP 任務必須的環節。完成這部分的介紹之后,結合 Transformers 庫,就可以自己解決常見的NLP問題了。
- 第 9 章至第 12 章不只包含 NLP ,將介紹如何使用 Transformer 模型來處理語音和視覺中的任務。還會介紹如何構建和共享模型,并針對生產環境優化對其進行優化。
安裝
直接使用 pip 安裝:
pip install transformers
這樣安裝得到的是輕量版本的 transformers 庫,也就是說,沒有安裝配套的 Pytorch 和 Tensorflow。由于接下來要經常使用到它們,所以建議直接安裝開發者版本,雖然會多花費一些時間和空間,但該版本會將所以依賴項全部一起安裝:
pip install transformers[sentencepiece]
自然語言處理簡介
在進入介紹 Transformer 模型之前,這里先概述一下什么是自然語言處理以及它有什么應用。
什么是NLP?
NLP是語言學和機器學習的一個領域,試圖理解與人類語言相關的一切。NLP任務的目的不僅是單獨理解單個單詞,而且能夠理解這些單詞的上下文。以下是常見NLP任務,以及每個任務的一些示例:
- 對整個句子進行分類:獲取評論的情感,檢測電子郵件是否為垃圾郵件,確定一個句子是否語法正確,或兩個句子是否邏輯相關
- 對句子中的每個單詞進行分類:識別句子的語法成分(名詞、動詞、形容詞)或命名實體(人、位置、組織)
- 生成文本內容:用自動生成的文本完成提示,用屏蔽詞填充文本中的空白
- 從文本中提取答案:給定問題和上下文,根據上下文中提供的信息提取問題的答案
- 從輸入文本生成新句子:將文本翻譯成另一種語言,文本摘要
NLP并不局限于書面文本。它還解決了語音識別和計算機視覺方面的復雜挑戰,例如生成音頻樣本的抄本或圖像描述。
為什么它具有挑戰性?
計算機處理信息的方式與人類不同。例如,當我們讀到“我餓了”這個句子時,我們很容易理解它的意思。類似地,給定“我餓了”和“我很難過”這兩個句子,我們可以很容易地判斷它們有多相似。對于機器學習模型,這樣的任務很困難,文本處理的方式需要考慮到是否便于模型從中學習。關于如何表示文本已經有了很多研究工作,我們將在下一章中討論一些方法。
Transformer模型能做什么?
本節將介紹 Transformer 模型到底能做什么,并將使用到 transformers 庫中的第一個函數:pipeline()
。該函數是 transfomres 庫中的一個重要函數,它將模型及其必要的前處理、后處理步驟集成起來,使得我們能夠直接輸入文本,并得到答案:
from transformers import pipelineclassifier = pipeline("sentiment-analysis")
classifier("I've been waiting for a HuggingFace course my whole life.")# 輸出:
[{'label': 'POSITIVE', 'score': 0.9598047137260437}]
也支持直接傳入多個句子:
classifier(["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"]
)# 輸出:
[{'label': 'POSITIVE', 'score': 0.9598047137260437},{'label': 'NEGATIVE', 'score': 0.9994558095932007}]
默認情況下,pipeline 會選擇一個特定的預訓練模型,該模型在英文的情感分析數據集上經過了微調。當我們常見 classifier
對象時,會下載并緩存模型,緩存過的模型再次使用時無需重新下載。
當我們將一段文本傳給 pipeline 時,主要包括了三個步驟:
- 通過預處理,將文本轉換為模型能夠理解的形式
- 將預處理過后的輸入傳遞給模型
- 模型的預測結果經過后處理,得到人類能夠理解的輸出
目前支持的 pipelines 如下:
feature-extraction
(得到文本的向量表示)fill-mask
ner
(命名實體識別)sentiment-analysis
summarization
text-generation
translation
zero-shot-classification
下面介紹其中幾個。
Zero-shot classification
我們將從一個比較難的任務開始:對未標記的文本進行分類。這中場景在現實中很常見,因為給文本打標簽通常很耗時,且需要領域專業知識。zero-shot-classification pipeline 很強大:我們可以指定用于分類的標簽,不依賴于預訓練模型的標簽。剛才已經介紹過模型如何將句子情感分類為正向或負向,而 zero-shot-classification 可以使用任何我們指定的標簽對文本進行分類。
from transformers import pipelineclassifier = pipeline("zero-shot-classification")
classifier("This is a course about the Transformers library",candidate_labels=["education", "politics", "business"],
)# 輸出:
{'sequence': 'This is a course about the Transformers library','labels': ['education', 'business', 'politics'],'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]}
zero-shot ,顧名思義,我們不需要在自己的數據上微調模型。它可以直接返回我們想要的標簽列表的概率分數。
text-generation
現在我們來看一下如何使用 pipeline 進行文本生成。主要的思想就是由我們提供一個 prompt(提示詞),模型會自動補全(續寫)prompt 后面的部分。文本生成具有隨機性,所以我們輸入同樣的 prompt 得到不同的結果是正常的。
from transformers import pipelinegenerator = pipeline("text-generation")
generator("In this course, we will teach you how to")
Copied# 輸出:
[{'generated_text': 'In this course, we will teach you how to understand and use ''data flow and data interchange when handling user data. We ''will be working with one or more of the most commonly used ''data flows — data flows of various types, as seen by the ''HTTP'}]
可以通過 num_return_sequences
參數來控制生成不同序列的個數,通過 max_length
參數來控制生成文本的總長度。
在pipeline中使用Hub中的模型
在之前的例子中,都是使用 pipeline 中對應各個任務默認的模型,實際上在使用 pipeline 時,我們也可以選擇 Hub 中的模型。比如,對于文本生成,到 Model Hub 中,點擊左側對應 tag 來查看某個特定任務的模型,也就是來到這樣一個頁面。
這里我們使用 distilgpt2 模型,以下是在 pipeline 中加載制定模型的方式:
from transformers import pipelinegenerator = pipeline("text-generation", model="distilgpt2")
generator("In this course, we will teach you how to",max_length=30,num_return_sequences=2,
)# 輸出:
[{'generated_text': 'In this course, we will teach you how to manipulate the world and ''move your mental and physical capabilities to your advantage.'},{'generated_text': 'In this course, we will teach you how to become an expert and ''practice realtime, and with a hands on experience on both real ''time and real'}]
還可以通過制定語言 tag 來進一步搜索想要的模型,然后選擇一個可以生成其他語言的模型。Hub 中甚至有一些權重文件是支持多種語言的。
在選定一個模型之后,會有一個小部件來直接在線嘗試。這樣,就可以在下載模型之前快速測試其功能。在瀏覽器中直接測試模型能力使用的是 Inference API,可以在 Hugging Face website 查看。在該頁面,可以直接與模型交互,輸入一些自己的文本,然后觀察模型處理輸入數據的過程。Inference API 也有付費版本,可以在 定價頁面 查看。
Mask filling
接下來要介紹的 pipeline 是 fill-mask
。該任務是要填充給定文本的空白部分:
from transformers import pipelineunmasker = pipeline("fill-mask")
unmasker("This course will teach you all about <mask> models.", top_k=2)# 輸出:
[{'sequence': 'This course will teach you all about mathematical models.','score': 0.19619831442832947,'token': 30412,'token_str': ' mathematical'},{'sequence': 'This course will teach you all about computational models.','score': 0.04052725434303284,'token': 38163,'token_str': ' computational'}]
top_k
參數指定返回的結果的種類數。需要注意的是,該模型填充了一個特殊的 <mask>
token,它通常被稱為 mask token。其他的 mask-filling 模型可能具有不同的 mask token,因此在使用其他模型時,需要先確定對應模型的 mask token 是什么。
Named entity recognition
在命名實體識別(NER)任務中,模型需要找到輸入文本中哪些部分是實體,如人名,地名,機構名等。
from transformers import pipelinener = pipeline("ner", grouped_entities=True)
ner("My name is Sylvain and I work at Hugging Face in Brooklyn.")# 輸出:
[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57}
]
上例中模型準確識別出了 Sylvain 是一個人名(PER),Hugging Face 是一個機構名(ORG),Brooklyn 是一個地名(LOC)。
我們在 pipeline 創建函數中傳入參數 grouped_entities=True
,告訴 pipeline 將句子中對應于同一實體的部分重新組合在一起:在上例中,模型將 “Hugging” 和 “Face” 正確地分組為一個機構名,盡管實體名由多個單詞組成。事實上,預處理可能會將一些單詞分割成更小的部分。例如,Sylvain 被分成四部分:S、##yl、##va、##in。在后處理步驟中,pipeline 會將它們重新組合成正確的單詞。
Question answering
問答任務會根據給定文本的信息回答問題。
from transformers import pipelinequestion_answerer = pipeline("question-answering")
question_answerer(question="Where do I work?",context="My name is Sylvain and I work at Hugging Face in Brooklyn",
)# 輸出:
{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'}
注意該 pipeline 是通過在給定文本中抽取部分信息作為答案,而非生成答案。
Summarization
摘要任務是將一個文本縮減為一個較短的文本,同時保留文本中的所有(或大部分)重要內容。
from transformers import pipelinesummarizer = pipeline("summarization")
summarizer("""America has changed dramatically during recent years. Not only has the number of graduates in traditional engineering disciplines such as mechanical, civil, electrical, chemical, and aeronautical engineering declined, but in most of the premier American universities engineering curricula now concentrate on and encourage largely the study of engineering science. As a result, there are declining offerings in engineering subjects dealing with infrastructure, the environment, and related issues, and greater concentration on high technology subjects, largely supporting increasingly complex scientific developments. While the latter is important, it should not be at the expense of more traditional engineering.Rapidly developing economies such as China and India, as well as other industrial countries in Europe and Asia, continue to encourage and advance the teaching of engineering. Both China and India, respectively, graduate six and eight times as many traditional engineers as does the United States. Other industrial countries at minimum maintain their output, while America suffers an increasingly serious decline in the number of engineering graduates and a lack of well-educated engineers.
"""
)# 輸出:
[{'summary_text': ' America has changed dramatically during recent years . The ''number of engineering graduates in the U.S. has declined in ''traditional engineering disciplines such as mechanical, civil '', electrical, chemical, and aeronautical engineering . Rapidly ''developing economies such as China and India, as well as other ''industrial countries in Europe and Asia, continue to encourage ''and advance engineering .'}]
像文本生成一樣,也可以指定結果的最大長度或最小長度。
Translation
翻譯任務,我們可以通過在任務名中指定語言對(如 translation_en_to_fr
)來使用默認的模型,但是更方便的方式是從 Model Hub 中挑選模型。這里以法語翻英語為例:
from transformers import pipelinetranslator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en")
translator("Ce cours est produit par Hugging Face.")# 輸出:
[{'translation_text': 'This course is produced by Hugging Face.'}]
同樣可以指定最大長度和最小長度。
目前展示的 pipeline 主要是介紹一些用法。它們是為特定任務而設計的,無法是用它們的變體。在下一節中,將介紹 pipeline()
函數內部的內容以及如何自定義其行為。
Transformers如何工作
本節將從較高的視角來概覽一下 Transformer 模型的架構。
Transformer歷史
下圖是 NLP 領域 Transformer 模型歷史的一些參考節點
Transformer 架構最早于 2017 年 6 月被提出,最初被用于解決機器翻譯任務。隨后有一系列頗具影響力的工作:
- June 2018: GPT,第一個 Transformer 預訓練模型,可以在各種 NLP 任務上微調
- October 2018: BERT, 另一個大型預訓練模型,BERT 用于產生更好的句子表示
- February 2019: GPT-2,升級版(并且更大的)GPT,由于倫理道德的原因,并未直接公開
- October 2019: DistilBERT,一個蒸餾版本的 BERT,速度快了60%,內存節省了40%,并保持了 BERT 97% 的性能
- October 2019: BART and T5,兩個大型預訓練模型,它們使用了與原始 Transformer 模型相同的架構
- May 2020, GPT-3,GPT-2 的更大版本,不需要微調,就可以在許多任務上表現得很好,這稱為零樣本學習(zero-shot learning)
當然,除了這個列表之中的模型還有許多相關工作,這里列出只是為了突出幾種不同類型的Transformer模型。大體上,它們可以分為三類:
- GPT-like,也稱為自回歸式 Transformer 模型
- BERT-like,也稱為自編碼式 Transformer 模型
- BART/T5-like,也稱為 seq2seq 式 Transformer 模型
稍后將更為詳細地介紹這三類模型。
Transformer是語言模型
上述所有 Transformer 模型(GPT、BERT、BART、T5等)都是作為語言模型(Language Model)進行訓練。也就是說,他們以自監督的方式在大量無標簽文本上進行訓練。自監督學習是一種訓練方式,其中目標函數是根據模型的輸入自動生成的。即不需要人類來標記數據。
這類模型建立了對語言的統計理解,但一般不能直接用于實際任務。因此,一般的預訓練模型會經歷一個稱為遷移學習的過程。在這個過程中,模型通過有監督的方式進行微調,即在給定任務上使用帶人工標簽的數據進行訓練。自監督訓練任務的一個例子是,在閱讀了前面 n 個單詞之后,預測句子中的下一個單詞。這被稱為 causal language modeling,因為輸出依賴于過去和現在的輸入,而不是未來的輸入。
另一個語言模型的例子是 masked language modeling,它需要預測句子中被掩碼遮蓋的單詞。
Transformer是大模型
除了極少數以輕量化為目的的 Transformer 模型(如 DistilBERT),一個通用的提高模型性能的策略是同時增大模型的尺寸和數據的規模(大力出奇跡)。
遺憾的是,訓練模型,尤其是大模型,需要大量數據。這在時間和計算資源方面變得非常昂貴。它甚至轉化為環境影響,如下圖所示。
這是一個由一個團隊領導的(非常大的)模型項目,該團隊試圖減少預培訓對環境的影響。運行大量試驗以獲得最佳超參數的 footprints 將更大。想象一下,如果每次一個研究團隊、一個學生組織或一家公司想要訓練一個模型,它都會從頭開始。這將導致巨大的、不必要的全球成本!這就是為什么共享語言模型是至關重要的:共享經過訓練的權重并在已經訓練的權重基礎上構建,可以降低社區的總體計算成本和 carbon footprint。
預訓練(Pretraining)是指從頭訓練一個模型,在開始時權重會隨機初始化,也不會有任何的先驗知識。預訓練通常需要在非常大規模的語料庫數據上進行,通常持續數周。
微調(Fine-tuning),是指在模型完成預訓練之后進行的訓練過程。在開始微調之前,首先需要拿到一個大型的、預訓練過的語言模型,然后再在自己特定的數據集上進行有監督訓練。為什么不直接在最終的任務上進行訓練呢?有以下原因:
- 預訓練模型已經在與微調數據集相似的數據集上進行了訓練。因此,微調過程能夠利用初始模型在預訓練期間獲得的知識(例如,對于NLP問題,預訓練模型將對任務所使用的語言有某種統計理解)。
- 由于模型已經在大規模數據上進行過預訓練,微調只需要較少的數據就能得到不錯的結果。
- 另外,訓練所需要的時間和資源也降低不少。
例如,可以利用一個經過英語訓練的預訓練模型,然后在 arXiv 語料庫上對其進行微調,從而形成一個基于科學/研究的模型。微調只需要很少的數據量:預訓練模型獲得的知識是 “遷移的”(transfered),因此稱為轉移學習(transfer learning)。
因此,微調模型具有更低的時間、數據、財務和環境成本。迭代不同的微調方案也更快、更容易,因為訓練比完全預訓練約束更少。這個過程也會比從頭開始的訓練取得更好的結果(除非有大量的數據),這就是為什么要利用預訓練模型——一個盡可能接近我們手頭任務的模型——并對其進行微調。
整體架構
本節將簡要介紹 Transformer 模型的整體架構。
引言
- 編碼器(左側):編碼器接收輸入并構建出輸入的表示(特征)。即,編碼器通過訓練來試圖理解輸入
- 解碼器(右側):解碼器使用編碼器得到的表示(特征)和另外一些輸入,來生成目標序列。即,解碼器通過訓練來生成輸出。
上述部分都可以獨立使用,具體使用哪一部分取決于任務:
- 僅有編碼器的模型:適合于需要理解輸入的任務,比如句子分類和命名實體識別。
- 僅有解碼器的模型:適合于生成式的任務,比如文本生成。
- 編碼器-解碼器模型(或者稱為 seq2seq 模型):適合于生成式,且也需要理解輸入的任務,比如機器翻譯和文本摘要。
后面會展開講。
注意力層
Transformer 模型的一個關鍵結構是注意力層。事實上,正如提出 Transformer 架構的論文的標題所說:“Attention is all you need”!我們將在本課程的稍后部分探討注意力層的細節。現在,從整體上來理解:該層將告訴模型在處理每個單詞的表示時,要特別注意序列中的某些單詞(并或多或少忽略其他單詞)。
舉個例子,在英語翻法語的任務中,給定輸入“You like this course”,翻譯模型還需要關注相鄰單詞 “You”,以獲得單詞 “like” 的正確翻譯,因為在法語中,動詞“like” 的變化取決于主語。然而,句子的其余部分對該詞的翻譯沒有幫助。同樣,在翻譯 “this” 時,模型需要注意單詞 “course”,因為 “this“ 的翻譯方式不同,取決于相關名詞是陽性還是陰性。同樣,句子中的其他單詞對于 “this” 的翻譯也無關緊要。對于更復雜的句子(以及更復雜的語法規則),該模型需要特別注意可能出現在句子中更遠的單詞,從而正確翻譯每個單詞。
同樣的概念適用于與自然語言相關的任何任務:一個單詞本身就有意義,但這種意義受到上下文的深刻影響,上下文可以是被研究單詞之前或之后的任何其他單詞。
現在我們已經整體上了解了注意力層是什么,那么讓我們來仔細看看Transformer架構。
原始架構
Transformer 架構最初設計用于機器翻譯。在訓練期間,編碼器接收特定語言的輸入(句子),而解碼器接收所需目標語言的相同句子。在編碼器中,注意力層可以使用句子中的所有單詞(因為,正如我們剛才看到的,給定單詞的翻譯可能取決于句子中的前后內容)。然而,解碼器按順序工作,只能注意它已經翻譯的句子中的單詞(因此,只有當前生成的單詞之前的單詞)。例如,當我們預測了翻譯目標的前三個單詞時,我們將它們提供給解碼器,解碼器隨后使用編碼器的所有輸入來嘗試預測第四個單詞。
為了在訓練過程中加快速度(訓練時模型能夠看到目標句子),會將整個目標句子輸入給解碼器,但不允許它使用后面的單詞(如果它在嘗試預測位置 2 的單詞時能夠訪問位置 2 的詞,那就不要預測了)。例如,當試圖預測第四個單詞時,注意力層只能訪問位置 1 到 3 的單詞。原始的 Transformer 架構如下所示,編碼器在左側,解碼器在右側:
注意,解碼器中的第一個注意力層關注解碼器的所有(過去的)輸入,但第二個注意力層使用編碼器的輸出。因此,它可以根據整個輸入句子,來預測當前單詞。這非常有用,因為不同的語言可能有語法規則,將單詞按不同的順序排列,或者句子后面提供的上下文可能有助于確定給定單詞的最佳翻譯。注意掩碼也可以在編碼器/解碼器中使用,以防止模型注意某些特殊單詞。例如,當將句子分批在一起時,用于使所有輸入具有相同長度的特殊填充詞。
Architectures與checkpoints
當我們在本課程中深入研究 Transformer 模型時,將反復提到 architecture 和 checkpoints 以及 model。這些術語的含義略有不同:
- architecture:這是模型的結構——每個層的定義以及模型中發生的每個操作。
- checkpoints:這些是將在給定架構中加載的權重。
- model:這是一個總括術語,不像 “architecture” 或 “checkpoints” 那么精確。它可以同時表示兩者。
例如,BERT是一種 architecture,而BERT-base-cased(谷歌團隊 BERT 的第一個版本訓練的一組權重)是一個 checkpoints。然而,人們可以說 “BERT model” 和“BERT-base-cased model”
Encoder模型
編碼器模型僅使用 Transformer 的編碼器。在每個階段,注意力層都可以訪問初始句子中的所有單詞。這些模型通常具有 “雙向” 關注的特點,通常稱為自編碼模型。這些模型的預訓練通常圍繞著以某種方式破壞給定的句子(例如,通過掩碼掉其中的隨機單詞),并讓模型尋找或重建初始句子。編碼器模型最適合于需要理解完整句子的任務,例如句子分類、命名實體識別(以及更一般的單詞分類)和抽取式問答。
這一系列模型的代表包括:
- ALBERT
- BERT
- DistilBERT
- ELECTRA
- RoBERTa
Decoder模型
解碼器模型僅使用 Transformer 的解碼器。在每個階段,對于給定的單詞,注意力層只能訪問句子中位于其前面的單詞。這些模型通常稱為自回歸模型。解碼器模型的預訓練通常圍繞著預測句子中的下一個單詞。這些模型最適合于涉及文本生成的任務。
這一系列模型的代表包括:
- CTRL
- GPT
- GPT-2
- Transformer XL
sequence-to-sequence模型
編碼器-解碼器模型(也稱為 sequence-to-sequence 模型)使用 Transformer 架構的兩部分。在每個階段,編碼器的注意力層可以訪問輸入句子中的所有單詞,而解碼器的注意力層只能訪問位于當前單詞之前的單詞。這類模型的預訓練可以使用編碼器或解碼器模型的目標來完成,但通常涉及一些更復雜的事情。例如,T5通過用一個掩碼特殊詞替換隨機的文本跨度(可以包含幾個單詞)來進行預訓練,其目的是預測該掩碼詞所替換的文本。seq2seq 模型最適合于根據給定輸入生成新句子的任務,例如文本摘要、機器翻譯或生成性問答。
這一系列模型的代表包括:
- BART
- mBART
- Marian
- T5
局限性與偏見
如果想要在生產中使用預訓練模型或微調版本,請注意,盡管這些模型是強大的工具,但它們也有局限性。其中最大的一點是,為了能夠對大量數據進行預培訓,研究人員通常會收集所有他們能找到的內容,從互聯網上獲得最好的和最壞的信息。
舉個例子,我們回到使用 BERT 模型的 fill-mask pipeline 的示例:
from transformers import pipelineunmasker = pipeline("fill-mask", model="bert-base-uncased")
result = unmasker("This man works as a [MASK].")
print([r["token_str"] for r in result])result = unmasker("This woman works as a [MASK].")
print([r["token_str"] for r in result])# 輸出:
['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic']
['nurse', 'waitress', 'teacher', 'maid', 'prostitute']
當要填寫這兩個句子中缺失的單詞時,模型只給出了一個性別無關的答案(waiter/waitress)。其他的職業通常與一個特定的性別相關,prostitute (妓女)在與“woman” 和 “work” 相關的前五位可能性中名列前茅。盡管 BERT 是少數幾個數據不來自互聯網的Transformer模型之一,而是使用比較中立的數據(它是根據英語維基百科和BookCorpus數據集訓練的),他還是會出現這種情況。因此,在使用這些工具時,需要記住,這些預訓練模型很容易產生性別歧視、種族主義或同性戀內容。根據標注數據對模型進行微調不會使這種固有偏見消失。