跑通别人的模型可能很简单,但是自己亲自实现的时候却遇到各种各样的问题。不过还算比较幸运,自己目前遇到的问题,基本上都被前人解决了。其实这个模型网上一大堆,至于为什么要亲自实现,我给自己的理由是:如果我们想做点比较新颖的东西,那网上一定是没有的,终究还是要自己亲自实现,或者说去修改别人的代码。厚积薄发吧!!
和大家分享一下自己遇到的坑,也算是警示后来人吧。
网上看到这样实现的。这样是不正确的,要是这样写,会各种报错。
lstm_cell_fw = tf.nn.rnn_cell.LSTMCell(hidden_dim)
lstm_cell_bw = tf.nn.rnn_cell.LSTMCell(hidden_dim)
lstm_cell_fw = tf.nn.rnn_cell.MultiRNNCell([lstm_cell_fw] * num_layers)
lstm_cell_bw = tf.nn.rnn_cell.MultiRNNCell([lstm_cell_bw] * num_layers)
分析: 其实我觉得关键在这里 [lstm_cell_bw] * num_layers 当你想构建一个num_layers层的LSTM网络是,如果直接这样使用列表的乘法,那么,lstm的实体lstm_cell_bw会直接复制num_layers个相同的lstm实体,这样构造多层时就会报错。别的一些csdn文章也有指出,希望大家稍加注意吧。
下面是正确的写法:
#dropout
if is_training:#只在训练的时候进行丢弃
fw_cells=[]
bw_cells=[]
for i in range(rnn_layers_nums):
cell_fw=tf.nn.rnn_cell.LSTMCell(hidden_dim,name='fw_LSTM%d'%i)
dropcell_fw=tf.nn.rnn_cell.DropoutWrapper(cell_fw,output_keep_prob=(1-drop_rate))
fw_cells.append(dropcell_fw)
cell_bw=tf.nn.rnn_cell.LSTMCell(hidden_dim,name='bw_LSTM%d'%i)
dropcell_bw=tf.nn.rnn_cell.DropoutWrapper(cell_bw,output_keep_prob=(1-drop_rate))
bw_cells.append(dropcell_bw)
lstm_cell_fw=tf.nn.rnn_cell.MultiRNNCell(fw_cells)
lstm_cell_bw=tf.nn.rnn_cell.MultiRNNCell(bw_cells)
这样修改之后就不会报错,在循环里面定义了rnn_layers_nums个不同的lstm实体。
这个问题我还没有完全解决
就是如果按照网上的模型加载方法加载已经训练好的模型时,会疯狂报错
2020-10-10 15:22:02.198719: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2
2020-10-10 15:22:05.383373: W tensorflow/core/framework/op_kernel.cc:1502] OP_REQUIRES failed at save_restore_v2_ops.cc:184 : Not found: Key Variable not found in checkpoint
2020-10-10 15:53:09.486828: W tensorflow/core/framework/op_kernel.cc:1502] OP_REQUIRES failed at save_restore_v2_ops.cc:184 : Not found: Key Variable not found in checkpoint
2020-10-10 15:55:53.045559: W tensorflow/core/framework/op_kernel.cc:1502] OP_REQUIRES failed at save_restore_v2_ops.cc:184 : Not found: Key Variable not found in checkpoint
2020-10-10 16:00:48.395935: W tensorflow/core/framework/op_kernel.cc:1502] OP_REQUIRES failed at save_restore_v2_ops.cc:184 : Not found: Key Variable not found in checkpoint
看着意思是有关键的变量不能在checkpoint文件里面加载过来,在网上看了很多种解决方法,都不行。我刚开始没有给图节点命名,网上有个文章就是要命名啥的,后来我加了还是不可以。真是能把人气死,一个bug改了一下午
如果按照下面这种方法加载,那会报错
tf.initialize_all_variables().run(session=self.sess)#self.sess是在类里面定义的也就是一个会话
saver = tf.train.Saver()
saver.restore(self.sess, tf.train.latest_checkpoint(self.checkpoints_dir))#加载最新的模型
不知道为什么,这样加载模型的时候,一些图结点总是对应不起来,也就是上面出现的错误,一些关键的变量找不到,唉
后来,直接加载模型里面保存的图,然后在加载各个节点的权值
tf.initialize_all_variables().run(session=self.sess)
saver = tf.train.import_meta_graph('./check_point/model.ckpt.meta')#加载模型的图结构信息
saver.restore(self.sess, tf.train.latest_checkpoint(self.para.savepath))#加载最新的权值
按照上面这个就可以加载了。没有报错
比较烦的也就这两个吧,可能解决方法不是最完美的,也不知道别的tensorflow版本是否合适,我现在用的是TF1.14
如果有更好的解决方法,希望大佬指点,不吝赐教呀,十分感谢!!!
import argparse
class Hpara():
parser = argparse.ArgumentParser()#构建一个参数管理对象
parser.add_argument('--datapath',default='./data/data.csv',type=str)
parser.add_argument('--testdatapath',default='./data/test.csv',type=str)
parser.add_argument('--label2idpath',default='./data/label2id.json',type=str)
parser.add_argument('--word2idpath',default='./data/word2id.json',type=str)
parser.add_argument('--id2labelpath',default='./data/id2label.json',type=str)
parser.add_argument('--id2wordpath',default='./data/id2word.json',type=str)
parser.add_argument('--testlabel2idpath',default='./data/test_label2id.json',type=str)
parser.add_argument('--testword2idpath',default='./data/test_word2id.json',type=str)
parser.add_argument('--testid2labelpath',default='./data/test_id2label.json',type=str)
parser.add_argument('--testid2wordpath',default='./data/test_id2word.json',type=str)
parser.add_argument('--max_sen_len',default=30,type=int)
parser.add_argument('--word2vector_dim',default=100,type=int)
parser.add_argument('--hidden_dim',default=200,type=int)
parser.add_argument('--token_nums',default=18414,type=int)
parser.add_argument('--label_nums', default=8, type=int)
parser.add_argument('--rnn_layers_nums',default=2,type=int)
parser.add_argument('--is_training',default=True,type=bool)
parser.add_argument('--drop_rate',default=0.2,type=float)
parser.add_argument('--learning_rate',default=0.05,type=float)
parser.add_argument('--epochs',default=1,type=int)
parser.add_argument('--cell_type',default='LSTM',type=str)
parser.add_argument('--savepath',default='./check_point',type=str)
parser.add_argument('--batch_size',default=8,type=int)
import pandas as pd
import numpy as np
import json
from model_para import Hpara
hp=Hpara()
parser = hp.parser
para = parser.parse_args(args=[])
def Create_dataset_and_vocab(para):
data=pd.read_csv(para.datapath,delimiter='\t')
word=list(data['word'])
label=list(data['label'])
label_vocab=list(set([l for l in label if str(l) !='nan']))
word_vocab=list(set([w for w in word if str(w) !='nan']))
#接下来为两个vocab创建字典
label2id=dict(zip(label_vocab,range(1,len(label_vocab)+1)))
word2id=dict(zip(word_vocab,range(1,len(word_vocab)+1)))
id2label=dict(zip(range(1,len(label_vocab)+1),label_vocab))
id2word=dict(zip(range(1,len(word_vocab)+1),word_vocab))
padding='PAD'#加入填充的数值,因为我是使用全部的字典,就不考虑特殊字符UNK了,如果有需要也可以加
label2id[padding]=0
word2id[padding]=0
id2label[0]=padding
id2word[0]=padding
#接下来是要构建数据集了,csv文件是使用空白分割的每个句子
word=[str(w) for w in word]
label=[str(l) for l in label]
all_sen=' '.join(word)
all_ls=' '.join(label)
sens=all_sen.split('nan')
sens=[sen.strip(' ') for sen in sens]
labels=all_ls.split('nan')
labels=[l.strip(' ') for l in labels]
sens=[sen.split(' ') for sen in sens]
labels=[l.split(' ') for l in labels]
#创建两个全零矩阵
sens_np=np.zeros([len(sens),para.max_sen_len],dtype=int)
labels_np=np.zeros([len(sens),para.max_sen_len],dtype=int)
#将上面两个矩阵转化为id矩阵,不到max_sen_len长度的填充,过的截断
for i in range(len(sens)):
sen=sens[i]
label=labels[i]
if len(sen)<=para.max_sen_len:
for j in range(len(sen)):
sens_np[i,j]=word2id[sen[j]]
labels_np[i,j]=label2id[label[j]]
else:
for j in range(para.max_sen_len):
sens_np[i,j]=word2id[sen[j]]
labels_np[i,j]=label2id[label[j]]
#保存这几个词典
if para.is_training==True:
with open(para.label2idpath,'w') as f:
json.dump(label2id,f)
with open(para.word2idpath,'w') as f:
json.dump(word2id,f)
with open(para.id2labelpath,'w') as f:
json.dump(id2label,f)
with open(para.id2wordpath,'w') as f:
json.dump(id2word,f)
else:
with open(para.testlabel2idpath,'w') as f:
json.dump(label2id,f)
with open(para.testword2idpath,'w') as f:
json.dump(word2id,f)
with open(para.testid2labelpath,'w') as f:
json.dump(id2label,f)
with open(para.testid2wordpath,'w') as f:
json.dump(id2word,f)
return sens_np,labels_np
其中里面的自注意力机制的代码快是我自己写的,可能会有瑕疵,希望大神指点
# -*- coding: utf-8 -*-
"""
Created on Tue Oct 6 22:27:51 2020
@author: DELL
"""
import os
import tensorflow as tf
import numpy as np
from model_para import Hpara
from data_utils import Create_dataset_and_vocab
from tqdm import tqdm
tf.reset_default_graph()#每次运行重置图
class Mymodel():
'''
这里我只是使用双向LSTM+mask-self-attention+crf,别的情况就先不考虑,都是类似的
'''
def __init__(self,para):
self.para=para
self.optimizer = tf.train.AdamOptimizer(self.para.learning_rate,name='adam')
self.initializer = tf.contrib.layers.xavier_initializer()#设置一个初始化器,这个初始化可以使得梯度大致相等的
self.global_step = tf.Variable(0, trainable=False, name="global_step", dtype=tf.int32)
self.embedding = tf.get_variable("emb", [self.para.token_nums, self.para.word2vector_dim], trainable=True,
initializer=self.initializer)#如果变量存在,就直接加载过来,如果不存在,自动创建
self.saver=tf.train.Saver()
self.sess=tf.Session()
self.build_model()
def build_model(self):
'''
下面开始构建我们的模型,最重要的就是如何搭建一个神经网络图,等图搭建完了之后再输入数据进行训练
'''
#首先定义两个输入的占位符
self.inputs=tf.placeholder(tf.int32,[None,self.para.max_sen_len],name='inputs')
self.targets=tf.placeholder(tf.int32,[None,self.para.max_sen_len],name='labels')
#那么接下来就是嵌入层了,将单词token转化为嵌入向量
self.inputs_emb = tf.nn.embedding_lookup(self.embedding, self.inputs,name='embedding')
#定义前向网络和后向网络
#dropout
if self.para.is_training:#只在训练的时候进行丢弃
fw_cells=[]
bw_cells=[]
for i in range(self.para.rnn_layers_nums):
cell_fw=tf.nn.rnn_cell.LSTMCell(self.para.hidden_dim,name='fw_LSTM%d'%i)
dropcell_fw=tf.nn.rnn_cell.DropoutWrapper(cell_fw,output_keep_prob=(1-self.para.drop_rate))
fw_cells.append(dropcell_fw)
cell_bw=tf.nn.rnn_cell.LSTMCell(self.para.hidden_dim,name='bw_LSTM%d'%i)
dropcell_bw=tf.nn.rnn_cell.DropoutWrapper(cell_bw,output_keep_prob=(1-self.para.drop_rate))
bw_cells.append(dropcell_bw)
lstm_cell_fw=tf.nn.rnn_cell.MultiRNNCell(fw_cells)
lstm_cell_bw=tf.nn.rnn_cell.MultiRNNCell(bw_cells)
else:
lstm_cell_fw=tf.nn.rnn_cell.MultiRNNCell([tf.nn.rnn_cell.LSTMCell(self.para.hidden_dim) for _ in range(self.para.rnn_layers_nums)])
lstm_cell_bw=tf.nn.rnn_cell.MultiRNNCell([tf.nn.rnn_cell.LSTMCell(self.para.hidden_dim) for _ in range(self.para.rnn_layers_nums)])
# lstm_cell_fw=tf.nn.rnn_cell.DropoutWrapper(lstm_cell_fw,output_keep_prob=(1-self.para.drop_rate))
# lstm_cell_bw=tf.nn.rnn_cell.DropoutWrapper(lstm_cell_bw,output_keep_prob=(1-self.para.drop_rate))
#下面是多层的Rnn
# lstm_cell_fw = tf.nn.rnn_cell.MultiRNNCell([lstm_cell_fw] * self.para.rnn_layers_nums)
# lstm_cell_bw = tf.nn.rnn_cell.MultiRNNCell([lstm_cell_bw] * self.para.rnn_layers_nums)
#计算一下输入的句子的长度
self.length_sens=tf.reduce_sum(tf.sign(self.inputs),axis=1,name='calcu_len')#需要将计算出的句子的长度传入给下面的函数,其实我觉得这里的长度计算还可以用来做mask-attention,正好一举两得
self.length_sens=tf.cast(self.length_sens,dtype=tf.int32,name='cast1')
outputs,state=tf.nn.bidirectional_dynamic_rnn(lstm_cell_fw,lstm_cell_bw,inputs=self.inputs_emb,sequence_length=self.length_sens,dtype=tf.float32)
#上面这行代码的outputs的是一个有两个元素的元组,一个前向的输出,一个后向的输出,大小均为[batch_size,unroll_steps,vector_dim]
#再将两个输出在最后一个维度进行拼接,双向循环神经网络的输出拼接方式有好几种呢。
outputs=tf.concat(outputs,2,name='concat1')#拼接完之后的最后一个维度会变为原来的二倍,当然你也可以将输出在第二个维度进行相加
#下面开始进行注意力机制
att_Q=tf.Variable(tf.random.truncated_normal(shape=[self.para.hidden_dim*2,self.para.hidden_dim*2]),trainable=True,name='attenion_size_Q')
att_K=tf.Variable(tf.random.truncated_normal(shape=[self.para.hidden_dim*2,self.para.hidden_dim*2]),trainable=True,name='attenion_size_K')
att_V=tf.Variable(tf.random.truncated_normal(shape=[self.para.hidden_dim*2,self.para.hidden_dim*2]),trainable=True,name='attenion_size_V')
Q=tf.matmul(outputs,att_Q,name='q')
K=tf.matmul(outputs,att_K,name='k')
V=tf.matmul(outputs,att_V,name='v')
qk=tf.matmul(Q,tf.transpose(K,[0,2,1],name='t1'),name='qk')/tf.sqrt(tf.constant(self.para.hidden_dim*2,dtype=tf.float32,name='scaled_factor'),name='sqrt1')#现在qk的大小是[batch_size,max_len,max_len]
#下面开始计算mask矩阵
mask=tf.sign(self.inputs,name='s1')#大小是[batch_size,max_len]
mask=tf.expand_dims(mask,1,name='expand1')#大小是[batch_size,1,max_len]
mask=tf.tile(mask,[1,self.para.max_sen_len,1],name='tile1')#大小是[batch_size,max_len,max_len]
padding_mask=-2**22+1
#下面开始mask,其实也就是将计算出的权值在padding的单词部分设置为一个非常小的数padding_mask=-2**32+1
#这样再经过softmax的时候,会将的padding的单词的权重变成一个十分接近0的数
weights=tf.nn.softmax(tf.where(tf.equal(mask,1),qk,tf.cast(tf.ones_like(mask)*padding_mask,dtype=tf.float32)),name='softmax')#[batch_size,maxlen,maxlen]
#计算好权值之后,接下来就是计算Z
Z=tf.matmul(weights,V,name='weighted_V')
#下面开始条件随机场
crf_w=tf.Variable(tf.random.truncated_normal([self.para.hidden_dim*2,self.para.label_nums]),name='crf_w')
#将Z调整为[batch_size,max_len,label_nums],也就是每句话里面每个单词的标签是什么,接下来将该张量输入crf
self.Z=tf.matmul(Z,crf_w,name='crf_inputs')
self.log_likelihood, self.transition_params = tf.contrib.crf.crf_log_likelihood(
self.Z, self.targets, self.length_sens)
self.batch_pred_sequence, self.batch_viterbi_score = tf.contrib.crf.crf_decode(self.Z,
self.transition_params,
self.length_sens)
self.loss = tf.reduce_mean(-self.log_likelihood,name='loss')
self.optimizer_op=self.optimizer.minimize(self.loss,global_step=self.global_step)
#接下来使用上面的crf_decode的解码输出计算准确率,无论我们需要什么结果,都可以在这里定义节点
self.acc=tf.reduce_mean(tf.cast(tf.equal(self.batch_pred_sequence,self.targets),dtype=tf.float32))/tf.constant(self.para.batch_size*self.para.max_sen_len,dtype=tf.float32)
#上面这个准确率我也不知道该怎么定义,啊哈哈哈,就用一下所有预测正确的除去每个batch总的单词数吧,啊哈哈哈,我太菜了
def batch_iter(self):
train_data,train_label=Create_dataset_and_vocab(self.para)
data_len = len(train_data)
num_batch = (data_len + self.para.batch_size - 1) // self.para.batch_size # 获取的是
indices = np.random.permutation(np.arange(data_len)) # 随机打乱下标
x_shuff = train_data[indices]
y_shuff = train_label[indices] # 打乱数据
for i in range(num_batch): # 按照batchsize取数据
start_offset = i * self.para.batch_size # 开始下标
end_offset = min(start_offset + self.para.batch_size, data_len) # 一个batch的结束下标
yield i, num_batch, x_shuff[start_offset:end_offset], y_shuff[
start_offset:end_offset] # yield是产生第i个batch,输出总的batch数,以及每个batch的训练数据和标签
def train(self):
loss=[]
if not os.path.exists(self.para.savepath):#判断模型文件是否存在
print('Create model file')
os.makedirs(para.savepath)
else:
self.saver.restore(self.sess,os.path.join(self.para.savepath,'model.ckpt'))
self.sess.run(tf.global_variables_initializer())
for k in tqdm(range(self.para.epochs)):
batch_train = self.batch_iter()
for i,total_num,data_step,label_step in batch_train:
_,ls,pred=self.sess.run([
self.optimizer_op,
self.loss,
self.batch_pred_sequence,
],
feed_dict={
self.inputs:data_step,
self.targets:label_step
})
loss.append(ls)
if i %100==0:
print('loss is :',ls)
self.saver.save(self.sess,os.path.join(self.para.savepath,'model.ckpt'))
return loss
def test(self):
#首先加载已经训练好的模型文件
tf.initialize_all_variables().run(session=self.sess)
saver = tf.train.import_meta_graph('./check_point/model.ckpt.meta')
saver.restore(self.sess, tf.train.latest_checkpoint(self.para.savepath))
#然后是加载数据集
batch_test=self.batch_iter()
for i,total_num,data_step,label_step in batch_test:
batch_pred_label=self.sess.run([self.batch_pred_sequence],feed_dict={
self.inputs:data_step,self.targets:label_step})
#还可以加一些计算准确率的运算,我就不弄了,很简单,和train的一样
if __name__ == "__main__":
hp = Hpara()
parser = hp.parser
para = parser.parse_args()
model=Mymodel(para)
#loss=model.train()
model.test()
链接:https://pan.baidu.com/s/135nceS4Gd3VwM7eFV7Lokg
提取码:cj2u
复制这段内容后打开百度网盘手机App,操作更方便哦
主要参考代码GitHub项目
TF模型加载和保存
最后:当然还有很多别的参考文献,十分感谢这些大神的分享,在人工智能这条路上,我也会将自己的想法,遇到的问题以及解决方法等等分享给更多的人,大家一起进步。
最后,转载注明出处哈。