TextRNN_Att
TextRNN-Att简介
TextRNN前面已经介绍过了,主体结构就是一个双向/单向的LSTM层,由于LSTM获得每个时间点的输出信息之间的“影响程度”都是一样的,而在关系分类中,为了能够突出部分输出结果对分类的重要性,引入加权的思想。而本篇模型在LSTM层之后引入了attention层,其实就是对lstm每刻的隐层进行加权平均。
模型结构:
-
输入层:输入是一个一个的句子,通过对它进行划分batch,sentence,然后进行编码
-
词嵌入层:将文本中的离散词汇表示(如单词或者字符)转换为连续的实值向量表示,也称为词嵌入(Word Embedding)。这些实值向量具有语义信息,能够捕捉词汇之间的语义关系,从而提供更丰富的特征表示。
-
LSTM层:双向LSTM是RNN的一种改进,其主要包括前后向传播,每个时间点包含一个LSTM单元用来选择性的记忆、遗忘和输出信息。模型的输出包括前后向两个结果,通过拼接作为最终的Bi-LSTM输出。公式如下:
-
注意力层:对lstm每刻的隐层进行加权平均,将词级别的特征合并到句子级别的特征。
M = tanh ( H ) M=\tanh \left(H \right) M=tanh(H)
α = s o f t max ( W T M ) \alpha =soft\max \left(W^TM \right) α=softmax(WTM)
r = H α T r=H\alpha ^T r=HαT
- 输出层:将句子层级的特征用于关系分类。
pytorch代码实现:
- 模型输入: [batch_size, seq_len]
- 经过embedding层:加载预训练词向量或者随机初始化, 词向量维度为embed_size: [batch_size, seq_len, embed_size]
- 双向LSTM:隐层大小为hidden_size,得到所有时刻的隐层状态(前向隐层和后向隐层拼接) [batch_size, seq_len, hidden_size * 2]
- 初始化一个可学习的权重矩阵w=[hidden_size * 2, 1]
- 对LSTM的输出进行非线性激活后与w进行矩阵相乘,将其映射为一个标量值。这个标量值可以看作是对 M 的加权求和,用于表示每个位置的重要程度或注意力权重。由[batch_size, seq_len,word_embedding]变为[batch_size, seq_len],即表示了一个句子当中每个词在这个句子当中的重要程度。接着,通过 Softmax 函数对 M 与 self.w 的点积进行归一化操作。这一步是为了将各个位置的权重转换为概率值,使得它们都落在 [0, 1] 的范围内,并且所有权重的总和为 1。这样可以确保每个位置的注意力权重都在合理的范围内,并且相对于其他位置的权重是有意义的。最后是维度扩展,这样做是为了方便后续的加权求和操作,因为 alpha 中的每个权重需要与 H 中对应位置的隐藏状态进行加权求和,所以它们的维度需要匹配。
- 将LSTM的每一时刻的隐层状态乘对应的分值后求和,得到加权平均后的终极隐层值[batch_size, hidden_size * 2]
- 对终极隐层值进行非线性激活后送入两个连续的全连接层[batch_size, num_class]
- 预测:softmax归一化,将num_class个数中最大的数对应的类作为最终预测[batch_size, 1]
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class Config(object):
"""配置参数"""
def __init__(self):
self.model_name = 'TextRNN_Att'
self.dropout = 0.5 # 随机失活
self.require_improvement = 1000 # 若超过1000batch效果还没提升,则提前结束训练
self.num_classes = 10 # 类别数
self.n_vocab = 10000 # 词表大小,在运行时赋值
self.num_epochs = 10 # epoch数
self.batch_size = 128 # mini-batch大小
self.pad_size = 32 # 每句话处理成的长度(短填长切)
self.learning_rate = 1e-3 # 学习率
self.embed = 300 # 字向量维度, 若使用了预训练词向量,则维度统一
self.hidden_size = 128 # lstm隐藏层
self.num_layers = 2 # lstm层数
self.hidden_size2 = 64
class Model(nn.Module):
def __init__(self, config):
super(Model, self).__init__()
self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)
self.lstm = nn.LSTM(config.embed, config.hidden_size, config.num_layers,
bidirectional=True, batch_first=True, dropout=config.dropout)
self.tanh1 = nn.Tanh()
self.w = nn.Parameter(torch.zeros(config.hidden_size * 2))
self.tanh2 = nn.Tanh()
self.fc1 = nn.Linear(config.hidden_size * 2, config.hidden_size2)
self.fc = nn.Linear(config.hidden_size2, config.num_classes)
def forward(self, x):
x, _ = x
# 词嵌入层
emb = self.embedding(x) # [batch_size, seq_len, embeding]=[128, 32, 300]
# LSTM层
H, _ = self.lstm(emb) # [batch_size, seq_len, hidden_size * num_direction]=[128, 32, 256]
# 注意力层
M = self.tanh1(H) # [128, 32, 256]
alpha = F.softmax(torch.matmul(M, self.w), dim=1).unsqueeze(-1) # [128, 32, 1]
out = H * alpha # [128, 32, 256]
#输出层
out = torch.sum(out, 1) # [128, 256]
out = F.relu(out) # [128, 256]
out = self.fc1(out) # [128, 64]
out = self.fc(out) # [128, 10]
return out
config=Config()
model=Model(config)
print(model)
输出:
Model(
(embedding): Embedding(10000, 300, padding_idx=9999)
(lstm): LSTM(300, 128, num_layers=2, batch_first=True, dropout=0.5, bidirectional=True)
(tanh1): Tanh()
(tanh2): Tanh()
(fc1): Linear(in_features=256, out_features=64, bias=True)
(fc): Linear(in_features=64, out_features=10, bias=True)
)