管道的內部
讓我們從一個完整的示例開始,看看在Chapter 1中執行以下代碼時在幕後發生了什麼
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
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}]
正如我們在Chapter 1中看到的,此管道將三個步驟組合在一起:預處理、通過模型傳遞輸入和後處理:
讓我們快速瀏覽一下這些內容。
使用分詞器進行預處理
與其他神經網絡一樣,Transformer 模型無法直接處理原始文本, 因此我們管道的第一步是將文本輸入轉換為模型能夠理解的數字。 為此,我們使用tokenizer(標記器),負責:
- 將輸入拆分為單詞、子單詞或符號(如標點符號),稱為標記(token)
- 將每個標記(token)映射到一個整數
- 添加可能對模型有用的其他輸入
所有這些預處理都需要以與模型預訓練時完全相同的方式完成,因此我們首先需要從Model Hub中下載這些信息。為此,我們使用AutoTokenizer
類及其from_pretrained()
方法。使用我們模型的檢查點名稱,它將自動獲取與模型的標記器相關聯的數據,並對其進行緩存(因此只有在您第一次運行下面的代碼時才會下載)。
因為sentiment-analysis
(情緒分析)管道的默認檢查點是distilbert-base-uncased-finetuned-sst-2-english
(你可以看到它的模型卡here),我們運行以下程序:
from transformers import AutoTokenizer
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
一旦我們有了標記器,我們就可以直接將我們的句子傳遞給它,然後我們就會得到一本字典,它可以提供給我們的模型!剩下要做的唯一一件事就是將輸入ID列表轉換為張量。
您可以使用🤗 Transformers,而不必擔心哪個 ML 框架被用作後端;它可能是 PyTorch 或 TensorFlow,或 Flax。但是,Transformers型號只接受張量作為輸入。如果這是你第一次聽說張量,你可以把它們想象成NumPy數組。NumPy數組可以是標量(0D)、向量(1D)、矩陣(2D)或具有更多維度。它實際上是張量;其他 ML 框架的張量行為類似,通常與 NumPy 數組一樣易於實例化。
要指定要返回的張量類型(PyTorch、TensorFlow 或 plain NumPy),我們使用return_tensors
參數:
raw_inputs = [
"I've been waiting for a HuggingFace course my whole life.",
"I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
print(inputs)
現在不要擔心填充和截斷;我們稍後會解釋這些。這裡要記住的主要事情是,您可以傳遞一個句子或一組句子,還可以指定要返回的張量類型(如果沒有傳遞類型,您將得到一組列表)。
以下是PyTorch張量的結果:
{
'input_ids': tensor([
[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102],
[ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0]
]),
'attention_mask': tensor([
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
])
}
輸出本身是一個包含兩個鍵的字典,input_ids
和attention_mask
。input_ids
包含兩行整數(每個句子一行),它們是每個句子中標記的唯一標記(token)。我們將在本章後面解釋什麼是attention_mask
。
瀏覽模型
我們可以像使用標記器一樣下載預訓練模型。🤗 Transformers提供了一個AutoModel
類,該類還具有from_pretrained()
方法:
from transformers import AutoModel
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
在這個代碼片段中,我們下載了之前在管道中使用的相同檢查點(它實際上應該已經被緩存),並用它實例化了一個模型。
這個架構只包含基本轉換器模塊:給定一些輸入,它輸出我們將調用的內容隱藏狀態(hidden states),亦稱特徵(features)。對於每個模型輸入,我們將檢索一個高維向量,表示Transformer模型對該輸入的上下文理解。
如果這不合理,不要擔心。我們以後再解釋。
雖然這些隱藏狀態本身可能很有用,但它們通常是模型另一部分(稱為頭部(head))的輸入。 在Chapter 1中,可以使用相同的體系結構執行不同的任務,但這些任務中的每個任務都有一個與之關聯的不同頭。
高維向量?
Transformers 模塊的向量輸出通常較大。它通常有三個維度:
- Batch size: 一次處理的序列數(在我們的示例中為2)。
- Sequence length: 序列的數值表示的長度(在我們的示例中為16)。
- Hidden size: 每個模型輸入的向量維度。
由於最後一個值,它被稱為「高維」。隱藏的大小可能非常大(768通常用於較小的型號,而在較大的型號中,這可能達到3072或更大)。
如果我們將預處理的輸入輸入到模型中,我們可以看到這一點:
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
torch.Size([2, 16, 768])
注意🤗 Transformers 模型的輸出與namedtuple
或詞典相似。您可以通過屬性(就像我們所做的那樣)或鍵(輸出["last_hidden_state"]
)訪問元素,甚至可以通過索引訪問元素,前提是您確切知道要查找的內容在哪裡(outputs[0]
)。
模型頭:數字的意義
模型頭將隱藏狀態的高維向量作為輸入,並將其投影到不同的維度。它們通常由一個或幾個線性層組成:
Transformers 模型的輸出直接發送到模型頭進行處理。
在此圖中,模型由其嵌入層和後續層表示。嵌入層將標記化輸入中的每個輸入ID轉換為表示關聯標記(token)的向量。後續層使用注意機制操縱這些向量,以生成句子的最終表示。
🤗 Transformers中有許多不同的體系結構,每種體系結構都是圍繞處理特定任務而設計的。以下是一個非詳盡的列表:
*Model
(retrieve the hidden states)*ForCausalLM
*ForMaskedLM
*ForMultipleChoice
*ForQuestionAnswering
*ForSequenceClassification
*ForTokenClassification
- 以及其他 🤗
對於我們的示例,我們需要一個帶有序列分類頭的模型(能夠將句子分類為肯定或否定)。因此,我們實際上不會使用AutoModel
類,而是使用AutoModelForSequenceClassification
:
from transformers import AutoModelForSequenceClassification
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
現在,如果我們觀察輸入的形狀,維度將低得多:模型頭將我們之前看到的高維向量作為輸入,並輸出包含兩個值的向量(每個標籤一個):
print(outputs.logits.shape)
torch.Size([2, 2])
因為我們只有兩個句子和兩個標籤,所以我們從模型中得到的結果是2 x 2的形狀。
對輸出進行後處理
我們從模型中得到的輸出值本身並不一定有意義。我們來看看,
print(outputs.logits)
tensor([[-1.5607, 1.6123],
[ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
我們的模型預測第一句為[-1.5607, 1.6123]
,第二句為[ 4.1692, -3.3464]
。這些不是概率,而是logits,即模型最後一層輸出的原始非標準化分數。要轉換為概率,它們需要經過SoftMax層(所有🤗Transformers模型輸出logits,因為用於訓練的損耗函數通常會將最後的激活函數(如SoftMax)與實際損耗函數(如交叉熵)融合):
import torch
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
tensor([[4.0195e-02, 9.5980e-01],
[9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
現在我們可以看到,模型預測第一句為[0.0402, 0.9598]
,第二句為[0.9995, 0.0005]
。這些是可識別的概率分數。
為了獲得每個位置對應的標籤,我們可以檢查模型配置的id2label
屬性(下一節將對此進行詳細介紹):
model.config.id2label
{0: 'NEGATIVE', 1: 'POSITIVE'}
現在我們可以得出結論,該模型預測了以下幾點:
- 第一句:否定:0.0402,肯定:0.9598
- 第二句:否定:0.9995,肯定:0.0005
我們已經成功地複製了管道的三個步驟:使用標記化器進行預處理、通過模型傳遞輸入以及後處理!現在,讓我們花一些時間深入瞭解這些步驟中的每一步。
✏️ 試試看! 選擇兩個(或更多)你自己的文本並在管道中運行它們。然後自己複製在這裡看到的步驟,並檢查是否獲得相同的結果!