天池下的瑞金医院MMC人工智能辅助构建知识图谱

浅谈知识图谱------天池下的瑞金医院MMC人工智能辅助构建知识图谱

前言

知识图谱是个很大的概念,可惜我没数据,借用瑞金医院的数据集,来谈下命名识别。
在这里插入图片描述

数据说明

在这里插入图片描述
数据使用 brat 进行标注,每个 .txt 文件对应一个 .ann 标注文件。
txt文件对应一篇糖尿病下的论文
ann文件有3列,以 \t 分隔,第一列为实体编号,第二列为实体类别,第三列为实体位置信息。实体位置信息共3列, 以空格分隔,分别代表实体的开始位置,结束位置,实体文本在这里插入图片描述
在这里插入图片描述

问题

这里我引用冠军队伍的代码,他们当时所面临的问题如下:
(1)他们是对一篇文章去做实体标注,文章的字数可能很长(几千到上万字),不可能直接输入到一个 RNN 中;
(2)样本中文章可能由于格式转换的一些原因,没有一个很好的句子边界,甚至一个词汇当中存在换行符 \n 或者句号 的情况,因此用换行 符或者句号去切割句子不一定合适。
(3)如果按照固定窗口大小的滑动窗口去切句子,刚好把一个实体切分成2个部分怎么办?
中文文本,面临是否要分词的选择;
下面是他们的解决方案:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

网络模型和效果展示

网络模型为了便于上下文的关联采用了双向的lstm,为了使滑动的时候不丢到相关联的词语采用了一层CRF,作为最后最后一层的预测。
在这里插入图片描述
在这里插入图片描述

代码

代码主要分为三个部分,实体的定义和处理、句子的切分和处理、模型的搭建,除此之外还有预测评估的部分

实体的定义和处理

class Entity(object):
    def __init__(self, ent_id, category, start_pos, end_pos, text):
        self.ent_id = ent_id
        self.category = category
        self.start_pos = start_pos
        self.end_pos = end_pos
        self.text = text

    def __gt__(self, other):
        return self.start_pos > other.start_pos

    def offset(self, offset_val):
        return Entity(self.ent_id,
                      self.category,
                      self.start_pos + offset_val,
                      self.end_pos + offset_val,
                      self.text)

    def __repr__(self):
        return '({}, {}, ({}, {}), {})'.format(self.ent_id,
                                               self.category,
                                               self.start_pos,
                                               self.end_pos,
                                               self.text)
                                              
class Entities(object):
    def __init__(self, ents):
        self.ents = sorted(ents)
        self.ent_dict = dict(zip([ent.ent_id for ent in ents], ents))

    def __getitem__(self, key):
        if isinstance(key, int) or isinstance(key, slice):
            return self.ents[key]
        else:
            return self.ent_dict.get(key, None)

    def offset(self, offset_val):
        ents = [ent.offset(offset_val) for ent in self.ents]
        return Entities(ents)

    def vectorize(self, vec_len, cate2idx):
        res_vec = np.zeros(vec_len, dtype=int)
        for ent in self.ents:
            res_vec[ent.start_pos: ent.end_pos] = cate2idx[ent.category]
        return res_vec

    def find_entities(self, start_pos, end_pos):
        res = []
        for ent in self.ents:
            if ent.start_pos > end_pos:
                break
            sp, ep = (max(start_pos, ent.start_pos), min(end_pos, ent.end_pos))
            if ep > sp:
                new_ent = Entity(ent.ent_id, ent.category, sp, ep, ent.text[:(ep - sp)])
                res.append(new_ent)
        return Entities(res)

    def merge(self):
        merged_ents = []
        for ent in self.ents:
            if len(merged_ents) == 0:
                merged_ents.append(ent)
            elif (merged_ents[-1].end_pos == ent.start_pos and
                  merged_ents[-1].category == ent.category):
                merged_ent = Entity(ent_id=merged_ents[-1].ent_id,
                                    category=ent.category,
                                    start_pos=merged_ents[-1].start_pos,
                                    end_pos=ent.end_pos,
                                    text=merged_ents[-1].text + ent.text)
                merged_ents[-1] = merged_ent
            else:
                merged_ents.append(ent)
        return Entities(merged_ents)

句子的切分和处理

data_dir = 'ruijin_round1_train2_20181022/'
ent2idx = dict(zip(ENTITIES, range(1, len(ENTITIES) + 1)))
idx2ent = dict([(v, k) for k, v in ent2idx.items()])
# print(idx2ent)
docs = Documents(data_dir=data_dir)
# ShuffleSplit() 随机排列交叉验证,生成一个用户给定数量的独立的训练/测试数据划分。样例首先被打散然后划分为一对训练测试集合。
# n_splits:划分训练集、测试集的次数,默认为10
# test_size: 测试集比例或样本数量,
# random_state:随机种子值,默认为None,可以通过设定明确的random_state,使得伪随机生成器的结果可以重复。

rs = ShuffleSplit(n_splits=1, test_size=20, random_state=2018)
train_doc_ids, test_doc_ids = next(rs.split(docs))
train_docs, test_docs = docs[train_doc_ids], docs[test_doc_ids]

num_cates = max(ent2idx.values()) + 1
sent_len = 64
vocab_size = 3000
emb_size = 100
sent_pad = 10
sent_extrator = SentenceExtractor(window_size=sent_len, pad_size=sent_pad)
train_sents = sent_extrator(train_docs)
test_sents = sent_extrator(test_docs)

train_data = Dataset(train_sents, cate2idx=ent2idx)
train_data.build_vocab_dict(vocab_size=vocab_size)

test_data = Dataset(test_sents, word2idx=train_data.word2idx, cate2idx=ent2idx)
class Sentence(object):
    """
    定义被切分的句子的类:
    text:句子的文本
    doc_id:句子所述文档id
    offset:句子相对文档的偏移距离
    ents:句子包含的实体列表
    """
    def __init__(self, doc_id, offset, text, ents):
        self.text = text
        self.doc_id = doc_id
        self.offset = offset
        self.ents = ents

    def __repr__(self):
        """
        内部魔法函数:以text显示类
        :return:
        """
        return self.text

    def __gt__(self, other):
        #内部魔法函数:按类的offset偏移距离对类进行排序
        return self.offset > other.offset

    def __getitem__(self, key):
        """
        内部魔法函数:预测结果评估时,去除句子两端延申的部分
        :param key:
        :return:
        """
        if isinstance(key, int):
            return self.text[key]
        if isinstance(key, slice):
            text = self.text[key]
            start = key.start or 0
            stop = key.stop or len(self.text)
            if start < 0:
                start += len(self.text)
            if stop < 0:
                stop += len(self.text)
            #改变实体相对于句子的偏移距离
            ents = self.ents.find_entities(start, stop).offset(-start)
            #改变句子相对于文档的偏移距离
            offset = self.offset + start
            return Sentence(self.doc_id, offset, text, ents)

    def _repr_html_(self):
        """
        内部函数:网页显示不同的实体以不同的颜色区分
        :return:
        """
        ents = []
        for ent in self.ents:
            ents.append({'start': ent.start_pos,
                         'end': ent.end_pos,
                         'label': ent.category})
        ex = {'text': self.text, 'ents': ents, 'title': None, 'settings': {}}
        return displacy.render(ex,
                               style='ent',
                               options={'colors': COLOR_MAP},
                               manual=True,
                               minify=True)

class SentenceExtractor(object):
    #句子切分器,窗口为windows,两端分别延申pad_size
    def __init__(self, window_size=50, pad_size=10):
        self.window_size = window_size
        self.pad_size = pad_size

    def extract_doc(self, doc):
        #句子切分函数,切分的时候注意每个切分的句子相对于文档的偏移距离,预测的时候还需要还原
        num_sents = math.ceil(len(doc.text) / self.window_size)
        doc = doc.pad(pad_left=self.pad_size, pad_right=num_sents * self.window_size - len(doc.text) + self.pad_size)
        sents = []
        for cur_idx in range(self.pad_size, len(doc.text) - self.pad_size, self.window_size):
            sent_text = doc.text[cur_idx - self.pad_size: cur_idx + self.window_size + self.pad_size]
            ents = []
            for ent in doc.ents.find_entities(start_pos=cur_idx - self.pad_size,
                                              end_pos=cur_idx + self.window_size + self.pad_size):
                ents.append(ent.offset(-cur_idx + self.pad_size))
            sent = Sentence(doc.doc_id,
                            offset=cur_idx - 2 * self.pad_size,
                            text=sent_text,
                            ents=Entities(ents))
            sents.append(sent)
        return sents

    def __call__(self, docs):
        #内部函数:将类当成函数形式的调用
        sents = []
        for doc in docs:
            sents += self.extract_doc(doc)
        return sents

模型的构建

def build_lstm_crf_model(num_cates, seq_len, vocab_size, model_opts=dict()):
    opts = {
        'emb_size': 256,
        'emb_trainable': True,
        'emb_matrix': None,
        'lstm_units': 256,
        'optimizer': keras.optimizers.Adam()
    }
    opts.update(model_opts)

    input_seq = Input(shape=(seq_len,), dtype='int32')
    if opts.get('emb_matrix') is not None:
        embedding = Embedding(vocab_size, opts['emb_size'], 
                              weights=[opts['emb_matrix']],
                              trainable=opts['emb_trainable'])
    else:
        embedding = Embedding(vocab_size, opts['emb_size'])
    x = embedding(input_seq)
    lstm = LSTM(opts['lstm_units'], return_sequences=True)
    x = Bidirectional(lstm)(x)
    crf = CRF(num_cates, sparse_target=True)
    output = crf(x)

    model = Model(input_seq, output)
    model.compile(opts['optimizer'], loss=crf.loss_function, metrics=[crf.accuracy])
    return model

代码和数据集:

我把代码和数据集打包了
链接:https://pan.baidu.com/s/1mvjPuoGRChTpIqCYrLB6VA
提取码:z9tz
复制这段内容后打开百度网盘手机App,操作更方便哦–来自百度网盘超级会员V3的分享

  • 15
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值