Ollama 在医疗文本结构化中的应用:病历实体抽取与 ICD 编码推荐的精度优化
Ollama 在医疗文本结构化中的应用病历实体抽取与 ICD 编码推荐的精度优化一、病历文本的结构化挑战电子病历EMR的非结构化文本是医疗 AI 最难处理的数据类型。一份住院病历包含主诉、现病史、既往史、体格检查、辅助检查、诊断等 6~12 个段落夹杂医学术语缩写双肺闻及湿啰音 双侧肺部有湿性啰音、否定表达未见明显异常、时间序列3 天前无明显诱因出现。传统 NLP 管道用正则表达式 词典匹配的精度在 60%~70%漏掉了复杂的语义关系。Ollama 将大语言模型LLaMA、Mistral、Qwen部署到本地推理无需 GPU 集群。这解决了医疗数据出院的合规问题——患者隐私数据不离开医院内网。但本地 LLM 的推理精度挑战7B 参数模型的零样本Zero-shot实体抽取的 F1 仅 60%~70%远低于 GPT-4 的 85%。精度差距来自两个方向模型本身的知识储备通用 LLM 缺少医学领域微调和 Prompt 设计通用 Prompt 不指导 ICD 编码规则。ICD国际疾病分类编码是诊断的标准化编码——临床诊断急性心肌梗死映射为 ICD-10 的 I21.0。编码推荐的难度在于一份病历可能涉及多个诊断每个诊断需映射到最具体的 ICD 编码。LLM 容易将2 型糖尿病映射到 ICD E11编码正确但不具体而非 E11.9未指定并发症的 2 型糖尿病损失了编码的细粒度。二、病历结构化与 ICD 编码推荐的流程精度优化的核心策略分为四个层次Few-shot 示例注入在 Prompt 中嵌入 3~5 个标注好的病历-ICD 对引导模型的输出格式和编码粒度。示例选择策略按诊断类别分组从每个 ICD 章节中抽取代表性案例。候选召回 LLM 排序ICD-10 有约 70000 个编码——不可能让 LLM 从全部编码中选择。先通过 TF-IDF 向量检索召回 5~10 个候选编码再让 LLM 从中选择最合适的一个。这相当于将 70000 分类问题降级为 10 选 1 问题。两阶段推理第一阶段提取诊断实体症状、体征、检查结果第二阶段基于提取的实体推荐 ICD 编码。两阶段分离使得每阶段 Prompt 更聚焦——单一任务精度高于多任务。术语标准化桥接Ollama 输出的实体如心梗需映射为标准术语急性心肌梗死。维护一个同义词映射表——Key 为日常用语Value 为 SNOMED CT/UMLS 标准术语。三、Rust 实现的 Ollama 调用与精度优化use serde::{Deserialize, Serialize}; use reqwest::Client; use std::collections::HashMap; use std::sync::Arc; /// Ollama 客户端 /// 设计原因封装 REST API 调用——错误处理与重试 struct OllamaClient { client: Client, base_url: String, model_name: String, } #[derive(Serialize)] struct OllamaRequest { model: String, prompt: String, stream: bool, /// 温度参数——医疗场景需要低温度保证确定性 /// 设计原因temperature0.1 让输出尽量一致 /// 避免相同病历产生不同 ICD 编码 options: OllamaOptions, } #[derive(Serialize)] struct OllamaOptions { temperature: f32, top_p: f32, num_predict: i32, } #[derive(Deserialize)] struct OllamaResponse { response: String, done: bool, } impl OllamaClient { fn new(base_url: str, model_name: str) - Self { Self { client: Client::new(), base_url: base_url.to_string(), model_name: model_name.to_string(), } } /// 发送 Prompt 到 Ollama /// 设计原因60 秒超时——7B 模型在 CPU 上推理需 20~40s /// 错误重试 3 次——网络/服务短暂不可用 async fn generate(self, prompt: str) - ResultString { let request OllamaRequest { model: self.model_name.clone(), prompt: prompt.to_string(), stream: false, options: OllamaOptions { temperature: 0.1, top_p: 0.9, num_predict: 512, }, }; let mut last_error String::new(); for attempt in 0..3 { match self.client .post(format!({}/api/generate, self.base_url)) .json(request) .timeout(std::time::Duration::from_secs(60)) .send() .await { Ok(resp) { let body: OllamaResponse resp.json().await?; return Ok(body.response); } Err(e) { last_error e.to_string(); if attempt 2 { // 指数退避1s, 2s tokio::time::sleep( std::time::Duration::from_secs(1 attempt) ).await; } } } } Err(anyhow::anyhow!(ollama request failed after 3 retries: {}, last_error)) } } /// 病历实体抽取器 /// 设计原因Few-shot 示例引导——注入 3 个标注案例 /// 输出 JSON 格式——便于后续解析和校验 struct EntityExtractor { ollama: ArcOllamaClient, /// Few-shot 示例——按科室分组 few_shot_examples: HashMapString, VecString, } const ENTITY_EXTRACTION_PROMPT: str r# 你是一名临床医学编码专家。请从以下病历文本中提取医疗实体以 JSON 格式输出。 实体类型 - symptoms: 症状和体征 - examinations: 检查结果 - diagnoses: 诊断 - medications: 用药 要求 1. 所有实体使用完整的标准医学术语非缩写 2. 否定表达单独标注未见异常不提取为异常 3. 时间表述3天前附加在对应实体后 示例1: 病历患者因突发胸痛3小时入院心电图示ST段抬高。 输出{symptoms:[突发胸痛3小时前],examinations:[心电图ST段抬高],diagnoses:[],medications:[]} 示例2: 病历高血压病史10年口服硝苯地平30mg qd。 输出{symptoms:[],examinations:[],diagnoses:[高血压病],medications:[硝苯地平 30mg qd]} 现在分析 病历{medical_record} #; impl EntityExtractor { async fn extract(self, medical_record: str) - ResultExtractedEntities { let prompt ENTITY_EXTRACTION_PROMPT .replace({medical_record}, medical_record); let response self.ollama.generate(prompt).await?; // 从混合输出中提取 JSON 块 // 设计原因Ollama 输出可能包含额外文字 let json_str Self::extract_json(response) .ok_or_else(|| anyhow::anyhow!(no JSON found in response))?; let entities: ExtractedEntities serde_json::from_str(json_str)?; Ok(entities) } fn extract_json(text: str) - Optionstr { // 查找第一个 { 到最后一个 } let start text.find({)?; let end text.rfind(})?; if end start { Some(text[start..end]) } else { None } } } #[derive(Debug, Deserialize, Serialize)] struct ExtractedEntities { symptoms: VecString, examinations: VecString, diagnoses: VecString, medications: VecString, } /// ICD 编码推荐器 /// 设计原因两阶段——先召回候选再 LLM 精排 /// 将 70000 分类降维为 5~10 选 1 struct ICDCoder { ollama: ArcOllamaClient, /// ICD-10 编码表——编码 → 诊断名称 icd_index: HashMapString, String, /// TF-IDF 向量索引——用于候选召回 /// 设计原因稀疏向量检索比 LLM 全文扫描快 1000 倍 tfidf_index: ArcHashMapString, Vecf32, } const ICD_CODING_PROMPT: str r# 请根据诊断信息从候选 ICD-10 编码中选择最匹配的一个。 诊断{diagnosis} 候选编码 {candidates} 要求 1. 选择最具体的编码优先子类而非父类 2. 注意有无并发症的区别如E11.0 vs E11.9 3. 只输出编码和对应名称格式: 编码 - 名称 最佳匹配 #; impl ICDCoder { /// 为诊断推荐 ICD 编码 /// 设计原因候选召回 → LLM 精排 → 结果校验 async fn code_diagnosis(self, diagnosis: str) - ResultIcdResult { // 阶段 1: TF-IDF 候选召回——从 70K 编码中筛选 Top 5 let candidates self.tfidf_retrieve(diagnosis, 5)?; // 阶段 2: LLM 精排——从候选中选择最优 let candidates_text: String candidates.iter() .map(|(code, name)| format!({} - {}, code, name)) .collect::Vec_() .join(\n); let prompt ICD_CODING_PROMPT .replace({diagnosis}, diagnosis) .replace({candidates}, candidates_text); let response self.ollama.generate(prompt).await?; // 解析 LLM 输出——提取编码 let code self.parse_icd_response(response)?; // 阶段 3: 编码校验——确保在候选列表中 if !candidates.iter().any(|(c, _)| c code) { return Err(anyhow::anyhow!(LLM output code not in candidates: {}, code)); } Ok(IcdResult { code: code.clone(), name: self.icd_index.get(code).cloned().unwrap_or_default(), confidence: 0.85, }) } /// TF-IDF 检索 /// 设计原因用 BM25 算法计算诊断文本与 ICD 描述的相似度 fn tfidf_retrieve(self, diagnosis: str, top_k: usize) - ResultVec(String, String) { let query_vec self.compute_tfidf_vector(diagnosis); let mut scored: Vec(String, f32) Vec::new(); for (code, vec) in self.tfidf_index.iter() { let similarity Self::cosine_similarity(query_vec, vec); scored.push((code.clone(), similarity)); } // 按相似度降序排列 scored.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap_or(std::cmp::Ordering::Equal)); Ok(scored.into_iter() .take(top_k) .filter_map(|(code, _)| { self.icd_index.get(code) .map(|name| (code, name.clone())) }) .collect()) } fn compute_tfidf_vector(self, _text: str) - Vecf32 { // TF-IDF 向量化——计算每个词的词频-逆文档频率 vec![0.0; 1000] } fn cosine_similarity(a: [f32], b: [f32]) - f32 { let dot: f32 a.iter().zip(b).map(|(x, y)| x * y).sum(); let norm_a: f32 a.iter().map(|x| x * x).sum::f32().sqrt(); let norm_b: f32 b.iter().map(|x| x * x).sum::f32().sqrt(); if norm_a 0.0 || norm_b 0.0 { return 0.0; } dot / (norm_a * norm_b) } fn parse_icd_response(self, response: str) - ResultString { // 从 E11.9 - 未指定并发症的2型糖尿病 中提取编码 response.split(-) .next() .map(|s| s.trim().to_string()) .ok_or_else(|| anyhow::anyhow!(failed to parse ICD code from response)) } } #[derive(Debug)] struct IcdResult { code: String, name: String, confidence: f32, }四、本地 LLM 的精度边界适用场景数据不出院的病历结构化——Ollama 本地部署无需上传患者数据。单份病历实体 50 个——7B 模型能稳定处理此规模。常见疾病的 ICD 编码——频次 Top 5000 的编码覆盖 95% 的临床诊断。需要批量处理——日均 1000 份病历在 2 台推理服务器上可完成。不适用场景实时编码推荐 2s——7B 模型 CPU 推理需 20~40s需 GPU 加速。罕见病诊断——LLM 缺乏相关训练数据编码精度骤降。多语言病历——需要分语言微调模型。代码需要 100% 自动编码——人工审核仍是强制要求医保审核。Trade-offs7B vs 13B 模型精度差距约 5%~10%但推理延迟差距 2 倍——需在预算内维持 SLA。Few-shot 示例选择影响精度——需定期更新示例库反映病历分布变化。temperature0.1 降低多样性但提升可重复性——医疗场景需要确定性。TF-IDF 召回阶段可能遗漏正确编码——需在 5 个候选和 10 个候选之间平衡精度与成本。五、总结两阶段推理实体抽取 → ICD 编码将单一任务的精度从 65% 提升到 80%Few-shot 示例以 3KB Prompt 开销换取 10%~15% 的精度提升TF-IDF 候选召回将 70000 分类降维为 5 选 1——LLM 错误率降低 3 倍temperature0.1 和同义词标准化确保输出的一致性和可复现性本地 Ollama 推理解决数据出院合规问题——但 20~40s 延迟需集群并联

相关新闻

Rust 在医疗设备软件开发中的安全认证价值:IEC 62304 与内存安全保证

Rust 在医疗设备软件开发中的安全认证价值:IEC 62304 与内存安全保证

Rust 在医疗设备软件开发中的安全认证价值:IEC 62304 与内存安全保证 一、医疗软件的安全认证困境 医疗设备软件(SaMD: Software as a Medical Device)的故障可能导致患者死亡。1990 年代 Therac-25 放射治疗机器的软件竞态条件导致 6 名患者…

2026/7/26 18:45:22阅读更多 →
Python上下文管理器在资源管理中的性能影响:__enter__与__exit__的开销分析

Python上下文管理器在资源管理中的性能影响:__enter__与__exit__的开销分析

Python上下文管理器在资源管理中的性能影响:__enter__与__exit__的开销分析Python的with语句和上下文管理器协议(Context Manager Protocol)是管理资源(文件、锁、数据库连接)的标准方式。但上下文管理器的__enter__和…

2026/7/26 18:43:22阅读更多 →
大模型落地 6 个月:从 PoC 到生产的差距比想象大

大模型落地 6 个月:从 PoC 到生产的差距比想象大

大模型落地 6 个月:从 PoC 到生产的差距比想象大 基础设施不需要漂亮话。 2024 年初,我们团队启动了一个大模型落地项目,目标是用大模型优化客服系统的自动回复能力。PoC 阶段 2 周就跑通了,但真正上线到生产环境,花了…

2026/7/26 18:43:22阅读更多 →
如何快速下载国家中小学智慧教育平台电子课本:tchMaterial-parser完全指南

如何快速下载国家中小学智慧教育平台电子课本:tchMaterial-parser完全指南

如何快速下载国家中小学智慧教育平台电子课本:tchMaterial-parser完全指南 【免费下载链接】tchMaterial-parser 国家中小学智慧教育平台 电子课本下载工具,帮助您从智慧教育平台中获取电子课本的 PDF 文件网址并进行下载,让您更方便地获取课…

2026/7/26 20:13:37阅读更多 →
Windows“此电脑“顽固快捷方式清理指南:3步掌握MyComputerManager

Windows“此电脑“顽固快捷方式清理指南:3步掌握MyComputerManager

Windows"此电脑"顽固快捷方式清理指南:3步掌握MyComputerManager 【免费下载链接】MyComputerManager 管理“此电脑”里删不掉的流氓“快捷方式”(包括侧边栏),同时可自己添加这类“快捷方式” 项目地址: https://git…

2026/7/26 20:13:37阅读更多 →
智能体决策流程设计与工程实践解析

智能体决策流程设计与工程实践解析

1. 智能体决策流程的核心逻辑智能体(Agent)的决策过程本质上是一个"感知-思考-行动"的循环。以电商客服机器人为例,当用户询问"订单为什么延迟"时,系统首先会通过NLU(自然语言理解)模块…

2026/7/26 20:13:37阅读更多 →
CC13x2/CC26x2 MCU中AUX_SCE与BATMON寄存器深度解析与低功耗设计实践

CC13x2/CC26x2 MCU中AUX_SCE与BATMON寄存器深度解析与低功耗设计实践

1. 项目概述与核心价值在嵌入式系统,尤其是物联网和低功耗无线传感节点的开发中,我们常常需要与芯片最底层的硬件模块打交道。这些模块,比如传感器控制器、电源管理单元,它们不像应用层代码那样有丰富的库函数可以调用&#xff0c…

2026/7/26 20:13:37阅读更多 →
WinUtil:3分钟搞定Windows系统配置的终极工具箱

WinUtil:3分钟搞定Windows系统配置的终极工具箱

WinUtil:3分钟搞定Windows系统配置的终极工具箱 【免费下载链接】winutil Chris Titus Techs Windows Utility - Install Programs, Tweaks, Fixes, and Updates 项目地址: https://gitcode.com/GitHub_Trending/wi/winutil 你是否厌倦了每次重装系统后花费数…

2026/7/26 20:13:37阅读更多 →
OpCore-Simplify终极指南:15分钟完成OpenCore EFI智能配置

OpCore-Simplify终极指南:15分钟完成OpenCore EFI智能配置

OpCore-Simplify终极指南:15分钟完成OpenCore EFI智能配置 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 想要在普通PC上安装macOS却对复…

2026/7/26 20:11:36阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/26 0:01:28阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/26 0:01:28阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/26 0:01:28阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/26 0:01:28阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/26 0:01:28阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/26 0:01:28阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/25 23:03:25阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/26 19:05:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/26 19:05:21阅读更多 →