多模態(tài)大模型:算法、應(yīng)用與微調(diào)
用LoRA訓(xùn)練Stable Diffusion時,首先凍結(jié)模型的權(quán)重,然后在U-Net結(jié)構(gòu)中注入LoRA模型,將其與交叉注意力模塊結(jié)合在一起,最后微調(diào)時將只對這部分參數(shù)進(jìn)行更新。
數(shù)據(jù)收集
本次微調(diào)將使用數(shù)碼寶貝數(shù)據(jù)集作為下游細(xì)分任務(wù),數(shù)據(jù)來源于數(shù)碼獸數(shù)據(jù)庫(http://digimons.net/digimon/chn.html)。Stable Diffusion的訓(xùn)練數(shù)據(jù)非常直觀,就是一張圖片對應(yīng)一段文本描述,因此我們需要先通過一個爬蟲將數(shù)據(jù)整理下來。
我們需要從數(shù)碼獸圖鑒網(wǎng)頁中爬取所有數(shù)碼獸的信息,包括名稱、介紹和對應(yīng)的圖片鏈接。然后將這些信息整理成下面這種格式,保存到對應(yīng)的文件夾中。
# 數(shù)據(jù)格式 metadata.jsonl + 圖片
folder/train/metadata.jsonl #存儲caption描述
folder/train/0001.png
folder/train/0002.png
folder/train/0003.png
# metadata.jsonl中的內(nèi)容
{"file_name": "0001.png", "text": "image 1 description"}
{"file_name": "0002.png", "text": "image 2 description"}
{"file_name": "0003.png", "text": "image 3 description"}
為了實現(xiàn)這個任務(wù),我們需要使用Python的爬蟲和文件操作相關(guān)的庫。首先,使用requests庫獲取數(shù)碼獸圖鑒頁面的HTML內(nèi)容,并使用BeautifulSoup庫解析HTML,以便對頁面進(jìn)行提取信息。然后我們分析這個頁面,所有的數(shù)碼獸都存在一個id為digimon_list的ul列表中,每一行就是一個li標(biāo)簽,這個標(biāo)簽里面有一個a標(biāo)簽,對應(yīng)了該數(shù)碼獸的詳情鏈接。
接下來,我們遍歷頁面中所有的li標(biāo)簽,提取數(shù)碼獸的名稱和詳情頁面鏈接。然后進(jìn)入詳情頁面,獲取數(shù)碼獸的介紹和圖片鏈接。最后,將這些信息整理成指定的格式,并保存到對應(yīng)的文件夾中。具體而言,我們需要在指定的文件夾中創(chuàng)建一個metadata.jsonl文件來保存每個圖片的文件名和對應(yīng)的描述文本,并使用文件名對應(yīng)的順序來保存對應(yīng)的圖片文件。
最終,我們會得到一個數(shù)據(jù)集,其中包含每個數(shù)碼獸的名稱、介紹和對應(yīng)的圖片,以及一個metadata.jsonl文件,其中保存了每個圖片的文件名和對應(yīng)的描述文本。
python
import os
import json
import requests
from bs4 import BeautifulSoup
# 創(chuàng)建文件夾
data_dir = "./train"
if not os.path.exists(data_dir):
os.makedirs(data_dir)
# 請求數(shù)碼獸圖鑒頁面
url = "http://digimons.net/digimon/chn.html"
response = requests.get(url)
soup = BeautifulSoup(response.content, "html.parser")
# 遍歷所有的 li 標(biāo)簽
digimon_list = soup.find("ul", id="digimon_list")
for digimon in digimon_list.find_all("li"):
try:
# 獲取數(shù)碼獸名稱和詳情頁面鏈接
name = digimon.find('a')["href"].split('/')[0]
detail_url = "http://digimons.net/digimon/" + digimon.find('a')["href"]
print(f"detail_url: {detail_url}")
# 進(jìn)入詳情頁面,獲取數(shù)碼獸介紹和圖片鏈接
response = requests.get(detail_url)
soup = BeautifulSoup(response.content, "html.parser")
caption = soup.find("div", class_="profile_eng").find('p').text.strip()
img_url = f"http://digimons.net/digimon/{name}/{name}.jpg"
# 保存圖片
img_data = requests.get(img_url).content
file_name = f"{len(os.listdir(data_dir)) + 1:04d}.png"
with open(os.path.join(data_dir, file_name), "wb") as f:
f.write(img_data)
# 將數(shù)據(jù)整理成指定的格式,并保存到對應(yīng)的文件中
metadata = {"file_name": file_name, "text": f"{name}. {caption}"}
with open(os.path.join(data_dir, "metadata.jsonl"), 'a') as f:
f.write(json.dumps(metadata) + '\n')
except Exception as _:
pass
數(shù)據(jù)集整理完成后,如果我們想讓其他人也可以用我們的數(shù)據(jù)集,那么就可以將其發(fā)布到Hugging Face Hub上。Hugging Face Hub是一個收集了多個領(lǐng)域中多種任務(wù)的模型以及數(shù)據(jù)集的平臺,使用戶可以非常方便地下載和使用各種資源。并且,Hugging Face也非常鼓勵用戶上傳自己的數(shù)據(jù)集,以幫助壯大AI學(xué)習(xí)社區(qū)并加快其發(fā)展步伐,造福大家。
如果你還沒有Hugging Face Hub賬號,需要先去其官網(wǎng)注冊一個賬號,然后按照以下步驟創(chuàng)建數(shù)據(jù)集。
- 點擊頁面右上角profile下的New DataSet選項創(chuàng)建一個新的數(shù)據(jù)集倉庫,如圖7-4所示。
- 輸入數(shù)據(jù)集的名稱,并選擇是否為公開數(shù)據(jù)集,公開數(shù)據(jù)集對所有人可見,而私有數(shù)據(jù)集僅對自己或組織成員可見。
圖7-4 Hugging Face創(chuàng)建數(shù)據(jù)集頁面樣例
1)進(jìn)入數(shù)據(jù)集頁面,點擊頂部菜單欄的“Files and versions”,然后點擊右邊“Add file”按鈕下的“upload files”按鈕,之后將圖片文件和訓(xùn)練數(shù)據(jù)元文件直接拖拽到上傳文件框,最后編寫修改信息,點擊提交即可,如圖7-5所示。
圖7-5 Hugging Face上傳數(shù)據(jù)集內(nèi)容頁面樣例
訓(xùn)練參數(shù)設(shè)置
需要注意的是,為了保證能夠成功運(yùn)行最新版的訓(xùn)練代碼,建議通過源碼重新安裝Diffusers庫。
bash
pip install git+https://github.com/Hugging Face/diffusers
然后我們需要初始化Accelerate分布式訓(xùn)練環(huán)境。
bash
> accelerate config
[2023-07-20 18:37:53,537] [INFO] [real_accelerator.py:110:get_accelerator] Setting ds_accelerator to cuda (auto detect)
NOTE: Redirects are currently not supported in Windows or MacOs.
--------------------------------------------------------------------------------------------------------------------In which compute environment are you running?
This machine
--------------------------------------------------------------------------------------------------------------------Which type of machine are you using?
No distributed training
Do you want to run your training on CPU only (even if a GPU / Apple Silicon device is available)? [yes/NO]: NO
Do you wish to optimize your script with torch dynamo?[yes/NO]: NO
Do you want to use DeepSpeed? [yes/NO]: NO
What GPU(s) (by id) should be used for training on this machine as a comma-seperated list? [all]: all
--------------------------------------------------------------------------------------------------------------------Do you wish to use FP16 or BF16 (mixed precision)?
no
accelerate configuration saved at C:\Users\admin/.cache\Hugging Face\accelerate\default_config.yaml
模型訓(xùn)練與測試
當(dāng)前,LoRA技術(shù)主要支持 UNet2DConditionalModel。Diffusers團(tuán)隊已經(jīng)推出了一款適用于LoRA的微調(diào)腳本,這款腳本的優(yōu)勢在于它能夠在僅有11GB GPU RAM的環(huán)境中穩(wěn)定運(yùn)行,而且不需要依賴8-bit等優(yōu)化技術(shù)。下面我們將展示了如何使用此腳本結(jié)合數(shù)碼獸數(shù)據(jù)集來進(jìn)行模型的微調(diào)操作。
bash
accelerate launch --mixed_precisinotallow="fp16" train_text_to_image_lora.py \
--pretrained_model_name_or_path="runwayml/stable-diffusion-v1-5" \
--train_data_dir="./train_data" \
--dataloader_num_workers=0 \
--resolutinotallow=512 --center_crop --random_flip \
--train_batch_size=1 \
--gradient_accumulation_steps=4 \
--max_train_steps=15000 \
--learning_rate=1e-04 \
--max_grad_norm=1 \
--lr_scheduler="cosine" --lr_warmup_steps=0 \
--output_dir="./finetune/lora/digimon" \
--checkpointing_steps=500 \
--validation_prompt="Blue Agumon" \
--seed=1024
該腳本是啟動了一個混合精度為fp16的加速微調(diào)訓(xùn)練任務(wù)。它采用的預(yù)訓(xùn)練模型是runwayml/stable-diffusion-v1-5,并從"./train_data"路徑下獲取訓(xùn)練數(shù)據(jù)。腳本配置為單線程加載數(shù)據(jù),并將圖像解析為512x512的分辨率,同時允許中心裁剪和隨機(jī)翻轉(zhuǎn)。雖然每次只處理一個批次的數(shù)據(jù),但它會累計四個批次的梯度進(jìn)行一次更新。訓(xùn)練的最大步數(shù)被設(shè)置為15000步,學(xué)習(xí)率為0.0001,并限制了梯度的最大范數(shù)為1。學(xué)習(xí)率調(diào)度器采用的是余弦退
火策略,不進(jìn)行預(yù)熱。訓(xùn)練結(jié)果將保存在"./finetune/lora/digimon"目錄下,每500步保存一次檢查點。此外,驗證的提示詞設(shè)置為"Blue Agumon",并指定了隨機(jī)種子為1024,以確保實驗的可重復(fù)性。
正如我們前面所討論的,LoRA的主要優(yōu)勢之一是可以通過訓(xùn)練比原始模型小幾個數(shù)量級的權(quán)重來獲得出色的結(jié)果。通過load_attn_procs函數(shù),我們可以在原始的Stable Diffusion模型權(quán)重之上加載額外的權(quán)重。
python
import torch
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
model_path = "runwayml/stable-diffusion-v1-5"
LoRA_path = "./finetune/lora/digimon" # 修改成本地LoRA模型路徑
pipe = StableDiffusionPipeline.from_pretrained(model_path, torch_dtype=torch.float16)
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
pipe.unet.load_attn_procs(LoRA_path)
pipe.to("cuda")
image = pipe("blue skin agumon", num_inference_steps=50).images[0]
image.save("test.png")
在上面這段代碼中,我們首先定義了兩個路徑,一個是主模型路徑model_path,如果還是使用原生的Stable Diffusion則不需要修改,還有一個是LoRA模型的路徑LoRA_path,需要將LoRA_path修改為正確的本地LoRA模型路徑。接下來創(chuàng)建StableDiffusionPipeline流水線對象,然后通過load_attn_procs方法用于加載本地的LoRA模型,并將其應(yīng)用于流水線的unet模塊,再將管道移至GPU以加快推理速度。最后,我們給定一個提示詞“blue skin agumon”,讓模型生成一個藍(lán)色皮膚的亞古獸,訓(xùn)練數(shù)據(jù)集的亞古獸圖片與生成的圖片如圖7-6所示。
圖7-6 原始亞古獸圖片(左)與微調(diào)后的模型生成的“藍(lán)色亞古獸”圖片(右)對比
在推理時我們還可以調(diào)整LoRA的權(quán)重系統(tǒng):
圖片
如果將設(shè)置為0時,其效果與只使用主模型完全一致;如果將設(shè)置為1時,與使用的效果相同。因此,如果LoRA存在過擬合的現(xiàn)象,我們可以將設(shè)置為較低的值,如果使用LoRA的效果不太明顯,那我們可以將設(shè)置為略高于1的值。
除了使用單個LoRA模型,還可以將多個LoRA模型同時添加到一個主模型中,同樣也可以調(diào)整兩個LoRA模型的權(quán)重:
圖片
如果將和都設(shè)置為0.5,那么在主模型添加的就是兩個LoRA模型的平均權(quán)重。如果將設(shè)置為0.7,設(shè)置為0.3,那么第一個LoRA模型的效果將占據(jù)70%的效果。
在代碼中,將LoRA權(quán)重與凍結(jié)的預(yù)訓(xùn)練模型權(quán)重合并時,也可以選擇調(diào)整與參數(shù)合并的權(quán)重數(shù)量scale,scale值為0表示不使用LoRA權(quán)重,而scale值為1表示使用完全微調(diào)的 LoRA 權(quán)重。
python
pipe.unet.load_attn_procs(lora_model_path)
pipe.to("cuda")
image = pipe(
"A agumon with blue skin.", num_inference_steps=25, guidance_scale=7.5, cross_attention_kwargs={"scale": 0.5}
).images[0]
image.save("blue_pokemon.png")