检索是无状态的,Prompt是脆弱不稳定的,两者都无法让系统持久地感知它已经学习、验证或认定为真正的内容。因此,本体变成了被动的参考,而不是主动的记忆,一致性在交互中逐渐瓦解。
本文探讨了一种不同的方法:本体记忆系统,一个专用的记忆层,使AI系统能够随时间保留、验证和重用结构化知识。
我们不是将Ontology视为静态工件或一次性上下文注入,而是将它们视为可以强化、检查冲突并在推理周期中重用的演化记忆对象。
结果不仅是更好的答案,而且是行为更像可靠知识工作者而非随机文本生成器的AI系统。
设想下,你是一家大型医院的医疗编码员。你的工作是将医生的诊断——比如“患者因未控制的2型糖尿病出现高血糖”——翻译成精确的医疗代码,如E11.65。这些代码是ICD-10-CM的一部分,这是一个拥有超过70,000个诊断代码的庞大系统。一个数字就能完全改变含义,错误可能导致保险索赔被拒、收入损失或合规问题。
随着大模型的兴起,一个关键问题出现了:AI能否准确记忆和回忆这些专业代码?这个被称为本体记忆的挑战,对价值4.5万亿美元的医疗保健行业具有重大影响。
本体是一种结构化的知识表示,它映射概念、关系和规则------就像按类型、颜色和品牌整理你的衣柜一样。在医学领域,像 ICD-10-CM 这样的本体不仅编码疾病,还编码严重程度、解剖位置和因果关系。
对于 AI 来说,记忆意味着:
这与缓存、将数据保存到磁盘或在数据库中查找信息不同。
问题是:AI 是否已经充分学习了这些代码,以至于无需外部帮助就能回忆起它们?
即使是经验丰富的编码员也不会记住全部 7 万个 ICD-10 编码。他们:
即使是专家,准确率也只有 80-95%,这凸显了为什么 AI 在处理罕见或细微的代码时会遇到很大的挑战
医院每天要处理数百份理赔单。每份理赔都需要多个准确编码——出错代价高昂。目前合格的编码员稀缺,错误可能导致理赔延迟或被拒。AI辅助编码(注意此处不是程序员的编程)有望提升速度和一致性:AI建议编码,人工审核,系统随时间不断改进。
不过只有在AI能可靠记忆编码时, 才能正常工作。否则,它会产生更多错误和额外工作量。
凡是存在大规模、结构化、高价值知识的地缝,本体记忆都至关重要:
当人工智能能够可靠地记住这类知识时,它是一个巨大的优势。当它做不到时,就是一种风险。
在常见代码上实现90%以上的准确率是具有变革性的;而40%的准确率则意味着根本没有什么作用。正是这一挑战促使我们构建了一个本体记忆评估系统,用以严格测试AI的性能——我们接下来将对此进行探讨。
在构建任何AI编码助手之前,我们需要严谨的数据。具体来说,我们提出了三个研究问题:
RQ1:记忆准确性- 当给定一个概念标签时,大语言模型(LLM)能多准确地回忆起确切的医疗编码?例如,如果我们给LLM输入"2型糖尿病伴高血糖",它能准确输出E11.65吗?我们通过以下指标来衡量:
完全匹配准确率:它是否完全正确?
编辑距离:需要多少次字符编辑来修正错误?(例如,E11.65 → E11.3 需要2次编辑)
Jecard相似度:字符/标记之间的重叠程度如何?(衡量部分正确性)
RQ2:流行度相关性- AI在常见编码上的表现是否比罕见编码更好?这一点很重要,因为医院遇到常见病情的频率远高于罕见病情。如果AI只对常见编码有效,它可能仍然对80%的病例有价值,而人类编码员可以处理罕见的20%。我们衡量:
高流行度准确率:在前25%最常见编码上的表现
低流行度准确率:在后25%最罕见编码上的表现
准确率下降程度:高流行度与低流行度之间的差距
RQ3:预测不变性- AI的输出是否一致且稳定?如果我们问同一个问题三次,是否会得到相同的答案?如果我们稍微调整问题的表述,答案会改变吗?这对生产系统很重要——用户期望确定性的行为。我们测试:
PI-1:重复完全相同的提示3次——答案相同的频率是多少?
PI-2:改变温度参数(随机性)——输出变化有多大?
PI-3:重新表述问题(不同措辞、不同语言)——答案是否保持不变?
这些问题让我们全面了解:不仅仅是"它是否有效?",而是"它在何时有效,效果如何,以及我们能否依赖它?"
第一步是获取真实的医疗数据,这需要反映现实世界医疗编码的复杂性。我们获取了:
ICD-10-CM 诊断编码(来自 CDC/CMS 官方来源的 51 个概念):
每个编码都包含:
数据摄取脚本(ingest_real_data.py)执行几个关键步骤:
步骤 1:流行度分桶- 我们不能只是随机抽取概念进行测试。如果那样做,我们很可能会得到大部分常见编码(因为它们在数据中出现得更多)。我们需要【分层抽样】:在不同流行度级别上实现均衡的代表性。
我们使用对数分布将概念划分为 50 个流行度桶:
这确保了当我们为评估抽取 30 个概念时,我们从每个桶级别获得 1-2 个概念,代表了从极其常见到极其罕见状况的完整谱系。
步骤 2:图结构创建- 每个概念在 Neo4j 中成为一个节点,具有以下属性:
(:Concept {
concept_id:"E11.65",
label:"Type 2 diabetes mellitus with hyperglycemia",
category:"Endocrine",
popularity_score:850,
popularity_bucket:43,
ontology:"ICD-10-CM"
})
概念链接到其本体:
(:Concept)-[
ART_OF]->(:Ontology{name:"ICD-10-CM",version:"2024"})
步骤 3:验证- 摄取脚本验证:
运行python scripts/ingest_real_data.py后,我们在 Neo4j Aura 中拥有了 101 个真实的医疗概念,准备进行评估。
数据加载完成后,我们构建了评估流程(run_full_evaluation.py)
分层抽样:比如,当评估30个概念时,系统不会只是从数据库中选取前30个。相反,它会:
这确保了平衡的代表性。如果糖尿病代码都在高流行度组,而罕见遗传病都在低流行度组,我们的样本会同时包含两者。
LLM 查询生成:对于每个概念,我们生成一个精心设计的提示。以 ICD-10-CM 为例:
You are a medical coding expert. Your taskistoprovide the exact ICD-10-CM code
forthe following diagnosis.Diagnosis: Type2diabetes mellituswithhyperglycemia
RespondwithONLY the ICD-10-CM code,nothingelse. Format: X00.00
Your response:
为什么采用这种特定格式?
异步评估:系统使用 Python 的asyncio并发发送所有查询:
asyncdefevaluate_all_concepts(self, concepts):
tasks = [self.evaluate_concept(c)forcinconcepts]
results =awaitasyncio.gather(*tasks)
returnresults
响应解析:LLM 的响应并不总是干净的。以下是各种可能的例子:
E11.65The ICD-10-CM code is E11.65Probably E11.65 or E11.9E11-65(用了短横线而不是句点)响应解析器使用特定于本体的正则表达式模式:
[A-Z][0-9]{2}\.?[0-9A-Z]*(匹配 E11.65, I10, Z79.4)它从响应中提取第一个有效代码,并根据已知模式进行验证。
指标计算:对于每个概念,我们计算:
预测值 == 真实值吗?布尔值:正确或不正确。这些指标提供了超越简单对/错的细微差别。一个始终接近正确答案(E11.3 而不是 E11.65)的 LLM,经过后处理后可能仍然有用,而一个产生随机代码(用 K56.9 表示糖尿病)的 LLM 则不然。
原始指标本身用处不大。报告生成器能将评估结果转化为人们真正能用上的洞见, 可以让大模型生成以下报告
Markdown 报告
JSON 导出
CSV 导出
PNG 图表
语义理解:GPT-5.2没有出现类别错误。
截断错误:仍然常见,但在减少
罕见代码:GPT-5.2 达到了 > 80% 的准确率,突破了“罕见代码障碍”。
不变的挑战:尽管准确性大幅提升,流行度偏差依然顽固存在:
看似矛盾的现象:罕见代码的准确性有所提高,但常见代码与罕见代码之间的性能差距反而扩大了。GPT-5.2 显示出最大的差距,其性能差异达到 47.6%。
这为何重要:模型似乎是为了追求整体准确性而优化的,而非均衡的性能。即使基础模型很强,罕见代码仍需要针对性技术,如上采样或微调。
业务影响:AI 呈现出明显的两级分化模式:
常见代码:约 81% 的准确率 - 经过验证即可使用
罕见代码:约 33% 的准确率 - 需要专家人工监督
我们进行了全面的不变性测试,以衡量每个模型输出结果的一致性。这对于生产部署至关重要——如果AI对同一个问题给出不同的答案,那它就是不可靠的。
不变性公式:PI = 1 - (U - 1) / (M - 1),其中 U = 唯一响应数,M = 提示词数量
GPT-5.2 预测不变性
不变性得分 (PI):
Example variations tested:
"What is the ICD-10-CM code for Type 2 diabetes with hyperglycemia?"
"Provide the diagnosis code for Type 2 diabetes mellitus with hyperglycemia"
"Give me the identifier for Type 2 diabetes with hyperglycemia"
关键洞察:预测不变性是衡量记忆置信度的可靠指标。当模型对某个编码不确定时,它会在不同的提示词变体下产生不一致的响应。这意味着我们可以使用PI分数来在没有真实答案的情况下估计可靠性——这对于生产环境中的筛选很有用。
业务影响:生产系统需要遵循提示词工程的最佳实践。如果没有这些保障措施,AI可能会根据问题表述方式的不同,对同一个诊断给出不同的编码——这在临床环境中是完全不可接受的。
python scripts/ingest_real_data.py
当你运行python scripts/run_full_evaluation.py ICD-10-CM --model gpt-5.2 --sample-size 50时,系统首先会连接到Neo4j Aura:
query="""
MATCH (c:Concept)-[
ART_OF]->(o:Ontology {name: $ontology})
RETURN c.concept_id AS concept_id,
c.label AS label,
c.popularity_bucket AS popularity_bucket,
c.popularity_score AS popularity_score
ORDER BY c.popularity_bucket DESC
"""
results= session.run(query,ontology="ICD-10-CM")
这段 Cypher 查询(Neo4j 的查询语言)会获取属于 ICD-10-CM 本体的所有概念,以及它们的流行度指标。结果是:一个包含 51 个概念的列表,每个概念都有 ID、标签和流行度数据。
抽样算法确保平衡代表性:
defstratified_sample(concepts, sample_size):
# Group by popularity bucket
buckets = {}
forconceptinconcepts:
bucket = concept['popularity_bucket']
ifbucketnotinbuckets:
buckets[bucket] = []
buckets[bucket].append(concept)
Output:
samples_per_bucket=max(1,sample_size//len(buckets))
sampled=[]forbucket,concepts_in_bucketinbuckets.items():n=min(samples_per_bucket,len(concepts_in_bucket))sampled.extend(random.sample(concepts_in_bucket,n))
whilelen(sampled)<sample_size:remaining=[cforcinconceptsifcnotinsampled]sampled.append(random.choice(remaining))returnsampled[:sample_size]
这样可以确保每个流行度等级都有代表性,避免偏向常见或罕见的代码。
### 第三步:LLM查询(异步处理)
针对每个医学概念,我们生成一个提示词并发送给大语言模型:
```python
asyncdefevaluate_concept(concept):
prompt =f"""You are a medical coding expert. Your task is to provide the exact ICD-10-CM code for the following diagnosis.Diagnosis:{concept['label']}
Respond with ONLY the ICD-10-CM code, nothing else. Format: X00.00
Your response:"""
# 发送给LLM(Ollama)
response =awaitllm_client.aquery(prompt)
# 解析响应
predicted_code = parser.extract_code(response)
# 计算指标
is_correct = (predicted_code == concept['concept_id'])
lev_distance = levenshtein_distance(concept['concept_id'], predicted_code)
jaccard_sim = jaccard_similarity(concept['concept_id'], predicted_code)
return{
'concept_id': concept['concept_id'],
'label': concept['label'],
'predicted': predicted_code,
'is_correct': is_correct,
'levenshtein': lev_distance,
'jaccard': jaccard_sim,
'popularity_bucket': concept['popularity_bucket']
}
通过使用async/await,我们可以同时运行所有10个(或30个、50个)评估任务。Python的事件循环能高效地管理这些I/O密集型的大语言模型请求。
在所有概念评估完成后,我们对各项指标进行汇总:
defcalculate_summary_metrics(results):
total =len(results)
correct =sum(1forrinresultsifr['is_correct'])
accuracy = correct / total
avg_levenshtein =sum(r['levenshtein']forrinresults) / total
avg_jaccard =sum(r['jaccard']forrinresults) / total
# 流行度分析
high_pop = [rforrinresultsifr['popularity_bucket'] >=38]
low_pop = [rforrinresultsifr['popularity_bucket'] <=13]
high_pop_acc =sum(1forrinhigh_popifr['is_correct']) /len(high_pop)
low_pop_acc =sum(1forrinlow_popifr['is_correct']) /len(low_pop)iflow_popelse0
degradation = high_pop_acc - low_pop_acc
return{
'accuracy': accuracy,
'avg_levenshtein': avg_levenshtein,
'avg_jaccard': avg_jaccard,
'high_pop_accuracy': high_pop_acc,
'low_pop_accuracy': low_pop_acc,
'degradation': degradation
}
这样我们就得到了用于执行摘要的核心数据。
我们使用 Matplotlib 和 Seaborn 来创建图表:
这样就生成了一个专业质量的图表,展示了流行度与准确性之间的相关性。
最后,我们把所有内容整理成一份 Markdown 报告。
报告会保存为report_ICD-10-CM_[timestamp].md,放在data/results/目录下。
我们最初想解决一个看似简单的问题:AI能否足够好地记住医学编码,从而辅助人类编码员?但答案正如AI研究中常见的那样——是微妙的。
AI能够部分记忆:它能做到70%完全正确,还有更多是“差不多对”(子类型错,但大类对)。它展示了对医学本体的语义理解能力,尽管缺乏完美的记忆。
流行度偏差真实存在且影响显著:AI擅长处理常见编码,但在罕见编码上表现不佳。这不是一个程序错误,而是神经网络从数据中学习方式的根本局限。你无法记住你几乎没见过的东西。
生产部署是可行的,但需要精心设计:作为一种验证工具或常见病例的助手,AI可以带来巨大的商业价值。但完全自动化还为时过早,且充满危险。
| 欢迎光临 链载Ai (http://www.lianzai.com/) | Powered by Discuz! X3.5 |