第 12 章

第 12 章:特征提取与需求预测——Feature extraction / Demand forecasting

第 12 章:特征提取与需求预测——Feature extraction / Demand forecasting

评分卡、梯度提升树、时序预测这类模型擅长处理数字,却天生看不懂文本。"这条新闻够不够正面""这个供应商是不是在放鹰""这一期促销的力度比上一期强多少"——真正有解释力的信号全在非结构化内容里,却进不了特征矩阵。本章讲怎么用 Jev 把文本判成能直接拼进特征表的列,再把这些语义信号送进需求预测。

预测模型卡在"结构化"这一关

传统预测模型的输入是一张数值表:历史销量、价格、滞后项、星期几、节假日标记。它们的学习方式决定了这件事——梯度提升树在数值轴上切分,逻辑回归给每个特征配一个权重,时序模型看的是数值的走势。文本进不来,不是因为模型弱,而是因为它没有能承接文本的位置。

于是长期只有两条老路:

  • 训一个文本模型:为每个场景单独标注、单独微调。重,慢,换一个业务口径就得重来一遍。
  • 让 LLM 抽成 JSON:把一段文本丢给生成式模型,让它吐结构化字段。贵,且不稳定——同一段输入两次,字段可能不一样,数字还可能被"编"出来。

Jev 给的是第三条路:把每个判断变成一列概率。一次 Noul 得到 P(true),一次 Score 得到 score = Σ i·p_i,一次 Choice 得到每个选项的概率。这些值天然是 0 到 1 之间的数字,能直接当特征用,而且校准过、可复现、按决策计价。生成式模型负责"写出各种可能",这一步只需要它"在具体这一格选一个数"。

两种粒度:直接问,还是多维打分

同样的文本要变成特征,先要决定问几个问题、变成几列。

最简的粒度是每行一个问题、出一列:问一个 Noul"这条内容是不是促销",得到一列 is_promotion_p,直接拼进表里就行。它便宜、解释清晰,但一列只承载一个语义。

更细的粒度是把判断拆成多个维度分别打分。agentjournal.dev 上的一篇实测("Jev judge call vs dimension scores")就对比了这两种做法:每行只问一个直接问题,与把 12–14 个维度分别打分、再用本地拟合的权重合成,在三个分类任务上较量。结果是多维打分在日文 NLI 上达到 0.9076,而单问题只有 0.8373——维度拆得越细,信号越多,准确率越高。但代价也写在同一篇里:多维方案把大约 25 倍的"难判的良性样本"误标成了攻击。

这正是一对典型的特征工程取舍:更多的列意味着更多的信号,也意味着更高的成本与更脆的误报面。上线时该问几个问题,取决于下游模型缺多少信息、以及你能忍多少误报。

flowchart LR
    A["非结构化文本
评论 / 公告 / 日志 / 工单"] --> B["Jev 判定
Noul / Choice / Score"] B --> C["概率 → 特征列
0–1"] C --> D["拼进数值特征表"] D --> E["下游模型
GBDT / 时序 / TabPFN"] E --> F["预测结果
并由概率列回溯解释"]

实例一:tab-jev——把每个选项的概率变成一列

edamame-labs/tab-jev 把这条路径做到了极简。它对每一行的文本问一个针对目标的 Noul,再加一组 Score 评分维度,然后做一件关键的事:把每个选项的概率直接转成该行旁边的一列,和原有的数字字段并排摆在一起。接着,它让一个表格基础模型(如 TabPFN)在上下文里直接从这些带标签的行学习——不训练、不改权重,全靠拼接好的特征表。

在 Kickstarter 众筹预测上,它的数字很说明问题:256 个标签下达到 0.745 AUC,而只靠 Jev 本身加校准是 0.682。两个信息点:一是 Jev 单独就能给一个不弱的下限;二是把语义列交给下游表格模型后,它在极低标签量(256)下就把上限又抬了一截。这正是"特征提取喂给下游模型"最想看到的形状——不是让 Jev 去做预测,而是让它把预测所需的语义补上去。

实例二:jevextract 与 JevSpan——先把实体抽出来,再当特征

如果特征不是"整段文本的一个分数",而是"文本里出现了哪些实体、各是什么",就需要先做抽取。两个项目把抽取的分工讲得很清楚。

gabazureus/jevextract 是 LangExtract 的替代方案,它的做法是:由代码提出候选 span(带精确 offset),Jev 对每个 span 只回答一个 Choice(属于某个 schema 类别,或"都不是"),再加一个句级的 Noul,然后把高于阈值的答案留下、把接近临界的标出来交人工复核。它的基准显示,成本比在 Gemini 3.5 Flash 上跑 LangExtract 低 10–26 倍,但 F1 略低(84.2 对 88.5)。这个取舍之所以可接受,是因为候选位置由确定性代码给出,模型只做分类——控制流留在代码里,判定交给 Jev,成本因此被压了下来。

lzq-0529/jev-span 则把这条路推到零样本命名实体识别(NER):在标点处切分文本,对每种实体类型、每个候选窗口问一个 Choice,再用第二个 Choice 复核这个提名(是该类型 / 不是 / 混合 / 部分),最后用第三个 Choice 定下边界。在 12 个中英文 NER 基准上,它平均 73.7 严格 F1,而对标的 Qwen3.8-27B 直接抽取是 72.1。

顺带一提 xyzzzh/GroundingJev:它借用同样的"一次非自回归前向"思路,用一个 Jev 式的 Qwen3.5-0.8B 小模型,把图像加指代短语一次映射成四个边界框坐标,相对自回归的基座模型有 8.61 倍的推理加速。它不是直接调 Jev API,而是说明了这个决策范式可以蒸馏进一个极小的模型里——当你要抽的特征量极大时,这条路能同时拿下成本和延迟。

从抽取到特征:给评分卡补上语义列

把上面的做法拼起来,一段文本进入特征表的全过程是这样:先按业务定义一组判定,一次请求问完,再把每种输出的结果摊成列。

FEATURE_SPEC = {
    "sentiment":       ("score", "这段内容整体是正面还是负面?"),
    "urgency":         ("score", "这条信息的紧急程度如何?"),
    "is_promotion":    ("noul",  "这条内容是不是一次促销或打折?"),
    "competitor_move": ("noul",  "这条内容是否包含竞品的价格或动作?"),
}

def text_features(text: str) -> dict[str, float]:
    d = client.systemone(
        state=text,
        questions={k: {"type": v[0], "question": v[1]} for k, v in FEATURE_SPEC.items()},
        instructions="只依据给出的文本判断;每个问题独立作答,不要相互迁就。",
    )
    feats: dict[str, float] = {}
    for key, (kind, _) in FEATURE_SPEC.items():
        ans = d[key]
        # Noul 出概率,Score 出加权分——两者都是 0–1 的数字,可直接当特征
        feats[f"{key}_p" if kind == "noul" else f"{key}_score"] = (
            ans.probability if kind == "noul" else ans.score
        )
    return feats

返回的 feats 就是一个字典,直接作为一行拼到原有的数字特征后面即可。注意 Score 的 score = Σ i·p_i 这一步——它把一整条概率分布压成一个有序分数,天然落在选项数对应的区间里,不用你手动归一化;而且因为它来自分布,一列分数随时能拆回各选项的概率来解释。

实例三:jev-curate 与 jev-align——数据管道里的判定

特征提取在数据管道里其实是同一件事的两个方向:一边是往里筛,一边是给人改。

AkashPriyadarshii/jev-curate 做的是筛选:它用 Jev 的 Noul 检查加校准过的置信度来筛合成的 JSONL 与 Parquet 行,把通过的行和被拒的行直接流式写盘。这等于把"哪些样本配得上进训练集"这件判断变成了一次判定调用,而且不需要把整批数据先读进内存。

Sutro 的 jev-align 走得更远:它用 Jev 的 Choice、Score 或 Boolean 评估 CSV、Parquet、JSONL 的每一行,把含糊的和用于审计的样本送给人工,然后用被接受的人工标注,通过 GEPA 去优化那份保存下来的定义。这里有个关键设计——人的修正不是去重训一个模型,而是去改进"定义"本身:定义越写越准,同一套判定就跟着变好。这条"人工纠错回流到定义"的闭环,是所有长期运行的特征管道都必须有的。

实例四:jlink、jevgrep、typeful-triage——把判定铺到记录、日志与工单

同一套特征化思路可以铺到更细的粒度上。

keltokhy/jlink 处理的是记录去重与关联:用一句大白话的匹配规则把两条记录判成"是否同一条",每个记录对问一个 Noul,并辅以**本地候选分块(blocking)**先缩小要比较的范围。分块是确定性的、免费的,模型只负责规则覆盖不到的那些对——又一次"规则先行、模型兜底"。

allebee/jevgrep 把这一招用到了日志流:对着一个大白话问题,逐行问一个 Noul,把概率达到阈值的行打印出来,连 tail -f 的实时输出也能过滤,仓库里还带了一份对 Claude 的人工标注基准。日志特征化因此可以在流上跑,而不是先落库再算。

cephalization/jev-triage(typeful-triage)把它变成了团队工具:一个多人协作的分诊面板,对每个 issue 问一组固定的类型化问题——kind、severity、urgency、duplicate、next step——而且每一次人工纠正都被保存下来、在后续运行时回显给模型。这就是特征提取的在线版:判定出的字段既是给下游用的特征,也是给后续判定用的上下文,人会越用越准。

需求预测:把语义信号喂给预测模型

需求预测(Demand forecasting)是官方 use-case-map 里 19 个行业用例之一,但它在这个社区里还没有一个高星的独立项目可引。所以这一节用通用示例讲清模式,落地时的口径要随业务定义调整。

需求预测的困境是:数字能告诉你"过去卖了多少",但说不清"这一期为什么会跳"。一次竞品降价、一条负面新闻、一个节假日的政策变动——这些都会让需求偏离历史趋势,而它们在时序数值里根本不可见。语义列补的就是这块解释性特征。

tab-jev 的 Kickstarter 众筹预测其实就是需求预测的一个变体:众筹额是需求信号,它靠语义列在低标签量下把 AUC 从 0.682 拉到 0.745。另一个量级上的证据来自 pjmenon45/Jev-IOT:它用超低成本的非自回归遥测分类器在 1000 万台以上智能电表上做 sub-150ms 的异常分诊与自动处置,成本低于每月 35 美元。遥测本身就是一类需求与用量信号,它证明了这类判定能在极端规模与延迟预算下运行。

把语义列接进时序预测,形状大致如下——这里的代码是通用示例,用来说明模式:

import pandas as pd
from sklearn.ensemble import GradientBoostingRegressor

FEATURE_COLS = ["sales_lag_1", "sales_lag_7", "sales_lag_30",
                "sentiment_score", "is_promotion_p", "competitor_move_p"]

def build_demand_frame(sales: pd.DataFrame, semantic_rows: pd.DataFrame) -> pd.DataFrame:
    df = sales.copy()
    for lag in (1, 7, 30):                       # 数值滞后特征:过去的需求
        df[f"sales_lag_{lag}"] = df["sales"].shift(lag)
    df = df.merge(semantic_rows, on="date", how="left")  # 语义列:为什么这一期会变
    return df.dropna()

frame = build_demand_frame(sales, semantic_rows)         # semantic_rows 来自 text_features
model = GradientBoostingRegressor().fit(frame[FEATURE_COLS], frame["sales"])

要点是别把语义列当成时序数值的替代品。数值滞后项负责"惯性的延续",语义列负责"拐点的解释"。二者拼在一起,模型才既看到趋势、也看到趋势之外的那一下扰动。

让语义列可监控、可解释

用概率列做特征,最大的好处是可审计:任何一个预测,都能沿着它用到的那几列往下拆——这一行的 sentiment_score 是多少、competitor_move_p 是多少、Score 的分数来自哪个选项占了多少概率。特征不再是黑箱里的一团数字,而是能逐条讲清来源的判定结果。

但也正因为它是概率,校准必须被当成一等公民来监控。scienthoon/jev-ood-calibration 做了一份独立的校准测试:在 900 条规则生成的支持工单加三个公开基准上跑 Jev,公开了每一次原始响应,并给出每条类型的偏差方向——Choice 和 Score 偏自信,Boolean 偏保守。这条结论对特征提取很实用:用 Noul 出的列,系统性上会比它声称的更容易触发阈值;用 Score 出的列,则可能把分布压得更陡。 上线后按批次复核校准,比上线前调一次阈值更重要。

小结

  • 预测模型吃不下文本;Jev 的第三种解法是把每个判断变成一列 0–1 的概率或分数,便宜、可复现、可校准。
  • 两种粒度:每行一个问题出一列;或 12–14 维分别打分再拟合权重——后者在日文 NLI 上 0.9076 对 0.8373,但把约 25 倍的难判良性样本误标成攻击,信号与代价同时变大。
  • tab-jev 把每个选项的概率转成列,交给 TabPFN 在上下文里学:256 标签下 0.745 AUC,对照 Jev 单独加校准的 0.682。
  • 抽取层的关键分工是"候选由代码给、分类由 Jev 判":jevextract 成本低 10–26 倍但 F1 84.2 对 88.5;JevSpan 在 12 个中英文 NER 基准上平均 73.7 对 72.1;GroundingJev 用一次前向换 8.61 倍加速。
  • 数据管道里,jev-curate 用 Noul 加校准置信度流式筛 row,jev-align 把人工修正经 GEPA 回流去优化定义;jlink、jevgrep、typeful-triage 把判定铺到记录、日志流与工单。
  • 需求预测是官方用例之一,当前缺高星项目,用"数值滞后特征 + 语义列"的通用模式落地;上线后要按 jev-ood-calibration 的结论监控校准——Choice/Score 偏自信、Boolean 偏保守。

把文本变成可进模型的特征之后,下一章我们转向另一个"筛人"的场景:招聘与线索生成——简历怎么分诊、线索怎么打分与去重。

📑 Jev 实战:用类型化决策重构软件

1 第 1 章:为什么需要决策层——从"生成一切"到"只做判定" 2 第 2 章:三种类型化输出——Choice、Score、Noul 与调用契约 3 第 3 章:十个决策形态——什么时候该把判断交给 Jev 4 第 4 章:Classification 与 Routing——把分诊做成一等公民 5 第 5 章:Detection 与 Scoring——二元判断与有序评分 6 第 6 章:Search 与 Retrieval——在候选集里找相关 7 第 7 章:Ranking 与 Verification——排序与校验 8 第 8 章:ML Feature Extraction 与 Structured Data Extraction——把文本变特征与结构化数据 9 第 9 章:检索与知识图谱——Search and retrieval / Graphs and knowledge graphs 10 第 10 章:模型路由与 LLM 护栏——Model routing / LLM guardrails 11 第 11 章:语义代码检查——Semantic code linting 12 第 12 章:特征提取与需求预测——Feature extraction / Demand forecasting 13 第 13 章:招聘与线索生成——Recruiting / Lead generation 14 第 14 章:客户支持——Customer support 15 第 15 章:理赔、金融犯罪与风险评估——Insurance claims / Financial crime / Risk assessment 16 第 16 章:法律与合规——Legal and compliance 17 第 17 章:电商市场与广告——E-commerce marketplaces / Advertising 18 第 18 章:内容审核与信任安全——Moderation and trust and safety 19 第 19 章:游戏——Gaming 20 第 20 章:实时应用——150 毫秒预算 21 第 21 章:科学发现——Scientific discovery 22 第 22 章:大数据上的 AI Map Reduce——为什么便宜 100 倍 23 第 23 章:通用验证——Universal Verification 24 第 24 章:Harness Engineering——把决策织进智能体骨架
← 返回本书大纲