权重与词频
清风输入法的候选排序由两个彼此独立的维度决定:
- 权重 (weight):候选词自带的静态排序分,来自词库或引擎语言模型,是默认排序维度。
- 词频 (frequency):你实际使用候选的统计(选了多少次、最近何时选的),是一套完全独立的排序维度。
核心设计:词频与权重彻底解耦。词频不加到 weight 上,而是作为独立维度在排序阶段与权重组合——排序时绝不修改任何候选的 weight。
权重 (weight)
权重是候选词自带的排序分,用于在没有其它信息时决定谁排前面。它的量级因来源不同而差异很大,并不是一个跨层统一的小区间:
| 来源 | 权重量级 |
|---|---|
| 拼音引擎(语言模型) | 单字 / 词可达百万级 |
| 拼音整句(Viterbi 合成的整句候选) | 30M 档(引擎内部锚定顶部用) |
| 码表候选 | 由码表内置权重或字根序决定 |
| 短语 / 用户词条 | 用户可设,默认 1000 |
因此,权重只在同一来源层内可比,跨层直接比数值意义不大——例如"把一条短语的 weight 设成 8000"并不能让它在拼音方案里压过一个常用拼音词(后者是百万级)。
词库权重归一化
方案可通过 [dictionaries.weight_spec] 把某个词库的原始权重按 median / max / min / target 映射到目标区间(mode 支持 linear / log),让不同来源的词库在同一方案里相对可比。这属于方案配置,普通用户一般不需要改动。
词频 (frequency)
词频只记录真实使用数据:{ count(累计使用次数), last_used(最近使用时间)},按 方案 + 编码 + 文本 存储在 userdata.redb 中。每次选词时 count + 1、刷新 last_used。
排序时,引擎把词频作为独立维度与权重组合,不改动 weight。码表方案与拼音方案采用不同的词频策略。
码表方案:基础排序 + 词频上浮
码表排序分两层:
- 基础排序(方案文件
[engine.codetable].base_sort):weight(按词库权重)或natural(按字根 / 自然序)。有些码表没有权重、只能自然排序,故基础序可选。详见方案配置制作。 - 用户词频(
[schema.codetable.frequency],开关):开启后以基础排序为基底,把用过的候选按词频上浮到未用过的之前(used-first)。
词频应用策略 strategy 有两种:
| 策略 | 语义 | 行为 | 适合 |
|---|---|---|---|
top | 一次到顶(MRU) | 选一次立刻置于已用档之首(按最近使用时间排) | "刚选过的下次就在首位" |
step(默认) | 逐次提升 | 按累计次数爬升,抗误选 | 稳健,默认 |
配置字段([schema.codetable.frequency]):
| 字段 | 默认 | 说明 |
|---|---|---|
enabled | false | 是否启用码表词频上浮(shipped 方案默认开启) |
strategy | step | top / step |
protect_top_n | 0 | 保护引擎基础序的前 N 位不被词频顶下来;0 = 不保护 |
protect_top_n
protect_top_n 在词频重排之后把基础排序原本的前 N 个候选按原相对序回填到最前面。它优先级高于词频——即使某候选词频极高,只要不在保护集内就进不了前 N 位。仅码表 / 混输方案生效(拼音方案固定为 0)。
码表的"用过"是永久的(count 不衰减)——用一次即可稳定上浮,不受权重量级压制。
拼音方案:带衰减的软置前
拼音候选多、还有整句组合,硬性 used-first 会破坏长句质量。因此拼音词频按衰减分参与排序:
age_hours = (now − last_used) / 3600
decay = exp(−ln2 × age_hours / half_life) // 半衰期衰减:刚用过≈1,久未用→0
freq_score = (base_scale × log2(count + 1) + recency_peak) × decay- 整句豁免:Viterbi 合成的整句候选(权重达 30M 档)恒锚定顶部,不被词频挤下。
- 软置前:非整句候选中,用过的按
freq_score排到未用过的之前。 - 阈值褪色:当
freq_score衰减到很低(久未用)时,该候选失去 used-first 资格,落回权重序。
拼音的"用过"是会褪色的(随半衰期衰减),这与码表的永久上浮相反。
配置字段([schema.pinyin.frequency]):
| 字段 | 默认 | 说明 |
|---|---|---|
enabled | false | 是否启用拼音词频(shipped 方案默认开启) |
half_life | 72(小时) | 半衰期(小时):越大,历史使用记得越久(配置为 0 时取此引擎默认) |
base_scale | 100 | 使用次数对分数的基础系数(配置为 0 时取此引擎默认) |
recency_peak | 0 | 与次数无关的"刚用过"峰值加成,随半衰期同步衰减;0 时完全退化为纯次数公式 |
用户如何调整排序
短语 / 用户词条权重
设置 → 词库 → 短语(或某方案的用户词库)的添加 / 编辑对话框中,权重字段默认 1000。在码表方案下,提高短语 / 用户词的 weight 可让它在同编码的候选中靠前。
拼音方案下的置顶
如上文所述,拼音候选权重是百万级,单纯把短语 weight 调到几千不足以稳定压过常用拼音词。更可靠的做法是使用一个不易与自然词碰撞的精确编码(例如 4 字母的 cobd),让短语只在该精确编码下出现。
weight: 0 与"不填权重"不同:不填时取默认 1000,显式写 0 则表示最低排序分,适合"必须收录但不希望干扰排序"的条目。
同权重的二级排序 (position)
短语 / 用户词条还有一个 position 字段,它不参与跨编码比较,仅在同编码同权重的候选之间做二级排序:同一编码下按 权重降序、position 升序排列。
| position | 含义 |
|---|---|
| 0(默认) | 落在 position > 0 的条目之后 |
| 1, 2, 3, … | 越小越靠前 |
设置 → 词库 中的"上移 / 下移 / 置顶"改的就是 position——你可以让多个条目权重都保持 1000,仅用箭头按钮调整组内顺序。
置顶 / 删除候选
在候选上使用置顶 / 删词操作会写入一层 shadow 规则(持久化在 userdata.redb),在不改动词库本身的前提下调整某个编码下候选的显隐与顺序。
混输模式下的层级关系
混输(码表 + 拼音)方案下,码表候选整体排在拼音候选之前:引擎在排序阶段对码表层整体提权,实现"层间隔离",让你输码表编码时即使与拼音撞码,码表答案也稳定靠前,而拼音字在码表无解时填补候选。
这个间隔由方案文件的 [engine.mixed].codetable_weight_boost 控制(出厂 10,000,000,一般无需改动),详见混输引擎配置。它只作用于排序阶段的层间提权,不改写候选自身的 weight——你在设置里看到 / 设置的 weight 始终是候选自身那个值。
设计中的注意事项
短语层与码表层是分开的 tier
短语候选(含命令直通车命令、字符组、模板短语)带有内部标记。在码表方案的生僻字过滤路径下:
- 短语永远保留,不会被当作"无常用字编码"过滤掉;
- 短语也不计入"同编码下是否有常用字"的判定,避免短语单独存在时让真正的码表生僻字被错误保留。
这让你配置短语(例如 dz = 我的地址)不会副作用地改变同编码的码表过滤行为。
字符组成员共享 group 权重
$AA("标点", "、。·…") 中所有字符候选的 weight 都等于该组短语条目的权重值;组内字符的相对顺序由字符串顺序决定,与权重无关。
相关参考
- 命令直通车 — 命令候选的权重选择
- 词库管理(设置 → 词库)
