Pharma NLP:药品命名实体识别的代码工程实践
“把这几十万份 ADR(不良反应)报告里的药品名、不良事件、剂量信息都提取出来?现在全靠人工,一个人一天顶多 80 份,三年的报告积压在那儿了。”药品 NER 跟你在 CoNLL-2003 上跑的那个 NER,不是一个物种。通用 NER 里,"苹果"要么是水果要么是公司,上下文清晰,实体边界明确。但你见过这种原始文本吗:患者因急性上呼吸道感染就诊,医嘱予注射用头孢曲松钠(规格:1g/支) 静脉滴注,每日一次,连用3天,同时口服盐酸左西替利嗪片5mg QD, 用药第2日出现皮疹、瘙痒,考虑药物性皮炎,遂停用头孢曲松钠……这段文字里,光药品实体就有:注射用头孢曲松钠(药品全名)头孢曲松钠(同一药品的简称,出现在后文)盐酸左西替利嗪片(第二个药品)它们还有嵌套关系:头孢曲松钠是注射用头孢曲松钠的活性成分子实体。剂量1g、5mg分属两个药品。给药途径静脉滴注、口服各自对应不同药物。不良事件皮疹、瘙痒和药物性皮炎是同一事件的不同描述层级。这才是真实的药品文本——充满缩写、嵌套、指代、同义词,而且还要命地非结构化。代码实现:1 数据预处理# preprocess.py# 药品文本专用预处理管道# 已脱敏,去除所有公司/系统标识importrefromtypingimportList,Tuple,Optional# 药品文本中常见的噪声模式NOISE_PATTERNS=[r'\s*(详见说明书)\s*',r'\[.*?科室.*?\]',r'【.*?】',# 结构化字段标记r'(?!\d)\.(?!\d)',# 非小数点的孤立句号(部分OCR产物)]# 医学缩写展开表(节选,实际版本 ~2400 条)ABBREV_MAP={'QD':'每日一次','BID':'每日两次','TID':'每日三次','QID':'每日四次','PRN':'按需','SOS':'必要时','IV':'静脉注射','IVD':'静脉滴注','IM':'肌肉注射','SC':'皮下注射','PO':'口服','SL':'舌下含服',}# 剂量单位标准化映射UNIT_NORM={'MG':'mg','UG':'μg','MCG':'μg','G':'g','ML':'ml','L':'L','IU':'IU','U':'U','TAB':'片','CAP':'粒','CAPS':'粒',}defclean_adr_text(text:str)-str:""" ADR 报告专用清洗函数。 处理顺序很重要:先处理结构噪声,再做字符级标准化, 最后缩写展开(避免缩写展开后被噪声规则误匹配)。 """# Step 1: 移除已知噪声模式forpatterninNOISE_PATTERNS:text=re.sub(pattern,'',text)# Step 2: 全角转半角(药品名常混用)text=_full2half(text)# Step 3: 剂量单位大写标准化# 注意:只对独立的单位词做替换,避免误改药品名text=re.sub(r'(?=\d)('+'|'.join(UNIT_NORM.keys())+r')(?=[\s,,。;]|$)',lambdam:UNIT_NORM.get(m.group(0).upper(),m.group(0)),text,flags=re.IGNORECASE)# Step 4: 英文医学缩写展开(可配置是否展开)# 注意:展开后实体边界会变,需要与标注对齐# text = _expand_abbrev(text, ABBREV_MAP) # 根据业务场景决定是否启用# Step 5: 多余空白符合并text=re.sub(r'\s+',' ',text).strip()returntextdef_full2half(text:str)-str:"""全角字符转半角(保留中文字符)"""result=[]forcharintext:code=ord(char)if0xFF01=code=0xFF5E:# 全角可打印字符result.append(chr(code-0xFEE0))elifcode==0x3000:# 全角空格result.append(' ')else:result.append(char)return''.join(result)defsent_tokenize_medical(text:str)-List[str]:""" 医学文本分句。 医学文本的分句比通用文本更复杂: - "1.5mg" 里的句点不是句子边界 - "i.v." "p.o." 等缩写里的句点不是边界 - 中文句号、分号、顿号才是主要边界 """# 保护模式:先把不应该切的地方替换成占位符text=re.sub(r'(\d+)\.(\d+)',r'\1__DOT__\2',text)# 小数点text=re.sub(r'([a-zA-Z])\.([a-zA-Z])',r'\1__DOT__\2',text)# 英文缩写# 按医学文本常用边界切句sents=re.split(r'(?=[。!?;\n])',text)# 还原占位符sents=[s.replace('__DOT__','.').strip()forsinsents]# 过滤无效句子sents=[sforsinsentsiflen(s)
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2510184.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!