拿LoRA代碼來(lái)微調(diào)大模型
1 簡(jiǎn)介L(zhǎng)oRA
上一期介紹了如何復(fù)用免費(fèi)的大模型源代碼,來(lái)搭配企業(yè)的專有數(shù)據(jù)而訓(xùn)練出形形色色的自用小模型。免費(fèi)代碼既省成本、可靠、省算力、又自有IP,可謂取之不盡、用之不竭的資源,豈不美哉!
重頭開(kāi)始訓(xùn)練自己的小模型,是一條鳥(niǎo)語(yǔ)花香之路。然而,基于別人的預(yù)訓(xùn)練(Pre-trained) 大模型,搭配自有數(shù)據(jù)而進(jìn)行微調(diào)(Fine-tuning),常常更是一條康莊大道。
隨著LLM 等大模型日益繁榮發(fā)展,基于這些大模型的遷移學(xué)習(xí)(Transfer learning),將其預(yù)訓(xùn)練好的模型加以微調(diào)(Fine tune),來(lái)適應(yīng)到下游的各項(xiàng)新任務(wù),已經(jīng)成為熱門(mén)的議題。關(guān)于微調(diào)技術(shù),其中LoRA 是一種資源消耗較小的訓(xùn)練方法,它能在較少訓(xùn)練參數(shù)時(shí)就得到比較穩(wěn)定的效果。
由于LoRA 的外掛模型參數(shù)非常輕量,對(duì)于各個(gè)下游任務(wù)來(lái)說(shuō),只需要搭配特定的訓(xùn)練數(shù)據(jù),并獨(dú)立維護(hù)自身的LoRA 參數(shù)即可。在訓(xùn)練時(shí)可以凍結(jié)原模型( 如ResNet50 或MT5) 的既有參數(shù),只需要更新較輕量的LoRA 參數(shù)即可,因而微調(diào)訓(xùn)練的效率很高。
LoRA 的全名是:Low-Rank Adaptation of Large Language Models( 及大語(yǔ)言模型的低階適應(yīng))。使用這種LoRA 微調(diào)方法進(jìn)行訓(xùn)練時(shí),并不需要調(diào)整原( 大)模型的參數(shù)值( 圖1 里的藍(lán)色部分),而只需要訓(xùn)練LoRA 模型的參數(shù)( 圖1 里的棕色部分)。
圖1 LoRA的架構(gòu)
(引自:https://heidloff.net/article/efficient-fine-tuning-lora/)
典型的LoRA 微調(diào)途徑是,使用下游任務(wù)的數(shù)據(jù)來(lái)對(duì)< 原模型 + LoRA> 進(jìn)行重新訓(xùn)練,讓該協(xié)同模型的性能在該下游任務(wù)上表現(xiàn)出最佳效果。
2 簡(jiǎn)介ResNet50
ResNet50是很通用的AI模型,他擅長(zhǎng)于圖像的特征提取(Feature extraction),然后依據(jù)特征來(lái)進(jìn)行分類(Classification)。所以,它能幫您瞬間探索任何一張圖像的特征,然后幫您識(shí)別出圖片里的人或物的種類。目前的ResNet50 可以準(zhǔn)確地識(shí)別出1000 種人或物,如日常生活中常遇到的狗、貓、食物、汽車和各種家居物品等。
3 下載LoRA源代碼
首先訪問(wèn)這個(gè)cccntu 網(wǎng)頁(yè),從Github 下載minLoRA源碼 ( 圖2)。
圖2 Github上的免費(fèi)LoRA源碼
然后,按下<code> 就自動(dòng)把minLoRA 源碼下載到本機(jī)里了。接著,把所下載的源代碼壓縮檔解開(kāi),放置于Wibdows 本機(jī)的Python 工作區(qū)里,例如 /Python310/目錄區(qū)里( 圖3)。
圖3 放置于本機(jī)的Python環(huán)境里
這樣,就能先在本機(jī)里做簡(jiǎn)單的測(cè)試,例如創(chuàng)建模型并拿簡(jiǎn)單數(shù)據(jù)( 或假數(shù)據(jù)) 來(lái)測(cè)試,有助于提升成功的自信心。
4 展開(kāi)微調(diào)訓(xùn)練
Step 1:準(zhǔn)備訓(xùn)練&測(cè)試數(shù)據(jù)
首先,準(zhǔn)備了/ox_lora_data/train/ 訓(xùn)練圖像集,包含2 個(gè)類--- 水母(Jellyfish) 和蘑菇(Mushroom),各有12 張圖像,如圖4。
圖4 12張圖像實(shí)例
此外,也準(zhǔn)備了/ox_lora_data/test/ 測(cè)試圖像集,也是水母和蘑菇,各有8 張圖像。
Step 2:準(zhǔn)備ResNet50預(yù)訓(xùn)練模型
本范例從torchvision.models 里加載resnet50 預(yù)訓(xùn)練模型。這ResNet50 屬于大模型,其泛化能力很好。然而,然而對(duì)于本范例的較少類的預(yù)測(cè)( 推論) 準(zhǔn)確度就常顯得不足?,F(xiàn)在,就拿本范例的測(cè)試圖像集,來(lái)檢測(cè)一下。程序碼如下:
# 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) 的正確辨識(shí)率:”, j / 8)
print(“n 蘑菇(Mushroom) 的正確辨識(shí)率:”, m / 8)
#------------------
#END
在本范例里,其圖像分為2 個(gè)類:水母和蘑菇。所以在此程序里,其< 水母、蘑菇> 的類標(biāo)簽(Label)分別為:[0, 1]。而在ResNet50 預(yù)訓(xùn)練模型里,其<水母、蘑菇> 類標(biāo)簽分別為:[107, 947]。于是,使用process_lx() 函數(shù),來(lái)把此程序里的類標(biāo)簽,轉(zhuǎn)換為ResNet50 的類別標(biāo)簽值。在此范例里,我們拿測(cè)試數(shù)據(jù)集里的< 水母、蘑菇> 各8 張圖像來(lái)給ResNet50 進(jìn)行分類預(yù)測(cè)。執(zhí)行時(shí),輸出如下:
-2
這顯示出:蘑菇的預(yù)測(cè)準(zhǔn)確度為:0.125,并不理想。亦即,可以觀察到了,大模型ResNet50 在這范例里的下游任務(wù)上,其預(yù)測(cè)的準(zhǔn)確度并不美好。于是,LoRA微調(diào)方法就派上用場(chǎng)了。
Step 3:定義LoRA模型,并展開(kāi)協(xié)同訓(xùn)練茲回顧LoRA 的架構(gòu)( 圖1)。在剛才的范例里,我們加載的ResNet50 模型,就是上圖里的Pretrained Weights( 即藍(lán)色) 部分?,F(xiàn)在,就準(zhǔn)備添加LoRA 模型,也就是上圖里的A 和B( 即棕色) 部分。
當(dāng)我們把A&B 部分添加上去了,就能展開(kāi)協(xié)同訓(xùn)練了。在協(xié)同訓(xùn)練時(shí),我們會(huì)先凍結(jié)Pretrained Weights部分的參數(shù),不去更改它;而只更新LoRA 的A&B 參數(shù)。一旦協(xié)同訓(xùn)練完成了,就會(huì)把LoRA 與ResNet50 的參數(shù)合并起來(lái)( 即上圖右方的橘色部分。請(qǐng)來(lái)看看程序碼:
# 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/’
#----------------------------------
# 把圖片轉(zhuǎn)換成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 參數(shù)添加到原模型 ------
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 模型, 協(xié)同訓(xùn)
練 ------’)
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) 的正確辨識(shí)率:”, j / 8)
print(“n 蘑菇(Mushroom) 的正確辨識(shí)率:”, m / 8)
#END
在此范例程序里, 把minLoRA 的源代碼, 與ResNet50預(yù)訓(xùn)練模型結(jié)合,展開(kāi)100 回合的微調(diào)協(xié)同訓(xùn)練。并輸出如下:
從上述的輸出結(jié)果,于是我們可以觀察到,當(dāng)ResNet50 在未加掛LoRA 時(shí),其< 蘑菇> 測(cè)試的預(yù)測(cè)準(zhǔn)確率是:0.125。當(dāng)我們完成協(xié)同訓(xùn)練100 回合之后,其預(yù)測(cè)準(zhǔn)確度提升到:0.75,達(dá)到微調(diào)的目的了。
5 結(jié)束語(yǔ)
本文就ResNet50 為例,說(shuō)明如何拿LoRA 源代碼,來(lái)對(duì)ResNet50 進(jìn)行微調(diào)。您已經(jīng)發(fā)現(xiàn)到了,微調(diào)可以讓ResNet50 更加貼心,滿足您的需求。這種途徑可以適用于各種大模型,例如MT5 大語(yǔ)言模型、以及StableDiffusion繪圖大模型等。
(本文來(lái)源于《EEPW》2024.1-2)
評(píng)論