AI 摘要
为什么是谐音梗
为什么是谐音梗
谐音梗(pun)理解是视觉-语言理解里一个很特殊的任务:它要求模型同时具备
- 图像识别能力(看到的是什么东西);
- 中文语音学知识(「梅」和「没」同音);
- 幽默感与推理能力(知道这是哪里好笑)。
前两个可以靠预训练补齐,第三个几乎是纯「品味」问题。所以微调要解决的核心问题是:怎么用可验证的方式教会模型品味。
数据流水线
整体流程:
图片爬取 → 人工/自动打标 → 谐音梗模板生成 → VLM 校验 → 难例挖掘 → LoRA 微调
1. 图片来源
我们没有用模型生成图片,而是从公开数据集里捞。图源有两个:
- 谐音梗相关:直接带有 pun 标注的数据集;
- 通用图片 + LLM 造梗:让 VLM 看着图想一个谐音梗。
第二条链路产量大、噪声也大,后面处理。
2. 模板生成
用 LLM 把「梗的文本」套进若干模板,生成 grounding 描述:
{
"image": "spicy-hotpot.jpg",
"pun_text": "锅气",
"answer": "辣锅",
"options": ["辣锅", "辣个锅", "啦个锅", "腊个锅"],
"type": "谐音替换",
"explanation": "「锅气」的「锅」与「辣锅」的「锅」同音,「气」谐音替换为「辣」。"
}
关键在选项构造——这是脏数据的主要来源。
我犯的三个错误
错误一:判重只比 lower()
最初我用 option.lower() == other.lower() 判重。结果:
A Bell和a bell被当成两个不同选项;T-shirt和T-SHIRT被当成两个不同选项。
结果我造出了 594 条「4 个选项里有 2 个是正确答案」的脏题。模型训出来之后在这批题上准确率反而下降,我一度以为是过拟合。
正确做法是归一化后再比:
import re
def norm(s: str) -> str:
return re.sub(r'[^a-z0-9]+', '', s.lower())
错误二:切分分组数不够
我最初用 i % 10 < 8 做 8:1
kind: task_type 分组,总共只有 7 组。结果前 7 组全部落进 train,val 和 test 是空的。
train 里没有 val 字段这件事,直到我打印 len(val) 得到 0 才发现。
现在改用稳定散列:
import hashlib
def bucket(item_id: str, n: int = 10) -> int:
return int(hashlib.md5(item_id.encode()).hexdigest(), 16) % n
切分必须保证 组数 > 分桶数,否则一定失效。
错误三:循环内变量没初始化
for item in items:
if item.has_image:
image_path = item.path
if item.has_answer:
answer = item.answer
records.append({
"image": image_path, # 上一个没有图的 item 的图!
"answer": answer,
})
当某个 item 既没有图也没有答案时,变量沿用上一轮的残值。结果 metadata 里 16 条记录错指别人的资源。
修法很简单——每轮开头重置:
for item in items:
image_path = None
answer = None
...
清洗流程
最终我的清洗脚本做这几件事:
| 检查项 | 手段 | 拦截量 |
|---|---|---|
| 选项归一化后重复 | norm() 去重 | 594 条 |
| 正确答案个数 ≠ 1 | 计数 | 1,203 条 |
| 图片路径不存在 | os.path.exists | 431 条 |
| 解析不出谐音关系 | LLM 复核 | 2,880 条 |
| 文本过短 / 模板味过重 | 长度 + 去模板化检测 | 1,077 条 |
清洗前后:
原始: 62,417 条
清洗后:56,232 条(保留 90.1%)
训练配置
base_model: Qwen3-VL-8B-Instruct
method: LoRA
lora_rank: 32
lora_alpha: 64
lora_dropout: 0.05
target_modules: [q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj]
lr: 1e-4
scheduler: cosine
warmup_ratio: 0.03
epochs: 3
batch_size: 4 (grad_accum 8)
max_pixels: 1280 * 28 * 28
几个经验:
- 必须冻结视觉编码器。我一开始没冻,显存直接爆,而且过拟合更严重(模型开始「记住」训练图而不是学谐音逻辑)。
max_pixels要压。默认分辨率下 8B 模型的显存吃紧,降到1280 * 28 * 28之后刚好能塞进单张 80G 卡。- LoRA rank 32 足够。试过 64,过拟合明显变差,验证集准确率反而掉 1.8 个点。
结果
| 指标 | Base | LoRA |
|---|---|---|
| 谐音梗选择准确率 | 41.2% | 67.8% |
| 谐音位置定位 IoU | 0.31 | 0.58 |
| 通用 VQA(未微调任务) | 68.5% | 67.9% |
通用能力基本不掉,说明 LoRA 没有把模型带偏。
下一步
- 难例挖掘:用当前模型推理全量数据,把「置信度低但 VLM 校验通过」的样本挑出来做第二轮训练;
- 三语泛化:中文谐音梗依赖音韵,迁移到日语和英语的难度更大,需要先确认「谜面 → 谐音替换」这个模式本身能不能跨语言成立。
一点碎碎念
谐音梗这个任务有个很微妙的地方:模型很容易学到「表层规律」而不是「语言规律」。比如我们发现,如果正确答案总是四个选项里唯一带某种声调的,模型就学会了「找带 X 调的选项」。
为了堵这个洞,我们专门构造了一批对抗样本:把正确答案的声调随机改变,同时重新生成干扰项。这类样本在全量数据里占 3%,但模型在它们上的准确率从 71% 掉到 48%,说明表层规律的漏洞比我们想象的大。
数据脏一点不要紧,假装数据干净才是真的要命。