Python3.6和tensorflow1.14实现Bi-LSTM+Self-Attention+CRF实现命名实体识别

一、自己亲自实现过程中踩的坑

跑通别人的模型可能很简单,但是自己亲自实现的时候却遇到各种各样的问题。不过还算比较幸运,自己目前遇到的问题,基本上都被前人解决了。其实这个模型网上一大堆,至于为什么要亲自实现,我给自己的理由是:如果我们想做点比较新颖的东西,那网上一定是没有的,终究还是要自己亲自实现,或者说去修改别人的代码。厚积薄发吧!!
和大家分享一下自己遇到的坑,也算是警示后来人吧。

1.1、TF1.14版本如何构建多层的LSTM或者别的RNN模型

网上看到这样实现的。这样是不正确的,要是这样写,会各种报错。

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实体。

1.2、模型的加载和保存

这个问题我还没有完全解决
就是如果按照网上的模型加载方法加载已经训练好的模型时,会疯狂报错

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

如果有更好的解决方法,希望大佬指点,不吝赐教呀,十分感谢!!!

二、代码(都加了注释)

2.1、创建model_para.py文件,该文件主要用来管理模型的参数,不至于想要的修改的时候,来回的找

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)

2.2、创建data_utils.py,该文件主要是加载数据集以及保存词典与id的对应关系,也很简单

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

2.3、创建model_modules.py,主要是模型的搭建、训练、测试

其中里面的自注意力机制的代码快是我自己写的,可能会有瑕疵,希望大神指点

# -*- 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模型加载和保存

最后:当然还有很多别的参考文献,十分感谢这些大神的分享,在人工智能这条路上,我也会将自己的想法,遇到的问题以及解决方法等等分享给更多的人,大家一起进步。
最后,转载注明出处哈。

  • 9
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip 人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip 人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip 人工智能NER作业-基于BiLSTM+CRF实现命名实体识别python源码+数据+实验报告.zip

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值