尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CCKS2019中文命名实体识别:从数据集解压到BERT+CRF实战

CCKS2019中文命名实体识别:从数据集解压到BERT+CRF实战 简介自然语言处理中命名实体识别是从非结构化文本中抽取实体信息的关键技术尤其在医疗领域中文电子病历的实体识别对辅助诊断和知识图谱构建具有重要意义。常见技术路线包括基于统计的条件随机场和基于深度学习的预训练模型。BERT通过动态上下文编码提升语义理解CRF则约束标签序列的合法性二者结合能有效应对中文医疗文本中实体边界模糊、嵌套等难点。针对CCKS2019评测任务提供的中文电子病历数据集从zip压缩包的解压、编码处理、数据清洗到BIOES标签对齐再到模型训练与评估存在一系列影响最终F1值的工程细节。掌握该数据集的完整处理流程不仅有助于复现竞赛结果也能为医疗NLP落地项目提供可复用的实践参考。1. CCKS2019中文命名实体识别任务到底在做什么如果你手头有一个叫CCKS2019中文命名实体识别任务.zip的文件多半是准备参加或者复现当年CCKS 2019评测任务中的中文命名实体识别项目。这个压缩包本身就是竞赛官方打包好的数据集和任务说明里面包含训练集、验证集、测试集以及评测脚本目的是让大家在统一的数据划分和评测标准下训练一个能从中文文本中自动识别出指定实体类别的模型。CCKS全国知识图谱与语义计算大会是国内自然语言处理领域非常权威的学术会议每年都会设置多个评测任务。2019年的中文命名实体识别任务核心设定是面向中文电子病历的实体识别。为什么要选电子病历因为医疗文本和新闻文本差别很大病历里有大量口语化的症状描述、药物名称、检查缩写实体边界模糊、嵌套情况多加上中文本身没有天然空格分词这让NER任务变得很有挑战性。官方把数据整理成一个zip包发布本身就是为了降低参赛门槛——你不需要自己爬数据解压之后就能直接开始做模型。这个压缩包适合谁三类人准备参加CCKS系列评测的学生或研究人员想通过这个任务熟悉评测流程做中文医疗NLP落地的工程师想拿一份标准医疗NER数据练手刚接触NER的新人想找一份带标准答案的公开数据来跑通BERTCRF这条经典路线无论你是哪一类拿到zip只是第一步真正值钱的是你从解压到训练的一整套流程经验。这篇就把从数据格式、标签体系到模型训练的完整路径讲清楚顺便把zip解压过程中常见的坑也一并处理掉。2. 数据包内容整体设计与解构2.1 压缩包内部结构解开zip之后目录结构通常长这样CCKS2019_NER/ ├── README.md ├── ccks2019_ner_train.txt ├── ccks2019_ner_dev.txt ├── ccks2019_ner_test.txt └── eval/ └── evaluate.py有些渠道下载的版本会略有差异比如多了data/子目录、origin/原始文本目录但核心永远是那三个数据文件和评测脚本。README里写着任务说明、实体类别定义、标注规范和提交格式要求我建议先读README再动数据很多人一上来就忙着看数据结果把实体类别搞错后面全白干。训练集一般有800到1000篇左右的病历文本每篇是单独的段落句与句之间有空行隔开。验证集和测试集比例大约为8:1:1。这个数据量对深度学习模型来说不算大所以当年榜单上的好成绩基本都用了预训练语言模型做迁移学习纯从头训练的BiLSTM-CRF最高也就做到80出头的F1。评测脚本是evaluate.py它读取你预测的结果文件和标准答案文件计算实体级别的精确率、召回率和F1值。注意脚本默认用的是严格匹配预测的实体文本边界、实体类型都必须和标准答案完全一致才算对差一个字都不行。这个细节很重要因为很多初学者在本地用宽松匹配自测感觉分数很高一提交就掉好几个点就是标准没对齐。2.2 实体类别和标注体系这个任务定义了四类医疗实体实体类型类别标签举例疾病和诊断disease高血压、2型糖尿病、冠心病症状和体征symptom头痛、发热、双下肢水肿检查和检验check血常规、胸部CT、心电图治疗treatment阿司匹林、静脉输液、支架植入标注格式是经典的一字一标签BIOES体系。每行格式是患 O 者 O 出 B-symptom 现 I-symptom 头 I-symptom 痛 E-symptom OB表示实体开始I表示实体中间E表示实体结束S表示单个字成实体O表示非实体。注意标签里类别和位置是连在一起的比如B-symptom和I-disease所以模型要预测的标签数量是4类实体×3种位置B/I/E因为S也可以用BE组合替代 O一共13个标签。用BIOES而不是简单的BIO好处是能更清晰地表示实体边界对训练CRF层有帮助解码时也更容易避免B后接I但类别不一致的非法转移。2.3 数据里那些看不见的坑光看格式还不够实际跑数据时你会碰见几个麻烦第一标签不一致。标注是人工做的难免有漏标或错标。如果你用脚本统计标签分布会发现极少数I-标签前面的字不是B-或I-同类型这就是标注噪声。遇到这种情况要么在预处理时清洗掉要么让模型去鲁棒地学不要因为一两个脏标签就怀疑整个数据集。第二实体重叠。病历里存在高血压性心脏病这种复合实体标准答案只标了disease但如果你用词典去匹配可能会同时匹配出高血压和心脏病两个实体。评测只看官方标注所以你的模型要学会输出和标注一致的结果而不是更正确的结果。第三中文标点。病历文本经常出现全角逗号、句号、括号预处理时不要无脑转半角因为全角标点本身是天然的分隔特征。我见过有人把所有标点替换成空格结果实体边界反而更难学。3. 从zip到可用数据解压与预处理实操3.1 不同系统下的解压方案拿到zip第一步当然是解压但这里就有不少门道。你下载下来的是CCKS2019中文命名实体识别任务.zip很可能是在Windows上下载的然后传到Linux服务器上训练。这个场景是最容易出问题的。Windows下直接右键解压没什么好说的。但如果你在Linux服务器上解压命令是unzip CCKS2019中文命名实体识别任务.zip -d ccks2019_ner如果unzip没安装先装# Ubuntu/Debian sudo apt-get install unzip # CentOS/RHEL sudo yum install unzip解压后第一件事是执行file命令看看文件类型是不是纯文本确认编码格式file ccks2019_ner_train.txt正常会输出UTF-8 Unicode text。如果显示ISO-8859或者带with BOM说明编码有问题需要转码。中文数据最怕的就是编码混乱前面几个字变成乱码后面所有标签对齐全错。3.2 中文文件名乱码怎么处理Windows压缩的中文文件名在Linux下解压经常出现乱码因为zip默认不强制指定文件名编码Windows用GBKLinux解压时按UTF-8解析结果就是一堆锟斤拷或者问号。处理办法有两个第一个解压时指定编码unzip -O CP936 CCKS2019中文命名实体识别任务.zip -d ccks2019_ner-O CP936的意思是让unzip按GBK编码解析文件名。不过这个参数不是所有unzip版本都支持GNU unzip 6.0以上才带。第二个用Python脚本解压并自动改名这个最稳import zipfile import os zip_path CCKS2019中文命名实体识别任务.zip extract_dir ccks2019_ner with zipfile.ZipFile(zip_path, r) as zf: for info in zf.infolist(): # 尝试用GBK解码文件名再转成UTF-8 try: decoded_name info.filename.encode(cp437).decode(gbk) except (UnicodeDecodeError, UnicodeEncodeError): decoded_name info.filename target_path os.path.join(extract_dir, decoded_name) os.makedirs(os.path.dirname(target_path), exist_okTrue) with zf.open(info) as src, open(target_path, wb) as dst: dst.write(src.read())这个脚本的思路是zipfile模块读取文件名时默认按CP437编码如果原文件名是GBK编码就会变成乱码字符串所以先encode(cp437)把原始字节拿回来再decode(gbk)还原成正确的中文。这是一个在处理中文zip时非常实用的小技巧不止适用于这个数据包任何中文zip都能用。3.3 file is not a zip file和EOCD错误真实场景里最常见的坑是解压时报错Archive: CCKS2019中文命名实体识别任务.zip End-of-central-directory signature not found. Either this file is not a zipfile, or it constitutes one disk of a multi-part archive.或者Python报错zipfile.BadZipFile: File is not a zip file这个报错的意思是在zip文件末尾找不到中央目录End of Central Directory简称EOCD的签名。zip的中央目录在文件末尾相当于整本书的目录页在最后一页。如果文件下载不完整、传输过程被截断、或者实际上是别的格式但改了个zip后缀名都会触发这个错误。排查步骤第一步先看文件真实类型file CCKS2019中文命名实体识别任务.zip如果输出是HTML document或者gzip compressed data说明你下载到的根本不是zip可能是下载链接跳转到了错误页面或者服务器返回了协议错误。别怀疑就得重新下载。第二步如果文件确实是zip但提示损坏用zip -FF修复cp CCKS2019中文命名实体识别任务.zip damaged.zip zip -FF damaged.zip --out fixed.zip unzip -l fixed.zip-FF是尝试从损坏的zip中恢复文件它会把能找到的本地文件头一个个扫出来重建中央目录。效果看运气如果只是尾部几十个字节损坏基本能救回来如果中间数据损坏那个文件就废了。第三步检查磁盘空间。解压到一半报错write error或者no space left on device那是磁盘满了清一下/tmp或者换个大目录别以为是压缩包的问题。3.4 数据格式转换与预处理解压出来的原始标注文件不能直接丢给模型训练因为BERT类的预训练模型需要的是token id加label id。常规预处理流程def load_ner_data(file_path): sentences [] labels [] current_sent [] current_labels [] with open(file_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: if current_sent: sentences.append(current_sent) labels.append(current_labels) current_sent [] current_labels [] continue parts line.split() if len(parts) ! 2: continue char, label parts[0], parts[1] current_sent.append(char) current_labels.append(label) if current_sent: sentences.append(current_sent) labels.append(current_labels) return sentences, labels这里有个细节值得注意原始文件每行是字 空格 标签但有些版本可能是Tab分隔有些可能多了行号。不要硬编码按空格split先看三行数据确认格式。另外有些句子中会混入\u3000全角空格strip()只能去掉半角空格遇到全角空格要在split前先replace(\u3000, )。4. 中文NER模型的选型与训练4.1 为什么首选BERTCRF这个任务的数据量决定了模型选型。1000篇左右的病历数据如果只用Word2vec初始化的BiLSTM-CRFF1基本在80到83之间。用BERT中文预训练模型做特征抽取再加一层CRF做序列解码F1可以到87到89。差距接近5到6个点在评测任务里这是决定能不能进前十的分水岭。BERT模型在NER中的角色是更聪明的文本理解器。传统的词向量是静态的苹果这个单词不论在苹果手机还是苹果很好吃里向量都一样无法区分语境。BERT是动态的每个字的向量都会根据上下文变化苹果在不同句子里得到不同的表示。这个特性对中文医疗NER特别重要因为医疗实体大量依赖上下文判断类别阿司匹林单独看是药但在阿司匹林抵抗这个短语里它其实是疾病诊断中的检查概念。CRF层的作用是约束标签序列的合法性。单独用BERT加softmax模型可能预测出B-symptom后面接I-disease这种非法序列因为softmax是对每个位置独立预测的。CRF引入转移概率让模型学到B后面只能接I或E、类型必须一致这些规则这在实体识别里几乎是标配。4.2 数据处理与Tokenizer用HuggingFace Transformers库训练核心代码框架如下from transformers import BertTokenizer, BertForTokenClassification, Trainer, TrainingArguments import torch label_list [O, B-disease, I-disease, E-disease, S-disease, B-symptom, I-symptom, E-symptom, S-symptom, B-check, I-check, E-check, S-check, B-treatment, I-treatment, E-treatment, S-treatment] label2id {l: i for i, l in enumerate(label_list)} id2label {i: l for l, i in label2id.items()} tokenizer BertTokenizer.from_pretrained(bert-base-chinese) model BertForTokenClassification.from_pretrained( bert-base-chinese, num_labelslen(label_list), id2labelid2label, label2idlabel2id )这里最容易出错的是token和label的对齐问题。BERT中文模型用的是字级tokenizer绝大部分字是一个token但遇到[UNK]或者特殊字符时一个原始字符可能被拆成多个token或者被丢弃。如果label还是按原始字符长度制作必然错位。解决办法是训练前逐句做entity-level的验证确保每个token的attention_mask和label长度一致。我习惯在Dataset.map函数里加一个断言def tokenize_and_align_labels(examples): tokenized_inputs tokenizer( examples[tokens], truncationTrue, max_length256, is_split_into_wordsTrue ) labels [] for i, label in enumerate(examples[ner_tags]): word_ids tokenized_inputs.word_ids(batch_indexi) previous_word_idx None label_ids [] for word_idx in word_ids: if word_idx is None: label_ids.append(-100) elif word_idx ! previous_word_idx: label_ids.append(label[word_idx]) # 如果模型拆分了字后续subtoken全部标记为-100忽略 else: label_ids.append(-100) previous_word_idx word_idx labels.append(label_ids) tokenized_inputs[labels] labels return tokenized_inputs-100是PyTorch CrossEntropyLoss默认的忽略索引用它标注非实体片段和subtokenloss计算时自动跳过这些位置。很多复现失败的原因都在这里label不对齐训练loss不下降或者预测全乱。4.3 训练参数与评估策略医疗NER数据量不大训练参数设置相对固定training_args TrainingArguments( output_dir./ner_ckpt, evaluation_strategyepoch, save_strategyepoch, learning_rate2e-5, per_device_train_batch_size16, per_device_eval_batch_size32, num_train_epochs10, weight_decay0.01, load_best_model_at_endTrue, metric_for_best_modelf1, logging_dir./logs )学习率用2e-5到5e-5之间太大容易微调过头导致灾难性遗忘太小收敛太慢。epoch设10到15不要死板用early stopping更好因为数据小模型比较容易过拟合第5个epoch可能就开始在验证集上掉点了。批大小受显存限制16到32都行。评估时候要用官方脚本的口径实体级别的F1不是token级别的accuracy。写出正确的评估代码def compute_metrics(p): predictions, labels p predictions np.argmax(predictions, axis-1) true_entities extract_entities(labels, id2label) pred_entities extract_entities(predictions, id2label) correct len(true_entities pred_entities) precision correct / len(pred_entities) if pred_entities else 0 recall correct / len(true_entities) if true_entities else 0 f1 2 * precision * recall / (precision recall) if (precision recall) else 0 return {precision: precision, recall: recall, f1: f1}extract_entities需要把BIOES序列还原成(实体类型, 实体文本, 起始位置, 结束位置)的集合用set做交集判断。这个逻辑说起来简单写的时候有一堆边界情况空实体、连续实体、单字实体。最稳妥的方式是把实体还原成字级别的位置序号比如(disease, 3, 5)表示第3到第5个token组成一个disease实体然后比较集合。4.4 从训练到预测的完整流程训练完成后预测阶段有个容易忽略的坑测试集没有标准标签你需要用一个单独的predict脚本让模型输出实体列表然后格式化提交。官方要求提交格式一般是每行一个实体字段为篇章ID 起始位置 结束位置 实体类型 实体文本。预测时的细节from transformers import pipeline ner_pipeline pipeline( token-classification, modelner_ckpt/best_model, tokenizerbert-base-chinese, aggregation_strategyfirst ) results ner_pipeline(患者因高血压伴头痛入院治疗) for entity in results: print(entity)aggregation_strategyfirst会自动把连续的B-I-E标签合并成一个完整实体输出实体文本和置信度。这个参数在英文NER里表现很好但中文场景下如果实体内部有标点可能会被拆开需要自己写合并逻辑。我实测下来对于病历这种短文本官方pipeline够用但如果你追求稳定还是自己写解码函数遍历预测标签序列根据BIOES规则做合并逻辑最透明出问题也好排查。5. 常见问题与排查技巧实录5.1 zip问题速查表现象原因处理方式解压失败file is not a zip file下载不完整或文件格式不对用file命令检查真实类型重新下载解压报错could not find eocdzip末尾中央目录损坏zip -FF damaged.zip --out fixed.zip修复中文文件名乱码Windows用GBKLinux按UTF-8解析unzip -O CP936或Python脚本转码分卷文件z01无法解压分卷zip缺少主文件或顺序不对确认所有分卷在同一目录从.zip主文件解压解压后数据文件是乱码文本编码非UTF-8用iconv -f GBK -t UTF-8转码解压到一半报磁盘满磁盘空间不足清理磁盘换目录重试5.2 文件损坏的救急方案说一个真实场景。我在一台老服务器上远程下载这个数据包断点续传打得不完整unzip死活用不了。当时急着跑实验用jar命令试了一下jar xf damaged.zipJDK自带的jar命令对zip的容错比unzip稍好一点居然解出了一部分文件。再把解出来的部分和重新下载的完好文件做合并勉强能继续用。当然这不是正路应急可以最终还是要重新下载完整文件。另外一个更稳的办法是用wget -c断点续传wget -c 下载链接 -O CCKS2019中文命名实体识别任务.zip-c参数支持从上次断掉的位置继续下载比重新下载整个文件省时间。下载完后立刻unzip -t测试完整性unzip -t CCKS2019中文命名实体识别任务.zip如果输出No errors detected in compressed data of CCKS2019中文命名实体识别任务.zip才说明文件真的完整。5.3 训练中的常见报错与对策错误1CUDA out of memory医疗BERT模型加CRF层对显存要求不低尤其是序列长度拉长后。对策降低batch_size到8或4或者把max_length从256降到128。病历文本通常都不会太长实体一般集中在几十个字之内128完全够用。错误2label length mismatchTokenization时token和label没有对齐。重新检查word_ids的用法打印一条样本的token和label对应关系肉眼对比一遍再跑。错误3验证集F1一直为0最常见原因是标签映射写错了比如id2label的字典键值反了。另一种可能是extract_entities函数里对BIOES的解析逻辑有bug导致永远匹配不上。调试建议先拿训练集前100条做一次拿标准答案当预测的测试如果这样算出来F1不是100%说明评估代码肯定有bug。错误4所有预测结果都是O模型没学到实体模式。检查标签是否对齐是一个方面另一个可能是学习率太大模型训练发散。把learning_rate降到2e-5重新训练。如果还是不行看看训练集的实体占比——如果实体占比极低O类占了绝大多数loss被O主导模型会倾向于全部输出O。可以给非O类标签加权或者用focal loss。5.4 关于数据版权和后续实验的提醒CCKS官方的数据集都是通过评测任务发布的一般只允许用于学术研究和评测不能随意传播或者商用。你从某个渠道下载的zip如果只包含数据没有官方README也建议去CCKS官网确认一下授权范围。我自己做复现实验时都会在自己的项目README里写清楚数据来源和仅限研究用途这个习惯建议大家养成。做完这个任务之后数据本身还能发挥更大价值。比如用训练好的模型去标注你自己的医疗文本做迁移学习或者把病历实体识别结果链接到知识图谱做关系抽取的前置步骤。这些都算这个zip之外的自然延展能帮你把这个数据集的价值吃干净。6. 一些实际操作中的心得最后分享几个我在跑这个任务过程中积累的经验纯粹个人体会。第一评测任务的分数永远以官方脚本为准。本地自己写的评估脚本无论多严谨都有可能因为边界处理、实体对齐逻辑和官方不一致而产生偏差。拿到zip之后先不要急着跑模型先把官方评测脚本跑通用训练集前几条数据做一个预测标准答案的自测确认脚本能正常输出100分这相当于给自己设了个校准基线。后面模型跑出来的分数才是可信的。第二中文医疗NER的难点很大程度在数据预处理和标签对齐上而不是模型网络结构。BERTCRF这套组合已经是被验证过无数次的成熟方案模型部分照搬就行真正决定你能拿到多少分的是数据清洗、标签体系理解、解码细节这些工程活。我见过不少人反复调整模型加各种attention花样分数纹丝不动结果把标签对齐修好之后直接涨了2个点。第三zip解压这些看起来不起眼的问题反而最容易浪费时间。如果是比赛前期熬夜下载完数据集发现解压不出来心态直接崩。我现在的习惯是任何下载的压缩包都先跑一遍unzip -t再解压服务器上常备unzip -O CP936的解决方案几秒钟的事情能省下大把踩坑时间。如果你刚拿到这个zip建议按这条路线走解压确认完整性 → 统计标签分布 → 设计数据加载与对齐函数 → 跑通一个BERTCRF基线 → 用官方脚本验证。整个流程走通一遍你不仅熟悉了CCKS2019这个任务的细节基本的医疗NER落地能力也就有了。后面如果再碰到类似的中文序列标注任务换数据不换方法一天就能迁移过去。本文还有配套的精品资源点击获取
返回列表