Files
FoodClassifier/exp_multimodal/README_OPENSET.md
T
2025-11-20 20:13:05 +08:00

6.6 KiB
Raw Blame History

开放式VLM识别 + 向量匹配方案

方案概述

将原有的"封闭集分类"(1万+候选菜品名放入提示词)改为"开放式识别+向量匹配",实现3秒内响应。

核心优势

  • 性能提升: 从10秒降至~2.5秒(符合3秒要求)
  • 🎯 准确性: VLM开放识别 + 向量库精确匹配
  • 📊 可扩展: 新增菜品只需重建向量库,无需修改代码

技术架构

图片 → VLM开放识别 → 清洗菜品名 → Embedding → FAISS检索 → 最佳匹配
         (2-3秒)      (<1ms)       (50ms)     (10ms)

文件说明

新增文件

  1. dish_name_cleaner.py - 菜品名清洗工具

    • 去除括号注释、地域前缀、英文等干扰信息
    • 同义词映射(西红柿→番茄)
  2. text_embedder.py - Ollama Embedding客户端

    • 调用 http://192.168.1.250:11434/v1/embeddings
    • 使用 quentinz/bge-large-zh-v1.5 模型
  3. vector_matcher.py - FAISS向量匹配器

    • 加载预构建的向量索引
    • 执行Top-K相似度检索
  4. build_dish_name_index.py - 构建向量库脚本(一次性任务)

    • labels.py 获取菜品名
    • 批量Embedding编码
    • 构建FAISS索引并保存
  5. exp_openset_vlm.py - 主程序入口

    • 测试开放式识别方案
    • 输出性能统计和识别结果

修改文件

  1. prompts.py - 新增 build_openset_prompt()

    • 开放式提示词(不提供候选列表)
    • 强调输出标准中文菜品名
  2. vlm_classifier.py - 新增 classify_image_openset()

    • 完整的开放识别+向量匹配流程
    • 综合VLM置信度和向量匹配分数

使用指南

步骤1: 构建向量索引(首次运行)

# 构建菜品名向量索引(约需5-10分钟,1万+菜品)
python -m exp_multimodal.build_dish_name_index --mode dish

# 可选参数:
#   --output: 自定义输出目录
#   --batch_size: 批量编码大小(默认100)
#   --embedder_url: Embedding服务地址
#   --embedder_model: Embedding模型名称

输出文件(保存在 faiss_vector_db/dish_names/):

  • dish_names.json - 菜品名列表
  • dish_embeddings.npy - 归一化后的向量
  • faiss_index.bin - FAISS索引文件

步骤2: 运行测试

# 测试开放式识别
python -m exp_multimodal.exp_openset_vlm --image <图片路径>

# 示例:
python -m exp_multimodal.exp_openset_vlm \
  --image "D:\MyProjects\PythonProjects\FoodClassifier\dataset\DishClassification\test\红烧肉\img04.png" \
  --top_k 3 \
  --min_score 0.5

主要参数

  • --image: 测试图片路径
  • --index_dir: FAISS索引目录(默认自动定位)
  • --top_k: 返回的候选数量(默认3
  • --min_score: 最低匹配分数阈值(默认0.5
  • --vlm_url: VLM服务地址(默认 http://192.168.1.250:11434
  • --vlm_model: VLM模型(默认 qwen2.5vl:32b

步骤3: 查看结果

输出示例:

{
  "raw_dish": "红烧肉",
  "cleaned_dish": "红烧肉",
  "vlm_confidence": 0.95,
  "candidates": [
    {"dish": "红烧肉", "match_score": 0.98, "final_score": 0.931},
    {"dish": "东坡肉", "match_score": 0.85, "final_score": 0.808},
    {"dish": "梅菜扣肉", "match_score": 0.72, "final_score": 0.684}
  ],
  "best_match": "红烧肉",
  "final_confidence": 0.931
}

性能指标

  • 总耗时: ~2.5秒
  • VLM推理: 2-3秒
  • Embedding: 30-80ms
  • FAISS检索: 5-15ms

配置说明

Ollama服务配置

确保以下服务正常运行:

  1. VLM服务 (视觉语言模型)

    • 地址: http://192.168.1.250:11434
    • 模型: qwen2.5vl:32b
    • 用途: 开放式菜品识别
  2. Embedding服务

    • 地址: http://192.168.1.250:11434/v1/embeddings
    • 模型: quentinz/bge-large-zh-v1.5
    • 用途: 文本向量化

性能调优

如果响应时间超过3秒

  1. 降低 top_k 值(减少检索数量)
  2. 提高 min_score 阈值(过滤低分候选)
  3. 检查网络延迟(VLM/Embedding服务)
  4. 考虑使用更快的VLM模型

如果准确率不理想

  1. 调整 prompts.py 中的开放式提示词
  2. 扩展 dish_name_cleaner.py 中的同义词映射
  3. 增加 top_k 值提供更多候选
  4. 降低 min_score 阈值(但可能增加误匹配)

常见问题

Q1: 如何添加新菜品?

A: 在 dataset/DishClassification/train/ 下添加新菜品文件夹,然后重新运行 build_dish_name_index.py

Q2: 向量库需要多久更新一次?

A: 仅在菜品列表变化时需要重建,日常识别无需重建

Q3: 如何切换到其他Embedding模型?

A: 修改 build_dish_name_index.pyexp_openset_vlm.py 中的 --embedder_model 参数

Q4: 能否同时使用封闭集和开放集方案?

A: 可以!两个方案互不影响:

  • 封闭集: 使用 classify_image() (原方案)
  • 开放集: 使用 classify_image_openset() (新方案)

Q5: FAISS索引占用多少存储空间?

A: 约50-100MB1万菜品 × 1024维 × 4字节)


目录结构

exp_multimodal/
├── dish_name_cleaner.py       # 菜品名清洗工具
├── text_embedder.py           # Ollama Embedding客户端
├── vector_matcher.py          # FAISS向量匹配器
├── build_dish_name_index.py  # 构建向量库脚本
├── exp_openset_vlm.py         # 主程序入口
├── prompts.py                 # 提示词(已修改)
├── vlm_classifier.py          # 分类器(已修改)
└── README_OPENSET.md          # 本文档

faiss_vector_db/
└── dish_names/                # 向量索引目录(自动生成)
    ├── dish_names.json        # 菜品名列表
    ├── dish_embeddings.npy    # 向量数据
    └── faiss_index.bin        # FAISS索引

性能对比

方案 Token消耗 响应时间 准确率 可扩展性
封闭集分类 ~50K 10秒
开放识别+向量匹配 ~500 2.5秒

下一步优化建议

  1. 提示词迭代: 根据实际测试结果调优 build_openset_prompt()
  2. 缓存机制: 对常见菜品的识别结果进行缓存
  3. 混合策略: VLM置信度低时,回退到封闭集二次确认
  4. 模糊匹配: 向量检索无结果时,使用编辑距离等兜底方案
  5. 批量识别: 支持一次处理多张图片

技术支持

如有问题,请检查:

  1. Ollama服务是否正常运行
  2. FAISS索引是否已构建
  3. 依赖包是否已安装(faiss-cpu, requests, numpy
  4. 图片路径是否正确

祝使用愉快! 🎉