知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)

目录

  • 一、pyhanlp
  • 二、stanfordnlp
  • 三、pyltp
  • 四、openNRE
    • 1、安装:我安装到windows上了
    • 2、使用
  • 五、基于TensorFlow 2自定义NER模型(构建、训练与保存模型范例)
    • 1、BiLSTM+CRF模型
    • 2、BERT+CRF(或 softmax)模型
      • 1、使用keras_bert:
      • 2、使用transformers
    • 3、BERT+SPAN模型
  • 六、SPO三元组抽取 / 关系抽取
    • 1、基于bert4keras(抽取三元组)
    • 2、基于DGCNN[CNN、Attention、BiLSTM]

一、pyhanlp

【基于java的,安装使用前必须先安装java环境】
1、安装:pip install pyhanlp
【安装过程中会自动安装jpype1,该模块仅支持到python3.7,所以python3.7以上的安装老是报错。】
2、使用:
1)分词

import pyhanlp
print([i.word for i in pyhanlp.HanLP.segment('我们都是中国人,坚持一个中国原则。')])

知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第1张图片

二、stanfordnlp

【官方GitHub介绍:https://stanfordnlp.github.io/stanfordnlp/training.html】
1、安装:pip 安装

 pip install stanfordnlp --proxy 111.666.88.688:808

2、简单使用

 import stanfordnlp

三、pyltp

【学习手札:https://blog.csdn.net/MebiuW/article/details/52496920 】
【基于C++的】

四、openNRE

GitHub:https://github.com/thunlp/OpenNRE#datasets
清华大学自然语言处理与社会人文计算实验室(THUNLP)推出的一款开源的神经网络关系抽取工具包,包括了多款常用的关系抽取模型。
使用wiki80数据集,包含80种关系。(也可以自己训练)
但是都是英文数据集,使用也都是基于英文的……

1、安装:我安装到windows上了

cmd中下载git相关安装文件:

git clone https://github.com/thunlp/OpenNRE.git

安装requirements.txt中的模块(括号中是我安装的模块版本)

torch==1.6.0   (1.9.0)
transformers==3.4.0  (4.21.3)
pytest==5.3.2 
scikit-learn==0.22.1 (0.23.2)
scipy==1.4.1  (1.4.1)
nltk>=3.6.4  (3.6.2)

安装openNRE

python setup.py develop

2、使用

【注意】
1)windows在导入包的时候会报错:TypeError: expected str, bytes or os.PathLike object, not NoneType
原因:opennre中的pretrain.py中的第13行在windows运行出错(os.getenv(‘HOME’)获取用户主文件地址,windows没有home地址)。
改为:

# default_root_path = os.path.join(os.getenv('HOME'), '.opennre') # Linux
default_root_path = os.path.join(str(Path.home()), 'opennre') # cqf: windows

2)windows在opennre.get_model(‘wiki80_cnn_softmax’)获取模型时报错:wget不是内部执行命令
原因:wget 是一个Linux环境下用于从万维网上提取文件的工具,windows使用时需要单独安装。
安装:①在网站 https://eternallybored.org/misc/wget/ 上下载windows 上适用的安装包(最新版就可);
   ②下载完成后解压:比如我下载的是 wget-1.21.3-win64.zip ,解压到 D:\software\wget-1.21.3-win64;
   ③添加环境变量:比如我的
知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第2张图片
导入模块、加载模型、预测

>>> import opennre
>>> model = opennre.get_model('wiki80_cnn_softmax') # 模型还包括:wiki80_bert_softmax、wiki80_bertentity_softmax、tacred_bert_softmax、tacred_bertentity_softmax
>>> model.infer({'text': 'Huang Xiaoming starred in the TV series "the emperor of Han Dynasty", in which he played Emperor Wu of Han Dynasty.', 'h': {'pos':(0,13)}, 't': {'pos':(41,66)}})
('notable work', 0.96822190284729)

五、基于TensorFlow 2自定义NER模型(构建、训练与保存模型范例)

NER实质:对目标句子序列进行特征向量表示,然后输入模型,预测句子中每个词对应所有 class 的概率,概率最高的即为其标注结果。
环境要求:(keras4bert环境要求后面单独说明)

keras_bert.__version__ = 0.88.0 # 要求keras >= 2.4.3
keras.__version__ = 2.4.3
tf.__version__ = 2.5.0
tfa.__version__ = 0.16.1
transformers.__version__ = 4.9.1

超参:

# 超参
config_path='data/chinese_L-12_H-768_A-12/bert_config.json'
check_point_path='data/chinese_L-12_H-768_A-12/bert_model.ckpt'
seq_len=200
layer_nums=4  # keras_bert加载BERT时参数output_layer_num的值,BERT模型每个encoder的MultiHeadSelfAttentio层数
training=False
trainable=False
num_label=4
drop_rate=0.3
is_training=True
hidden_size=600
TransBERT_MODEL_NAME='data/bert-base-uncased'

1、BiLSTM+CRF模型

理解说明:
CRF作用:①训练过程中作为损失函数,计算loss;②预测过程中,用于解码,获取得分最高的句子标记结果。
CRF的解码函数:
tfa.text.crf_decode()获取CRF解码结果,即最高分数的句子标记结果,返回结果包括:
① decode_tags: A [batch_size, max_seq_len] matrix, with dtype tf.int32. Contains the highest scoring tag indices.
② best_score: A [batch_size] vector, containing the score of decode_tags.

1、模型构建

import tensorflow as tf
import tensorflow_addons as tfa # 需单独安装

# 基于tf.keras.layers.Layer类定义一个自己的CRF层
class CRF(tf.keras.layers.Layer):
    def __init__(self, label_num) -> None:
        super().__init__()
        self.trans_params = tf.Variable(
            tf.random.uniform(shape=(label_num, label_num)), name="transition")
    
    def call(self, inputs, labels, seq_lens):
        log_likelihood, self.trans_params = tfa.text.crf_log_likelihood(
                                                inputs,  # tensor:[batch_size, max_seq_len, num_tags] 
                                                labels,  # tensor:[batch_size, max_seq_len] 
                                                seq_lens,  # seq_lens为各个句子的真实长度(不包括padding的部分,本模型训练也将不存在于vocab中的部分排除)
                                                transition_params=self.trans_params)
        loss = tf.reduce_mean(-log_likelihood)
        return loss

class BiLSTM_CRF_model(tf.keras.Model):
    def __init__(self, embedding_dim, vocab_size, hidden_size, label_num) -> None:
        super().__init__()
        self.embeding_layer=tf.keras.layers.Embedding(input_dim=vocab_size, 
    						  output_dim=embedding_dim, 
    						  input_length=None,  #Length of input sequences,如果该层后面连接flatten并dense则必须指定input_length。
    						  embeddings_initializer='uniform')
        self.bilstm_layer=tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(hidden_size, return_sequences=True), 
                                                        merge_mode='concat')
        self.dense = tf.keras.layers.Dense(label_num)
        self.crf_layer=CRF(label_num)

    def call(self, input, labels=None, externalEmbed_file=None, training=True):
        # tf.math.not_equal()比较x、y相等情况,返回bool类型的tensor,shape同x或y
        # tf.cast()强制将给定数据转换为指定数据类型
        seq_lens=tf.math.reduce_sum(tf.cast(tf.math.not_equal(input, 0), dtype=tf.int32), axis=-1) # 计算各个橘子的真实长度
        if externalEmbed_file:
            x=tf.nn.embedding_lookup(externalEmbed_file, input)
        else:
            x=self.embeding_layer(input)
        x=self.bilstm_layer(x)
        logits=self.dense(x) # 得到CRF的输入[batch_size, max_seq_len, num_tags]
        if training:
            labels = tf.convert_to_tensor(labels, dtype=tf.int32)
            loss=self.crf_layer(logits, labels, seq_lens)
            return loss, logits, seq_lens
        else:
            return logits, seq_lens

继承tf.keras.Model类构建的模型结构:
知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第3张图片
2、模型训练与保存:

def get_acc_one_step(logits, text_lens, labels_batch, model):
    '''
    【这个计算方式不是很好(一般一个句子中什么都不是的部分占比很大,导致即使全部标记结果都是非实体,那最终准确率也很高),
    最后准确率普遍偏高,模型效果不咋地,可以尝试计算精确率、召回率】
    计算实体识别准确率: 计算每个句子标注的准确率,然后所有句子准确率相加求平均。
    '''
    paths = []
    accuracy = 0
    for logit, text_len, labels in zip(logits, text_lens, labels_batch):
        viterbi_path, _ = tfa.text.viterbi_decode(logit[:text_len],  # BiLSTM_CRF模型中dense层的输出(CRF的输入/model.predict(dataset)结果),[batch_size, max_seq_len, num_tags] 
                                                  model.get_layer('crf').get_weights()[0] # 获取CRF层的转移矩阵, [num_tags, num_tags]
                                                  )
        paths.append(viterbi_path)
        correct_prediction = tf.equal(
            tf.convert_to_tensor(tf.keras.preprocessing.sequence.pad_sequences([viterbi_path], padding='post'),
                                 dtype=tf.int32),
            tf.convert_to_tensor(tf.keras.preprocessing.sequence.pad_sequences([labels[:text_len]], padding='post'),
                                 dtype=tf.int32)
        )
        accuracy = accuracy + tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    accuracy = accuracy / len(paths)
    return accuracy

def train_main(vocab_file, tag_file, train_path, output_dir):
	# text_sequences, label_sequences分别为token2id、padding之后的句子、标签tensor
	train_dataset = tf.data.Dataset.from_tensor_slices((text_sequences, label_sequences)) # 生成text和label一一对应的tensor
	train_dataset = train_dataset.shuffle(len(text_sequences)).batch(batch_size, drop_remainder=True) # 打乱上述tensor的顺序并分成多个大小为batch_size的batch
	
	model = BiLSTM_CRF_model(hidden_size = hidden_num, vocab_size = len(vocab2id), label_num= len(tag2id), embedding_dim = embedding_size)
	optimizer = tf.keras.optimizers.Adam(lr)
	
	# tf.train.Checkpoint是变量保存与恢复类,只保存模型的参数,不保存模型的计算过程,因此一般用于在具有模型源代码的时候恢复之前训练好的模型参数。
	# ckpt.restore(),模型中的变量还没有被建立的时候,Checkpoint 可以等到变量被建立的时候再进行数值的恢复(即提前声明也不会报错)
	# tf.train.CheckpointManager()对保存文件管理,指定文件保存路径、文件名前缀、保留的 Checkpoint数目
	ckpt = tf.train.Checkpoint(optimizer=optimizer, model=model) 
    ckpt.restore(tf.train.latest_checkpoint(output_dir)) # 载入已训练的模型文件,以恢复模型(可以进一步训练或者用于预测)。当保存了多个文件时,载入最近的一个
    ckpt_manager = tf.train.CheckpointManager(ckpt,
                                            output_dir,
                                            checkpoint_name='bilstm_crf_model.ckpt',
                                            max_to_keep=3)
	# 10个epoch,batch_size大小的batch基于Adam优化器(学习率1e-3)循环训练
	for epoch in range(10):
	    for _, (text_batch, labels_batch) in enumerate(train_dataset):
	        step = step + 1
	        with tf.GradientTape() as tape:
	            loss, logits, text_lens = model(text_batch, 
	                                            labels_batch, 
	                                            externalEmbed_file=False)
	            gradients = tape.gradient(loss, model.trainable_variables)
	            optimizer.apply_gradients(zip(gradients, model.trainable_variables))
	        if step==1:
	            print(model.summary())
	
	        accuracy = get_acc_one_step(logits, text_lens, labels_batch, model)
	        print('epoch %d, step %d, loss %.4f , accuracy %.4f' % (epoch, step, loss, accuracy))
	        if accuracy > best_acc:
	            best_acc = accuracy
	            ckpt_manager.save() # 保存训练好的模型的参数(路径中若有不存在的文件夹会自动创建),得到三个文件:checkpoint、.ckpt-28.index、.ckpt-28.data-00000-of-00001
	            # ckpt.save(model_path_file) # 不通过CheckpointManager的话也可直接保存模型的参数
	            print("model saved")

if __name__=='__main__':
    train_main(vocab_file='data/vocab_file.txt', 
               tag_file='data/tag_file.txt', 
               train_path='data/train.txt', 
               output_dir='checkpoints/')

训练结果:loss值、准确率
知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第4张图片知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第5张图片
3、使用模型预测:
① 加载训练好的模型参数
② 使用predict()对输入序列预测
③ 基于预测的logit和模型中的转移矩阵,使用tfa.text.viterbi_decode()解码,得到最佳结果路径

def predict_main(vocab_file, tag_file, output_dir):
    vocab2id, id2vocab = read_vocab(vocab_file)
    tag2id, id2tag = read_vocab(tag_file)

    model = BiLSTM_CRF_model(hidden_size = hidden_num, 
                             vocab_size = len(vocab2id), 
                             label_num= len(tag2id), 
                             embedding_dim = embedding_size)
    optimizer = tf.keras.optimizers.Adam(lr)

    ckpt = tf.train.Checkpoint(optimizer=optimizer, model=model)
    ckpt.restore(tf.train.latest_checkpoint(output_dir))

    text = input("input:")
    dataset = tf.keras.preprocessing.sequence.pad_sequences([[vocab2id.get(char,0) for char in text]], padding='post')
    logits, text_lens = model.predict(dataset)
    paths = []
    for logit, text_len in zip(logits, text_lens):
        viterbi_path, _ = tfa.text.viterbi_decode(
        									logit[:text_len], 
        									model.get_layer('crf').get_weights()[0])
        paths.append(viterbi_path)

    print('结果路径:',paths) # 结果路径: [[1, 1, 1, 1, 1, 1, 5, 6, 6, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5, 6, 6, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

参考:https://github.com/saiwaiyanyu/bi-lstm-crf-ner-tf2.0

2、BERT+CRF(或 softmax)模型

BERT中包含四种special token,分别是:
  [UNK]:指代在vocab中找不到的字/词
  [CLS]:添加在每句句首。对于用于分类的向量,会聚集所有的分类信息
  [SEP]:添加在每句句尾
  [MASK]:使用[MASK]替换句子中的部分字/词。用于MLM(屏蔽语言模型)。

1、使用keras_bert:

tokenizer数据处理部分:

from keras_bert import Tokenizer

token2id={}
vocab_file=open('data/chinese_L-12_H-768_A-12/vocab.txt', 'r', encoding='utf-8')
for w in vocab_file.readlines():
    w=w.strip()
    token2id[w]=len(token2id)
tokenizer=Tokenizer(token2id)
r=tokenizer.tokenize('我是中糹国人!') # 对句子分字,并在句首和句尾分别添加[CLS]、[SEP]
print(r) # 结果:['[CLS]', '我', '是', '中', '糹', '国', '人', '!', '[SEP]']
r_encode=tokenizer.encode('我是糹中国人!') # 返回array类型的tikens_id, segments_id

方式一、模型构建代码:继承tf.keras.Model()
bert_model( [tokenid_tensor, segmentid_tensor] )

class NonMaskingLayer(tf.keras.layers.Layer):
    def __init__(self, **kwargs):
        self.supports_masking = True
        super(NonMaskingLayer, self).__init__(**kwargs)
    def build(self, input_shape):
        pass
    def compute_mask(self, inputs, input_mask=None):
        # do not pass the mask to the next layers
        return None
    def call(self, x, mask=None):
        return x
class BertCrf(tf.keras.Model):
    def __init__(self, 
                 seq_len=200, 
                 bertOut_layer_nums=4,  
                 bertTraining=False, 
                 bertTrainable=False, 
                 num_label=4, 
                 drop_rate=0.3, 
                 is_training=True,
                 config_path='data/chinese_L-12_H-768_A-12/bert_config.json',
                 check_point_path='data/chinese_L-12_H-768_A-12/bert_model.ckpt'):
        super(BertCrf, self).__init__()
        self.config_path=config_path
        self.check_point_path=check_point_path
        self.seq_len=seq_len
        self.layer_nums=bertOut_layer_nums
        self.training=bertTraining
        self.trainable=bertTrainable 
        self.num_label=num_label
        self.drop_rate=drop_rate
        self.is_training=is_training
        
        self.bert_model=keras_bert.load_trained_model_from_checkpoint(
				        		self.config_path,
				                self.check_point_path,
				                seq_len=self.seq_len,
				                output_layer_num=self.layer_nums, # 决定bert输出的最后一个维度是768 * bertOut_layer_nums
				                training=self.training,
				                trainable=self.trainable)
        self.NonMask_layer=NonMaskingLayer()
        self.Dropout_layer=tf.keras.layers.Dropout(self.drop_rate)
        self.Dense_layer=tf.keras.layers.Dense(self.num_label)
        self.crf_layer=CRF(self.num_label)
    
    def call(self, input, labels=None, train=False):
        '''
        input: [padded_tokenid_tensor, padded_segmentid_tensor], tensor shape=[batch, seq_len]
        '''
        seq_reallens=tf.math.reduce_sum(tf.cast(tf.math.not_equal(input[0], 0), dtype=tf.int32), axis=-1)
        out_put=self.bert_model(input)
        out_put=self.NonMask_layer(out_put)
        out_put=self.Dropout_layer(out_put)
        logits=self.Dense_layer(out_put)
        if train:
            labels=tf.convert_to_tensor(labels, dtype=tf.int32)
            loss=self.crf_layer(logits, labels, seq_reallens)
            return loss, logits, seq_reallens
        else:
            return logits, seq_reallens

模型结构:
知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第6张图片
方式二、模型构建代码:链式(串联)方式
bert_model.inputs , bert_model.output

class MyBertCrfModel:
    def __init__(self, 
                 seq_len=200, 
                 bertOut_layer_nums=4, 
                 bertTraining=False, 
                 bertTrainable=False, 
                 num_label=4, 
                 drop_rate=0.3, 
                 is_training=True,
                 config_path='data/chinese_L-12_H-768_A-12/bert_config.json',
                 check_point_path='data/chinese_L-12_H-768_A-12/bert_model.ckpt'):
        self.config_path=config_path
        self.check_point_path=check_point_path
        self.seq_len=seq_len
        self.layer_nums=bertOut_layer_nums
        self.training=bertTraining
        self.trainable=bertTrainable 
        self.num_label=num_label
        self.drop_rate=drop_rate
        self.is_training=is_training
    
    def build_model(self):
        '''
        这种方式构建的模型,可以将BERT详细的模型结构打印出来。
        返回模型对象,后续直接使用model.compile()、 model.fit()、model.save()、 model.predict()编译、训练、保存和预测
        model调用时,有四个输入[input1, input2, input3, input4], 分别对应:
         input1:bert的输入1,padded_tokenid_tensor,shape=[batch, seq_len]
         input2:bert的输入2,padded_segmentid_tensor,shape=[batch, seq_len],即段向量,用来区分两个句子,用于句子级别的Mask任务,第一句话标记0(两个句子时,另一句为1)
         input3:crf层要用的labels, [batch, seq_len]
         input4:crf层要用的各句子真实长度, [batch]
        '''
        label_input = tf.keras.layers.Input(shape=(self.seq_len,), name='target_ids', dtype='int32')
        seq_reallens = tf.keras.layers.Input(shape=(), name='input_reallens', dtype='int32')
        bert_model = keras_bert.load_trained_model_from_checkpoint(self.config_path,
                                                        self.check_point_path,
                                                        seq_len=self.seq_len,
                                                        output_layer_num=self.layer_nums,
                                                        training=self.training,
                                                        trainable=self.trainable)
        # bert_model=tf.keras.Model(bert_model.inputs, bert_model.output)
        out_put=NonMaskingLayer()(bert_model.output)
        out_put=tf.keras.layers.Dropout(self.drop_rate)(out_put, training=self.is_training)
        logits=tf.keras.layers.Dense(self.num_label)(out_put)
        # bert_dense_model=tf.keras.Model(bert_model.inputs, logits)

        loss=CRF(self.num_label)(logits, label_input, seq_reallens) 
        # bert_model.inputs是包含两个tensor的list,分别是Input-Token、Input-Segment
        model=tf.keras.Model([bert_model.inputs[0],bert_model.inputs[1],label_input, seq_reallens], loss) # input=[BERT模型的两个输入+自定义的两个输入层]
        model.summary()
        return model

模型结构图:
知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第7张图片

2、使用transformers

from transformers import BertTokenizer, TFBertModel, BertConfig

构建BERT+BiLSTM模型:

def build_model_tran():
    label_input = tf.keras.layers.Input(shape=(seq_len,), name='target_ids', dtype='int32')
    seq_reallens = tf.keras.layers.Input(shape=(), name='input_reallens', dtype='int32')
    
    input_ids=tf.keras.layers.Input(shape=(seq_len,), name='input_ids', dtype='int32')
    token_type_ids=tf.keras.layers.Input(shape=(seq_len,), name='token_type_ids', dtype='int32')
    attention_masks=tf.keras.layers.Input(shape=(seq_len,), name='attention_masks', dtype='int32')
    bert_input=[input_ids, token_type_ids, attention_masks]

    # 加载tf2的bert模型(模型文件是pytorch版本)
    bert_configs = BertConfig.from_pretrained(TransBERT_MODEL_NAME, num_labels=num_label)
    bert_model = TFBertModel.from_pretrained(TransBERT_MODEL_NAME, 
    										 config=bert_configs, 
    										 from_pt=True) # 注意,当加载.bin模型文件时,from_pt必须为True;加载.h5文件则直接默认值
    bert_model.trainable = False

    sequence_output = bert_model(bert_input)[0]
    output=tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(hidden_size, return_sequences=True), 
                                         merge_mode='concat')(sequence_output)
    output=tf.keras.layers.Dropout(drop_rate)(output)
    logits=tf.keras.layers.Dense(num_label)(output)
    loss=CRF(num_label)(logits, label_input, seq_reallens)

    model=tf.keras.Model(bert_input+[label_input, seq_reallens], logits)
    model.summary()
    return model

model=build_model_tran()

模型层次结构:
知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第8张图片

3、BERT+SPAN模型

未完待续

六、SPO三元组抽取 / 关系抽取

1、基于bert4keras(抽取三元组)

任务实质:p是给定的几种S-O之间的关系,其实任务实质就是抽取句子中S和O并确定他们之间关系属于哪一种。(实体识别 + 关系分类)
效果较差的一般思路
  ①s、p、o作为整体的序列标注来搞;
  ②先通过序列标注识别出实体s、o,然后使用分类模型对s-o进行关系分类。
思路主干:先构建S预测模型 subject_model,预测 S ,然后构建P、O预测模型 object_model,共享 S 特征向量,与随机选择的 Si 的特征向量共同预测 Si 对应的所有 p、o。
环境要求

'''
苏神bert4keras开发环境:tf 1.14 + keras 2.3.1
python3.8.0
'''
bert4keras.__version = 0.11.3
tensorflow.__version__ = 2.2.0(最好) / 2.5.0
keras.__version__ = 2.3.1 # 要求keras <= 2.3.1,与keras_bert冲突,keras_bert要求keras >= 2.4.3

模块导入

import json
import numpy as np
from tqdm import tqdm

import os
os.environ["TF_KERAS"] = '1'

from bert4keras.backend import keras, K, batch_gather
from bert4keras.layers import Loss
from bert4keras.layers import LayerNormalization
from bert4keras.tokenizers import Tokenizer
from bert4keras.models import build_transformer_model
from bert4keras.optimizers import Adam, extend_with_exponential_moving_average
from bert4keras.snippets import sequence_padding, DataGenerator
from bert4keras.snippets import open, to_array

from tensorflow.keras.layers import Input, Dense, Lambda, Reshape
from tensorflow.keras.models import Model

config_path='data/chinese_L-12_H-768_A-12/bert_config.json'
checkpoint_path='data/chinese_L-12_H-768_A-12/bert_model.ckpt'
dict_path = 'data/chinese_L-12_H-768_A-12/vocab.txt'

基于bert4keras的tokenizer
tokenizer处理部分:将字符转换为index表示,101是[CLS],102是[SEP]

tokenizer = Tokenizer(dict_path, do_lower_case=True)
token_ids, segment_ids = tokenizer.encode('查尔斯·阿兰基斯(Charles Aránguiz),1989年4月17日出生于智利圣地亚哥,智利职业足球运动员,司职中场,效力于德国足球甲级联赛勒沃库森足球俱乐部', 
										   maxlen=maxlen)
print(token_ids, '\n', segment_ids)

结果:
在这里插入图片描述

基于bert三元组抽取联合模型逻辑图(参考苏神抽取三元组):
数据集(batch)处理:一个text中s单独用首(1,0)尾(0,1)二维向量表示(shape = [token_ids_len, 2]),o、p统一用三维向量表示(shape = [token_ids_len, num_p, 2])
注意:s、o的label分开标注,防止s、o出现重叠的极端情况:如《鲁迅自传》由江苏文艺出版社出版,包含三元组 (鲁迅自传, 作者, 鲁迅) 。
知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第9张图片
模型结构:【基于GPLinker使用相同的环境配置】知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第10张图片
调用bert模型(bert4keras加载的bert模型从0层开始,最后一层(11层)是最后一个encoder的Normalization层);
构建三元组模型代码(苏神GitHub完整代码):

class TotalLoss(Loss):
    """subject_loss与object_loss之和,都是二分类交叉熵
    """
    def compute_loss(self, inputs, mask=None):
        subject_labels, object_labels = inputs[:2]
        subject_preds, object_preds, _ = inputs[2:]
        if mask[4] is None:
            mask = 1.0
        else:
            mask = K.cast(mask[4], K.floatx())
        # subject部分loss
        subject_loss = K.binary_crossentropy(subject_labels, subject_preds)
        subject_loss = K.mean(subject_loss, 2)
        subject_loss = K.sum(subject_loss * mask) / K.sum(mask)
        # object部分loss
        object_loss = K.binary_crossentropy(object_labels, object_preds)
        object_loss = K.sum(K.mean(object_loss, 3), 2)
        object_loss = K.sum(object_loss * mask) / K.sum(mask)
        # 总的loss
        return subject_loss + object_loss

class Nre_model:
    def __init__(self, relation_num = 20, maxlen = 128, batch_size = 64, 
            config_path = 'data/chinese_L-12_H-768_A-12/bert_config.json',
            checkpoint_path = 'data/chinese_L-12_H-768_A-12/bert_model.ckpt',
            dict_path = 'data/chinese_L-12_H-768_A-12/vocab.txt') -> None:
        self.relation_num = relation_num
        self.maxlen = maxlen
        self.batch_size = batch_size
        self.config_path = config_path
        self.checkpoint_path = checkpoint_path
        self.dict_path = dict_path

        # 补充输入
        subject_labels = Input(shape=(None, 2), name='Subject-Labels')
        subject_ids = Input(shape=(2,), name='Subject-Ids')
        object_labels = Input(shape=(None, relation_num, 2), name='Object-Labels')

        # 加载预训练模型
        bert = build_transformer_model(
            config_path=config_path,
            checkpoint_path=checkpoint_path,
            return_keras_model=False,
        )

        # 预测subject
        output = Dense(
            units=2, activation='sigmoid', kernel_initializer=bert.initializer
        )(bert.model.output)
        subject_preds = Lambda(lambda x: x**2)(output)

        self.subject_model = Model(bert.model.inputs, subject_preds)
        # subject_model.summary()

        # 传入subject,预测object
        # 通过Conditional Layer Normalization将subject融入到object的预测中
        output = bert.model.layers[-2].get_output_at(-1)  # cqf:获取-2层的输出(-1层是bert最后一个encoder的FeedForward-Norm层)
        subject = Lambda(self._extract_subject)([output, subject_ids])
        output = LayerNormalization(conditional=True)([output, subject])
        output = Dense(
            units=relation_num * 2,
            activation='sigmoid',
            kernel_initializer=bert.initializer
        )(output)
        output = Lambda(lambda x: x**4)(output)
        object_preds = Reshape((-1, relation_num, 2))(output)

        self.object_model = Model(bert.model.inputs + [subject_ids], object_preds)
        # object_model.summary()


        subject_preds, object_preds = TotalLoss([2, 3])([
            subject_labels, object_labels, subject_preds, object_preds,
            bert.model.output
        ])

        # 训练模型
        self.train_model = Model(
            bert.model.inputs + [subject_labels, subject_ids, object_labels],
            [subject_preds, object_preds]
        )
        # AdamEMA = extend_with_exponential_moving_average(Adam, name='AdamEMA')
        # optimizer = AdamEMA(learning_rate=1e-5)
        optimizer = Adam(learning_rate=1e-4) # 去掉EMA并增大学习率 (学习率为1e-3时,loss第三轮就超过了36,为1e-5时欠拟合也严重)
        self.train_model.compile(optimizer=optimizer)

    def _extract_subject(self, inputs):
        """根据subject_ids从output中取出subject的向量表征
        """
        output, subject_ids = inputs
        start = batch_gather(output, subject_ids[:, :1])
        end = batch_gather(output, subject_ids[:, 1:])
        subject = K.concatenate([start, end], 2)
        return subject[:, 0]

模型保存说明:使用keras中模型保存和加载

# 方法1
# 保存结构、参数、优化器参数,可以使用keras.models的load_model()进行加载,并可继续进行训练
subject_model.save('best_subject_model.model') 
# 加载
from keras.models import load_model
my_model = load_model('best_subject_model.model')

# 方法2
# 只保存了模型参数而没有保存模型结构,它的保存格式是hdf5。
# 只能通过Model对象提供的load_weights()方法加载模型权重(加载之前要构建一模一样的模型,然后该方法将权重喂给各层), 它保存的数据不能用于继续训练模型。
subject_model.save_weights('best_subject_weight.weight') 
object_model.save_weights('best_oubject_weight.weight')
# 加载   事先构建一模一样的模型结构subject_model
subject_model.load_weights('best_subject_weight.weight')

2、基于DGCNN[CNN、Attention、BiLSTM]

参考代码:苏神解读
模块导入:

'''keras == 2.2.4
   tensorflow == 1.8.0
   python3.6.8'''
from keras.layers import *
from keras.models import Model
import keras.backend as K
from keras.callbacks import Callback
from keras.optimizers import Adam

数据标签处理过程:

模型结构:
知识图谱中“三元组”抽取——Python中模型总结实战(基于TensorFlow2.5)_第11张图片

相关代码:

未完待续……
附:一个免费的在线画图软件

你可能感兴趣的:(TensorFlow,python,知识图谱,tensorflow)