电子病历结构化之实体识别(附完整项目代码)
对于医学领域的自然语言文献,例如医学教材、医学百科、临床病例、医学期刊、入院记录、检验报告等,这些文本中蕴含大量医学专业知识和医学术语。将实体识别技术与医学专业领域结合,利用机器读取医学文本,可以显著提高临床科研的效率和质量,并且可服务于下游子任务。医学领域中非结构化的文本,都是由中文自然语言句子或句子集合组成。实体抽取是从非结构化医学文本中找出医学实体,如疾病、症状的过程。
电子病历结构化解析
如上图所示,医院证明本文实现的是对案件的诊断,疾病,病历证明、入院记录(主诉,现病史,既往史等),出院记录(诊治过程)进行实体识别,并对疾病和体征的患者患病与否进行标注。
一、任务拆解
模块一: span-based 命名实体识别模型
- 该模块识别并抽取出与医学临床相关的实体,并将他们归类到预先定义好的类别。将医学文本命名实体划分为九大类,包括:疾病(dis),临床表现(sym),药物(dru),医疗设备(equ),医疗程序(pro),身体(bod),医学检验项目(ite),微生物类(mic),科室(dep)
数据集:Cblue-CmeEE
模型:Efficient GlobalPointe(Roberta wwm)
GlobalPointer多头识别嵌套实体
GlobalPointer是由苏剑林大佬提出来的一个span-based NER模型,可以很好的解决实体嵌套的问题,相比于"LSTM/BERT+CRF"的模型结构,GlobalPointer的设计更为优雅。
模型结构
如上图所示,右上三角的蓝色为序列中可能取到的实体,假设序列长度为 n ,则该序列能取到的可能的实体个数为 n(n+1)/2 个。假设有 m 种实体分类,则该问题转化为 m 个" n(n+1)/2 选 k "的问题。
序列encode我们通常用bert来做,encode过后,第 i 个位置和第 j 个位置的表示分别为 hi 和 hj ,通过变换, qi,α=Wq,αhi+bq,α 和 ki,α=Wk,αhi+bk,α ,每个span推断是实体的得分为 sα(i,j)=qi,α⊤kj,α 。其中 α 为实体的类别。
位置编码
仅仅是上面的得分还不够,如果不加入位置编码会造成“任意两个实体的首尾组合都当成目标预测出来”的问题。GlobalPointer加入了旋转位置编码-RoPE,进而引入位置信息。对于位置 i ,有变换矩阵 Ri ,满足关系Ri⊤Rj = Rj−i ,加入 q , k 中,有:
sα(i,j)=(Riqi,α)⊤(Rjkj,α)=qi,α⊤Ri⊤Rjkj,α=qi,α⊤Rj−ikj,α
损失函数
一个适用于多标签分类的损失函数,形式为
log(1+∑(i,j)∈Pαe−sα(i,j))+log(1+∑(i,j)∈Qαesα(i,j))
其中 Pα 是所有类型为 α 的实体首尾集合, Qα 是所有非实体或类型非 α 的实体首尾集合。
解码阶段,当 sα(i,j)>0 看作是实体的输出。
上面写的是概要模型结构,建议看原作者博客。见参考中链接
提交了CMeEE榜单,效果还ok
模型 | CMeEE-P | CMeEE-R | CMeEE-F1 |
ernie | 62.560 | 69.538 | 65.865 |
roberta_wwm | 67.104 | 66.460 | 66.780 |
2. 超长文本处理
bert模型由于位置编码的限制,不能处理超过512字符的长文本,但很多电子病历的长度往往超过bert能处理的最大长度。此处我们采用长文本分段,batch predict,拼接结果的方式解决这个问题。 假设请求参数中包含三段文本[0, 1, 2],其中0,2为长文本需要截成两断,即给出映射关系 {0: [1, 2], 1: [3], 2:[4, 5]}。将分割后的若干短句进行predict,再通过映射拼接还原。 关键代码如下:
def data_process(data, max_len):mapping = OrderedDict() # 存文本与片段的映射列表id = 0 # 记录第几个片段sents = []code_mapping = OrderedDict()for i, d in enumerate(data):code = d['code']text = d['text']lenth = len(text)code_mapping[i] = codeif len(text) <= max_len:mapping[i] = [id]id += 1sents.append(text)continues_idx, e_idx = 0, max_lenwhile True:sec = text[s_idx: e_idx]if i not in mapping:mapping[i] = [id]else:mapping[i].append(id)id += 1sents.append(sec)if e_idx >= lenth: breaks_idx = e_idxe_idx = min(lenth, e_idx+max_len)return sents, mapping, code_mappingdef post_process(res, mapping, code_mapping, max_len):entities_list = []for k, v in mapping.items():cur = []start, end = v[0], v[-1]for i, entities in enumerate(res[start: end+1]):# 处理下标for entity in entities:entity['start_idx'] += max_len * ientity['end_idx'] += max_len * icur += entitiesentities_list.append({"code": code_mapping[k],"entities": cur})return entities_list
模块二: 时间抽取
正则表达式方式抽取时间,正则表达式如下,共126个正则
class Regexparser():def __init__(self, regex, logger=None):'''正则匹配的构造函数:param regex: 加载正则,list形式, 用于正则匹配的结构为namedtuple:param logger: 日志logger'''if logger is not None:self.logger = loggerelse:self.logger = logging.getLogger('utils.time.parser')Regex = namedtuple("Regex", ["pattern"])self.re = []for t in regex:try:pattern = re.compile(t)except Exception as e:self.logger.error("regex has some problem! %s\n%s" % (e, traceback.format_exc()))continueself.re.append(Regex(pattern=pattern))self.logger.info('正则parser模块初始化完毕!')def match_all(self, content):'''用批量正则匹配文本:param content: 待匹配文本:return: 匹配到的内容和位置,list形式返回'''info = []for i in self.re:result_finditer = i.pattern.finditer(content)for k in result_finditer:span = k.span()info.append({'word': k.group(), 'start': span[0], 'end': span[1]})ret = {'text': content,'entities': info}return ret
模块三:后处理
- 负面实体判断
方法:分句,疾病/症状和负面前缀在同一句出现即否定疾病/症状,目前否定词为['无', '没有', '否认']。当然也可以用句法分析找出否定词和疾病/症状有无关联关系。
def neg_match(negword_list, entities, text):'''negword_list: 存放否定词illness: 疾病return: 若存在该疾病则返回True,否则返回False'''for r in entities:# 拆短句short_sentences = re.split(r'[,,::;;。]', text)if r['type'] in ['dis', 'sym']:flag = Trueillness = r['entity']for short_sentence in short_sentences:for negword in negword_list:if negword in short_sentence and illness in short_sentence:r['is_neg'] = 'neg'flag = Falseif flag:r['is_neg'] = 'pos'return entities
2. 贪心去重
正则表达式进行时间抽取会有两个问题:
- 不同正则表达式可能抽取相同的内容,即需要去重
- 相连的时间抽取成两个实体,需要把实体合并。如抽取“2021年”,“5月”,“1日”三个实体,应连起来为“2021年5月1日”
方法:贪心
def fixup(data):'''目前的问题为:1. 去重2. 合并相连的时间 如抽取2021年,5月,1日,连起来为2021年5月1日:param ::return:'''text, entities = data['text'], data['entities']# 去重info = []for x in entities:if x not in info: info.append(x)if len(info) <= 1:data = {'text': text,'entities': info}return dataret = [info[0]]# 合并挨着的实体info.sort(key=lambda x: x['start'])for x in info:if ret[-1]['end'] >= x['start']:start = ret[-1]['start']end = max(ret[-1]['end'], x['end'])ret[-1]['end'] = endret[-1]['word'] = text[start: end]else:ret.append(x)# 为了和实体识别结果保持统一,end_span需要 -1for x in ret: x['end'] -= 1data = {'text': text,'entities': ret}return datadef combine_entities_time(text, entities, parser):ret = parser.match_all(text)ret = fixup(ret)for r in ret['entities']:entities.append({"start_idx": r['start'],"end_idx": r['end'],"type": "time","entity": r['word'],})return entities
二、GPU CUDA环境配置踩坑
- CUDA配置流程见参考
2. CPU指令集需要提单修改为Sandy Bridge,IvyBridge之类,否则tensor加载不到gpu。很坑。。。谷歌的方法尝试了一遍都不好使,bug太隐蔽了
电子病历结构化之实体识别(附完整项目代码)相关推荐
- 电子病历结构化发展路线图谱
电子病历结构化发展路线图谱 2014-04-06 随着医院信息化的发展,国内医院信息化建设重点逐步从管理信息系统转到临床信息系统.在临床信息系统的应用中发现,所有的临床信息最终都要反映到患者病历中.病 ...
- 如何实现抖音狗头,人工智能,附完整项目代码
详细见 人工智能python+dlib+opencv技术10分钟实现抖音人脸变狗头详细图文教程和完整项目代码 https://blog.csdn.net/wyx100/article/details/ ...
- Spark Core项目实战(1) | 准备数据与计算Top10 热门品类(附完整项目代码及注释)
大家好,我是不温卜火,是一名计算机学院大数据专业大二的学生,昵称来源于成语-不温不火,本意是希望自己性情温和.作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己 ...
- Pytorch TextCNN实现中文文本分类(附完整训练代码)
Pytorch TextCNN实现中文文本分类(附完整训练代码) 目录 Pytorch TextCNN实现中文文本分类(附完整训练代码) 一.项目介绍 二.中文文本数据集 (1)THUCNews文本数 ...
- 基于傅里叶变换的音频重采样算法 (附完整c代码)
前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲, ...
- 灰狼(GWO)算法(附完整Matlab代码,可直接复制)
尊重他人劳动成果,请勿转载! 有问题可留言或私信,看到了都会回复解答! 其他算法请参考: 1.粒子群(PSO)优化算法(附完整Matlab代码,可直接复制)https://blog.csdn.net/ ...
- 详细介绍用MATLAB实现基于A*算法的路径规划(附完整的代码,代码逐行进行解释)(一)--------A*算法简介和环境的创建
本系列文章主要介绍基于A*算法的路径规划的实现,并使用MATLAB进行仿真演示.本文作为本系列的第一篇文章主要介绍如何进行环境的创建,还有一定要记得读前言!!! 本系列文章链接: ------- ...
- 音频自动增益 与 静音检测 算法 附完整C代码
前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...
- java 对音频文件降噪_(转)音频降噪算法 附完整C代码
转:https://www.cnblogs.com/cpuimage/p/8905965.html 降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法 ...
最新文章
- 在java的程序里date类型比较大小
- 附录2:Numpy实例记录
- iOS系列教程 目录 (持续更新...)
- [转载] 中华典故故事(孙刚)——13 马虎
- Vue使用全局样式,页面没有发生变化:逗号是中文的,引起错误,样式不变化 也没有报错就是不起作用
- live555 源码分析:简介
- java读取TXT文件的方法
- 求最大公约数——辗转相除法
- Angular 内嵌视图、宿主视图
- [译]R语言——Shiny框架之入门(二):如何构建一个Shiny应用
- Sublime text 3 快捷键
- js 获取iframe页面元素
- 使用Aspose在C#中将PLT转换为PDF或JPEG图像
- Roguelike到底是啥?讲讲和Roguelike 相关知识(搬运)
- 【转帖】GBase 数据库
- 世界50所知名大学提供开放课程
- c++ ado连接mysql数据库_c++通过ADO连接数据库
- 乐鑫ESP32-C3开发(一)简述和目录
- java抽象类存在的意义
- 毕业一周年总结-不忘初心,砥砺前行