第 13/60 天
引言
在 AI 漫画创作中,最核心的难题之一就是角色一致性——如何让同一个角色在不同分镜、不同场景、不同角度下保持统一的面貌?第 5 天我们探讨了角色一致性的概念,而今天要介绍的 LoRA(Low-Rank Adaptation) 正是解决这一问题的终极武器。
LoRA 最初是作为大语言模型微调技术出现的,但很快被 Stable Diffusion 社区采纳,成为定制化图像生成的事实标准。通过训练一个轻量级的 LoRA 模型(通常 10-200MB),你可以让 AI 学会特定角色的面部特征、服装风格、画风笔触,甚至某种构图偏好。相比 DreamBooth 的全模型微调(2-7GB),LoRA 训练速度快 10 倍、文件体积小 100 倍,且可以叠加多个 LoRA 实现多角色控制。
本文将带你从零开始,完成一个完整的漫画角色 LoRA 训练流程,包括数据集准备、参数配置、训练执行和推理应用。
核心概念
LoRA 的工作原理
LoRA 在 Stable Diffusion 的交叉注意力层(Cross-Attention Layers)中注入低秩矩阵,用极小的参数量来”偏移”原模型的权重分布。数学上,原本的权重更新 ΔW ∈ ℝ^(d×k) 被分解为两个低秩矩阵 A ∈ ℝ^(d×r) 和 B ∈ ℝ^(r×k),其中 r 远小于 min(d,k):
W' = W + BA
在推理时,这个轻量级权重矩阵与基础模型合并,实现角色特征的注入,而几乎不增加推理时间。
关键训练参数
| 参数 | 推荐值 | 说明 |
|---|---|---|
| rank (r) | 16-64 | 秩越高,模型能记住的细节越多,但过拟合风险也越大 |
| learning_rate | 1e-4 | AdamW 优化器的典型学习率 |
| train_batch_size | 1-4 | 取决于 GPU 显存(8GB 显存建议 batch=1) |
| max_train_steps | 1000-2000 | 50-100 张图片通常 1500 步足够 |
| resolution | 512/768 | 与基础模型训练分辨率一致 |
| network_dim | 128 | SDXL 推荐 128,SD 1.5 推荐 64 |
LoRA vs 其他方案对比
| 方案 | 文件大小 | 训练时间 | 角色一致性 | 可叠加性 |
|---|---|---|---|---|
| LoRA | 10-200MB | 15-30min | ★★★★☆ | ✅ 可叠加多个 |
| DreamBooth | 2-7GB | 60-120min | ★★★★★ | ❌ 独占 |
| Textual Inversion | 10-100KB | 20-40min | ★★☆☆☆ | ✅ 可叠加 |
| IP-Adapter | 100-500MB | 无需训练 | ★★★☆☆ | ✅ 可叠加 |
实战步骤
步骤 1:环境准备
首先,安装训练所需的依赖。推荐使用 Kohya_ss GUI 或直接使用 Diffusers 库进行训练。
# 创建训练环境
python3 -m venv lora_train_env
source lora_train_env/bin/activate
# 安装核心依赖
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
pip install diffusers accelerate transformers xformers
pip install datasets pillow wandb
pip install bitsandbytes
# 验证安装
python -c "import torch; print(f'CUDA available: {torch.cuda.is_available()}, GPU: {torch.cuda.get_device_name(0)}')"
步骤 2:数据集准备
数据集的质量直接影响 LoRA 的训练效果。以下是数据集准备的最佳实践:
# dataset_prepare.py — 数据集预处理脚本
import os
import json
from PIL import Image
from pathlib import Path
# 数据集目录结构
# dataset/
# ├── character_name/
# │ ├── 001.png
# │ ├── 001.txt # 对应的提示词
# │ ├── 002.png
# │ └── 002.txt
# └── metadata.jsonl
def prepare_dataset(character_name, image_dir, output_dir):
"""
漫画角色数据集预处理
- 统一分辨率
- 生成标注文件
- 验证图片完整性
"""
output_path = Path(output_dir) / character_name
output_path.mkdir(parents=True, exist_ok=True)
metadata = []
valid_extensions = {'.png', '.jpg', '.jpeg', '.webp'}
for i, img_path in enumerate(sorted(Path(image_dir).iterdir())):
if img_path.suffix.lower() not in valid_extensions:
continue
# 打开并检查图片
img = Image.open(img_path).convert('RGB')
# 统一分辨率到 512x512(保持宽高比下填充)
img.thumbnail((512, 512), Image.LANCZOS)
new_img = Image.new('RGB', (512, 512), (255, 255, 255))
offset = ((512 - img.width) // 2, (512 - img.height) // 2)
new_img.paste(img, offset)
# 保存处理后的图片
new_name = f"{i+1:03d}.png"
new_img.save(output_path / new_name)
# 生成 BLIP 标注(简化版,实际建议用 BLIP 模型)
caption = f"a portrait of {character_name}, detailed face, anime style"
# 写入标注文件
with open(output_path / f"{i+1:03d}.txt", 'w') as f:
f.write(caption)
metadata.append({
"file_name": new_name,
"text": caption,
"character": character_name
})
# 写入 metadata
with open(output_path / "metadata.jsonl", 'w') as f:
for item in metadata:
f.write(json.dumps(item, ensure_ascii=False) + 'n')
print(f"✅ 数据集准备完成:共 {len(metadata)} 张图片")
print(f" 输出路径: {output_path}")
if __name__ == "__main__":
prepare_dataset("lisa_manga", "./raw_images/lisa", "./dataset")
步骤 3:训练配置
使用 Diffusers 训练 LoRA 的完整配置:
# train_lora.py — 完整 LoRA 训练脚本
import torch
from diffusers import StableDiffusionXLPipeline, AutoencoderKL
from diffusers import DDPMScheduler
from diffusers.optimization import get_scheduler
from diffusers.utils import convert_state_dict_to_diffusers
from peft import LoraConfig, get_peft_model_state_dict
from datasets import load_dataset
from torch.utils.data import DataLoader
from torchvision import transforms
import argparse
import os
def train_lora(
pretrained_model_name="sd-models/realisticVisionV60B1",
dataset_path="./dataset/lisa_manga",
output_dir="./lora_models",
lora_rank=64,
learning_rate=1e-4,
train_batch_size=1,
max_train_steps=1500,
save_every_n_steps=500,
):
"""训练漫画角色 LoRA 模型"""
# 1. 加载预训练模型
noise_scheduler = DDPMScheduler.from_pretrained(
pretrained_model_name, subfolder="scheduler"
)
pipe = StableDiffusionXLPipeline.from_pretrained(
pretrained_model_name,
torch_dtype=torch.float16,
variant="fp16",
)
vae = AutoencoderKL.from_pretrained(
"madebyollin/sdxl-vae-fp16-fix",
torch_dtype=torch.float16,
)
# 2. 冻结 VAE 和 UNet 的主干
vae.requires_grad_(False)
pipe.unet.requires_grad_(False)
# 3. 配置 LoRA
lora_config = LoraConfig(
r=lora_rank,
lora_alpha=lora_rank // 2,
target_modules=["to_q", "to_k", "to_v", "to_out.0"],
lora_dropout=0.1,
bias="none",
)
pipe.unet.add_adapter(lora_config)
# 4. 加载数据集
dataset = load_dataset("imagefolder", data_dir=dataset_path, split="train")
train_transforms = transforms.Compose([
transforms.Resize(512, interpolation=transforms.InterpolationMode.BILINEAR),
transforms.CenterCrop(512),
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5]),
])
def preprocess(examples):
images = [train_transforms(img.convert("RGB")) for img in examples["image"]]
return {"pixel_values": images, "caption": examples["text"]}
dataset = dataset.with_transform(preprocess)
train_dataloader = DataLoader(
dataset, batch_size=train_batch_size, shuffle=True
)
# 5. 优化器设置
optimizer = torch.optim.AdamW(
pipe.unet.parameters(), lr=learning_rate
)
lr_scheduler = get_scheduler(
"cosine",
optimizer=optimizer,
num_warmup_steps=100,
num_training_steps=max_train_steps,
)
# 6. 训练循环
pipe.unet.train()
global_step = 0
for epoch in range(100): # 外层循环,由 max_train_steps 控制退出
for batch in train_dataloader:
if global_step >= max_train_steps:
break
# 编码潜空间
latents = vae.encode(
batch["pixel_values"].to("cuda", dtype=torch.float16)
).latent_dist.sample() * 0.18215
# 添加噪声
noise = torch.randn_like(latents)
timesteps = torch.randint(0, noise_scheduler.config.num_train_timesteps,
(latents.shape[0],), device="cuda")
noisy_latents = noise_scheduler.add_noise(latents, noise, timesteps)
# 预测噪声
noise_pred = pipe.unet(noisy_latents, timesteps,
encoder_hidden_states=pipe.text_encoder(
batch["caption"] * 77 # 简化处理
)[0]).sample
# 计算损失
loss = torch.nn.functional.mse_loss(noise_pred.float(), noise.float())
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
lr_scheduler.step()
if global_step % 50 == 0:
print(f"Step {global_step}/{max_train_steps} | Loss: {loss.item():.6f}")
global_step += 1
if global_step >= max_train_steps:
break
# 7. 保存 LoRA 权重
os.makedirs(output_dir, exist_ok=True)
unet_lora_state_dict = get_peft_model_state_dict(pipe.unet)
output_path = os.path.join(output_dir, "character_lora.safetensors")
torch.save(
{"lora": convert_state_dict_to_diffusers(unet_lora_state_dict)},
output_path,
)
print(f"✅ LoRA 模型已保存: {output_path}")
if __name__ == "__main__":
train_lora()
步骤 4:执行训练
# 运行训练
python train_lora.py
--pretrained_model_name "runwayml/stable-diffusion-v1-5"
--dataset_path "./dataset/lisa_manga"
--output_dir "./lora_models"
--lora_rank 64
--learning_rate 1e-4
--train_batch_size 1
--max_train_steps 1500
# 训练完成后检查输出
ls -lh ./lora_models/
# 预期输出: character_lora.safetensors (约 50-150MB)
步骤 5:在推理中使用 LoRA
训练完成后,在 AI 漫画生成工作流中加载 LoRA:
# inference_with_lora.py — 使用 LoRA 生成漫画分镜
import torch
from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
import os
# 加载基础模型 + LoRA
model_id = "runwayml/stable-diffusion-v1-5"
pipe = StableDiffusionPipeline.from_pretrained(
model_id,
torch_dtype=torch.float16,
safety_checker=None,
)
pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
pipe = pipe.to("cuda")
# 加载 LoRA 权重
lora_path = "./lora_models/character_lora.safetensors"
pipe.load_lora_weights(lora_path, adapter_name="character")
# 生成不同分镜的多张图片
scenes = [
"a portrait of <lora:character:1.0> lisa, close-up, cheerful expression, manga style",
"a portrait of <lora:character:1.0> lisa, side view, serious look, anime shading",
"a portrait of <lora:character:1.0> lisa, full body, standing under cherry blossom tree",
"a portrait of <lora:character:1.0> lisa, action pose, dynamic angle, manga panel",
]
for i, prompt in enumerate(scenes):
image = pipe(
prompt=prompt,
negative_prompt="low quality, distorted face, bad anatomy, extra fingers",
num_inference_steps=30,
guidance_scale=7.5,
width=512,
height=768,
).images[0]
os.makedirs("./output", exist_ok=True)
image.save(f"./output/scene_{i+1:02d}.png")
print(f"✅ 生成: scene_{i+1:02d}.png — {prompt[:40]}...")
步骤 6:使用 Kohya_ss GUI(推荐新手)
Kohya_ss 提供了图形化界面,无需编写代码:
# 安装 Kohya_ss
git clone https://github.com/bmaltais/kohya_ss.git
cd kohya_ss
python setup.py
# 启动 GUI
python kohya_gui.py --listen 0.0.0.0 --port 7860
# 浏览器访问 http://localhost:7860
# 在 "LoRA" 标签页中配置:
# 1. 基础模型路径
# 2. 数据集文件夹路径
# 3. 训练参数(建议: Dim=64, LR=1e-4, Steps=1500)
# 4. 点击 "Start training"
数据集质量检查清单
{
"dataset_quality_checklist": {
"image_count": {
"min": 20,
"recommended": "50-100",
"note": "少于 20 张难以收敛,超过 200 张边际效益递减"
},
"image_diversity": {
"angles": ["正面", "3/4侧面", "正侧面", "俯视", "仰视"],
"expressions": ["开心", "严肃", "惊讶", "悲伤", "愤怒"],
"compositions": ["特写", "半身", "全身"],
"note": "每个角度/表情至少 3-5 张"
},
"image_quality": {
"resolution": "512x512 或更高",
"format": "PNG (无损)",
"background": "尽量干净背景,或使用透明背景",
"note": "模糊、低对比度、光照过曝的图片应剔除"
},
"captioning": {
"format": "每张图片配套 .txt 文件",
"content": "描述角色特征、表情、动作、背景",
"trigger_word": "使用统一的触发词如 'lisa_manga'",
"example": "lisa_manga, a young girl with red hair, smiling, school uniform, front view"
}
}
}
常见问题
Q1:训练 LoRA 需要多少张图片?
最少 20 张高质量图片,推荐 50-100 张。图片质量比数量更重要——10 张高质量图片胜过 100 张低质量图片。图片应覆盖不同角度、表情和构图。
Q2:训练时出现过拟合(overfitting)怎么办?
过拟合表现为 LoRA 只能生成训练集中见过的角度,无法泛化。解决方案:① 增加训练图片数量 ② 降低 rank 值(32→16)③ 增大 dropout(0.1→0.2)④ 减少训练步数 ⑤ 增加数据增强(随机裁剪、翻转、色彩抖动)。
Q3:LoRA 权重应该设置多少?
在推理时通过 <lora:name:weight> 控制权重,默认为 1.0。建议范围 0.5-1.5。权重过高会导致角色特征过度而破坏构图,权重过低则角色特征不明显。实践中先从 0.8 开始,逐步调整。
Q4:多角色 LoRA 如何叠加?
Stable Diffusion WebUI 和 ComfyUI 都支持加载多个 LoRA。例如同时加载 lisa_manga 和 tom_manga 两个 LoRA,在提示词中分别引用 <lora:lisa_manga:1.0> 和 <lora:tom_manga:1.0>。注意总权重和不宜超过 2.0,否则可能导致画面混乱。
Q5:训练时 VRAM 不足怎么办?
使用 --enable_xformers 启用 xformers 内存优化,将 batch size 设为 1,使用 gradient checkpointing,降低分辨率到 384×384 先测试。如果仍不足,考虑使用 LoRA 训练的”梯度累积”(gradient accumulation)或使用云端 GPU(AutoDL、Google Colab 等)。
总结
LoRA 训练是 AI 漫画创作者打造专属角色库的核心技能。掌握以下要点,你就能像传统漫画家设计角色一样,用 AI 精确控制每一个角色的视觉特征:
- 数据集质量决定一切:50-100 张覆盖多角度、多表情的高质量图片,加上精准的标注,是 LoRA 成功的基础
- 参数调优是艺术:rank 64 + LR 1e-4 + 1500 steps 是通用起点,但每个角色和风格都需要微调
- 多 LoRA 叠加实现多角色控制:可以在同一场景中通过不同的 LoRA 权重控制多个角色,实现漫画般的多人互动画面
- 训练与推理分离:一次训练,无限次使用——训练好的 LoRA 文件可以分享、复用,并嵌入到完整的漫画工作流中
- 持续迭代:随着角色在不同漫画中出现,可以不断补充训练数据,生成更高质量的 LoRA 版本
下一篇文章将介绍 IP-Adapter 实现角色一致性——一种无需训练、即插即用的角色控制方案,与 LoRA 形成完美的互补关系。















暂无评论内容