Transformers 相關的庫

  • Transformers 核心庫: 模型加載 模型訓練
  • Tokenizer 分詞器: 對數據進行預處理,將文本轉換成模型可以理解的格式(Token序列)
  • Datasets 數據集庫: 下載公開的數據集,並且進行預處理
  • Evaluate 評估函示: 像是準確率 F1, BLEU, ROUGE 等等
  • PEFT 高校微調方法: 只訓練一小部分參數,而不是全部重新訓練,提供常用的微調方法
  • Accelerate 分布式訓練: 對訓練過程進行加速,提供了分布式訓練的方法
  • Optimum 優化加速庫: 提供了一些優化方法,比如混合精度訓練,對訓練過程進行加速
  • Gradio 可視化庫: 提供了一些可視化的方法,透過幾行程式達到快速外部交互的效果

前置環境

需要安裝以下套件:

  1. python
  2. conda
  3. pytorch
  • 以我的狀況為例,我是cuda支援的版本是12.4,這代表小於12.4的版本都可以支援cuda,建議使用pip進行安裝
  • 30xx 或是 40xx 顯卡要安裝 cu11 以上的版本

最後測試是否安裝成功

1
2
3
4
5
C:\Users\Name>python
>>> import torch
>>> torch.cuda.is_available()
True # 如果是 True 代表安裝成功
>>>

確認cuda版本

  1. 進入 nvdia 控制面板
  2. 點選系統信息
  3. 查看 cuda 版本

Transformers 安裝

執行以下指令安裝 transformers

1
2
pip install transformers datasets evaluate peft accelerate gradio optimum sentencepiece
pip install jupyterlab scikit-learn pandas matplotlib tensorboard nltk rouge

關於Pipeline

這是一個簡單的工具,可以幫助我們快速的使用模型進行預測,包含三個階段:

  1. Tokenization 數據處理: 將文本轉換成模型可以理解的格式(Token序列)
    • Raw Text ("This is apple") -> Tokenization -> Token IDs ([100, 101, 2708])
  2. Model 模型調用: 使用模型進行預測
    • [-4.3630, 4.6859]
  3. Post-processing 結果後處理: 將模型的輸出轉換成我們需要的格式
    • Pisitive: 99.89%, Negative: 0.11%

有很多不同 Pipeline 支援的任務,可以透過以下程式碼查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from transformers.pipelines import SUPPORTED_TASKS
for k, v in SUPPORTED_TASKS.items():
print(k, v["type"])

# 顯示出以下 這邊我們會以 text-classification 為例
audio-classification | audio
automatic-speech-recognition | multimodal
text-to-audio | text
feature-extraction | multimodal
text-classification | text
token-classification | text
question-answering | text
table-question-answering | text
visual-question-answering | multimodal
document-question-answering | multimodal
fill-mask | text
summarization | text
translation | text
text2text-generation | text
text-generation | text
zero-shot-classification | text
zero-shot-image-classification | multimodal
zero-shot-audio-classification | multimodal
conversational | text
image-classification | image
image-feature-extraction | image
image-segmentation | multimodal
image-to-text | multimodal
object-detection | multimodal
zero-shot-object-detection | multimodal
depth-estimation | image
video-classification | video
mask-generation | multimodal
image-to-image | image

快速上手

1
2
3
4
from transformers import * 

pipe = pipeline("text-classification") # 指定任務
pip("This is a good day") # 會印出 {'label': 'POSITIVE', 'score': 0.9998}

使用指定模型

可以前往 https://huggingface.co/models 找到模型名稱建立相對應的Pipeline。

指定任務 -> 再指定模型 -> 創建基於指定模型的 Pipeline

1
2
3
4
from transformers import * 

pipe = pipeline("text-classification", model="uer/roberta-base-finetuned-dianping-chinese")
pipe("今天是好天氣") # 會印出 {'label': 'POSITIVE', 'score': 0.9998}

預先加載模型,再創建Pipeline

一定要使用模型相對應的 Tokenizer,否則會出現錯誤

1
2
3
4
5
6
from transformers import * 

model = AutoModelForSequenceClassification.from_pretrained("uer/roberta-base-finetuned-dianping-chinese")
toeknizer = AutoTokenizer.from_pretrained("uer/roberta-base-finetuned-dianping-chinese")
pipe = pipeline("text-classification", model=model, tokenizer=tokenizer)
pipe("今天是好天氣") # 會印出 {'label': 'POSITIVE', 'score': 0.9998}

使用GPU

1
2
3
4
from transformers import * 

pipe = pipeline("text-classification") # 指定任務
print(pipe.model.device) # 會顯示 device(type='cuda', index=0)

關於Tokenizer

Tokenizer 是將文本轉換成模型可以理解的格式(Token序列),這是模型的第一步,因為模型只能理解數字,而不能理解文本。因此 Tokenizer 是非常重要的一個環節。

基本使用

  • 加載保存
    • from_pretrained: 從預訓練模型中加載 Tokenizer
    • save_pretrained: 將 Tokenizer 保存到本地
  • 句子分詞
    • tokenize: 句子分詞
    • encode: 句子轉換成 ID
  • 查看字典
    • vocab: 顯示 Tokenizer 的詞彙表
  • 索引轉換
    • convert_tokens_to_ids: 將 Token 轉換成 ID
    • convert_ids_to_tokens: 將 ID 轉換成 Token
  • 填充截斷
    • padding: 對文本進行填充
    • truncation: 對文本進行截斷

快速上手

  • 直接使用 tokenizer(sen, padding=“max_length”, max_length=10) 可以會傳以下資訊
    • offset_mapping: 會顯示每個 token 在原始文本中的位置,通常會合 word_ids 一起使用
      • 會長這樣 [(0, 0), (0, 2), (2, 5), (5, 8), (8, 11), (0, 0)]
    • word_ids: 會顯示每個 token 對應的原始文本中的單詞
      • 會長這樣 [None, 0, 1, 2, 3, None]
    • input_ids: 會顯示每個 token 對應的 ID
      • 會長這樣 [101, 0, 1, 2, 3, 102]
    • attention_mask: 會顯示每個 token 對應的注意力遮罩,哪些部分是有效的哪些是填充的
      • 會長這樣 [1, 1, 1, 1, 1, 1, 0, 0]
    • token_type_ids: 會顯示每個 token 對應的 token type
      • 會長這樣 [0, 0, 0, 0, 0, 0, 0, 0]
  • add_special_tokens: 是否加入特殊的 token,像是 [CLS], [SEP] 等等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from transformers import AutoTokenizer

# 1. 加載與保存
## 可以在 /users/.cache/huggingface/hub 下找到
tokenizer = AutoTokenizer.from_pretrained("user/tokenizer-name")
tokenizer.save_pretrained("tokenizer-name")

# 2. 分詞
tokenizer = toeknizer("今天是好天氣")
print(tokenizer) # 會印出 ['今天', '是', '好', '天氣']

# 3. 查看辭典
print(tokenizer.vocab) # 會印出 {'今天': 0, '是': 1, '好': 2, '天氣': 3}
print(tokenizer.vocab_size) # 可以查看辭典的大小

# 4. 索引轉換
ids = tokenizer.convert_tokens_to_ids("今天是好天氣")
print(ids) # 會印出 [0, 1, 2, 3]
str_sen = tokenizer.convert_ids_to_tokens([0, 1, 2, 3])
print(str_sen) # 會印出 ['今天', '是', '好', '天氣']

## 更便捷的方法
### add_special_tokens - 是否加入特殊的 token,像是 [CLS], [SEP] 等等
ids = tokenizer.encode("今天是好天氣", add_special_tokens=True)
print(ids) # 會印出 [101, 0, 1, 2, 3, 102]
str_sen = tokenizer.decode([101, 0, 1, 2, 3, 102])
print(str_sen) # 會印出 '[CLS] 今天 是 好 天氣 [SEP]'

# 5. 填充與截斷
## 太短的句子要 填充
ids = tokenizer.encode("今天是好天氣", padding="max_length", max_length=10)
print(ids) # [101, 0, 1, 2, 3, 102, 0, 0, 0, 0]

## 過長的句子要 截斷(truncation) 你會發現 special token 必須包含在裡面
ids = tokenizer.encode("今天是好天氣", truncation=True, max_length=4)
print(ids) # [101, 0, 1, 102]


# 6. 最常用的方法
inputs = tokenizer("今天是好天氣", padding="max_length", max_length=10, return_offsets_mapping=True)
print(inputs) # input_ids, attention_mask, token_type_ids, offset_mapping 都會印出來
inputs.word_ids() # 會印出 'word_ids': [None, 0, 1, 2, 3, 3, None] 因為有可能一個單字拆成多個 token 你會看到 3, 3 表示這個單字被拆成兩個 token

# 7. 處理 batch
sens = ["今天是好天氣", "明天是好天氣"]
inputs = tokenizer(sens, padding="max_length", max_length=15)
print(inputs)

特殊Tokenizer的加載

huggingface 上有很多模型不是官方的,有可能我們下載的模型,他的 Tokenizer 不在官方的 Tokenizer 中,這時候我們可以使用 trust_remote_code=True 來加載 Tokenizer,表示我們相信這個 Tokenizer 是安全的。以 https://huggingface.co/THUDM/chatglm-6b/tree/main 為例,他所使用的 Tokenizer 是 tokenization_chatglm.py 他自己實現的。

1
2
3
4
5
6
7
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True)
# 加載完後可以保存
tokenizer.save_pretrained("chatglm-6b")
# 加載回來也要添加 trust_remote_code=True
tokenizer = AutoTokenizer.from_pretrained("chatglm-6b", trust_remote_code=True)

關於 Model

模型的類型

  1. 只有 Encoder: 每個詞都能看到完整上下文
    • 常用預訓練模型: BERT, RoBERTa, ALBERT
    • 適合任務:文本分類、命名實體識別、閱讀理解
  2. 只有 Decoder: 每個詞能看到上文,但是不能看到下文
    • 常用預訓練模型: GPT, LLaMA
    • 適合任務:文本生成
  3. Encoder+Decoder: 前面的token只能看後面的,但是到了後面的token就是純粹的解碼器模型了
    • 常用預訓練模型: MART, T5, GLM, Marian, mBART
    • 適合任務:文本摘要, 翻譯

Model Head

  • 通常是連接在模型後的層,通常為1個或多個fully connected layer
  • 他負責將模型的輸出轉換成我們需要的格式,以解決不同類型的任務
  • 常見的像是 ForMaskedLMHead (遮住某個字來猜), ForSeq2SeqLMHead (翻譯), ForQuestionAnsweringHead (問答), ForSequenceClassification (分類)

快速上手

這邊主要會介紹 Model 的基本使用方法

  • 在線加載: AutoModel.from_pretrained
  • 模型下載
  • 離線加載
  • 模型參數下載

模型調用

  1. 不帶 model head 的模型調用
  2. 帶 model head 的模型調用

下載模型 AutoModel

方法1: 線上下載
直接透過 AutoModel.from_pretrained()

1
2
3
from transformers import AutoConfig, AutoModel, AutoTokenizer

model = AutoModel.from_pretrained("hf1/rbt3")

方法2: 模型下載
或是可以透過git的方式,甚至是去huggingface自己下載.bin檔案

1
2
!git clone "https://huggingface.co/hf1/rbt3"
!git lfs clone "https://huggingface.co/hf1/rbt3" --include="*.bin"

然後你就可以在本地加載了:

1
2
3
from transformers import AutoConfig, AutoModel, AutoTokenizer

model = AutoModel.from_pretrained("本地資料夾位置")

模型參數加載 AutoConfig

可以透過AutoConfig查看模型相關參數

1
2
3
4
from transformers import AutoConfig, AutoModel, AutoTokenizer

config = AutoConfig.from_pretrained("rbt3")
config.[想看的參數] # e.g config.output_attentions

模型調用 AutoTokenizer + AutoModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from transformers import AutoConfig, AutoModel, AutoTokenizer

# 先進行tokenize
sen = "今天天氣很好今天天氣很好"
tokenizer = AutoTokenizer.from_pretrained("rbt3")
inputs = tokenizer(sen, return_tensors="pt")
print(inputs) # 會列印出 input_ids, token_type_ids, attention_mask

# 調用model
model = AutoModel.from_pretrained("rbt3") # 參數可以根據config.[想看的參數]來輸出 e.g. AutoModel.from_pretrained("rbt3", output_attentions = True)
output = model(**inputs)
print(output) # 就會輸出 last_hidden_state, pooler_output, ... 等 (看你model想輸出多少)
print(output.last_hidden_state) # 可以印出特定想看的結果
print(output.last_hidden_state.size()) # 可以印出維度 torch.size([1, 12, 768]) 1 batch size, 12 整個token長度, 768 結果size
print(len(inputs["input_ids"][0])) # 可以看inputs的長度發現是12

Model Head的模型調用

Model Head 有點像是模型的最後一層,他負責將模型的輸出轉換成我們需要的格式,以解決不同類型的任務。

我們用 AutoModelForSequenceClassification 文本分類為例:

1
2
3
4
5
6
7
8
9
10
from transformers import AutoModelForSequenceClassification 

clz_model = AutoModelForSequenceClassification.from_pretrained("rbts")
print(clz_model.config.num_labels) # 可以看到有幾個分類會印出 2
print(clz_model(**inputs)) # 兩個分類的機率就會印出來

# 我們也可以設定成10個分類
clz_model = AutoModelForSequenceClassification.from_pretrained("rbts", num_labels=10)
print(clz_model.config.num_labels) # 可以看到有幾個分類會印出 10
print(clz_model(**inputs)) # 兩個分類的機率就會印出來

模型微調範例

step 1 加載數據

先去下載 ChnSentiCorp_ht1_all.csv 預測資料存在本地

1
2
3
4
from transformers import AutoConfig, AutoModel, AutoTokenizer

import pandas as pd
data = pd.read_csv("./ChnSentiCorp_ht1_all.csv").dropna() # 把空移除

Step 2 創建 dataset

後面Dataloader會根據整批的dataset

  1. 進行 90:10 的訓練和測試資料分割
  2. 進行 tokenizer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch.utils.data import Dataset

class MyDataset(Dataset):
def __init__(self) -> None:
super().__init__()
self.data = pd.read_csv("./ChnSentiCorp_ht1_all.csv").dropna()

def __getitem__(self, index):
return self.data.iloc[index]["review"], self.data.iloc[index]["label"]

def __len__(self):
return len(self.data)

dataset = MyDataset()
for i in range(5):
print(dataset[i]) # 印出來應該會長('sentence', 1)

Step3 劃分數據集

接下來我們可以使用 random_split 劃分數據:

1
2
3
4
from torch.utils.data import random_split

trainset, validset = random_split(dataset, lengths=[0.9, 0.1]) # 90:10
print(len(trainset, len(validset))) # (6989, 776)

Step 4 創建Dataloader

開始處理 batch 資料,進行 batch 的 tokenizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from torch.utils.data import DataLoader

tokenizer = AutoTokenizer.from_pretrained("rbt3")

def collate_func(batch):
texts, labels = [], []
for item in batch:
texts.append(item[0])
labels.append(item[1])
inputs = tokenizer.batch_encode_plus(texts, max_length=512, padding="max_length", truncation=True, return_tensors="pt")
inputs["labels"] = torch.tensor(labels) # 在裡面多添加一個key叫做labels, 這樣就有input_ids, labels, ...
return inputs

trainloader = DataLoader(trainset, batch_size=32, shuffle=True, collate_fn=collate_func)
validloader = DataLoader(validset, batch_size=32, shuffle=True, collate_fn=collate_func)
print(next(enumerate(trainloader))[1]) # 可以印出來第一筆,{'input': [[101, 1, 2], [101, 2, 1], ...], 'label': tensor([1, 1, ...])}

Step 5 創建模型及優化器

1
2
3
4
5
6
7
8
9
from torch.optim import Adam 
from transformers import AutoModelForSequenceClassification

model = AutoModelForSequenceClassification.from_pretrained("./rbt3/")
optimizer = Adam(model.parameters(), lr=2e-5)

# 使用 gpu
if torch.cuda.is_available():
model = model.cuda()

Step 6 訓練與驗證

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

def evaluate():
model.eval()
acc_num = 0
with torch.inference_mode():
for batch in validloader:
if torch.cuda.is_available():
batch = {k: v.cuda() for k, v in batch.items()}
ouput = model(**batch)
pred = torch.argmax(ouput.logits, dim=-1)
acc_num += (pred.long() == batch["labels"]).float().sum() # 因為是batch,我們想知道這個batch ouput準確的共有多少
return acc_num / len(validset)

def train(epoch=3, log_step=100):
global_step = 0
for ep in range(epoch):
model.train()
for batch in trainloader:
if torch.cuda.is_available():
batch = {k: v.cuda() for k, v in batch.items()}
optimizer.zero_grad()
output = model(**batch)
output.loss.backword() # 進行backforward
optimizer.step() # 更新模型
if global_step % log_step == 0:
print(f"ep: {ep}, global_step: {global_step}, loss:{output.loss.item()}")
global_step += 1
acc = evaluate()
print(f"ep: {ep}, acc: {acc}")

# 就可以開始訓練了
train()

Step 9 進行預測

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sen = "我覺得這家酒店不錯 飯很好吃!"
id2_label = {0: "差評", 1:"好評"}
with torch.inference_mode():
inputs = tokenizer(sen, return_tensors="pt")
inputs = {k: v.cuda() for k, v in inputs.items()}
logits = model(**inputs).logits
pred = torch.argmax(logits, dim=-1)
print(f"輸入: {sen} \n模型預測結果:{id2_label(pred.item())}") # 會顯示 輸入: 我覺得這家酒店不錯 飯很好吃! \n模型預測結果:好評

# 也可以直接使用 pipeline
from transformer import pipeline

pipe = pipeline("text-classification", model=model, tokenizer=tokenizer, device=0)
print(pipe(sen)) # 會印出 -> [{'label': 'LABEL1', 'score': 0.99}]

# 我們希望幫我把結果進行轉換可以設定 model.config
model.config.id2lable = id2_label
print(pipe(sen)) # 會印出 -> 好評

參考文獻