import tensorflow.compat.v1 as tf
#import tensorflow as tf
import numpy as np
#from tensorflow.contrib import rnn
from tensorflow.python.ops import rnn,rnn_cell
#from tensorflow.contrib.crf import crf_log_likelihood #crf_log_likehood条件随机场的对数似然
from tensorflow_addons.text.crf import crf_log_likelihood
import inspect
#from tensorflow.python.keras.optimizers import adam_v2
from tensorflow.python.keras.optimizers import Adam
tf.disable_v2_behavior()
from tensorflow import keras
#from tensorflow.keras import layers
#x=layers.Flatten()
# In[2]:
def network(inputs,shapes,num_tags,lstm_dim=100,initializer=tf.compat.v1.truncated_normal_initializer()):
#接收一个批次样本的特征数据,计算出网络输出值
#:param char:2-D[None,None]第一个是批次,第二个是长度,填充的长度
#:param bound:2-D[None,None]
#:param flag:2-D[None,None]
#:param radical:2-D[None,None]
#:param pinyin:2-D[None,None]
#-----------------------------------特征嵌入(embedding_lookup)————将所有特征id转换成一个固定长度的向量---------------------
embedding=[]
keys=list(shapes.keys())
for key in keys:
#下面转化为循环语句代替
with tf.variable_scope(key+'_embedding'):#定义一个变量空间
lookup=tf.get_variable(
name=key+'_embedding',
shape=shapes[key],
initializer=initializer#初始化
)
embedding.append(tf.nn.embedding_lookup(lookup, inputs[key]))#对inputs里的char等进行映射,分别将char等特征映射成char_lookup的向量
embed=tf.concat(embedding,axis=-1)#最后一个维度上进行特征拼接 #shape [None,None,char_dim+bound_dim+flag_dim+radical_dim+pinyin_dim]
sign=tf.sign(tf.abs(inputs[keys[0]]))#将不是0都变为1,最终将每一行求和变为句子真实长度 tf.sign将负数变为-1,0变为0,正数变为1 tf.abs()求数值的绝对值
lengths=tf.reduce_sum(sign,reduction_indices=1)#reduce_sum去掉所有长度为1,若char第一个批次为1,怕两个将都去掉,因此指定reduction_indices=1
#最终求出句子的真实长度
num_time=tf.shape(inputs[keys[0]])[1]#获取序列的长度(包括填充的长度)
#-------------------------循环神经网络编码---------------------------------------
with tf.variable_scope('BiLstm_layer1'):#定义第一层的BILSTM
lstm_cell={}#应该有两个cell
for name in['forward1','backward1']:#第一层的前向与后向
with tf.variable_scope(name):
lstm_cell[name]=tf.nn.rnn_cell.BasicLSTMCell(
#状态向量的维度是个稳定的数值,需要定义lstm_dim进行传递(默认100)
lstm_dim,#神经元的个数
)
outputs1,finial_states1=tf.nn.bidirectional_dynamic_rnn(
lstm_cell['forward1'],
lstm_cell['backward1'],
embed,
dtype=tf.float32,
sequence_length=lengths#将每个句子的实际长度告知,(由于前文的进行句子填充,减少计算)
)#bidirectional_dynamic_rnn为双向动态rnn
outputs1=tf.concat(outputs1,axis=-1)#b,L,2*LSTM_DIM
with tf.variable_scope('BiLstm_layer2'):#定义第二层的BILSTM
lstm_cell={}#应该有两个cell
for name in['forward','backward']:#第二层的前向与后向
with tf.variable_scope(name):
lstm_cell[name]=tf.nn.rnn_cell.BasicLSTMCell(
#状态向量的维度是个稳定的数值,需要定义lstm_dim进行传递(默认100)
lstm_dim#神经元的个数
)
outputs,finial_statesl=tf.nn.bidirectional_dynamic_rnn(
lstm_cell['forward'],
lstm_cell['backward'],
outputs1,
dtype=tf.float32,
sequence_length=lengths#将每个句子的实际长度告知,(由于前文的进行句子填充,减少计算)
) #bidirectional_dynamic_rnn为双向动态rnn
output=tf.concat(outputs,axis=-1)#batch_size,max_length,2*lstm_dim
#----------------------------------------输出映射------------------矩阵相乘-------两个全连接
output=tf.reshape(output,[-1,2*lstm_dim])#reshape成二维矩阵 batch_size*maxlength,2*lstm_dim
with tf.variable_scope('project_layer1'):
w=tf.get_variable(
name='w',
shape=[2*lstm_dim,lstm_dim],
initializer=initializer
)
b=tf.get_variable(
name='b',
shape=[lstm_dim],
initializer=tf.zeros_initializer()
)
output=tf.nn.relu(tf.matmul(output,w)+b)#tf.nn.relu为激活函数
with tf.variable_scope('project_layer2'):
w=tf.get_variable(
name='w',
shape=[lstm_dim,num_tags],
initializer=initializer
)
b=tf.get_variable(
name='b',
shape=[num_tags],
initializer=tf.zeros_initializer()
)
output=tf.matmul(output,w)+b
output=tf.reshape(output,[-1,num_time,num_tags])
return output,lengths #batch_size,max_length,num_tags lengths
#最终将序列的输出和每一句话的真实长度都返回给我
# In[3]:
class Model(object):
def __init__(self,dict,lr=0.0001):
#------------用到的参数值---------------
self.num_char=len(dict['word'][0])#具体有多少个字
self.num_bound=len(dict['bound'][0])
self.num_flag=len(dict['flag'][0])
self.num_radical=len(dict['radical'][0])
self.num_pinyin=len(dict['pinyin'][0])
self.num_tags=len(dict['label'][0])
self.char_dim=100#字符要映射成多少的向量,即每个字的特征
self.bound_dim=20#每个边界特征
self.flag_dim=50#每个字标签特征
self.radical_dim=50#每个偏旁特征
self.pinyin_dim=50#每个拼音特征
self.lstm_dim=100
self.lr=lr
#---------------------定义接收数据的placeholder--------------------------
tf.compat.v1.disable_eager_execution()
# 修改前--x = tf.placeholder(dtype=tf.int32,shape=[None,None],name='number')
# 修改后--x = tf.Variable(tf.zeros(shape=[None,None]),name='number')
#self.char_inputs=tf.Variable(tf.zeros(shape=[None,None]),name='char_inputs')
self.char_inputs=tf.placeholder(dtype=tf.int32,shape=[None,None],name='char_inputs')
#self.bound_inputs=tf.Variable(tf.zeros(shape=[None,None]),name='bound_inputs')
self.bound_inputs=tf.placeholder(dtype=tf.int32,shape=[None,None],name='bound_inputs')
#self.flag_inputs=tf.Variable(tf.zeros(shape=[None,None]),name='flag_inputs')
self.flag_inputs=tf.placeholder(dtype=tf.int32,shape=[None,None],name='flag_inputs')
#self.radical_inputs=tf.Variable(tf.zeros(shape=[None,None]),name='radical_inputs')
self.radical_inputs=tf.placeholder(dtype=tf.int32,shape=[None,None],name='radical_inputs')
#self.pinyin_inputs=tf.Variable(tf.zeros(shape=[None,None]),name='pinyin_inputs')
self.pinyin_inputs=tf.placeholder(dtype=tf.int32,shape=[None,None],name='pinyin_inputs')
#self.targets=tf.Variable(tf.zeros(shape=[None,None]),name='targets')
self.targets=tf.placeholder(dtype=tf.int32,shape=[None,None],name='targets')#target为真实值
self.global_step=tf.Variable(0,trainable=False)
#global_step记录的是当前训练的迭代次数 trainable的作用就是决定该层在训练时是否更新权重。当为True时则更新,为False时不更新
#无需更新,只用来计数
#---------------------计算模型输出值---------------------------
self.logits,self.lengths=self.get_logits(self.char_inputs,
self.bound_inputs,
self.flag_inputs,
self.radical_inputs,
self.pinyin_inputs
)
#----------------------计算损失--------------------------------
self.cost=self.loss(self.logits,self.targets,self.lengths)
#----------------------优化器优化---------------------------(如果后期损失值过大,考虑梯度是否过大)
#采用梯度截断技术
#tf.variable_scope用于定义创建变量(层)的操作的上下文管理器。
#with tf.variable_scope('optimizer'):
#opt=adam_v2.Adam(self.lr)
#opt=tf.keras.optimizers.Adam(self.lr)
#opt=tf.train.AdamOptimizer(self.lr)
#grad_vars=opt.compute_gradients(self.cost)
#计算出所有参数的导数
#clip_grad_vars=[[tf.clip_by_value(g,-5,5),v] for g,v in grad_vars]
#按照值进行截断,g、v通过限定值进行截断
#clip_grad_vars为截断之后的梯度
#self.train_op=opt.apply_gradients(clip_grad_vars,self.global_step)
#使用截断后的梯度,进行参数的更新 即clip_grad_vars每次更新,后续计数加一
#self.saver=tf.Train.Saver(tf.global_variables(),max_to_keep=5)#模型保存器 max_to_keep=5只保留5次最新的模型,如果硬盘足够大,可以大一些
with tf.variable_scope('optimizer'):
#opt=adam_v2.Adam(self.lr)
opt=Adam(self.lr)
with tf.GradientTape() as tape:
grads=tape.gradient(self.cost,tf.trainable_variables())
clip_grad_vars = [[tf.clip_by_value(g, -5, 5), v] for g, v in zip(grads, dict)]
# 使用截断后的梯度,进行参数的更新,即clip_grad_vars每次更新,后续计数加1
self.train_op = opt.apply_gradients(clip_grad_vars, self.global_step)
self.saver = tf.train.Saver(tf.global_variables(), max_to_keep=5)
def get_logits(self,char,bound,flag,radical,pinyin):#定义一个网络,接受一个批次样本的特征数据,计算一个网络的输出值。即x-f(x),不计算损失
#即为输出
#char形状是2D[None,None],第一个是批次,第二个是数据长度
#bound-2D
#flag-2D
#radicial-2D
#pinyin-2D
#return:3-D[batch_size,max_length,num_tags]
shapes={}
shapes['char']=[self.num_char,self.char_dim]
shapes['bound']=[self.num_bound,self.bound_dim]
shapes['flag']=[self.num_flag,self.flag_dim]
shapes['radical']=[self.num_radical,self.radical_dim]
shapes['pinyin']=[self.num_pinyin,self.pinyin_dim]
inputs={}
inputs['char']=char
inputs['bound']=bound
inputs['flag']=flag
inputs['radical']=radical
inputs['pinyin']=pinyin
return network(inputs,shapes,lstm_dim=self.lstm_dim,num_tags=self.num_tags)
#------------------定义损失函数---------------------
def loss(self,output,targets,lengths):#需要接受bilstm输出,真实的标签和真实的句子长度
b=tf.shape(lengths)[0]
num_steps=tf.shape(output)[1]#num_steps即加一个时刻
with tf.variable_scope('crf_loss'):#转移矩阵
small=-1000.0#即概率接近0
start_logits=tf.concat(
[small*tf.ones(shape=[b,1,self.num_tags]),tf.zeros(shape=[b,1,1])],axis=-1
)#shape=[b,1,self.num_tags]为-1000,最后第32个即tf.zeros(shape=[b,1,1])为0
pad_logits=tf.cast(small*tf.ones([b,num_steps,1]),tf.float32)#pad_logits每个时刻加一个状态
logits=tf.concat([output,pad_logits],axis=-1)
logits=tf.concat([start_logits,logits],axis=1)
targets=tf.concat(
[tf.cast(self.num_tags*tf.ones([b,1]),tf.int32),targets],axis=-1
)
self.trans=tf.get_variable(
name='trans',
shape=[self.num_tags+1,self.num_tags+1],
initializer=tf.truncated_normal_initializer()
)#self.trans转移矩阵体现关系
log_likehood,self.trans=crf_log_likelihood(
inputs=logits,
tag_indices=targets,
transition_params=self.trans,
sequence_lengths=lengths
)
return tf.reduce_mean(-log_likehood)#log_likehood最大化,但是为了降维最小化,加负号
# In[4]:
#bilstm进行特征提取,CRF相较于传统的CRF++的统计算法,整个序列作归一化,将200维向量映射为31维向量
grads=tape.gradient(self.cost,tf.trainable_variables())这一句报错,求解答