前几天看到一篇关于大连理工大学的研三学长的去世新闻,仔细看了他的遗书,很是泪目。他说同样的条件,做出的实验结果是不同的。
在训练我这个模型的时候,深深体会到了这个感受,有时候收敛,有时候无论怎么也不收敛。可能这个还容易解释一点,模型的很多参数是初始化的,不同的参数会跑到局部最you,模型陷在了一个局部最优点,出不去。
可能我这个模型的结构和参数都有问题,在训练过程中,损失最低也就是0.9+,然后别的一直就很高,都是三位数的损失,然后在测试集上训练的损失也很高,很是头疼的。
希望大家一起加油,马克思哲学告诉我们,前途是光明的,但道路是曲折的,事物总是螺旋上升,遵循否定之否定规律。好吧!!!不装了,不过马克思哲学是真的厉害噢,很佩服马克思的洞察力。愿逝者安息,下辈子成功做一只有人爱的猫猫。
感觉是自己导师给了自己机会,自己没有把握住,入学一个多月了,我一直都是在游离的状态,导师让我跟着一个博士师姐做项目,我觉得有时自己在逃避,自己乱学。其实,这些知识,是永远学不完的,只能以点带面,专注一个方向,接下来决定花时间好好帮助师姐做项目了--------方面级情感分析。导师也有很大的可能性让我开这个方向的题目。加油,是要好好看论文了。我真菜噢,课题组最清闲的人。
有些参数没有用到,自己改改吧
# -*- coding: utf-8 -*-
"""
Created on Wed Oct 14 20:24:48 2020
@author: DELL
"""
import argparse
class Hpara():
parser = argparse.ArgumentParser()#构建一个参数管理对象
parser.add_argument('--traindatapath',default='./data/train.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('--attenion_drop_rate',default=0.2,type=float)
parser.add_argument('--attention_size',default=400,type=int)
parser.add_argument('--is_mask',default=True,type=bool)
parser.add_argument('--max_sen_len',default=200,type=int)
parser.add_argument('--train_nums',default=19717,type=int)
parser.add_argument('--test_nums',default=200,type=int)
parser.add_argument('--word2vector_dim',default=100,type=int)
parser.add_argument('--hidden_dim',default=400,type=int)
parser.add_argument('--token_nums',default=9275,type=int)
parser.add_argument('--label_nums', default=14, 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.1,type=float)
parser.add_argument('--learning_rate',default=0.005,type=float)
parser.add_argument('--epochs',default=150,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=50,type=int)
之前一个师姐说,拿到一个模型,需要改写的最多的就是数据处理部分,确实是这样,你拿到模型之后,别人的数据集和我们的一般是不一样的,那么我们首先要做的事,就是把自己的数据转化为这个模型需要的形式。
下面的代码就是,两个数据集不一样,那么处理的时候,就要写不同的程序处理的。
mport 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 data_process(para):
'''
我想使用训练数据和测试数据里面的全部标签和文本,这样就不用在标签字典里面加入
特殊字符,也就词典包括所有词和符号,如果有需要,还要修改数据处理的部分的代码
Parameters
----------
para : parser 实例
管理各种模型的参数,这样修改起来也简单方便.
Returns
-------
返回训练数据以及保存各种中间变量.
'''
train_data=pd.read_csv(para.traindatapath,delimiter=' ')
test_data=pd.read_csv(para.testdatapath,delimiter=' ')
train_text=list(train_data['text'])
train_label=list(train_data['label'])
test_text=list(test_data['text'])
test_label=list(test_data['label'])
text_set=list(set(list(train_data['text'])+list(test_data['text'])))
label_set=list(set(list(train_data['label'])+list(test_data['label'])))
#词和标签转化为id
word2id=dict(zip(text_set,range(1,len(text_set)+1)))
label2id=dict(zip(label_set,range(1,len(label_set)+1)))
#id转化为单词和标签
id2word=dict(zip(range(1,len(text_set)+1),text_set))
id2label=dict(zip(range(1,len(label_set)+1),label_set))
padding='PAD'#加入填充的数值,因为我是使用全部的字典,就不考虑特殊字符UNK了,如果有需要也可以加
label2id[padding]=0
word2id[padding]=0
id2label[0]=padding
id2word[0]=padding
def convert_to_array(text,label,word_vocab,label_vocab,para,training):
#将数据转化为矩阵
indics=[i for i,x in enumerate(text) if x=='。']#多少个句号就有多少句话,数据集有点大,就取前2000行吧
indics.insert(0,-1)
if training:
nums=para.train_nums
array=np.zeros([nums,para.max_sen_len],dtype=np.int32)
array_label=np.zeros([nums,para.max_sen_len],dtype=np.int32)
else:
nums=para.test_nums
array=np.zeros([nums,para.max_sen_len],dtype=np.int32)
array_label=np.zeros([nums,para.max_sen_len],dtype=np.int32)
for i in range(1,nums+1):
index=indics[i]
last_index=indics[i-1]
for j in range(index-last_index):
if j>99:
break
word=text[j+last_index+1]
l=label[j+last_index+1]
array[i-1,j]=word_vocab[word]
array_label[i-1,j]=label_vocab[l]
return array,array_label
train_array,train_label_array=convert_to_array(train_text,train_label,word2id,label2id,para,True)
test_array,test_label_array=convert_to_array(test_text,test_label,word2id,label2id,para,False)
#下面将数据返回,并且保存词典
#保存这几个词典
if para.is_training:
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 train_array,train_label_array,test_array,test_label_array
def data_process_ds1(para):
train_data=pd.read_csv(para.traindatapath,delimiter='\t')
test_data=pd.read_csv(para.testdatapath,delimiter='\t')
train_data_no_nan=train_data.dropna()
test_data_no_nan=test_data.dropna()
train_text=list(train_data['text'])
train_label=list(train_data['label'])
test_text=list(test_data['text'])
test_label=list(test_data['label'])
word_vocab=list(set(list(train_data_no_nan['text'])+list(test_data_no_nan['text'])))
label_vocab=list(set(list(train_data_no_nan['label'])+list(test_data_no_nan['label'])))
#建立词典
word2id=dict(zip(word_vocab,range(1,len(word_vocab)+1)))
label2id=dict(zip(label_vocab,range(1,len(label_vocab)+1)))
id2word=dict(zip(range(1,len(word_vocab)+1),word_vocab))
id2label=dict(zip(range(1,len(label_vocab)+1),label_vocab))
word2id['pad']=0
label2id['pad']=0
id2label[0]='pad'
id2word[0]='pad'
#下面开始找nan的位置
def create_ds_arr(text,label,word2id,label2id,para):
nan_ids=[i for i,t in enumerate(text) if str(t)=='nan']
nan_ids.insert(0,-1)
train_arr=np.zeros([len(nan_ids)-1,para.max_sen_len],dtype=np.int32)
train_l=np.zeros([len(nan_ids)-1,para.max_sen_len],dtype=np.int32)
for i in range(1,len(nan_ids)):
index=nan_ids[i]
last_index=nan_ids[i-1]
for j in range(index-last_index-1):
if j>=para.max_sen_len:
break
word=text[j+last_index+1]
l=label[j+last_index+1]
train_arr[i-1,j]=word2id[word]
train_l[i-1,j]=label2id[l]
return train_arr,train_l
t1,l1=create_ds_arr(train_text,train_label,word2id,label2id,para)
t2,l2=create_ds_arr(test_text,test_label,word2id,label2id,para)
return t1,l1,t2,l2
自己写了两种注意力机制的代码实现,带掩码的自注意力机制和带掩码的加性注意力。加性注意力的实现是有点问题的,因为原始论文实现的使用了编码器和解码器的隐向量,但是对于这个模型,相当于只有编码器的隐向量,如何实现加性注意力还需要灵活应用的。不知道你们有没有一些感觉,就是注意力机制,其实就是在考虑如何生成一组隐向量的权值,其实这个生成的方法是很多很多的,但是具体哪个效果好,还要自己设计实验来验证,所以,同志们,大胆的去胡思乱想吧,说不定自己设计的忘了的结果就很好。深度学习缺少严格健全的数学理论支撑,所以,计算机到底学到了什么,我们也不知道,可能,他们可以学习的知识系统呢,啊哈哈哈
# -*- coding: utf-8 -*-
"""
Created on Thu Oct 15 14:21:07 2020
@author: DELL
"""
import tensorflow as tf
from tensorflow.keras import layers
import tensorflow_addons as tfa
class RNN_layer(layers.Layer):
def __init__(self,para):
super().__init__(self)
self.para=para
self.fw_lstm1=layers.LSTM(self.para.hidden_dim, return_sequences = True, go_backwards= False, dropout = self.para.drop_rate, name = "fwd_lstm")
self.bw_lstm1=layers.LSTM(self.para.hidden_dim, return_sequences = True, go_backwards= True, dropout = self.para.drop_rate, name = "bwd_lstm")
self.bilstm1=layers.Bidirectional(merge_mode = "concat", layer = self.fw_lstm1, backward_layer = self.bw_lstm1, name = "bilstm")
self.fw_lstm2=layers.LSTM(self.para.hidden_dim, return_sequences = True, go_backwards= False, dropout = self.para.drop_rate, name = "fwd_lstm")
self.bw_lstm2=layers.LSTM(self.para.hidden_dim, return_sequences = True, go_backwards= True, dropout = self.para.drop_rate, name = "bwd_lstm")
self.bilstm2=layers.Bidirectional(merge_mode = "concat", layer = self.fw_lstm2, backward_layer = self.bw_lstm2, name = "bilstm")
def call(self,inputs):
outputs=self.bilstm1(inputs,training=self.para.is_training)
#
return outputs
#自注意力机制层
class Self_Attention_Layer(layers.Layer):
def __init__(self,para):
super().__init__(self)
self.para=para
self.dense_Q=layers.Dense(self.para.attention_size,use_bias=False,trainable=True,kernel_initializer=tf.keras.initializers.GlorotNormal())
self.dense_K=layers.Dense(self.para.attention_size,use_bias=False,trainable=True,kernel_initializer=tf.keras.initializers.GlorotNormal())
self.dense_V=layers.Dense(self.para.attention_size,use_bias=False,trainable=True,kernel_initializer=tf.keras.initializers.GlorotNormal())
self.dropout=layers.Dropout(self.para.attenion_drop_rate,name='attenion_drop')
self.softmax=layers.Softmax()
def call(self,inputs,sen_len):
#就算QKV
Q=self.dense_Q(inputs)
K=self.dense_K(inputs)
V=self.dense_V(inputs)
#下面开始做注意力机制,如果使用mask操作,还要用到句子的长度,不使用mask操作会简单很多
QK=tf.matmul(Q,tf.transpose(K,[0,2,1]))#现在QK的大小是[batch_size,max_sen_len,max_sen_len]
if self.para.is_mask:
#接下来实现带有mask操作的自注意力机制,之前尝试使用句子的长度来做mask没有弄成,现在再次尝试
mask=tf.sequence_mask(sen_len,maxlen=self.para.max_sen_len)
mask=tf.expand_dims(mask,1)#mask主要是将填充的地方的权值设置的非常小,这样在加权的时候就会是填充的单词起到作用了
mask=tf.tile(mask,[1,tf.shape(QK)[1],1])#现在有了mask矩阵,下面开始将pading的单词的权重是设置的很小
padding_val=-2**32
QK=tf.where(mask,QK,tf.ones_like(QK)*padding_val)/tf.sqrt(tf.cast(self.para.hidden_dim,dtype=tf.float32))#采用的是缩放的点积
QK=self.softmax(QK)
Z=tf.matmul(QK,V)
else:
#不使用mask操作,还要有个缩放因子
QK=self.softmax(QK/tf.sqrt(self.para.hidden_dim))
#softmax之后就是加权求和输出z,很简单的矩阵乘法
Z=tf.matmul(QK,V)#使用这个矩阵乘法之后,默认在最后两个维度进行做乘法,也就是加权求和了
return Z
#加性注意力机制,该注意力机制好像是首先应用在seq2seq模型里面的,需要使用到编码器和解码器这两个部分的向量,
#但是对于这个LSTM+attention+crf实现命名实体识别模型的,由于没有解码器,因为隐向量只有一部分,如何做到
#活学活用是一件挺难的事。 ,下面是我根据我自己的理解写的,可能有不对的地方。
class additive_attention_layer(layers.Layer):
'''
至于为什么要这么去实现加性attention机制,我也不清楚。因为深度学习本来就是解释性特别的低
我只是在想如何计算一组权重,这种权重可以根据不同的计算方式得到,但是到底是好是坏谁也不知道,
因而我就根据网上的一些文章,自己去尝试实现一下。对不对估计还需要大佬的指教,模型的好坏只能靠实验结果确定
'''
def __init__(self,para):
super().__init__(self)
self.para=para
self.dense=layers.Dense(self.para.attention_size,trainable=True,activation='tanh')#这个是需要一个激活函数的
self.dropout=layers.Dropout(rate=self.para.attenion_drop_rate)
self.softmax=layers.Softmax()
def build(self,input_shape):
#我想使用这个权值矩阵将经过全连接层作用之后的输出的大小[batch_size,max_len,attention_size]
#调正为[batch_size,maxlen,maxlen],就和自注意力机制层是一样的
self.attention_u=self.add_weight(name='atten_u',shape=(self.para.attention_size,self.para.max_sen_len),initializer = tf.random_uniform_initializer(), dtype = "float32", trainable = True)
super.build(input_shape)
def call(self,inputs,sen_len):
'''
Parameters
----------
inputs : tensor
循环神经网络的输出.
sen_len : tensor
每个batch里面句子的长度,用来实现mask操作.
Returns
-------
返回加权之后的隐向量.
'''
alpha=self.dense(inputs)
if self.para.is_mask:
alpha=tf.matmul(alpha,self.attention_u)
mask=tf.sequence_mask(sen_len,maxlen=self.para.max_sen_len)
mask=tf.expand_dims(mask,1)
mask=tf.tile(mask,[1,tf.shape(alpha)[1]],1)#[batch_size,maxlen,maxlen]
padding_val=-2**22
alpha=tf.where(mask,alpha,tf.ones_like(alpha)*padding_val)
alpha=self.softmax(alpha)
Z=tf.matmul(alpha,inputs)
else:
alpha=tf.matmul(alpha,self.attention_u)#将alpha的大小由[batch_size,max_len,attention_size]调整为大小为[batch_size,maxlen,maxlen]
alpha=self.softmax(alpha)
Z=tf.matmul(alpha,inputs)#将权值与隐向量相乘做为新的隐向量
return self.dropout(Z,training=self.para.is_training)
class Crf_layer(layers.Layer):
def __init__(self,para):
super().__init__(self)
self.para=para
self.dense=layers.Dense(self.para.label_nums,use_bias=False,trainable=True,kernel_initializer=tf.keras.initializers.GlorotNormal())
def call(self,inputs,targets,lens):
'''
inputs是经过attention层之后的输出,这里还要将输入的大小[batch_size,maxlen,attention_dim]调整为
[batch_size,maxlen,label_nums]
'''
out=self.dense(inputs)#调整大小为[batch_size,maxlen,nums_label]
self.log_likelihood,self.tran_paras=tfa.text.crf_log_likelihood(out, targets, lens)
self.batch_pred_sequence,self.batch_viterbi_score=tfa.text.crf_decode(out,self.tran_paras,lens)
self.loss=tf.reduce_sum(-self.log_likelihood)
return self.loss,self.batch_pred_sequence
# -*- coding: utf-8 -*-
"""
Created on Fri Oct 16 12:09:22 2020
@author: DELL
"""
import tensorflow as tf
import os
import numpy as np
from tensorflow.keras import layers
from tqdm import tqdm
import matplotlib.pyplot as plt
from Model_modules import RNN_layer,Self_Attention_Layer,Crf_layer
from data_utils import data_process,data_process_ds1
from Model_para import Hpara
class Model_NER(tf.keras.Model):
def __init__(self,para):
super().__init__(self)
self.para=para
self.embeddinglayer=layers.Embedding(input_dim=self.para.token_nums,output_dim=self.para.word2vector_dim,input_length=self.para.max_sen_len, name = "embeding")
self.rnn_layer=RNN_layer(self.para)
self.atte_layer=Self_Attention_Layer(self.para)
self.crf_layer=Crf_layer(self.para)
def call(self,traintext,label):
#计算一下长度
traintext=tf.convert_to_tensor(traintext)
label=tf.convert_to_tensor(label)
self.lens=tf.reduce_sum(tf.sign(traintext),axis=-1)
self.out=self.embeddinglayer(traintext)
self.out=self.rnn_layer(self.out)
self.out=self.atte_layer(self.out,self.lens)
self.loss,self.batch_pred_seq=self.crf_layer(self.out,label,self.lens)
return self.loss,self.batch_pred_seq
def batch_iter(x, y, batch_size = 16):
data_len = len(x)
num_batch = (data_len + batch_size - 1) // batch_size#获取的是
indices = np.random.permutation(np.arange(data_len))#随机打乱下标
x_shuff = x[indices]
y_shuff = y[indices]#打乱数据
for i in range(num_batch):#按照batchsize取数据
start_offset = i*batch_size #开始下标
end_offset = min(start_offset + 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_and_test(para):
model=Model_NER(para)
if not os.path.exists(para.savepath):
print('make model savepath----')
os.makedirs(para.savepath)
else:
print('load model weight----')
model.load_weights(os.path.join(para.savepath,'ckpt'))
optimizer=tf.keras.optimizers.RMSprop(lr=para.learning_rate,rho=0.9, epsilon=1e-06)
def train_step(x,y):
with tf.GradientTape() as tape:
loss,batch_pred_seq=model(x,y)
gradients=tape.gradient(loss,model.trainable_variables)
optimizer.apply_gradients(zip(gradients,model.trainable_variables))
return loss,batch_pred_seq
#加载数据集
train_arr,train_l,test_arr,test_l=data_process_ds1(para)
for e in tqdm(range(para.epochs)):
loss_epoch=0
for i,num_batch,x,y in tqdm(batch_iter(train_arr, train_l,para.batch_size)):
loss_step,pred_step=train_step(x,y)
loss_epoch+=loss_step
if (i+1) % 5==0:
print('\n第 %d epoch 的第%d步的损失是%f\n'%(e,i+1,loss_step))
print('\n第 %d 个epoch的平均损失是%f\n'%(e+1,loss_epoch/num_batch))
#保存模型
model.save_weights(os.path.join(para.savepath,'ckpt'))
#下面对模型进行测试
pred_loss=[]
for i,num_batch,test_x,test_y in tqdm(batch_iter(test_arr, test_l)):
loss,pred=model(test_x,test_y)
pred_loss.append(loss)
plt.plot(pred_loss)
plt.xlabel('steps')
plt.ylabel('loss')
if __name__=='__main__':
hp=Hpara()
parser = hp.parser
para = parser.parse_args(args=[])
train_and_test(para)
链接:https://pan.baidu.com/s/1cKMJnq4_CIoDJM6XMnTSPQ
提取码:a0l7
复制这段内容后打开百度网盘手机App,操作更方便哦