Files
HomeAgent/docs/zh/triple-extract-attention.md
JianFeeeee 931c803421 refactor(docs): 核心仓文档与介绍站展示名改名为 HologramAgent
只改仓库内文件的纯展示品牌名与已随批 2 落地的安装路径;运行时契约、仓库 URL、
域名、环境变量、可执行名、插件名一律不动。

一、纯展示名(HomeAgent → HologramAgent,共 44 处 / 18 个文件)
- README.md 4、README_EN.md 4(标题、client 变体说明、Windows 安装包名、AGPL 段)
- docs/ 8 个文件 14 处(git-branching 1、ci-cd-runbook 1、commondev-design 3、
  commondev-agent-runtime-design 4、deploy-runbook 1、legacy-issues 1、
  prod-migration-checklist 1、triple-extract-attention 2)
- assets/docs/ 6 个文件 14 处(zh/en 的 OVERVIEW 2+2、ARCHITECTURE 2+2、
  PLUGIN_DEV 3+3)
- assets/branding/mascot-xiaozhai-guide.md 2 处(身份行、标志名)
- site/index.html 6 处(title、meta description、og:title、logo alt、页头字、
  插件卡片文案 "HologramAgent 接入 AgentMail")

二、安装路径对齐批 2 产物(共 10 处)
- /usr/lib/homeagent → /usr/lib/hologram(7 处:README 2、README_EN 2、
  multimodal-space 3)
- /usr/share/doc/homeagent → /usr/share/doc/hologramagent(3 处:README 1、
  README_EN 1、multimodal-space 1;注意 Doc 路径带 agent 后缀)
- site/index.html 的 19 处 HomeAgent 里,13 处是 github.com/JianFeeeee/HomeAgent
  仓库 URL,属红线整串保留;只改 URL 之外的展示文案(其余 13 处零改动)

三、明确不改(红线 / 一致性冲突)
- site/index.html 的 23 个 github.com/JianFeeeee/homeagentsdk 外链(21 条
  example + 2 条纯仓库),SDK 仓改名属批 4-b,一个未动
- 核心仓仓库 URL、sdk./introduce.homeagent.jianfgit.xyz 域名、HOMEAGENT_ 前缀、
  homed/waiter/hmapdev、.hmap、homeagent-mail-bridge 等插件名、third_party/、
  internal/ 与 cmd/ 全部零改动
- ~/.homeagent 路径本批保持原样:cmd/waiter/main.go:109、daemon.go:290 硬编码
  仍为 .homeagent,全仓无 .hologram 命中;照规格改文档会指向程序不用的目录,
  需与代码侧那条线一起改(遗留项,已单独上报)

判据:文档区展示名残留归零(44 文件)、红线计数与 base e3584bb 逐项相同、
三条变异均被咬住且还原后复绿。
2026-10-21 17:00:48 +08:00

31 KiB
Raw Blame History

三元组抽取:注意力模型方案

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 字」等多条约束 + 换示例)→ 模型输出变成 <record> 碎片循环。

版本 C(只加一条规则、不动示例)→ 正常。

★ 与之前「加 nonce 后出现 JSON 截断」是同一类问题: qwen3:1.7b 的结构化输出对提示词形态极其敏感。 教训:约束要加,但一次只加一条,且必须逐版本实测。

坑 3:JSON 解析的贪婪匹配

re.search(r'\{.*\}', raw, re.S)   # ← 贪婪到最后一个 }

模型输出「{"fields":[]} …说明文字… {"fields":[]}」时, 匹配到的是两段连在一起的非法 JSON ⇒ 100% 判 parse 失败, 而原始输出其实完全正常。

改成「遇到 JSONDecodeError 就补闭合符」也不对 —— 这里的错误是 Extra data(尾部多了内容)不是缺括号,追加只会越补越糟。

正确做法:从每个 { 起点向右逐个 } 枚举结束位置, 第一个能 json.loads 成功的片段就是答案。

★ 这个坑最隐蔽:症状(parse 失败 100%)看起来像模型崩了, 实际是我的解析器太贪心。原始输出要先看,再怀疑模型。

坑 4:无值记录会让模型崩溃

「CodeGraph安装任务」(12 字、无任何数值)→ 模型输出 实现的的第10000年发布\n</record>CodeGraph的\n</record>... 的碎片循环。

这类句子本来就没有可拆字段(零字段是正确答案), 送进模型只是浪费 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 同域)
能跨域泛化? 否(域外零字段)
实际可用性 运维域内可用;通用域不可用

而 HologramAgent 是通用 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%

这些是只有语义能区分的:

插件名称   = 从零开发 HologramAgent 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 拆分保留作为离线批处理手段 (一次标注、反复使用),不进在线路径。