BLIP2 工业实战(一):从零实现 LAVIS 跌倒检测 (微调与"踩坑"指南)

在学习完 BLIP/BLIP2 模型原理后,如何将其应用于工业开发至关重要。本文将作为"跌倒检测"实战项目的第一篇,详细记录如何从零到一,使用 Salesforce 的 LAVIS 框架实现一个完整的工业化流程:模型部署 -> 数据构建 -> 微调 -> 验证。
本文最大的特色是**"踩坑"与"避坑"。我们将首先复现一个"失败"的预训练模型推理,然后重点展示如何 自定义数据集 (Dataset)** 和 Builder ,以及如何配置
yaml文件来微调 BLIP2。我们将详细分析每一个关键步骤中(特别是 JSON 构建和 Windows 训练)我曾犯过的错误,帮助读者节省宝贵的调试时间。希望本文项目对您有所帮助。
文章目录
- [BLIP2 工业实战(一):从零实现 LAVIS 跌倒检测 (微调与"踩坑"指南)](#BLIP2 工业实战(一):从零实现 LAVIS 跌倒检测 (微调与“踩坑”指南))
-
- 一、引言:项目目标与环境配置
-
- [1.1 项目背景与"踩坑"忠告](#1.1 项目背景与“踩坑”忠告)
- [1.2 环境配置](#1.2 环境配置)
- 二、步骤一:"翻车"的基线推理
-
- [2.1 基于 LAVIS 加载模型](#2.1 基于 LAVIS 加载模型)
- [2.2 第一次推理:意料之中的失败](#2.2 第一次推理:意料之中的失败)
- [三、步骤二:构建 VQA 微调数据集](#三、步骤二:构建 VQA 微调数据集)
-
- [3.1 目录结构](#3.1 目录结构)
- [3.2 JSON 文件构建与踩坑](#3.2 JSON 文件构建与踩坑)
- [四、步骤三:自定义 LAVIS 的 Dataset 与 Builder](#四、步骤三:自定义 LAVIS 的 Dataset 与 Builder)
-
- [4.1 为什么要自定义?](#4.1 为什么要自定义?)
- [4.2 🧱 构建 Dataset](#4.2 🧱 构建 Dataset)
- [4.3 🧱 构建 Builder](#4.3 🧱 构建 Builder)
- [4.4 注册 Builder](#4.4 注册 Builder)
- [五、步骤四:配置 YAML 与执行微调](#五、步骤四:配置 YAML 与执行微调)
-
- [5.1 训练 YAML 配置文件](#5.1 训练 YAML 配置文件)
- [5.2 执行微调与结果验证](#5.2 执行微调与结果验证)
- 六、总结与下一步
一、引言:项目目标与环境配置
我们的目标是实现一个工业级的"跌倒检测"项目,完整流程规划为:部署 -> 数据构建 -> 微调 -> 量化 -> 部署。本篇(一)将重点覆盖前三个环节。
1.1 项目背景与"踩坑"忠告
本文将以"编者"(即作者我)的亲身经历,记录从零开始的整个微调过程。
编者忠告:
我认为自己已经把整个微调过程能犯的错误都犯过了。因此,跟着我的思路进行数据集构建和配置,可以帮助你避开绝大多数的 Bug。
1.2 环境配置
不同库版本间有很强的依赖性。如果条件允许,请尽量使用我的这套环境,可以避免在配置上花费不必要的时间。
表格 1:关键环境依赖
| 库 (Library) | 版本 (Version) |
|---|---|
python |
3.10 |
torch |
2.7.1 |
torchvision |
0.22.1 |
transformer |
4.31.0 |
huggingface-hub |
0.25.2 |
LAVIS |
main branch |
二、步骤一:"翻车"的基线推理
我们首先尝试直接使用 LAVIS 加载预训练的 BLIP2 模型,看看它在"跌倒检测"这个特定任务上的"零样本" (Zero-shot) 表现。
2.1 基于 LAVIS 加载模型
我们选择 blip2_t5 模型和 pretrain_flant5xl 类型。
python
# 代码块 1: 加载预训练 BLIP-2 模型
from lavis.models import load_model_and_preprocess
import torch
from PIL import Image
import requests
# 1. 加载 BLIP-2 模型
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 注意:LAVIS 目前支持 flant5xl, flant5xxl, opt2.7b, opt6.7b 等
model, vis_processors, _ = load_model_and_preprocess(
name="blip2_t5",
model_type="pretrain_flant5xl",
is_eval=True,
device=device,
)
2.2 第一次推理:意料之中的失败
我们加载一张本地的跌倒图像,并向模型提问。
python
# 代码块 2: 执行零样本推理
# 2. 加载本地图像
url = "../dataset/falldown/images/1.jpg" # 换成你自己的图像路径
raw_image = Image.open(url).convert("RGB")
# 3. 构造问题 (VQA)
question = "Is someone falling in this image? please answer yes or no:"
# 4. 预处理
image = vis_processors["eval"](raw_image).unsqueeze(0).to(device)
sample = {
"image": image,
"text_input": question
}
# 5. 生成答案
output = model.generate(sample)
print(f"提问: {question}")
print(f"回答: {output[0]}")
# 预期(错误)回答: a man is being helped by another man on the ground
🔴 失败分析 (The Problem)
我们想要的回答 :
"yes"或者"no"。模型的实际回答 :
"a man is being helped by another man on the ground"。可以发现,这个回答和我们想要的答案相差甚远。模型没有在"回答问题",而是在"描述图像"。无论我们怎么修改提示词 (Prompt),模型都只会返回对图像的完整阐述。
结论 🟢 :预训练模型无法理解我们的特定任务(VQA 式的跌倒判断)。为此,构建一份新的数据集来微调模型至关重要。
三、步骤二:构建 VQA 微调数据集
这是整个项目中最关键、也是"坑"最多的一步。
3.1 目录结构
我们采用 LAVIS VQA 任务的标准格式:images 文件夹 + json 标注文件。
/dataset/falldown/
├── images/
│ ├── 1.jpg
│ ├── 2.jpg
│ └── ... (编者用了10张图)
└── train.json


3.2 JSON 文件构建与踩坑
JSON 文件记录了图像、问题和答案。以下是最终的正确格式:
json
# 代码块 3: train.json (正确格式)
[
{
"instance_id": 1,
"image": "1.jpg",
"text_input": "Is anyone falling",
"answer": ["yes"]
},
{
"instance_id": 2,
"image": "2.jpg",
"text_input": "Is anyone falling in this image?",
"answer": ["yes"]
}
]
⚠️ 踩坑警告:LAVIS 数据集构建的核心 Bug
在创建 JSON 文件时,我犯了两个致命错误,浪费了大量时间:
- 键名错误 (Key Names) :LAVIS 默认的 Builder (如
COCOVQADataset) 对 JSON 中的key有严格要求。如果你使用的键名(如"question")和 Builder 期望的(如"text_input")不符,它根本读不到数据。
- 解决方案 :要么修改 JSON 键名,要么(像我一样)重新构建一个自己的 Builder。
- 答案维度错误 (Dimension Error) :这是最隐蔽的 Bug!
- 错误写法 :
"answer": "yes"- 正确写法 :
"answer": ["yes"]- 报错 :
RuntimeError: shape '[24, -1, 32, 64]' is invalid for input of size 655360- 原因 :LAVIS 默认
answer是一个列表 (List) ,它会遍历列表中的每个 答案。如果你写了"yes",它会把这个字符串当作列表,遍历为"y","e","s"三个独立的答案,导致后续处理(如text_processor)时数据维度彻底崩溃。
四、步骤三:自定义 LAVIS 的 Dataset 与 Builder
由于我们的 JSON 键名和默认 Builder 不匹配,我们必须自定义 Dataset 和 Builder。
4.1 为什么要自定义?
LAVIS 在执行 train.py 时,必须通过 yaml 中指定的 Builder 来构建Dataset。当现有的 Builder 无法满足我们的任务需求(如键名不匹配、数据处理逻辑不同)时,我们必须自定义。
4.2 🧱 构建 Dataset
在 LAVIS/lavis/datasets/datasets/ 路径下创建你自己的 Python 文件(例如 my_vqa_dataset.py)。
python
# 代码块 4: my_vqa_dataset.py (自定义 Dataset)
import os
import json
import random
from PIL import Image
from lavis.datasets.datasets.vqa_datasets import VQADataset
from collections import OrderedDict
# __DisplMixin 是可选的,用于美观地显示样本,这里省略以简化
# ... (省略 __DisplMixin) ...
class FalldownVQADataset(VQADataset):
def __init__(self, vis_processor, text_processor, vis_root, ann_paths):
super().__init__(vis_processor, text_processor, vis_root, ann_paths)
def __getitem__(self, index):
ann = self.annotation[index]
image_path = os.path.join(self.vis_root, ann["image"])
image = Image.open(image_path).convert("RGB")
image = self.vis_processor(image)
# 对应 JSON 中的 "text_input" 键
question = self.text_processor(ann["text_input"])
# 对应 JSON 中的 "answer" 键 (注意它是一个列表)
answer_weight = {}
for answer in ann["answer"]:
if answer in answer_weight.keys():
answer_weight[answer] += 1 / len(ann["answer"])
else:
answer_weight[answer] = 1 / len(ann["answer"])
answers = list(answer_weight.keys())
weights = list(answer_weight.values())
return {
"image": image,
"text_input": question,
"answers": answers,
"weights": weights,
}
# -----------------------------------------------------------------
# 关键!LAVIS 的 VQA 任务在训练时需要 "text_output" 键
# -----------------------------------------------------------------
class FalldownVQAInstructDataset(FalldownVQADataset):
def __getitem__(self, index):
data = super().__getitem__(index)
if data is not None:
# 从答案列表中随机选一个作为训练目标
data['text_output'] = random.choice(data["answers"])
return data
# collater 貌似在新版 LAVIS 中非必须,但保留有好处
# def collater(self, samples):
# data = super().collater(samples)
# if 'answer' in data:
# data['text_output'] = data['answer']
# return data
✅ 关键点
必须要有 FalldownVQAInstructDataset 这个子类!
FalldownVQADataset(基类) 负责从 JSON 加载数据,键为"answers"。- LAVIS 的 VQA 训练管道 (task) 在训练时,会去寻找一个名为
"text_output"的键作为 T5 模型的 Decoder 输入。FalldownVQAInstructDataset的作用就是从"answers"列表中随机选一个,并将其赋值给"text_output",从而桥接数据和模型。
4.3 🧱 构建 Builder
在 LAVIS/lavis/datasets/builders/ 路径下创建 my_data_builder.py。
python
# 代码块 5: my_data_builder.py (自定义 Builder)
from lavis.common.registry import registry
from lavis.datasets.builders.base_dataset_builder import BaseDatasetBuilder
# 关键:导入我们刚创建的 Dataset 类
from lavis.datasets.datasets.my_vqa_dataset import FalldownVQAInstructDataset
# 注册一个新名字,这个名字将在 YAML 文件中用到!!!
@registry.register_builder("my_falldown_dataset")
class MyDatasetBuilder(BaseSbuilDatasetBuilder):
train_dataset_cls = FalldownVQAInstructDataset
eval_dataset_cls = FalldownVQAInstructDataset
# 跳过 LAVIS 默认的数据集下载步骤
def _download_data(self):
return
4.4 注册 Builder
最后,在 LAVIS/lavis/datasets/builders/__init__.py 文件中,导入我们刚创建的 Builder。
python
# 代码块 6: 在 __init__.py 中注册
from lavis.datasets.builders.base_dataset_builder import load_dataset_builder
# ... (其他 builders) ...
# 添加这一行,让 LAVIS 框架能找到我们的 Builder
from lavis.datasets.builders.my_data_builder import MyDatasetBuilder
五、步骤四:配置 YAML 与执行微调
数据和代码准备就绪,现在我们配置训练文件。
5.1 训练 YAML 配置文件
在你的项目目录下创建一个 my.yaml 文件。
yaml
# 代码块 7: my.yaml (训练配置文件)
model:
arch: blip2_t5
model_type: pretrain_flant5xl
load_pretrained: True
# T5 模型路径
t5_model: google/flan-t5-xl
# BLIP2 预训练权重路径 (你需要自己下载)
pretrained: "E:/root/autodl-tmp/blip2_pretrained_flant5xl.pth"
freeze_vit: True # 微调阶段,冻结 ViT
datasets:
# 关键:这里的名字必须和 Builder 注册的名字一致
my_falldown_dataset:
data_type: images
vis_processor:
train:
name: "blip_image_train"
image_size: 224
text_processor:
train:
name: "blip_instruction"
build_info:
images:
# 图像文件夹的绝对路径
storage: E:/LLM/dataset/falldown/images
annotations:
train:
storage:
# 标注文件的绝对路径
- E:/LLM/dataset/falldown/train.json
val:
storage:
- E:/LLM/dataset/falldown/train.json
test:
storage:
- E:/LLM/dataset/falldown/train.json
run:
distributed: False # Windows 下必须为 False
task: vqa # 关键:指定任务为 VQA
lr_sched: "linear_warmup_cosine_lr"
init_lr: 1e-4
min_lr: 1e-5
warmup_lr: 1e-6
weight_decay: 0.05
max_epoch: 50
batch_size_train: 2 # 根据你的显存调整
batch_size_eval: 2
num_workers: 4
warmup_steps: 2000
seed: 42
output_dir: "output/BLIP2/FallDown_stage1"
amp: True
resume_ckpt_path: null
evaluate: False
train_splits: ["train"]
device: "cuda"
world_size: 1
dist_url: "env://"
⚠️ 踩坑警告:YAML 配置与 Windows 训练
- Builder 注册名错误 :
datasets:下的键名my_falldown_dataset必须和MyDatasetBuilder类@registry.register_builder("my_falldown_dataset")中注册的名字完全一致 。否则会报错:AttributeError: 'NoneType' object has not attribute 'default_config_path'。- 缺少 Task :
run:下的task: vqa不能省略 !LAVIS 依赖这个task来确定使用哪个训练流程(如RunnerVQA)。- Windows 分布式错误 :如果你在 Windows 下训练 (如我),
run:distributed必须设为False。
- 报错 :
ValueError: Default process group has not been initialized...- 解决方案 :除了设为
False,还需要注释掉LAVIS/lavis/runners/runner_base.py文件中第 422 行 (或附近) 的dist.barrier()。
5.2 执行微调与结果验证
一切就绪,开始训练!
cmd
# 代码块 8: 启动训练
# 假设你在 LAVIS 的根目录
python train.py --cfg-path E:/LLM/dataset/falldown/my.yaml
(图 2:模型训练日志截图)
训练完成后,权重保存在 output_dir 中。我们加载微调后的权重(例如第 30 个 epoch 的),再试一次。
python
# 代码块 9: 加载微调后的模型并进行推理
from lavis.models import load_model_and_preprocess
import torch
from PIL import Image
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model, vis_processors, _ = load_model_and_preprocess(
name="blip2_t5",
model_type="pretrain_flant5xl",
is_eval=True,
device=device,
)
# 加载我们微调后的权重
ckpt_path = "./output/BLIP2/FallDown_stage1/checkpoint_30.pth" # 换成你的权重路径
ckpt = torch.load(ckpt_path, map_location="cpu")
msg = model.load_state_dict(ckpt["model"], strict=False)
print(f"Model load status: {msg}")
# --- 使用和步骤一完全相同的代码进行推理 ---
url = "../dataset/falldown/images/1.jpg"
raw_image = Image.open(url).convert("RGB")
question = "Anyone falldown?" # 换个问法
image = vis_processors["eval"](raw_image).unsqueeze(0).to(device)
sample = {
"image": image,
"text_input": question
}
output = model.generate(sample)
print(f"提问: {question}")
print(f"回答: {output[0]}")
# 预期(正确)回答: yes
六、总结与下一步
⛔ 注意:过拟合警告
我们的模型现在会正确回答
yes了。但请注意:我们只用了 10 张图进行微调。这个模型现在很可能只会输出 "yes" ,无论你给它看什么(即过拟合)。如果想真正实现一个鲁棒的跌倒检测模型,下一步必须混合通用 VQA 数据集 (如 COCO-VQA)和你自己构建的"负样本" (即没有跌倒的图像,答案为
"no"),一起进行微调。
总结:在本篇(一)中,我们成功复现了 BLIP2 的"零样本"失败,并通过自定义 LAVIS 的 Dataset 和 Builder,成功微调了一个(过拟合的)跌倒检测 VQA 模型。
下一步展望:在专栏(二)中,我们将解决过拟合问题,