**输入两个句子判断,判断它们之间的关系。参考ESIM(可以只用LSTM,忽略Tree-LSTM),用双向的注意力机制实现**

数据集:The Stanford Natural Language Processing Group

任务一博客链接:https://blog.csdn.net/qq_51983316/article/details/129314052

任务二博客链接:https://blog.csdn.net/qq_51983316/article/details/129387225

参考论文:
1、Reasoning about Entailment with Neural Attention https://arxiv.org/pdf/1509.06664v1.pdf
2、Enhanced LSTM for Natural Language Inference https://arxiv.org/pdf/1609.06038v3.pdf

目录

一、数据集

二、知识点学习

(一)文本匹配

1、基本概念

2、任务定义

(二)BiLSTM 模型

1、LSTM

2、BiLSTM

(三)注意力机制

(四)ESIM 模型详解

1、Input Encoding

2、Local Inference Modeling

3、Inference Composition

4、Prediction

三、实验

(一)代码实现

1、main.py

2、feature_extraction.py

3、绘制对比图

(二)结果分析

一、数据集

训练集共有 550152 项,语言为英文,匹配关系有蕴含(Entailment)、矛盾(Contradiction)、中立/不冲突(Neutral)和未知(-)四种,具体分布情况如下(-出现的非常少):

Training pairs: 550152

Dev pairs: 10000

Test pairs: 10000

Total pairs: 570152

Train labels: {'entailment': 183416, 'neutral': 182764, '-': 785, 'contradiction': 183187}
Dev labels: {'entailment': 3329, 'neutral': 3235, '-': 158, 'contradiction': 3278}
Test labels: {'entailment': 3368, 'neutral': 3219, '-': 176, 'contradiction': 3237}

sentence1: A black race car starts up in front of a crowd of people.
sentence2: A man is driving down a lonely road.
output: 矛盾(C)

sentence1: A soccer game with multiple males playing.
sentence2: Some men are playing a sport.
output: 蕴含(E)

sentence1: A smiling costumed woman is holding an umbrella.
sentence2:A happy woman in a fairy costume holds an umbrella.
output: 中立(N)

二、知识点学习

(一)文本匹配

1、基本概念

文本匹配是NLP领域的一个重要的基础问题,顾名思义,文本匹配就是通过计算文本相似度判断两段文本之间的关系,如相似/相悖等。其中,信息检索、自然语言推理、问答匹配、机器翻译、对话系统等NLP任务都可以视为文本匹配问题。文本匹配可以抽象为给定一段文本作为查询(Query),从大量的文档(Documents)匹配出最佳的文档。

例如网页搜索可抽象为网页同用户搜索Query的一个相关性匹配问题,自动问答可抽象为候选答案与问题的满足度匹配问题,文本去重可以抽象为文本与文本的相似度匹配问题。

2、任务定义

传统的方案主要是基于统计学方法通过词汇重合度来计算两段文本的字面相似,通过字面相似度来衡量文本的匹配度不妥,存在较多的不足,因为同一语义的文本在形式上千变万化,两段文本可以表现为字面相似但词序不同而导致语义完全相反;例如:“我喜欢你”,“我不喜欢你”,这两句话表现的语义完全不一样,但是从字层面表现的结果,相似度较高。

因此引入深度神经网络

表示型模型主要是将两段文本转换成一个语义向量,然后计算向量之间的相似度,其更侧重对语义向量表示层的构建,它的优势是结构简单、解释性强,且易于实现,是深度学习出现之后应用最广泛的深度文本匹配方法。典型的网络结构有 DSSM、LSTM 和 ESIM。

交互型模型摒弃了表示型模型的先建模后匹配的思路,通过attention为代表的结构来对两段文本进行不同粒度的交互(词级、短语级等),然后将各个粒度的匹配结果通过一种结构来聚合起来,作为一个超级特征向量进而得到最终的匹配关系。假设全局的匹配度依赖于局部的匹配度,在输入层就进行词语间的匹配和语义建模,它的优势是可以很好的把握语义焦点,对上下文重要性合理建模,效果较优于表示型,后续被广泛使用。

(二)BiLSTM 模型

1、LSTM

LSTM(Long short-term memory)是一种特殊的RNN,解决了RNN长期记忆能力不足、梯度消失、梯度爆炸等问题。LSTM非常适合对时间数据序列进行处理,原因是在时间序列中的重要事件之间可能存在未知持续时间的滞后,而对于间隙长度的相对不敏感性是LSTM的一大优势。‎

LSTM单元由输入门、遗忘门、输出门和单元状态组成,该单元记住任意时间间隔内的值,三个门控制进出单元的信息流,LSTM模型结构如下图所示。

LSTM 与 RNN一样,也是通过内部状态的传递来发掘序列元素间的依赖关系。LSTM 为了解决RNN 梯度更新上的缺陷,引入了门控机制,LSTM的门控环节分为遗忘、输入、输出三部分并且引入了一个状态单元协调整个网络的运作

公共LSTM单元由输入门、遗忘门、输出门和单元状态组成,该单元记住任意时间间隔内的值,并且三个门控制进出单元的信息流。其中,输入门决定当前时刻网络的输入有多少保存到单元状态;遗忘门决定上一时刻的单元状态有多少保留到当前时刻;而输出门控制当前单元状态有多少需要输出到当前的输出值。LSTM神经元结构和工作机制如图:

对于LSTM神经元结构,x{_t} 为 t 时刻输入,h{_t}为 t 时刻隐藏层状态,C_t为 t 时刻单元内部状态,σ tanh 为激活函数。

2、BiLSTM

双向长短期记忆神经网络(Bidirectional Long Short Term Memory Network)是过去和未来隐藏层的状态都可以递归,进行反馈的神经网络,可以很好的发掘时间序列数据间隐藏的联系。BiLSTM 网络可以挖掘当前数据同过去及未来时刻数据的内在联系,提升模型预测精度和数据利用率。

相较于单向的 LSTM 网络,BiLSTM 具有正向和反向传播的双向循环结构。除此以外,BiLSTM 在 LSTM 数据从过去到未来单向流动的基础上增加了从未来到过去的数据流向,且用于过去的隐藏层和用于未来的隐藏层之间相互独立,所以 BiLSTM 可以更好的发掘数据的时序特征,使用BiLSTM模型并沿时间轴进行展开,具体结构如下图所示。

其中,x 为模型输入,h 为隐藏层状态,y 为输出。BiLSTM可以同时处理正反两个时间流向的模型,因此其具有两个方向的隐藏层。如图中所示正向传播的隐藏层与反向传播的隐藏层之间并没有发生交互,可以拆分开,当成两个独立且数据流向相反的网络。

假设 \vec{h_t} 为 t 时刻正向 LSTM 网络的隐藏层状态,计算公式如下所示。可以看作是单层的 LSTM 网络,由 t-1 时刻状态 \vec{h_{t-1}},计算时刻状态 \vec{h_t} 的过程,X_t 为 t 时刻的输入。

\vec{h_t}=LSTM\left(X_t,\ \vec{h_{t-1}}\right)

其中,\vec{h_t} 为 t 时刻正向 LSTM 网络的隐藏层状态,LSTM为LSTM单元,X_t 为 t 时刻的输入,\vec{h_{t-1}} 为  t-1 时刻正向 LSTM 网络的隐藏层状态。同样地,反向 LSTM 网络的隐藏层状态 \vec{h_t} 计算方式也和上述公式一致。

举例:

前向的LSTML依次输入“我”,“爱”,“你”得到三个向量{hL0,hL1,hL2}。

后向的LSTMR依次输入“你”,“爱”,“我”得到三个向量{hR0,hR1,hR2}。

最后将前向和后向的隐向量进行拼接得到{[hL0,hR2],[hL1,hR1],[hL2,hR0]},即{h0,h1,h2}。对于情感分类任务来说,我们采用的句子表示往往是[hL2,hR2],因为这其中包含了前向和后向的所有信息。

(三)注意力机制

注意力机制(Attention Mechanism)最早应用于计算机视觉领域。

顾名思义,该机制通过模拟人的大脑在某一时刻,对大量信息中的某一信息分配更多的注意力,在信息处理中对模型输入序列的接受呈现有选择性的特点。在编解码器框架内,通过在编码段加入Attention机制,对源数据序列进行数据加权变换,或者在解码端引入Attention机制,对目标数据进行加权变化,可以有效提高序列对序列的自然方式下的系统表现。

注意力一般分为两种:
(1)自上而下的有意识的注意力,称为聚焦式注意力(Focus Attention)。聚焦式注意力是指有预定目的、依赖任务的,主动有意识地聚焦于某一对象的注意力。
(2)自下而上的无意识的注意力,称为基于显著性的注意力(Saliency Based Attention)。由外界刺激驱动的注意,不需要主动干预,也和任务无关。如果一个对象的刺激信息不同于其周围信息,一种无意识的“赢者通吃”(Winner-Take-All)或者门控(Gating)机制就可以把注意力转向这个对象。不管这些注意力是有意还是无意,大部分的人脑活动都需要依赖注意力。

量化来说,Attention机制就是给定一组向量集合values,以及一个向量query,该机制根据该query计算values的加权求和,其实就是一个查询(query)到一系列键值(key-value)对的映射。

注意力机制的计算可以分为两步:一是在所有输入信息上计算注意力分布,二是根据注意力分布来计算输入信息的加权平均

我们从输出端,即解码器部分,倒过来一步一步分析Attention机制的原理与公式。

S_t=f(S_{t-1},\ Y_{t-1},C_t)

其中,S_t 是指解码器在 t 时刻的状态输出,S_{t-1} 是指解码器在 t-1 时刻的状态输出,Y_{t-1} 是 t-1 时刻的 label,f 是一个RNN。

C_t=\sum_{j=1}^{T_x}{a_{tj}h_j}

利用 a_{tj} 进行加权求和得到 C_t。其中,h_j 是指第 j 个输入在编码器里的输出,也称为隐层状态。而 {a_{tj} 是相应的权重注意力分布,其计算公式如下:

a_{tj}=\frac{exp(e_{tj})}{\sum_{k=1}^{T_x}{exp(e_{tk})}}

在此 a 指当前这一步解码器对齐第 j 个输入的程度

e_{tj}=g(S_{t-1},\ h_j)=V\cdot tanh(W\cdot h_{j}+U\cdot S_{t-1}+b)

在此 g 可以用一个小型的神经网络来逼近,它用来计算S_{t-1}h_j这两者的关系分数(打分函数),如果分数大则说明关注度较高,注意力分布就会更加集中在这个输入序列上。

综上,Attention机制总结来说就是当前一步输出S_t应该对齐哪一步输入,主要取决于前一步输出S_{t-1}和这一步输入的编码器结果h_j,机制原理如下图所示。

Attention机制具有鲜明的优点。该机制不仅可以灵活的捕捉全局和局部的联系,也通过并行计算减少模型训练时间。Attention机制每一步计算不依赖于上一步的计算结果,因此可以并行处理。除此以外,该机制的模型复杂度小,参数较少,模型效果也较好。

(四)ESIM 模型详解

原始论文:Enhanced LSTM for Natural Language Inference

论文解读:https://zhuanlan.zhihu.com/p/500151241

首先,需要明确自然语言推理(Natural Language Inference,NLI)的定义:主要用来判断两个句子给定前提(premise)和假设(hypothesis),判断语义上的关系,一般可以分为:Entailment(蕴含)、Contradiction(矛盾)、Neutral(中立)。在 NLP 中,判断蕴含或是矛盾的关系十分必要,例如信息检索、语义分析、常识推理等方面都会用到。评价标准简单有效,可以直接在NLI中专注于语义理解和语义表示,如此生成好的句子就可以直接迁移应用到其他的任务。

ESIM的模型框架如下:

作者在文中提到可以采用句法的Tree-LSTM处理,也可以用BiLSTM处理。在此只用LSTM,忽略Tree-LSTM,并用双向的注意力机制实现。

由上述框架可以看出,ESIM一共包含四部分:

  • Input Encoding(输入)
  • Local Inference Modeling(局部推理建模
  • Inference Composition(组合推理
  • Prediction(输出)

1、Input Encoding

首先使用BiLSTM来对输入的两个句子进行embedding嵌入:

编码公式如下:

经过BiLSTM的编码之后得到a_bar b_bar,此步主要是对这两个语句中的词语进行上下文表示。其中 \vec{a_i} 和 \vec{b_j} 都是BiLSTM对应时间步上隐藏层的输出构成新的Embedding。

代码实现

""" 定义PyTorch模型类Input_Encoding,将输入的文本序列进行编码 """
class Input_Encoding(nn.Module):def __init__(self, len_feature, len_hidden, len_words, longest, weight=None, layer=1, batch_first=True, drop_out=0.5):super(Input_Encoding, self).__init__()# 输入的特征维度self.len_feature = len_feature# LSTM 的隐藏层大小self.len_hidden = len_hidden# 词嵌入的单词数self.len_words = len_words#  LSTM 的层数self.layer = layer# 最长的输入句子的长度self.longest = longest# dropout 层,用于防止过拟合self.dropout = nn.Dropout(drop_out)# 如果 weight 是 None,则使用 xavier_normal_ 函数初始化一个形状为 (len_words, len_feature) 的张量 x,并使用这个张量作为词嵌入层的权重if weight is None:x = nn.init.xavier_normal_(torch.Tensor(len_words, len_feature))self.embedding = nn.Embedding(num_embeddings=len_words, embedding_dim=len_feature, _weight=x).cuda()# 否则,使用给定的权重 weight 初始化词嵌入层else:self.embedding = nn.Embedding(num_embeddings=len_words, embedding_dim=len_feature, _weight=weight).cuda()# 初始化双向 LSTMself.lstm = nn.LSTM(input_size=len_feature, hidden_size=len_hidden, num_layers=layer, batch_first=batch_first,bidirectional=True).cuda()# 定义前向传播函数,用于对输入的句子进行编码def forward(self, x):# 将输入的数据转换为 torch.LongTensor 类型并放到 GPU 上x = torch.LongTensor(x).cuda()# 使用词嵌入层对输入进行词嵌入x = self.embedding(x)# 使用 dropout 对词嵌入结果进行正则化x = self.dropout(x)# 使用 flatten_parameters() 函数将 LSTM 的参数展开self.lstm.flatten_parameters()# 将结果输入到 LSTM 中进行编码x, _ = self.lstm(x)return x

2、Local Inference Modeling

该层主要任务是进行差异性计算,即计算两个句子词与词之间的相似度,得到相似度矩阵:

对于表示向量,如果两个词之间联系越大,就意味着他们之间的距离和夹角就越少,比如(1,0)和(0,1)之间的联系,就没有(0.5,0.5)和(0.5,0.5)之间的联系大。

其次生成相似性加权后的向量:

这也是ESIM的精髓所在。简言之,比如 a 中有一个单词 "good",首先我分析这个词和另一句话中各个词之间的联系,计算得到的结果 e_{ij} 标准化后作为权重,用另一句话中的各个词向量按照权重去表示 "good",逐个分析对比后得到新的序列。以上过程称为本地推理模型。

得到encoding值与加权encoding值之后,下一步是分别对这两个值做差异性计算,作者认为这样的操作有助于模型效果的提升,论文中有两种计算方法,分别是对位相减与对位相乘,最后把encoding两个状态的值与相减、相乘的值拼接起来。

本质上来说就是对两个句子的表示做差和点积:

代码实现

""" 局部推理模块 """
"""基于注意力机制的编码器-解码器框架:注意力机制通常被用来计算输入序列中每个位置对于输出序列的重要程度,进而加强对于相关信息的关注和利用。
本代码中,a_bar和b_bar分别表示输入序列a和b的编码表示,通过计算矩阵e,可以得到a_bar和b_bar之间的交互关系。
接着,通过softmax函数将e中的数值归一化,得到a_bar和b_bar在交互关系下的注意力分布a_tilde和b_tilde,分别用来加权计算b_bar和a_bar的信息。
最后,将a_bar、a_tilde、b_bar、b_tilde之间的差异和乘积信息进行拼接,形成新的表示m_a和m_b。
进一步用于后续的分类或回归任务。"""
class Local_Inference_Modeling(nn.Module):def __init__(self):super(Local_Inference_Modeling, self).__init__()# 将输入值转换为概率分布self.softmax_1 = nn.Softmax(dim=1).cuda()self.softmax_2 = nn.Softmax(dim=2).cuda()# 这个模型类的前向函数实现了两个句子之间的局部推理模型def forward(self, a_bar, b_bar):# e 是注意力矩阵。matmul()函数计算两个输入张量a_bar和b_bar的矩阵乘积e = torch.matmul(a_bar, b_bar.transpose(1, 2)).cuda()# 将 e 矩阵的第二个维度进行 softmax 操作,得到一个概率分布矩阵a_tilde = self.softmax_2(e)# 将 a_tilde 与 b_bar 做矩阵乘法,得到一个新的矩阵a_tilde = a_tilde.bmm(b_bar)b_tilde = self.softmax_1(e)b_tilde = b_tilde.transpose(1, 2).bmm(a_bar)# 这四个矩阵在最后一个维度上进行拼接,得到一个新的矩阵m_a = torch.cat([a_bar, a_tilde, a_bar - a_tilde, a_bar * a_tilde], dim=-1)m_b = torch.cat([b_bar, b_tilde, b_bar - b_tilde, b_bar * b_tilde], dim=-1)return m_a, m_b

3、Inference Composition

由于ESIM还需要综合所有信息进行全局分析,因此通过组合推理模块把所有信息储存在一个序列中。在这一层中,把之前的值再一次送到了BiLSTM中,这里的BiLSTM的作用和之前的并不一样,这里主要是用于捕获局部推理信息 m_a 和 m_b 及其上下文,以便进行推理组合。

v_{a,t} = BiLSTM(F(m_{a,t}),t)

v_{b,t} = BiLSTM(F(m_{b,t}),t)

其中,F是一个单层神经网络(ReLU作为激活函数),主要用来减少模型的参数避免过拟合,另外,上面的 t 表示BiLSTM在 t 时刻的输出。 最后把BiLSTM得到的值进行池化操作,分别是最大池化与平均池化,并把池化之后的值再一次的拼接起来。

代码实现

""" 组合推理模块 (对蕴含假设和前提进行编码和组合) """
class Inference_Composition(nn.Module):# len_feature 特征向量的维度;len_hidden_m 在Local_Inference_Modeling中得到的组合向量的维度def __init__(self, len_feature, len_hidden_m, len_hidden, layer=1, batch_first=True, drop_out=0.5):# 调用父类构造函数以初始化该模块super(Inference_Composition, self).__init__()# 定义线性变换层,将Local_Inference_Modeling得到的组合向量降维为len_feature维self.linear = nn.Linear(len_hidden_m, len_feature).cuda()# 定义LSTM层,其中包含了两个方向的隐状态,并将降维后的组合向量作为输入;bidirectional=True表示为双向LSTMself.lstm = nn.LSTM(input_size=len_feature, hidden_size=len_hidden, num_layers=layer, batch_first=batch_first,bidirectional=True).cuda()self.dropout = nn.Dropout(drop_out).cuda()def forward(self, x):x = self.linear(x)x = self.dropout(x)# 将LSTM层的权重参数展开以提高效率self.lstm.flatten_parameters()# 将降维后的组合向量作为LSTM层的输入;输出为xx, _ = self.lstm(x)return x

4、Prediction

然后再将 v 与一个全连接层相连,其中全连接层使用了tanh的激活函数,得到的结果送到softmax层以便输出,使用多类别的交叉熵计算损失。

代码实现

""" 预测类 """
class Prediction(nn.Module):# len_v 输入的向量的长度;len_mid 表示中间层的大小def __init__(self, len_v, len_mid, type_num=4, drop_out=0.5):super(Prediction, self).__init__()# 定义 mlp 多层感知机# nn.Dropout(drop_out):dropout 层,防止过拟合。# nn.Linear(len_v, len_mid):全连接层,将输入的向量映射到中间层。# nn.Tanh():激活函数,使用双曲正切函数进行非线性变换。# nn.Linear(len_mid, type_num):全连接层,将中间层的特征映射到预测的类别数。self.mlp = nn.Sequential(nn.Dropout(drop_out), nn.Linear(len_v, len_mid), nn.Tanh(),nn.Linear(len_mid, type_num)).cuda()# 定义前向传播方法def forward(self, a, b):# 计算 m_a 在第二个维度上的平均值v_a_avg = a.sum(1)/a.shape[1]# 计算 m_a 在第二个维度上的最大值v_a_max = a.max(1)[0]# 计算 m_b 在第二个维度上的平均值v_b_avg = b.sum(1) / b.shape[1]# # 计算 m_b 在第二个维度上的最大值v_b_max = b.max(1)[0]# 将四个向量连接在一起形成一个新的向量out_put = torch.cat((v_a_avg, v_a_max,v_b_avg,v_b_max), dim=-1)# 将新的向量输入到多层感知机中进行预测,返回预测结果return self.mlp(out_put)

总之,ESIM模型整体架构如下:

ESIM算法总体思路比较清晰,一些过程也是在人为地构建特征。在模型实现过程中则需要考虑批次训练中的数据对应问题,以及可能出现的误计算,例如最大池化的计算。

代码实现

""" ESIM模型 """
"""在 ESIM 模型中,首先将输入的句子 a 和 b 通过 Input_Encoding 模块编码成相应的表示,
然后将编码后的 a 和 b 作为输入传给 Local_Inference_Modeling 模块进行局部推理,
接着将得到的结果传递给 Inference_Composition 模块进行推理融合,
最后通过 Prediction 模块预测两个句子是否具有某种关系"""
class ESIM(nn.Module):def __init__(self, len_feature, len_hidden, len_words, longest, type_num=4, weight=None, layer=1, batch_first=True,drop_out=0.5):super(ESIM, self).__init__()# 将词嵌入的长度和句子中最长的长度分别赋值给实例变量self.len_words和self.longestself.len_words = len_wordsself.longest = longest# 创建input_encoding对象,是Input_Encoding类的一个实例,Input_Encoding类是用来对输入进行编码的# layer表示ESIM模型的层数self.input_encoding = Input_Encoding(len_feature, len_hidden, len_words, longest, weight=weight, layer=layer,batch_first=batch_first, drop_out=drop_out)# 创建对象,用来对两个句子进行本地推理的,也就是计算两个句子中每个词语之间的关联度self.local_inference_modeling = Local_Inference_Modeling()# Inference Composition层中的输入是由四个部分拼接而成的,分别是a_bar, a_tilde, a_bar - a_tilde和 a_bar * a_tilde。# 每个部分的特征向量维度都是len_hidden,所以总共是4 * len_hidden,且在拼接之前需要将a_bar和b_bar的维度都扩展为4 * len_hidden。# 因此,输入到Inference Composition层中的向量维度是8 * len_hidden。self.inference_composition = Inference_Composition(len_feature, 8 * len_hidden, len_hidden, layer=layer,batch_first=batch_first, drop_out=drop_out)self.prediction = Prediction(8 * len_hidden, len_hidden, type_num=type_num, drop_out=drop_out)def forward(self, a, b):a_bar=self.input_encoding(a)b_bar=self.input_encoding(b)m_a, m_b = self.local_inference_modeling(a_bar, b_bar)v_a = self.inference_composition(m_a)v_b = self.inference_composition(m_b)out_put = self.prediction(v_a, v_b)return out_put

三、实验

参数设置:

样本个数:550152
训练集:测试集 : 8:2
模型:ESIM
词嵌入初始化:随机初始化;GloVe预训练模型初始化

random_seed:2023
学习率:0.001

len_feature(输入的特征维度):50

len_hidden(LSTM的隐藏层大小):50

iter_times:50

batch_size:1000

运行环境:

python:3.7

pytorch:1.7.0(gpu)

cuda版本:10.1

(一)代码实现

1、main.py

import random
from feature_extraction import Random_embedding, Glove_embedding, get_batch
from comparison_plot import NN_plot, NN_embdding
from Neural_Network import ESIM# 读取数据
with open('data/snli_1.0/snli_1.0_train.txt', 'r') as f:temp = f.readlines()# 读取预训练词向量模型glove
with open('data/glove.6B.50d.txt', 'rb') as f:lines = f.readlines()# 将GloVe模型训练得到的词向量存储到字典中
trained_dict = dict()
n = len(lines)
for i in range(n):line = lines[i].split()trained_dict[line[0].decode("utf-8").upper()] = [float(line[j]) for j in range(1, 51)]# 初始化参数设置
data = temp[1:]
learning_rate = 0.001
len_feature = 50
len_hidden = 50
iter_times = 50
batch_size = 1000# random embedding
random.seed(2023)
random_embedding = Random_embedding(data=data)
random_embedding.get_words()
random_embedding.get_id()# trained embedding : glove
random.seed(2023)
glove_embedding = Glove_embedding(data=data, trained_dict=trained_dict)
glove_embedding.get_words()
glove_embedding.get_id()# 绘图比较结果
NN_plot(random_embedding, glove_embedding, len_feature, len_hidden, learning_rate, batch_size, iter_times)

2、feature_extraction.py

与任务二的方法和代码一致,分别使用随机初始化和Glove预训练模型初始化进行特征提取

import random
import re
import torch
from torch.utils.data import Dataset, DataLoader
from torch.nn.utils.rnn import pad_sequence""" 训练集和测试集的划分 """
def data_split(data, test_rate=0.2):# 创建两个空列表train和test,分别用来存放训练集和测试集数据train = list()test = list()# 计数器i记录添加的数据量i = 0# 使用for循环遍历输入的数据集data中的每一条数据for num in data:i += 1# 使用random.random()函数生成0-1之间的随机数# 如果随机数大于0.2,则将该条数据添加到训练集train中,否则添加到测试集test中。if random.random() > test_rate:train.append(num)else:test.append(num)# 此时返回分割好的测试集和训练集return train, test""" 定义Random_embedding的类 """
class Random_embedding():def __init__(self, data, test_rate=0.2):self.dict_words = dict()# 将输入的数据集按照制表符(\t)进行分割_data = [item.split('\t') for item in data]# 列表中第6个、第7个元素分别是 sentence1 和 sentence2,第1个元素是label标签值# 将这些元素重新组合成一个新的二维列表,并将其赋值给实例变量self.dataself.data = [[item[5], item[6], item[0]] for item in _data]# 根据第一个句子的长度对数据集进行排序,以便后续的处理self.data.sort(key=lambda x: len(x[0].split()))self.len_words = 0self.train, self.test = data_split(self.data, test_rate=test_rate)# 定义字典type_dict,用于将关系类型转化为数字编码self.type_dict = {'-': 0, 'contradiction': 1, 'entailment': 2, 'neutral': 3}# 将训练集中的每个子列表的第3个元素转化为数字编码,并存储在实例变量 self.train_y 中self.train_y = [self.type_dict[term[2]] for term in self.train]self.test_y = [self.type_dict[term[2]] for term in self.test]# 初始化训练集和测试集中两个句子的矩阵,分别表示第一个句子和第二个句子self.train_s1_matrix = list()self.test_s1_matrix = list()self.train_s2_matrix = list()self.test_s2_matrix = list()self.longest = 0# 定义 get_words 的方法,用于将数据集中的单词存储到词典 dict_words 中def get_words(self):# 定义正则表达式模式,用于匹配单词# 该模式包含大写字母、小写字母、竖线和单引号,可以匹配英文字母和撇号pattern = '[A-Za-z|\']+'for term in self.data:# 这行开始遍历term中的每个字符串,将其转换为大写字母并使用正则表达式模式pattern匹配其中的单词# 对于每个匹配到的单词,如果它不在单词字典self.dict_words中,就将它添加到字典中,并为它分配一个新的编号# 该函数利用字典的特性,实现了单词计数和编号分配的功能for i in range(2):s = term[i]s = s.upper()words = re.findall(pattern, s)for word in words:if word not in self.dict_words:self.dict_words[word] = len(self.dict_words)+1self.len_words = len(self.dict_words)def get_id(self):pattern = '[A-Za-z|\']+'for term in self.train:s = term[0]s = s.upper()words = re.findall(pattern, s)# 对于每个term,将其两个句子分别处理成编号列表itemitem = [self.dict_words[word] for word in words]# 将item的长度与当前已处理的最长句子长度self.longest比较,更新self.longest的值self.longest = max(self.longest, len(item))# 将item添加到对应的train_s1_matrix和train_s2_matrix中self.train_s1_matrix.append(item)s = term[1]s = s.upper()words = re.findall(pattern, s)item = [self.dict_words[word] for word in words]self.longest = max(self.longest, len(item))self.train_s2_matrix.append(item)for term in self.test:s = term[0]s = s.upper()words = re.findall(pattern, s)item = [self.dict_words[word] for word in words]self.longest = max(self.longest, len(item))self.test_s1_matrix.append(item)s = term[1]s = s.upper()words = re.findall(pattern, s)item = [self.dict_words[word] for word in words]self.longest = max(self.longest, len(item))self.test_s2_matrix.append(item)# 表示单词字典中的单词数量(包括一个额外的未使用的编号0)self.len_words += 1""" 定义Glove_embedding的类 """
class Glove_embedding():def __init__(self, data, trained_dict, test_rate=0.2):self.dict_words = dict()_data = [item.split('\t') for item in data]self.data = [[item[5], item[6], item[0]] for item in _data]self.data.sort(key=lambda x: len(x[0].split()))# 已训练好的词向量字典self.trained_dict = trained_dictself.len_words = 0self.train, self.test = data_split(self.data, test_rate=test_rate)# 定义关系类型的字典 self.type_dict,其中键为关系类型,值为相应的数字标识self.type_dict = {'-': 0, 'contradiction': 1, 'entailment': 2, 'neutral': 3}# 将训练集 self.train 中每个元素的第3个元素(即关系类型)转换为数字标识,并赋值给 self.train_yself.train_y = [self.type_dict[term[2]] for term in self.train]self.test_y = [self.type_dict[term[2]] for term in self.test]self.train_s1_matrix = list()self.test_s1_matrix = list()self.train_s2_matrix = list()self.test_s2_matrix = list()self.longest = 0self.embedding = list()  # 词向量矩阵def get_words(self):# 首先在嵌入矩阵 self.embedding 中添加一个全零向量,这个向量将会用作填充self.embedding.append([0]*50)pattern = '[A-Za-z|\']+'for term in self.data:for i in range(2):s = term[i]s = s.upper()words = re.findall(pattern, s)for word in words:  # Process every wordif word not in self.dict_words:self.dict_words[word] = len(self.dict_words)# 如果它在预训练的词向量字典 self.trained_dict 中,则将其对应的词向量添加到嵌入矩阵 self.embeddingif word in self.trained_dict:self.embedding.append(self.trained_dict[word])else:# 否则将一个全零的向量添加到该矩阵中,将嵌入矩阵中的所有向量长度都填充到50(因为在预训练的词向量字典中,每个词的向量长度都是 50)self.embedding.append([0] * 50)self.len_words = len(self.dict_words)def get_id(self):pattern = '[A-Za-z|\']+'# 对于训练集中的每个元素(句子对),将句子中的单词转换为对应的 IDfor term in self.train:# sentence1s = term[0]s = s.upper()words = re.findall(pattern, s)item = [self.dict_words[word] for word in words]# 记录最长的 ID 序列长度self.longest = max(self.longest, len(item))# train_s1/s2_matrix 表示句子1和句子2的 ID 序列self.train_s1_matrix.append(item)# sentence2s = term[1]s = s.upper()words = re.findall(pattern, s)item = [self.dict_words[word] for word in words]self.longest = max(self.longest, len(item))self.train_s2_matrix.append(item)# 对于测试集中的每个元素(句子对),将句子中的单词转换为对应的 IDfor term in self.test:s = term[0]s = s.upper()words = re.findall(pattern, s)item = [self.dict_words[word] for word in words]self.longest = max(self.longest, len(item))self.test_s1_matrix.append(item)s = term[1]s = s.upper()words = re.findall(pattern, s)item = [self.dict_words[word] for word in words]self.longest = max(self.longest, len(item))self.test_s2_matrix.append(item)# 将字典中的单词数加1,以便添加一个用于填充句子的特殊符号self.len_words += 1""" 自定义数据集,用于文本分类任务 """
class ClsDataset(Dataset):  # 定义 ClsDataset 的类,继承了 Dataset 类def __init__(self, sentence1, sentence2, relation):self.sentence1 = sentence1self.sentence2 = sentence2self.relation = relation  # 标签# 用于获取数据集中的一个样本,item 表示样本的索引,函数返回索引为 item 的句子和关系def __getitem__(self, item):return self.sentence1[item], self.sentence2[item], self.relation[item]def __len__(self):# 返回数据集中样本的数量return len(self.relation)""" 自定义batch数据的输出形式 """
# 函数 collate_fn是 PyTorch 中 DataLoader 类的一个参数,用于在迭代数据时组合数据样本
# 将一个 batch 中的数据样本按照句子长度进行填充,以便构造成一个张量
def collate_fn(batch_data):sents1, sents2, labels = zip(*batch_data)# 转换为张量sentences1 = [torch.LongTensor(sent) for sent in sents1]padded_sents1 = pad_sequence(sentences1, batch_first=True, padding_value=0)sentences2 = [torch.LongTensor(sent) for sent in sents2]padded_sents2 = pad_sequence(sentences2, batch_first=True, padding_value=0)return torch.LongTensor(padded_sents1), torch.LongTensor(padded_sents2),  torch.LongTensor(labels)# 使用自定义数据集,通过 dataloader 可以实现对整个数据集的批量迭代
def get_batch(x1, x2, y, batch_size):dataset = ClsDataset(x1, x2, y)dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False, drop_last=True, collate_fn=collate_fn)return dataloader

3、绘制对比图

import matplotlib.pyplot
import torch
import torch.nn.functional as F
from feature_extraction import get_batch
from torch import optim
from Neural_Network import ESIM
import random
import numpydef NN_embdding(model, train, test, learning_rate, iter_times):# Adam优化器初始化优化器对象optimizer = optim.Adam(model.parameters(), lr=learning_rate)# 定义损失函数为交叉熵损失函数loss_fun = F.cross_entropytrain_loss_record = list()test_loss_record = list()train_record = list()test_record = list()for iteration in range(iter_times):# 释放缓存torch.cuda.empty_cache()model.train()for i, batch in enumerate(train):torch.cuda.empty_cache()x1, x2, y = batchpred = model(x1, x2).cuda()# 清空优化器的梯度缓存optimizer.zero_grad()y = y.cuda()loss = loss_fun(pred, y).cuda()# 计算损失对模型参数的梯度loss.backward()# 更新模型参数optimizer.step()with torch.no_grad():model.eval()train_acc = list()test_acc = list()train_loss = 0test_loss = 0for i, batch in enumerate(train):torch.cuda.empty_cache()x1, x2, y = batchy=y.cuda()pred = model(x1, x2).cuda()loss = loss_fun(pred, y).cuda()train_loss += loss.item()_, y_pre = torch.max(pred, -1)acc = torch.mean((torch.tensor(y_pre == y, dtype=torch.float)))train_acc.append(acc)for i, batch in enumerate(test):torch.cuda.empty_cache()x1, x2, y = batchy=y.cuda()pred = model(x1, x2).cuda()loss = loss_fun(pred, y).cuda()test_loss += loss.item()_, y_pre = torch.max(pred, -1)acc = torch.mean((torch.tensor(y_pre == y, dtype=torch.float)))test_acc.append(acc)trains_acc = sum(train_acc) / len(train_acc)tests_acc = sum(test_acc) / len(test_acc)train_loss_record.append(train_loss / len(train_acc))test_loss_record.append(test_loss/ len(test_acc))train_record.append(trains_acc.cpu())test_record.append(tests_acc.cpu())print("---------- 迭代轮次", iteration + 1, "----------")print("Train loss:", train_loss / len(train_acc))print("Test loss:", test_loss / len(test_acc))print("Train accuracy:", trains_acc)print("Test accuracy:", tests_acc)return train_loss_record, test_loss_record, train_record, test_recorddef NN_plot(random_embedding, glove_embedding, len_feature, len_hidden, learning_rate, batch_size, iter_times):train_random = get_batch(random_embedding.train_s1_matrix, random_embedding.train_s2_matrix,random_embedding.train_y, batch_size)test_random = get_batch(random_embedding.test_s1_matrix, random_embedding.test_s2_matrix,random_embedding.test_y, batch_size)train_glove = get_batch(glove_embedding.train_s1_matrix, glove_embedding.train_s2_matrix,glove_embedding.train_y, batch_size)test_glove = get_batch(glove_embedding.test_s1_matrix, glove_embedding.test_s2_matrix,glove_embedding.test_y, batch_size)random.seed(2023)numpy.random.seed(2023)torch.cuda.manual_seed(2023)torch.manual_seed(2023)random_model = ESIM(len_feature, len_hidden, random_embedding.len_words, longest=random_embedding.longest)random.seed(2023)numpy.random.seed(2023)torch.cuda.manual_seed(2023)torch.manual_seed(2023)glove_model = ESIM(len_feature, len_hidden, glove_embedding.len_words, longest=glove_embedding.longest,weight=torch.tensor(glove_embedding.embedding, dtype=torch.float))random.seed(2023)numpy.random.seed(2023)torch.cuda.manual_seed(2023)torch.manual_seed(2023)trl_ran, tsl_ran, tra_ran, tea_ran = NN_embdding(random_model, train_random, test_random, learning_rate,iter_times)random.seed(2023)numpy.random.seed(2023)torch.cuda.manual_seed(2023)torch.manual_seed(2023)trl_glo, tsl_glo, tra_glo, tea_glo = NN_embdding(glove_model, train_glove, test_glove, learning_rate,iter_times)x = list(range(1, iter_times + 1))matplotlib.pyplot.subplot(2, 2, 1)matplotlib.pyplot.plot(x, trl_ran, 'r--', label='random')matplotlib.pyplot.plot(x, trl_glo, 'b--', label='glove')matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Train Loss")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Loss")matplotlib.pyplot.subplot(2, 2, 2)matplotlib.pyplot.plot(x, tsl_ran, 'r--', label='random')matplotlib.pyplot.plot(x, tsl_glo, 'b--', label='glove')matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Test Loss")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Loss")matplotlib.pyplot.subplot(2, 2, 3)matplotlib.pyplot.plot(x, tra_ran, 'r--', label='random')matplotlib.pyplot.plot(x, tra_glo, 'b--', label='glove')matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Train Accuracy")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Accuracy")matplotlib.pyplot.ylim(0, 1)matplotlib.pyplot.subplot(2, 2, 4)matplotlib.pyplot.plot(x, tea_ran, 'r--', label='random')matplotlib.pyplot.plot(x, tea_glo, 'b--', label='glove')matplotlib.pyplot.legend(fontsize=10)matplotlib.pyplot.title("Test Accuracy")matplotlib.pyplot.xlabel("Iterations")matplotlib.pyplot.ylabel("Accuracy")matplotlib.pyplot.ylim(0, 1)matplotlib.pyplot.tight_layout()fig = matplotlib.pyplot.gcf()fig.set_size_inches(8, 8, forward=True)matplotlib.pyplot.savefig('result/result.jpg')matplotlib.pyplot.show()

(二)结果分析

整体看来,无论是训练集还是测试集,随机初始化的效果均比glove预训练模型初始化的效果好

随机初始化结果:训练集能达到0.8698

glove预训练模型初始化结果:训练集达到0.8260

至于最后的结果随机初始化要优于预训练模型初始化的原因,可能有以下几点:

1、ESIM模型中采用了许多特殊的结构,如BiLSTM、注意力机制等。这些结构可以更好地捕捉语义信息,因此在某些情况下,使用随机初始化可以更好地适应模型结构。

2、随机初始化在某些情况下可能比预训练模型更好,这是因为预训练模型可能会过拟合到特定领域或任务的数据。例如,如果使用一个基于大规模通用语料库训练的预训练模型来解决特定领域的任务,该模型可能会过度拟合通用数据,并不一定能够很好地适应特定领域的数据。此时,使用随机初始化可以避免这种情况,因为它不会受到先前训练数据的影响,更容易适应特定任务的数据。但是在某些情况下,使用预训练模型可以获得更好的性能,这取决于预训练模型的质量和特定任务的数据。

3、ESIM模型可能会更好地适应特定的任务和数据集。预训练模型通常是在大规模的通用语料库上进行训练的,而ESIM模型是在特定的任务和数据集上进行训练的。

上述也只是设想,还请各位大佬批评指正!

总结:

由于是初学者,学习过程中参考了很多大佬的资料和代码,均附上参考链接:

1、https://blog.csdn.net/qq_42365109/article/details/115704688

2、邱锡鹏——《神经网络与深度学习》

3、https://blog.csdn.net/weixin_42691585/article/details/106665861

4、https://blog.csdn.net/Raki_J/article/details/122075646

5、https://blog.csdn.net/David_B/article/details/118703883

6、https://blog.csdn.net/Mr_Meng__NLP/article/details/122120520

7、【NLP】文本匹配——ESIM算法实现 - 知乎 (zhihu.com)

8、语义相似度匹配(二)—— ESIM模型_相似度匹配模型_微知girl的博客-CSDN博客

9、https://blog.csdn.net/lq_fly_pig/article/details/123956552

10、https://blog.csdn.net/qq_43586043/article/details/114810767

11、双向长短期记忆网络(BiLSTM)详解_敷衍zgf的博客-CSDN博客

以上就是NLP-Beginner的任务三,欢迎各位前辈批评指正!

NLP-Beginner任务三学习笔记:基于注意力机制的文本匹配相关推荐

  1. 【动手深度学习-笔记】注意力机制(四)自注意力、交叉注意力和位置编码

    文章目录 自注意力(Self-Attention) 例子 Self-Attention vs Convolution Self-Attention vs RNN 交叉注意力(Cross Attenti ...

  2. 【动手深度学习-笔记】注意力机制(一)注意力机制框架

    生物学中的注意力提示 非自主性提示: 在没有主观意识的干预下,眼睛会不自觉地注意到环境中比较突出和显眼的物体. 比如我们自然会注意到一堆黑球中的一个白球,马路上最酷的跑车等. 自主性提示: 在主观意识 ...

  3. NLP学习笔记(五) 注意力机制

    大家好,我是半虹,这篇文章来讲注意力机制 (Attention Mechanism) 在序列到序列模型中的应用 在上一篇文章中,我们介绍了序列到序列模型,其工作流程可以概括为以下两个步骤 首先,用编码 ...

  4. 【深度学习】基于注意力机制的Transformer处理医疗影像

    文章目录 1 前言 2 Self-Attention 3 Multi-Head Attention 4 MedT:用于医学图像分割的Transformer 5 基于Transformer的端到端视频实 ...

  5. 【文献阅读笔记】之基于注意力机制的深度学习路面裂缝检测

    中文,计算机辅助设计与图形学学报,第 32 卷 第 8 期,2020 年 8 月. DOI: 10.3724/SP.J.1089.2020.18059 摘要: 为实现自动准确地检测路面裂缝, 提升路面 ...

  6. 【NLP】四万字全面详解 | 深度学习中的注意力机制(四,完结篇)

    作者 | 蘑菇先生 知乎 | 蘑菇先生学习记 深度学习Attention小综述系列: 四万字全面详解 | 深度学习中的注意力机制(一) 四万字全面详解 | 深度学习中的注意力机制(二) 四万字全面详解 ...

  7. 【NLP】四万字全面详解 | 深度学习中的注意力机制(三)

    NewBeeNLP原创出品 公众号专栏作者@蘑菇先生 知乎 | 蘑菇先生学习记 深度学习Attenion小综述系列: 四万字全面详解 | 深度学习中的注意力机制(一) 四万字全面详解 | 深度学习中的 ...

  8. 【NLP】四万字全面详解 | 深度学习中的注意力机制(二)

    NewBeeNLP原创出品 公众号专栏作者@蘑菇先生 知乎 | 蘑菇先生学习记  前情提要:四万字全面详解 | 深度学习中的注意力机制(一) 目前深度学习中热点之一就是注意力机制(Attention ...

  9. 关于ATIS以及基于注意力机制的递归神经网络模型 的学习记录

    关于ATIS以及基于注意力机制的递归神经网络模型 的学习记录 此为本人学习的类笔记,主要内容为借助Google翻译机译的论文WHAT IS LEFT TO BE UNDERSTOOD IN ATIS? ...

最新文章

  1. 智办事:高效远程办公指南
  2. 独家 | 一文带你熟悉贝叶斯统计
  3. 各种加密算法用法及作用
  4. 二级c语言需要记库函数不,【2017年必备】计算机等级二级C语言上机考试题库(熟记必过,不看后悔).doc...
  5. mysql数据库安全配置规范_MySQL数据库安全配置
  6. 怎么把jdk和jRE的Javadoc文档整合到MyEclipse
  7. 【Visual c++ Build Tools】下载
  8. Android studio 快速“Gradle的依赖缓存可能损坏”问题
  9. Echarts16 ---散点图-趋势图
  10. 522还不知道怎么表白吗?——经典设计模式之【观察者模式】
  11. 手把手教你:CSS+JS 打造一个有个性的滚动条
  12. Linux ssh免密登录
  13. 如何使用git 生成patch 和打入patch
  14. 《Focal Loss GHM Loss Dice Los》论文笔记
  15. hdu 6656 2019杭电多校第7场 期望题
  16. 熱銷商品查詢php,ecshop首页调用指定商品分类下推荐热销最新商品
  17. 计算机应用研究、计算机工程与应用、计算机科学与探索投稿经验
  18. gephi软件_类似gephi的软件
  19. 解决交通仿真软件(SUMO)中找不到“select edge”选项的问题
  20. 美团秋招笔试——算法岗

热门文章

  1. movie_recommendation_spark1
  2. 假设检验(显著性检验)
  3. Thread优先级之优先级别
  4. 嵌入式linux实验截图,嵌入式linux实验二.pdf
  5. linux CST与EDT时区互转
  6. K_均值聚类算法(算法设计与C代码实现)
  7. AudioKit 教程:入门
  8. AWS免费服务器申请
  9. 风险预测模型评价第二弹:NRI的R语言计算
  10. 如何运行jnlp文件