mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-05 00:37:32 +00:00
只改仓库内文件的纯展示品牌名与已随批 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 逐项相同、
三条变异均被咬住且还原后复绿。
759 lines
31 KiB
Markdown
759 lines
31 KiB
Markdown
# 三元组抽取:注意力模型方案
|
||
|
||
> 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 解析的贪婪匹配
|
||
|
||
```python
|
||
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 拆分保留作为**离线批处理**手段
|
||
(一次标注、反复使用),不进在线路径。
|