图2 医学知识库与词库结构关系
医学知识库的建设繁杂,但是对于医疗业务的可持续发展意义重大,所有基于知识库的工作成果,可以在任何时间任何地点无壁垒解读,持续的放大价值,推动医疗领域的发展。需要注意的是,医疗知识库处在不断的迭代完善过程中,我们在建设知识库的过程中,要充分考虑知识库的扩展性。
2. 应用算法处理病历文本
应用算法处理病历文本的过程,包含以下几个部分。
医学实体标记(分词+标注)首先,需要分离医学实体,这一步主要应用自然语言处理中的分词算法来实现,分词的算法有多种,每种算法有各自的优劣。我们在实际应用中在业务不同阶段使用了不同的算法,主要考量包括:一是算法的效果是否能达到我们的需要;二是算法底层数据结构是否可以实时更新、实时生效,因为我们无法一开始就建立一个非常完备的医学词库,实际应用时需要可以随时更新分词词库(内存+外部词库),让更新的词项实时生效。然后,需要对医学实体做标注,这一步相对简单,如果是使用HMM分词,可以使用维特比算法标注,也可以依赖词库标注。
任何一个HMM都可以通过下列五元组来描述:param
obs:观测序列;aram states:隐状态;aram start_p:初始概率(隐状态);param
trans_p:转移概率(隐状态);param emit_p: 发射概率 (隐状态表现为显状态的概率)
这个例子可以用如下的HMM来描述:
states=('Rainy','Sunny')
observations=('walk','shop','clean')
start_probability={'Rainy':0.6,'Sunny':0.4}
transition_probability={
'Rainy':{'Rainy':0.7,'Sunny':0.3},
'Sunny':{'Rainy':0.4,'Sunny':0.6},
}
emission_probability={
'Rainy':{'walk':0.1,'shop':0.4,'clean':0.5},
'Sunny':{'walk':0.6,'shop':0.3,'clean':0.1},
}
求解最可能的隐状态序列是HMM的三个典型问题之一,通常用维特比算法解决。维特比算法就是求解HMM上的最短路径(-log(prob),也即是最大概率)的算法。稍微用中文讲讲思路,很明显,第一天天晴还是下雨可以算出来:定义V[时间][今天天气]
=
概率,注意今天天气指的是,前几天的天气都确定下来了(概率最大)今天天气是X的概率,这里的概率就是一个累乘的概率了。因为第一天我的朋友去散步了,所以第一天下雨的概率V[第一天][下雨] = 初始概率[下雨] *
发射概率[下雨][散步] = 0.6 * 0.1 = 0.06,同理可得V[第一天][天晴] = 0.24
。从直觉上来看,因为第一天朋友出门了,她一般喜欢在天晴的时候散步,所以第一天天晴的概率比较大,数字与直觉统一了。从第二天开始,对于每种天气Y,都有前一天天气是X的概率
* X转移到Y的概率 *
Y天气下朋友进行这天这种活动的概率。因为前一天天气X有两种可能,所以Y的概率有两个,选取其中较大一个作为V[第二天][天气Y]的概率,同时将今天的天气加入到结果序列中比较V[最后一天][下雨]和[最后一天][天晴]的概率,找出较大的哪一个对应的序列,就是最终结果。
这个例子的Python代码:
# -*- coding:utf-8 -*-
# Filename: viterbi.py
# Author:hankcs
# Date: 2014-05-13 下午8:51
states=('Rainy','Sunny')
observations=('walk','shop','clean')
start_probability={'Rainy':0.6,'Sunny':0.4}
transition_probability={
'Rainy':{'Rainy':0.7,'Sunny':0.3},
'Sunny':{'Rainy':0.4,'Sunny':0.6},
}
emission_probability={
'Rainy':{'walk':0.1,'shop':0.4,'clean':0.5},
'Sunny':{'walk':0.6,'shop':0.3,'clean':0.1},
}
# 打印路径概率表
defprint_dptable(V):
print" ",
foriinrange(len(V)):print"}"%i,
foryinV[0].keys():
print"%.5s: "%y,
fortinrange(len(V)):
print"%.7s"%("%f"%V[t][y]),
defviterbi(obs,states,start_p,trans_p,emit_p):
"""
:param obs:观测序列
:param states:隐状态
:param start_p:初始概率(隐状态)
:param trans_p:转移概率(隐状态)
:param emit_p: 发射概率 (隐状态表现为显状态的概率)
:return:
"""
# 路径概率表 V[时间][隐状态] = 概率
V=[{}]
# 一个中间变量,代表当前状态是哪个隐状态
path={}
# 初始化初始状态 (t == 0)
foryinstates:
V[0][y]=start_p[y]*emit_p[y][obs[0]]
path[y]=[y]
# 对 t > 0 跑一遍维特比算法
fortinrange(1,len(obs)):
V.append({})
newpath={}
foryinstates:
# 概率 隐状态 = 前状态是y0的概率 * y0转移到y的概率 * y表现为当前状态的概率
(prob,state)=max([(V[t-1][y0]*trans_p[y0][y]*emit_p[y][obs[t]],y0)fory0instates])
# 记录最大概率
V[t][y]=prob
# 记录路径
newpath[y]=path[state]+[y]
# 不需要保留旧路径
path=newpath
print_dptable(V)
(prob,state)=max([(V[len(obs)-1][y],y)foryinstates])
return(prob,path[state])
defexample():
returnviterbi(observations,
states,
start_probability,
transition_probability,
emission_probability)
printexample()
输出:
012
Rainy:0.060000.038400.01344
Sunny:0.240000.043200.00259
(0.01344,['Sunny','Rainy','Rainy'])
NLP应用
具体到分词系统,可以将天气当成"标签”,活动当成"字或词”。那么,几个NLP的问题就可以转化为:词性标注:给定一个词的序列(也就是句子),找出最可能的词性序列(标签是词性)。如ansj分词和ICTCLAS分词等。分词:给定一个字的序列,找出最可能的标签序列(断句符号:[词尾]或[非词尾]构成的序列)。结巴分词目前就是利用BMES标签来分词的,B(开头),M(中间),E(结尾),S(独立成词)命名实体识别:给定一个词的序列,找出最可能的标签序列(内外符号:[内]表示词属于命名实体,[外]表示不属于)。如ICTCLAS实现的人名识别、翻译人名识别、地名识别都是用同一个Tagger实现的。HMM是一个通用的方法,可以解决贴标签的一系列问题。
我们在实际应用时,将词性改造成"词性+医学实体TAG”的方式,这样可以带来两个好处,一是一般分词工具已经把词性标注集成到分词算法中去了,这样可以省去我们自己标注的工作;二是病历文本的叙述也是遵循中文语法的,所以在做句法分析时,可以将医学实体的通用词性提取出来,然后使用通用的句法分析模型分析处理,避免在去做针对医学领域句法分析语料标注、训练的工作。
构建语义层级
构建语义层级的过程类似人阅读的过程,以句子为单位从左向右读取原始文本,然后由主控系统分析器对句子进行句法+规则分析,得到具体的按语法结构组织的数据,最后在由控制器根据语义建立新的语义节点并加入到新的层级。整体过程如图3所示。
这里需要注意问题有:
在医学领域,很多表述按照医学惯例是不符合常用的语法的,这部分的结构需要单独处理,如:PT2bN0M0,这里面包含了T肿瘤大小及局部浸润范围、N淋巴结受累情况、M远处转移三种分期指标信息,类似这种领域特有表示,一般需要特殊处理,然后映射到语义层级通用结构,方便后续跟着语义层级的其它信息统一处理。
一个语义节点的开始和结束的界限有时是非常模糊的,这需要我们预先做一个实体词的关联分析模型,以此来辅助判定后续的动作。另外,保证我们的系统针对层级误差有一定的健壮性是非常有必要的。
3. 抽取信息
构建完病历文本的知识结构之后,就是抽取数据进行实际应用的阶段了,主要的步骤包括:构建抽取模版、验证抽取模版、实际抽取数据。
构建抽取模版是确立结构化目标以及定义抽取规则的过程,简单的说就是定义一个Excel表头并制定每一个列字段的数据抽取规则的过程。表头的定义非常简单,例如:姓名、年龄、症状等。抽取规则的定义要相对复杂,需要对语义层级有一定的了解,因为语义层级是一个树状结构,所以对应的抽取规则也是一个层级结构,并且最后一个层级要能真正定位我们关注的数据。
图4是一个规则示例,语义层级的节点的TAG帮助我们定位到具体的语义层级节点,运算规则帮助我们进行匹配运算和对输出结果的逻辑转换,关键值标示了我们要抽取的目标。
图4单规则结构示意
图5表示我们一个简单的抽取模版的示意,姓名和CT是我们的表头内容,后续是我们每个字段的具体抽取规则链。规则链标示了寻找数据的层级结构,例如:抽取CT信息时要先找到现病史所在语义层级,然后在以此层级为基础找到CT语义层级,进而获取目标信息。
图5抽取模版
需要注意的点:
注意抽取模版和抽取规则的复用。
要同时支持规则组的抽取,以满足的对医疗事件的抽取需求,例如我们的示例模版会将CT的全部信息给抽取出来,但是无法详细的抽取出CT的事件、地点、结果等信息,这时便需要通过规则组先定位到CT语义节点,然后以此节点出发,抽取此CT事件的详细信息。
病历智能处理引擎架构
病历智能处理引擎架构主要分为六个子系统,示意如图6:
图6 病历智能处理引擎架构
病历导入系统:负责对各个来源的各种格式的病历数据进行病历文本的提取。
语义层级构建系统:病历文本导入系统后,通过此系统进行语义层级上下文的构建。此系统依赖自然语言处理系统、知识库词库维护系统。
自然语言处理系统:负责对病历文本进行分词、句法分析、语义分析的工作,除了封装通用的处理算法,还定制了医学特有的处理算法。
知识库词库维护系统:负责知识库、词库的维护工作,是自然语言处理系统的基础。
CRF(Case Report Format)规则定制系统:负责定制抽取规则。
结构化抽取系统:以语义层级上下文为基础,通过CRF抽取规则进行结构化的抽取。
以上是六个子系统的职责与关系的介绍,每个子系统中又包含了多个职责模块,具体如图7:
图7 病历智能处理引擎模块示意
模块划分主要分为四层,从上到下分别对应病历导入系统、语义层级处理系统、自然语言处理系统以及结构化抽取系统。
这里没有突出知识库词库维护系统以及CRF规则定制系统的具体模块,因为对整个系统来讲,我们仅需要以上两个系统的产出,即词库知识库以及CRF抽取规则。
病历导入的模块主要是为了兼容不同的格式,所以对主流的Word、Excel等做了支持策略,同时为后续的扩展提供了保障。
语义层级处理系统的核心是层级的动作分类器,构建整个语义上下文的骨架。
自然语言处理系统构建填充了语义层级上下文的内容,语义处理管道是主要的处理流,保证了自定义处理的扩展。
结构化抽取系统的核心是抽取器,这里包含了一个任务中心,在此可以对抽取结果进行下载以及查看正在抽取任务的进度。
病历智能处理引擎在临床科研自动化中的应用
科研对很多医生而言是一个刚需,评职称需要论文,写论文需要做科研。临床科研的主要方式是选定一个课题,然后选择病历,对病历进行结构处理,录入结构化病历记录表中,再对这些数据进行统计分析,以分析结果得到的统计图表为基本架构,撰写科研论文。
在这个过程中,最耗费时间和精力的,最容易出错的,就是病历的结构化处理,而这正是前文阐述的病历智能处理引擎的强项,一个使用病历智能处理引擎的的科研过程如下图。
新屿作为一家医疗大数据服务公司,为医疗科研过程开发了数款产品,构成了一个医疗科研数据自动化处理平台。整体架构如图9所示。
病历文本通过病历智能处理引擎进行自然语言处理后,处理结果写入科研宝,并使用易统计进行分析,产生分析报表。
病历智能处理引擎在智能辅助诊疗中的应用
病历,特别是专家写的病历,本身就是一笔巨大的知识财富,将这些知识进行处理、分析、统计、挖掘,可以构成一个病历知识库,供更多的人共享,即构成一个智能辅助诊疗系统。整体架构如图10所示。
图10 智能辅助诊疗系统架构
病历知识库、循证医学知识库、科研文献知识库、用药知识库共同构成一个辅助诊疗知识库,通过知识匹配搜索引擎对外提供服务。患者或者医生录入病史、检查结果等信息,系统匹配初步诊断结果,搜索诊疗计划,产生多个辅助诊疗建议,供患者和医生进行参考。
目前新屿辅助诊疗系统尚在原型验证阶段,受医疗管理、医学伦理的限制,大规模商业应用目前看来还为时尚早,但是利用大数据技术支持下的智能医疗,实现多专家会诊定制最优诊疗方案是一件值得所有人期待的事。
总结
使用传统IT技术,通过信息化手段提高医疗效率的工作已经做了几十年了,医疗机构信息化程度也越来越高,对效能提升、信息快速流通起到了巨大的作用,但是,仅仅通过信息化手段进一步提高效率的空间越来越小,边际效益越来越少。通过引入更多人工智能的因素,辅助医生更快、更准确、更全面地开展医疗与科研工作,将会是下一阶段IT技术的发展趋势,但是新的道路总是曲折的,期待我们共同去探索。
作者简介:
吴大帅,新屿算法工程师,曾供职于宅米网、新达达,从事系统架构设计、算法设计等工作。
李智慧,《大型网站技术架构:核心原理与案例分析》作者,从事大型网站、分布式系统、大数据方面的研发工作。
责编:钱曙光(qianshg@csdn.net)
声明:本文为《程序员》原创文章,未经允许不得转载,更多精彩文章请点击「阅读原文」订阅《程序员》。