精华内容
下载资源
问答
  • bert实体关系抽取
    千次阅读
    2021-09-26 13:19:48

    BERT实体关系抽取

    序言

    项目参考了BioBERThttps://github.com/yuanxiaosc/Entity-Relation-Extraction两个BERT应用模型,BioBERT 是一种生物医学语言表示模型,专为生物医学命名实体识别、关系提取、问答等生物医学文本挖掘任务而设计。由于本项目是BERT在生物医学领域的研究与应用,因此使用了BioBERT训练好的模型作为初始模型,在Entity-Relation-Extraction项目上进行微调实现实体关系抽取。

    项目部署

    环境要求

    Pycharm、TensorFlow 1.11.0和、Python2 和或Python3(TensorFlow 1.12.0、python3.6实测可运行)

    程序目录

    --Entity-Relation-Extraction(Medical)
    	--.github
    	--bert
    	--bin
    		--evaluation
    		--predicate_classifiction
    		--subject_object_labeling
    			--ner_data
    	--output
    	--pretrained_model
    		--biobert_v1.1_pubmed
    	--raw_data
    	--produce_submit_json_file.py
    	--run_predicate_classification.py
    	--run_sequnce_labeling.py
    

    evaluation:模型评估程序文件
    predicate_classifiction:数据预处理文件
    subject_object_labeling:数据预处理文件
    output:输出文件夹
    pretrained_model:与训练模型文件
    raw_data:原始数据文件

    项目运行

    模型训练

    首先我们要了解原始数据的格式,即raw_data中数据的格式,其中包括:train_data.tsv、test1_data_postag.tsv、dev_data.tsv(训练数据、预测数据、评估数据)以及定义的关系文档relation.txt。关系文档中存放了我们定义的关系及其对应的实体类型,如({“object_type”: “treat”, “predicate”: “GENE”, “subject_type”: “DIEASE”})。

    • train_data.tsv文件数据格式:
    { "text": "SAMD11 is used to treat diabetes", "spo_list": [{"predicate": "treat", "object_type": "GENE", "subject_type": "DISEASE", "object": "SAMD11", "subject": "diabetes"}]}
    

    其中,"text"表示我们需要模型预测的一句话,"spo_list"中存放的则是这句话中所含有的关系及实体,按照定义,一种关系对应了两种实体,即<实体一,关系,实体二>,"predicate"表示的是预测出来的关系,如(treat),“object_type”、"subject_type"表示的是该关系对应的两种实体的类型,如(GENE、DIEASE),“object”、"subject"表示的这句话中的关系所对应的具体实体,如(SAMD11、diabetes)。

    • test1_data_postag.tsv文件数据格式:
    { "text": "SAMD11 is used to treat diabetes"}
    

    和train.tsv文件相比,预测数据只有需要输入模型的文本,没有预测结果值。

    run_predicate_classification.py

    该模型是对Google-Bert模型的数据处理模块及下游任务进行了微调操作,主要是预测出输入文本中具有的关系。

    在训练模型之前,我们需要先对原始数据进行数据预处理操作,将其转换成输入模型的数据格式

    python bin/predicate_classifiction/predicate_data_manager.py
    

    运行完上述数据预处理程序后会在predicate_classifiction文件夹下生成classification_data文件存放处理完成的数据,其中包括test、train、valid数据,train、dev文件夹中有:predicate_out.txt、text.txt、token_in.txt、token_in_not_UNK.txt数据,相较之下test中只是少了predicate_out.txt数据。

    • predicate_out.txt中存放的是每句话中的关系
    treat
    treat
    treat
    treat
    treat
    
    • text.txt中存放的是输入的文本语句
    SAMD11 is used to treat diabetes
    CD105 is used to treat neurodegenerative
    CD34 is used to treat cardiovascular
    Gata4 is used to treat auto-immunes diseases
    FAM41C is used to treat myocardial infarction
    
    • token_in.txt中存放的是对输入文本进行分词后的结果
    SA ##MD ##11 is used to treat diabetes
    CD ##10 ##5 is used to treat ne ##uro ##de ##gene ##rative
    CD ##34 is used to treat card ##iovascular
    G ##ata ##4 is used to treat auto - immune ##s diseases
    FA ##M ##41 ##C is used to treat my ##oc ##ard ##ial in ##far ##ction
    
    • token_in_not_UNK.txt同样也是分词后的结果
    SA ##MD ##11 is used to treat diabetes
    CD ##10 ##5 is used to treat ne ##uro ##de ##gene ##rative
    CD ##34 is used to treat card ##iovascular
    G ##ata ##4 is used to treat auto - immune ##s diseases
    FA ##M ##41 ##C is used to treat my ##oc ##ard ##ial in ##far ##ction
    

    有了处理后的数据,接下来可以进行关系标注模型run_predicate_classification.py的训练,训练参数如下:

    python run_predicate_classification.py/
    	--task_name=SKE_2019
    	--do_train=true
    	--do_eval=false
    	--data_dir=bin/predicate_classifiction/classification_data
    	--vocab_file=pretrained_model/biobert_v1.1_pubmed/vocab.txt
    	--bert_config_file=pretrained_model/biobert_v1.1_pubmed/bert_config.json
    	--init_checkpoint=pretrained_model/biobert_v1.1_pubmed/model.ckpt-1000000
    	--max_seq_length=128
    	--train_batch_size=32
    	--learning_rate=2e-5
    	--num_train_epochs=6.0
    	--output_dir=./output/predicate_classification_model/epochs6/
    

    该模型是在Google-Bert模型的基础上进行了Fine-Tuning操作,主要修改了数据预处理模块及模型的下游任务模块

    • 数据处理模块
    	//首先定义了需要标注的标签,根据实际需求添加
        def get_labels(self):
            return ['treat','cause','unlabel']
        //数据处理模块
        def convert_single_example(ex_index, example, label_list, max_seq_length,
                               tokenizer):
        """Converts a single `InputExample` into a single `InputFeatures`."""
    
        if isinstance(example, PaddingInputExample):
            return InputFeatures(
                input_ids=[0] * max_seq_length,
                input_mask=[0] * max_seq_length,
                segment_ids=[0] * max_seq_length,
                label_ids=[0] * len(label_list),
                is_real_example=False)
    
        label_map = {}
        for (i, label) in enumerate(label_list):
            label_map[label] = i
        text = example.text_a
    
        tokens_a = example.text_a.split(" ")
        tokens_b = None
        if example.text_b:
            tokens_b = tokenizer.tokenize(example.text_b)
    
        if tokens_b:
            # Modifies `tokens_a` and `tokens_b` in place so that the total
            # length is less than the specified length.
            # Account for [CLS], [SEP], [SEP] with "- 3"
            _truncate_seq_pair(tokens_a, tokens_b, max_seq_length - 3)
        else:
            # Account for [CLS] and [SEP] with "- 2"
            if len(tokens_a) > max_seq_length - 2:
                tokens_a = tokens_a[0:(max_seq_length - 2)]
        tokens = []
        segment_ids = []
        tokens.append("[CLS]")
        segment_ids.append(0)
        for token in tokens_a:
            tokens.append(token)
            segment_ids.append(0)
        tokens.append("[SEP]")
        segment_ids.append(0)
    
        if tokens_b:
            for token in tokens_b:
                tokens.append(token)
                segment_ids.append(1)
            tokens.append("[SEP]")
            segment_ids.append(1)
    
        input_ids = tokenizer.convert_tokens_to_ids(tokens)
    
        # The mask has 1 for real tokens and 0 for padding tokens. Only real
        # tokens are attended to.
        input_mask = [1] * len(input_ids)
    
        # Zero-pad up to the sequence length.
        while len(input_ids) < max_seq_length:
            input_ids.append(0)
            input_mask.append(0)
            segment_ids.append(0)
    
        assert len(input_ids) == max_seq_length
        assert len(input_mask) == max_seq_length
        assert len(segment_ids) == max_seq_length
    
        label_list = example.label.split(" ")
        label_ids = _predicate_label_to_id(label_list, label_map)
    
        if ex_index < 5:
            tf.logging.info("*** Example ***")
            tf.logging.info("guid: %s" % (example.guid))
            tf.logging.info("tokens: %s" % " ".join(
                [tokenization.printable_text(x) for x in tokens]))
            tf.logging.info("input_ids: %s" % " ".join([str(x) for x in input_ids]))
            tf.logging.info("input_mask: %s" % " ".join([str(x) for x in input_mask]))
            tf.logging.info("segment_ids: %s" % " ".join([str(x) for x in segment_ids]))
            tf.logging.info("label_ids: %s" % " ".join([str(x) for x in label_ids]))
    
        feature = InputFeatures(
            input_ids=input_ids,
            input_mask=input_mask,
            segment_ids=segment_ids,
            label_ids=label_ids,
            is_real_example=True)
        return feature
    

    值得注意的是:由于我们输入的是一句话,因此只有text_a,而text_b则定义为None,数据处理后我们得到了guid、tokens、input_ids、input_mask、segment_ids、label_ids数据,如输入:"‘SA ##MD ##11 is used to treat diabetes’":

    guid:'train-0'
    tokens:['[CLS]', 'SA', '##MD', '##11', 'is', 'used', 'to', 'treat', 'diabetes', '[SEP]']
    input_ids:[101, 13411, 18219, 14541, 1110, 1215, 1106, 7299, 17972, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
    input_mask:[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
    segment_ids:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
    label_ids:[1, 0]
    
    • 模型下游任务
    def create_model(bert_config, is_training, input_ids, input_mask, segment_ids,
                     labels, num_labels, use_one_hot_embeddings):
        """Creates a classification model."""
        model = modeling.BertModel(
            config=bert_config,
            is_training=is_training,
            input_ids=input_ids,
            input_mask=input_mask,
            token_type_ids=segment_ids,
            use_one_hot_embeddings=use_one_hot_embeddings)
          
        output_layer = model.get_pooled_output() //获取[CLS]返回的特征向量
        hidden_size = output_layer.shape[-1].value //获取特征向量的维度;Eg:768
        //创建分类概率矩阵,维度(标签个数,向量维度);Eg:(2,768)
        output_weights = tf.get_variable(
            "output_weights", [num_labels, hidden_size],
            initializer=tf.truncated_normal_initializer(stddev=0.02))
        //构建偏置矩阵
        output_bias = tf.get_variable(
            "output_bias", [num_labels], initializer=tf.zeros_initializer())
        with tf.variable_scope("loss"):
            if is_training:
                # I.e., 0.1 dropout
                output_layer = tf.nn.dropout(output_layer, keep_prob=0.9)
            logits_wx = tf.matmul(output_layer, output_weights, transpose_b=True) //输出特征矩阵乘分类权重矩阵
            logits = tf.nn.bias_add(logits_wx, output_bias) //加上偏置矩阵
            probabilities = tf.sigmoid(logits) //使用sigmoid函数做概率映射
            label_ids = tf.cast(labels, tf.float32) //将实际标签映射成指定数值序列
            per_example_loss = tf.reduce_sum(
                tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=label_ids), axis=-1) //使用交叉熵计算损失函数
            loss = tf.reduce_mean(per_example_loss)
            return loss, per_example_loss, logits, probabilities
    

    由于是预测句子中的关系,是一个简单的多分类问题,因此只用get_pooled_output()获取[CLS]标签的词向量,另外,使用了sigmoid作为激活函数将词向量映射成标签的概率值。

    模型训练完成后会在output文件夹中写入predicate_classification_model文件保存训练好的模型。

    run_sequnce_labeling.py

    该模型是对Google-Bert模型的数据处理模块及下游任务进行了微调,主要是实现实体及关系标注。

    在训练模型之前,我么们同样要先进行数据预处理操作,将其转换为模型输入的数据格式。

    python bin/subject_object_labeling/subject_object_labeling.py
    

    运行完上述数据预处理程序后会在subject_object_labeling文件夹下生成sequence_labeling_data文件存放处理完成的数据,其中包括test、train、valid数据,其中,train、valid文件夹中包括:bert_tokener_error_log.txt、text.txt、token_in.txt、token_in_not_UNK.txt、token_label_and_one_prdicate_ou.txtt数据。

    • bert_tokener_error_log.txt存放的是由于分词导致错误的数据文件
    • text.txt存放的是输入语句,若在一句话中有多个关系,则将这句话重复多遍
    SAMD11 is used to treat diabetes, but it may lead to auto-immunes diseases
    SAMD11 is used to treat diabetes, but it may lead to auto-immunes diseases
    CD105 is used to treat neurodegenerative
    CD34 is used to treat cardiovascular
    Gata4 is used to treat auto-immunes diseases
    
    • token_in.txt存放的是句子的分词结果及对应的关系
    SA ##MD ##11 is used to treat diabetes , but it may lead to my ##oc ##ard ##ial in ##far ##ction		treat
    SA ##MD ##11 is used to treat diabetes , but it may lead to my ##oc ##ard ##ial in ##far ##ction	cause
    CD ##10 ##5 is used to treat ne ##uro ##de ##gene ##rative	treat
    CD ##34 is used to treat card ##iovascular	treat
    G ##ata ##4 is used to treat auto - immune ##s diseases	treat
    
    • token_in_not_UNK.txt中存放的同样也是句子的分词结果及对应的关系
    SA ##MD ##11 is used to treat diabetes , but it may lead to my ##oc ##ard ##ial in ##far ##ction		treat
    SA ##MD ##11 is used to treat diabetes , but it may lead to my ##oc ##ard ##ial in ##far ##ction	cause
    CD ##10 ##5 is used to treat ne ##uro ##de ##gene ##rative	treat
    CD ##34 is used to treat card ##iovascular	treat
    G ##ata ##4 is used to treat auto - immune ##s diseases	treat
    
    • token_label_and_one_prdicate_out.txt中存放的是句子对应的标签及关系
    B-GENE I-GENE I-GENE O O O O B-DIEASE	treat
    B-OBJ I-OBJ I-OBJ O O O O B-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE	treat
    B-GENE I-GENE O O O O B-DIEASE I-DIEASE	treat
    B-OBJ I-OBJ I-OBJ O O O O B-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE	treat
    B-GENE I-GENE I-GENE I-GENE O O O O B-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE	treat
    

    有了处理后的数据,接下来可以进行实体及关系标注模型run_sequnce_labeling.py的训练,训练参数如下:

    --task_name=SKE_2019
    --do_train=true
    --do_eval=false
    --data_dir=bin/subject_object_labeling/sequence_labeling_data
    --vocab_file=pretrained_model/biobert_v1.1_pubmed/vocab.txt
    --bert_config_file=pretrained_model/biobert_v1.1_pubmed/bert_config.json
    --init_checkpoint=pretrained_model/biobert_v1.1_pubmed/model.ckpt-1000000
    --max_seq_length=128
    --train_batch_size=32
    --learning_rate=2e-5
    --num_train_epochs=9.0
    --output_dir=./output/sequnce_labeling_model/epochs9/
    

    该模型是对Google-Bert模型的数据预处理模块及下游任务进行了微调,进而实现实体关系标注:

    • 数据处理模块
    	//定以标注标签
        def get_token_labels(self):
            BIO_token_labels = ["[Padding]", "[category]", "[##WordPiece]", "[CLS]", "[SEP]", "B-GENE", "I-GENE", "B-DIEASE","I-DIEASE", "O",'B-SUB','I-SUB','B-OBJ','I-OBJ']  # id 0 --> [Paddding]
            return BIO_token_labels
        def get_predicate_labels(self):
            return ['treat', 'cause']
    

    该模型要同时预测实体和关系,因此定义了get_token_labels、get_predicate_labels两类标签,其中get_token_labels是对实体进行标注,get_predicate_labels是对关系进行标注。

    def convert_single_example(ex_index, example, token_label_list, predicate_label_list, max_seq_length,
                               tokenizer):
        """Converts a single `InputExample` into a single `InputFeatures`."""
        if isinstance(example, PaddingInputExample):
            return InputFeatures(
                input_ids=[0] * max_seq_length,
                input_mask=[0] * max_seq_length,
                segment_ids=[0] * max_seq_length,
                token_label_ids=[0] * max_seq_length,
                predicate_label_id = [0],
                is_real_example=False)
    
        token_label_map = {} //将token_labels标签映射成数值字典
        for (i, label) in enumerate(token_label_list):
            token_label_map[label] = i
    
        predicate_label_map = {} //将predicate_labels标签映射成数值字典
        for (i, label) in enumerate(predicate_label_list):
            predicate_label_map[label] = i
    
        text_token = example.text_token.split("\t")[0].split(" ") //获取输入的一句话
        if example.token_label is not None:
            token_label = example.token_label.split("\t")[0].split(" ") //存放分词结果对应的token_label标签
        else:
            token_label = ["O"] * len(text_token)
        assert len(text_token) == len(token_label)
    
        text_predicate = example.text_token.split("\t")[1] //获取输入一句话对应的predicate_labels标签
        if example.token_label is not None:
            token_predicate = example.token_label.split("\t")[1] //存放输入一句话对应的predicate_labels标签
        else:
            token_predicate = text_predicate
        assert text_predicate == token_predicate
    
        tokens_b = [text_predicate] * len(text_token) //存放关系标签,重复展开与text_a一样长
        predicate_id = predicate_label_map[text_predicate] //将关系标签映射成数字序列
        _truncate_seq_pair(text_token, tokens_b, max_seq_length - 3)
        tokens = []
        token_label_ids = []
        segment_ids = []
        tokens.append("[CLS]")
        segment_ids.append(0)
        token_label_ids.append(token_label_map["[CLS]"])
    
        for token, label in zip(text_token, token_label):
            tokens.append(token)
            segment_ids.append(0)
            token_label_ids.append(token_label_map[label])
    
        tokens.append("[SEP]")
        segment_ids.append(0)
        token_label_ids.append(token_label_map["[SEP]"])
    
        input_ids = tokenizer.convert_tokens_to_ids(tokens)
        #bert_tokenizer.convert_tokens_to_ids(["[SEP]"]) --->[102]
        bias = 1 //1-100 dict index not used
        for token in tokens_b:
          //将关系标签词向量加入输入语句对应的词向量中,即增加关系信息
          input_ids.append(predicate_id + bias) //add  bias for different from word dict
          segment_ids.append(1)
          token_label_ids.append(token_label_map["[category]"])
    
        input_ids.append(tokenizer.convert_tokens_to_ids(["[SEP]"])[0]) //102
        segment_ids.append(1)
        token_label_ids.append(token_label_map["[SEP]"])
    
        while len(input_ids) < max_seq_length:
            input_ids.append(0)
            input_mask.append(0)
            segment_ids.append(0)
            token_label_ids.append(0)
            tokens.append("[Padding]")
    
        assert len(input_ids) == max_seq_length
        assert len(input_mask) == max_seq_length
        assert len(segment_ids) == max_seq_length
        assert len(token_label_ids) == max_seq_length
    
        if ex_index < 5:
            tf.logging.info("*** Example ***")
            tf.logging.info("guid: %s" % (example.guid))
            tf.logging.info("tokens: %s" % " ".join(
                [tokenization.printable_text(x) for x in tokens]))
            tf.logging.info("input_ids: %s" % " ".join([str(x) for x in input_ids]))
            tf.logging.info("input_mask: %s" % " ".join([str(x) for x in input_mask]))
            tf.logging.info("segment_ids: %s" % " ".join([str(x) for x in segment_ids]))
            tf.logging.info("token_label_ids: %s" % " ".join([str(x) for x in token_label_ids]))
            tf.logging.info("predicate_id: %s" % str(predicate_id))
    
        feature = InputFeatures(
            input_ids=input_ids,
            input_mask=input_mask,
            segment_ids=segment_ids,
            token_label_ids=token_label_ids,
            predicate_label_id=[predicate_id],
            is_real_example=True)
        return feature
    

    值得注意的是:
    1、模型的text_b不再是None,存放的是该句话对应的关系标签,其长度与text_a相同,对应的token_label为[category]
    2、在input_ids输入词向量序列中加入了关系的词向量

    数据处理后我们得到了guid、tokens、input_ids、input_mask、segment_ids、token_label_ids、predicate_id数据,如输入:"‘SA ##MD ##11 is used to treat diabetes treat’“和其对应的标签”‘B-GENE I-GENE I-GENE O O O O B-DIEASE treat’":

    guid:'train-0'
    tokens:[CLS] SA ##MD ##11 is used to treat diabetes [SEP] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding]
    input_ids: 101 13411 18219 14541 1110 1215 1106 7299 17972 102 1 1 1 1 1 1 1 1 102 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    input_mask:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    segment_ids: 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    token_label_ids:3 5 6 6 9 9 9 9 7 4 1 1 1 1 1 1 1 1 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    predicate_id:0
    
    • 模型下游任务
    def create_model(bert_config, is_training, input_ids, input_mask, segment_ids,
                     token_label_ids, predicate_label_id, num_token_labels, num_predicate_labels,
                     use_one_hot_embeddings):
        """Creates a classification model."""
        model = modeling.BertModel(
            config=bert_config,
            is_training=is_training,
            input_ids=input_ids,
            input_mask=input_mask,
            token_type_ids=segment_ids,
            use_one_hot_embeddings=use_one_hot_embeddings)
        //关系预测任务
        predicate_output_layer = model.get_pooled_output()
        intent_hidden_size = predicate_output_layer.shape[-1].value
        predicate_output_weights = tf.get_variable(
            "predicate_output_weights", [num_predicate_labels, intent_hidden_size],
            initializer=tf.truncated_normal_initializer(stddev=0.02))
    
        predicate_output_bias = tf.get_variable(
            "predicate_output_bias", [num_predicate_labels], initializer=tf.zeros_initializer())
    
        with tf.variable_scope("predicate_loss"):
            if is_training:
                # I.e., 0.1 dropout
                predicate_output_layer = tf.nn.dropout(predicate_output_layer, keep_prob=0.9)
    
            predicate_logits = tf.matmul(predicate_output_layer, predicate_output_weights, transpose_b=True)
            predicate_logits = tf.nn.bias_add(predicate_logits, predicate_output_bias)
            predicate_probabilities = tf.nn.softmax(predicate_logits, axis=-1)
            predicate_prediction = tf.argmax(predicate_probabilities, axis=-1, output_type=tf.int32)
            predicate_labels = tf.one_hot(predicate_label_id, depth=num_predicate_labels, dtype=tf.float32)
            predicate_per_example_loss = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(logits=predicate_logits, labels=predicate_labels), -1)
            predicate_loss = tf.reduce_mean(predicate_per_example_loss)
         
        //实体标注任务
        token_label_output_layer = model.get_sequence_output()
    
        token_label_hidden_size = token_label_output_layer.shape[-1].value
    
        token_label_output_weight = tf.get_variable(
            "token_label_output_weights", [num_token_labels, token_label_hidden_size],
            initializer=tf.truncated_normal_initializer(stddev=0.02)
        )
        token_label_output_bias = tf.get_variable(
            "token_label_output_bias", [num_token_labels], initializer=tf.zeros_initializer()
        )
        with tf.variable_scope("token_label_loss"):
            if is_training:
                token_label_output_layer = tf.nn.dropout(token_label_output_layer, keep_prob=0.9)
            token_label_output_layer = tf.reshape(token_label_output_layer, [-1, token_label_hidden_size])
            token_label_logits = tf.matmul(token_label_output_layer, token_label_output_weight, transpose_b=True)
            token_label_logits = tf.nn.bias_add(token_label_logits, token_label_output_bias)
    
            token_label_logits = tf.reshape(token_label_logits, [-1, FLAGS.max_seq_length, num_token_labels])
            token_label_log_probs = tf.nn.log_softmax(token_label_logits, axis=-1)
            token_label_one_hot_labels = tf.one_hot(token_label_ids, depth=num_token_labels, dtype=tf.float32)
            token_label_per_example_loss = -tf.reduce_sum(token_label_one_hot_labels * token_label_log_probs, axis=-1)
            token_label_loss = tf.reduce_sum(token_label_per_example_loss)
            token_label_probabilities = tf.nn.softmax(token_label_logits, axis=-1)
            token_label_predictions = tf.argmax(token_label_probabilities, axis=-1)
            # return (token_label_loss, token_label_per_example_loss, token_label_logits, token_label_predict)
        //模型损失值计算
        loss = 0.5 * predicate_loss + token_label_loss
        return (loss,
                predicate_loss, predicate_per_example_loss, predicate_probabilities, predicate_prediction,
                token_label_loss, token_label_per_example_loss, token_label_logits, token_label_predictions)
    

    由于该模型需要对实体和关系分别进行标注,因此定义了两个模型输出参数predicate_output_layertoken_label_output_layer,以及两个全连接层predicate_losstoken_label_loss。其中,predicate_output_layer是对关系进行预测,是一个多标签分类问题,因此只需要使用get_pooled_output()获取到[CLS]标签的词向量,而token_label_output_layer则是对句子中的实体进行标注,因此需要使用get_sequence_output()获取整个句子的词向量。

    此外,还需要注意的是该模型的损失值loss是两个全连接层的损失值的综合:loss = 0.5 * predicate_loss + token_label_loss

    模型训练完之后,会在output文件夹中写入sequnce_labeling_model文件保存训练好的模型。

    模型预测

    模型预测与训练一样,先由run_predicate_classification模型预测出测试数据集中每一句话的关系,再由run_sequnce_labeling模型标注出预测句子中的实体及关系。

    run_predicate_classification.py

    我们利用训练好的模型对测试文件进行关系预测,运行参数如下:

    --task_name=SKE_2019
    --do_predict=true
    --data_dir=bin/predicate_classifiction/classification_data
    --vocab_file=pretrained_model/biobert_v1.1_pubmed/vocab.txt
    --bert_config_file=pretrained_model/biobert_v1.1_pubmed/bert_config.json
    --init_checkpoint=output/predicate_classification_model/epochs6/model.ckpt-0
    --max_seq_length=128
    --output_dir=./output/predicate_infer_out/epochs6/ckpt0
    

    值得注意的是再数据预处理模块中,由于测试数据集没有关系标签,因此为了和预测时的输入数据保持一致,需要先添加一个unlabel标签

    def _create_example(self, lines, set_type):
        """Creates examples for the training and dev sets."""
        examples = []
        for (i, line) in enumerate(lines):
            guid = "%s-%s" % (set_type, i)
            if set_type == "test":
                text_str = line
                predicate_label_str = 'unlabel'
            else:
                text_str = line[0]
                predicate_label_str = line[1]
            examples.append(
                InputExample(guid=guid, text_a=text_str, text_b=None, label=predicate_label_str))
        return examples
    

    数据处理后我们得到了guid、tokens、input_ids、input_mask、segment_ids、label_ids数据,如输入:"‘SA ##MD ##11 is used to treat diabetes’":

    guid:test-0
    tokens:['[CLS]', 'SA', '##MD', '##11', 'is', 'used', 'to', 'treat', 'diabetes', '[SEP]']
    input_ids:[101, 13411, 18219, 14541, 1110, 1215, 1106, 7299, 17972, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
    input_mask:[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
    segment_ids:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...
    label_ids:[0, 1]
    

    模型及下游任务在训练时已讲过,接下来我们看下模型的关系预测输出:

    tf.logging.info("***** Running prediction*****")
            tf.logging.info("  Num examples = %d (%d actual, %d padding)",
                            len(predict_examples), num_actual_predict_examples,
                            len(predict_examples) - num_actual_predict_examples)
            tf.logging.info("  Batch size = %d", FLAGS.predict_batch_size)
            predict_drop_remainder = True if FLAGS.use_tpu else False
            predict_input_fn = file_based_input_fn_builder(
                input_file=predict_file,
                seq_length=FLAGS.max_seq_length,
                label_length=label_length,
                is_training=False,
                drop_remainder=predict_drop_remainder)
            result = estimator.predict(input_fn=predict_input_fn)
            output_score_value_file = os.path.join(FLAGS.output_dir, "predicate_score_value.txt")
            output_predicate_predict_file = os.path.join(FLAGS.output_dir, "predicate_predict.txt")
            with tf.gfile.GFile(output_score_value_file, "w") as score_value_writer:
                with tf.gfile.GFile(output_predicate_predict_file, "w") as predicate_predict_writer:
                    num_written_lines = 0
                    tf.logging.info("***** Predict results *****")
                    for (i, prediction) in enumerate(result):
                        probabilities = prediction["probabilities"]
                        if i >= num_actual_predict_examples:
                            break
                        output_line_score_value = " ".join(
                            str(class_probability)
                            for class_probability in probabilities) + "\n"
                        predicate_predict = []
                        for idx, class_probability in enumerate(probabilities):
                            if class_probability > 0.5:
                                predicate_predict.append(label_list[idx])
                        output_line_predicate_predict = " ".join(predicate_predict) + "\n"               predicate_predict_writer.write(output_line_predicate_predict)
                        score_value_writer.write(output_line_score_value)
                        num_written_lines += 1
            assert num_written_lines == num_actual_predict_examples
    

    在预测程序中我们定义了两个预测文件predicate_score_value.txtpredicate_predict.txt,其中,predicate_score_value.txt文件用来存放模型预测出来测试数据中每一句话对应到不同关系的概率值,predicate_predict.txt文件则用来存放测试数据集中每一句话对应的关系概率值大于0.5的概率。

    模型预测完成后,会在output文件夹下新建predicate_infer_out\epochs6\ckpt0文件夹,其中存放了predicate_predict、predicate_score_value、predict三个文件:

    • predicate_predict
    treat cause
    treat unlabel
    treat
    treat
    treat unlabel
    
    • predicate_score_value
    0.5944473 0.5069371 0.498385
    0.5740756  0.498385 0.5615229
    0.5615229 0.47858068 0.47900787
    0.5729883 0.49133754 0.47858068
    0.6151916 0.5069371 0.4920553
    

    有了测试数据集中的关系预测结果,接下来就是通过run_sequnce_labeling模型对其实体及关系进行标注,在此之前,我们需要先对run_predicate_classification模型的输出结果进行预处理。

    python bin/predicate_classifiction/prepare_data_for_labeling_infer.py
    

    数据预处理的目的是将输入的测试数据按照run_predicate_classification模型预测出来的关系标签对应,如果一句话对应了多个标签则将这句话重复多遍和关系一一对应,值得注意的是句子之中预测出来的unlabel将被删除,代表这句话中没有标签。

    数据预处理完成后会在bin/subject_object_labeling/sequence_labeling_data文件夹下新建test文件夹,其中包括了text_and_one_predicate、token_in_and_one_predicate、token_in_not_UNK_and_one_predicate文档:
    text_and_one_predicate.txt中存放的是测试数据集及预测数来的关系:

    SAMD11 is used to treat diabetes, but it may lead to auto-immunes diseases	cause
    SAMD11 is used to treat diabetes, but it may lead to auto-immunes diseases	cause
    CD105 is used to treat neurodegenerative	treat
    CD34 is used to treat cardiovascular	treat
    Gata4 is used to treat auto-immunes diseases	treat
    FAM41C is used to treat myocardial infarction	treat
    

    token_in_and_one_predicate.txt中存放的是测试数据集的分词结果及对应的关系:

    SA ##MD ##11 is used to treat diabetes , but it may lead to my ##oc ##ard ##ial in ##far ##ction		treat
    SA ##MD ##11 is used to treat diabetes , but it may lead to my ##oc ##ard ##ial in ##far ##ction	cause
    CD ##10 ##5 is used to treat ne ##uro ##de ##gene ##rative	treat
    CD ##34 is used to treat card ##iovascular	treat
    G ##ata ##4 is used to treat auto - immune ##s diseases	treat
    FA ##M ##41 ##C is used to treat my ##oc ##ard ##ial in ##far ##ction	treat
    

    token_in_not_UNK_and_one_predicate.txt存放的是测试是测试数据集分词结果及关系标签:

    SA ##MD ##11 is used to treat diabetes , but it may lead to my ##oc ##ard ##ial in ##far ##ction		treat
    SA ##MD ##11 is used to treat diabetes , but it may lead to my ##oc ##ard ##ial in ##far ##ction	cause
    CD ##10 ##5 is used to treat ne ##uro ##de ##gene ##rative	treat
    CD ##34 is used to treat card ##iovascular	treat
    G ##ata ##4 is used to treat auto - immune ##s diseases	treat
    FA ##M ##41 ##C is used to treat my ##oc ##ard ##ial in ##far ##ction	treat
    

    我们注意到前两句话其实是输入测试数据集中的第一句话重复了两遍,只是因为run_predicate_classification模型预测出这句话中具有两个关系,因此将其重复两遍,目的是让run_sequnce_labeling模型对输入序列进行单一的关系抽取。

    run_sequnce_labeling.py

    我们利用训练好的模型对预处理后的数据进行实体关系抽取,运行参数如下:

    --task_name=SKE_2019
    --do_predict=true
    --data_dir=bin/subject_object_labeling/sequence_labeling_data
    --vocab_file=pretrained_model/biobert_v1.1_pubmed/vocab.txt
    --bert_config_file=pretrained_model/biobert_v1.1_pubmed/bert_config.json
    --init_checkpoint=output/sequnce_labeling_model/epochs9/model.ckpt-0
    --max_seq_length=128
    --output_dir=./output/sequnce_infer_out/epochs9/ckpt0
    

    由于该模型预测时的测试数据没有训练时的训练数据的token_labels层的实体标签,因此在数据预处理阶段将其全部标"O",和训练时相同,将"text_b"以关系标签展开成"text_a"相同长度的序列,在实际输入序列后同样加入"text_b"的序列构成"input_ids"。

    数据处理后我们得到了guid、tokens、input_ids、input_mask、segment_ids、token_label_ids、predicate_id数据,如输入:"‘SA ##MD ##11 is used to treat diabetes treat’“和其对应的标签”‘B-GENE I-GENE I-GENE O O O O B-DIEASE treat’":

    guid:test-0
    tokens:[CLS] SA ##MD ##11 is used to treat diabetes [SEP] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding] [Padding]
    input_ids:101 13411 18219 14541 1110 1215 1106 7299 17972 102 1 1 1 1 1 1 1 1 102 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    input_mask:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    segment_ids:0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    token_label_ids:3 9 9 9 9 9 9 9 9 4 1 1 1 1 1 1 1 1 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
    predicate_id:0
    

    模型及下游任务在训练时已讲,接下来我们看模型的实体关系预测和输出:

    tf.logging.info("***** Running prediction*****")
            tf.logging.info("  Num examples = %d (%d actual, %d padding)",
                            len(predict_examples), num_actual_predict_examples,
                            len(predict_examples) - num_actual_predict_examples)
            tf.logging.info("  Batch size = %d", FLAGS.predict_batch_size)
    
            predict_drop_remainder = True if FLAGS.use_tpu else False
            predict_input_fn = file_based_input_fn_builder(
                input_file=predict_file,
                seq_length=FLAGS.max_seq_length,
                is_training=False,
                drop_remainder=predict_drop_remainder)
    
            result = estimator.predict(input_fn=predict_input_fn)
            token_label_output_predict_file = os.path.join(FLAGS.output_dir, "token_label_predictions.txt")
            predicate_output_predict_file = os.path.join(FLAGS.output_dir, "predicate_predict.txt")
            predicate_output_probabilities_file = os.path.join(FLAGS.output_dir, "predicate_probabilities.txt")
            with open(token_label_output_predict_file, "w", encoding='utf-8') as token_label_writer:
                with open(predicate_output_predict_file, "w", encoding='utf-8') as predicate_predict_writer:
                    with open(predicate_output_probabilities_file, "w", encoding='utf-8') as predicate_probabilities_writer:
                        num_written_lines = 0
                        tf.logging.info("***** token_label predict and predicate labeling results *****")
                        for (i, prediction) in enumerate(result):
                            token_label_prediction = prediction["token_label_predictions"]
                            predicate_probabilities = prediction["predicate_probabilities"]
                            predicate_prediction = prediction["predicate_prediction"]
                            if i >= num_actual_predict_examples:
                                break
                            token_label_output_line = " ".join(token_label_id2label[id] for id in token_label_prediction) + "\n"
                            token_label_writer.write(token_label_output_line)
                            predicate_predict_line = predicate_label_id2label[predicate_prediction]
                            predicate_predict_writer.write(predicate_predict_line + "\n")
                            predicate_probabilities_line = " ".join(str(sigmoid_logit) for sigmoid_logit in predicate_probabilities) + "\n"
                            predicate_probabilities_writer.write(predicate_probabilities_line)
                            num_written_lines += 1
            assert num_written_lines == num_actual_predict_examples
    

    模型运行后会在output文件夹下新建sequnce_infer_out\epochs9\ckpt0文件,包括predicate_predict、predicate_probabilities、token_label_predictions三个文件,其中predicate_predict.txtpredicate_probabilities.txt是对测试数据集中句子关系的预测文件,token_label_predictions则是对输入句子进行实体标注的文件。

    predicate_probabilities.txt文件中存放的是每一个句子映射到全部关系标签上的概率值(总共有3个标签,对应3个概率值):

    0.5944473 0.5069371 0.498385
    0.5740756  0.498385 0.5615229
    0.5615229 0.47858068 0.47900787
    0.5729883 0.49133754 0.47858068
    0.6151916 0.5069371 0.4920553
    

    predicate_predict.txt文件中存放的是一个句子对应所有标签概率中最大的概率所对应的标签:

    treat
    cause
    treat
    treat
    treat
    treat
    

    token_label_predictions.txt文件中存放的是对输入测试数据集的实体标签:

    B-GENE I-GENE I-GENE O O O O B-DIEASE
    B-OBJ I-OBJ I-OBJ O O O O B-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE	
    B-GENE I-GENE O O O O B-DIEASE I-DIEASE
    B-OBJ I-OBJ I-OBJ O O O O B-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE	
    B-GENE I-GENE I-GENE I-GENE O O O O B-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE I-DIEASE
    

    至此,我们对输入的测试数据集进行了关系及对应的实体标注,获得了每一句话对应的关系及关系对应的实体,接下来通过运行produce_submit_json_file.py将模型输出结果进行组合成最终的输出结果:

    python produce_submit_json_file.py
    

    程序运行完将在output文件夹下新建final_text_spo_list_result文件夹存放最终结果keep_empty_spo_list_subject_predicate_object_predict_output.txt,该文档中保存了预测数据集对应的实体及关系:

    { "text": "SAMD11 is used to treat diabetes", "spo_list": [{"predicate": "treat", "object_type": "GENE", "subject_type": "DISEASE", "object": "SAMD11", "subject": "diabetes"}]}
    { "text": "CD105 is used to treat neurodegenerative", "spo_list": [{"predicate": "treat", "object_type": "GENE", "subject_type": "DISEASE", "object": "CD105", "subject": "neurodegenerative"}]}
    { "text": "CD34 is used to treat cardiovascular", "spo_list": [{"predicate": "treat", "object_type": "GENE", "subject_type": "DISEASE", "object": "CD34", "subject": "cardiovascular"}]}
    { "text": "Gata4 is used to treat auto-immunes diseases", "spo_list": [{"predicate": "treat", "object_type": "GENE", "subject_type": "DISEASE", "object": "Gata4", "subject": "auto-immunes diseases"}]}
    { "text": "FAM41C is used to treat myocardial infarction", "spo_list": [{"predicate": "treat", "object_type": "GENE", "subject_type": "DISEASE", "object": "FAM41C", "subject": "myocardial infarction"}]}
    

    总结

    整个项目使用了两个模型run_predicate_classification.pyrun_sequnce_labeling.py,都是在Google-Bert模型的基础上进行了微调,其中,第一个模型主要是用来对句子中的关系进行初步预测,将一个句子中含有的多种关系转换成句子与关系一一对应的形式,第二个模型主要是进行实体及关系抽取,因此定义了两个下游任务,分别进行实体和关系的预测,再通过数据处理操作将模型的预测结果组合成需要的形式。

    项目使用了BioBERT训练好的模型作为初始模型,在Entity-Relation-Extraction项目上进行微调实现实体关系抽取在医学领域的应用。项目模型下载:Entity-Relation-Extraction(Medical).zip,如有侵权请及时私信删除!

    更多相关内容
  • 基于Bert实体关系抽取模型

    千次阅读 2021-05-10 19:24:09
    信息抽取(Information Extraction, IE)是从自然语言文本中抽取实体、属性、关系及事件等事实类信息的文本处理技术,是信息检索、智能问答、智能对话等人工智能应用的重要基础,一直受到业界的广泛关注。信息抽取任务...

    关注微信公众号:NLP分享汇。【喜欢的扫波关注,每天都在更新自己之前的积累】

    文章链接:https://mp.weixin.qq.com/s/OebxnvwjQiVbBZZFL2Un3A


    前言

    信息抽取(Information Extraction, IE)是从自然语言文本中抽取实体、属性、关系及事件等事实类信息的文本处理技术,是信息检索、智能问答、智能对话等人工智能应用的重要基础,一直受到业界的广泛关注。信息抽取任务涉及命名实体识别、指代消解、关系分类等复杂技术,极具挑战性。而本文旨在介绍如何利用Bert预训练模型进行关系抽取任务。

     

    相关链接

    GitHub:https://github.com/yuanxiaosc/Entity-Relation-Extraction

    竞赛官网:http://lic2019.ccf.org.cn/

    理解难度:★★★★

    解决思路

    1、先使用bert搭建的关系分类模型,简单来看就是一个多标签分类任务,类别就是下述的那几种关系;

    2、接着用预测出来的关系和文本,使用bert搭建一个实体抽取的模型,其简单来看也是一个分类模型,类别是:

    ["[Padding]", "[category]", "[##WordPiece]", "[CLS]", "[SEP]", "B-SUB", "I-SUB", "B-OBJ", "I-OBJ", "O"]

    【 SUB对应的就是subject,B-SUB就是第一个实体开始的位置,后续的是I-SUB,OBJ就是第二个实体 】

    关系类型如下:

    ['丈夫', '上映时间', '专业代码', '主持人', '主演', '主角', '人口数量', '作曲', '作者', '作词', '修业年限', '出品公司', '出版社', '出生地', '出生日期', '创始人', '制片人', '占地面积', '号', '嘉宾', '国籍', '妻子', '字', '官方语言', '导演', '总部地点', '成立日期', '所在城市', '所属专辑', '改编自', '朝代', '歌手', '母亲', '毕业院校', '民族', '气候', '注册资本', '海拔', '父亲', '目', '祖籍', '简称', '编剧', '董事长', '身高', '连载网站', '邮政编码', '面积', '首都']

     

    思路分析

    • 所以第二个模型就是预测每一个tokens的标示,最后根据标示可提取出实体对。

    • 第二个模型是一个多分类任务,我们知道一句话中有可能有多个三元组,为此在进行第二个模型的时候,是先依据第一个模型预测出来的关系类如当前句子预测出3个关系,那么就重复该句话分成3个样本,那么3个样本就对应的是3个多分类单标签任务,为了使实体对和关系对应,所以第二个模型在计算loss的时候是综合考虑了关系和tokens标示的预测的。

    • 说到这里可能会有这样的疑惑?两个模型(所谓的管道),似乎没有交集,都是先处理准备好各自数据,然后各自训练各自的,而且第二个模型同时进行了关系和实体标示预测,那么第一个模型只预测了关系,那么第一个模型存在的意义是什么?直接用第二模型不就可以啦?

      • 逻辑是这样的:

        训练确实没有交集,各自训练各自的,因为训练样本都是精确的,无可厚非,但是在预测的时候我们只有一句话,要预测出这句话中的所有三元组,如果只采用第二个模型的话,它一句话根据当前的关系只能预测出一种三元组,所以需要第一个模型打前阵,先将句子中有多少种关系预测出来,然后再将句子依照关系分成一句话一个三元组,训练的时候我们是知道每一个句子有多少种关系,但是预测的时候我们并不知道,这就是第一个模型存在的意义,那么我就要用第二个模型同时解决一句话预测所有三元组呢?其实很难,因为首先tokens标记就是问题,怎么将标示和关系对应呢?是吧,所以这里所谓管道,其实是在预测过程体现的,下面就可以看到另外按照上面的思路,该模型bert的输入端一个样本的特征应该是当前句子和关系,而不仅仅是一个句子,不然的话将一句话拆分成多个样本还有什么意义?大家不就都一样啦,正是由于还有关系,才能实现同一句子抽取出不同的实体对(对应当前关系)。

    • 那么输入端是怎么将将关系整合进去的呢?

      • 解决办法是这样的:

        将关系作为当前句子的下句,还记得text_b吗?第一个模型设为了None,这里将类别平铺成和text_a一样长度,当成一句话作为text_a(当前句子)的下文,该部分具体看convert_single_example:

     

    核心代码部分

    模型一:bert搭建的关系分类模型

    def create_model(bert_config, is_training, input_ids, input_mask, segment_ids,                 labels, num_labels, use_one_hot_embeddings):    """Creates a classification model."""    model = modeling.BertModel(        config=bert_config,        is_training=is_training,        input_ids=input_ids,        input_mask=input_mask,        token_type_ids=segment_ids,        use_one_hot_embeddings=use_one_hot_embeddings)    # In the demo, we are doing a simple classification task on the entire    # segment.    #    # If you want to use the token-level output, use model.get_sequence_output()    # instead.    output_layer = model.get_pooled_output()    hidden_size = output_layer.shape[-1].value    output_weights = tf.get_variable(        "output_weights", [num_labels, hidden_size],        initializer=tf.truncated_normal_initializer(stddev=0.02))    output_bias = tf.get_variable(        "output_bias", [num_labels], initializer=tf.zeros_initializer())    with tf.variable_scope("loss"):        if is_training:            # I.e., 0.1 dropout            output_layer = tf.nn.dropout(output_layer, keep_prob=0.9)        logits_wx = tf.matmul(output_layer, output_weights, transpose_b=True)        logits = tf.nn.bias_add(logits_wx, output_bias)        probabilities = tf.sigmoid(logits)        label_ids = tf.cast(labels, tf.float32)        per_example_loss = tf.reduce_sum(            tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=label_ids), axis=-1)        loss = tf.reduce_mean(per_example_loss)        return loss, per_example_loss, logits, probabilities

     

    模型二:bert搭建一个实体抽取模型

    1、关系预测部分

        predicate_output_layer = model.get_pooled_output()    intent_hidden_size = predicate_output_layer.shape[-1].value    predicate_output_weights = tf.get_variable(        "predicate_output_weights", [num_predicate_labels, intent_hidden_size],        initializer=tf.truncated_normal_initializer(stddev=0.02))    predicate_output_bias = tf.get_variable(        "predicate_output_bias", [num_predicate_labels], initializer=tf.zeros_initializer())    with tf.variable_scope("predicate_loss"):        if is_training:            # I.e., 0.1 dropout            predicate_output_layer = tf.nn.dropout(predicate_output_layer, keep_prob=0.9)        predicate_logits = tf.matmul(predicate_output_layer, predicate_output_weights, transpose_b=True)        predicate_logits = tf.nn.bias_add(predicate_logits, predicate_output_bias)        predicate_probabilities = tf.nn.softmax(predicate_logits, axis=-1)        predicate_prediction = tf.argmax(predicate_probabilities, axis=-1, output_type=tf.int32)        predicate_labels = tf.one_hot(predicate_label_id, depth=num_predicate_labels, dtype=tf.float32)        predicate_per_example_loss = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(logits=predicate_logits, labels=predicate_labels), -1)        predicate_loss = tf.reduce_mean(predicate_per_example_loss)

    2、序列标注部分

        token_label_output_layer = model.get_sequence_output()    token_label_hidden_size = token_label_output_layer.shape[-1].value    token_label_output_weight = tf.get_variable(        "token_label_output_weights", [num_token_labels, token_label_hidden_size],        initializer=tf.truncated_normal_initializer(stddev=0.02)    )    token_label_output_bias = tf.get_variable(        "token_label_output_bias", [num_token_labels], initializer=tf.zeros_initializer()    )    with tf.variable_scope("token_label_loss"):        if is_training:            token_label_output_layer = tf.nn.dropout(token_label_output_layer, keep_prob=0.9)        token_label_output_layer = tf.reshape(token_label_output_layer, [-1, token_label_hidden_size])        token_label_logits = tf.matmul(token_label_output_layer, token_label_output_weight, transpose_b=True)        token_label_logits = tf.nn.bias_add(token_label_logits, token_label_output_bias)        token_label_logits = tf.reshape(token_label_logits, [-1, FLAGS.max_seq_length, num_token_labels])        token_label_log_probs = tf.nn.log_softmax(token_label_logits, axis=-1)        token_label_one_hot_labels = tf.one_hot(token_label_ids, depth=num_token_labels, dtype=tf.float32)        token_label_per_example_loss = -tf.reduce_sum(token_label_one_hot_labels * token_label_log_probs, axis=-1)        token_label_loss = tf.reduce_sum(token_label_per_example_loss)        token_label_probabilities = tf.nn.softmax(token_label_logits, axis=-1)        token_label_predictions = tf.argmax(token_label_probabilities, axis=-1)        # return (token_label_loss, token_label_per_example_loss, token_label_logits, token_label_predict)    loss = 0.5 * predicate_loss + token_label_loss
    展开全文
  • 使用Bert完成实体之间关系抽取

    千次阅读 2022-01-21 00:52:58
    向AI转型的程序员都关注了这个号????????????机器学习AI算法工程 公众号:datayx大创所需,所以写了一个模型用来完成关系抽取。最后在百度DuIE数据集的完整测试集上达到95.37%正确率...

    38a746a6f0a7e9ef23c60940664865e7.gif

    向AI转型的程序员都关注了这个号👇👇👇

    机器学习AI算法工程   公众号:datayx

    大创所需,所以写了一个模型用来完成关系抽取。

    最后在百度DuIE数据集的完整测试集上达到95.37%正确率

    效果:

    Source Text:  《在夏天冬眠》是容祖儿演唱的一首歌曲,收录于专辑《独照》中

    Entity1:  独照  Entity2:  在夏天冬眠  Predict Relation:  所属专辑  True Relation:  所属专辑

    Source Text:  2.花千骨花千骨是由慈文传媒集团制作并发行,高林豹、林玉芬、梁胜权联合执导,霍建华 、赵丽颖领衔主演,蒋欣、杨烁、张丹峰、马可、徐海乔、李纯等主演的古装仙侠玄幻 仙侠剧

    Entity1:  赵丽颖  Entity2:  花千骨  Predict Relation:  主演  True Relation:  主演

    Source Text:  在与王祖贤恋爱期间的齐秦,也是他事业最辉煌的时期,《大约在冬季》《无情的雨》《不让我的眼泪陪我过夜》《如果云知道》《夜夜夜夜》等经典曲目都是他为王祖贤所创作的,从这些歌也能感受到两个人是真爱,但是为什么就是没有一个结果呢

    Entity1:  齐秦  Entity2:  大约在冬季  Predict Relation:  歌手  True Relation:  歌手

    Source Text:  《甜蜜与厮杀》是连载在红袖添香网上的一部奇幻魔法小说,作者是kijimi1

    Entity1:  kijimi1  Entity2:  甜蜜与厮杀  Predict Relation:  作者  True Relation:  作者

    使用方法

    代码 获取方式:

    关注微信公众号 datayx  然后回复 关系抽取 即可获取。

    准备

    1. 将DUIE文件路径放置于代码同目录(或者自己的数据,具体可见loader.py),更加具体的获取和数据处理见下文

    2. 将bert-base-chinese放置于同目录下的bert-base-chinese下或者自行指定位置

    3. 安装pytorch,cuda,transformer,numpy等组件(实际测试可运行环境为pytorch=1.5.1 transformers=2.5.1)

    train and eval

    (注意,在此之前,请做好数据的获取和预处理,步骤见文)

    python3 main.py执行训练,并得到Fine-Tuing后的BERT

    python3 demo.py得到样例输出,或自行阅读代码,修改test函数的传入参数内容即可自定义。

    如果仅用于测试和实际使用,可以下载已经训练好的Model,然后调用demo.py下对应函数

    caculate_acc:计算每一个类别的正确率

    demo_output:随机选择样本,输出原文,实体对以及预测的关系,即实例输出

    Model download(92.5%正确率的)

    地址:https://pan.baidu.com/s/123qVcRa5SBKcMBLWxP5bKQ

    提取码:bert

    Model download(95.37%正确率的)

    链接:https://pan.baidu.com/s/1ffOzN3FZ1foepB6NcSF5qQ 提取码:bert

    数据

    数据使用的是百度发布的DUIE数据,包含了实体识别和关系抽取

    原数据地址:https://ai.baidu.com/broad/download?dataset=dureader

    打开后在左侧栏选择knowledge extraction,然后如下界面点击下载train_data.json和dev_data.json,然后放到对应的位置

    运行loader.py里的prepare_data,观察到目录里生成了train.json和dev.json

    截止这里,数据的预处理完成了,可以运行main和demo

    cb29a2192235f6c2e4484f903178cc8a.png

    我对数据进行了预处理,提取关系抽取需要的部分

    关系设定有49类,还是非常的丰富的

    id2rel={0: 'UNK', 1: '主演', 2: '歌手', 3: '简称', 4: '总部地点', 5: '导演', 

            6: '出生地', 7: '目', 8: '出生日期', 9: '占地面积', 10: '上映时间',

            11: '出版社', 12: '作者', 13: '号', 14: '父亲', 15: '毕业院校', 

            16: '成立日期', 17: '改编自', 18: '主持人', 19: '所属专辑', 

            20: '连载网站', 21: '作词', 22: '作曲', 23: '创始人', 24: '丈夫', 

            25: '妻子', 26: '朝代', 27: '民族', 28: '国籍', 29: '身高', 30: '出品公司', 

            31: '母亲', 32: '编剧', 33: '首都', 34: '面积', 35: '祖籍', 36: '嘉宾', 

            37: '字', 38: '海拔', 39: '注册资本', 40: '制片人', 41: '董事长', 42: '所在城市',

            43: '气候', 44: '人口数量', 45: '邮政编码', 46: '主角', 47: '官方语言', 48: '修业年限'}   

    数据的格式如下,ent1和ent2是实体,rel是关系

    9b181be846149fded0c4c12b9aad9a9d.png

    Model

    模型就是直接使用Bert用于序列分类的(BertEncoder+Fc+CrossEntropy)

    具体的处理就是把ent1,ent2和sentence直接拼接送进模型

    相对我之前对Bert的粗糙处理,这里加上了MASK-Attention一起送进模型

    Result

    从百度的原数据中选择20000条,测试数据2000条(原数据相对很小的一部分)

    训练参数:10 Epoch,0.001学习率,设置label共有49种(包含UNK,代表新关系和不存在关系)

    然后在训练前和训练后的分别在测试数据上测试,可以看到Fine-Tuing高度有效

    测试集正确率达到 92.5%

    修正:后来在所有的数据上训练和测试,测试数据36w,测试数据4w,eval正确率95+%

    dc1dbf6ca1910192d0c3152cfe50aebc.png

    实际测试

    在数据中抽取一部分实际测试

    效果不错

    db062d8a58ae729229bce6fd74327e6e.png

    机器学习算法AI大数据技术

     搜索公众号添加: datanlp

    23a4d1dc71dcbd0c65bf53bf0891c2a8.png

    长按图片,识别二维码


    阅读过本文的人还看了以下文章:

    TensorFlow 2.0深度学习案例实战

    基于40万表格数据集TableBank,用MaskRCNN做表格检测

    《基于深度学习的自然语言处理》中/英PDF

    Deep Learning 中文版初版-周志华团队

    【全套视频课】最全的目标检测算法系列讲解,通俗易懂!

    《美团机器学习实践》_美团算法团队.pdf

    《深度学习入门:基于Python的理论与实现》高清中文PDF+源码

    《深度学习:基于Keras的Python实践》PDF和代码

    特征提取与图像处理(第二版).pdf

    python就业班学习视频,从入门到实战项目

    2019最新《PyTorch自然语言处理》英、中文版PDF+源码

    《21个项目玩转深度学习:基于TensorFlow的实践详解》完整版PDF+附书代码

    《深度学习之pytorch》pdf+附书源码

    PyTorch深度学习快速实战入门《pytorch-handbook》

    【下载】豆瓣评分8.1,《机器学习实战:基于Scikit-Learn和TensorFlow》

    《Python数据分析与挖掘实战》PDF+完整源码

    汽车行业完整知识图谱项目实战视频(全23课)

    李沐大神开源《动手学深度学习》,加州伯克利深度学习(2019春)教材

    笔记、代码清晰易懂!李航《统计学习方法》最新资源全套!

    《神经网络与深度学习》最新2018版中英PDF+源码

    将机器学习模型部署为REST API

    FashionAI服装属性标签图像识别Top1-5方案分享

    重要开源!CNN-RNN-CTC 实现手写汉字识别

    yolo3 检测出图像中的不规则汉字

    同样是机器学习算法工程师,你的面试为什么过不了?

    前海征信大数据算法:风险概率预测

    【Keras】完整实现‘交通标志’分类、‘票据’分类两个项目,让你掌握深度学习图像分类

    VGG16迁移学习,实现医学图像识别分类工程项目

    特征工程(一)

    特征工程(二) :文本数据的展开、过滤和分块

    特征工程(三):特征缩放,从词袋到 TF-IDF

    特征工程(四): 类别特征

    特征工程(五): PCA 降维

    特征工程(六): 非线性特征提取和模型堆叠

    特征工程(七):图像特征提取和深度学习

    如何利用全新的决策树集成级联结构gcForest做特征工程并打分?

    Machine Learning Yearning 中文翻译稿

    蚂蚁金服2018秋招-算法工程师(共四面)通过

    全球AI挑战-场景分类的比赛源码(多模型融合)

    斯坦福CS230官方指南:CNN、RNN及使用技巧速查(打印收藏)

    python+flask搭建CNN在线识别手写中文网站

    中科院Kaggle全球文本匹配竞赛华人第1名团队-深度学习与特征工程

    不断更新资源

    深度学习、机器学习、数据分析、python

     搜索公众号添加: datayx  

    d02790bc1401d4d975476b5c1409c995.png

    展开全文
  • 实体关系抽取作为信息抽取、自然语言理解、信息检索等领域的核心任务和重要环节,能够从文本中抽取实体对间的语义关系.近年来,深度学习在联合学习、远程监督等方面上的应用,使关系抽取任务取得了较为丰富的研究成果....
  • 利用Bert进行关系抽取

    2022-05-09 15:33:18
    Bert模型是谷歌2018年10月底公布的,反响巨大,效果不错,在各大比赛上面出类拔萃,它的提出主要是针对word2vec等模型的不足,在之前的预训练模型(包括word2vec,ELMo等)都会生成词向量,这种类别的预训练模型属于...

    Bert模型是谷歌2018年10月底公布的,反响巨大,效果不错,在各大比赛上面出类拔萃,它的提出主要是针对word2vec等模型的不足,在之前的预训练模型(包括word2vec,ELMo等)都会生成词向量,这种类别的预训练模型属于domain transfer。而近一两年提出的ULMFiT,GPT,BERT等都属于模型迁移,简单说BERT 模型是将预训练模型和下游任务模型结合在一起的,核心目的就是:是把下游具体NLP任务的工作逐渐移到预训练产生词向量上。

    在使用的时候(链接: https://pan.baidu.com/s/1Mv7iVBaBNEusd7qqbOkYow 提取码: 6inw),或者https://github.com/yuanxiaosc/Entity-Relation-Extraction),一般是需要下面三个脚本的,我们也不必修改,直接拿过来使用就ok。
    modeling.py
    optimization.py
    tokenization.py

    其中tokenization是对原始句子内容的解析,分为BasicTokenizer和WordpieceTokenizer两个,一般来说BasicTokenizer

    主要是进行unicode转换、标点符号分割、中文字符分割、去除重音符号等操作,最后返回的是关于词的数组(中文是字的数组),WordpieceTokenizer的目的是将合成词分解成类似词根一样的词片。例如将"unwanted"分解成["un", "##want", "##ed"]这么做的目的是防止因为词的过于生僻没有被收录进词典最后只能以[UNK]代替的局面,因为英语当中这样的合成词非常多,词典不可能全部收录。FullTokenizer的作用就很显而易见了,对一个文本段进行以上两种解析,最后返回词(字)的数组,同时还提供token到id的索引以及id到token的索引。这里的token可以理解为文本段处理过后的最小单元。上述来源https://www.jianshu.com/p/22e462f01d8c,更多该脚本的内容可以看该链接,下面主要用到FullTokenizer这个类。

    真正需要修改是:
    run_classifier.py
    run_squad.py

    分别是解决分类,读理解任务,其实套路差不多,我们具体来看一下run_classifier.py。

    首先BERT主要分为两个部分。一个是训练语言模型(language model)的预训练(run_pretraining.py)部分。另一个是训练具体任务(task)的fine-tune部分,预训练部分巨大的运算资源,但是其已经公布了BERT的预训练模型。

    这里需要中文,直接下载就行,总得来说,我们要做的就是自己的数据集上进行fine-tune。

    run_classifier.py中的类如下

    InputExample类主要定义了一些数据预处理后要生成的字段名,如下:

    guid就是一个id号,一般将数据处理成train、dev、test数据集,那么这里定义方式就可以是相应的数据集+行号(句子),text_a 就是当前的句子,text_b是另一个句子,因为有的任务需要两个两个句子,如果任务中没有的话,可以将text_b设为None,label就是标签,InputFeatures类主要是定义了bert的输入格式,形象化点就是特征,即上面的格式使我们需要将原始数据处理成的格式,但并不是bert使用的最终格式,且还会通过一些代码将InputExample转化为InputFeatures,这才是bert最终使用的数据格式,当然啦这里根据自己的需要还可以自定义一些字段作为中间辅助字段,但bert最基本的输入字段就需要input_ids,input_mask和segment_ids这三个字段,label_id是计算loss时候用到的,input_ids,segment_ids分别对应单词id和句子(上下句标示),Input_mask就是记录的是填充信息,具体看下面:

    DataProcessor,这是一个数据预处理的基类,里面定义了一些基本方法。XnliProcessor、MnliProcessor、MrpcProcessor、ColaProcessor四个类是对DataProcessor的具体实现,这里之所以列举了四个是尽可能多的给用户呈现出各种demo,具体到实际使用的时候我们只需要参考其写法,定义一个自己的数据预处理类即可,其中一般包括如下几个方法:get_train_examples,get_dev_examples,get_test_examples,get_labels,_create_examples。其中前三个都通过调用_create_examples返回一个InputExample类数据结构,get_labels就是返回类别,所以重点就是以下两个函数:

    这里的tokenization的convert_to_unicode就是将文本转化为utf-8编码。

    上述就是数据预处理过程,也是需要我们自己根据自己的数据定义的,其实呢,这并不是Bert使用的最终样子,其还得经过一系列过程才能变成其能处理的数据格式,该过程是通过接下来的四个方法完成的:
    convert_single_example
    file_based_convert_examples_to_features
    file_based_input_fn_builder
    truncate_seq_pair

    只不过一般情况下我们不需要修改,它都是一个固定的流程
    convert_single_example:

    bert的输入:

    代码中的input_ids,segment_ids分别代表token,segment,同时其还在句子的开头结尾加上了[CLS]和SEP]标示。

     input_ids中就是记录的是使用FullTokenizer类convert_tokens_to_ids方法将tokens转化成单个字的id。segment_ids就是句子级别(上下句)的标签,大概形式:

    # (a) For sequence pairs:
     #  tokens:   [CLS] is this jack ##son ##ville ? [SEP] no it is not . [SEP]
     #  type_ids: 0     0  0    0    0     0       0 0     1  1  1  1   1 1
     # (b) For single sequences:
     #  tokens:   [CLS] the dog is hairy . [SEP]
     #  type_ids: 0     0   0   0  0     0 0

    当没有text_b的时候,就都是0。还有一个input_mask,其就是和最大长度有关,假设我们定义句子的最大长度是120,当前句子长度是100,那么input_mask前100个元素都是1,其余20个就是0。最后返回的就是一个InputFeatures类。

    file_based_convert_examples_to_features

    很简单啦,因为在训练的时候为了读写快速方便便将数据制作成TFrecords 数据格式,该函数主要就是将上述返回的InputFeatures类数据,保存成一个TFrecords数据格式,关于TFrecords数据格式的制作可以参考这篇《TFrecords 制作数据集小例子(多标签)》
    file_based_input_fn_builder

    对应的就是从TFrecords 解析读取数据。
    truncate_seq_pair

    就是来限制text_a和text_b总长度的,当超过的话,会轮番pop掉tokens。

    至此整个数据的预处理才算处理好,其实最后最关键的就是得到了那个TFrecords文件

    下面看模型部分
    create_model
    model_fn_builder

    整个模型过程采用了tf.contrib.tpu.TPUEstimator这一高级封装的API

    model_fn_builder是壳,create_model是核心,其内部定义了loss,预测概率以及预测结果等等。
    model_fn_builder

    其首先调用create_model得到total_loss, per_example_loss, logits, probabilities等等,然后针对不同的状态返回不同的结果(output_spec),如果是train则返回loss,train_op等,如果是dev则返回一些评价指标如accuracy,如果是test则返回预测结果。下一步

    create_model

    这里可以说整个Bert使用的最关键的地方,我们使用Bert大多数情况无非进行在定义自己的下游工作进行fine-tune,就是在这里定义的。见下面的代码:

    def create_model(bert_config, is_training, input_ids, input_mask, segment_ids,
                     labels, num_labels, use_one_hot_embeddings):
      """Creates a classification model."""
      model = modeling.BertModel(
          config=bert_config,
          is_training=is_training,
          input_ids=input_ids,
          input_mask=input_mask,
          token_type_ids=segment_ids,
          use_one_hot_embeddings=use_one_hot_embeddings)
     
      # In the demo, we are doing a simple classification task on the entire
      # segment.
      #
      # If you want to use the token-level output, use model.get_sequence_output()
      # instead.
      output_layer = model.get_pooled_output()
     
      hidden_size = output_layer.shape[-1].value
     
      output_weights = tf.get_variable(
          "output_weights", [num_labels, hidden_size],
          initializer=tf.truncated_normal_initializer(stddev=0.02))
     
      output_bias = tf.get_variable(
          "output_bias", [num_labels], initializer=tf.zeros_initializer())
     
      with tf.variable_scope("loss"):
        if is_training:
          # I.e., 0.1 dropout
          output_layer = tf.nn.dropout(output_layer, keep_prob=0.9)
     
        logits = tf.matmul(output_layer, output_weights, transpose_b=True)
        logits = tf.nn.bias_add(logits, output_bias)
        probabilities = tf.nn.softmax(logits, axis=-1)
        log_probs = tf.nn.log_softmax(logits, axis=-1)
     
        one_hot_labels = tf.one_hot(labels, depth=num_labels, dtype=tf.float32)
     
        per_example_loss = -tf.reduce_sum(one_hot_labels * log_probs, axis=-1)
        loss = tf.reduce_mean(per_example_loss)
     
        return (loss, per_example_loss, logits, probabilities)

    首先调用modeling.BertModel得到bert模型,bert模型的输入:input_ids,input_mask,segment_ids

    model = modeling.BertModel(
          config=bert_config,
          is_training=is_training,
          input_ids=input_ids,
          input_mask=input_mask,
          token_type_ids=segment_ids,
          use_one_hot_embeddings=use_one_hot_embeddings

    config是bert的配置文件,在开头下载的中文模型中里面有,直接加载即可,use_one_hot_embeddings是根据是不是用GPU而定的,其他字段上述都说过。

    bert模型的输出其有两种情况:

    model.get_sequence_output()
    model.get_pooled_output()

    第一种输出结果是[batch_size, seq_length, embedding_size]

    第二种输出结果是[batch_size, embedding_size]

    第二种结果是第一种结果在第二个维度上面进行了池化,要是形象点比喻的话,第一种结果得到是tokens级别的结果,第二种是句子级别的,其实就是一个池化。

    需要我们我们定义部分

    这部分就是需要我们根据自己的任务自己具体定义,假设是一个简单的分类,那么就是定义一个全连接层将其转化为[batch_size, num_classes],output_weights和output_bias就是对应全连接成的权值,后面就是loss,使用了tf.nn.log_softmax应该是一个多分类,多标签的话可以使用tf.nn.sigmoid。总得来说,使用bert进行自己任务的时候,可以千变万化,变的就是这里这个下游。
    最后就是主函数(main),主要就是通过人为定义的一些配置值(FLAGS)将上面的流程整个组合起来,这里大体说一下流程:

    processors = {
          "cola": ColaProcessor,
          "mnli": MnliProcessor,
          "mrpc": MrpcProcessor,
          "xnli": XnliProcessor,
      }

    这里就是定义数据预处理器的,记得把自己定义的预处理包含进来,自己起一个名字,到时候通过外部参数字段task_name来指定用哪个(简单说就是处理哪个数据)。

    数据预处理完了,就使用tf.contrib.tpu.TPUEstimator定义模型:

     最后就是根据不同模式(train/dev/test,这也是运行时可以指定的)运行estimator.train,estimator.evaluate,estimator.predict。

    总结:

    1. 总体来说,在进行具体工作时,需要改的核心就是:

           1) 继承DataProcessor定义一个自己的数据预处理类;

           2) 在create_model中定义自己的具体下游工作。

    2. 关于bert上游的具体模型定义这里没有,实在感兴趣可以看modeling.py脚本,优化器部分是optimization.py

    3. 这里没有从头训练bert模型,因为耗时耗力,没有资源一般来说很难,关于预训练的部分是run_pretraining.py


    二、实践

    结合一个例子就会更清楚一点(链接: https://pan.baidu.com/s/1Mv7iVBaBNEusd7qqbOkYow 提取码: 6inw),或者

    https://github.com/yuanxiaosc/Entity-Relation-Extraction

    这个例子是一个比赛,其目的是从给定的一个句子中抽取所有三元组(关于三元组抽取,这里有另外一篇《当Bert遇上Keras:这可能是Bert最简单的打开姿势 - 科学空间|Scientific Spaces)》,涉及到的关系有:

    例子 输入句子: "text": "《古世》是连载于云中书城的网络小说,作者是未弱"

    输出三元组: "spo_list": [{"predicate": "作者", "object_type": "人物", "subject_type": "图书作品", "object": "未弱", "subject": "古世"}, {"predicate": "连载网站", "object_type": "网站", "subject_type": "网络小说", "object": "云中书城", "subject": "古世"}]}

    解决思路如下:

    先使用bert搭建关系的分类模型,简单来看就是一个多标签分类任务,类别就是上述的那几种关系

    接着用预测出来的关系和文本,使用bert搭建一个实体抽取的模型,其简单来看也是一个分类模型,类别是:

    SUB对应的就是subject,B-SUB就是第一个实体开始的位置,后续的是I-SUB,OBJ就是第二个实体,所以第二个模型就是预测每一个tokens的标示,最后根据标示可提取出实体对。

    第二个模型是一个多分类的单标签任务,我们知道一句话中有可能有多个三元组,为此在进行第二个模型的时候,是先依据第一个模型预测出来的关系类如当前句子预测出3个关系,那么就重复该句话分成3个样本,那么3个样本就对应的是3个多分类单标签任务,为了使实体对和关系对应,所以第二个模型在计算loss的时候是综合考虑了关系和tokens标示的预测的。过程中所有结果都会生成保存在out文件夹下。

    1. 总体简介

    run_predicate_classification.py, run_sequnce_labeling.py分别对应的就是第一,二个模型,其写法套路参考就是上述介绍的run_classifier.py函数。produce_submit_json_file.py 就是将结果转化成比赛需要提交的数据格式,注意到最后的格式还需要实体对的类型,我们通过上面得到了三元组,但是还缺实体的类型,代码中是使用了字典,依据关系确定对应实体类型。
     

    例如当前是父亲关系的话,这一对实体应该说的是两个人。pretrained_model是一个文件夹,将预训练好的中文模型解压文件夹放到该文件夹下。bert也是一个文件夹,主要放一些run_predicate_classification.py, run_sequnce_labeling.py需要用到的脚本,其实主要的就是上述介绍的那三个基本的脚本modeling.py,optimization.py,tokenization.py。bin该文件夹下主要就是一些数据预处理的文件。evaluation文件夹是评价函数,predicate_classifiction和subject_object_labeling是对应的第一个模型和第二个模型进行前需要的数据预处理脚本。raw_data该文件夹下主要就是放的原始数据。

    2. 预测阶段

    准备关系分类数据

    python bin/predicate_classifiction/predicate_data_manager.py

    原始数据train和dev两个json文件大体样式是:

    test.json: 没有spo_list,待预测

    运行完上述程序后会在bin/predicate_classifiction/文件下生成一个classification_data文件夹里面对应的train,test,valid文件夹,文件夹下生成的基本都是。

    predicate_out.txt  :提取出每一个句子的关系集合

    text.txt  :每一个句子的文本

     token_in_not_UNK.txt :分字

    token_in.txt

    后面两个主要还是使用了tokenization脚本中的FullTokenizer一些类方法。

    注意test是没有标签的,因为要看线下效果,即可以将dev数据集当成test数据集处理放在test目录下,后面评价的话就可以。所以如果是比赛的话直接按上面运行,非比赛的的可以:

    predicate_data_manager.py set: Competition_Mode = False

     其实区别就是test文件夹到底是真的test还是dev

    关系分类模型训练

    python run_predicate_classification.py \
    --task_name=SKE_2019 \
    --do_train=true \
    --do_eval=false \
    --data_dir=bin/predicate_classifiction/classification_data \
    --vocab_file=pretrained_model/chinese_L-12_H-768_A-12/vocab.txt \
    --bert_config_file=pretrained_model/chinese_L-12_H-768_A-12/bert_config.json \
    --init_checkpoint=pretrained_model/chinese_L-12_H-768_A-12/bert_model.ckpt \
    --max_seq_length=128 \
    --train_batch_size=32 \
    --learning_rate=2e-5 \
    --num_train_epochs=6.0 \
    --output_dir=./output/predicate_classification_model/epochs6/

     经过我们上面分析,我们知道其是通过改造run_classifier.py得到的,那么主要变得地方就是定义了自己的数据预处理器和模型下游,具体看一下。自定义的数据预处理器是

    SKE_2019_Multi_Label_Classification_Processor

    方法一样,这里主要内部主要多了一个get_examples方法,因为当前的数据label和文本放在两个不同的文件,这个方法就是同时加载两个文件对应组合。再看一下create_model,最关键的就是:
     

     with tf.variable_scope("loss"):
            if is_training:
                # I.e., 0.1 dropout
                output_layer = tf.nn.dropout(output_layer, keep_prob=0.9)
     
            logits_wx = tf.matmul(output_layer, output_weights, transpose_b=True)
            logits = tf.nn.bias_add(logits_wx, output_bias)
            probabilities = tf.sigmoid(logits)
            label_ids = tf.cast(labels, tf.float32)
            per_example_loss = tf.reduce_sum(
                tf.nn.sigmoid_cross_entropy_with_logits(logits=logits, labels=label_ids), axis=-1)
            loss = tf.reduce_mean(per_example_loss)
     
            return loss, per_example_loss, logits, probabilities

     就是加一一层全连接,因为是多标签嘛,所以用的是sigmoid。其他的话就是在评价的时候即model_fn_builder中,概率阈值使用的是0.5。

    在预测的时候,采用了同样的阈值(0.5),该部分在main中最后。

     因为要将结果保存起来,所以在main中定义了一下写入操作都也很简单。

    运行的最后结果就是会在out文件夹下生成一个 predicate_classification_model文件夹保存该模型训练好的模型参数。

    准备序列标注数据

    python bin/subject_object_labeling/sequence_labeling_data_manager.py

    运行完上述程序后会在bin/subject_object_labeling/文件下生成一个sequence_labeling_data 文件夹里面对应的train,test,valid文件夹,文件夹下生成基本都是。

    bert_tokener_error_log.txt

    text.txt:可以看到如果句子有多个三元组是分成多个样本的

    token_in_not_UNK.txt  token_in.txt和前面类似。

    token_label_and_one_prdicate_out.txt:可以看到SUB和OBJ就是对应实体对的标示,后面是关系。

    序列标注模型训练

    python run_sequnce_labeling.py \
    --task_name=SKE_2019 \
    --do_train=true \
    --do_eval=false \
    --data_dir=bin/subject_object_labeling/sequence_labeling_data \
    --vocab_file=pretrained_model/chinese_L-12_H-768_A-12/vocab.txt \
    --bert_config_file=pretrained_model/chinese_L-12_H-768_A-12/bert_config.json \
    --init_checkpoint=pretrained_model/chinese_L-12_H-768_A-12/bert_model.ckpt \
    --max_seq_length=128 \
    --train_batch_size=32 \
    --learning_rate=2e-5 \
    --num_train_epochs=9.0 \
    --output_dir=./output/sequnce_labeling_model/epochs9/

    同理其还是对run_classifier.py进行了改造,先看数据预处理函数器:

    其里面最大不同就是定义了两个label函数

    一个是实体标示label,一个是关系label。接着看一下create_model,其分别两大部分:predicate_loss和token_label_loss即关系预测和实体标示预测。先看关系部分predicate_loss

     

     predicate_output_layer = model.get_pooled_output()
     
        intent_hidden_size = predicate_output_layer.shape[-1].value
     
        predicate_output_weights = tf.get_variable(
            "predicate_output_weights", [num_predicate_labels, intent_hidden_size],
            initializer=tf.truncated_normal_initializer(stddev=0.02))
     
        predicate_output_bias = tf.get_variable(
            "predicate_output_bias", [num_predicate_labels], initializer=tf.zeros_initializer())
     
        with tf.variable_scope("predicate_loss"):
            if is_training:
                # I.e., 0.1 dropout
                predicate_output_layer = tf.nn.dropout(predicate_output_layer, keep_prob=0.9)
     
            predicate_logits = tf.matmul(predicate_output_layer, predicate_output_weights, transpose_b=True)
            predicate_logits = tf.nn.bias_add(predicate_logits, predicate_output_bias)
            predicate_probabilities = tf.nn.softmax(predicate_logits, axis=-1)
            predicate_prediction = tf.argmax(predicate_probabilities, axis=-1, output_type=tf.int32)
            predicate_labels = tf.one_hot(predicate_label_id, depth=num_predicate_labels, dtype=tf.float32)
            predicate_per_example_loss = tf.reduce_sum(tf.nn.sigmoid_cross_entropy_with_logits(logits=predicate_logits, labels=predicate_labels), -1)
            predicate_loss = tf.reduce_mean(predicate_per_example_loss)

    因为该部分使用的数据是将句子已经分成单标签了,所以使用的是sigmoid,其实基本和关系分类模型训练中的差不多,只不过一个是多标签一个是单标签。

    再看token_label_loss部分

    最需要注意的就是上面红框的部分,其不再用bert的model.get_pooled_output()输出模式,而是使model.get_sequence_output(),因为结果需要预测每个tokens的标示,每个tokens应该只有一个标示,也是一个多分类任务。最后的loss是综合两部分:

    loss = 0.5 * predicate_loss + token_label_loss

    这里的predicate_probabilities就是预测关系的概率值,predicate_prediction是概率最大的那个关系,token_label_predictions就是预测的实体标示(当然啦,也可以返回具体每一个tokens关于每一个标示的概率)。运行的最后结果就是会在out文件夹下生成一个 sequnce_labeling_model文件夹保存该模型训练好的模型参数。训练过程到此结束了。

    说到这里可能会有这样的疑惑?两个模型(所谓的管道),似乎没有交集,都是先处理准备好各自数据,然后各自训练各自的,而且第二个模型同时进行了关系和实体标示预测,那么第一个模型只预测了关系,那么第一个模型存在的意义是什么?直接用第二模型不就可以啦?

    逻辑是这样的:

    训练确实没有交集,各自训练各自的,因为训练样本都是精确的,无可厚非,但是在预测的时候我们只有一句话,要预测出这句话中的所有三元组,如果只采用第二个模型的话,它一句话根据当前的关系只能预测出一种三元组,所以需要第一个模型打前阵,先将句子中有多少种关系预测出来,然后再将句子依照关系分成一句话一个三元组,训练的时候我们是知道每一个句子有多少种关系,但是预测的时候我们并不知道,这就是第一个模型存在的意义,那么我就要用第二个模型同时解决一句话预测所有三元组呢?其实很难,因为首先tokens标记就是问题,怎么将标示和关系对应呢?是吧,所以这里所谓管道,其实是在预测过程体现的,下面就可以看到。

    另外按照上面的思路,该模型bert的输入端一个样本的特征应该是当前句子和关系,而不仅仅是一个句子,不然的话将一句话拆分成多个样本还有什么意义?大家不就都一样啦,正是由于还有关系,才能实现同一句子抽取出不同的实体对(对应当前关系),那么输入端是怎么将将关系整合进去的呢?

    解决办法是这样的:

    将关系作为当前句子的下句,还记得text_b吗?第一个模型设为了None,这里将类别平铺成和text_a一样长度,当成一句话作为text_a(当前句子)的下文,该部分具体看convert_single_example:

    假设当前句子长度为5,关系是作者,那么tokens_b(text_b) = ['作者','作者','作者','作者','作者'],具体代码都很简单,这里就举一个例子:

    假设原始tokens : 胡歌是仙剑的主演,关系是:主演

    那么经过convert_single_example函数处理后大体相当于:

    tokens     :           CLS   胡   歌   是   仙   剑   的   主   演   SEP   主演   主演   主演   主演   主演   主演   主演   主演  SEP

    input_ids :           101   200 201 202 203 204 205 206 207 102   2  2   2  2  2   2  2   2   102  0 0 0

    segment_ids :       0       0     0     0    0     0    0     0     0     0        1       1        1         1         1         1      1         1       1 0 0 0

    input_mask :     1   1   1   1   1    1    1     1     1     1    1       1        1         1         1         1      1         1       1  0 0 0

    token_label   :        [CLS]    B-SUB I-SUB o B-OBJ I-OBJ o o o [SEP] [category] [category] [category] [category] [category] [category] [category] [category]  [SEP] [Padding] [Padding] [Padding]

    predicate_id : 1

    几点说明:

    1) bert输入需要字段input_ids ,segment_ids,input_mask,计算loss需要token_label predicate_id;

    2)红色的部分是填充到最大长度,填充部分都是0;

    3)关于词的id 原始文本就使用tokenizer.convert_tokens_to_ids就可以得到(上面的101,200等等),类别("主演")的id呢?这里是将所有类别制作成一个字典,类如主演的value就是1,上述之所以是2,是因为代码中同一加了一个偏置。那么这里类别的id和经过tokenizer.convert_tokens_to_ids转化后的字id有没有可能是冲突的呢?没有!,因为   tokenizer.convert_tokens_to_ids没有用1-100之内的id,当前类别又没有超过100,所以不会冲突

    4) 还记得论文中这幅图吗?
     

     对应到这里:

    第一个模型应该是对应图b,第二个模型应该包括两部分一部分是关系预测一部分是实体预测,关系预测相当于图a(sentence 2 = text_b),实体预测部分相当于c ,注意图d举的例子本来可以看做是一个实体识别的例子,但我们这里输入毕竟使用了 text_b,如果强行对应的话,只能看做是和c(知识问答)最对应了吧。

    三、预测阶段

    关系分类模型预测

    运行

    python run_predicate_classification.py \
      --task_name=SKE_2019 \
      --do_predict=true \
      --data_dir=bin/predicate_classifiction/classification_data \
      --vocab_file=pretrained_model/chinese_L-12_H-768_A-12/vocab.txt \
      --bert_config_file=pretrained_model/chinese_L-12_H-768_A-12/bert_config.json \
      --init_checkpoint=output/predicate_classification_model/epochs6/model.ckpt-478 \
      --max_seq_length=128 \
      --output_dir=./output/predicate_infer_out/epochs6/ckpt478

    这里就是加载上述已经训练好的模型进行关系预测,这里要加载哪个模型需要手动设置一下即可

    init_checkpoint

     运行完后,会在out文件夹下生成一个predicate_infer_out文件夹,里面主要有:

    predicate_predict.txt类别预测结果

     predicate_score_value.txt:类别概率,以0.5为阈值确定最终结果

     把关系分类模型预测结果转换成序列标注模型的预测输入

    python bin/predicate_classifiction/prepare_data_for_labeling_infer.py

    其主要的作用就是像上面所说的依照关系将其分成单句对应一个关系

    会在bin//subject_object_labeling/sequence_labeling_data/test 文件夹下生成

    text_and_one_predicate.txt:单句文本

    token_in_and_one_predicate.txt:单句文本以及对应的关系

    token_in_not_UNK_and_one_predicate.txt

     

    序列标注模型预测

    python run_sequnce_labeling.py \
      --task_name=SKE_2019 \
      --do_predict=true \
      --data_dir=bin/subject_object_labeling/sequence_labeling_data \
      --vocab_file=pretrained_model/chinese_L-12_H-768_A-12/vocab.txt \
      --bert_config_file=pretrained_model/chinese_L-12_H-768_A-12/bert_config.json \
      --init_checkpoint=output/sequnce_labeling_model/epochs9/model.ckpt-1237 \
      --max_seq_length=128 \
      --output_dir=./output/sequnce_infer_out/epochs9/ckpt1237

    手动设置一下要加载的模型init_checkpoint

    运行完后会在out文件夹下生成 sequnce_infer_out文件夹,里面主要是:

    predicate_predict.txt:

    predicate_probabilities.txt:关系预测概率,单标签,选取最大的概率作为最终结果

    token_label_predictions.txt:实体表示预测结果(其实对应每一个token也是单标签多分类)

    生成实体-关系结果

    python produce_submit_json_file.py

    主要就是按句子分组整合,生成最终预测数据

    运行完后会在out文件夹下生成final_text_spo_list_result文件夹,里面:

    keep_empty_spo_list_subject_predicate_object_predict_output.json:

    因为这里采用的是非比赛模型,所以test中的数据其实是dev数据。

    评价

    关系抽取模型性能

    运行

    python bin/evaluation/evaluate_classifiction.py

    结果

     

    过程很简单,就是看每一条样本的具有的真实关系集合和预测关系集合(如下代码中的golden_data_list, predict_data_list),然后看两者是否相同,不相同的话再看看谁是谁的子集,最后分别统计结果。

    从上面可以看出,一共有1000条样本,完全预测正确的是551,除此之外,只预测出真实关系集合中的一个子集数是83个,另外还有125个是预测的关系集合中完全包含了真实关系集合,当然啦剩下的1000-551-83-125=241个样本的预测结果都是交叉的。

    实体抽取模型性能

    运行

    python bin/evaluation/evaluate_labeling.py 

     结果

     其过程就是将看预测的三元组对的个数,correct spo num是预测对的三元组个数,submitted spo num是一共预测出的三元组个数,golden set spo num是真实的三元组个数,当然得在text分组下对比是否相等才有意义,最后就是根据这三个数计算准确率,召回率以及F1值。

    总结

    1. 把握住  bert的输入:input_ids,input_mask,segment_ids (主要在convert_single_example生成)bert的输出:model.get_sequence_output() ,model.get_pooled_output();

    2. 具体做的时候主要修改:继承DataProcessor类定义自己的数据预处理器,在create_model定义自己具体下游的任务。
     

     

     

     

     

     

     

    展开全文
  • 中文实体关系抽取,对实体关系抽取不了解的可以先看。顺便求star~ 数据 中文实体关系抽取数据实在太难找了,data中是忘记在哪里找的人物关系数据集,一共11+1种关系,数据质量不太好,但也找不到其他的了。 (更新...
  • bert实践:关系抽取解读

    万次阅读 多人点赞 2019-07-31 16:42:46
    前言 bert模型是谷歌2018年10月底公布的,反响巨大,效果不错,在各大比赛上面出类拔萃,...而近一两年提出的ULMFiT,GPT,BERT等都属于模型迁移,说白了BERT 模型是将预训练模型和下游任务模型结合在一起的,核心...
  • 该代码以管道式的方式处理实体关系抽取任务,首先使用一个多标签分类模型判断句子的关系种类,然后将句子和可能的关系类型输入序列标注模型中,序列标注模型标注出句子中的实体,最终结合预测的关系实体输出实体...
  • Entity and Relation Extraction Based on TensorFlow and BERT. 基于TensorFlow和BERT的管道式实体关系抽取,2019语言与智能技术竞赛信息抽取任务解决方案。Schema based Knowledge Extraction, SKE 2019
  • 关系分类是抽取实体关系的一个重要的NLP任务。关系抽取中的SOTA方法主要基于卷积神经网络或者循环神经网络。最近,预训练的BERT模型在NLP分类和序列标注任务上取得了非常成功的结果。关系分类同上述任务不同,它...
  • 常常在想,自然语言处理到底在做的是一件什么样的事情?到目前为止,我所接触到的NLP其实都是在做一件事情,即将自然语言转化为一种计算机能够理解的形式。这一点在知识图谱、信息抽取、文本摘要这...
  • 在清华大学开源的OpenNRE项目基础上实现中文实体关系识别 github项目地址,点我 文章目录一、中文关系抽取训练结果测试结果二、使用前准备三、注意事项 一、中文关系抽取 使用哈工大,BERT-wwm,中文bert,在20w...
  • 事物、概念之间的关系是人类知识中非常重要的一个部分,但是他们通常隐藏在海量的非结构文本中。为了从文本中抽取这些关系事实,从早期的模式匹配到近年的神经网络,大量的研究在多年前就已经展开。然...
  • BERT实现关系分类抽取(pytorch)

    千次阅读 2021-12-03 10:25:11
    前两天在GitHub上看到这样一个关系分类抽取模型(地址:GitHub),这个模型的思路大致是将关系抽取转化成对两个实体关系进行分类,在这里对模型文件进行一下解释记录。 在这个项目中模型的结构定义在relation_...
  • 这一点在知识图谱、信息抽取、文本摘要这些任务中格外明显。不同的任务的差异在于目标的转化形式不一样,因而不同的任务难度、处理方式存在差异。这个系列文章【文本信息抽取与结构化】,在自然语言处理中是非常有用...
  • 『2021语言与智能技术竞赛』- 关系抽取任务 官方的baseline是将关系抽取任务转换成序列标注任务,使用Paddle实现。 本文将提供bert4keras的实现 本文的代码地址https://github.com/hgliyuhao/LIC2021_EE_baseline ...
  • 实体关系抽取

    2017-08-08 12:23:33
    实体关系抽取
  • 本文学习对象: bert实践:关系抽取解读 ...任务目标:从给定的一个句子中...实体抽取的模型----多分类的单标签任务-----预测每一个tokens的标示,最后根据标示可提取出实体对,例如预测未弱是obj(客体),古世是sub(主体
  • BERT:代码解读、实体关系抽取实战

    千次阅读 多人点赞 2019-10-31 09:43:18
    一、BERT的主要亮点 1. 双向Transformers 2.句子级别的应用 3.能够解决的任务 二、BERT代码解读 1. 数据预处理 1.1 InputExample类 1.2 InputFeatures类 1.3 DataProcessor 重点 1.4 convert_single_...
  • bert-实体抽取

    千次阅读 2019-07-12 11:44:30
    import tensorflow as tf ...from bert import modeling from bert import tokenization from bert import optimization import os import pandas as pd flags = tf.flags FLAGS = flags.FLAGS ...
  • 基于BERT的心血管医疗指南实体关系抽取方法.pdf
  • 关系抽取(Relation Extraction,简称RE)的概念是1988年在MUC大会上提出,是信息抽取的基本任务之一,目的是为了识别出文本实体中的目标关系,是构建知识图谱的重要技术环节。 知识图谱是语义关联的实体,它将人们...
  • 目标:给定句子和句子中的两个实体,判断这两个实体之间的关系 来源:关系抽取 代码解读: model.py import torch import torch.nn as nn from transformers import BertModel class SentenceRE(nn.Module): ...
  • Bert CNN信息抽取

    千次阅读 2021-08-03 15:01:07
    给定schema约束集合及句子sent,其中schema定义了关系P以及其对应的主体S和客体O的类别,例如 (S_TYPE:人物,P:妻子,O_TYPE:人物)、(S_TYPE:公司,P:创始人,O_TYPE:人物)等。任务要求参评系统自动地对句子进行...
  • 条件Layer Norm出现问题:抽取S,P,O时候类别不均衡,0太多1太少 解决方法:将概率值做n次方。 解决思路: 原来输出一个概率值p,代表类别1的概率是p,我现在将它变为pnp^{n}pn,也就是认为类别1的概率是pnp^{n}pn,除...
  • 阿里云医疗实体关系抽取大赛

    千次阅读 多人点赞 2021-06-18 09:32:19
    1、本项目是基于阿里云比赛开放的医疗数据集去做的实体关系抽取。下面会从数据的详情,模型的选取,模型的训练,模型的验证和模型的预测去讲述。 2、数据准备阶段 1、数据来源是阿里云医疗大赛,选取的是其中一个子...
  • nlp中的实体关系抽取方法总结

    万次阅读 多人点赞 2020-07-04 21:23:00
    Pipeline方法指先抽取实体、再抽取关系。相比于传统的Pipeline方法,联合抽取能获得更好的性能。虽然Pipeline方法易于实现,这两个抽取模型的灵活性高,实体模型和关系模型可以使用独立的数据集,并不需要同时标注...
  • 中文实体关系抽取实践

    万次阅读 多人点赞 2019-07-17 16:21:04
    关于实体关系抽取的技术发展脉络,感兴趣的可以看一下: https://www.cnblogs.com/theodoric008/p/7874373.html 关系抽取有限定关系抽取和开放关系抽取,这里主要说限定关系抽取即分类问题 其过程常常又有监督...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,274
精华内容 1,309
关键字:

bert实体关系抽取

友情链接: awen_shaolei.rar