SVO语义检索的系统化方案
SVO 3.2 语义检索的系统化方案
零、一句话概述
把 SVO 3.2 表达式分解成"概念簇 / 动作命题 / 逻辑命题"三类语义原子——所有作用域关系通过 : 绑定或 PropRef 引用表达——对每类原子建最合适的索引,检索时多路召回 + 逻辑过滤 + 结构哈希匹配 + 学习排序,最后沿引用链聚合成完整语义单元返回。
核心设计哲学:语义是连续的,交给向量;结构是离散的,交给索引。两者解耦,各尽其职。
SVO 3.2 取消了 >> 算子,把原本混在"情境"里的两类东西——非命题性作用域(话题/身份/范围/句子级副词)与命题性推导(条件/让步/因果/推论)——彻底分开。前者归入 :(属性),后者归入 >(力)。检索系统必须同步这个切分,否则会在相同的"情境"标签下混入本质不同的两类语义,引入噪声。
一、第一性原理:定义"语义相关"
脱离对"相关"的精确定义去谈检索,必然做出一锅粥式的系统。SVO 检索服务的相关性是分层的,不同层级需要不同的索引手段。
| 层级 | 名称 | 含义 | 示例 | 索引手段 |
|---|---|---|---|---|
| L1 | 概念相关 | 词项语义相似 | 查"汽车"召回"轿车""SUV" | 概念向量 |
| L2 | 命题相关 | 主谓宾整体相似,角色对齐 | 查"K 表示 AGI 还远"召回同义改写 | 命题向量 + 倒排 |
| L3 | 作用域相关 | 话题/身份/范围属性匹配 | 查"低资源场景下的模型压缩" | 作用域属性索引 |
| L4 | 推导相关 | 条件/让步/因果链的前件或后件匹配 | 查"如果模型开源会怎样"命中条件链的前件 | 逻辑命题索引 |
| L5 | 蕴含相关 | 逻辑等价与否定/量词/模态的正确区分 | "所有学生通过" ≡ "没有学生没通过" | 逻辑过滤规则 |
| L6 | 结构类比 | 骨架相同、实体不同 | "A 促使 B 认识到 C" 类比检索 | 结构指纹哈希 |
关键洞察:SVO 3.2 的算子精简(三算子二元本体)天然对应这六层分解——: 负责 L1/L3,> 负责 L2/L4/L6,逻辑过滤器覆盖 L5。每一层都有明确的算子责任人。
二、语义原子化:入库时的分解
一条 SVO 3.2 表达式入库时,不作为整体被索引,而是分解成三类独立可检索的原子,通过 ID 保留原始关系。
2.1 三类原子(3.2 版)
① 概念簇 (Concept)
由 : 链坍缩出的复合单位,代表实体、概念或作用域属性。
- 实体型:
(OpenAI:创始:元老):Karpathy - 概念型:
(协作式:中间态) - 作用域型:
(AI:辅助:编程:方面)、(前:负责人) - 句子级修饰型:
显然、(不幸:的是)
② 动作命题 (Action Proposition)
施事 > 动词 > 受事 三元组。> 的两端都是词项或概念簇(而非完整命题)。
- 示例:
Karpathy > 表示 > [PropRef]、论文 > 促使 > Tishby
③ 逻辑命题 (Logical Proposition)
(前件) > 逻辑连词 > (后件) 结构。> 的两端都是封装的完整命题。连词为 则 / 尽管 / 导致 / 因此 / 促使 / 以便 等有限集合。
- 示例:
(该计划 > 不:公开) > 则 > (该计划 > 无法:获得 > 认可)
2.2 原子间的三种引用关系(PropRef 机制扩展)
3.2 中 PropRef 不再只表达"言说嵌套",而是统一表达三种命题级引用。每种引用类型必须独立记录,以便反向遍历。
| 引用类型 | 源字段 | 指向 | 典型 SVO 形式 | 检索用途 |
|---|---|---|---|---|
| **言说引用**(`ref:utterance`) | 动作命题的宾语槽 | 另一个命题 | `K > 表示 > (P)` | 区分"事实"和"对事实的陈述" |
| **修饰引用**(`ref:modifier`) | 作用域概念的修饰对象 | 一个命题 | `显然 : (P)`、`(AI:方面) : (P)` | 区分"在 X 下成立的 Q"与"关于 X 的 Q" |
| **推导引用** (`ref:logical`) | 逻辑命题的前件/后件槽 | 两个命题 | `(P1) > 则 > (P2)` | 条件/让步/因果检索 |
2.3 分解示例(3.2 版)
原始 3.2 表达式:
(前:负责人) : Karpathy > ((今天 & 明确 & (向:团队)) : 表示) > ((该计划 > 不:公开) > 则 > (该计划 > (无法:获得) > 认可))
分解结果:
概念簇:
C1 = (前:负责人) role=scope
C2 = Karpathy role=entity
C3 = 今天 role=scope (时间状语)
C4 = 明确 role=scope (方式状语)
C5 = (向:团队) role=scope (对象状语)
C6 = 该计划 role=entity
C7 = 认可 role=concept
动作命题:
P1: [C2] > 表示 > [PropRef→L1]
谓词修饰: [C3, C4, C5] (通过 ref:modifier 指向 P1)
实体修饰: C1 修饰 C2 (通过 ref:modifier,绑定到 P1 的 subject 槽)
P2: C6 > 不:公开
(这里的"不"作为极性标记记在 P2 的 polarity 字段)
P3: C6 > 无法:获得 > C7
(这里的"无法"作为模态标记记在 P3 的 modality 字段)
逻辑命题:
L1: [P2] > 则 > [P3]
connector = 则
(P2、P3 通过 ref:logical 被 L1 引用)
根节点:
P1 是表达式的入口,整句被 P1 索引
三、四层索引架构
3.1 索引 A:概念向量索引(服务 L1)
作用:存储所有概念簇的向量,支持"找语义相近的概念"。
解法:概念簇入库时按 role 字段(entity / concept / scope / sentence_mod)路由到不同的子索引。查询时按查询词类型选择召回的子索引集合:
- 查询实体 → 召回 entity + concept 子索引
- 查询场景 → 召回 scope 子索引
- 查询谓词修饰语 → 召回 sentence_mod 子索引
编码策略(分两阶段):
阶段一:加性组合(零训练)
v(concept) = v(核心词) + Σ α_i · v(修饰词_i)
其中 α_i 按绑定深度衰减(如 0.8^depth)。
阶段二:Transformer + 绑定深度位置编码
- 线性化为
[CLS] 修饰1 : 修饰2 : ... : 核心词 [SEP] - 绑定深度作为位置编码,让模型区分层级
- CLS 池化输出向量
- 按 role 字段训练四个 LoRA 适配器,不强行统一编码空间
训练数据(四类对比对):
- 同指正例:同实体不同描述(Wikidata 对齐)
- 属性敏感正例:同实体不同属性强调点
- 混淆负例:同属性不同核心词(强迫模型关注核心词)
- 属性翻转负例:
(前:CEO):Xvs(现任:CEO):X - (3.2 新增)role 混淆负例:
(AI:方面)vsAI(强迫模型识别"方面"这个尾缀改变了 role)
3.2 索引 B:动作命题索引(服务 L2、L6)
动作命题是检索的核心单位,同时建三套互补索引:
B1. 倒排索引(精确角色查询)
- 按施事、动词、受事分别建倒排表
- 支持 SPARQL 风格硬过滤:"找所有施事=Karpathy 的动作命题"
- 引擎:Elasticsearch
B2. 结构指纹哈希(L6 结构类比)
每个动作命题生成结构指纹,包含:
- 槽位数与槽位类型序列
- 极性字段(是否含
不) - 模态字段(是否含
可能/必须) - 量词字段(
所有/某/没有/无) - 嵌套深度(宾语是否为 PropRef)
B3. 槽位向量索引(L2 语义模糊匹配)
- 每个槽位填入对应概念簇的向量(复用索引 A)
- 按槽位位置拼接或加位置编码后聚合,绝对不能简单平均
- 极性、模态、量词、连接词(对逻辑命题)作为额外特征位
3.3 索引 C:逻辑命题索引(服务 L4)
数据结构:每条逻辑命题记录包含
antecedent_ref→ 前件命题 IDconnector→ 连词原子词(则/尽管/导致/因此/促使/以便)consequent_ref→ 后件命题 IDantecedent_vec/consequent_vec→ 两端的复用向量
支持的查询模式:
- 前件查询:"如果模型开源会怎样?" → 找所有前件 ≈ "模型开源" 的逻辑命题,返回其后件
- 后件查询:"什么情况下项目会失败?" → 找所有后件 ≈ "项目失败" 的逻辑命题,返回其前件
- 连词过滤:"尽管…但是…" 型逻辑 vs "如果…就…" 型逻辑,通过 connector 字段硬过滤
- 因果链追踪:沿
antecedent_ref链式遍历,找出"P → Q → R"的推导链
工程实现:Postgres 关系表 + Qdrant 两份前件/后件向量索引。查询时按 connector 过滤后再做向量召回。
3.4 索引 D:作用域修饰索引(服务 L3)
作用:快速回答"在某个作用域(话题/身份/范围)下的所有命题"。
数据结构:一张多对多关系表
proposition_id→ 被修饰的命题scope_concept_id→ 作为作用域的概念簇 IDmodifier_type→topic/identity/sentence_adverb/scope_range
为什么独立于 PropRef 表:虽然 ref:modifier 已经在 PropRef 表里,但作用域修饰查询频率极高,且经常需要按 modifier_type 过滤。为查询效率,单独物化成一张表(其实是 PropRef 表的视图索引)。
查询流程:
- 查询词("AI 领域") → 索引 A 召回相关的 scope 概念
- 拿概念 ID → 查询索引 D,反查所有被这些概念修饰的命题
- 返回命题列表,进入精排
这样分工后,索引 D 的查询结果更纯净——返回的都是"在此作用域下成立的命题",而不会混入"以此为条件推出的命题"。
四、完整检索流程(3.2 版)
用户查询 (自然语言或 SVO 3.2 表达式)
│
▼
┌── 步骤 1: 查询解析 ──┐
│ 解析成 SVO 3.2 树 │
│ 拆成 概念/动作命题/ │
│ 逻辑命题 │
│ 标记必须项 vs 软需求 │
└──────────┬───────────┘
│
┌─────────┬─────┴─────┬──────────┐
▼ ▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
│索引 A │ │索引 B │ │索引 C │ │索引 D │
│概念 │ │动作 │ │逻辑 │ │作用域 │
│向量 │ │命题 │ │命题 │ │修饰 │
└───┬───┘ └───┬───┘ └───┬───┘ └───┬───┘
│ │ │ │
└────┬────┴──────────┴──────────┘
▼
┌─ 步骤 2: 多路召回并集 ──┐
│ 候选命题池 (百~千条) │
└──────────┬───────────────┘
▼
┌─ 步骤 3: 逻辑过滤 ─────┐
│ • 极性(肯定/否定) │
│ • 量词(全称/存在) │
│ • 模态(必须/可能) │
│ • 作用域覆盖 │
│ • 连词一致性 (3.2 新) │
└──────────┬─────────────┘
▼
┌─ 步骤 4: 命题级精排 ───┐
│ 学习排序融合六维特征: │
│ • 概念相似度 │
│ • 动作命题相似度 │
│ • 逻辑命题相似度 │
│ • 作用域覆盖度 │
│ • 倒排命中数 │
│ • 逻辑约束满足度 │
└──────────┬─────────────┘
▼
┌─ 步骤 5: 引用展开与回溯 ┐
│ 沿 ref:utterance 展开 │
│ 沿 ref:modifier 补全作用域│
│ 沿 ref:logical 展开推导链 │
│ 同源表达式聚合加分 │
└──────────┬───────────────┘
▼
最终结果
步骤 3 的杀手锏例子:
- 查询
(模型 > 开源) > 则 > (?),候选(模型 > 开源) > 尽管 > (性能 > 下降)→ connector 不匹配,过滤 - 查询
所有:学生 > 通过 > 考试,候选某:学生 > 通过 > 考试→ 量词不同,降权 - 查询
现有:框架 > 适用,候选现有:框架 > 不:适用→ 极性翻转,过滤 - 查询
(AI:方面) : (K > 偏向 > X),候选(生物:方面) : (K > 偏向 > X)→ 作用域不匹配,过滤
这几类过滤在纯向量方案里是致命盲区,在 3.2 的结构化方案里是几十行规则代码。
五、核心技术挑战与解法
5.1 SVO 3.2 解析器的稳定性(最关键风险点)
3.2 把消歧责任从算子级推到了操作数形态级。
行动指引:
- 建立解析稳定性基准测试。准备 500 句带歧义的自然语言,每句 10 个人工改写,跑解析器。统计同语义句的 SVO 3.2 树一致率(以结构指纹为准)。
- 一致率门槛:低于 85% 不得投入检索系统建设。
- 优先稳定"作用域 vs 条件"的识别——这是 3.2 解析器最容易出错的地方。中文里"在 X 的情况下"既可能是话题(
(X) : (...))也可能是条件((X) > 则 > (...)),必须用上下文特征分类,不能靠正则。
5.2 编码器必须"懂算子 + 懂连词"
要求编码器对 > 的两种承载(动作流 vs 逻辑流)通过两端操作数形态自动区分,且对连词词项(则/尽管/导致)敏感——这是更细的要求。
| 对比类型 | 正例 / 负例 | 期望距离 |
|---|---|---|
| 角色翻转 | `A > 杀 > B` vs `B > 杀 > A` | 远 |
| 同义动作 | `A > 喜欢 > B` vs `A > 爱 > B` | 近 |
| 极性翻转 | `不:适用` vs `适用` | 远 |
| 量词差异 | `所有:学生 > 通过` vs `某:学生 > 通过` | 中 |
| 作用域翻转 | `(战时):(军队>训练)` vs `(和平):(军队>训练)` | 远 |
| **连词翻转** | `(P) > 则 > (Q)` vs `(P) > 尽管 > (Q)` | 远 |
| **逻辑流 vs 动作流** | `(P) > 导致 > (Q)` vs `P > 导致 > Q`(误解析) | 远 |
连词翻转这一类尤为关键。如果编码器分不清"则"和"尽管",索引 C 的精排就是瞎排。
5.3 双重本体在向量空间中的投影
3.2 的二元本体(属性 / 力)在向量空间里建议显式投影到两个子空间:
- 属性子空间:编码概念簇、作用域修饰
- 力子空间:编码动作命题、逻辑命题
同一个 BGE-base 主干 + 两个投影头(projection head)。训练时在两个子空间分别做对比学习。查询时按查询的本体类型路由到对应子空间。
六、数据模型(3.2 核心表结构)
-- 概念簇表
CREATE TABLE concept (
id BIGSERIAL PRIMARY KEY,
canonical TEXT NOT NULL, -- SVO 线性形式
core_word TEXT NOT NULL, -- 核心词
modifiers TEXT[] NOT NULL, -- 修饰链
role VARCHAR(16) NOT NULL, -- entity|concept|scope|sentence_mod
vector VECTOR(768) -- 复用 A 索引向量
);
-- 动作命题表
CREATE TABLE action_proposition (
id BIGSERIAL PRIMARY KEY,
subject_id BIGINT REFERENCES concept(id),
verb TEXT NOT NULL,
object_id BIGINT REFERENCES concept(id), -- NULL 表示宾语是 PropRef
object_ref BIGINT, -- PropRef:指向另一命题
polarity SMALLINT NOT NULL DEFAULT 1, -- 1=肯定, -1=否定
modality VARCHAR(16), -- 必须|可能|NULL
quantifier VARCHAR(16), -- 所有|某|没有|NULL
fingerprint BIGINT NOT NULL, -- 结构指纹哈希
vector VECTOR(768)
);
-- 逻辑命题表(3.2 新增)
CREATE TABLE logical_proposition (
id BIGSERIAL PRIMARY KEY,
antecedent_id BIGINT NOT NULL REFERENCES action_proposition(id),
connector VARCHAR(16) NOT NULL, -- 则|尽管|导致|因此|促使|以便
consequent_id BIGINT NOT NULL REFERENCES action_proposition(id),
antecedent_vec VECTOR(768),
consequent_vec VECTOR(768)
);
-- 引用关系表(3.2 重构:拆分 ref_type)
CREATE TABLE prop_ref (
id BIGSERIAL PRIMARY KEY,
source_type VARCHAR(16) NOT NULL, -- action|logical|concept
source_id BIGINT NOT NULL,
target_type VARCHAR(16) NOT NULL, -- action|logical
target_id BIGINT NOT NULL,
ref_type VARCHAR(16) NOT NULL -- utterance|modifier|logical
);
CREATE INDEX idx_ref_target ON prop_ref(target_type, target_id, ref_type);
CREATE INDEX idx_ref_source ON prop_ref(source_type, source_id, ref_type);
关键设计点:
action_proposition和logical_proposition分表存储,避免字段冗余。精排时可以并表查询。prop_ref表同时服务三种引用类型,通过ref_type区分。反向查询效率靠idx_ref_target保证。polarity/modality/quantifier作为命题的头等字段,不混在向量里。这是逻辑过滤层能存在的前提——过滤只发生在离散字段上。
七、工程落地路径
7.1 基础设施选型
| 组件 | 选型 | 理由 |
|---|---|---|
| 倒排索引 (B1) | Elasticsearch | 角色字段查询零门槛 |
| 向量索引 (A/B3/C/D) | Qdrant 或 Milvus | 支持带 metadata 的过滤 |
| 结构哈希 (B2) | Redis | 哈希倒排 O(1) |
| 关系表(含 PropRef) | Postgres | 外键保一致,PropRef 反向查询用索引 |
| **逻辑命题表 (3.2 新)** | Postgres + pgvector | 关系+向量共存,避免跨库事务 |
| 编码器 | BGE-base + LoRA | 中文效果好,微调成本低 |
不需要图数据库的判断依然成立——PropRef 表配合两个方向的索引就够了。3.2 增加了 ref_type 维度,但这只是多加一列过滤,仍在关系数据库的舒适区内。
7.2 三阶段演进(3.2 版)
阶段一:最简可行版本(1-2 个月)
- 概念簇加性组合,零训练
- 动作命题用倒排 + 结构哈希
- 逻辑命题表建起来,但先不上向量,只做 connector 精确匹配
- 作用域修饰索引直接用 Postgres 多对多表
- 逻辑过滤用规则
- 目标:跑通全链路,收集 query → expected 数据
阶段二:神经增强(2-3 个月)
- 微调编码器(包含 3.2 新增的两类对比对)
- 槽位向量索引上线
- 逻辑命题的前件/后件向量索引上线
- 学习排序模型接入(六维特征)
- 目标:L3、L4 召回率显著提升
阶段三:精细优化(持续)
- 概念编码器按 role 分四个 LoRA
- 属性 / 力双子空间投影
- 结构指纹 lattice 扩展
- 逻辑过滤规则从 bad case 反向补充
7.3 评估指标(3.2 版)
- 角色准确率:施事/受事对齐
- 极性准确率:是否错召反义
- 量词一致率:量词强度匹配
- 作用域覆盖率:话题/身份命中(原情境覆盖率拆分项)
- 嵌套深度保持率:言说展开正确性
- 连词一致率(3.2 新):条件/让步/因果的连词是否正确匹配
- 逻辑链完整率(3.2 新):推导链被完整展开的比例
连词一致率是最能暴露 3.2 系统优劣的指标。
八、快速索引查找表
| 我想做 | 去哪个组件 |
|---|---|
| 找含特定实体的表达式 | 索引 A(role=entity) |
| 找含特定话题/场景的表达式 | 索引 A(role=scope) → 索引 D |
| 找含特定句子级态度词(如"显然") | 索引 A(role=sentence\_mod) → 索引 D |
| 找角色精确匹配的动作命题 | 索引 B1(倒排) |
| 找结构相同的类比动作命题 | 索引 B2(结构哈希) |
| 找语义模糊相似的动作命题 | 索引 B3(槽位向量) |
| **找满足"如果 X 则…"的推导** | **索引 C(前件向量)** |
| **找"…导致 X"的推导** | **索引 C(后件向量)** |
| **找特定连词的推导链** | **索引 C(connector 过滤)** |
| 找"在某作用域下"的命题 | 索引 D |
| 避免召回反义命题 | 步骤 3 逻辑过滤(polarity) |
| 避免召回不同连词的逻辑 | 步骤 3 逻辑过滤(connector) |
| 追溯"谁说了这句话" | PropRef 反查 ref:utterance |
| 展开"他说了什么" | PropRef 正查 ref:utterance |
| **追溯"这个结论的前提是什么"** | **PropRef 反查 ref:logical** |
| **展开"这个条件会推出什么"** | **PropRef 正查 ref:logical** |
No comments to display
No comments to display