赞
踩
【基于java的,安装使用前必须先安装java环境】
1、安装:pip install pyhanlp
【安装过程中会自动安装jpype1,该模块仅支持到python3.7,所以python3.7以上的安装老是报错。】
2、使用:
1)分词
import pyhanlp
print([i.word for i in pyhanlp.HanLP.segment('我们都是中国人,坚持一个中国原则。')])
【官方GitHub介绍:https://stanfordnlp.github.io/stanfordnlp/training.html】
1、安装:pip 安装
pip install stanfordnlp --proxy 111.666.88.688:808
2、简单使用
import stanfordnlp
【学习手札:https://blog.csdn.net/MebiuW/article/details/52496920 】
【基于C++的】
GitHub:https://github.com/thunlp/OpenNRE#datasets
清华大学自然语言处理与社会人文计算实验室(THUNLP)推出的一款开源的神经网络关系抽取工具包,包括了多款常用的关系抽取模型。
使用wiki80数据集,包含80种关系。(也可以自己训练)
但是都是英文数据集,使用也都是基于英文的……
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
【注意】
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;
③添加环境变量:比如我的
导入模块、加载模型、预测
>>> 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)
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'
理解说明:
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类构建的模型结构:
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值、准确率
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
BERT中包含四种special token,分别是:
[UNK]:指代在vocab中找不到的字/词
[CLS]:添加在每句句首。对于用于分类的向量,会聚集所有的分类信息
[SEP]:添加在每句句尾
[MASK]:使用[MASK]替换句子中的部分字/词。用于MLM(屏蔽语言模型)。
BERT的三种输入(可通过模块中的tokenizer处理得到):字向量token_ids,段向量segment_ids、位置向量position_ids
token_ids:使用 vocab 中的字索引表示的向量集合,shape=(batch_size, seq_len)
segment_ids:当每个文本只有一个句子时,则segment_ids为0向量,shape=(batch_size, seq_len);BERT处理句子对的分类任务时(就是判断两个文本是否是语义相似的),会将两个句子拼接作为输入,此时segment_ids为前一句的0向量和后一句的1向量拼接而成
position_ids:对应位置下标,由于是固定的,会在模型内部生成,不需要手动再输入一遍
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
【注意】当输入两个文本时:(与bert4keras很类似)
r=tokenizer.tokenize('我是中糹国人!', second='谁不说俺家乡好,啊啊啊啊!') # 对句子分字,并在句首和句尾分别添加[CLS]、[SEP]
print(r)# 结果:['[CLS]', '我', '是', '中', '糹', '国', '人', '!', '[SEP]']
r_encode=tokenizer.encode('我是糹中国人!', second='谁不说俺家乡好,啊啊啊啊!') # 返回array类型的tikens_id, segments_id
print(r_encode)
结果:
方式一、模型构建代码:继承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
模型结构:
方式二、模型构建代码:链式(串联)方式
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
模型结构图:
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()
模型层次结构:
未完待续
任务实质: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)
结果:
**【注意】**当输入两个文本时:
tokenizer = Tokenizer(dict_path, do_lower_case=True)
token_ids, segment_ids = tokenizer.encode('查尔斯·阿兰基斯(Charles Aránguiz),1989年4月17日出生于智利圣地亚哥,智利职业足球运动员,司职中场,效力于德国足球甲级联赛勒沃库森足球俱乐部',
second_text = '中国是社会主义社会,坚持共产党的领导,为人民服务,必须是为人民。',
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出现重叠的极端情况:如《鲁迅自传》由江苏文艺出版社出版,包含三元组 (鲁迅自传, 作者, 鲁迅) 。
模型结构:【基于GPLinker使用相同的环境配置】
调用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')
参考代码:苏神解读
模块导入:
'''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
数据标签处理过程:
模型结构:
相关代码:
未完待续……
附:一个免费的在线画图软件
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。