对于医学领域的自然语言文献,例如医学教材、医学百科、临床病例、医学期刊、入院记录、检验报告等,这些文本中蕴含大量医学专业知识和医学术语。将实体识别技术与医学专业领域结合,利用机器读取医学文本,可以显著提高临床科研的效率和质量,并且可服务于下游子任务。医学领域中非结构化的文本,都是由中文自然语言句子或句子集合组成。实体抽取是从非结构化医学文本中找出医学实体,如疾病、症状的过程。

电子病历结构化解析

如上图所示,医院证明本文实现的是对案件的诊断,疾病,病历证明、入院记录(主诉,现病史,既往史等),出院记录(诊治过程)进行实体识别,并对疾病和体征的患者患病与否进行标注。

一、任务拆解

模块一: span-based 命名实体识别模型

  1. 该模块识别并抽取出与医学临床相关的实体,并将他们归类到预先定义好的类别。将医学文本命名实体划分为九大类,包括:疾病(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

模块三:后处理

  1. 负面实体判断

方法:分句,疾病/症状和负面前缀在同一句出现即否定疾病/症状,目前否定词为['无', '没有', '否认']。当然也可以用句法分析找出否定词和疾病/症状有无关联关系。

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环境配置踩坑

  1. CUDA配置流程见参考

2. CPU指令集需要提单修改为Sandy BridgeIvyBridge之类,否则tensor加载不到gpu。很坑。。。谷歌的方法尝试了一遍都不好使,bug太隐蔽了

电子病历结构化之实体识别(附完整项目代码)相关推荐

  1. 电子病历结构化发展路线图谱

    电子病历结构化发展路线图谱 2014-04-06 随着医院信息化的发展,国内医院信息化建设重点逐步从管理信息系统转到临床信息系统.在临床信息系统的应用中发现,所有的临床信息最终都要反映到患者病历中.病 ...

  2. 如何实现抖音狗头,人工智能,附完整项目代码

    详细见 人工智能python+dlib+opencv技术10分钟实现抖音人脸变狗头详细图文教程和完整项目代码 https://blog.csdn.net/wyx100/article/details/ ...

  3. Spark Core项目实战(1) | 准备数据与计算Top10 热门品类(附完整项目代码及注释)

      大家好,我是不温卜火,是一名计算机学院大数据专业大二的学生,昵称来源于成语-不温不火,本意是希望自己性情温和.作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己 ...

  4. Pytorch TextCNN实现中文文本分类(附完整训练代码)

    Pytorch TextCNN实现中文文本分类(附完整训练代码) 目录 Pytorch TextCNN实现中文文本分类(附完整训练代码) 一.项目介绍 二.中文文本数据集 (1)THUCNews文本数 ...

  5. 基于傅里叶变换的音频重采样算法 (附完整c代码)

    前面有提到音频采样算法: WebRTC 音频采样算法 附完整C++示例代码 简洁明了的插值音频重采样算法例子 (附完整C代码) 近段时间有不少朋友给我写过邮件,说了一些他们使用的情况和问题. 坦白讲, ...

  6. 灰狼(GWO)算法(附完整Matlab代码,可直接复制)

    尊重他人劳动成果,请勿转载! 有问题可留言或私信,看到了都会回复解答! 其他算法请参考: 1.粒子群(PSO)优化算法(附完整Matlab代码,可直接复制)https://blog.csdn.net/ ...

  7. 详细介绍用MATLAB实现基于A*算法的路径规划(附完整的代码,代码逐行进行解释)(一)--------A*算法简介和环境的创建

       本系列文章主要介绍基于A*算法的路径规划的实现,并使用MATLAB进行仿真演示.本文作为本系列的第一篇文章主要介绍如何进行环境的创建,还有一定要记得读前言!!! 本系列文章链接: ------- ...

  8. 音频自动增益 与 静音检测 算法 附完整C代码

    前面分享过一个算法<音频增益响度分析 ReplayGain 附完整C代码示例> 主要用于评估一定长度音频的音量强度, 而分析之后,很多类似的需求,肯定是做音频增益,提高音量诸如此类做法. ...

  9. java 对音频文件降噪_(转)音频降噪算法 附完整C代码

    转:https://www.cnblogs.com/cpuimage/p/8905965.html 降噪是音频图像算法中的必不可少的. 目的肯定是让图片或语音 更加自然平滑,简而言之,美化. 图像算法 ...

最新文章

  1. 在java的程序里date类型比较大小
  2. 附录2:Numpy实例记录
  3. iOS系列教程 目录 (持续更新...)
  4. [转载] 中华典故故事(孙刚)——13 马虎
  5. Vue使用全局样式,页面没有发生变化:逗号是中文的,引起错误,样式不变化 也没有报错就是不起作用
  6. live555 源码分析:简介
  7. java读取TXT文件的方法
  8. 求最大公约数——辗转相除法
  9. Angular 内嵌视图、宿主视图
  10. [译]R语言——Shiny框架之入门(二):如何构建一个Shiny应用
  11. Sublime text 3 快捷键
  12. js 获取iframe页面元素
  13. 使用Aspose在C#中将PLT转换为PDF或JPEG图像
  14. Roguelike到底是啥?讲讲和Roguelike 相关知识(搬运)
  15. 【转帖】GBase 数据库
  16. 世界50所知名大学提供开放课程
  17. c++ ado连接mysql数据库_c++通过ADO连接数据库
  18. 乐鑫ESP32-C3开发(一)简述和目录
  19. java抽象类存在的意义
  20. 毕业一周年总结-不忘初心,砥砺前行

热门文章

  1. 查看微信小程序的原始ID
  2. 《信息安全保障》一3.2 信息安全管理方法与实施
  3. java web商城项目难度_JavaWeb网上商城的反思
  4. 落草QQ群成员提取器,邮件群发
  5. 用通俗易懂的方式讲解:CatBoost 算法原理及案例
  6. GB 55037-2022 建筑防火通用规范(全文)
  7. 封闭图形的填充问题研究
  8. Android特效View之二之 闪闪发光Shimmer字体特效
  9. Flutter自定义控件之饼状图、大转盘
  10. php表单验证_用PHP进行表单验证