leboncoin:微调如何击败RAG

在leboncoin——法国最大的分类广告平台,我们每天帮助数百万用户出售他们的物品。广告发布是我们市场的核心,这是供应进入平台的关键时刻。当有人列出一部iPhone出售时,我们会要求他们填写属性:品牌、型号、存储和颜色。这些属性驱动搜索过滤器,帮助买家找到他们想要的东西。

挑战是什么?填写所有这些字段需要时间。这就是认知团队的用武之地。我们是一个专注于通过构建ML和GenAI驱动的服务来使广告创建更快、更顺畅的产品和ML团队,以减少卖家工作量并提高广告质量。

为了应对这一挑战,我们早在2020年就开发了一个机器学习模型:一个基于字符n-gram(2-5 gram)的文本分类器,使用简单的神经网络架构:

标题文本 → CountVectorizer(字符n-gram)→ Dense(32) → Dropout → Dense(每个属性)

它的优雅之处在于其简单性:将标题转换为稀疏的n-gram向量,嵌入到32维空间中,然后独立地对每个属性进行分类。该模型快速、服务成本低廉,在数百万广告上训练,并每天重新训练。

对于以文本为主的属性,它效果显著。如果有人写"iPhone 13 Pro 256GB",字符n-gram会捕获"iPho"、"Phon"、"hone"、"13 P"、"Pro"、"256G"、"GB"——这些信号足以可靠地提取品牌(Apple)、型号(iPhone 13 Pro)和存储(256GB)。

但颜色是另一回事。

我们生产模型在颜色属性上的准确率可以做得更好。标题通常根本没有提到颜色("出售我的iPhone,完美状态!")或使用模糊的术语("Pacific Blue" vs "Sierra Blue",都是蓝色,但具体是哪个?)。

信息就在图像中。人类可以看照片并立即看到手机是蓝色的。但我们的基于文本的模型看不到图像。

那时我们决定尝试视觉语言模型。

假设很简单:如果我们将标题和图像都提供给模型,我们应该得到更好的预测,特别是对于颜色等视觉属性。

1、为什么选择Qwen3-VL-8B?

我们选择Qwen3-VL-8B作为基础模型,认为它是当前最先进的开源VLM。使用该模型的决定基于三个主要因素:

  1. 法语支持:对于处理我们的广告至关重要。
  2. 资源效率:它可以在带有4位量化的单个A10G GPU上运行,这是AWS上负担得起且广泛可用的实例。
  3. 原生支持结构化提取:由vLLM原生支持引导式JSON解码,对我们的用例至关重要。

我们还与Claude Haiku 4.5进行了基准测试。我们的基础Qwen实验匹配或超过了专有API,证实了领域适应对于此特定任务比原始模型能力更重要。

2、现实世界的复杂性:多类别、多任务预测

在深入我们的实验之前,让我向您展示"广告参数预测"在规模上的实际含义。这不是一个玩具问题。

我们支持16个类别,每个类别有不同的属性和标签空间:

高复杂度(品牌密集型类别)
+------------+------------+-------------------------------------------+
| 类别       | 属性数量   | 复杂性说明                                |
+------------+------------+-------------------------------------------+
| 服装       | 6          | 仅品牌就有1,500+个                        |
| 鞋子       | 5          | 700+品牌,尺寸依赖关系                    |
| 手机       | 4          | 700+型号,品牌→型号依赖关系               |
| 配饰       | 5          | 200+品牌                                  |
+------------+------------+-------------------------------------------+

中等复杂度
+-----------------+------------+----------------------------------+
| 类别            | 属性数量   | 复杂性说明                       |
+-----------------+------------+----------------------------------+
| 婴儿用品        | 3          | ~200品牌                         |
| 手表            | 4          | 100+奢侈和大众市场品牌           |
| 家用电器        | 3          | ~75品牌,产品类型                |
+-----------------+------------+----------------------------------+

较低复杂度(仍是多标签)
+-----------------------------+------------+---------------------------+
| 类别                        | 属性数量   | 说明                      |
+-----------------------------+------------+---------------------------+
| 玩具                        | 多个       | 品牌较少                  |
| 自行车                      | 多个       | 属性依赖关系              |
| 家具                        | 多个       |                           |
| 装饰品                      | 多个       |                           |
| 电子游戏                    | 多个       |                           |
| 游戏机                      | 多个       |                           |
+-----------------------------+------------+---------------------------+

总计:16个类别中的200+唯一属性,数千个可能的标签

有些类别"简单"(自行车:3个属性中的20个标签)。其他类别是噩梦(服装:仅品牌就有1,500+个,还没算颜色、尺寸、类型...)。

然后还有依赖关系。在手机中,有效型号取决于品牌:

- 如果phone_brand = "Apple",则phone_model是{iPhone 13, iPhone 13 Pro, iPhone 14, ...}之一(42个型号)
- 如果phone_brand = "Samsung",则phone_model是{Galaxy S23, Galaxy S24, ...}之一(80+型号)
- 总计:24个品牌 × 不同型号 = 700+有效组合,但任何给定品牌只有约30个有效

这就是我们正在应对的机器学习现实:一个多类别、多任务、多标签分类问题,具有复杂的层次依赖关系和庞大的标签空间。

3、精确匹配:我们如何衡量成功(以%计)

在整篇文章中,我们使用精确匹配(所有属性正确),因为部分预测仍需要手动校正。逐属性准确率可能会产生误导:在服装上,59%的准确率仅转化为6.5%的精确匹配,意味着93.5%的预测仍需要用户干预。精确匹配是衡量用户体验的更好指标。

在接下来的6周里,我们用Qwen尝试了三种不同的方法。以下是有效的方法、无效的方法,以及我们为什么希望先尝试微调。

4、V0 —— 天真方法:把所有东西都放进提示中

我们的第一次尝试是最明显的。我们有一个强大的视觉语言模型(Qwen3-VL-8B)。我们有包含所有有效属性值的标记数据。为什么不直接告诉模型所有选项,让它选择?

我们构建了一个简单的提示系统:

  1. 获取广告标题和图像
  2. 将每个属性的所有有效值注入提示中
  3. 要求模型提取正确的值

提示看起来像这样:

你是一个从广告中提取产品属性的助手。

给定这个广告:
标题:"iPhone 13 Pro 256GB excellent état"
图像:[image1.jpg, image2.jpg]

提取以下属性。仅使用提供的列表中的值。

phone_brand(有效值):Apple, Samsung, Xiaomi, Huawei, OnePlus, Google,
Sony, LG, Nokia, Motorola, OPPO, Vivo, Realme, Honor, Asus, ZTE, Alcatel,
Blackberry, HTC, Lenovo, Meizu, Nothing, Fairphone, ... [24个品牌]
phone_model(有效值):iPhone 13, iPhone 13 Mini, iPhone 13 Pro,
iPhone 13 Pro Max, iPhone 14, iPhone 14 Plus, iPhone 14 Pro, iPhone 14 Pro Max,
iPhone 15, iPhone 15 Plus, iPhone 15 Pro, iPhone 15 Pro Max, Galaxy S23,
Galaxy S23+, Galaxy S23 Ultra, Galaxy S24, ... [742个型号!]

phone_memory(有效值):16GB, 32GB, 64GB, 128GB, 256GB, 512GB, 1TB [8个值]

phone_color(有效值):Noir, Blanc, Bleu, Rouge, Vert, ... [16种颜色]

返回包含提取值的JSON对象。

对于手机,提示中有790个标签。也许还可以管理。

但对于服装呢?提示需要列出1,500+个服装品牌。不可能。

4.1 发生了什么

一切都出错了。

对于手机等"简单"类别(类别17),结果平庸:29.5%的精确匹配(意味着所有属性正确)。这比我们的生产模型(37%)还差。

但对于复杂类别,结果是灾难性的:

+-------------------+-------+--------+-----------+-----------------------------+
| 类别              | 属性  | 标签   | V0 提示   | 出了什么问题                |
+-------------------+-------+--------+-----------+-----------------------------+
| 服装              | 6     | 1600+  | 0.0%      | 仅品牌就有1500+个           |
| 装饰品            | 4     | 75     | 0.0%      | 模型被选项混淆              |
| 玩具              | 3     | 36     | 5.0%      | 数量少但仍然失败            |
| 儿童家具          | 3     | 237    | 5.6%      | 200个婴儿用品品牌           |
+-------------------+-------+--------+-----------+-----------------------------+

零。在服装上,模型在200个样本中无法完全正确地做出一个预测。

4.2 为什么失败了

1. 提示长度爆炸

对于服装,我们有6个属性需要预测:

- 品牌:1,500+标签
- 类别:16标签
- 颜色:21标签
- 服装类型:4标签
- 服装尺寸:59标签

仅列出品牌名称就会消耗约15,000个token。尽管Qwen3-VL-8B支持128K上下文窗口,但我们发现模型很难从1,500+个相似的品牌名称中选出正确的选项。大量的选择导致了混淆和幻觉。

2. 相似选项混淆了模型

当给模型500个手机型号时,它开始在相似名称上犯错:

- "iPhone 13 Pro" vs "iPhone 13 Pro Max"
- "Galaxy S23" vs "Galaxy S23+" vs "Galaxy S23 Ultra"
- "Xiaomi 13" vs "Xiaomi 13 Pro" vs "Xiaomi 13 Ultra"

模型会选择看起来合理但不完全正确的选项。

3. 不理解依赖关系

这是一个微妙但关键的问题:属性之间存在依赖关系。

如果phone_brand = "Apple",则phone_model必须是42个Apple型号之一(iPhone 13, iPhone 13 Pro, iPhone 14...)。
如果phone_brand = "Samsung",则phone_model必须是80+个Samsung型号之一(Galaxy S23, Galaxy S24...)。

有24个品牌,每个品牌有自己的有效型号列表,总计700+个手机型号。

但我们简单的提示只是将所有700+个型号列在一起。模型有时会预测brand = "Apple"和model = "Galaxy S23",这是纯粹的幻觉。或者它会选择一个该品牌不存在的型号。

服装更糟糕。clothing_type(上衣、下装、套装、其他)决定了有效的clothing_st(尺寸/样式)值。此外,大量的品牌标签需要"模糊匹配"策略,在标题中进行类似正则表达式的词搜索以找到最接近的品牌匹配,因为我们无法枚举所有有效组合。

4.3 我们学到了什么

教训1:将所有标签倒入提示中无法扩展。模型会不堪重负。

教训2:没有显式的依赖关系处理,模型会做出不一致的预测。

教训3:仅靠提示工程无法解决复杂的结构化提取问题。

我们需要更聪明的方法。

5、V1 - 复杂RAG:带依赖关系处理的级联2步预测

V0失败是因为我们一次向模型抛出所有东西。显而易见的解决方案:将问题分解成更小的部分。

我们设计了一个带显式依赖关系处理的级联2步预测系统:

第1步:首先预测"父"属性(如品牌)

第2步:使用父预测过滤有效的"子"选项(如型号),然后预测这些选项

这需要构建几个组件:

1. 依赖关系配置

我们映射了所有属性之间的父子关系。

// 手机类别:24个品牌 → 742个型号
{
  "dependencies": [{
    "master": "phone_brand",
    "dependent": "phone_model",
    "mapping": {
      "apple": ["iphone11", "iphone11pro", "iphone11promax", "iphone12",
                "iphone12mini", "iphone12pro", "iphone13", "iphone13mini",
                "iphone13pro", "iphone13promax", "iphone14", "iphone14plus",
                "iphone14pro", "iphone14promax", "iphone15", "iphone15plus",
                "iphone15pro", "iphone15promax", "iphone16", "iphone16pro",
                ...],  // 42个Apple型号
      "samsung": ["galaxys23", "galaxys23plus", "galaxys23ultra",
                  "galaxys24", "galaxys24plus", "galaxys24ultra",
                  "galaxyzflip5", "galaxyzfold5", ...],  // 80+个Samsung型号
      "xiaomi": ["xiaomi13", "xiaomi13pro", "xiaomi14", ...],
      // ... 24个品牌总计,742个型号总计
    }
  }]}

// 服装类别:更复杂

{
  "dependencies": [
    {
      "dependent": "clothing_brand",
      "strategy": "fuzzy_match",  // 无法枚举1,503个品牌!
      "short_list": ["nike", "zara", "kiabi", "levis", "adidas", "hm",
                     "decathlon", "shein", "ralphlauren", "lacoste", ...]
      // 使用模糊匹配和"常见品牌"短列表
    },
    {
      "master": "clothing_type",
      "dependent": "clothing_st",
      "mapping": { /* 4种类型 → 59种尺寸 */ }
    }
  ]
}

不仅仅是配置,它是编码为数据结构的领域知识。而且必须为16个类别维护,有些类别有多个依赖链。

2. 两步推理管道

# 简化的级联预测流程

async def predict_with_cascade(ad_title: str, images: list[str], category: str):
    # 第1步:获取父属性
    parent_attrs = get_parent_attributes(category)  # 例如 ["phone_brand"]

    parent_prompt = build_prompt(
        ad_title, images,
        attributes=parent_attrs,
        valid_values=get_all_values(parent_attrs)  # 较小的列表
    )

    parent_predictions = await model.predict(parent_prompt)
    # 结果:{"phone_brand": "Apple"}

    # 第2步:获取带过滤选项的子属性
    child_attrs = get_child_attributes(category)  # 例如 ["phone_model"]

    # 关键见解:基于预测的品牌过滤有效型号
    filtered_values = filter_by_parent(
        child_attrs,
        parent_predictions  # 现在只显示Apple型号
    )

    child_prompt = build_prompt(
        ad_title, images,
        attributes=child_attrs,
        valid_values=filtered_values,  # 只有约50个Apple型号,而不是500+
        context=parent_predictions  # 告诉模型品牌是Apple
    )

    child_predictions = await model.predict(child_prompt)
    # 结果:{"phone_model": "iPhone 13 Pro"}

    # 合并结果
    return {**parent_predictions, **child_predictions}

3. 动态模式生成

最后,我们还构建了一个模式生成器,为依赖字段创建带有oneOf分支的JSON模式:

def build_schema_with_dependencies(category: str, parent_values: dict):
    """生成只允许有效组合的JSON模式。"""
    schema = {"type": "object", "properties": {}}

    for attr, parent in get_dependencies(category):
        parent_value = parent_values.get(parent)
        if parent_value:
            # 只包含对此父级有效的子值
            valid_children = dependency_config[attr][parent_value]
            schema["properties"][attr] = {
                "type": "string",
                "enum": valid_children
            }

    return schema

架构

V1系统看起来是这样的:

这是一项重大的工程工作: — 约300行依赖配置系统 — 约400行级联预测器 — 约250行动态模式构建器 — 约150行值映射 — 加上测试、边缘情况处理和文档

大约3周的工作。

5.1 发生了什么

好消息:V1比V0好得多。

+-------------+-----------+----------------+-------------+
| 类别        | V0 提示   | V1 复杂RAG     | 改进        |
+-------------+-----------+----------------+-------------+
| 手机        | 29.5%     | 38.5%          | +9.0%       |
| 服装        | 0.0%      | 10.5%          | +10.5%      |
| 装饰品      | 0.0%      | 13.5%          | +13.5%      |
| 玩具        | 5.0%      | 29.0%          | +24.0%      |
| 电子游戏    | 67.0%     | 86.4%          | +19.4%      |
+-------------+-----------+----------------+-------------+

我们从0%到在困难类别上实际工作。级联方法解决了依赖问题。不再有"Apple品牌,Galaxy型号"的胡言乱语。

但也有代价:

1. 延迟翻倍

两次推理调用意味着双倍的时间:

  • 第1步:约300ms
  • 第2步:约300ms
  • 总计:约600ms + 开销

对于实时API来说,这很痛苦。

2. 维护噩梦

每次有新手机型号发布,我们都必须更新依赖配置。三星发布了新的Galaxy?更新配置。苹果宣布了iPhone 16?更新配置。

而且配置必须完美。如果我们忘记将某个型号添加到三星列表中,系统就永远不会预测它,即使模型在标题中清楚地看到了"Galaxy S24"。

3. 到处都是边缘情况

如果第1步预测错了品牌怎么办? 现在第2步使用了错误的过滤列表。错误会级联。

只有一个型号的品牌呢?它们仍然需要两步吗?

循环依赖呢?(是的,有些类别有循环依赖。)

每个边缘情况都需要特殊处理、更多代码和更多bug。

4. 仍然没有击败生产环境

令人沮丧的是:即使有所有这些复杂性,V1在平均水平上只匹配了我们的生产模型。我们在某些类别上击败了它(电子游戏:86.4% vs 82%),但在其他类别上输了(手表:17.5% vs 25.5%)。

所有这些工程,我们大致是打平了。

5.2 我们学到了什么

教训4:级联预测解决了依赖问题,但以延迟和复杂性为代价。

教训5:基于配置的方法创造了维护负担。每个新产品都需要手动更新。

教训6:3周的工程来匹配(不是击败)现有解决方案,ROI不好。

我们需要更简单的东西,可以自动学习依赖关系。

6、V2 —— 简单的解决方案:如果只是微调呢?

在构建级联系统数周后,我们有了一个认识:模型不需要我们告诉它iPhone是Apple制造的。它可以从例子中学习。

想想看。在我们的训练数据中,每次模型看到"iPhone 13 Pro",标签都写着品牌:Apple,型号:iPhone 13 Pro。每次它看到"Galaxy S24",标签都写着品牌:Samsung,型号:Galaxy S24。

给模型展示数千个这样的例子,它就会学到这个模式。不需要配置文件。不需要依赖解析器。不需要两步骤联。

品牌和型号之间的关系不是我们需要工程化的。而是模型可以学习的东西。

6.1 我们尝试了什么

所以我们决定使用LoRA和Unsloth构建微调管道:

1. 数据集准备

我们从数据库中采样了真实广告: — 16个类别 — 每个类别约200个例子 — 每个例子:标题、图像、真实属性

我们将它们格式化为法语的聊天风格对话:

{
  "messages": [
    {
      "role": "system",
      "content": "Tu extrais les attributs produits à partir du titre et de l'image. Réponds en JSON valide uniquement."
    },
    {
      "role": "user",
      "content": [
        {"type": "image", "image": "image_base64_data"},
        {"type": "text", "text": "Catégorie: Téléphones\nTitre: \"iPhone 13 Pro 256GB Bleu Pacifique excellent état\"\n\nExtrais: phone_brand, phone_model, phone_memory, phone_color"}
      ]
    },
    {
      "role": "assistant",
      "content": "{\"phone_brand\": \"Apple\", \"phone_model\": \"iPhone 13 Pro\", \"phone_memory\": \"256GB\", \"phone_color\": \"Bleu\"}"
    }
  ]
}

2. LoRA微调

我们使用Unsloth进行高效微调:

from unsloth import FastVisionModel
from trl import SFTTrainer, SFTConfig

model, tokenizer = FastVisionModel.from_pretrained(
    "Qwen/Qwen3-VL-8B-Instruct",
    load_in_4bit=True,  # 适合单个A10G GPU
)

model = FastVisionModel.get_peft_model(
    model,
    r=16,              # LoRA秩
    lora_alpha=32,     # 缩放因子
    target_modules=[   # 要适配哪些层
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj"
    ],
)

trainer = SFTTrainer(
    model=model,
    train_dataset=train_data,
    eval_dataset=val_data,
    args=SFTConfig(
        num_train_epochs=3,
        per_device_train_batch_size=2,
        gradient_accumulation_steps=8,
        learning_rate=2e-4,
        warmup_steps=50,
        eval_strategy="steps",
        eval_steps=100,
        save_strategy="steps",
        save_steps=100,
        load_best_model_at_end=True,
    ),
    callbacks=[EarlyStoppingCallback(early_stopping_patience=3)],
)

训练时间:单个A10G GPU约1小时。

就是这样。没有依赖配置。没有级联逻辑。没有值映射器。

3. 带引导式解码的推理

对于服务,我们使用带有JSON模式约束的vLLM:

from openai import AsyncOpenAI

client = AsyncOpenAI(base_url="http://localhost:8000/v1")

response = await client.chat.completions.create(
    model="fine-tuned-qwen",
    messages=[
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_content_with_images}
    ],
    extra_body={
        "guided_json": json_schema_for_category  # 枚举约束
    }
)

引导式解码确保有效的JSON输出,值来自我们的枚举列表——99%以上的有效响应率。

架构

组件:1个(模型) 推理调用:1次 配置文件:0 维护:需要时重新训练

6.2 发生了什么

结果让我们惊讶:

+-------------+-------------+----------------+-------+
| 类别        | 复杂RAG     | 微调V2         | 差值  |
+-------------+-------------+----------------+-------+
| 手机        | 38.5%       | 49.5%          | +11.0 |
| 家用电器    | 62.0%       | 72.0%          | +10.0 |
| 服装        | 10.5%       | 16.5%          | +6.0  |
| 玩具        | 29.0%       | 40.0%          | +11.0 |
| 自行车      | 14.0%       | 26.5%          | +12.5 |
| 平板电脑    | 50.5%       | 57.5%          | +7.0  |
| 游戏机      | 86.4%       | 87.5%          | +1.1  |
+-------------+-------------+----------------+-------+

微调在16个类别中的10个中获胜。而且胜利是显著的,通常有10%+的改进。

结果表

+--------------------------+-------+-------+-------+--------+----------------+
| 类别                     | 生产  | Haiku | V0    | V1 RAG | V2 FT (Δ vs V1)|
+--------------------------+-------+-------+-------+--------+----------------+
| 手机                     | 37.0% | 27.0% | 29.5% | 38.5%  | 49.5% (+11.0%) |
| 家用电器                 | 67.0% | 29.0% | 50.0% | 62.0%  | 72.0% (+10.0%) |
| 服装                     | 6.5%  | 6.0%  | 0.0%  | 10.5%  | 16.5% (+6.0%)  |
| 婴儿用品                 | 29.5% | 16.0% | 39.5% | 42.5%  | 44.5% (+2.0%)  |
| 装饰品                   | 18.0% | 14.0% | 0.0%  | 13.5%  | 17.0% (+3.5%)  |
| 玩具                     | 46.5% | 18.0% | 5.0%  | 29.0%  | 40.0% (+11.0%) |
| 手表                     | 25.5% | 7.0%  | 19.0% | 17.5%  | 17.5% (0.0%)   |
| 电子游戏                 | 82.0% | 83.0% | 67.0% | 86.4%  | 80.0% (-6.4%)  |
| 自行车                   | 32.5% | 13.0% | 13.5% | 14.0%  | 26.5% (+12.5%) |
| 儿童房家具               | 27.3% | 19.0% | 5.6%  | 22.4%  | 26.6% (+4.2%)  |
| 平板电脑                 | 40.5% | 51.0% | 12.0% | 50.5%  | 57.5% (+7.0%)  |
| 游戏机                   | 80.5% | 85.4% | 79.0% | 86.4%  | 87.5% (+1.1%)  |
+--------------------------+-------+-------+-------+--------+----------------+
| 平均值                   | 34.9% | 24.3% | 22.4% | 34.6%  | 37.7% (+3.1%)  |
+--------------------------+-------+-------+-------+--------+----------------+

图例: V0 = 天真提示(所有标签在提示中),1周工程 V1 = 复杂提示(级联2步),3周工程 V2 = 微调(1小时训练),1周工程 (+X.X%) = 与V1复杂RAG的差值

6.3 为什么微调赢了

1. 依赖关系是学习的,不是配置的

模型看到了数千个"iPhone"与品牌:Apple一起出现的例子。它学到了这个模式。我们不需要告诉它。

当V2看到一个带有"iPhone 15 Pro Max"的新广告时,它会自动预测品牌:Apple,即使我们从未在任何配置中明确列出该型号。

2. 单次推理调用

V1需要两次调用(父→子)。V2只需要一次。约40%更快。

3. 没有配置漂移

当三星发布新手机时,V1需要配置更新。V2只需要在下一个训练批次中看到该手机的例子,这会随着用户列出新产品而自然发生。

4. 模型学习视觉模式

这是微妙的一点。V1的级联系统在根本上仍然是基于文本的,图像作为上下文。V2是在标题+图像→属性上端到端训练的。

微调后的模型学会了真正看图像。对于颜色属性(我们最初的动机!),这产生了巨大的差异。

6.4 我们学到了什么

教训7:微调可以隐式学习依赖关系。不需要工程化它们。

教训8:1小时的训练可以替代3周的架构。

教训9:简单的解决方案往往胜过聪明的工程。

7、工程投入比较

老实说,每种方法的成本是多少:

+------------------+----------+---------------+--------+-----------+
| 方法             | 工程时间 | 代码量        | 维护   | 准确率    |
+------------------+----------+---------------+--------+-----------+
| V0 天真提示      | ~1周     | ~200行        | 低     | 22.4%     |
| V1 复杂RAG       | ~3周     | ~1100 + 500c  | 高     | 34.6%     |
| V2 微调          | ~1周     | ~1300行       | 低     | 37.7%     |
+------------------+----------+---------------+--------+-----------+

V2需要与V1类似的代码(数据集准备、训练循环、推理),但是: — 没有依赖配置文件 — 没有级联编排逻辑 — 没有值映射层 — 没有两步推理的边缘情况处理

而且关键是:没有持续的配置维护。

8、何时使用每种方法

微调并不总是答案。以下是我们的框架:

使用天真提示(V0):

  • 当你在原型设计且需要快速得到结果时
  • 标签空间小(每个属性<50个选项)
  • 你没有训练数据

使用复杂RAG(V1):

  • 你需要可解释性("模型因为检索到的例子Y而选择了X")
  • 无法获得训练数据
  • 标签每天都在变化,重新训练不可行
  • 你负担不起GPU训练

使用微调(V2):

  • 你有标记的训练数据(我们有6个月的标记广告)
  • 准确率是优先考虑的
  • 你想要更简单的部署和维护
  • 你可以定期重新训练(每月、每季度)
  • 属性之间存在依赖关系

对于我们的用例——具有稳定(但庞大)标签集和大量训练数据的结构化属性提取,微调是明显的赢家。

9、为什么生产模型在某些类别上仍然获胜

查看我们的结果,有趣的是:生产的n-gram模型在某些类别上仍然击败了我们的微调VLM:

+-------------+--------+--------+--------+--------+
| 类别        | 标签   | 生产   | V2 FT  | 获胜者 |
+-------------+--------+--------+--------+--------+
| 电子游戏    | 88     | 82.0   | 80.0   | 生产   |
| 手表        | 184    | 25.5   | 17.5   | 生产   |
| 玩具        | 36     | 46.5   | 40.0   | 生产   |
| 家具        | 80     | 23.0   | 19.5   | 生产   |
+-------------+--------+--------+--------+--------+

为什么简单的字符n-gram分类器在这些类别上能胜过8B参数的VLM?

n-gram模型的秘密武器:结构化文本上的模式匹配

生产模型使用带有字符n-gram(2-5)的CountVectorizer进行结构化文本上的模式匹配,在具有可预测标题格式的类别中证明非常有效。

例如,在电子游戏类别(88个标签)中,结构化标题如"FIFA 24 PS5 neuf sous blister"允许模型捕获"PS5"、"FIFA"、"24"和"neuf"等n-gram,准确识别游戏机品牌、型号和游戏类型。模型的数百万个学习模式立即将"PS5"等术语与Sony关联起来,高效处理有限的品牌和型号集合。

同样,在手表类别(184个标签)中,品牌密集的标题如"Montre Rolex Submariner homme automatique"产生品牌指示性n-gram("Rolex"、"Subm"),可以在138个常提到的手表品牌中立即识别品牌和类型,通常不需要图像。

VLM何时获胜:视觉信息和庞大的标签空间

微调的VLM在n-gram模型挣扎的地方表现出色:

  • 手机:V2获胜49.5% vs 37.0%(790个标签)。视觉区分处理颜色、型号变体(如Pro/Pro Max相机布局),并克服了标题中742个相似字符模式的混淆。
  • 家用电器:V2获胜72.0% vs 67.0%(129个标签)。品牌标志(Dyson、Samsung、LG)和产品类型在视觉上通常比描述更清晰。
  • 服装:V2获胜16.5% vs 6.5%(1,600+标签)。n-gram在这里失败。颜色和风格(休闲、正式)几乎总是视觉的,标志识别了许多1,500+个标题中没有的品牌。
  • 自行车:V2略输26.5% vs 32.5%(20个标签)。视觉分类通过车架几何形状确定自行车类型(VTT、公路、城市),有时从车架确定尺寸。

10、见解:文本密度 + 标签空间大小

我们可以根据两个因素粗略预测哪个模型会获胜:

根据文本密度和标签空间选择正确的模型

示例: "FIFA 24 PS5 neuf" → N-gram:所有信息都在文本中,标签空间小 "Montre Rolex Submariner" → N-gram:品牌在标题中,标签适中 "iPhone bon état" + [图像] → VLM:颜色/型号在图像中,742个型号可选 "Robe été" + [图像] → VLM:品牌/颜色在图像中,1,500+个品牌!

混合机会

这种分析暗示了一种潜在的混合方法: — 对于文本密度高的类别(电子游戏、手表、游戏机)使用快速、廉价的n-gram模型 — 对于视觉信息重要的低文本密度类别(服装、手机、电器)使用VLM

基于类别的路由器可以获得两全其美:在文本足够时使用n-gram速度,在图像重要时使用VLM准确率。

11、下一步是什么

这还不是最终的生产模型。我们一直在将微调的VLM与内部的多模态变压器架构进行挑战,该架构融合了文本n-gram和视觉嵌入,它实际上表现得非常好。"简单微调VLM"与"定制轻量级融合模型"之间的战斗仍在进行中。

请继续关注下一篇博文,我们将深入比较这两种方法,并揭示哪种方法进入了生产环境。

关键要点

  1. 从简单开始,但要准备好尝试微调。我们的V0是正确的第一个实验。但我们应该在构建V1的复杂性之前尝试微调。
  2. 依赖关系可以学习,而不是工程化。1小时的训练教会了模型500行配置试图编码的内容。
  3. 复杂架构有隐藏成本。V1有效,但维护负担不可持续。
  4. 训练数据 > 聪明的提示。模型从10K例子中学到的比从我们精心设计的提示中学到的多。
  5. 简单的解决方案往往获胜。V2的架构装在一个盒子里。V1的有七个组件。V2赢了。

最好的代码往往是你不写的代码。有时最聪明的工程决策是让模型学习,而不是试图通过架构来教它!


原文链接: How 1 hour of fine-tuning beat 3 weeks of RAG engineering

汇智网翻译整理,转载请标明出处