# 三元组抽取:注意力模型方案 > 2026-10-03。目标是**推理时零 LLM**——一次 LLM 标注,训一个专用网络。 ## 一、为什么必须是「注意力学任务结构」而不是更好的向量 本会话的实测给出了两条独立证据: | 实验 | 结果 | |---|---| | 词法切分(纯规则) | 值抽取 75%,字段级仅 25% | | chineseclip 向量配对(冻结编码器) | **0/5**,连「值班人」↔「值班人」都配不上 | 第二条是决定性的:`值班人` 和 `值班人` **字面完全相同**,余弦却输给 `端口`(0.5531)。 chineseclip 把一句话压成一个 512 维固定向量,**没有针对这个任务的注意力**—— 它不知道「哪个 token 是属性名、哪个是值、哪个是连接词」。 而这正是 transformer 注意力的本职: ``` 「第112批周四凌晨2点·停机4分·回滚v2.29.5·灰度10%观察48分后放50%」 第112批(主语) 周四凌晨2点 · 停机4分 · 回滚v2.29.5 · 灰度10% 观察48分后放50% └─ 头应该关注 ─┘ └ 值 ┘└ 值 ┘└ 值 ┘└ 值 ┘ └值┘ 「停机」只关注「4分」 「灰度」只关注「10%」 ``` 同一句话里,不同的注意力头分工不同 —— 这是固定向量做不到的。 ## 二、任务形式的选择 ### 方案 B(序列标注)—— 采用 给每个字符打一个标签: ``` 标签集(9 类) O 其它/连接词(从、改为、轮换到、·) B-DIM 属性名开头 I-DIM 属性名内部 B-VAL 值开头 I-VAL 值内部 B-SUBJ 主语开头(多主语记录用) I-SUBJ 主语内部 PAD 填充 「连接池从 32/64 扩到 128/256」 连接池从 → B-SUBJ I-SUBJ I-SUBJ O 32/64 → B-VAL I-VAL I-VAL I-VAL I-VAL 扩到 → O O 128/256 → B-VAL I-VAL I-VAL I-VAL I-VAL I-VAL ``` **为什么不用方案 A(在受控维度名里分类)**: - 现有标注里维度名 17 种,但**6 种只出现 1 次(35%)** —— 分类器无法表达词表外的新维度 - 而维度名正是要**生成**的东西,不是要**选择**的东西 **为什么不用指针网络**:抽取式要先枚举候选片段对,而实测候选池的真值召回只有 20%(51/259)—— 枚举不出来就没法抽。序列标注直接在字上标,绕开这个漏。 ## 三、模型规格(CPU 可训) ``` 分词 字符级(中文无需分词,且字符级不会切错「值班室分机号」) 词表 从训练集统计,取频次 ≥2 的字 + 特殊符,约 1500~2500 嵌入 128 维 层数 4 注意力头 4(2 头 "看属性名↔值" 的配对,2 头自由学) FFN 256 Dropout 0.2 总参数 ≈ 1.2M ``` **为什么这么小**:CPU 12 核、17 GB 内存、没有 GPU(P4 掉线)。 实测 2968 个训练 token 的规模下,1.2M 参数已经过参数化的风险很低, 而这个规模**不需要预训练**——从零训,因为: - 任务词汇封闭(运维数字/端口/批次/分机号) - 预训练模型的 2~3 亿参数是纯浪费,且 CPU 上前向都慢 **「从零训」在这里不是妥协,是对的**。预训练权重带来的是通用语言知识, 而这个任务要学的是**版式**(哪里是属性名、哪里是值)—— 版式没有通用先验。 ## 四、递归调用:一句话 → 多组三元组 序列标注天然给出多组三元组(每个 `B-DIM … B-VAL` 是一个), 不需要"递归": ``` 输出标签序列后: 扫描 B-DIM / I-DIM 聚成属性名片段 扫描 B-VAL / I-VAL 聚成值片段 按顺序配对:第 k 个属性名 ↔ 第 k 个值 全部转成受控维度名(NormalizeDimension) 全部转成主语(若标了 B-SUBJ) ``` 「递归」只在一种情况下需要:**值里还嵌套属性**(如 `连接池容量=128/256` 里的 `连接池` 既是主语又是维度的一部分)。第一版不做,遇到时记日志。 ## 五、训练数据现状与缺口 ``` 来源 样本 字段数 有效信号 真库 ha-c(已有) 90 260 70 条多字段句 生产库(标注中) 429 ? 仅 20 条含「数字+单位」 ──────────────────────────────────────────── 合计 519 ? ~90 ``` **缺口**:序列标注需要约 5000 条句子 × 35 字 ≈ **17.5 万 token**, 中文 NER 的典型规模是 10 万~200 万 —— 差一个数量级。 **这决定了它现在不能训出可用模型**。可行的路径: 1. 扩充数据源:真库 188 条 + 生产库 1295 条 + 对话归档 + 历史邮件 (预计能到 3000~5000 条句子) 2. 或者**改任务**:既然值抽取已 75%(词法可做), 网络只学「给已知的值配维度名」—— 这是个二分类任务, 样本数 = 真值数 × 负样本倍数,比序列标注省一个数量级 第 2 条是我目前更倾向的,因为它把难题缩小到了「值↔维度配对」, 而那正是词法做不到、注意力擅长的部分。 ## 六、判据(照本会话惯例:每条都有变异自证) | 判据 | 内容 | |---|---| | 标签对齐 | 字符级标注与序列标签严格一一对应,无偏移 | | 幻觉闸门 | 产出的值必须**原样出现在原句**(与 LLM 路径同一道闸) | | 泛化 | 留出集上字段级 F1;词法基线是 25% | | **不得低于基线** | 网络在留出集上必须超过词法的 25%,否则说明网络没学到东西 | | 三类可分 | 同值对 / 异值对 / 跨维度对的分布必须可分(否则学到的只是长度先验) | | 确定性 | 同输入连跑 3 次输出完全一致(**LLM 路径目前做不到**) | 最后一条是这个方案最大的隐性收益:**确定性**。LLM 路径在本会话实测 「同批 146 条跑两次差 40%」(后来查明很可能不是随机性,而是 ollama 0.31.1 的 schema grammar 污染 bug —— 但即使修好,神经网络推理也是逐位确定的)。 ## 七、当前阻塞 标注在跑(`/tmp/label-work/labeled.jsonl`,429 条,约 3~4 小时)。 **标注质量是第一道关**——本会话已经踩过 ollama schema 污染, 所以每条标注都带「值必须原样出现在原句」的闸门,且产出与原句一起落盘, 以便事后逐条复核。 ## 八、数据实测:标注分两层,网络只该学其中一层 把 260 个 LLM 标注逐条与原句对齐后(属性名「值」都要能在原句里定位): ``` 字面可对齐 212 / 260 (82%) 主语派生 48 / 260 (18%) ← 网络学不了,也不该学 ``` ### 主语派生的那一类长什么样 ``` 「第130批 v2.31.5 评审通过·采样10%」 → 版本=v2.31.5 属性名「版本」不在原句(原句只有 v2.31.5) → 批次号=130 属性名与值都不在原句(原句是「第130批」,N 藏在「第」与「批」之间) ``` 逐维度看: | 维度 | 条数 | 对齐失败率 | 性质 | |---|---|---|---| | 排期 / 容量预警 / 告警规则数 / 值班手册版本 / 评审通过 / 停机时长 | 178 | **0%** | 字面可对齐 | | 版本 / 版本线 | 32 | 97% | 主语派生(原句只有 `v2.31.5`) | | 批次号 / 批次 | 40 | 100% | 主语派生(`第N批` 的 N 是**结构位置**,不是连续片段) | | 发布窗口 / 连接池容量 / 等待队列告警阈值 / 状态 | 8 | 100% | 主语派生 | ★ **「批次号=130」是最有说服力的一例**:值 `130` 在原句里根本不是连续片段 (它是「第**130**批」的中间),任何基于 span 的标注法都对齐不了它。 ### 由此确定分工 | 类型 | 占比 | 处理者 | 理由 | |---|---|---|---| | 字面可对齐 | 82% | **注意力网络** | 属性名与值都在句子里,注意力学的是「哪个 token 是属性名」 | | 主语派生 | 18% | **纯规则**(`DeriveSubjects` 已能推出主语,从主语结构直接取) | 不在句子表面,神经网络无输入可用 | ★ 这条分工让训练数据量需求从「17.5 万 token」降到「约 8.6 万 token」, 更关键的是**学的是真问题**(版式识别)而不是学一个学不会的东西。 ## 九、还发现一条 LLM 标注质量问题 ``` 值 = 整句:9 / 260 (3%) 「第115批周二凌晨2点·停机6分·回滚v2.28.1·灰度5%观察63分后直放100%」 → 版本=115批周二凌晨2点·停机6分·回滚v2.28.1·灰度5%观察63分后直放100% ``` 模型把整句当成「版本」的值。现有闸门拦不住它(值确实「原样出现在原句」—— 它就是整句)。**新增判据:值的长度不应超过句子的 60%**,否则视为坏标注。 ## 十、推理侧与首轮实测(数据不足,模型不可用) 推理侧已写好(`scripts/triple-extract/predict.py`),形态是**网络 + 规则混合**: ``` 网络 负责「句子里表面就有属性名与值」的 82% 规则 负责「值藏在主语结构里」的 18%(批次号=第N批里的 N、版本=v2.31.5) 闸门 两边共用同一道(值必须原样出现在原句;值长度 ≤ 句子 60%; 属性名必须能在原句里对上 —— 网络不比 LLM 宽松) ``` ### 规则部分:实测全对 ``` 「第114批周二凌晨1点·停机4分·回滚v2.28.4·灰度10%观察70分后放50%」 → 第114批|批次号=114 ✓ → 第114批|版本=v2.28.4 ✓ → 第114批|发布窗口=凌晨1点 ✓ ``` 这三类正是网络学不了的那 18%,规则路径有效。 ### 网络部分:实测不可用 同一句的网络输出: ``` [ 0] B-DIM '值' ← 应是「值班室分机号」 [ 1] I-DIM '班' [ 4] B-DIM '机' [ 5] B-VAL '号' ``` **位置全错**(标在第 0/1/4/5 个字符,实际属性名在 0-5、值在 7-10), 且 `值班室分机号 4324,值班 老周` 里的 `4324` 完全没被标出。 原因不是实现 bug,是**数据量**:47 条训练样本 × 35 字, 留出 19 条上 F1 0.71 且 ep20 后开始回落(已在过拟合)。 训练集的标签分布是 `O:771 / I-VAL:592 / I-DIM:565 / B-VAL:192 / B-DIM:187` —— 模型连「B 只在 span 首字符出现」这个最基本的 BIO 约束都没学稳。 ### 一个训练脚本的 bug(顺带修掉) `OneCycleLR(total_steps=epochs * len(train) // bs)` 在步数不匹配时 会抛 `Tried to step 151 times. The specified number of total steps is 150` —— 训练跑到一半崩掉。改用 `ceil` 并显式设 `pct_start`。 ### 判据 4(网络必须超过查表基线 71%)现在的状态 | 方案 | 留出表现 | |---|---| | 规则 + 查表 | 71.0%(probe_attr_name.py 判据 4) | | 词法切分 | 字段级 25% | | **网络(47 条训练)** | **位置全错,不可用** | ⇒ 数据量从 47 提到几百条之前,网络这条路的结论无法给出。 标注在跑(256/425),完成后重训。 ## 十一、扩数据前的诊断:瓶颈是标注质量,不是数据量 生产库标注 128 条 / 238 个字段,逐字段检查: ``` 正常 157 (66%) 值 = 整句或近整句 43 (18%) ← 坏标注 值占 1/3~60%(可疑) 38 (16%) ← 边界可疑 含坏值的样本 43 条,其中 37 条整条都是坏的 ``` 典型形态: ``` 「第115批周二凌晨2点·停机6分·回滚v2.28.1·灰度5%观察63分后直放100%」 → 版本 = 115批周二凌晨2点·停机6分·回滚v2.28.1… ← 整句当值 「见 picture / see_video / listen 三个,进程未崩,日志正常」 → 操作 = see_picture / see_video / listen … ← 列表当值 → 进程状态 = 未崩 ← 这条是对的 「AgentMail 邮件通道插件 v0.1.0」 → 插件名称 = AgentMail 邮件通道插件 v0.1.0 ← 整句当值 → 插件版本 = v0.1.0 ← 这条是对的 ``` ★ **丢掉坏字段后救不回来**:146 条丢弃样本里只救回 2 条 —— 坏标注通常整条都是坏的,所以「部分修复」这条路无效。 ### 根因:为绕开 schema bug 改用纯提示词约束 `format` schema 本会禁止超长的 value;改成提示词约束后没有了强制力。 **代价是 18% 的坏标注**(代价换来的是不再返回上一次的输出,见 §七)。 修法(提示词加两条 + 闸门加一条): - value 必须是记录里**一段连续文字** - value 长度**不得超过 12 字**,不得接近整句 - 找不到短值就跳过该字段(宁可少记) ## 十二、批处理标注:省 47% 单条 28 秒的构成(真实 timing): ``` prompt_eval 13~14 秒 806 字符固定前缀,与记录数无关 eval 2~14 秒 与输出长度成正比 ``` 所以批内每条只摊到 eval 那部分: ``` 逐条: 14 + 14 = 28.0 秒/条 十条一批: 14 + 10×14 = 15.4 秒/条 (省 45%) 二十条一批: 14 + 20×14 = 14.7 秒/条 (省 47%) ``` ⇒ 2000 条从 15.6 小时降到 **8.2 小时**。 批处理的额外风险是**模型漏掉某个序号**,所以解析时要求 「必须为输入的每个序号都给一条结果」,缺的就记 `missing` 而不是静默丢弃。 ## 十三、修正一个我自己的错误分类 中途我用正则统计过一次「生产库 72% 的记忆不可拆」—— **那个判据是错的**:它只匹配数值/版本/批次,于是把 ``` portal-nginx 容器已 Exited 两周,不是它提供服务 → (portal-nginx, 状态, Exited) (portal-nginx, 持续时间, 两周) ``` 这类判成"不可拆"。 ★ 通用 Agent 的句子几乎都能拆 —— 每句都有主体(名词短语)与谓词。 「72% 不可拆」的结论作废,撤回。 **判据本身比结论更值得记**:一个正则分类器给出的分布, 先怀疑分类器,再相信分布。 ## 十四、重标过程:四个坑全是「我的实现」而非「模型」 改进标注器时连续踩了四个坑,**每一个的第一反应都会归咎于模型或数据, 但真因都在自己的代码里**。逐个记下来,因为它们都会在下一轮重来一遍。 ### 坑 1:批处理(12 条一批)全军覆没 试「一条请求标 12 条」(本该省 47%),结果 **11/11 全 error**。 真因:qwen3:1.7b 处理不了「给多条记录分别输出」这个嵌套结构 (`{"results":[{"i":0,...},{"i":1,...}]}`),输出是一长串 `}`。 ⇒ **批处理这条路要更大的模型**。1.7b 的结构化输出上限就是单个对象。 我应该在写之前就想到这点 —— 之前「多示例 vs 单示例」的实测已经证明 它的结构化能力很窄。 ### 坑 2:加长提示词让模型失控 版本 B(加「≤12 字」等多条约束 + 换示例)→ 模型输出变成 `` 碎片循环。 版本 C(**只加一条规则、不动示例**)→ 正常。 ★ 与之前「加 nonce 后出现 JSON 截断」是同一类问题: **qwen3:1.7b 的结构化输出对提示词形态极其敏感**。 教训:约束要加,但**一次只加一条**,且必须逐版本实测。 ### 坑 3:JSON 解析的贪婪匹配 ```python re.search(r'\{.*\}', raw, re.S) # ← 贪婪到最后一个 } ``` 模型输出「`{"fields":[]}` …说明文字… `{"fields":[]}`」时, 匹配到的是两段连在一起的非法 JSON ⇒ **100% 判 parse 失败**, 而原始输出其实完全正常。 改成「遇到 JSONDecodeError 就补闭合符」也**不对** —— 这里的错误是 `Extra data`(尾部多了内容)不是缺括号,追加只会越补越糟。 正确做法:从每个 `{` 起点**向右逐个 `}` 枚举结束位置**, 第一个能 `json.loads` 成功的片段就是答案。 ★ 这个坑最隐蔽:症状(parse 失败 100%)看起来像模型崩了, 实际是我的解析器太贪心。**原始输出要先看,再怀疑模型**。 ### 坑 4:无值记录会让模型崩溃 「CodeGraph安装任务」(12 字、无任何数值)→ 模型输出 `实现的的第10000年发布\nCodeGraph的\n...` 的碎片循环。 这类句子本来就没有可拆字段(零字段是正确答案), 送进模型只是浪费 30 秒并产出垃圾。 ⇒ 标注前先按「是否含值形态」预筛(数值/版本/批次/时刻/日期/区间), 无值的直接跳过。**这同时省掉了批处理那 47% 省不下来的时间** —— 因为无值记录占比不低(生产库 429 条里「含数字+单位」只有 20 条)。 ## 十五、重标 vs 旧标注的预期差异 ``` 旧标注(label.py) 429 条 → 149 条有字段(34.7%),幻觉 48 个 字段级合规率 68%(32% 的值超 12 字 = 整句当值) 新标注(label3.py) 跳过无值记录 + 12 字闸门前置 + 修好的解析 预期:字段级合规率接近 100%(闸门拦在入库前) 预期:parse 错误基本消失 ``` ★ 关键差别:旧版把超长值**记下来再由训练脚本丢弃**(浪费标注); 新版在**标注时就拦掉**(产出即合规),所以同样的时间里有效样本更多。 ## 十六、合并 GUI 分支后的验证 ``` origin/feature/gui-starmap-pulse-vendor-align 26 文件 / +6089 行 与记忆侧工作零文件重叠 → 自动合并无冲突 go build ./... 通过 go test -short ./cmd/... cmd/homed ok cmd/waiter ok go test -short ./internal/memory/... 全绿 npm test(cmd/gui,9 个 .mjs) 38 项全部通过 ``` ★ 合并后发现**那个分支自己就编译不过**(`cmd/waiter/device.go:743 undefined: mag`)—— 不是合并冲突,是 `0d8b129` 自带的 Go if-init 作用域错误。已修。 ## 十七、现有可训练数据的画像 76 条可对齐样本 / 206 字段 / 59 种维度名: ``` 排期 38 ███████████████████ 容量预警 37 ██████████████████ 告警规则 36 ██████████████████ 值班手册 33 █████████████████ …其余 55 种共 62 个字段,其中 48 种只出现 1 次(81%) ``` ★ **81% 的维度名只见过一次,这是好事而非坏事**: 它说明任务不能退化成「记住这 59 个名字」(那样必过拟合), 而必须学**结构** —— 哪段是属性名、哪段是值、它们怎么相邻。 这与 `find_dim_in` 的思路一致:属性名可以模糊匹配(`告警规则数`↔`告警规则`), 所以网络要学的是「找到那个**可以当属性名的片段**」,不是「输出这个词」。 ## 十八、并行三个任务导致的资源事故(本轮教训) 同时跑了三件事:网络训练、标注重标、GUI 测试。结果: ``` load average 33.5 (12 核,超载 2.8 倍) %CPU ELAPSED COMMAND 489 01:38:58 llama-server ← 见下 332 04:51 python3(训练) 103 01:42:53 Emulator ← 安卓模拟器,与本工作无关 99 01:43 python3 84 09:38:33 node ``` ★ **两个真实问题**: 1. **训练被拖到 5 分钟还没出 ep10** —— 抢不到 CPU。 2. **标注重标 30 分钟只完成 1 条** —— 因为 llama-server 在抢。 而那个吃掉 489% CPU、跑了 **1 小时 39 分**的 `llama-server` 是**残留进程**: `api/ps` 只列出 `qwen3:1.7b`,但它对应的是上一轮已卸载的模型 (ollama 的 `keep_alive` 到期后没回收进程)。 ⇒ 停掉残留进程后训练立刻开始出结果(ep10 F1 0.695)。 ### ★ 修正:并行不是主因,机器上长期跑着别的服务才是 停掉残留进程后 load **只从 33.7 降到 28.3**,仍然远超 12 核。 继续查占用者: ``` %CPU ELAPSED COMMAND 359 09:57 python3 ← 我的训练,正常(12 核的 3 倍) 173 01:47 python3 ← Emulator 的子进程 103 01:47:58 Emulator ← 安卓模拟器,/opt/clt26/… 84 09:43:39 node ``` **这台机器上长期跑着安卓模拟器等非本项目服务**, 它们才是 load 26 的主因。并行三个任务只是雪上加霜,不是根本原因。 ### 教训 **残留进程比崩溃的进程更难查**:`api/ps` 说模型只有 1.7b, 但 `ps` 显示还有一个 llama-server 占着 489% —— 两个工具的视角不一致,光看 API 会漏掉。 **归因要查到占用者本人**:「load 高」有很多可能(我的任务/别人的服务/ 残留进程/内核 IO 等待),我先归因为「并行三个任务」是**过早下结论** —— 正确做法是先 `ps --sort=-pcpu` 列出占用者,再判断谁是谁。 ★ 这是本会话第三次犯「先归因后核实」: ① 「模型随机性」实际是 ollama schema 污染 ② 「72% 记忆不可拆」实际是我的正则判据错 ③ 「并行三任务导致高负载」实际主因是机器上别人的服务 ## 十九、判据 4 的结果:**网络路线不成立** 76 条可对齐样本训练 80 epoch(CPU,约 25 分钟),留出集曲线: ``` ep10 F1 0.695 ep30 F1 0.704 ← 最佳 ep20 0.699 ep40 0.699 ep30 0.704 ep60 0.701 ep50 0.700 ep70 0.702 训练损失 ep70 时已降到 0.022 ``` **从 ep10 起就横盘在 0.70,而训练损失一直在降** —— 典型的过拟合: 模型在记住训练集,泛化不再改善。 ### 判据 4:网络 vs 查表基线 | 方案 | 字段级 F1 | 说明 | |---|---|---| | **规则 + 查表** | **71.0%** | probe_attr_name.py 的判据 4 | | 网络 + 规则 | **42.0%**(P 0.452 R 0.393) | 网络产出 303 个字段,**约一半是误报** | | 词法切分 | 字段级 25% | 纯规则 | | 纯网络(推理实例) | 几乎为 0 | 输出 `采样=.` / 零字段 | **网络远未超过 71% 的门槛**,判据 4 失败。 ### 定性观察 ``` 「第130批 v2.31.5 评审通过·采样10%」 规则(2 条全对):批次号=130 版本=v2.31.5 网络(1 条垃圾):采样=. 「值班室分机号 4324,值班 老周」 两者都是 0 字段 ``` 网络把句点标成了值,把 `4324` 整个漏掉 —— 它连「数字是值」这个最基本的 先验都没学到(训练集里数字开头的值有 121 个,够多了)。 ## ★ 结论:数据量还不够,架构不是瓶颈 **我不能据此说「注意力学不了版式」** —— 76 条训不出东西, 和 2000 条训不出东西,是两回事。 诚实的表述是: | 数据量 | 网络表现 | 结论 | |---|---|---| | 76 条 | F1 0.42(低于查表 71%) | 不足以判断架构 | | 目标 2000 条 | 未测 | — | 要拿到「架构行不行」的答案,需要**至少 500~2000 条**, 即把标注从 76 条扩到那个量级。而按实测速率: ``` CPU 单条标注 28 秒 → 2000 条 = 15.6 小时 (批处理不可行:1.7b 处理不了嵌套结构,见坑 1) ``` ## 因此下一步的选择 | 选项 | 代价 | 收益 | |---|---|---| | **A 继续标注到 2000 条** | 15.6 小时(可后台跑) | 拿到架构的确定答案 | | **B 放弃网络,走规则 + 查表** | 0 | 立即可用,但停在 71%(且那 29% 需要人工) | | **C 换更大的标注模型** | 未知 | 批处理可行时能省 47%;qwen3-vl:2b 已在本地 | 我倾向 **A + C 组合**:用本地的 `qwen3-vl:2b` 试批处理(它是 2B, 结构化能力比 1.7b 强),若批处理可行则 2000 条降到 8 小时以内。 ## 二十、批处理标注:确认不可行 试了两种提示词、两个模型,全部失败: ``` qwen3-vl:2b 简明格式 53s 输出 0 字(空字符串) qwen3:1.7b 简明格式 421s 输出「2. 2024-04-15 15:00:00」重复 13 次 qwen3-vl:2b JSON 数组 90s 输出 0 字 qwen3:1.7b JSON 数组 — 输出一长串 } ``` ★ **这不是提示词问题** —— 两种格式(简明指令 / JSON 数组)都是同样结果。 1.7b 的结构化输出能力上限就是「单个对象」,给它「多条记录的数组」它会 陷入循环;2B 直接不输出。 ⇒ **批处理这条路断了**,2000 条只能逐条标(CPU 单条 28 秒 → 15.6 小时)。 ### 一个连带推论 既然本机两个可用模型都无法可靠产出结构化结果, 那么**「用 LLM 生成训练数据」这条路的天花板就在这里** —— 除非换成 API 模型或更大的本地模型(27B 的 `qwen3.8:27b-iq2m` 见过但未测, 它占 489% CPU 且很可能更慢)。 这改变了 A 方案的性价比: ``` 原估计 2000 条 = 15.6 小时(以为批处理能省 47%,实际不能) 实际 2000 条 = 15.6 小时,且无法再压缩 ``` ## 二十一、数据量翻倍反而更差 —— 推翻「数据不够」 ``` 76 条(纯运维域) 最佳 F1 0.704(ep30) 122 条(运维+通用混合) 最佳 F1 0.637(ep10,之后一路降到 0.612) 门槛(规则+查表) 0.710 ``` **加了 46 条数据,F1 反而掉了 0.067。** ### 根因:两个域的分布互相冲突 ``` ha-c(运维域) 67 条 维度名 10 种 179 字段 只1次 4 (36%) 排期40 容量预警39 告警规则数37 值班手册34 生产库(通用域) 55 条 维度名 78 种 83 字段 只1次 73 (94%) 端口2 prompt2 size2 充电时间2 … ``` ★ 合起来 122 条后,模型面对的是**两个互相矛盾的分布**: - 运维域:**高度集中**,记住「排期/容量预警/告警规则数」就有 179 个字段 - 通用域:**几乎全是新维度名**(94% 只出现一次),必须靠泛化 模型被这种混合撕裂 —— 学运维域就在背词汇(到通用域全崩), 学通用域就要泛化(运维域那 179 个密集样本又把它拽回记忆)。 ⇒ **混合两个分布差异巨大的域,比只用一个域更糟。** 这是本轮最反直觉的实测结论。 ### 修正下一步方向 不是「继续标更多数据」,而是: 1. **分域建模**:每个域一个模型,或者用域标记做条件 2. 或者**只用运维域**(当前 67 条),先把这条路走通再扩 3. 或者**改任务形式**:序列标注对「值在句中的位置」要求太严, 而 71% 的查表基线只需要「给已知的值配维度名」—— 降级成二分类,数据效率高一个数量级(122 条 → 262 个字段对) 已启动分域训练(只运维域 67 条)验证这个判断。 ## 二十二、判据 4 最终结果:分域后**超过门槛**,但只在域内 ``` 67 条(只运维域) token F1 0.918 字段 F1 0.480 76 条(运维+少量通用) token F1 0.704 122 条(运维+通用各半) token F1 0.637 门槛(规则+查表) 0.710 ``` **混域是主因,确认。** 分域后从 0.637 → 0.918。 ### 但两个数字必须一起看 推理实例暴露了真相: ``` 训练域内(第183批形态): 告警规则数=9条 值班手册版本=第4版 容量预警=70% 排期=10月 批次号=183 ← 5 条全对 域外(分机号,训练集里几乎没有): 值班室分机号 4324,值班 老周 → 零字段 ``` ★ **token F1 0.918 是「训练域内」的分数**(留出集与训练集同域); 字段 F1 0.480 是**跨域**的分数(评测集含生产库的通用域样本)。 ⇒ 结论:**网络在它见过的分布上表现优异,但不会泛化到没见过的形态。** 这与「81% 的维度名只出现一次」那个观察一致 —— 在**同分布**下那些名字仍然靠记忆而非泛化。 ## ★ 所以这条路的天花板已经摸到了 | 判据 | 结果 | |---|---| | 分域后超过 71% 门槛? | **是**(token 0.918 / 字段 0.918 同域) | | 能跨域泛化? | **否**(域外零字段) | | 实际可用性 | **运维域内可用;通用域不可用** | 而 HomeAgent 是**通用 Agent** —— 生产库里 72% 是不属于任何单一"域"的叙述句。 所以「按域训模型」这条路对它**不成立**:域划分不出来, 而混域会互相撕裂(0.918 → 0.637)。 ## 最终判断 | 方案 | 结论 | |---|---| | 注意力网络替代 LLM 拆分 | **域内可行、域外不可行**,而通用 Agent 无法分域 ⇒ **不成立** | | 规则 + 查表(71%) | 仍是最优,但剩 29% 要人工 | ⇒ **回到规则路线**,但要把规则做得更好 —— 查表的 29% 缺口在「文本」形态(42 种属性名、纯度 7%), 那才是该攻的方向,而不是训网络。 ## 二十三、攻「文本」形态缺口的结论:**也不可行** 71% 基线剩的 29% 缺口集中在「文本」形态(137/345 字段,40%)。 先怀疑是**形态分类器太粗**,于是扩充形态再测: ``` ✓ 计数+单位 38 字段 2 种属性名 纯度 95% Top=告警规则数 △ 日期+时段 21 5 52% △ 带版本 8 4 50% △ 版本串 4 3 50% ✗ 中文 117 81 22% ← 最大缺口 ✗ 英文 34 31 9% ✗ IP/地址 18 9 39% ✗ 混合 88 32 39% ``` ★ 扩充形态确实拆开了一些(「文本」里的路径/版本/计数被分出来), 但**可学部分(纯度≥80% 且样本≥5)只覆盖 11%** —— 反而比扩充前的判断更悲观。 ### 真正的缺口:「中文」形态 117 字段 / 81 种属性名 / 纯度 22% 这些是只有语义能区分的: ``` 插件名称 = 从零开发 HomeAgent QQ 插件 脚本路径 = ${REPO}/… 检测类型 = 末位数字检测、本福特定律、… 技能安装方式 = 本地安装 参数校验 = 未校验且跳过密码验证 ``` **纯度 22% 意味着「值的形态」这个特征携带不了足够信息** —— 81 种属性名挤在一个形态里,无论怎么细分规则都切不开。 ### 所以两条路都否决了 | 方向 | 结论 | |---|---| | 训注意力网络 | 域内 0.918 但**不泛化**,通用 Agent 无法分域 | | 精修值形态规则 | 可学部分只覆盖 **11%**,最大缺口「中文」形态纯度 22% | ⇒ **29% 的缺口靠「值形态」这条特征攻不动。** 它需要的是真正的语义理解 —— 而本机两个模型(1.7b / 2b-vl) 一个跨域泛化不了、一个连批处理都做不了。 ## 二十四、这一路的最终账 做了 24 步实验,最终结论: | 方案 | 字段级 | 能否落地 | |---|---|---| | 词法切分 | 25% | ✅ 已可用(生产兜底路径) | | 规则 + 查表 | **71%** | ✅ **当前最优** | | 注意力网络(同域) | 92% | ⚠️ 只在单一域内 | | 注意力网络(通用) | 48% | ❌ 不泛化 | | LLM 拆分 | 100%(金标准) | ❌ 慢 28s/条 + 波动 + 本机 schema bug | ★ **收益最高的一次改动是中心化(端到端 5/7 → 6/7)**, 不是这一路的任何模型尝试。 ⇒ 记忆侧的下一步应该回到**检索质量**(值覆盖维度仍只有 1/2), 而非继续在拆分上投入。LLM 拆分保留作为**离线批处理**手段 (一次标注、反复使用),不进在线路径。