
简介面向自然语言处理与医学信息学研究者的中文电子病历命名实体识别数据集源自CCKS 2019评测任务共含1379例真实病历样本。每份样本包含原始文本与实体标注实体类型覆盖手术、解剖部位、药物、疾病和诊断、影像检查、实验室检验六类并给出实体类别与起止位置可直接用于医疗实体识别模型的训练与评测。压缩包共8个文件以txt训练文本、xlsx测试表格、json标注答案及docx任务说明为主整体仅1.18MB轻量易用。目前已有1400余人学习参考适合入门医疗NLP或参加同类评测的开发者快速获取标准数据开展模型训练与效果对比。1. 一个 .rar 背后的中文医学 NLP 起点CCKS 2019 中文电子病历数据集是中文医疗信息抽取里绕不开的评测语料整个包以 .rar 形式分发打开后是一批脱敏电子病历文本和对应的实体、关系标注。很多 NLP 工程师第一次接触医疗领域第一反应是解压直接跑 BERT结果在 rar 解压和标注解析上就耗掉半天。这篇按解压、解析、基线和验证的顺序把从压缩包到可训练样本的完整链路过一遍。内容只依赖压缩包本身的信息不需要去对接医院系统也不需要额外申请接口。适合刚入行中文医疗 NLP 的算法工程师也适合有通用 NER 经验、想了解电子病历标注结构的人做迁移参考。2. 从 rar 解压到文件完整校验命令、编码与密码坑2.1 为什么医疗文本数据经常用 RAR 分发这类评测数据用 .rar 而不是 .zip 的原因不复杂RAR 对大量中文小文件的压缩率更高支持分卷、恢复记录和文件名编码指定。电子病历数据集由几百个 txt 文件组成文本重复度高RAR 的压缩算法通常能压到原始体积的三分之一以下。另一个现实原因是出题方当初在 Windows 环境打包WinRAR 是默认工具所以“CCKS 2019 中文电子病历数据集.rar”这个文件名才会普遍流传。拿到包之后不要用 unzip 硬解先确认系统里有没有支持 RAR 的解压工具。Linux 服务器经常只装了 zip直接执行 unzip 会报unknown file type容易误判文件损坏。2.2 用 unar 解压最少依赖的命令行方式在 Linux 服务器上常见做法是安装 unar 而不是 rar 命令行。unar 基于 libarchive对非 UTF-8 文件名编码的兼容性比 rar 好能自动处理中文文件名不会解出一堆乱码目录。安装完成后先列出压缩包内容lsar CCKS 2019 中文电子病历数据集.rar这个命令只列目录不解压输出里会显示压缩包内的文件路径。看到中文文件名后再执行真正解压unar -e GB18030 -o ./ccks2019_ehr CCKS 2019 中文电子病历数据集.rar-e GB18030让 unar 用 GB18030 解码文件名-o指定输出目录-f可以在目标目录非空时强制覆盖。如果文件名本来就是 UTF-8 编码-e参数可以省略建议先用lsar输出判断避免解压出乱码目录。如果服务器上已经装了官方 unrar也可以用unrar xunrar x CCKS 2019 中文电子病历数据集.rar ./ccks2019_ehr/注意unrar x保留压缩包内目录结构unrar e会把所有文件平铺到当前目录。电子病历数据通常按编号文件组织平铺后同名文件容易互相覆盖不建议对多文件语料使用e参数。Windows 用户可以用 7-Zip 打开 RAR但 7-Zip 对 GBK 文件名的处理不如 unar 稳定解压后如果出现乱码再换 unar 重试。工具命令示例适用场景unarunar -e GB18030 -o out file.rarLinux 下中文文件名兼容最好unrarunrar x file.rar官方格式支持完整可做完整性测试7-Zip右键7-Zip打开压缩包Windows 快速查看和部分解压rarrar x file.rar需要创建分卷或加密包时使用2.3 遇到密码和损坏包先确认来源再谈破解数据集压缩包有时会带密码尤其从网盘或群聊转发的版本。命令行解压遇到密码会提示Enter password手动输入即可。如果不知道密码正确做法是回到原始发布页确认密码是否公开不要第一时间想到 rar password cracker 或“rar 密码移除”这类工具。这类工具的原理是逐字尝试候选口令针对用 AES-256 加密的 RAR5 包速度取决于 GPU 和字典普通文件可能要跑几天。更重要的是评测数据通常不是强加密设密码只是为了限制传播真正的授权要联系发布组织。团队协作时我一般会先检查压缩包是不是分卷格式比如文件名末尾带.part1.rar此时必须用rar x而不是unrar x前者会按顺序读取后续分卷。解压时报Unexpected end of archive多半是传输过程中文件被截断。这时先重新下载不要尝试修复工具# 网络差时用 rsync 断点续传比 wget 重来更实际 rsync -avP userhost:/path/CCKS*.rar .2.4 解压后的完整性验证解压完成后一个容易被忽略的步骤是校验文件数量和大小。RAR 包里的文件列表可能与实际解压结果不一致常见原因是磁盘空间不足或权限问题。先用lsar记录预期数量再对比实际目录lsar CCKS 2019 中文电子病历数据集.rar | grep -c \.txt$ find ./ccks2019_ehr -name *.txt | wc -l如果两个数字不一致优先检查解压过程是否有 I/O 错误。更严格的做法是用unrar t测试压缩包完整性它会对每个文件做 CRC 校验unrar t CCKS 2019 中文电子病历数据集.rar输出中OK表示文件校验无误CRC failed说明源文件损坏。到这里rar 解压的流程才算完整走通接下来可以放心解析数据。3. 解析电子病历标注从原始 txt 到 BIO 序列3.1 理解压缩包里的两类文件解压后常见目录结构大概是这个样子ccks2019_ehr/ ├── 原始病历/ │ ├── 0001.txt │ ├── 0002.txt └── 标注数据/ ├── 实体标注/ │ ├── 0001.txt └── 关系标注/ ├── 0001.txt这里的“原始病历”是一段没有标签的出院小结或入院记录“实体标注”按字符位置记录实体。CCKS 2019 电子病历评测里实体类型常见为身体部位、症状、疾病、检查、治疗但具体标签名要解压后看不要假设所有版本一致。关系标注则记录头实体、尾实体和关系类型文件结构和实体标注完全不同。先打开一个文件看前 30 行比直接写解析器更稳妥head -30 ./ccks2019_ehr/标注数据/实体标注/0001.txt这一步能确认分隔符是 Tab、空格还是逗号有没有空行。BIO 文件通常以空行分隔句子每行两列字符和标签。偏移量文件则像下面这样急性阑尾炎 病名 5 9意思是原文第 5 到第 9 个字符是“急性阑尾炎”这个实体。这里的偏移量是字符计数还是字节计数需要验证中文在 UTF-8 里占三个字节如果按字节标注解析前要先编码再定位。3.2 把偏移量标注转成 BIO 序列写一个通用转换函数输入原文和实体列表输出字符级 BIO 标签。第一版不考虑嵌套实体只处理非重叠标注def offsets_to_bio(text, entities): chars list(text) tags [O] * len(chars) for start, end, etype in entities: # 防止越界和标签重叠 if start 0 or end len(chars): continue if tags[start] ! O: continue tags[start] B- etype for i in range(start 1, end): tags[i] I- etype return list(zip(chars, tags))这个函数有两个设计点先检查tags[start]遇到重叠实体时保留先出现的避免B-被后续覆盖成I-导致标签序列不合法。电子病历标注容易在长实体内部出现子实体比如“左肺上叶”是身体部位“左肺上叶结节”是症状两者位置重叠。第一版把这种冲突直接跳过等后续根据评测要求决定是否支持嵌套。3.3 BIO 文件解析与标签集对齐如果是逐行 BIO 文件解析逻辑更直接def load_bio(path): samples [] chars, tags [], [] with open(path, encodingutf-8) as f: for line in f: line line.rstrip(\n) if not line: if chars: samples.append((chars, tags)) chars, tags [], [] continue char, tag line.split(\t) chars.append(char) tags.append(tag) if chars: samples.append((chars, tags)) return samples解析后做两件事打印所有出现的标签类型并检查是否只有O、B-、I-三种前缀。from collections import Counter label_counter Counter(tag for _, tags in samples for tag in tags) print(label_counter)如果出现S-或E-前缀说明原数据用的是 BIOES 标签可以直接保留也可以在预处理阶段统一映射成 BIO。对 BERT 类模型来说BIOES 和 BIO 的 F1 差距很小关键是训练和预测用同一套标签体系。3.4 统计实体长度和类别分布拿到标签后第一版统计能快速发现数据质量问题。实体类别不平衡在电子病历中很常见症状和检查类实体可能占多数治疗类很少。统计每个类别的实体数量、平均长度以及重复实体占比def count_entities(samples): cnt Counter() len_sum Counter() for chars, tags in samples: current None start 0 for i, tag in enumerate(tags): if tag.startswith(B-): current tag[2:] start i elif tag.startswith(I-) and current tag[2:]: pass else: if current: cnt[current] 1 len_sum[current] i - start current None return {k: (cnt[k], len_sum[k] / cnt[k]) for k in cnt}这段代码在标签从I-跳回O时结算实体。实测中常见的问题是B-后跟了不同类型的I-比如B-symptom后面跟I-disease这时统计代码会忽略这段但模型训练时仍会按标签序列计算损失。最好在预处理阶段就把这种非法序列修正成O避免模型学到错误边界。4. 用 BERT 做电子病历 NER 基线参数设置与长文本策略4.1 为什么先选 BERT 而不是直接换医疗模型对中文电子病历数据第一个可跑通的基线通常用 BERT-base-Chinese。模型在通用中文语料预训练医学术语覆盖一般但电子病历句式短、上下文集中BERT 的字向量能提供稳定起点。相比更大的预训练模型它的显存占用和推理速度更适合先验证标签设计是否正确。这不等同于直接跳过词法特征。电子病历里“右肺上叶”这类嵌套器官表达BERT 能学到部分边界信息但训练数据太少时依然会切错。所以第二步再换成 RoBERTa-wwm-ext 或医疗领域预训练模型对比实体级别 F1。先把基线的标签映射准备好label_list sorted({tag for _, tags in samples for tag in tags}) label2id {tag: i for i, tag in enumerate(label_list)} id2label {i: tag for tag, i in label2id.items()}标签映射必须保证训练和预测共用一份否则推理时会出现标签错位尤其在多进程分布式训练时每个进程都要加载同一个映射文件。4.2 把字符级标签对齐到 BERT token用 Transformers 加载分词器后把字符标签对齐到模型输入 token。这是电子病历 NER 最容易出 bug 的地方。BERT-base-Chinese 基本按字切分但词表里仍存在少量多字 token不能假设全部是单字。from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(bert-base-chinese) def tokenize_and_align(sample): text .join(sample[chars]) char_tags sample[tags] encoding tokenizer( text, truncationTrue, max_length256, return_offsets_mappingTrue ) labels [-100] * len(encoding[input_ids]) for idx, offset in enumerate(encoding[offset_mapping]): if offset (0, 0): continue char_idx offset[0] if char_idx len(char_tags): labels[idx] label2id[char_tags[char_idx]] encoding[labels] labels return encoding-100是 PyTorch 交叉熵损失函数默认忽略的标签把[CLS]、[SEP]和子词切分产生的额外 token 都设成 -100。这里对多字 token 取了第一个字符的标签对中文字粒度的 BERT 来说基本够用。如果后续换成带自定义词表的模型要改成取最后一个字符的标签或者对 token 内部不同的标签做投票。4.3 Trainer 训练与超参数选择用 Trainer 跑微调的代码量最少但参数要按电子病历短文本的特点调整from transformers import AutoModelForTokenClassification, Trainer, TrainingArguments model AutoModelForTokenClassification.from_pretrained( bert-base-chinese, num_labelslen(label2id), id2labelid2label, label2idlabel2id ) args TrainingArguments( output_dir./ehr_ner, learning_rate3e-5, per_device_train_batch_size16, per_device_eval_batch_size32, gradient_accumulation_steps2, num_train_epochs5, evaluation_strategyepoch, save_strategyepoch, weight_decay0.01, warmup_ratio0.1, logging_steps50, fp16True ) trainer Trainer( modelmodel, argsargs, train_datasettrain_dataset, eval_dataseteval_dataset, ) trainer.train()电子病历 NER 常用的学习率范围是 2e-5 到 5e-5。数据集只有几千条句子时5 个 epoch 容易过拟合建议结合验证集 F1 做早停或者把 epoch 降到 3。gradient_accumulation_steps在显存不足时等价于增大 batch size但会拖慢收敛速度调大后要同步调低学习率。fp16需要 GPU 支持 AMP否则训练速度反而下降。评估指标不要用准确率电子病历中绝大多数 token 是O准确率会被背景类拉高。要计算实体级别的 Precision、Recall、F1先把模型输出还原成(实体类型, 起始位置, 结束位置)的片段再与标注做集合匹配。这部分可以用seqeval库它直接接受字符串标签列表不需要手动转换。4.4 长文本截断策略电子病历的出院小结经常超过 2000 字直接截断到 256 会丢掉大量实体。常见做法是滑窗把原文切成长度 240 的块相邻块之间保留 80 字重叠对每块独立预测最后合并时按重叠区间的实体去重。def sliding_window(text, window240, step160): for start in range(0, len(text), step): end min(start window, len(text)) yield text[start:end] if end len(text): break滑窗重叠的副作用是同一个实体可能在多个窗口出现合并时按“起始位置优先”的规则保留。更稳妥的方法是预测结果先映射回原文位置再做重叠区间合并。电子病历中相同实体重复出现很常见所以不能简单去重要同时考虑实体类别和具体位置。5. 数据质量验证与一个增强技巧5.1 用一致性检查抓标注错误解析完数据后先对训练集和验证集做一套交叉一致性检查能避免模型在脏标注上白跑。最简单的是检查同一实体在相同上下文里是否出现不同标签。电子病历中“肺炎”有时标成疾病有时标成症状这种歧义如果比例过高训练时的梯度会反复横跳。context_counter {} for chars, tags in train_samples eval_samples: text .join(chars) for tag, span in extract_entities(chars, tags): key (text[max(0, span[0]-2):span[1]2], span[2]) context_counter.setdefault(key, set()).add(tag)这里extract_entities是把 BIO 标签还原成实体列表的函数。统计后发现同一上下文对应多个类型时优先按训练集众数修正验证集而不是反过来。如果 train 和 eval 的风格明显不一致说明划分方式有问题。5.2 用 K 折验证看数据分布电子病历数据集通常按病例编号划分但一个患者多条记录会同时出现在训练集和验证集里导致实体重复泄漏。用StratifiedKFold按患者 ID 分组折叠能看到标注风格差异对 F1 的影响。这个值直接决定你做的模型是真正学到了实体边界还是记住了训练集里的病例措辞。5.3 把处理后的语料重新打成 rar预处理完成后把转换出的 BIO 数据和标签映射打包备份方便后续复现。重新压缩时建议保留目录结构并增加恢复记录rar a -ep1 -rr10% -ver -s ccks2019_ehr_processed.rar 原始病历/ 标注数据/-rr10%添加 10% 的恢复记录能在网络传输损坏时尝试修复-s开启固实压缩对大量小体积 txt 能提高压缩率。这样备份下来的包既是可复现实验的原始凭证也能在换机器时快速迁移不需要重新解压和解析一遍。对后续接手的人来说一个带内部目录和标签映射的 rar 包比零散文件更不容易出错。本文还有配套的精品资源点击获取