目錄
一、PEFT的關鍵概念和方法
部分參數微調
概念
方法
優勢
適配器(Adapters)
方法
優勢
低秩分解(Low-rank Factorization)
方法
優勢
差分微調(Delta Tuning)
方法
優勢
多任務學習(Multi-task Learning)
方法
優勢
二、低秩矩陣分解技術
低秩分解的原理
常見的低秩分解方法
奇異值分解(Singular Value Decomposition, SVD)
主成分分析(Principal Component Analysis, PCA)
非負矩陣分解(Non-negative Matrix Factorization, NMF)
低秩分解的應用
例子
三、部分參數微調
部分參數微調的原理
常見的部分參數微調方法
頂層微調(Top-layer Tuning)
中間層微調(Intermediate-layer Tuning)
瓶頸層微調(Bottleneck-layer Tuning)
層歸一化參數微調(Layer Normalization Parameter Tuning)
特定參數組微調(Specific Parameter Group Tuning)
代碼實例
四、適配器
適配器的原理
適配器的結構
降維層(Down-projection layer)
非線性激活函數
升維層(Up-projection layer)
殘差連接
適配器的插入位置
代碼實例
五、差分微調(Delta Tuning)
差分微調的原理
代碼實例
六、多任務學習(Multi-task Learning)
多任務學習的原理
多任務學習的方法
硬共享(Hard Parameter Sharing)
軟共享(Soft Parameter Sharing)
代碼實例?
PEFT(Parameter-Efficient Fine-Tuning)是一種在大規模預訓練模型(如Transformer模型)上進行高效微調的方法。這種方法的主要目標是通過優化較少的參數來實現模型的高效微調,從而降低計算成本和存儲需求,同時保持或接近原始模型的性能。PEFT 在實際應用中非常重要,特別是在資源受限的環境中。
一、PEFT的關鍵概念和方法
部分參數微調
概念
部分參數微調是一種只調整模型中特定參數或層的方法,僅微調模型的一部分參數而不是整個模型,例如某些特定的層、子網絡或參數組。這種方法選擇性地微調一些關鍵部分,這可以大大減少需要優化的參數數量,從而減少計算負擔和內存使用。
方法
凍結大部分層:只微調最后幾層或特定的中間層。
選擇性解凍:在訓練過程中,逐步解凍更多的層。
層歸一化:只微調歸一化層的參數,如BatchNorm層或LayerNorm層。
優勢
高效性:減少需要優化的參數數量,節省計算資源。
穩定性:通過限制參數更新的范圍,減少過擬合風險。
適配器(Adapters)
在模型的特定層中插入小的適配器模塊,這些模塊通常比原始層小得多,只需微調適配器的參數,適配器的參數是獨立優化的。這種方法可以在保持原始模型架構的前提下實現高效微調。
方法
在Transformer層中插入適配器:通常插入在每個Transformer層的前饋網絡部分。
參數共享:在多任務學習中,不同任務的適配器可以共享部分參數。
優勢
靈活性:適配器模塊可以插入到不同層中,適應不同任務的需求。
節省資源:大大減少需要微調的參數量。
低秩分解(Low-rank Factorization)
將模型參數矩陣分解成兩個較小的矩陣進行優化。這種方法可以減少參數量,同時保持模型的表示能力。
方法
矩陣分解:將一個大矩陣 W 分解為兩個小矩陣 A 和 B,即 W≈A×B。
訓練過程:只微調小矩陣 A 和 B,而不更新整個大矩陣。
優勢
減少參數量:有效降低模型的參數規模。
保持性能:在很多情況下,可以保持模型性能不變或略微下降。
差分微調(Delta Tuning)
只微調與原始模型參數的差值部分,而不是整個參數集。這種方法可以在節省計算資源的同時,實現有效的參數更新。這種方法通過優化參數的增量來實現模型調整。
方法
參數初始化:從預訓練模型加載參數。
增量更新:僅優化參數的增量部分 Δθ,即 Δθ′=θ+Δθ。
優勢
節省內存:只存儲和更新參數的增量部分。
穩定性:原始模型參數作為基礎,有助于保持模型性能。
多任務學習(Multi-task Learning)
多任務學習是一種通過共享參數在多個任務之間進行微調的方法。這種方法利用多個任務的共同信息,提高模型的泛化能力。
方法
共享層:在多個任務之間共享部分模型層,減少總參數量。
任務特定層:每個任務擁有一些特定的參數層,用于處理任務特有的信息。
交替訓練:在不同任務的數據上交替進行訓練。
優勢
參數共享:通過共享參數,顯著減少總參數量。
提高泛化能力:利用多個任務的共同信息,提高模型的泛化性能。
二、低秩矩陣分解技術
低秩分解(Low-rank Factorization)是一種在機器學習和信號處理領域中廣泛應用的技術,主要用于降維、壓縮和優化模型參數。通過將一個高維矩陣分解成兩個或多個低維矩陣,低秩分解可以有效減少參數數量,同時保持原始矩陣的大部分信息和結構特征。?
低秩分解的原理
低秩分解基于矩陣的秩(rank),即矩陣中線性獨立行或列的最大數目。低秩分解通過將一個高秩矩陣近似為兩個或多個低秩矩陣的乘積,從而降低參數維度。常見的低秩分解方法包括SVD(奇異值分解)、PCA(主成分分析)、NMF(非負矩陣分解)等。
給定一個矩陣,其低秩分解可以表示為:
其中,和
,k 是低秩近似的秩,通常 k 遠小于 m 和 n。
常見的低秩分解方法
奇異值分解(Singular Value Decomposition, SVD)
原理:SVD將矩陣分解為三個矩陣的乘積,即 ,其中 U 和 V 是正交矩陣, ΣΣ 是對角矩陣,其對角線上的元素為奇異值。
低秩近似:選擇前 k 個最大的奇異值及其對應的奇異向量,得到矩陣的低秩近似
主成分分析(Principal Component Analysis, PCA)
原理:PCA通過對數據進行協方差矩陣的特征值分解,找到數據的主成分,即方差最大的方向。
低秩近似:選擇前 k 個主成分構建新的低維空間,從而實現降維。
非負矩陣分解(Non-negative Matrix Factorization, NMF)
原理:NMF將一個非負矩陣分解為兩個非負矩陣的乘積,即 ,其中 A 和 B 均為非負矩陣。
低秩近似:通過優化目標函數(如平方誤差)找到最優的非負矩陣 A 和 B。
低秩分解的應用
降維:通過低秩分解將高維數據映射到低維空間,從而減少計算復雜度,提高模型效率。
壓縮:在深度學習中,通過低秩分解壓縮權重矩陣,減少模型參數量和存儲需求。
去噪:低秩分解能夠有效去除數據中的噪聲,提高數據的質量和模型的魯棒性。
推薦系統:在協同過濾中,低秩分解用于分解用戶-物品評分矩陣,預測用戶對未評分物品的偏好。
例子
假設有一個權重矩陣,我們希望將其分解為兩個低秩矩陣 A 和 B,其中
和
。通過低秩分解,我們將原始矩陣的參數數量從
減少到
,顯著降低了參數量和計算成本。
三、部分參數微調
部分參數微調(Partial Parameter Tuning)是一種高效優化方法,通過選擇性地微調模型中的一部分參數來實現模型的適應性調整。這個方法適用于大規模預訓練模型(如BERT、GPT等),能夠在節省計算資源的同時保持模型性能。可以大幅減少需要優化的參數數量,降低計算成本和訓練時間。通過限制參數更新的范圍,降低模型過擬合的風險。在計算資源有限的情況下,快速適應新任務或新數據。
部分參數微調的原理
部分參數微調基于以下原則:
凍結大部分參數:保持模型的大部分參數不變,僅微調特定層或參數組。
選擇性解凍:逐步解凍和微調更多層或參數,以逐步適應新的任務或數據。
常見的部分參數微調方法
頂層微調(Top-layer Tuning)
方法:只微調模型的最后幾層或輸出層。這種方法通常用于下游分類或回歸任務,因為頂層參數直接影響模型的最終輸出。
優勢:大幅減少需要優化的參數數量,同時保留預訓練模型提取的底層特征。
中間層微調(Intermediate-layer Tuning)
方法:選擇模型的中間層進行微調。這種方法適用于需要調整模型內部特征表示的任務。
優勢:靈活性較高,可以根據具體任務選擇最相關的層進行微調。
瓶頸層微調(Bottleneck-layer Tuning)
方法:微調網絡中的瓶頸層,即那些對信息流具有瓶頸效應的層(如Transformer中的注意力層)。
優勢:通過微調瓶頸層,可以有效調整模型的表示能力,適應不同的任務需求。
層歸一化參數微調(Layer Normalization Parameter Tuning)
方法:只微調層歸一化(LayerNorm)或批歸一化(BatchNorm)層的參數。
優勢:歸一化層參數較少,但對模型的穩定性和收斂速度有重要影響,因此微調這些參數可以帶來顯著的性能提升。
特定參數組微調(Specific Parameter Group Tuning)
方法:根據任務需求,選擇性地微調特定參數組,如詞嵌入層參數或特定注意力頭的參數。
優勢:精細控制微調過程,優化最相關的參數,節省資源。
代碼實例
凍結大部分層:只微調BERT模型的最后一層Transformer層和輸出層。
from transformers import BertModel, BertForSequenceClassification
import torchmodel = BertForSequenceClassification.from_pretrained('bert-base-uncased')# 凍結所有層
for param in model.bert.parameters():param.requires_grad = False# 只微調最后一層和分類頭
for param in model.bert.encoder.layer[-1].parameters():param.requires_grad = True
for param in model.classifier.parameters():param.requires_grad = Trueoptimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-4)
選擇性解凍:逐步解凍更多的Transformer層進行微調。
# 逐步解凍更多層
for i in range(-1, -4, -1): # 解凍最后三層for param in model.bert.encoder.layer[i].parameters():param.requires_grad = True
四、適配器
適配器(Adapters)是部分參數微調的一種具體實現方法,通過在預訓練模型的特定層中插入小型的適配器模塊來實現模型微調。適配器的設計初衷是為了在保持預訓練模型的大部分參數不變的情況下,實現對新任務的適應性調整。
適配器的原理
適配器的基本思想是將適配器模塊插入到預訓練模型的各個層中,這些模塊通常包含少量參數,并且在微調過程中只更新這些參數。這樣做的好處是減少了需要優化的參數量,同時利用了預訓練模型中已學習到的豐富特征。
適配器的結構
降維層(Down-projection layer)
將輸入特征降維到一個較小的空間。通常是一個線性變換,例如全連接層。,其中, x 是輸入特征,
和
是降維層的權重和偏置。
非線性激活函數
在降維層之后,應用非線性激活函數(如ReLU,)來引入非線性特性。
升維層(Up-projection layer)
將降維后的特征升維回原始空間。也是一個線性變換。
其中,和
是升維層的權重和偏置。
殘差連接
將升維后的特征與原始輸入特征相加,形成殘差連接。這可以幫助模型保持原有的特征表示,同時引入適配器模塊的調整。
適配器模塊的整體結構如下:
適配器的插入位置
適配器模塊可以插入到預訓練模型的不同位置,常見的插入位置包括:
Transformer層內部:在Transformer層的多頭注意力子層和前饋神經網絡子層之間插入適配器模塊。
每個Transformer層之后:在每個Transformer層之后插入適配器模塊。
特定層中:根據任務需求,在特定的層中插入適配器模塊。
代碼實例
import torch
import torch.nn as nn
from transformers import BertModel, BertConfigclass Adapter(nn.Module):def __init__(self, input_dim, bottleneck_dim):super(Adapter, self).__init__()self.down_proj = nn.Linear(input_dim, bottleneck_dim)self.up_proj = nn.Linear(bottleneck_dim, input_dim)self.activation = nn.ReLU()def forward(self, x):z = self.down_proj(x)z = self.activation(z)z = self.up_proj(z)return x + zclass BertWithAdapters(nn.Module):def __init__(self, model_name, adapter_dim):super(BertWithAdapters, self).__init__()self.bert = BertModel.from_pretrained(model_name)self.adapters = nn.ModuleList([Adapter(self.bert.config.hidden_size, adapter_dim) for _ in range(self.bert.config.num_hidden_layers)])def forward(self, input_ids, attention_mask=None, token_type_ids=None):outputs = self.bert(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)sequence_output = outputs[0]for i, adapter in enumerate(self.adapters):sequence_output = adapter(sequence_output)return sequence_output# 使用適配器微調BERT模型
model = BertWithAdapters('bert-base-uncased', adapter_dim=64)
optimizer = torch.optim.Adam(model.adapters.parameters(), lr=1e-4)
五、差分微調(Delta Tuning)
差分微調(Delta Tuning)是一種高效的模型微調方法,通過僅優化與預訓練模型參數的差值部分(增量),而不是整個參數集,從而降低計算和存儲成本。這種方法特別適用于大規模預訓練模型,如BERT、GPT等,在實際應用中能有效地減少微調時的資源消耗。
差分微調的原理
差分微調的基本思想是將模型的參數表示為預訓練參數和微調增量的組合。具體來說,對于預訓練模型的參數 θ,我們在微調過程中引入一個增量參數 Δθ,使得微調后的參數 θ′ 可以表示為:
在訓練過程中,我們只優化增量參數 Δθ,保持預訓練參數 θ 不變。
代碼實例
加載預訓練模型:
from transformers import BertModel, BertConfig# 加載預訓練的BERT模型
model = BertModel.from_pretrained('bert-base-uncased')
pretrained_params = {name: param.clone() for name, param in model.named_parameters()}
定義增量參數
創建增量參數 Δθ,并將其初始化為零。
import torch# 定義增量參數
delta_params = {name: torch.zeros_like(param) for name, param in pretrained_params.items()}
微調模型
在訓練過程中,只更新增量參數 Δθ。
optimizer = torch.optim.Adam(delta_params.values(), lr=1e-4)for epoch in range(num_epochs):for batch in data_loader:# 前向傳播outputs = model(input_ids=batch['input_ids'], attention_mask=batch['attention_mask'])# 計算損失loss = loss_function(outputs, batch['labels'])# 反向傳播optimizer.zero_grad()loss.backward()# 更新增量參數optimizer.step()# 更新模型參數for name, param in model.named_parameters():param.data = pretrained_params[name] + delta_params[name]
完整的demo:
import torch
from transformers import BertModel, BertTokenizerclass DeltaBertModel(nn.Module):def __init__(self, model_name):super(DeltaBertModel, self).__init__()self.bert = BertModel.from_pretrained(model_name)self.delta_params = nn.ParameterDict({name: nn.Parameter(torch.zeros_like(param))for name, param in self.bert.named_parameters()})def forward(self, input_ids, attention_mask=None, token_type_ids=None):outputs = self.bert(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)return outputsdef update_parameters(self):for name, param in self.bert.named_parameters():param.data += self.delta_params[name].data# 初始化模型和優化器
model_name = 'bert-base-uncased'
model = DeltaBertModel(model_name)
optimizer = torch.optim.Adam(model.delta_params.parameters(), lr=1e-4)# 模擬訓練循環
for epoch in range(num_epochs):for batch in data_loader:input_ids = batch['input_ids']attention_mask = batch['attention_mask']labels = batch['labels']# 前向傳播outputs = model(input_ids, attention_mask=attention_mask)logits = outputs[0]# 計算損失loss = loss_function(logits, labels)# 反向傳播optimizer.zero_grad()loss.backward()optimizer.step()# 更新模型參數model.update_parameters()
六、多任務學習(Multi-task Learning)
多任務學習(Multi-task Learning, MTL)是一種機器學習方法,通過同時學習多個相關任務來提高模型的泛化能力和效率。與單任務學習不同,多任務學習旨在通過共享不同任務之間的信息,利用它們的共同特性,提升整體模型的性能。多任務學習在自然語言處理、計算機視覺、推薦系統等領域有廣泛的應用。
多任務學習的原理
多任務學習的基本思想是將多個相關任務放在同一個模型中進行訓練,這些任務共享部分模型參數,從而實現知識的共享和互補。多任務學習的主要目標是通過共享不同任務之間的信息來提高模型的泛化能力,減少過擬合風險。
多任務學習的方法
多任務學習的實現可以通過多種方法,主要包括硬共享(hard parameter sharing)和軟共享(soft parameter sharing)兩種。
硬共享(Hard Parameter Sharing)
硬共享是多任務學習中最常見的方法,多個任務共享模型的部分層或參數。在這種方法中,底層網絡的參數在所有任務之間共享,而任務特定的參數只在各自的任務上進行微調。
import torch
import torch.nn as nnclass SharedModel(nn.Module):def __init__(self):super(SharedModel, self).__init__()self.shared_layers = nn.Sequential(nn.Linear(768, 256),nn.ReLU(),nn.Linear(256, 128),nn.ReLU())self.task1_head = nn.Linear(128, 10) # 任務1的輸出層self.task2_head = nn.Linear(128, 5) # 任務2的輸出層def forward(self, x, task):x = self.shared_layers(x)if task == 'task1':return self.task1_head(x)elif task == 'task2':return self.task2_head(x)# 創建模型和優化器
model = SharedModel()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)# 訓練循環
for epoch in range(num_epochs):for batch in data_loader:inputs, labels, task = batch['inputs'], batch['labels'], batch['task']optimizer.zero_grad()outputs = model(inputs, task)loss = loss_function(outputs, labels)loss.backward()optimizer.step()
軟共享(Soft Parameter Sharing)
軟共享通過為每個任務定義獨立的模型參數,同時在訓練過程中通過正則化項使得不同任務的參數盡可能相似。這種方法保留了任務特定的特征,同時實現了信息共享。
class SoftSharedModel(nn.Module):def __init__(self):super(SoftSharedModel, self).__init__()self.task1_layers = nn.Sequential(nn.Linear(768, 256),nn.ReLU(),nn.Linear(256, 128),nn.ReLU())self.task2_layers = nn.Sequential(nn.Linear(768, 256),nn.ReLU(),nn.Linear(256, 128),nn.ReLU())self.shared_layers = nn.Sequential(nn.Linear(128, 64),nn.ReLU(),nn.Linear(64, 32),nn.ReLU())self.task1_head = nn.Linear(32, 10)self.task2_head = nn.Linear(32, 5)def forward(self, x, task):if task == 'task1':x = self.task1_layers(x)elif task == 'task2':x = self.task2_layers(x)x = self.shared_layers(x)if task == 'task1':return self.task1_head(x)elif task == 'task2':return self.task2_head(x)# 創建模型和優化器
model = SoftSharedModel()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)# 訓練循環
for epoch in range(num_epochs):for batch in data_loader:inputs, labels, task = batch['inputs'], batch['labels'], batch['task']optimizer.zero_grad()outputs = model(inputs, task)loss = loss_function(outputs, labels)loss.backward()optimizer.step()
代碼實例?
from transformers import BertModel, BertTokenizerclass MultiTaskBertModel(nn.Module):def __init__(self, model_name):super(MultiTaskBertModel, self).__init__()self.bert = BertModel.from_pretrained(model_name)self.shared_layers = nn.Linear(768, 128)self.task1_head = nn.Linear(128, 10) # 任務1:文本分類self.task2_head = nn.Linear(128, 5) # 任務2:情感分析def forward(self, input_ids, attention_mask=None, token_type_ids=None, task=None):outputs = self.bert(input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids)pooled_output = outputs[1] # 使用BERT的[CLS]標記的輸出shared_output = self.shared_layers(pooled_output)if task == 'task1':return self.task1_head(shared_output)elif task == 'task2':return self.task2_head(shared_output)# 創建模型和優化器
model_name = 'bert-base-uncased'
model = MultiTaskBertModel(model_name)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)# 訓練循環
for epoch in range(num_epochs):for batch in data_loader:input_ids = batch['input_ids']attention_mask = batch['attention_mask']labels = batch['labels']task = batch['task']optimizer.zero_grad()outputs = model(input_ids, attention_mask=attention_mask, task=task)loss = loss_function(outputs, labels)loss.backward()optimizer.step()