惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
Kaspersky official blog
T
Threat Research - Cisco Blogs
N
News and Events Feed by Topic
Hacker News: Ask HN
Hacker News: Ask HN
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 叶小钗
Security Latest
Security Latest
Spread Privacy
Spread Privacy
aimingoo的专栏
aimingoo的专栏
N
News and Events Feed by Topic
Webroot Blog
Webroot Blog
U
Unit 42
Cyberwarzone
Cyberwarzone
小众软件
小众软件
Scott Helme
Scott Helme
Engineering at Meta
Engineering at Meta
Microsoft Security Blog
Microsoft Security Blog
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
爱范儿
爱范儿
S
Schneier on Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Schneier on Security
Schneier on Security
Latest news
Latest news
GbyAI
GbyAI
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
The Register - Security
The Register - Security
WordPress大学
WordPress大学
博客园_首页
Blog — PlanetScale
Blog — PlanetScale
PCI Perspectives
PCI Perspectives
Jina AI
Jina AI
AI
AI
NISL@THU
NISL@THU
I
Intezer
G
GRAHAM CLULEY
B
Blog
S
Secure Thoughts
IT之家
IT之家
宝玉的分享
宝玉的分享
Recent Announcements
Recent Announcements
Y
Y Combinator Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
酷 壳 – CoolShell
酷 壳 – CoolShell
有赞技术团队
有赞技术团队
V2EX - 技术
V2EX - 技术
Recorded Future
Recorded Future
Hacker News - Newest:
Hacker News - Newest: "LLM"

博客园 - VipSoft

MinerU - 将非结构化文档(PDF、图片、Office 文件等)转换为机器可读的 Markdown 和 JSON LangChain 入门 服务端部署-FastAPI LangChain 入门 LangSmith LangChain 入门 实战 - 食谱推荐 LangChain 入门 Memory 会话记忆 LangChain 入门 Tools 工具 LangChain 入门 Tools 工具 LangChain 入门 Prompts 提示词 LangChain 入门 Message 消息 LangChain 入门 Model 的初始化和调用 LangChain 入门 Agent 的基本运行机制 AI 0基础学习,名词解析 LangChain 和 LangGraph AI大模型知识体系 Dify — Workflow - 数据可视化 Dify — 连接MySQL配置 Dify — Chatflow - 数据库智能查询 Dify — Chatflow - 文档知识库 Dify — Agent 智能体 高安全券码、注册码生成 Dify — 文本生成应用 Dify — 聊天助手 -- 知识库 Windows 下 Docker 安装 Dify Ollama — 命令 Ollama — 为什么能够运行不同的模型 Ollama — 接口 Qwen — 自定义模型文件 Ollama Qwen — 安装测试 Ollama Windows 安装 & 指定安装目录 跟着AI学AI - 诊断结论信息抽取 - 批量处理脚本 跟着AI学AI - 诊断结论信息抽取 - 模型评估与调试 跟着AI学AI - 诊断结论信息抽取 - 模型训练 轻型民用无人驾驶航空器安全操控理论考试培训材料 FreeRedis Helper Windbg w3wp.DMP 内存分析 跟着AI学AI - 诊断结论信息抽取 - 数据增强 QQ 录屏软件 Java - 加权随机算法 - 示例 Java - 加权随机算法--Demo Java LoadBalanceUtil 负载均衡、轮询加权 SpringBoot 集成 IP2Region 获取IP地域信息 Windows 服务器和虚拟主机,创建.开头的文件夹 .well-known 跟着AI学AI - 诊断结论信息抽取 - 数据格式转换BERT训练格式 跟着AI学AI - 诊断结论信息抽取 - LabelStudio 标注 -- 结论标注 跟着AI学AI - 诊断结论信息抽取 - 学习路径 跟着AI学AI - UV 安装 数据标注工具 Label-Studio `VIRTUAL_ENV=venv` does not match the project environment 跟着AI学AI - 学习路径 - 命名实体识别(NER)和信息抽取(IE) 跟着AI学AI - 需要购买显卡吗? C# 无BOM的UTF-8编码 Vue ref reactive Vue - el-table 嵌套表 Spring `@Scheduled` 中这些参数的区别、组合和应用场景 Python 找出同步日志中的重复数据 Python - UV PyCharm 不能识别 .venv 的环境
跟着AI学AI - 诊断结论信息抽取 - 模型压缩与部署
VipSoft · 2026-05-11 · via 博客园 - VipSoft
# 设置镜像源的环境变量
(vippython) PS D:\OpenSource\Python\VipPython> $env:HF_ENDPOINT = "https://hf-mirror.com"
# 添加依赖
(vippython) PS D:\OpenSource\Python\VipPython\information_extraction> uv add fastapi==0.136.1
(vippython) PS D:\OpenSource\Python\VipPython\information_extraction> uv add uvicorn==0.30.6
# 切换下目录,否则会报文件不存在
(vippython) PS D:\OpenSource\Python\VipPython> cd D:\OpenSource\Python\VipPython\information_extraction
(vippython) PS D:\OpenSource\Python\VipPython>  D:\OpenSource\Python\VipPython\.venv\Scripts\python.exe -c "import uvicorn; print(uvicorn.__version__)"
0.46.0
# 启动服务
(vippython) PS D:\OpenSource\Python\VipPython> uv run uvicorn ner_service:app --reload --host 0.0.0.0 --port 8001

debug_server.py -- PyCharm debug 没跑起来。说是有版本错误不搞了(见最后的图)

# debug_server.py - 新建一个调试入口文件
import uvicorn
from ner_service import app

if __name__ == "__main__":
    # 使用 reload=False 以便调试
    uvicorn.run(
        app,
        host="127.0.0.1",
        port=8000,
        reload=False,  # 设为False以便断点生效
        log_level="debug"
    )

ner_service.py

# ner_service.py - 修复后的版本
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List, Dict, Optional
import torch
from transformers import AutoTokenizer, AutoModelForTokenClassification
import json
import re
from datetime import datetime

app = FastAPI(title="心电图NER服务", description="心电图报告实体识别服务")


class ECGReport(BaseModel):
    text: str
    report_id: Optional[str] = None


class Entity(BaseModel):
    text: str
    label: str
    start: int
    end: int
    confidence: float


class NERResponse(BaseModel):
    report_id: str
    text: str
    entities: List[Entity]
    summary: Dict


class NERService:
    def __init__(self, model_path='./ecg_ner_model'):
        self.model_path = model_path
        self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
        self.load_model()

    def load_model(self):
        print(f"加载模型: {self.model_path}")
        self.tokenizer = AutoTokenizer.from_pretrained(self.model_path)
        self.model = AutoModelForTokenClassification.from_pretrained(self.model_path)
        self.model.to(self.device)
        self.model.eval()

        # 加载标签映射
        try:
            with open(f'{self.model_path}/id2label.json', 'r', encoding='utf-8') as f:
                self.id2label = json.load(f)
                self.id2label = {int(k): v for k, v in self.id2label.items()}
        except:
            # 如果没有模型,使用默认映射
            self.id2label = {0: "O", 1: "B-指标名称", 2: "I-指标名称"}

    def predict(self, text):
        """预测文本实体"""
        # 编码
        inputs = self.tokenizer(
            text,
            return_tensors="pt",
            truncation=True,
            max_length=256,
            padding=True
        )

        inputs = {k: v.to(self.device) for k, v in inputs.items()}

        # 预测
        with torch.no_grad():
            outputs = self.model(**inputs)
            predictions = torch.argmax(outputs.logits, dim=2)

        # 解码
        tokens = self.tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])
        predictions = predictions[0].cpu().numpy()

        entities = []
        current_entity = None

        for i, (token, pred_id) in enumerate(zip(tokens, predictions)):
            label = self.id2label.get(pred_id, "O")

            if label.startswith('B-'):
                if current_entity:
                    entities.append(current_entity)

                current_entity = {
                    'text': token.replace('##', ''),
                    'label': label[2:],
                    'start': i,
                    'end': i + 1,
                    'confidence': 1.0
                }

            elif label.startswith('I-') and current_entity:
                current_entity['text'] += token.replace('##', '')
                current_entity['end'] = i + 1

            elif label == 'O' and current_entity:
                entities.append(current_entity)
                current_entity = None

        if current_entity:
            entities.append(current_entity)

        return entities

    def extract_summary(self, entities):
        """提取报告摘要 - 修复版本"""
        summary = {}

        # 修复:正确处理Entity对象
        # 将Entity对象转换为字典列表以便处理
        entity_dicts = []
        for entity in entities:
            if hasattr(entity, 'dict'):
                # 如果是Pydantic模型
                entity_dicts.append(entity.dict())
            elif isinstance(entity, dict):
                # 如果是字典
                entity_dicts.append(entity)
            else:
                # 如果是其他对象,尝试转换为字典
                entity_dicts.append({
                    'text': getattr(entity, 'text', ''),
                    'label': getattr(entity, 'label', ''),
                    'start': getattr(entity, 'start', 0),
                    'end': getattr(entity, 'end', 0)
                })

        # 提取心率信息
        hr_info = {}
        for i, entity in enumerate(entity_dicts):
            if entity['label'] == '指标名称':
                if entity['text'] in ['平均心率', '最快心率', '最慢心率', '心率']:
                    # 查找对应的数值(下一个实体可能是数值)
                    if i + 1 < len(entity_dicts) and entity_dicts[i + 1]['label'] == '数值':
                        hr_info[entity['text']] = entity_dicts[i + 1]['text']
                    # 也可能数值在更后面的位置
                    else:
                        for j in range(i + 1, min(i + 5, len(entity_dicts))):
                            if entity_dicts[j]['label'] == '数值':
                                hr_info[entity['text']] = entity_dicts[j]['text']
                                break

        if hr_info:
            summary['心率'] = hr_info

        # 提取事件信息
        events = []
        event_labels = ['事件类型', '诊断结论', '事件子类']
        for entity in entity_dicts:
            if entity['label'] in event_labels:
                events.append(entity['text'])

        if events:
            summary['主要事件'] = list(set(events))  # 去重

        # 提取数值异常
        abnormalities = []
        heart_rate_value = None

        # 先找到心率数值
        for entity in entity_dicts:
            if entity['label'] == '数值':
                try:
                    value = float(entity['text'])
                    # 检查是否有前面的指标名称
                    idx = entity_dicts.index(entity)
                    if idx > 0 and entity_dicts[idx - 1]['label'] == '指标名称':
                        if '心率' in entity_dicts[idx - 1]['text']:
                            heart_rate_value = value
                            if value > 100:
                                abnormalities.append(f"心率过高({value}次/分)")
                            elif value < 60:
                                abnormalities.append(f"心率过低({value}次/分)")
                except:
                    pass

        if abnormalities:
            summary['异常提示'] = abnormalities

        # 添加实体统计
        entity_counts = {}
        for entity in entity_dicts:
            label = entity['label']
            entity_counts[label] = entity_counts.get(label, 0) + 1

        if entity_counts:
            summary['实体统计'] = entity_counts

        return summary


# 初始化服务
ner_service = NERService(model_path='./ecg_ner_model')  # 使用你训练好的模型路径


@app.post("/predict", response_model=NERResponse)
async def predict_entities(report: ECGReport):
    """预测实体"""
    try:
        # 预测
        entities_dict = ner_service.predict(report.text)

        # 转换为Entity对象列表
        entity_responses = [
            Entity(
                text=e['text'],
                label=e['label'],
                start=e['start'],
                end=e['end'],
                confidence=e.get('confidence', 1.0)
            )
            for e in entities_dict
        ]

        # 提取摘要
        summary = ner_service.extract_summary(entity_responses)

        return NERResponse(
            report_id=report.report_id or f"report_{int(datetime.now().timestamp())}",
            text=report.text,
            entities=entity_responses,
            summary=summary
        )

    except Exception as e:
        import traceback
        error_detail = traceback.format_exc()
        print(f"错误: {error_detail}")
        raise HTTPException(status_code=500, detail=str(e))


@app.get("/health")
async def health_check():
    """健康检查"""
    return {"status": "healthy", "model_loaded": True, "device": str(ner_service.device)}


@app.get("/test")
async def test_endpoint():
    """测试端点"""
    test_text = "平均心率为76次/分"
    entities = ner_service.predict(test_text)
    return {
        "test_text": test_text,
        "entities": entities,
        "entity_count": len(entities)
    }


# 运行命令: uvicorn ner_service:app --reload --host 0.0.0.0 --port 8001

# 如果直接运行这个文件
if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8001, reload=True)

启服务
image
image

Request Body

{
    "text": "平均心率为76次/分,最快心率是142次/分,发生于01-17 15:57:16,最慢心率是46次/分,发生01-18 07:57:21,其中心动过速事件(心率>100次/分),持续时间占总时间的1.3%,心动过缓事件(心率<60次/分),持续时间占总时间的4.0%. 室性早搏共发生2695次,占总心搏数的9.0%,包括.2695次单发室早.42次三联律. 诊断: 1、窦性心律(心率波动于46次/分--142次/分之间) 2、频发室性早搏(2695次单发室早.插入性室早.42次三联律) 3、心率变异性分析:SDNN 211.54(正常参考值范围:102-180ms),SDANN 139.41(正常参考值范围:92-162ms)"
}

Reponse Body

{
    "report_id": "report_1778489993",
    "text": "平均心率为76次/分,最快心率是142次/分,发生于01-17 15:57:16,最慢心率是46次/分,发生01-18 07:57:21,其中心动过速事件(心率>100次/分),持续时间占总时间的1.3%,心动过缓事件(心率<60次/分),持续时间占总时间的4.0%. 室性早搏共发生2695次,占总心搏数的9.0%,包括.2695次单发室早.42次三联律. 诊断: 1、窦性心律(心率波动于46次/分--142次/分之间) 2、频发室性早搏(2695次单发室早.插入性室早.42次三联律) 3、心率变异性分析:SDNN 211.54(正常参考值范围:102-180ms),SDANN 139.41(正常参考值范围:92-162ms)",
    "entities": [
        {
            "text": "平均心率",
            "label": "指标名称",
            "start": 1,
            "end": 5,
            "confidence": 1.0
        },
        {
            "text": "76",
            "label": "数值",
            "start": 6,
            "end": 7,
            "confidence": 1.0
        },
        {
            "text": "次/分",
            "label": "单位",
            "start": 7,
            "end": 10,
            "confidence": 1.0
        },
        {
            "text": "最快心率",
            "label": "指标名称",
            "start": 11,
            "end": 15,
            "confidence": 1.0
        },
        {
            "text": "142",
            "label": "数值",
            "start": 16,
            "end": 17,
            "confidence": 1.0
        },
        {
            "text": "次/分",
            "label": "单位",
            "start": 17,
            "end": 20,
            "confidence": 1.0
        },
        {
            "text": "01-1715:57:16",
            "label": "日期时间",
            "start": 24,
            "end": 32,
            "confidence": 1.0
        },
        {
            "text": "最慢心率",
            "label": "指标名称",
            "start": 33,
            "end": 37,
            "confidence": 1.0
        },
        {
            "text": "46",
            "label": "数值",
            "start": 38,
            "end": 39,
            "confidence": 1.0
        },
        {
            "text": "次/分",
            "label": "单位",
            "start": 39,
            "end": 42,
            "confidence": 1.0
        },
        {
            "text": "01-1807:57:21",
            "label": "日期时间",
            "start": 45,
            "end": 53,
            "confidence": 1.0
        },
        {
            "text": "心动过速事件",
            "label": "事件类型",
            "start": 56,
            "end": 62,
            "confidence": 1.0
        },
        {
            "text": "心率>100次/分",
            "label": "条件定义",
            "start": 63,
            "end": 70,
            "confidence": 1.0
        },
        {
            "text": "持续时间占总时间的1.3%",
            "label": "时间占比",
            "start": 72,
            "end": 85,
            "confidence": 1.0
        },
        {
            "text": "心动过缓事件",
            "label": "事件类型",
            "start": 86,
            "end": 92,
            "confidence": 1.0
        },
        {
            "text": "心率<60次/分",
            "label": "条件定义",
            "start": 93,
            "end": 100,
            "confidence": 1.0
        },
        {
            "text": "持续时间占总时间的4.0%",
            "label": "时间占比",
            "start": 102,
            "end": 115,
            "confidence": 1.0
        },
        {
            "text": "室性早搏",
            "label": "事件类型",
            "start": 116,
            "end": 120,
            "confidence": 1.0
        },
        {
            "text": "2695",
            "label": "数值",
            "start": 123,
            "end": 125,
            "confidence": 1.0
        },
        {
            "text": "次",
            "label": "单位",
            "start": 125,
            "end": 126,
            "confidence": 1.0
        },
        {
            "text": "占总心搏数的9.0%,",
            "label": "时间占比",
            "start": 127,
            "end": 138,
            "confidence": 1.0
        },
        {
            "text": "2695",
            "label": "数值",
            "start": 141,
            "end": 143,
            "confidence": 1.0
        },
        {
            "text": "次",
            "label": "单位",
            "start": 143,
            "end": 144,
            "confidence": 1.0
        },
        {
            "text": "单发室早",
            "label": "事件子类",
            "start": 144,
            "end": 148,
            "confidence": 1.0
        },
        {
            "text": "42",
            "label": "数值",
            "start": 149,
            "end": 150,
            "confidence": 1.0
        },
        {
            "text": "次",
            "label": "单位",
            "start": 150,
            "end": 151,
            "confidence": 1.0
        },
        {
            "text": "三联律",
            "label": "事件子类",
            "start": 151,
            "end": 154,
            "confidence": 1.0
        },
        {
            "text": "诊断",
            "label": "诊断类别",
            "start": 155,
            "end": 157,
            "confidence": 1.0
        },
        {
            "text": "窦性心律",
            "label": "诊断结论",
            "start": 160,
            "end": 164,
            "confidence": 1.0
        },
        {
            "text": "心率波动于46次/分--142次/分之间",
            "label": "数值范围",
            "start": 165,
            "end": 182,
            "confidence": 1.0
        },
        {
            "text": "频发室性早搏",
            "label": "诊断结论",
            "start": 185,
            "end": 191,
            "confidence": 1.0
        },
        {
            "text": "2695",
            "label": "数值",
            "start": 192,
            "end": 194,
            "confidence": 1.0
        },
        {
            "text": "次",
            "label": "单位",
            "start": 194,
            "end": 195,
            "confidence": 1.0
        },
        {
            "text": "单发室早",
            "label": "事件子类",
            "start": 195,
            "end": 199,
            "confidence": 1.0
        },
        {
            "text": "42",
            "label": "数值",
            "start": 206,
            "end": 207,
            "confidence": 1.0
        },
        {
            "text": "次",
            "label": "单位",
            "start": 207,
            "end": 208,
            "confidence": 1.0
        },
        {
            "text": "三联律",
            "label": "事件子类",
            "start": 208,
            "end": 211,
            "confidence": 1.0
        },
        {
            "text": "心率变异性分析",
            "label": "诊断结论",
            "start": 214,
            "end": 221,
            "confidence": 1.0
        },
        {
            "text": "[UNK]",
            "label": "指标名称",
            "start": 222,
            "end": 223,
            "confidence": 1.0
        },
        {
            "text": "211.54",
            "label": "数值",
            "start": 223,
            "end": 226,
            "confidence": 1.0
        },
        {
            "text": "正常参考值范围:102-180ms",
            "label": "条件定义",
            "start": 227,
            "end": 239,
            "confidence": 1.0
        },
        {
            "text": "[UNK]",
            "label": "指标名称",
            "start": 241,
            "end": 242,
            "confidence": 1.0
        },
        {
            "text": "139.41",
            "label": "数值",
            "start": 242,
            "end": 245,
            "confidence": 1.0
        },
        {
            "text": "正常参考值范围:92",
            "label": "条件定义",
            "start": 246,
            "end": 255,
            "confidence": 1.0
        }
    ],
    "summary": {
        "心率": {
            "平均心率": "76",
            "最快心率": "142",
            "最慢心率": "46"
        },
        "主要事件": [
            "心动过缓事件",
            "单发室早",
            "室性早搏",
            "心率变异性分析",
            "窦性心律",
            "三联律",
            "心动过速事件",
            "频发室性早搏"
        ],
        "异常提示": [
            "心率过高(142.0次/分)",
            "心率过低(46.0次/分)"
        ],
        "实体统计": {
            "指标名称": 5,
            "数值": 10,
            "单位": 8,
            "日期时间": 2,
            "事件类型": 3,
            "条件定义": 4,
            "时间占比": 3,
            "事件子类": 4,
            "诊断类别": 1,
            "诊断结论": 3,
            "数值范围": 1
        }
    }
}

注意: PyCharm Debug 运行的话,会存在版本冲突问题
f56b32493303629fa078a56fd8d77107