金融RAG的幻觉难题:用原子知识单元让模型"说实话"
一句话总结:把回答拆成最小知识单元,逐个验证正确性,用细粒度奖励训练模型不再胡编乱造。
📖 为什么金融RAG这么容易"说胡话"
如果你问大模型"贵州茅台2025年一季度的每股收益是多少",它可能会回答:"截至2025年5月15日,贵州茅台基本每股收益为70.86元"。
听起来很专业,对吧?但问题是:检索文档里明确写着"截至2025年3月31日",模型把时间搞错了。
这就是金融RAG的典型幻觉问题:模型生成了与检索文档相矛盾的内容。在金融领域,这种错误的代价很高——把财报日期搞错可能导致严重的投资决策失误。
RAG(检索增强生成)技术原本是为了解决大模型知识过时的问题,让模型可以"开卷考试"。但在实际应用中,即使把正确答案摆在面前,模型仍然会"抄错"。论文中的图1直观展示了这个问题:

图1:模型回答中的幻觉问题——虽然检索文档明确标注了正确的时间(3月31日),但模型生成的回答却错误地关联到了5月15日。这种"看着答案抄错"的现象在金融领域尤为危险。
🔍 现有方法的两个硬伤
方法一:人工标注参考答案
传统做法是让人工标注每个问题的标准答案,然后用模型生成的回答和标准答案对比。
问题:金融数据更新频繁,人工标注成本极高。财报数据每天都在变,你不可能为每个时间点都标注一套答案。
方法二:粗粒度二元奖励
另一种方法是用另一个模型判断生成的回答是否忠实于检索文档,给出一个"正确/错误"的二元标签作为奖励信号。
问题:这种"一刀切"的奖励太粗糙了。比如模型生成了5个知识点,其中4个正确、1个错误,粗粒度奖励会给整个回答打0分。这样模型学不到"哪个知识点错了",优化方向模糊。
论文的图2对比了粗粒度和细粒度奖励的区别:

图2:粗粒度奖励(上)vs 细粒度奖励(下)。粗粒度奖励只给整体打分,模型不知道错在哪;细粒度奖励逐个验证每个知识单元,提供更精确的优化信号。
🧠 核心方法:RLFKV框架
论文提出了RLFKV(Reinforcement Learning with Fine-grained Knowledge Verification)框架,核心思路是:把回答拆成最小的"知识原子",逐个验证,给每个原子打分。
什么是"原子知识单元"
金融领域的信息通常可以用四元组表示:(实体, 指标, 数值, 时间)。
比如: - (贵州茅台, 基本每股收益, 70.86元, 截至2025年3月31日) - (苹果公司, 营收, 948亿美元, 2025财年Q1)
这种四元组就是一个"原子知识单元"——不能再拆分的最小知识颗粒。
这让我想到了量子力学里的"原子"概念:物质可以无限细分,但到了原子层面就是最小单位了。同样,一段金融回答可以分成多个句子,每个句子可以分成多个知识点,到了四元组这个层面,就是知识的"原子"。
框架整体流程
RLFKV框架包含两个核心步骤:
第一步:原子知识单元分解与验证
- 用另一个LLM(论文用Qwen3-32B)把模型回答拆成四元组
- 对每个四元组,让验证模型检查它与检索文档是否一致
- 生成二元验证分数:1=正确,0=错误
第二步:计算奖励并优化
根据验证结果计算两个奖励信号:
忠实奖励(Faithfulness Reward): $\(r_f = \frac{1}{e^{\eta \cdot \min(\text{error\_count}, \gamma)}}\)$
这个公式的设计很巧妙: - 错误单元数越多,奖励越低(指数衰减) - 但设置了上限γ,防止奖励变成0,保持梯度稳定 - η是温度参数,控制惩罚力度
信息量奖励(Information Reward): $\(r_i = \begin{cases} 1 & \text{if } k \geq k_0 \\ 0 & \text{otherwise} \end{cases}\)$
其中k是当前回答的单元数,k₀是基础模型生成的单元数。
为什么要加这个奖励?为了防止"奖励黑客"。如果只用忠实奖励,模型可能学会一个取巧策略:只生成一个简单正确的句子,这样错误数为0,忠实奖励最高。但这样的回答毫无信息量。
这个设计让我想到考试:如果只看正确率,学生可能只答最简单的题;但如果要求"至少答够N道题",就能保证答题量。
总奖励是两者的平均值,然后用GRPO算法优化策略模型。
GRPO算法简介
GRPO(Group Relative Policy Optimization)是DeepSeek团队提出的强化学习算法,和传统PPO的主要区别是:不需要训练一个价值网络(Critic Model)。
传统PPO需要一个价值网络来估计"当前状态的价值",这相当于需要一个"老师"告诉模型"你这个回答大概值多少分"。训练价值网络需要大量显存和计算资源。
GRPO的思路是:对同一个问题生成多个候选回答,用奖励模型的均值和标准差来归一化,高分的给正奖励,低分的给负奖励。这样就不需要额外的价值网络了。
用考试来类比: - 传统PPO:有个老师预估你这次能考80分,你考了85分就算进步 - GRPO:你同一张卷子做5遍,自己跟自己比,选最好的那次
这种"组内相对比较"的方式大大降低了训练成本,让大模型的强化学习训练更加高效。
📊 实验结果:细粒度确实更有效
数据集
论文用了两个数据集:
| 数据集 | 规模 | 内容 |
|---|---|---|
| FDD | 1,461样本 | 股票描述 |
| FDD-ANT(新提出) | 2,000样本 | 股票、基金、宏观经济指标 |
FDD-ANT是论文新提出的真实业务数据集,覆盖更广的金融领域,更具挑战性。
主要结果
表1展示了不同方法在两个数据集上的表现:

表1:在FDD和FDD-ANT数据集上的忠实度(Faith.)和信息量(Info.)对比。RLFKV在两个数据集上都取得了最佳效果,忠实度提升约3个百分点。
关键发现:
-
RLFKV效果稳定:在两个数据集上,忠实度都提升了约3个百分点(从86.5%→89.5%,从90.2%→93.3%)
-
信息量奖励必不可少:消融实验显示,去掉信息量奖励后,忠实度变化不大,但信息量显著下降。这证明了防止"奖励黑客"的必要性。
-
细粒度优于粗粒度:图3对比了两种奖励方式的训练曲线:

图3:细粒度奖励(蓝色)vs 粗粒度奖励(橙色)。细粒度奖励不仅最终效果更好,而且收敛更快(约2000步达到稳定),训练更稳定。
错误分析:方法还不够完美
论文还对模型的错误进行了分类(图5):

图5:即使使用RLFKV,仍存在三种主要错误类型。时间相关错误占比高达83%,是未来改进的重点方向。
三种错误类型: 1. 时间遗漏(55%):检索文档有时间信息,但回答里忘了提 2. 时间不准确(28%):相对时间表达、财年与日历年转换错误 3. 数值错误(17%):主要是四舍五入不精确
这个分析很有价值:时间问题是金融RAG的最大痛点。金融数据的时间敏感性极强,"昨天涨停"和"今天涨停"含义完全不同。
💡 我的观点和启发
技术创新点
这篇论文的核心贡献不是某个全新的模型架构,而是一个工程化的问题拆解思路:把复杂的"幻觉问题"拆解成可验证的最小单元。
这种思路在很多领域都适用: - 代码审查:把PR拆成多个独立的改动点,逐个review - 产品测试:把用户路径拆成多个步骤,逐个验证 - 知识问答:把长回答拆成多个事实,逐个核实
工程落地思考
如果要实际部署这个方法,有几个工程问题需要考虑:
-
验证模型的准确性:论文用Qwen3-32B做验证,但验证模型本身也可能出错。如果验证模型把正确的单元判为错误,会给错误的训练信号。可能需要用更强的模型(如GPT-4)做验证,或者用ensemble方式提高鲁棒性。
-
四元组抽取的边界情况:金融文本有些复杂表达不太适合四元组。比如"该公司近三年营收复合增长率为15%",时间、实体、指标都好提取,但"数值"是"15%"还是"复合增长率"?
-
训练成本:虽然GRPO省掉了价值网络,但每次都要生成多个候选回答、逐个验证,计算量不小。论文没有详细讨论训练成本,这对实际部署很重要。
和其他工作的关联
这篇论文的"原子知识单元"思路让我想到另一个工作:Atomic Fact Decomposition for Attributed Question Answering。那篇论文也是把回答分解成原子事实,然后逐个归因。不同之处在于: - RLFKV关注的是"验证正确性",用于训练模型 - Atomic Fact Decomposition关注的是"找到来源",用于归因报告
这两个方向其实是互补的:验证→归因→修正,构成完整的幻觉治理流程。
局限性
论文坦诚地指出了一个局限:时间问题仍然难以解决。55%的错误是"时间遗漏",这说明模型学会了"说正确的话",但还没学会"说完整的话"。
这可能是因为: 1. 时间信息在金融文本中通常不是核心焦点,容易被模型忽略 2. 四元组结构对时间的表达不够丰富(只有timestamp字段,没有"时间上下文"的概念)
未来方向
基于论文的错误分析,我认为可以探索几个方向:
-
时间感知的奖励设计:给时间相关的单元更高的权重,或者在奖励函数中加入时间覆盖率的考量
-
结构化时间推理:不把时间当作普通字段,而是引入时间推理模块,专门处理"同比增长"、"环比下降"、"近三年复合增长"等时间表达
-
多粒度验证:除了四元组级别的细粒度验证,还可以加入句子级别、段落级别的验证,确保信息的完整性
🔧 代码示例:如何实现原子知识单元验证
下面是一个简化的Python示例,展示如何实现论文中的原子知识单元验证逻辑:
from dataclasses import dataclass
from typing import List
import math
@dataclass
class AtomicKnowledgeUnit:
"""原子知识单元:金融四元组"""
entity: str # 实体(如:贵州茅台)
metric: str # 指标(如:每股收益)
value: str # 数值(如:70.86元)
timestamp: str # 时间(如:2025年3月31日)
def __str__(self):
return f"({self.entity}, {self.metric}, {self.value}, {self.timestamp})"
def decompose_to_units(response: str, decomposer_model) -> List[AtomicKnowledgeUnit]:
"""
将模型回答分解为原子知识单元
实际实现需要调用LLM进行结构化抽取
"""
prompt = f"""请将以下金融文本分解为(实体, 指标, 数值, 时间)四元组:
文本:{response}
输出格式:每行一个四元组,用逗号分隔
"""
# 调用模型进行分解(这里省略具体实现)
units = decomposer_model.generate(prompt)
return units
def verify_unit(unit: AtomicKnowledgeUnit, retrieved_doc: str, verifier_model) -> bool:
"""
验证单个原子知识单元与检索文档是否一致
返回True表示正确,False表示错误
"""
prompt = f"""请判断以下知识单元是否与参考文档一致:
知识单元:{unit}
参考文档:{retrieved_doc}
只回答"正确"或"错误"
"""
result = verifier_model.generate(prompt)
return "正确" in result
def compute_faithfulness_reward(error_count: int, eta: float = 1.0, gamma: int = 5) -> float:
"""
计算忠实奖励
公式:r_f = 1 / exp(eta * min(error_count, gamma))
"""
score = min(error_count, gamma)
return 1.0 / math.exp(eta * score)
def compute_information_reward(current_units: int, baseline_units: int) -> int:
"""
计算信息量奖励
当前单元数 >= 基线单元数 则奖励1,否则0
"""
return 1 if current_units >= baseline_units else 0
def compute_total_reward(
units: List[AtomicKnowledgeUnit],
verification_results: List[bool],
baseline_unit_count: int
) -> dict:
"""
计算总奖励
"""
error_count = sum(1 for r in verification_results if not r)
faithfulness_reward = compute_faithfulness_reward(error_count)
information_reward = compute_information_reward(len(units), baseline_unit_count)
total_reward = (faithfulness_reward + information_reward) / 2
return {
"faithfulness_reward": faithfulness_reward,
"information_reward": information_reward,
"total_reward": total_reward,
"error_count": error_count,
"unit_count": len(units)
}
# 使用示例
if __name__ == "__main__":
# 模拟数据
response = "截至2025年3月31日,贵州茅台基本每股收益为70.86元。同期净利润为287.8亿元。"
retrieved_doc = "贵州茅台2025年一季报显示:截至2025年3月31日,基本每股收益70.86元,净利润287.8亿元..."
# 假设分解和验证结果
units = [
AtomicKnowledgeUnit("贵州茅台", "基本每股收益", "70.86元", "2025年3月31日"),
AtomicKnowledgeUnit("贵州茅台", "净利润", "287.8亿元", "2025年3月31日")
]
verification_results = [True, True] # 两个单元都正确
baseline_units = 2 # 基线模型生成的单元数
rewards = compute_total_reward(units, verification_results, baseline_units)
print(f"奖励计算结果:{rewards}")
# 输出:{'faithfulness_reward': 1.0, 'information_reward': 1, 'total_reward': 1.0, ...}
📚 参考资料
- 论文原文:Mitigating Hallucination in Financial RAG via Fine-Grained Knowledge Verification
- GRPO算法:DeepSeekMath: Pushing the Limits of Mathematical Reasoning
- 原子事实分解:Atomic Fact Decomposition Helps Attributed Question Answering
📝 总结
这篇论文提出了一种简单但有效的思路来解决金融RAG的幻觉问题:把回答拆成最小知识单元,逐个验证,用细粒度奖励训练模型。
核心洞察是:幻觉不是"全对全错"的问题,而是"部分对部分错"的问题。粗粒度的奖励信号让模型不知道错在哪,细粒度奖励则能精准定位问题。
当然,方法还不完美——时间相关问题占错误的83%,这是未来需要重点攻克的方向。但论文提供了一个清晰的框架,后续工作可以在此基础上继续优化奖励设计、改进验证机制、加强时间推理能力。
对于金融领域的RAG系统来说,这篇论文的价值在于:它把一个模糊的"幻觉问题"变成了可量化、可优化的技术问题。这比提出一个新的模型架构更有实用价值。