作者:高煥堂、鄭仲平
本文將說明如何善用英特爾(Intel)所研發的新產品和相關技術,來提升AI模型的訓練效率,以便提供更優質的推論功能。其中,英特爾推出了兩項核心產品:
- 核心軟體是:Intel-extension-for-pytorch。
- 核心硬體是:Intel(R) Data Center GPU Flex 170。
而與以上核心產品息息相關的重要技術是:19381
- BF16浮點數精度。
- 資料格式設為 Channel_last資料格式(即是NHWC格式)。
為了循序漸進介紹上述的新產品及其相關的使用技術,本文先從大家熟悉ResNet50圖像分類模型做為起步範例,並搭配LoRA來進行外掛訓練,以便從這簡單範例中充分理解如何活用上述的創新產品及相關技術。
<ResNet50+LoRA>訓練範例
簡介ResNet50
ResNet50是很通用的AI模型,擅長於圖像的特徵提取(Feature extraction),然後依據特徵來進行分類(Classification)。所以,它能幫您瞬間探索任何一張圖像的特徵,然後幫您識別出圖片裡的人或物的種類。目前的ResNet50可以準確地識別出1,000種人或物,如日常生活中常遇到的狗、貓、食物、汽車和各種家居物品等。例如,您可以從百度圖片上截取一張224x224大小的圖片(圖1):

圖1:輸入給ResNet50的圖像
當您把這圖片提交給ResNet50,它會瞬間探索並進行分類,然後告訴您:我預測這是大熊貓(Giant panda)。
簡介LoRA
隨著大語言模型(LLM)等大模型日益繁榮發展,基於這些大模型的遷移學習(Transfer learning),將其預訓練好的模型加以微調(Fine tune),來適應到下游的各項新任務,已經成為熱門的議題。關於微調技術,其中LoRA是一種資源消耗較小的訓練方法,它能在較少訓練參數時就得到比較穩定的效果。
LoRA的全名是:Low-Rank Adaptation of Large Language Models (及大語言模型的低階適應)。使用這種LoRA微調方法進行訓練時,並不需要調整原(大)模型的參數值(圖2裡的藍色部分),而只需要訓練LoRA模型的參數(圖2裡的棕色部分)。

圖2:LoRA的架構圖 (來源:https://deci.ai/blog/fine-tune-llama-2-with-lora-for-question-answering/)
典型的LoRA微調途徑是,使用下游任務的資料來對<原模型 + LoRA>進行重新訓練,讓該協同模型的性能在該下游任務上表現出最佳效果。
在一般CPU環境裡訓練ResNet50
先來觀察一般CPU環境的ResNet50訓練流程。
Step-1:準備訓練資料(Training data)
首先,準備了/oopc/m_data/train/圖像集,包含三個類別—斑馬、貓頭鷹和蘑菇:
各類別都有80張圖像,例如斑馬圖像:
總共有240張圖像。
Step-2: 準備ResNet50預訓練模型
本範例從torchvision.models裡載入resnet50預訓練模型。程式碼如下:
# RESNET_basic_001.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
data_path = 'c:/oopc/m_data/train/'
#------------------------------------
# Make torch deterministic
# 設定:每次訓練的W&B初始化都是一樣的
_ = torch.manual_seed(0)
#-------------------------------------
# 載入ResNet50預訓練模型
resnet_model = models.resnet50(
weights=models.ResNet50_Weights.IMAGENET1K_V1)
# 遷移學習不需要梯度(不更改權重)
for param in resnet_model.parameters():
param.requires_grad = False
#------------------------------------------
resnet_model.eval()
#------------------------------------------
# 準備Training data
# 把圖片轉換成Tensor
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor()
])
data_set = ImageFolder(data_path, transform=transform)
length = len(data_set)
print('\n', length, "張圖片")
print('本地分類標籤:斑馬-->0, 貓頭鷹-->1, 蘑菇-->2')
bz = 10
train_loader = DataLoader(data_set, batch_size=bz, shuffle=True)
for idx, (images, labels) in enumerate(train_loader):
break
print('\n本地讀取的類別Labels:')
print(labels)
#--------------------------------------
def process_lx(labels):
lx = labels.clone()
for i in range(bz):
if(labels[i]==0):
lx[i]=340
elif(labels[i]==1):
lx[i]=24
elif(labels[i]==2):
lx[i]=947
return lx
print('\n轉換為ResNet50的類別Labels:')
res_labels = process_lx(labels)
print(res_labels)
#------------------------------------------------------
#END
在本範例裡,其圖像分為3個類別:斑馬、貓頭鷹、蘑菇。所以在此程式裡,其<斑馬、貓頭鷹、蘑菇>的類別標籤(Label)分別為:[0, 1, 2]。而在ResNet50預訓練模型裡,其<斑馬、貓頭鷹、蘑菇>類別標籤分別為:[340, 24, 947]。於是,使用process_lx()函數,來把此程式裡的類別標籤,轉換為ResNet50的類別標籤值。 當我們在Windows環境裡執行這個程式時,會輸出如下:
Step-3: 使用ResNet50預訓練模型進行推論
這ResNet50屬於大模型,其泛化能力很好。然而,對於本範例的較少類別的預測(推論)準確度就常顯得不足。現在,就拿本範例的訓練圖像集來檢測一下。程式碼如下:
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
data_path = 'c:/oopc/m_data/train/'
#------------------------------------
# Make torch deterministic
# 設定:每次訓練的W&B初始化都是一樣的
_ = torch.manual_seed(0)
#-------------------------------------
# 載入ResNet50預訓練模型
resnet_model = models.resnet50(
weights=models.ResNet50_Weights.IMAGENET1K_V1)
# 遷移學習不需要梯度(不更改權重)
for param in resnet_model.parameters():
param.requires_grad = False
#------------------------------------------
resnet_model.eval()
#------------------------------------------
# 準備Training data
# 把圖片轉換成Tensor
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor()
])
data_set = ImageFolder(data_path, transform=transform)
length = len(data_set)
bz = 10
train_loader = DataLoader(data_set, batch_size=bz, shuffle=True)
#--------------------------------------
def process_lx(labels):
lx = labels.clone()
for i in range(bz):
if(labels[i]==0):
lx[i]=340
elif(labels[i]==1):
lx[i]=24
elif(labels[i]==2):
lx[i]=947
return lx
#=======================================
def test():
correct = 0
total = 0
wrong_counts = [0 for i in range(3)] # 本範例有3類別
with torch.no_grad():
for idx, (images, la) in enumerate(train_loader):
labels = process_lx(la)
prediction = resnet_model(images)
for idx, zv in enumerate(prediction):
if torch.argmax(zv) == labels[idx]:
correct +=1
else:
#print(idx)
wrong_counts[la[idx]] +=1
total +=1
#print(correct)
print('\n', f'Accuracy: {round(correct/total, 3)}')
for i in range(len(wrong_counts)):
print(f'wrong counts for the class {i}: {wrong_counts[i]}')
#------------------------------------------
test()
#------------------------------------------
#END
在此程式裡,我們拿訓練資料集裡的<斑馬、貓頭鷹、蘑菇>共240張圖像來給ResNet50進行分類預測。當我們在Windows環境裡執行這個程式時,會輸出如下:
這顯示出:預測準確度為:0.358。其中,有24張斑馬(class_0)圖像預測錯誤。也有64張斑馬(class_1)圖像預測錯誤。還有66張蘑菇(class_2)圖像預測錯誤。
可以觀察到了,大模型ResNet50在這範例裡的下游任務上,其預測的準確度並不美好。於是,LoRA微調方法就派上用場了。
Step-4: 定義LoRA模型,並展開協同訓練
茲回顧LoRA的架構(圖3):

圖3:更詳細的LoRA的架構圖 (來源:https://huggingface.co/docs/peft/conceptual_guides/lora/)
在剛才的範例裡,我們載入的ResNet50模型,就是上圖裡的Pretrained Weights(即藍色)部分。現在,就準備添加LoRA模型,也就是上圖裡的A和B(即棕色)部分。
OpenVINO更多的新功能陸續問世!新版本將新增對新硬體以及更多生成式AI模型的支援,在2024年的新開始,讓我們一起透過免費線上講座「掌握OpenVINO在生成式AI最新功能, 並實現Model Server創新應用」來探索這套越來越強大的工具,讓您的專案開發更如虎添翼,名額有限快來報名!
當我們把A&B部分添加上去了,就能展開協同訓練了。在協同訓練時,我們會先凍結Pretrained Weights部分的參數,不去更改它;而只更新LoRA的A&B參數。
一旦協同訓練完成了,就會把LoRA與ResNet50的參數合併起來(即上圖右方的橘色部分。請來看看程式碼:
# lora_RESNET_basic_003_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
data_path = 'c:/oopc/m_data/train/'
base_path = 'c:/oopc/m_data/'
#------------------------------------
# Make torch deterministic
# 設定:每次訓練的W&B初始化都是一樣的
_ = torch.manual_seed(0)
#-------------------------------------
# 載入ResNet50預訓練模型
resnet_model = models.resnet50(
weights=models.ResNet50_Weights.IMAGENET1K_V1)
# 遷移學習不需要梯度(不更改權重)
for param in resnet_model.parameters():
param.requires_grad = False
#-----------------------------------------
resnet_model.eval()
#------------------------------------------
# 準備Training data
# 把圖片轉換成Tensor
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor()
])
data_set = ImageFolder(data_path, transform=transform)
length = len(data_set)
bz = 60
train_loader = DataLoader(data_set, batch_size=bz, shuffle=True)
#--------------------------------------
def process_lx(labels):
lx = labels.clone()
for i in range(bz):
if(labels[i]==0):
lx[i]=340
elif(labels[i]==1):
lx[i]=24
elif(labels[i]==2):
lx[i]=947
return lx
#=======================================
def test():
correct = 0
total = 0
wrong_counts = [0 for i in range(3)] # 本範例有3類別
with torch.no_grad():
for idx, (images, la) in enumerate(train_loader):
labels = process_lx(la)
prediction = resnet_model(images)
for idx, zv in enumerate(prediction):
if torch.argmax(zv) == labels[idx]:
correct +=1
else:
wrong_counts[la[idx]] +=1
total +=1
#print(correct)
print('\n', f'Accuracy: {round(correct/total, 3)}')
for i in range(len(wrong_counts)):
print(f'wrong counts for the class {i}: {wrong_counts[i]}')
#------------------------------------------
test()
#====== 定義LoRA模型 ===============================
class Lora(nn.Module):
def __init__(self, m, n, rank=10):
super().__init__()
self.m = m
self.A = nn.Parameter(torch.randn(m, rank))
self.B = nn.Parameter(torch.zeros(rank, n))
def forward(self, inputs):
inputs = inputs.view(-1, self.m)
return torch.mm(torch.mm(inputs, self.A), self.B)
lora = Lora(224 * 224 * 3, 1000)
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(lora.parameters(), lr=1e-4)
#----------------------------------------------------
print('\n------ 外掛LoRA模型,協同訓練: ------')
base = 0
epochs = 100
for ep in range(epochs+1):
total_loss = 0
for idx, (images, la) in enumerate(train_loader):
labels = process_lx(la)
pred = resnet_model(images) + lora(images)
loss = loss_fn(pred, labels)
loss.backward()
optimizer.step()
optimizer.zero_grad()
total_loss += loss.item() * bz
if((base+ep)%5 == 0):
loss_np = total_loss / 240
print('ep=', base+ep,'/',base+epochs, ', loss=', loss_np)
#------ Saved to *.CKPT ---------------------------------------
FILE = base_path + 'LORA_for_RESNET_ep100.ckpt'
torch.save(lora.state_dict(), FILE)
print('\nsaved to ' + FILE)
#-------------------------------------------------------------------
def test_lora_resnet():
correct = 0
total = 0
wrong_counts = [0 for i in range(3)]
with torch.no_grad():
for idx, (images, la) in enumerate(train_loader):
labels = process_lx(la)
prediction = resnet_model(images)+ lora(images)
for idx, zv in enumerate(prediction):
if torch.argmax(zv) == labels[idx]:
correct +=1
else:
wrong_counts[la[idx]] +=1
total +=1
print('\n', f'Accuracy: {round(correct/total, 3)}')
for i in range(len(wrong_counts)):
print(f'wrong counts for the class {i}: {wrong_counts[i]}')
#----------------------------------------------------------------
print('\n------LoRA 協同測試: ------')
test_lora_resnet()
#-----------------------------------------------------------------
#END
請仔細觀察這程式的執行情境:單純使用CPU環境(沒有使用GPU)搭配一般Pytorch框架,並在Windows或Ubuntu裡展開協同訓練。
例如,在Ubuntu環境裡,使用命令(Command):
就展開訓練100回合,執行結果顯示如下圖:

圖4:訓練環境是:CPU+Pytorch 3.10搭配Ubuntu
從上述的輸出結果,於是我們可以觀察到:
【協同訓練之前的預測準確率】 當ResNet50在未加掛 LoRA時,其預測的準確率是:0.358。且斑馬圖像的誤辨次數為:24;而貓頭鷹的誤判次數則是:64,至於蘑菇的誤判次數為: 66。
【協同訓練花費時間】 這是單純使用CPU環境下進行訓練(沒有使用GPU),我們把LoRA外掛到ResNet50,然後進行協同訓練100回合,其訓練的耗時為:405 秒。
【協同訓練之後的預測準確率】 當我們完成協同訓練100回合之後,再拿訓練資料集的<斑馬、貓頭鷹、蘑菇>圖像(一樣240張)來檢驗看看<ResNet50 + LoRA>整合模型的預測能力。結果發現了,這整合模型的預測準確度高達100%了。亦即,誤判次數皆是:0。
結語
在這篇文章中,我們先以單純CPU環境來訓練<ResNet50 + LoRA>的整合模型;並記錄其訓練100回合的花費時間長度(即405秒)。
接下來的文章我們將使用更高等級的Intel GPU環境來進行<ResNet50 + LoRA>的協同訓練,並展示相關的軟硬體技術,也呈現各種等級GPU環境的訓練效能提升的幅度,成為促進AI生態未來發展的強大力量。
- LoRA微調三步驟:以大語言模型MT5為例 - 2024/05/02
- 為什麼Gemma採取Decoder-Only Transformer架構呢? - 2024/04/08
- 如何從0訓練企業自用Gemma模型 - 2024/04/03
訂閱MakerPRO知識充電報
與40000位開發者一同掌握科技創新的技術資訊!