医学电子病历命名实体识别的实现总结
一、整体概述
本次实验数据集来自CCKS2018的电子病历命名实体识别的评测任务,原始数据样本包括:一般醒目,出院情况,病史情况,病史特点,诊疗经过,共提供600份标注好的电子病历文本,共需识别含治疗方式、身体部位、疾病症状、医学检查、疾病实体五类实体。
实验采用==BERT+BiLSTM+CRF==构建模型(仅使用BiLSTM来训练NER模型的效果很差)。
二、实现过程
2.1. BIO标注
对句子中的每个单词标记相应的标签,得到最终的命名实体识别结果。
O :非实体部分
TREATMENT:治疗方式
BODY:身体部位
SIGN:疾病症状
CHECK:医学检查
DISEASE:疾病实体
数据转换脚本:
def transfer(self):
f = open(self.train_filepath, 'w+')
count = 0
for root,dirs,files in os.walk(self.origin_path):
for file in files:
filepath = os.path.join(root, file)
if 'original' not in filepath:
continue
label_filepath = filepath.replace('.txtoriginal','')
print(filepath, '\t\t', label_filepath)
content = open(filepath).read().strip()
res_dict = {}
for line in open(label_filepath):
res = line.strip().split(' ')
start = int(res[1])
end = int(res[2])
label = res[3]
label_id = self.label_dict.get(label)
for i in range(start, end+1):
if i == start:
label_cate = label_id + '-B'
else:
label_cate = label_id + '-I'
res_dict[i] = label_cate
for indx, char in enumerate(content):
char_label = res_dict.get(indx, 'O')
print(char, char_label)
f.write(char + '\t' + char_label + '\n')
f.close()
return
2.2 模型训练
模型构建采用PyTorch实现,使用预训练字向量,作为embedding层输入,然后经过两个双向LSTM层进行编码,编码后加入dense层,最后送入CRF层进行序列标注。
模型训练代码如下:
self.train_manager = DataManager(batch_size=self.batch_size, tags=self.tags)
self.total_size = len(self.train_manager.batch_data)
data = {
"batch_size": self.train_manager.batch_size,
"input_size": self.train_manager.input_size,
"vocab": self.train_manager.vocab,
"tag_map": self.train_manager.tag_map,
}
self.save_params(data)
self.dev_manager = DataManager(batch_size=60, data_type="dev")
# 验证集
# self.dev_batch = self.dev_manager.iteration()
self.model = BiLSTMCRF(
tag_map=self.train_manager.tag_map,
batch_size=self.batch_size,
vocab_size=len(self.train_manager.vocab),
dropout=self.dropout,
embedding_dim=self.embedding_size,
hidden_dim=self.hidden_size,
)
self.model = self.model.cuda()
self.restore_model()
2.3 模型测试与评估
与上述在同一文件,主要对模型的测试和评估,测试集来源于原数据集。
data_map = self.load_params()
input_size = data_map.get("input_size")
self.tag_map = data_map.get("tag_map")
self.vocab = data_map.get("vocab")
print('input_size',input_size)
print('tag_map',self.tag_map)
self.model = BiLSTMCRF(
tag_map=self.tag_map,
vocab_size=input_size,
embedding_dim=self.embedding_size,
hidden_dim=self.hidden_size
)
self.model = self.model.cuda()
self.test_manager = DataManager(batch_size=60, data_type="dev")
self.restore_model()
2.4 前端文本颜色标注与标签映射
前端部分采用开源项目ner-app(文章结尾给出相关链接)实现,原项目构建时,解析后的文本已经赋值相应的颜色标注类型,但是我不想去修改模型输出结果(因为测试和训练在一个文件),因此就在前端定义了如下颜色标注类型。
// 定义一个对象来存储类型和颜色的映射
const color = {
'BODY': 'white',
'TREATMENT': 'white',
'SIGNS': 'white',
'CHECK': 'white',
'DISEASE': 'white',
};
const background = {
'BODY': 'deeppink',
'TREATMENT': 'orange',
'SIGNS': '#42b983',
'CHECK': 'blue',
'DISEASE': 'red',
};
2.5 前端标注
实现效果如下,具体标注代码如下:
_dataNer(dataProps)
.then((res) => {
if (res.status === 200) {
console.log("后端返回数据:", res.data);
setmyJson(res.data)
message.success("数据标识成功");
setIsLoading(false);
let data = res.data.data
console.log("data",data)
let str = "";
let len = articleContent.length;
let i, j, k;
for (i = 0; i < len; i++) {
for (j = 0; j < data.length; j++) {
if (i === parseInt(data[j].start)) {
str += `<Tooltip title="prompt text"><span onclick="alert('实体:${data[j].type}')" style="color:${color[data[j].type]};background:${background[data[j].type]}">`;
for (
k = parseInt(data[j].start);
k <= parseInt(data[j].stop);
k++
) {
str += articleContent[k];
}
str += `</span ></Tooltip> `;
i=k;
}
}
str += articleContent[i];
}
setMarkdownContent(str);
}
})
2.6 后端实现
后端使用Flask搭建,调用训练好的模型然后对数据进行训练,接收json格式前端数据,返回数据类型也是json。
这里有个坑,后端返回的json不是array类型,因此在前端标注中遍历的时候会出错。
@app.route('/data', methods=['GET', 'POST'])
def data():
data = request.json # 获取前端传递的数据
print(data)
# 在这里执行数据标识的逻辑,将结果保存到 identified_data 变量中
cn = ChineseNER("predict")
identified_data = cn.predict(data['articleContent'])
print("identified_data:",identified_data)
# 返回标识结果给前端
return jsonify({"status": 200, "data": identified_data})
if __name__ == '__main__':
# print(data_ner())
app.run(host="0.0.0.0",port=8000)
2.7 整体展示
实验整体界面展示:
数据标识展示:
点击相应实体会有实体类型提示:
侧边有相应数据统计展示:
三、 实验总结
3.1 LSTM相对于RNN的优势
RNN能够把以前的信息联系到现在,从而解决现在的问题。但是,随着预测信息和相关信息间的间隔增大, RNNs很难去把它们关联起来。如果序列过长,一方面会出现梯度消散或梯度爆炸的问题,另一方面,展开后的前馈神经网络会占用过大的内存。
LSTM非常适合用于对时序数据的建模,如文本数据。能将长时期的依赖关系联系起来。
3.2 为什么采用CRF
BiLSTM 可以预测出每一个字属于不同标签的概率,然后使用 Softmax 得到概率最大的标签,作为该位置的预测值。但是在预测的时候会忽略了标签之间的关联性,因此 BiLSTM+CRF 在 BiLSTM 的输出层加上一个 CRF,使得模型可以考虑类标之间的相关性
CRF层可以向最终的预测标签添加一些约束,以确保它们是有效的。这些约束可以由CRF层在训练过程中从训练数据集自动学习。示例如下:
句子中第一个单词的标签应该以“B-”或“O”开头,而不是“I-”
“B-label1 I-label2 I-label3 I-label4”,在这个模式中,label1、label2、label3、label4应该是相同的命名实体标签。例如,“B-BODY I-BODY I-BODY”是有效的,但是“B-BODY I-SIGNS”是无效的。
“O I-label”无效。一个命名实体的第一个标签应该以“B-”而不是“I-”开头,换句话说,有效的模式应该是“O B-label”。
………
有了这些有用的约束,无效预测标签序列的数量将显著减少。
3.3 BERT的作用
BERT预训练模型通过对海量的文本语料进行自监督学习,可以为单词学习好的特征表示。借助Attention机制,BERT模型可以将任意位置的两个单词之间的距离转换为“1”,有效地解决长期依赖问题。
BERT 预训练模型可以充分挖掘到每个词的上下文信息,得到词的双向嵌入表示。 与BiLSTM + CRF相比,Bert + BiLSTM + CRF只是将embedding层(输入层)换成了BERT。
四、 参考资料
bert——Bert-BiLSTM-CRF命名实体识别演变-CSDN博客
基于BiLSTM+CRF医学病例命名实体识别项目_基于bi-lstm+crf的医疗医疗命名实体识别-CSDN博客
Vue实现一个命名实体识别的可视化标注系统(前端核心代码)_命名实体识别前端怎么写-CSDN博客
医学命名实体识别: 中科院刘焕勇老师的命名实体识别项目 (gitee.com)
88679/article/details/100673242)
Vue实现一个命名实体识别的可视化标注系统(前端核心代码)_命名实体识别前端怎么写-CSDN博客