PubMedQA 是一個生物醫學問答資料集,適用於訓練醫療領域的問答系統。本文將詳細介紹如何使用 Python 和 Hugging Face 的 datasets 和 transformers 函式庫,對 PubMedQA 資料集進行預處理,包含載入、分割、合併上下文、Tokenization 等步驟,為後續訓練大語言模型做好準備。過程中會使用 flan-t5-xxl 模型的 tokenizer 進行文字轉換,並示範如何計算最大查詢長度,以確保模型訓練效率。最後,文章也說明瞭如何使用 bitsandbytes 函式庫對模型進行量化,以減少記憶體使用,提升訓練效率。
資料預處理:使用PubMedQA資料集
本章節將介紹如何使用PubMedQA資料集進行資料預處理。PubMedQA是一個獨特的生物醫學問答(QA)資料集,從PubMed摘要中收集而來,旨在根據對應的摘要回答研究問題。
PubMedQA資料集組成
PubMedQA資料集包含三種型別的例項:
- 1,000個由專家註解的例項
- 61,200個未標註的例項
- 211,300個人工生成的例項
每個例項包含以下組成部分:
- 問題:研究文章標題或從中衍生出的問題
- 上下文:對應的摘要,但不包含結論
- 長答案:摘要的結論,旨在回答研究問題
- 是/否/可能答案:總結結論,指示答案是肯定、否定或不確定
載入資料集
首先,我們需要從Hugging Face Hub載入資料集。以下是載入資料集的程式碼:
from datasets import load_dataset
# 從Hugging Face Hub下載並載入資料集
dataset = load_dataset('pubmed_qa', 'pqa_labeled')
print(f"訓練資料集大小:{len(dataset['train'])}")
內容解密:
此段程式碼使用Hugging Face函式庫的Datasets模組載入資料集。我們載入了pubmed_qa資料集的pqa_labeled變體。使用load_dataset()函式,可以輕鬆地從Hugging Face Hub取得所需的資料集。載入資料集後,我們列印訓練資料集的大小,以瞭解訓練資料集中的樣本數量。
訓練-測試資料集分割
接下來,我們需要將資料集分割為訓練和測試資料集,以便在訓練資料集上微調模型,並在測試資料集上評估模型的效能。
# 從訓練資料集中提取1000個樣本
train_dataset = dataset['train'].shuffle().select(range(800))
# 從訓練資料集中提取200個樣本作為測試資料集
test_dataset = dataset['train'].shuffle().select(range(800, 1000))
內容解密:
首先,我們使用shuffle()函式隨機化樣本的順序,然後使用select()函式選擇特定的樣本範圍,從而提取800個樣本作為訓練資料集。接著,我們再次使用shuffle()函式隨機化樣本的順序,但這次選擇範圍800到1000之間的樣本,提取200個樣本作為測試資料集。這些樣本將用於評估模型的效能。
資料轉換
由於PubMedQA資料集的上下文欄位是一個字典,包含上下文和句子列表,因此我們需要轉換此欄位以建立可以被tokenize並用於訓練模型的段落。
# 建立新的資料集,包含轉換後的上下文欄位
transformed_train_dataset = train_dataset.from_dict({
'context': [". ".join(example['context']['contexts']) for example in train_dataset],
'question': train_dataset['question'],
'answer': train_dataset['long_answer'],
})
# 建立新的資料集,包含轉換後的上下文欄位
transformed_test_dataset = test_dataset.from_dict({
'context': [". ".join(example['context']['contexts']) for example in test_dataset],
'question': test_dataset['question'],
'answer': test_dataset['long_answer'],
})
# 列印第一個樣本的轉換後上下文
print("轉換後上下文:", transformed_train_dataset['context'][0])
內容解密:
此段程式碼建立新的資料集,包含轉換後的上下文欄位。我們使用列表推導式將上下文中的句子列表連線成一個段落,並將其儲存在新的context欄位中。同時,我們保留了原始的question和long_answer欄位。最後,我們列印第一個樣本的轉換後上下文,以驗證轉換結果。
資料集轉換與 Tokenization 處理
在建立生成式大語言模型的過程中,資料的預處理是至關重要的步驟。首先,我們需要對原始資料集進行轉換,以建立新的資料集,並將多個上下文句子合併成單一字串。接著,我們將介紹如何使用 Hugging Face 的 Transformers 函式庫進行 tokenization,將文字輸入轉換為模型可理解的格式。
步驟 2.3:轉換資料集
首先,我們遍歷訓練資料集中的每個範例,將個別的上下文句子使用 join() 函式連線成單一字串。這個轉換後的上下文被指定給新資料集 transformed_train_dataset 的 context 欄位。同時,我們也將原始訓練資料集中的 question 和 long_answer 欄位複製到新資料集的對應欄位。
同樣的轉換過程也被應用於測試資料集,建立新的 transformed_test_dataset,包含轉換後的上下文、問題和長答案欄位。
# 將訓練資料集的 context 欄位轉換為單一字串
transformed_train_dataset = ...
# 將測試資料集的 context 欄位轉換為單一字串
transformed_test_dataset = ...
內容解密:
- 遍歷訓練和測試資料集,將多個上下文句子合併成單一字串。
- 將原始資料集的
question和long_answer欄位複製到新資料集。
轉換後的上下文範例如下:
Obesity may be associated with lower prostate specific antigen through hemodilution. We examined the relationship between body mass index and prostate specific antigen by age in men without prostate cancer in a longitudinal aging study to determine whether prostate specific antigen must be adjusted for body mass index.. The study population included 994 men (4,937 observations) without prostate cancer in the Baltimore Longitudinal Study of Aging. Mixed effects models were used to examine the relationship between prostate specific antigen and body mass index in kg/m(2) by age. Separate models were explored in men with prostate cancer censored at diagnosis, for percent body fat measurements, for weight changes with time and adjusting for initial prostate size in 483 men (2,523 observations) with pelvic magnetic resonance imaging measurements.. In men without prostate cancer body mass index was not significantly associated with prostate specific antigen after adjusting for age (p = 0.06). A 10-point body mass index increase was associated with a prostate specific antigen difference of -0.03 ng/ml (95% CI -0.40-0.49). Results were similar when men with prostate cancer were included, when percent body fat was substituted for body mass index, and after adjusting for prostate volume. Longitudinal weight changes also had no significant association with prostate specific antigen.
步驟 2.4:Tokenize 資料集
為了訓練模型,我們需要將文字輸入轉換為模型可理解的格式。這是透過使用 Transformers 函式庫中的 tokenizer 實作的。
載入 Tokenizer
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
model_id = "google/flan-t5-xxl"
# 載入 FLAN-t5-XL 的 tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_id)
內容解密:
- 從 Hugging Face 的 Transformers 模組匯入必要的元件,包括
AutoTokenizer和AutoModelForSeq2SeqLM。 - 定義
model_id為所需模型的識別符號,這裡使用的是 Google 開發的預訓練語言模型flan-t5-xxl。 - 使用
AutoTokenizer.from_pretrained()方法載入模型的 tokenizer,將其指定給tokenizer變數。
Tokenize 結合輸入
from datasets import concatenate_datasets
import numpy as np
# Tokenize 結合後的輸入
tokenizedQueries = concatenate_datasets([transformed_train_dataset]).map(
lambda x: tokenizer(x["context"], x["question"], truncation=True),
batched=True,
remove_columns=["context", "question", "answer"]
)
queryLengths = [len(x) for x in tokenizedQueries["input_ids"]]
# 取最大長度的百分位數以獲得更好的利用率,如果記憶體不足可以減少這個值
maxQueryLength = int(np.percentile(queryLengths, 100))
print(f"Max query length: {maxQueryLength}")
內容解密:
- 使用
concatenate_datasets將資料集合併,並使用map方法對資料集進行 tokenization。 lambda函式對每個範例進行 tokenization,將context和question合併並進行截斷。- 設定
batched=True以批次處理資料,並移除原始欄位以節省空間。 - 計算 tokenized 後的輸入序列長度,並取其百分位數作為最大查詢長度。
此圖示呈現了 tokenization 的流程:
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 內容解密:
rectangle "轉換" as node1
rectangle "tokenization" as node2
rectangle "計算長度" as node3
node1 --> node2
node2 --> node3
@enduml
圖表翻譯: 此圖示呈現了從原始資料集到計算最大查詢長度的整個流程。首先,原始資料集經過轉換成為新的資料集。接著,這些資料被送入 tokenizer,變成 tokenized 資料集。最後,計算這些 tokenized 資料的長度,並得出最大查詢長度。
大語言模型的預處理與訓練
資料集預處理的重要性
在訓練大語言模型(LLM)之前,資料集的預處理是至關重要的步驟。適當的預處理可以確保模型能夠有效地學習並產生高品質的輸出。
步驟 2.5:預處理資料集
首先,我們需要使用輸入和輸出長度資訊來對整個資料集進行預處理。這樣做可以確保我們的資料集在訓練前就已經準備好,並且可以儲存到磁碟中以便後續使用。
預處理函式
def preprocess_dataset(sample, padding="max_length"):
tokenizer.pad_token = tokenizer.eos_token
# 結合 'context' 和 'question' 欄位
inputs = ['根據以下內容回答問題。 ' + context + ' ' + question
for context, question in zip(sample["context"], sample["question"])]
# 對輸入進行分詞
model_inputs = tokenizer(inputs, max_length=maxQueryLength,
padding=padding, truncation=True)
# 對標籤進行分詞,使用 text_target 引數
labels = tokenizer(text_target=sample["answer"], max_length=maxResponseLength,
padding=padding, truncation=True)
# 當填充到最大長度時,將標籤中的填充符號替換為 -100
if padding == "max_length":
labels["input_ids"] = [[(l if l != tokenizer.pad_token_id else -100) for l in label]
for label in labels["input_ids"]]
model_inputs["labels"] = labels["input_ids"]
return model_inputs
套用預處理函式
tokenized_train_dataset = transformed_train_dataset.map(preprocess_dataset,
batched=True,
remove_columns=['context', 'question', 'answer'])
tokenized_test_dataset = transformed_test_dataset.map(preprocess_dataset,
batched=True,
remove_columns=['context', 'question', 'answer'])
儲存預處理後的資料集
tokenized_train_dataset.save_to_disk("data/train")
tokenized_test_dataset.save_to_disk("data/eval")
#### 內容解密:
- 定義預處理函式:
preprocess_dataset函式負責對資料集進行預處理,包括結合上下文和問題、分詞、以及準備標籤。 - 結合上下文和問題:將上下文和問題結合起來,形成模型的輸入。
- 分詞:使用
tokenizer對輸入和標籤進行分詞,確保輸入和輸出的長度符合模型的要求。 - 填充和截斷:根據
padding引數決定是否填充或截斷輸入和標籤。 - 替換填充符號:在標籤中,將填充符號替換為 -100,以便在計算損失時忽略這些符號。
- 套用預處理函式:使用
map方法將預處理函式套用到訓練和測試資料集上。 - 儲存資料集:將預處理後的資料集儲存到磁碟中,以便後續使用。
步驟 3:模型訓練/微調
為了減少模型所需的記憶體,我們將使用 bitsandbytes 函式庫的 LLM.int8() 技術對凍結的 LLM 進行量化,將模型的權重和啟用從浮點數值轉換為 8 位元整數值。
量化步驟:
- 安裝
bitsandbytes函式庫。 - 載入凍結的 LLM 模型。
- 使用 LLM.int8() 函式將模型量化為 int8。
- 儲存量化後的模型。
#### 圖表翻譯:
此量化過程可以顯著減少模型所需的記憶體,使得在資源有限的環境中訓練大語言模型成為可能。