目錄
一、準備階段
1.導入模塊
2.指定使用的是GPU還是CPU
3.加載數據集
二、對數據添加詞元和分詞
1.根據BERT的預訓練,我們要將一個句子的句頭添加[CLS]句尾添加[SEP]
2.激活BERT詞元分析器
3.填充句子為固定長度
代碼解釋:
三、數據處理
1.創建masks掩碼矩陣
代碼解釋:
2.拆分數據集
3.將所有的數據轉換為torch張量
4.選擇批量大小并創建迭代器
代碼解釋:
四、BERT模型配置
1.初始化一個不區分大小寫的 BERT 配置:
代碼解釋:
2.這些配置參數的作用:
3.加載模型
4.優化器分組參數
代碼解釋:
5.訓練循環的超參數
代碼解釋:
五、訓練循環
代碼解釋:
訓練圖解:
?六、使用測試數據集進行預測和評估
七、使用馬修斯相關系數(MCC)評估
2. 代碼實現:
1. 測試數據預處理與預測
2.模型預測與結果收集
3.計算MCC
到這里就完美收官咯!!!!! 大家點個贊吧!!!
本章將微調一個BERT模型來預測下游的可接受性判斷任務,如果你的電腦還沒有配置相關環境的可以去使用?Colaboratory - Colab,里面已經全部幫你配置好啦!而且還可以免費使用GPU。
一、準備階段
1.導入模塊
導入所需的預訓練相關模塊,包括用于詞元化的 BertTokenizer、用于配置 BERT 模型的 BertConfig,還有 Adam 優化器(AdamW),以及序列分類模塊(BertFo SequenceClassification):
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset,DataLoader,RandomSampler,SequentialSampler
from sklearn.model_selection import train_test_split
from transformers import BertTokenizer,BertConfig
from transformers import BertForSequenceClassification,get_linear_schedule_with_warmup
# from transformers import AdamW
from torch.optim import AdamW
from tqdm import tqdm,trange
import pandas as pd
import io
import numpy as np
import matplotlib.pyplot as plt# 導入進度條
from tqdm import tqdm,trange
# 導入常用的標注python模塊
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
2.指定使用的是GPU還是CPU
使用GPU加速對我們的訓練非常又有幫助
device = torch.device("cuda" if torch.cuda.is_avsilable() else "cpu")
3.加載數據集
這里的代碼和數據是參考https://github.com/Denis2054/Transformers-for-NLP-2nd-Edition/tree/main/Chapter03
使用git倉庫拉取一下就可以得到數據了
df=pd.read_csv("你拉取的數據in_domain_train.tsv的路徑",delimiter='\t',header=None,names=['sentence_source','label','label_notes','sentence'])
# 展示數據維度
df.shape # (8551,4)
隨機抽取十個樣本數據的看看:
df.sample(10)
?以看到數據集中的數據包含了以下四列(即.tsv文件中四個用制表符分隔的列)。
● 第1列:句子來源(用編號表示)
● 第2列:標注(0=不可接受,1= 可接受)
● 第3列:作者的標注
● 第4列;要分類的句子
二、對數據添加詞元和分詞
1.根據BERT的預訓練,我們要將一個句子的句頭添加[CLS]句尾添加[SEP]
代碼解釋:將數據中的需要分類的句子提取為sentences,循環出每個句子,在每個句子的句頭添加[CLS]句尾添加[SEP],將數據中的標簽提取為labels
sentences=df.sentence.values
sentences=["[CLS]"+ sentence+"[SEP]" for sentence in sentences]
labels=df.label.values
2.激活BERT詞元分析器
這里是初始化一個預訓練BERT詞元分析器。相比與從頭開始訓練一個詞元分析器相對,節省很多時間和資源。們選擇了一個不區分大小寫的詞元分析器,激活它,并展示對第一個句子詞元 化之后的結果:
代碼講解:tokenizer是我們初始化的詞元分析器,BertTokenizer.from_pretrained('bert-base-uncased')是使用BERT中自帶的預訓練好的參數,關于BertTokenizer.from_pretrained可以去看我的另外一章博客BertTokenizer.from_pretreined。
? ? ? ? tokenizer_texts是已經每個句子詞元分析好的一個迭代器,因為sentences中保存的句子的type為array類型,而tokenize中要傳入的是字符串類型,所以這里要強轉一下
詞元分析后的一條句子為:Tokenize the first sentence: ['[CLS]', 'our', 'friends', 'wo', 'n', "'", 't', 'buy', 'this', 'analysis', ',', 'let', 'alone', 'the', 'next', 'one', 'we', 'propose', '.', '[SEP]']
tokenizer=BertTokenizer.from_pretrained('bert-base-uncased')
tokenizer_texts=[tokenizer.tokenize(str(sent)) for sent in sentences]
print("Tokenize the first sentence: ")
print(tokenizer_texts[0])
# Tokenize the first sentence:
['[CLS]', 'our', 'friends', 'wo', 'n', "'", 't', 'buy', 'this', 'analysis', ',', 'let', 'alone', 'the', 'next', 'one', 'we', 'propose', '.', '[SEP]']
3.填充句子為固定長度
上面的處理中我們不難想到,每個句子分析后的長度會隨句子的大小而改變,而在BERT微調的時候需要保證句子的長度應用,所以我們要將長度不夠的句子進行填充,我們將這個最大長度設置為128,對于長度超過128的數據我們將它截斷,保證每個句子序列的大小都為128
代碼解釋:
input_ids:里面保存的是將上面的句子分詞后的詞元列表轉化成對應數字的列表,其中將詞元一個個的循環出來后經過tokenizer.convert_tokens_to_ids,它能把分詞后的詞元轉化為整數 ID,從而讓深度學習模型能夠處理文本數據。在不同的庫中,其使用方式可能會有所不同,但核心功能是一致的。
第二個input_ids:保存的是將每個詞元序列轉化成相同大小后的迭代器,pad_sequences函數的主要作用是將多個序列填充或截斷至相同的長度,這在處理序列數據(像文本序列)時十分關鍵,因為神經網絡通常要求輸入數據具有統一的形狀
from tensorflow.keras.preprocessing.sequence import pad_sequences
MAX_LEN=128
input_ids=[tokenizer.convert_tokens_to_ids(x) for x in tokenizer_texts]
print(input_ids[0])
input_ids=pad_sequences(input_ids,maxlen=MAX_LEN,dtype='long',truncating='post',padding='post')
print(input_ids[0])
三、數據處理
1.創建masks掩碼矩陣
如果不知道為什么需要掩碼的可以去看看transformers架構
為了防止模型對填充詞元進行注意力計算,我們在前面的步驟中對序列進行了填充補齊。但是我們希望防止模型對這些填充 的詞元進行注意力計算!首先創建一個空的 attention_masks 列表,用于存儲每個序列的注意力掩碼。然后, 對于輸入序列(input_ids)中的每個序列(seq),我們遍歷其中的每個詞元。
針對每個詞元,我們判斷其索引是否大于 0。如果大于 0,則將對應位置的掩碼 值設置為1,表示該詞元是有效詞元。如果等于0,則將對應位置的掩碼值設置為0, 表示該詞元是填充詞元。最終得到的 attention_masks 列表中的每個元素都是一個與對應輸入序列長度相同的 列表,其中每個位置的掩碼值表示該位置的詞元是否有效(1表示有效,0表示填充)。
通過使用注意力掩碼,可確保在模型的注意力計算中,只有真實的詞元會被考慮, 而填充詞元則被忽略。這樣可提高計算效率,并減少模型學習無用信息的概率。
代碼解釋:
attention_masks是保存所有詞元掩碼的列表,上面我們說到input_ids保存的是將每個詞元序列轉化成相同大小后的迭代器,我們將里面的每個詞元序列遍歷為seq,判斷seq中的值是否大于0,如果大于0,那么它是有效詞元,將他對應的掩碼設置為1,反正為0
attention_masks=[]
for seq in input_ids:seq_mask=[float(i>0) for i in seq]attention_masks.append(seq_mask)
print(attention_masks[0])
"""[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0,1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.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, 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.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,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.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, 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.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, 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.0]"""
2.拆分數據集
將數據拆分成訓練集和驗證集,訓練集和測試集的比例為9:1
# 拆分訓練集和驗證集 (90%訓練,10%驗證)
train_inputs, val_inputs, train_labels, val_labels = train_test_split(input_ids, labels, test_size=0.1, random_state=2025)
train_masks, val_masks, _, _ = train_test_split(attention_masks, labels, test_size=0.1, random_state=2025)
3.將所有的數據轉換為torch張量
微調模型需要使用 torch 張量,所以我們需要將數據轉換為 torch 張量:
train_inputs = torch.tensor(train_inputs)validation_inputs = torch.tensor(validation_inputs)txain_labels=torch.tensor(train_labels)validation_labels = torch.tensor(validation_labels)train_masks = torch.tensor(train_masks)validation_masks = torch.tensor(validation_masks)
4.選擇批量大小并創建迭代器
如果一股腦地將所有數據都喂進機器,會導致機器因為內存不足而崩潰。所以需 要將數據一批一批地喂給機器。這里將把批量大小(batch size)設置為 32 并創建迭代 器。然后將迭代器與 torch的 DataLoader 相結合,以批量訓練大量數據集,以免導致 機器因為內存不足而崩潰:
代碼解釋:
這里我們選的批量大小(batch_size)為32,使用TensorDataset和DataLoader創建訓練數據迭代器,如果對TensorDataset和DataLoader不清楚的可以去看看我的另外一篇博客:TensorData和DataLoader
RandomSampler 是一種隨機采樣器,從給定的數據集中隨機抽取樣本,可選擇有放回或無放回采樣。無放回采樣時,從打亂的數據集里抽取樣本;有放回采樣時,可指定抽取的樣本數量?num_samples
。
batch_size=32
# 訓練數據迭代器
train_data=TensorDataset(train_inputs,train_masks,train_labels)
train_sampler=RandomSampler(train_data)
train_dataloader=DataLoader(train_data,sampler=train_sampler,batch_size=batch_size)
# 測試數據迭代器validation_data=TensorDataset(validation_inputs,validation_masks,validation_label)
validation_sampler=RandomSampler(train_data)
validation_dataloader=DataLoader(validation_data,sampler=validation_sampler,batch_size=batch_size)
四、BERT模型配置
1.初始化一個不區分大小寫的 BERT 配置:
代碼解釋:
后面的代碼尋妖用到transformers這個包,如果沒有的pip安裝一下。
configuration是初始化了一個包含BERT預訓練模型中所有超參數的配置實例,如果BertConfig中不加任何的參數,那么會生成一個標準的BERT-base配置:
{"hidden_size": 768, # 每個Transformer層的維度"num_hidden_layers": 12, # Transformer層數(深度)"num_attention_heads": 12, # 注意力頭的數量"intermediate_size": 3072, # FeedForward層的中間維度"vocab_size": 30522, # 詞表大小(需與預訓練模型一致)"max_position_embeddings": 512, # 最大序列長度...
}
?model:BertModel是根據配置生成一個隨機初始化權重的BERT模型,根據傳入的配置信息生成。
configuration是一個保存模型內部存儲的配置信息副本
try:import transformers
except:print("installing transformers")
from transformers import BertModel,BertConfig
configuration=BertConfig()model=BertModel(config=configuration)
configuration=model.config
print(configuration)
"""
輸出為:
BertConfig {"_attn_implementation_autoset": true,"attention_probs_dropout_prob": 0.1,"classifier_dropout": null,"hidden_act": "gelu","hidden_dropout_prob": 0.1,"hidden_size": 768,"initializer_range": 0.02,"intermediate_size": 3072,"layer_norm_eps": 1e-12,"max_position_embeddings": 512,"model_type": "bert","num_attention_heads": 12,"num_hidden_layers": 12,"pad_token_id": 0,"position_embedding_type": "absolute","transformers_version": "4.50.0","type_vocab_size": 2,"use_cache": true,"vocab_size": 30522
}
"""
2.這些配置參數的作用:
"""
attention probs_dropout_prob:對注意力概率應用的 dropout 率,這里設置為
0.1。
● hidden_act;編碼器中的非線性激活函數,這里使用 gelu。gelu 是高斯誤差線
性單位(Gaussian Eror Linear Units)激活函數的簡稱,它對輸入按幅度加權,
使其成為非線性。
● hidden_dropout_prob:應用于全連接層的 dropout 概率。嵌入、編碼器和匯聚
器層中都有全連接。輸出不總是對序列內容的良好反映。匯聚隱藏狀態的序
第3章 微調BERT 模型
列可改善輸出序列。這里設置為0.1。
● hidden_size:編碼器層的維度,也是匯聚層的維度,這里設置為768。
● initializer_range:初始化權重矩陣時的標準偏差值,這里設置為0.02。
· intermediate_size:編碼器前饋層的維度,這里設置為3072。
● layer_norm_eps:是層規范化層的 epsilon 值,這里設置為le-12。
● max_position_embeddings:模型使用的最大長度,這里設置為512。
● model_type:模型的名稱,這里設置為 bert。
● numattention_heads:注意力頭數,這里設置為12。
· num_hidden_layers:層數,這里設置為12。
● pad_tokenid:使用0作為填充詞元的HD,以避免對填充詞元進行訓練。
57
?· type_vocab_size:token_type_ids的大小用于標識序列。例如,“the dog[SEP]
?The cat.[SEP]”可用詞元 ID [0,0,0,1,1,1]表示。
· vocab_size:模型用于表示 input_ids 的不同詞元數量。換句話說,這是模型
可以識別和處理的不同詞元或單詞的總數。在訓練過程中,模型會根據給定
的詞表將文本輸入轉換為對應的詞元序列,其中包含的詞元數量是
vocab_size。通過使用這個詞表,模型能夠理解和表示更廣泛的語言特征。這
里設置為 30522。
講解完這些參數后,接下來將加載預訓練模型。
"""
3.加載模型
現在開始加載預訓練BERT模型
BertForSequenceClassification.from_pretrained
?能夠讓你加載預訓練的 BERT 模型權重,并且可以根據需求調整模型以適應特定的序列分類任務。這個方法非常實用,因為借助預訓練的權重,模型通常能更快收斂,并且在特定任務上表現更優。第一個參數bert-base-uncased意思是加載BERT的默認權重,如果你有別的模型權重可以填寫它的名字或者路徑;nums_labels:表示你這個任務中的類別數,我們這個任務的label只有兩種,所以這里是2。
DataParallel:DataParallel 是一種數據并行的實現方式,其核心思想是將大規模的數據集分割成若干個較小的數據子集,然后將這些子集分配到不同的計算節點(如 GPU)上,每個節點運行相同的模型副本,但處理不同的數據子集。在每一輪訓練結束后,各節點會將計算得到的梯度進行匯總,并更新模型參數。如果不知道分布式計算的可以去看看我的另外一篇博客如何在多個GPU上訓練
model=BertForSequenceClassification.from_pretrained("bert-base-uncased",num_labels=2)
model=nn.DataParallel(model)
model.to(device)
4.優化器分組參數
在將為模型的參數初始化優化器。在進行模型微調的過程中,首先需要初始化 預訓練模型已學到的參數值。 微調一個預訓練模型時,通常會使用之前在大規模數據上訓練好的模型作為初始 模型。這些預訓練模型已通過大量數據和計算資源進行了訓練,學到了很多有用的特 征表示和參數權重。因此,我們希望在微調過程中保留這些已經學到的參數值,而不 是重新隨機初始化它們。 所以,程序會使用預訓練模型的參數值來初始化優化器,以便在微調過程中更好 地利用這些已經學到的參數。這樣可以加快模型收斂速度并提高微調效果;
代碼解釋:
這段代碼是用與為BERT模型的參數設置差異化的權重衰減策略,是訓練Transformer模型時的常用技巧
param_optimizer是以字典的方式保存模型中所有可訓練參數的名稱和值,named_parameters()函數是獲取模型中所有的參數名稱和值。
no_decay是定義無需權重衰減的參數類型,權重衰減對偏置項bias和歸一化層的weight無益,bias可能破壞模型對稱性,LayerNorm的weight需保持靈活性,正則化會抑制其適應性。
optimizer_grouped_parametes: 組1:分組設置優化策略,篩選出參數名稱中不包含bias和LayerNorm.weight的參數然后將權重衰減率設為0.1。組2:禁止權重衰減的參數,篩選出參數名稱中包含bias和LayerNorm.weight的參數將權重衰減率設為0.0
param_optimizer=list(model.named_parameters())no_decay=['bias','LayerNorm.weight']
optimizer_grouped_parametes=[{'params':[p for n,p in param_optimizer if not any(nd in n for nd in no_decay)],'weight_decay_rate':0.1},{"params":[p for n,p in param_optimizer if any(nd in n for nd in no_decay)],'weight_decay_rate':0.0}
]
5.訓練循環的超參數
訓練循環中的超參數非常重要,盡管它們看起來可能無害。例如,Adam 優化器 會激活權重衰減并經歷一個預熱階段。學習率(lr)和預熱率(warnup)應該在優化階段的早期設置為一個非常小的值,在一 定迭代次數后逐漸增加。這樣可以避免出現過大的梯度和超調問題,以更好地優化模 型目標。
代碼解釋:
使用AdamW優化器,將模型的參數傳入,并設置初始學習率為2e-5
定義一個函數calculate_accuracy來度量準確率,用于測試結果與標注進行比較,向函數中傳入預測結果的概率分布和真實標簽,pred_flat是查找這個概率分布中的最大概率的索引,flatten是將數組一維化,labels_flat是真實結果的一維化。最后,計算預測結果展平后的數組中與展平后的標簽數組相等的元素數量占標簽數組長度的比例,并將這個比例作為結果返回。
optimizer=AdamW(optimizer_grouped_parametes,lr=2e-5)
def calculate_accuracy(preds, labels):"""計算準確率的優化版本"""preds = np.argmax(preds, axis=1).flatten()labels = labels.flatten()return np.sum(preds == labels) / len(labels)
五、訓練循環
我們的訓練循環將遵循標準的學習過程。輪數(epochs)設置為 4,并將繪制損失和 準確率的度量值。訓練循環使用 dataloader 來加載和訓練批量。我們將對訓練過程進 行度量和評估。 首先初始化 train_loss_set(用于存儲損失和準確率的數值,以便后續繪圖)。然后 開始訓練每一輪,并運行標準的訓練循環,
代碼解釋:
這段代碼實現了BERT模型的完整訓練和驗證流程,包含以下核心步驟:
- 初始化訓練記錄容器
- 循環訓練多個epoch
- 每個epoch包含訓練階段和驗證階段
- 記錄并輸出訓練指標
train_loss_history:? 儲存每個epoch的平均訓練損失
val_accuracy_history:存儲每個epoch的驗證集準確率
代碼太多了,大家在代碼中看注釋吧,這里主要說一下訓練步驟
1.初始化記錄容器
2.設置外層epoch(循環次數)循環
3.設置model為訓練模式
4.將數據挨個前向傳播和反向傳播更新參數
5.計算平均訓練損失
6.將model設置為評估階段并進行評估
7.計算平均驗證準確率
8.打印訓練信息
train_loss_history = [] # 存儲每個epoch的平均訓練損失
val_accuracy_history = [] # 存儲每個epoch的驗證集準確率for epoch_i in trange(epochs, desc="Epoch"):# ========== 訓練階段 ==========model.train() # 設置模型為訓練模式total_train_loss = 0 # 初始化累計損失for batch in train_dataloader:# 數據轉移到GPUb_input_ids, b_input_mask, b_labels = tuple(t.to(device) for t in batch)# 梯度清零model.zero_grad()# 前向傳播outputs = model(b_input_ids,attention_mask=b_input_mask,labels=b_labels)# 多GPU處理:取平均損失loss = outputs.loss.mean()# 反向傳播loss.backward()"""在深度學習訓練時,梯度可能會變得非常大,這會導致訓練不穩定,甚至引發梯度爆炸的問題。torch.nn.utils.clip_grad_norm_ 函數通過對梯度的范數進行裁剪,避免梯度變得過大,從而讓訓練過程更加穩定。"""torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) # 梯度裁剪# 參數更新optimizer.step()scheduler.step()total_train_loss += loss.item()# 記錄平均訓練損失avg_train_loss = total_train_loss / len(train_dataloader)train_loss_history.append(avg_train_loss) # 記錄歷史損失# ========== 驗證階段 ==========model.eval()total_eval_accuracy = 0model.eval() # 設置模型為評估模式total_eval_accuracy = 0 # 初始化累計準確率with torch.no_grad(): # 禁用梯度計算for batch in val_dataloader:b_input_ids, b_input_mask, b_labels = tuple(t.to(device) for t in batch)# 前向傳播outputs = model(b_input_ids, attention_mask=b_input_mask)# .logits是將model中還沒有經過激活函數的值提取出來,因為驗證不需要激活# 這樣節省了顯存,.detach將張量從計算圖中分離,?斷開梯度追蹤。因為驗證階段不需要計算梯度,這一步可以節省內存并避免不必要的計算。# .cpu():如果張量在GPU上(例如通過.to('cuda')加載),這一步會將其移動到CPU內存中。NumPy無法直接處理GPU上的張量,必須轉移到CPU。logits = outputs.logits.detach().cpu().numpy()label_ids = b_labels.to('cpu').numpy()total_eval_accuracy += calculate_accuracy(logits, label_ids)avg_val_accuracy = total_eval_accuracy / len(val_dataloader)val_accuracy_history.append(avg_val_accuracy)# 打印訓練信息print(f"\nEpoch {epoch_i + 1}/{epochs}")print(f"Train loss: {avg_train_loss:.4f}")print(f"Validation Accuracy: {avg_val_accuracy:.4f}")
訓練圖解:
graph TDA[開始訓練] --> B[設置訓練模式]B --> C[遍歷訓練數據]C --> D[數據轉GPU]D --> E[梯度清零]E --> F[前向傳播]F --> G[計算損失]G --> H[反向傳播]H --> I[梯度裁剪]I --> J[參數更新]J --> K[學習率更新]K --> CC --> L[計算平均損失]L --> M[驗證模式]M --> N[遍歷驗證數據]N --> O[前向傳播]O --> P[計算準確率]P --> Q[計算平均準確率]Q --> R[記錄結果]R --> S[打印信息]S --> T[完成epoch?]T --是--> U[結束訓練]T --否--> B
?六、使用測試數據集進行預測和評估
們使用了in_domain_traintsv 數據集訓練 BERT下游模型。現在我們將使用基 于留出法!分出的測試數據集 outof_domain_dev.v 文件進行預測。我們的目標是預 測句子在語法上是否正確。 以下代碼展示了測試數據準備過程:
# 加載測試數據
test_df = pd.read_csv("out_of_domain_dev.tsv", delimiter='\t', header=None,names=['sentence_source', 'label', 'label_notes', 'sentence'])# 預處理測試數據
test_input_ids, test_attention_masks, test_labels = preprocess_data(test_df, tokenizer, max_len=128)# 創建預測DataLoader
prediction_dataset = TensorDataset(test_input_ids, test_attention_masks, test_labels)
prediction_dataloader = DataLoader(prediction_dataset, sampler=SequentialSampler(prediction_dataset),batch_size=batch_size)# 初始化存儲
predictions = []
true_labels = []model.eval()
for batch in prediction_dataloader:batch = tuple(t.to(device) for t in batch)b_input_ids, b_input_mask, b_labels = batchwith torch.no_grad():outputs = model(b_input_ids, attention_mask=b_input_mask)logits = outputs.logits.detach().cpu().numpy()label_ids = b_labels.cpu().numpy()predictions.append(logits)true_labels.append(label_ids)# 計算準確率
flat_predictions = np.concatenate(predictions, axis=0)
flat_predictions = np.argmax(flat_predictions, axis=1)
flat_true_labels = np.concatenate(true_labels, axis=0)accuracy = np.sum(flat_predictions == flat_true_labels) / len(flat_true_labels)
print(f"Test Accuracy: {accuracy:.4f}")
七、使用馬修斯相關系數(MCC)評估
?1、MCC的核心原理與優勢
馬修斯相關系數(Matthews Correlation Coefficient, MCC)是一種綜合評估二分類模型性能的指標,尤其適用于類別不平衡數據集。其優勢包括:
- ?全面性:同時考慮真陽性(TP)、真陰性(TN)、假陽性(FP)、假陰性(FN)。、
- ?魯棒性:在類別分布不均衡時仍能準確反映模型性能(例如醫學診斷中的罕見病檢測)。
- ?可解釋性:取值范圍為[-1, 1],1表示完美預測,0表示隨機猜測,-1表示完全錯誤。
計算公式:
2. 代碼實現:
1. 測試數據預處理與預測
# 加載測試數據(示例路徑需替換為實際路徑)
test_df = pd.read_csv("out_of_domain_dev.tsv", delimiter='\t', header=None,names=['sentence_source', 'label', 'label_notes', 'sentence'])# 預處理(復用preprocess_data函數)
test_input_ids, test_attention_masks, test_labels = preprocess_data(test_df, tokenizer, max_len=128)# 創建DataLoader
prediction_dataset = TensorDataset(test_input_ids, test_attention_masks, test_labels)
prediction_dataloader = DataLoader(prediction_dataset, sampler=SequentialSampler(prediction_dataset), batch_size=batch_size)
2.模型預測與結果收集
?
# 初始化存儲
predictions = []
true_labels = []model.eval()
for batch in prediction_dataloader:batch = tuple(t.to(device) for t in batch)b_input_ids, b_input_mask, b_labels = batchwith torch.no_grad():outputs = model(b_input_ids, attention_mask=b_input_mask)logits = outputs.logits.detach().cpu().numpy()label_ids = b_labels.cpu().numpy()predictions.append(logits)true_labels.append(label_ids)# 合并結果
flat_predictions = np.concatenate(predictions, axis=0)
flat_predictions = np.argmax(flat_predictions, axis=1) # 將logits轉為類別(0/1)
flat_true_labels = np.concatenate(true_labels, axis=0)
3.計算MCC
from sklearn.metrics import matthews_corrcoefmcc = matthews_corrcoef(flat_true_labels, flat_predictions)
print(f"Test MCC: {mcc:.4f}")