拿LoRA代碼來微調大模型
1 簡介LoRA
上一期介紹了如何復用免費的大模型源代碼,來搭配企業的專有數據而訓練出形形色色的自用小模型。免費代碼既省成本、可靠、省算力、又自有IP,可謂取之不盡、用之不竭的資源,豈不美哉!
重頭開始訓練自己的小模型,是一條鳥語花香之路。然而,基于別人的預訓練(Pre-trained) 大模型,搭配自有數據而進行微調(Fine-tuning),常常更是一條康莊大道。
隨著LLM 等大模型日益繁榮發展,基于這些大模型的遷移學習(Transfer learning),將其預訓練好的模型加以微調(Fine tune),來適應到下游的各項新任務,已經成為熱門的議題。關于微調技術,其中LoRA 是一種資源消耗較小的訓練方法,它能在較少訓練參數時就得到比較穩定的效果。
由于LoRA 的外掛模型參數非常輕量,對于各個下游任務來說,只需要搭配特定的訓練數據,并獨立維護自身的LoRA 參數即可。在訓練時可以凍結原模型( 如ResNet50 或MT5) 的既有參數,只需要更新較輕量的LoRA 參數即可,因而微調訓練的效率很高。
LoRA 的全名是:Low-Rank Adaptation of Large Language Models( 及大語言模型的低階適應)。使用這種LoRA 微調方法進行訓練時,并不需要調整原( 大)模型的參數值( 圖1 里的藍色部分),而只需要訓練LoRA 模型的參數( 圖1 里的棕色部分)。
圖1 LoRA的架構
(引自:https://heidloff.net/article/efficient-fine-tuning-lora/)
典型的LoRA 微調途徑是,使用下游任務的數據來對< 原模型 + LoRA> 進行重新訓練,讓該協同模型的性能在該下游任務上表現出最佳效果。
2 簡介ResNet50
ResNet50是很通用的AI模型,他擅長于圖像的特征提取(Feature extraction),然后依據特征來進行分類(Classification)。所以,它能幫您瞬間探索任何一張圖像的特征,然后幫您識別出圖片里的人或物的種類。目前的ResNet50 可以準確地識別出1000 種人或物,如日常生活中常遇到的狗、貓、食物、汽車和各種家居物品等。
3 下載LoRA源代碼
首先訪問這個cccntu 網頁,從Github 下載minLoRA源碼 ( 圖2)。
圖2 Github上的免費LoRA源碼
然后,按下<code> 就自動把minLoRA 源碼下載到本機里了。接著,把所下載的源代碼壓縮檔解開,放置于Wibdows 本機的Python 工作區里,例如 /Python310/目錄區里( 圖3)。
圖3 放置于本機的Python環境里
這樣,就能先在本機里做簡單的測試,例如創建模型并拿簡單數據( 或假數據) 來測試,有助于提升成功的自信心。
4 展開微調訓練
Step 1:準備訓練&測試數據
首先,準備了/ox_lora_data/train/ 訓練圖像集,包含2 個類--- 水母(Jellyfish) 和蘑菇(Mushroom),各有12 張圖像,如圖4。
圖4 12張圖像實例
此外,也準備了/ox_lora_data/test/ 測試圖像集,也是水母和蘑菇,各有8 張圖像。
Step 2:準備ResNet50預訓練模型
本范例從torchvision.models 里加載resnet50 預訓練模型。這ResNet50 屬于大模型,其泛化能力很好。然而,然而對于本范例的較少類的預測( 推論) 準確度就常顯得不足?,F在,就拿本范例的測試圖像集,來檢測一下。程序碼如下:
# Lora_ResNet50_001_test.py
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import Dataset, DataLoader
import torchvision.models as models
path = ‘c:/ox_lora_data/’
#----------------------------------
model = models.resnet50(
w e i g h t s = m o d e l s . R e s N e t 5 0 _We i g h t s .
IMAGENET1K_V1)
#----------------------------------
def process_lx(labels, batch_size):
lx = labels.clone()
for i in range(batch_size):
if(labels[i]==0): lx[i]=107
elif(labels[i]==1): lx[i]=947
return lx
#----------------------------------
T = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor()
])
#----------------------------------
test_ds = ImageFolder(path + ‘test/’, transform=T)
test_dl = DataLoader(test_ds, batch_size=1)
model.eval()
with torch.no_grad():
j, m = (0, 0)
for idx, (image, la) in enumerate(test_dl):
labels = process_lx(la, 1)
pred = model(image)
k = torch.argmax(pred[0])
if(la[0]==0 and k==107):
j += 1
elif(la[0] == 1 and k==947):
m += 1
print(“n 水母(Jellyfish) 的正確辨識率:”, j / 8)
print(“n 蘑菇(Mushroom) 的正確辨識率:”, m / 8)
#------------------
#END
在本范例里,其圖像分為2 個類:水母和蘑菇。所以在此程序里,其< 水母、蘑菇> 的類標簽(Label)分別為:[0, 1]。而在ResNet50 預訓練模型里,其<水母、蘑菇> 類標簽分別為:[107, 947]。于是,使用process_lx() 函數,來把此程序里的類標簽,轉換為ResNet50 的類別標簽值。在此范例里,我們拿測試數據集里的< 水母、蘑菇> 各8 張圖像來給ResNet50 進行分類預測。執行時,輸出如下:
-2
這顯示出:蘑菇的預測準確度為:0.125,并不理想。亦即,可以觀察到了,大模型ResNet50 在這范例里的下游任務上,其預測的準確度并不美好。于是,LoRA微調方法就派上用場了。
Step 3:定義LoRA模型,并展開協同訓練茲回顧LoRA 的架構( 圖1)。在剛才的范例里,我們加載的ResNet50 模型,就是上圖里的Pretrained Weights( 即藍色) 部分?,F在,就準備添加LoRA 模型,也就是上圖里的A 和B( 即棕色) 部分。
當我們把A&B 部分添加上去了,就能展開協同訓練了。在協同訓練時,我們會先凍結Pretrained Weights部分的參數,不去更改它;而只更新LoRA 的A&B 參數。一旦協同訓練完成了,就會把LoRA 與ResNet50 的參數合并起來( 即上圖右方的橘色部分。請來看看程序碼:
# Lora_ResNet50_002_train.py
import numpy as np
import torch
import torch.nn as nn
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torch.utils.data import Dataset, DataLoader
import torchvision.models as models
from functools import partial
import min_lora_model as Min_LoRA
import min_lora_utils as Min_LoRA_Util
path = ‘c:/ox_lora_data/’
#----------------------------------
# 把圖片轉換成Tensor
T = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor()
])
def process_lx(labels, batch_size):
lx = labels.clone()
for i in range(batch_size):
if(labels[i]==0): lx[i]=107
elif(labels[i]==1): lx[i]=947
return lx
#----------------------------------
model = models.resnet50(
weight s =mode l s .ResNet50_We ight s .
IMAGENET1K_V1)
#-------- 添加LoRA --------
my_lora_config = { nn.Linear: { “weight”: partial(
Min_LoRA.LoRAParametrization.from_linear,
rank=16),
}, }
#---- 把LoRA 參數添加到原模型 ------
Min_LoRA.add_lora(model, lora_config=my_lora_
config)
parameters = [
{ “params”: list(Min_LoRA_Util.get_lora_
params(model))}, ]
# 只更新LoRA 的Weights
optimizer = torch.optim.Adam(parameters, lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
model.train()
bz = 4
train_ds = ImageFolder(path + ‘ train/ ’ ,
transform=T)
train_dl = DataLoader(train_ds, batch_size=bz,
shuffle=True)
length = len(train_ds)
#----------------------------------
print(‘n------ 外掛LoRA 模型, 協同訓
練 ------’)
epochs = 25
for ep in range(epochs+1):
total_loss = 0
for idx, (images, la) in enumerate(train_dl):
labels = process_lx(la, bz)
pred = model(images)
loss = loss_fn(pred, labels)
loss.backward()
optimizer.step()
optimizer.zero_grad()
total_loss += loss.item() * bz
if(ep%5 == 0):
print(‘ ep=’, ep, ‘, loss=’, total_loss /
length )
#-------------- testing ---------------
test_ds = ImageFolder(path + ‘test/’, transform=T)
test_dl = DataLoader(test_ds, batch_size=1)
model.eval()
with torch.no_grad():
j, m = (0, 0)
for idx, (image, la) in enumerate(test_dl):
labels = process_lx(la, 1)
pred = model(image)
k = torch.argmax(pred[0])
if(la[0]==0 and k==107): j += 1
elif(la[0] == 1 and k==947): m += 1
print(“n 水母(Jellyfish) 的正確辨識率:”, j / 8)
print(“n 蘑菇(Mushroom) 的正確辨識率:”, m / 8)
#END
在此范例程序里, 把minLoRA 的源代碼, 與ResNet50預訓練模型結合,展開100 回合的微調協同訓練。并輸出如下:
從上述的輸出結果,于是我們可以觀察到,當ResNet50 在未加掛LoRA 時,其< 蘑菇> 測試的預測準確率是:0.125。當我們完成協同訓練100 回合之后,其預測準確度提升到:0.75,達到微調的目的了。
5 結束語
本文就ResNet50 為例,說明如何拿LoRA 源代碼,來對ResNet50 進行微調。您已經發現到了,微調可以讓ResNet50 更加貼心,滿足您的需求。這種途徑可以適用于各種大模型,例如MT5 大語言模型、以及StableDiffusion繪圖大模型等。
(本文來源于《EEPW》2024.1-2)
評論