BERT原理解讀及HuggingFace Transformers微調入門

2022-01-02 皮皮魯的科技星球

本文同時發布於我的個人網站,公式圖片顯示效果更好,歡迎訪問:https://lulaoshi.info/machine-learning/attention/bert

自BERT(Bidirectional Encoder Representations from Transformer)[1]出現後,NLP界開啟了一個全新的範式。本文主要介紹BERT的原理,以及如何使用HuggingFace提供的 transformers 庫完成基於BERT的微調任務。

預訓練

BERT在一個較大的語料上進行預訓練(Pre-train)。預訓練主要是在數據和算力充足的條件下,訓練一個大模型,在其他任務上可以利用預訓練好的模型進行微調(Fine-tune)。

訓練目標

BERT使用了維基百科等語料庫數據,共幾十GB,這是一個龐大的語料庫。對於一個GB級的語料庫,僱傭人力進行標註成本極高。BERT使用了兩個巧妙方法來無監督地訓練模型:Masked Language Modeling和Next Sentence Prediction。這兩個方法可以無需花費時間和人力標註數據,以較低成本無監督地得到訓練數據。圖1就是一個輸入輸出樣例。

對於Masked Language Modeling,給定一些輸入句子(圖1中最下面的輸入層),BERT將輸入句子中的一些單詞蓋住(圖1中Masked層),經過中間的詞向量和BERT層後,BERT的目標是讓模型能夠預測那些剛剛被蓋住的詞。還記得英語考試中,我們經常遇到「完形填空」題型嗎?能把完形填空做對,說明已經理解了文章背後的語言邏輯。BERT的Masked Language Modeling本質上就是在做「完形填空」:預訓練時,先將一部分詞隨機地蓋住,經過模型的擬合,如果能夠很好地預測那些蓋住的詞,模型就學到了文本的內在邏輯。

   圖1 BERT預訓練的輸入和輸出

除了「完形填空」,BERT還需要做Next Sentence Prediction任務:預測句子B是否為句子A的下一句。Next Sentence Prediction有點像英語考試中的「段落排序」題,只不過簡化到只考慮兩句話。如果模型無法正確地基於當前句子預測Next Sentence,而是生硬地把兩個不相關的句子拼到一起,兩個句子在語義上是毫不相關的,說明模型沒有讀懂文本背後的意思。

詞向量

在基於深度學習的NLP方法中,文本中的詞通常都用一維向量來表示。某兩個詞向量的 Cosine 距離較小,說明兩個詞在語義上相似。

詞向量一般由Token轉換而成。英文中,一個句子中的詞由空格、句號等標點隔開,我們很容易從句子中獲得詞。英文的詞通常有前綴、後綴、詞根等,在獲得英文的詞後,還需要抽出詞根,比如圖1所展示的,將「playing」切分為「play」和「##ing」。如果不對英文詞進行類似詞根抽取,詞表過大,不容易擬合。對於英文,「play」和「##ing」分別對應兩個Token。

中文一般由多個字組成一個詞,傳統的中文文本任務通常使用一些分詞工具,得到嚴格意義上的詞。在原始的BERT中,對於中文,並沒有使用分詞工具,而是直接以字為粒度得到詞向量的。所以,原始的中文BERT(bert-base-chinese)輸入到BERT模型的是字向量,Token就是字。後續有專門的研究去探討,是否應該對中文進行必要的分詞,以詞的形式進行切分,得到向量放入BERT模型。

為了方面說明,本文不明確區分字向量還是詞向量,都統稱為詞向量。

我們首先需要將文本中每個Token都轉換成一維詞向量。假如詞向量的維度為hidden_size,句子的Token長度為seq_len,或者說句子共包含seq_len個Token,那麼上圖中,輸入就是seq_len * hidden_size。再加上batch_size,那麼輸入就是batch_size * seq_len * hidden_size。上圖只展示了一個樣本,未體現出batch_size,或者可以理解成batch_size = 1,即每次只處理一條文本。為便於理解,本文的圖解中不考慮batch_size這個維度,實際模型訓練時,batch_size通常大於1。

詞向量經過BERT模型一系列複雜的轉換後,模型最後仍然以詞向量的形式輸出,用以對文本進行語義表示。輸入的詞向量是seq_len * hidden_size,句子共seq_len個Token,將每個Token都轉換成詞向量,送入BERT模型。經過BERT模型後,得到的輸出仍然是seq_len * hidden_size維度。輸出仍然是seq_len的長度,其中輸出的i 個位置(0 < i  < seq_len)的詞向量,表示經過了擬合後的第i個Token的語義表示。後續可以用輸出中每個位置的詞向量來進行一些其他任務,比如命名實體識別等。

除了使用Masked方法故意蓋住一些詞外,BERT還加了一些特殊的符號:[CLS]和[SEP]。[CLS]用在句首,是句子序列中i = 0位置的Token。BERT認為輸出序列的i = 0位置的Token對應的詞向量包含了整個句子的信息,可對整個句子進行分類。[SEP]用在分割前後兩個句子上。

微調

經過預訓練後,得到的模型可以用來微調各類任務。

單文本分類任務。剛才提到,BERT模型在文本前插入一個[CLS]符號,並將該符號對應的輸出向量作為整篇文本的語義表示,用於文本分類,如圖2所示。對於[CLS]符號,可以理解為:與文本中已有的其它字/詞相比,這個無明顯語義信息的符號會更「公平」地融合文本中各個字/詞的語義信息。    圖2 單文本分類語句對分類任務。語句對分類任務的實際應用場景包括:問答(判斷一個問題與一個答案是否匹配)、語句匹配(兩句話是否表達同一個意思)等。對於該任務,BERT模型除了添加[CLS]符號並將對應的輸出作為文本的語義表示,輸入兩句話之間用[SEP]符號作分割。    圖3 語句對分類序列標註任務。序列標註任務的實際應用場景包括:命名實體識別、中文分詞、新詞發現(標註每個字是詞的首字、中間字或末字)、答案抽取(答案的起止位置)等。對於該任務,BERT模型利用文本中每個Token對應的輸出向量對該Token進行標註(分類),如下圖所示(B(Begin)、I(Inside)、E(End)分別表示一個詞的第一個字、中間字和最後一個字)。    圖4 序列標註模型結構

Transformer是BERT的核心模塊,Attention注意力機制又是Transformer中最關鍵的部分。前一篇文章,我們介紹了Attention注意力機制和Transformer,這裡不再贅述。BERT用到的主要是Transformer的Encoder,沒有使用Transformer Decoder。

把多個Transformer Encoder組裝起來,就構成了BERT。在論文中,作者分別用12個和24個Transformer Encoder組裝了兩套BERT模型,兩套模型的參數總數分別為110M和340M。

   圖5 BERT中的Transformer EncoderHuggingFace Transformers

使用BERT和其他各類Transformer模型,繞不開HuggingFace(https://huggingface.co/)提供的Transformers生態。HuggingFace提供了各類BERT的API(transformers庫)、訓練好的模型(HuggingFace Hub)還有數據集(datasets)。最初,HuggingFace用PyTorch實現了BERT,並提供了預訓練的模型,後來。越來越多的人直接使用HuggingFace提供好的模型進行微調,將自己的模型共享到HuggingFace社區。HuggingFace的社區越來越龐大,不僅覆蓋了PyTorch版,還提供TensorFlow版,主流的預訓練模型都會提交到HuggingFace社區,供其他人使用。

使用transformers庫進行微調,主要包括:

Tokenizer:使用提供好的Tokenizer對原始文本處理,得到Token序列;構建模型:在提供好的模型結構上,增加下遊任務所需預測接口,構建所需模型;Tokenizer

下面兩行代碼會創建 BertTokenizer,並將所需的詞表加載進來。首次使用這個模型時,transformers 會幫我們將模型從HuggingFace Hub下載到本地。

>>> from transformers import BertTokenizer
>>> tokenizer = BertTokenizer.from_pretrained('bert-base-cased')

用得到的tokenizer進行分詞:

>>> encoded_input = tokenizer("我是一句話")
>>> print(encoded_input)
{'input_ids': [101, 2769, 3221, 671, 1368, 6413, 102], 
'token_type_ids': [0, 0, 0, 0, 0, 0, 0], 
'attention_mask': [1, 1, 1, 1, 1, 1, 1]}

得到的一個Python dict。其中,input_ids最容易理解,它表示的是句子中的每個Token在詞表中的索引數字。詞表(Vocabulary)是一個Token到索引數字的映射。可以使用decode()方法,將索引數字轉換為Token。

>>> tokenizer.decode(encoded_input["input_ids"])
'[CLS] 我 是 一 句 話 [SEP]'

可以看到,BertTokenizer在給原始文本處理時,自動給文本加上了[CLS]和[SEP]這兩個符號,分別對應在詞表中的索引數字為101和102。decode()之後,也將這兩個符號反向解析出來了。

token_type_ids主要用於句子對,比如下面的例子,兩個句子通過[SEP]分割,0表示Token對應的input_ids屬於第一個句子,1表示Token對應的input_ids屬於第二個句子。不是所有的模型和場景都用得上token_type_ids。

>>> encoded_input = tokenizer("您貴姓?", "免貴姓李")
>>> print(encoded_input)
{'input_ids': [101, 2644, 6586, 1998, 136, 102, 1048, 6586, 1998, 3330, 102], 
'token_type_ids': [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1], 
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}

句子通常是變長的,多個句子組成一個Batch時,attention_mask就起了至關重要的作用。

>>> batch_sentences = ["我是一句話", "我是另一句話", "我是最後一句話"]
>>> batch = tokenizer(batch_sentences, padding=True, return_tensors="pt")
>>> print(batch)
{'input_ids': 
 tensor([[ 101, 2769, 3221,  671, 1368, 6413,  102,    0,    0],
        [ 101, 2769, 3221, 1369,  671, 1368, 6413,  102,    0],
        [ 101, 2769, 3221, 3297, 1400,  671, 1368, 6413,  102]]), 
 'token_type_ids': 
 tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 0, 0, 0, 0]]), 
 'attention_mask': 
 tensor([[1, 1, 1, 1, 1, 1, 1, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1]])}

對於這種batch_size = 3的場景,不同句子的長度是不同的,padding=True表示短句子的結尾會被填充[PAD]符號,return_tensors="pt"表示返回PyTorch格式的Tensor。attention_mask告訴模型,哪些Token需要被模型關注而加入到模型訓練中,哪些Token是被填充進去的無意義的符號,模型無需關注。

Model

下面兩行代碼會創建BertModel,並將所需的模型參數加載進來。

>>> from transformers import BertModel
>>> model = BertModel.from_pretrained("bert-base-chinese")

BertModel是一個PyTorch中用來包裹網絡結構的torch.nn.Module,BertModel裡有forward()方法,forward()方法中實現了將Token轉化為詞向量,再將詞向量進行多層的Transformer Encoder的複雜變換。

forward()方法的入參有input_ids、attention_mask、token_type_ids等等,這些參數基本上是剛才Tokenizer部分的輸出。

>>> bert_output = model(input_ids=batch['input_ids'])

forward方法返回模型預測的結果,返回結果是一個tuple(torch.FloatTensor),即多個Tensor組成的tuple。tuple默認返回兩個重要的Tensor:

>>> len(bert_output)
2

last_hidden_state:輸出序列每個位置的語義向量,形狀為:(batch_size, sequence_length, hidden_size)。pooler_output:[CLS]符號對應的語義向量,經過了全連接層和tanh激活;該向量可用於下遊分類任務。下遊任務

BERT可以進行很多下遊任務,transformers庫中實現了一些下遊任務,我們也可以參考transformers中的實現,來做自己想做的任務。比如單文本分類,transformers庫提供了BertForSequenceClassification類。

class BertForSequenceClassification(BertPreTrainedModel):
    def __init__(self, config):
        super().__init__(config)
        self.num_labels = config.num_labels
        self.config = config

        self.bert = BertModel(config)
        classifier_dropout = ...
        self.dropout = nn.Dropout(classifier_dropout)
        self.classifier = nn.Linear(config.hidden_size, config.num_labels)

        ...
        
    def forward(
        ...
    ):
        ...

        outputs = self.bert(...)
        pooled_output = outputs[1]
        pooled_output = self.dropout(pooled_output)
        logits = self.classifier(pooled_output)

        ...

在這段代碼中,BertForSequenceClassification在BertModel基礎上,增加了nn.Dropout和nn.Linear層,在預測時,將BertModel的輸出放入nn.Linear,完成一個分類任務。除了BertForSequenceClassification,還有BertForQuestionAnswering用於問答,BertForTokenClassification用於序列標註,比如命名實體識別。

transformers 中的各個API還有很多其他參數設置,比如得到每一層Transformer Encoder的輸出等等,可以訪問他們的文檔(https://huggingface.co/docs/transformers/)查看使用方法。

參考資料

Devlin J, Chang M-W, Lee K, Toutanova K. BERT: pre-training of deep bidirectional transformers for language understanding. Proceedings of the 2019 Conference of the North American Chapter of the Association for Computational Linguistics: Human Language Technologies, Volume 1 (Long and Short Papers). Minneapolis, Minnesota: Association for Computational Linguistics, 2019: 4171–4186.徹底理解Google BERT(https://www.jianshu.com/p/46cb208d45c3)圖解BERT模型:從零開始構建BERT(https://cloud.tencent.com/developer/article/1389555)

相關焦點

  • 輕鬆玩轉BERT!Transformers快速上手
    現在介紹一個開源庫huggingface/transformers,在它的基礎上去做開發就會輕鬆很多。Transformers提供了數千種經過預訓練的模型,可以對100多種語言的文本執行任務,例如文本分類,信息提取,問題回答,文本摘要,翻譯,文本生成等。
  • 使用Hugging Face管道輕鬆應用NLP預訓練模型
    微調然而,預訓練的BERT模型仍然是非常通用的。為了能夠將其用於情感分析、命名實體識別、文本摘要、翻譯或其他方面,我們需要針對特定用例對模型進行微調。這最大的優點是,這種微調相對便宜:大部分的權重已經在訓練前階段完成,只需要做一次。
  • Pytorch-Transformers 1.0 發布,支持六個預訓練框架,含 27 個預...
    哪些支持PyTorch-Transformers(此前叫做pytorch-pretrained-bert)是面向自然語言處理,當前性能最高的預訓練模型開源庫。為了幫助微調這些模型,我們提供了幾種可以在微調腳本中激活的技術 run_bert_classifier.py 和 run_bert_squad.py:梯度累積(gradient-accumulation),多GPU訓練(multi-gpu training),分布式訓練(distributed training )和16- bits 訓練( 16-bits training)。
  • Transformers2.0讓你三行代碼調用語言模型,兼容TF2.0和PyTorch
    項目地址:https://github.com/huggingface/transformersTransformers 2.0 新特性像 pytorch-transformers 一樣使用方便;像 Keras 一樣功能強大和簡潔;在 NLU 和 NLG 任務上實現高性能;對教育者和實踐者的使用門檻低
  • Hugging Face官方課程來了!Transformers庫維護者之一授課,完全免費
    課程主頁:https://huggingface.co/course/chapter0?fw=pt整個系列的課程分為入門(Introduction)、進階(Diving in)和高級(Advanced),其中具體如下:入門:Transformer 模型、使用 Transformers、微調預訓練模型以及分享模型和 tokenizers;進階:Datasets 庫、Tokenizers 庫、主要 NLP 任務以及如何尋求幫助;高級:專用架構、加速訓練、自定義訓練
  • Huggingface BERT源碼詳解:應用模型與訓練優化
    BertIntermediate               2. BertOutput            3. BertEmbeddings            4.由於需要用到每個 token對應的輸出而不只是某幾個,所以這裡的BertModel不用加入 pooling 層;3.5 BertForQuestionAnswering這一模型用於解決問答任務,例如 SQuAD 任務。問答任務的輸入為問題 +(對於 BERT 只能是一個)回答組成的句子對,輸出為起始位置和結束位置用於標出回答中的具體文本。
  • 最強NLP預訓練模型庫PyTorch-Transformers正式開源!支持6個預訓練框架,27個預訓練模型
    更多優質內容請關注微信公眾號「AI 前線」(ID:ai-front) 照例先上開源地址:https://github.com/huggingface/pytorch-transformers#quick-tour官網:https://huggingface.co/pytorch-transformers
  • HuggingFace| Transformers核心源碼閱讀和實踐
    目前該庫實現的預訓練模型如下:bert-base-multilingual-uncasedbert-base-multilingual-casedbert-large-uncased-whole-word-maskingbert-large-cased-whole-word-masking上述預訓練好的模型的主要差異在於
  • Hugging Face官方NLP課程來了!Transformers庫維護者之一授課,完全免費
    課程主頁:https://huggingface.co/course/chapter0?fw=pt整個系列的課程分為入門(Introduction)、進階(Diving in)和高級(Advanced),其中具體如下:入門:Transformer 模型、使用 Transformers、微調預訓練模型以及分享模型和 tokenizers;進階:Datasets 庫、Tokenizers 庫、主要 NLP 任務以及如何尋求幫助;高級:專用架構、加速訓練、自定義訓練
  • 文本挖掘從小白到精通(十六)--- 像使用scikit-learn一樣玩轉BERT
    筆者不打算詳細討論BERT是什麼,也不打算討論它的內部機理,筆者只是想以最小的工作量向你展示如何利用Sci-kit Learn來「二次開發」bert特徵抽取器,兼顧易用性和靈活性。做好這個bert特徵抽取器以後,筆者將用一個文本情緒分類數據集來檢驗一下經封裝的Sci-kit Learn transformer的實際效果。
  • PyTorch-Transformers:最先進的自然語言處理庫(附帶python代碼)
    https://github.com/huggingface/pytorch-transformers我們可以簡單地用Python導入它並進行實驗。我對現在NLP的研發速度感到非常驚訝,每一篇新論文、每一個框架和庫都在推動著這個不可思議的強大領域的發展。
  • 基於Transformers入門自然語言處理
    我將之前零零散散的Transformer博客、講解文章進行了整理,形成了一個完整的教程叫做:learn nlp with transformers,翻譯過來是:基於Transformers入門自然語言處理。
  • 基於Transformers入門自然語言處理!
    我將之前零零散散的Transformer博客、講解文章進行了整理,形成了一個完整的教程叫做:learn nlp with transformers,翻譯過來是:基於Transformers入門自然語言處理。
  • BERT小學生級上手教程,從原理到上手全有圖示,還能直接在線運行
    現在,科技博主Jay Alammar創作了一篇《第一次使用BERT的圖形化指南》,用非常簡單清晰的方式介紹了如何上手BERT,從BERT的原理到實際操作的過程都有圖示,甚至圖比代碼都多。量子位為大家編譯搬運如下~
  • 老周帶你讀Bert文本分類代碼 (pytorch篇一)
    這次想和大家一起讀的是huggingface的pytorch-pretrained-BERT代碼examples裡的文本分類任務run_classifier。關於原始碼可以在huggingface的github中找到。
  • 微信開源推理加速工具 TurboTransformers
    在多種 CPU 和 GPU 硬體上獲得了超過 PyTorch/TensorFlow 和目前主流優化引擎(如 onnxruntime-mkldnn/onnxruntime-gpu、torch JIT、NVIDIA faster transformers)的性能表現。為 NLP 推理任務特點量身定製。
  • AI基礎:一文看懂BERT
    (黃海廣)AI 基礎:Python開發環境設置和小技巧AI 基礎:Python 簡易入門AI 基礎:Numpy 簡易入門AI 基礎:Pandas 簡易入門輸出:「真」或「假」模型架構現在您已經了解了如何使用BERT的示例,讓我們仔細了解一下他的工作原理。
  • 【BERT】Sentence-Bert論文筆記
    知乎專欄 | 自然語言處理相關論文論文全名叫做Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks;論文地址:arxiv.org/abs/1908.1008;論文代碼:https://github.com/UKPLab/ sentence-transformers
  • ...TurboTransformers,性能超越 PyTorch/TensorFlow 與主流優化引擎
    在多種 CPU 和 GPU 硬體上獲得了超過 PyTorch/TensorFlow 和目前主流優化引擎(如 onnxruntime-mkldnn/onnxruntime-gpu、torch JIT、NVIDIA faster transformers)的性能表現。 為 NLP 推理任務特點量身定製。