赞
踩
按照输入和输出的结构进行分类:
按照RNN的内部构造进行分类:
学习了什么是RNN模型:
RNN的循环机制使模型隐层上一时间步产生的结果, 能够作为当下时间步输入的一部分(当下时间步的输入除了正常的输入外还包括上一步的隐层输出)对当下时间步的输出产生影响.
学习了RNN模型的作用:
以一个用户意图识别的例子对RNN的运行过程进行简单的分析:
学习了RNN模型的分类:
按照输入和输出的结构进行分类:
N vs N - RNN:
N vs 1 - RNN:
1 vs N - RNN:
N vs M - RNN:
按照RNN的内部构造进行分类:
Pytorch中传统RNN工具的使用:
# 导入工具包 >>> import torch >>> import torch.nn as nn >>> rnn = nn.RNN(5, 6, 1) # (embedding_dim, hidden_size, num_layer) >>> input = torch.randn(1, 3, 5) # [batch_size, seq_len, embedding_dim] >>> h0 = torch.randn(1, 3, 6) # [num_layer * bi, seq_len, hidden_size] >>> output, hn = rnn(input, h0) >>> output # [batch_size, seq_len, hidden_size] tensor([[[ 0.4282, -0.8475, -0.0685, -0.4601, -0.8357, 0.1252], [ 0.5758, -0.2823, 0.4822, -0.4485, -0.7362, 0.0084], [ 0.9224, -0.7479, -0.3682, -0.5662, -0.9637, 0.4938]]], grad_fn=<StackBackward>) >>> hn # [num_layer * bi, batch_size, hidden_size] tensor([[[ 0.4282, -0.8475, -0.0685, -0.4601, -0.8357, 0.1252], [ 0.5758, -0.2823, 0.4822, -0.4485, -0.7362, 0.0084], [ 0.9224, -0.7479, -0.3682, -0.5662, -0.9637, 0.4938]]], grad_fn=<StackBackward>)
import torch import torch.nn as nn batch_size = 2 num_layer = 1 seq_len = 3 hidden_size = 6 embedding_dim = 5 # [embedding_dim, hidden_size, num_layer] rnn = nn.RNN(embedding_dim, hidden_size, num_layer,batch_first = True ) # [batch_size, seq_len, embedding_dim] input1 = torch.randn(batch_size, seq_len, embedding_dim) # [num_layer, batch_size, hidden_size] h_0 = torch.randn(num_layer, batch_size, hidden_size) out, h_1 = rnn(input1, h_0)
# [batch_size, seq_len, hidden_size]
out.size()
torch.Size([2, 3, 6])
# [num_layer, batch_size, hidden_size]
h_1.size()
torch.Size([1, 2, 6])
梯度消失或爆炸的危害:
学习了传统RNN的结构并进行了分析;
根据结构分析得出了传统RNN的计算公式.
学习了激活函数tanh的作用:
学习了Pytorch中传统RNN工具的使用:
nn.RNN类初始化主要参数解释:
nn.RNN类实例化对象主要参数解释:
实现了nn.RNN的使用示例, 获得RNN的真实返回结果样式.
学习了传统RNN的优势:
学习了传统RNN的缺点:
学习了什么是梯度消失或爆炸:
梯度消失或爆炸的危害:
# 定义LSTM的参数含义: (input_size, hidden_size, num_layers) # 定义输入张量的参数含义: (sequence_length, batch_size, input_size) # 定义隐藏层初始张量和细胞初始状态张量的参数含义: # (num_layers * num_directions, batch_size, hidden_size) >>> import torch.nn as nn >>> import torch >>> rnn = nn.LSTM(5, 6, 2) >>> input = torch.randn(1, 3, 5) >>> h0 = torch.randn(2, 3, 6) >>> c0 = torch.randn(2, 3, 6) >>> output, (hn, cn) = rnn(input, (h0, c0)) >>> output tensor([[[ 0.0447, -0.0335, 0.1454, 0.0438, 0.0865, 0.0416], [ 0.0105, 0.1923, 0.5507, -0.1742, 0.1569, -0.0548], [-0.1186, 0.1835, -0.0022, -0.1388, -0.0877, -0.4007]]], grad_fn=<StackBackward>) >>> hn tensor([[[ 0.4647, -0.2364, 0.0645, -0.3996, -0.0500, -0.0152], [ 0.3852, 0.0704, 0.2103, -0.2524, 0.0243, 0.0477], [ 0.2571, 0.0608, 0.2322, 0.1815, -0.0513, -0.0291]], [[ 0.0447, -0.0335, 0.1454, 0.0438, 0.0865, 0.0416], [ 0.0105, 0.1923, 0.5507, -0.1742, 0.1569, -0.0548], [-0.1186, 0.1835, -0.0022, -0.1388, -0.0877, -0.4007]]], grad_fn=<StackBackward>) >>> cn tensor([[[ 0.8083, -0.5500, 0.1009, -0.5806, -0.0668, -0.1161], [ 0.7438, 0.0957, 0.5509, -0.7725, 0.0824, 0.0626], [ 0.3131, 0.0920, 0.8359, 0.9187, -0.4826, -0.0717]], [[ 0.1240, -0.0526, 0.3035, 0.1099, 0.5915, 0.0828], [ 0.0203, 0.8367, 0.9832, -0.4454, 0.3917, -0.1983], [-0.2976, 0.7764, -0.0074, -0.1965, -0.1343, -0.6683]]], grad_fn=<StackBackward>)
import torch
import torch.nn as nn
batch_size = 3
seq_len = 5
hidden_size = 6
num_layers = 2
embedding_dim = 30
lstm = nn.LSTM(
input_size = embedding_dim,
hidden_size = hidden_size,
num_layers = num_layers,
bidirectional = False,
batch_first = True
)
input = torch.randn([batch_size, seq_len, embedding_dim])
h_0 = torch.randn([num_layers, batch_size, hidden_size])
c_0 = torch.randn([num_layers, batch_size, hidden_size])
out, (h_t, c_t) = lstm(input, (h_0, c_0))
# [batch_size, seq_len, hidden_size]
out.size()
torch.Size([3, 5, 6])
# [num_layers, batch_size, hidden_size]
h_t.size()
torch.Size([2, 3, 6])
# [num_layers, batch_size, hidden_size]
c_t.size()
torch.Size([2, 3, 6])
bi_lstm = nn.LSTM(
input_size = embedding_dim,
hidden_size = hidden_size,
num_layers = num_layers,
bidirectional = True,
batch_first = True
)
input = torch.randn([batch_size, seq_len, embedding_dim])
h_0 = torch.randn([num_layers * 2, batch_size, hidden_size])
c_0 = torch.randn([num_layers * 2, batch_size, hidden_size])
output, (h_t, c_t) = bi_lstm(input, (h_0, c_0))
output.size() # [batch_size, seq_len, hidden_size * 2]
torch.Size([3, 5, 12])
h_t.size() # [num_layer * 2, batch_size, hidden_size]
torch.Size([4, 3, 6])
c_t.size() # [num_layer * 2, batch_size, hidden_size]
torch.Size([4, 3, 6])
遗忘门结构分析:
输入门结构分析:
细胞状态更新分析:
输出门结构分析:
什么是Bi-LSTM ?
Pytorch中LSTM工具的使用:
LSTM优势:
LSTM缺点:
>>> import torch >>> import torch.nn as nn >>> rnn = nn.GRU(5, 6, 2) >>> input = torch.randn(1, 3, 5) >>> h0 = torch.randn(2, 3, 6) >>> output, hn = rnn(input, h0) >>> output tensor([[[-0.2097, -2.2225, 0.6204, -0.1745, -0.1749, -0.0460], [-0.3820, 0.0465, -0.4798, 0.6837, -0.7894, 0.5173], [-0.0184, -0.2758, 1.2482, 0.5514, -0.9165, -0.6667]]], grad_fn=<StackBackward>) >>> hn tensor([[[ 0.6578, -0.4226, -0.2129, -0.3785, 0.5070, 0.4338], [-0.5072, 0.5948, 0.8083, 0.4618, 0.1629, -0.1591], [ 0.2430, -0.4981, 0.3846, -0.4252, 0.7191, 0.5420]], [[-0.2097, -2.2225, 0.6204, -0.1745, -0.1749, -0.0460], [-0.3820, 0.0465, -0.4798, 0.6837, -0.7894, 0.5173], [-0.0184, -0.2758, 1.2482, 0.5514, -0.9165, -0.6667]]], grad_fn=<StackBackward>)
GRU(Gated Recurrent Unit)也称门控循环单元结构, 它也是传统RNN的变体, 同LSTM一样能够有效捕捉长序列之间的语义关联, 缓解梯度消失或爆炸现象. 同时它的结构和计算要比LSTM更简单, 它的核心结构可以分为两个部分去解析:
内部结构分析:
Bi-GRU与Bi-LSTM的逻辑相同, 都是不改变其内部结构, 而是将模型应用两次且方向不同, 再将两次得到的LSTM结果进行拼接作为最终输出. 具体参见上小节中的Bi-LSTM.
Pytorch中GRU工具的使用:
GRU的优势:
GRU的缺点:
- 将Q,K进行纵轴拼接, 做一次线性变化, 再使用softmax处理获得结果最后与V做张量乘法.
- 将Q,K进行纵轴拼接, 做一次线性变化后再使用tanh函数激活, 然后再进行内部求和, 最后使用softmax处理获得结果再与V做张量乘法.
- 将Q与K的转置做点积运算, 然后除以一个缩放系数, 再使用softmax处理获得结果最后与V做张量乘法.
# 如果参数1形状是(b × n × m), 参数2形状是(b × m × p), 则输出为(b × n × p)
# 保证第一个维度相同,后面两个维度进行矩阵乘法运算
>>> input = torch.randn(10, 3, 4)
>>> mat2 = torch.randn(10, 4, 5)
>>> res = torch.bmm(input, mat2)
>>> res.size()
torch.Size([10, 3, 5])
import torch import torch.nn as nn import torch.nn.functional as F class Attn(nn.Module): def __init__(self, query_size, key_size, value_size1, value_size2, output_size): """初始化函数中的参数有5个, query_size代表query的最后一维大小 key_size代表key的最后一维大小, value_size1代表value的导数第二维大小, value = (1, value_size1, value_size2) value_size2代表value的倒数第一维大小, output_size输出的最后一维大小""" super(Attn, self).__init__() # 将以下参数传入类中 self.query_size = query_size self.key_size = key_size self.value_size1 = value_size1 self.value_size2 = value_size2 self.output_size = output_size # 初始化注意力机制实现第一步中需要的线性层. self.attn = nn.Linear(self.query_size + self.key_size, value_size1) # 初始化注意力机制实现第三步中需要的线性层. self.attn_combine = nn.Linear(self.query_size + value_size2, output_size) def forward(self, Q, K, V): """forward函数的输入参数有三个, 分别是Q, K, V, 根据模型训练常识, 输入给Attion机制的 张量一般情况都是三维张量, 因此这里也假设Q, K, V都是三维张量""" # 第一步, 按照计算规则进行计算, # 我们采用常见的第一种计算规则 # 将Q,K进行纵轴拼接, 做一次线性变化, 最后使用softmax处理获得结果 attn_weights = F.softmax( self.attn(torch.cat((Q[0], K[0]), 1)), dim=1) # 然后进行第一步的后半部分, 将得到的权重矩阵与V做矩阵乘法计算, # 当二者都是三维张量且第一维代表为batch条数时, 则做bmm运算 attn_applied = torch.bmm(attn_weights.unsqueeze(0), V) # 之后进行第二步, 通过取[0]是用来降维, 根据第一步采用的计算方法, # 需要将Q与第一步的计算结果再进行拼接 output = torch.cat((Q[0], attn_applied[0]), 1) # 最后是第三步, 使用线性层作用在第三步的结果上做一个线性变换并扩展维度,得到输出 # 因为要保证输出也是三维张量, 因此使用unsqueeze(0)扩展维度 output = self.attn_combine(output).unsqueeze(0) return output, attn_weights
"""关于上述代码的解释""" """ 假设 Q的形状为[1, 1, q_size] K的形状为[1, 1, k_size] V的形状为[1, v_size1, v_size2] 输出维度为 output_size """ """ 根据Attention 的实现步骤 第一步: 根据注意力计算规则, 对Q,K,V进行相应的计算,在这里选择第一种计算方式,即Q,K 进行纵向拼接, 做一次线性变换后,进行softmax 第二步: 根据第一步采用的计算方法, 如果是拼接方法,则需要将Q与第二步的计算结果再进行拼接, 如果是转置点积, 一般是自注意力, Q与V相同, 则不需要进行与Q的拼接. 第三步: 最后为了使整个attention机制按照指定尺寸输出, 使用线性层作用在第二步的结果上做一个线性变换, 得到最终对Q的注意力表示. """ """ 针对以上三步进行详细说明: 第一步: 由于Q的形状[1, 1, q_size] ,K 形状[1, 1, k_size] Q[0] 形状 [1, q_size] K[0] [1, k_size] 对QKV,"Q,K纵向拼接" torch.cat((Q[0], K[0]), dim = 1) 进行一次线性变换(线性变换在最后一个维度进行) self.attn(torch.cat((Q[0], K[0]), 1), dim = 1)。 由于Q[0],K[0]在纵向进行了cat cat后的形状为[1, q_size + k_size] 因此,第一个线性层输入维度为self.attn = nn.Linear(q_size + k_size, ) QK拼接后,需要经过线性变换和softmax与V进行张量乘法 V的形状为[1, v_size1, v_size2] 要与V做矩阵乘法,需要改变形状为[1, 1, v_size1] 因此,确定了第一个线性变换self.attn的输出维度为v_size1 self.attn = nn.Linear(q_size + k_size, v_size1) 同时得到了attn_weights的形状[1, v_size1] (attn_weights = F.softmax(self.attn(torch.cat((Q[0], K[0]) ,1)), dim = 1)) V是三维张量,因此需要给attn_weights扩展一个维度 attn_weight.unsqueeze(0) 变为[1, 1, v_size1] 进行矩阵乘法,得到根据第一种计算方式计算的QKV attn_applied = torch.bmm(attn_weights.unsqueeze(0), V) attn_applied 形状为 [1, 1, v_size2] """ """ 第二步: 由于是拼接操作,因此attn_applied还需要与Q进行一次拼接操作 (第三种计算方式不需要再次拼接) Q形状为[1, 1, q_size] Q[0]降维,形状为[1, q_size] attn_applied 形状为[1, 1, v_size2] attn_applied[0]进行降维 形状为[1, v_size2] 得到 output = torch.cat((Q[0], attn_applied[0]), 1) output形状为[1, q_size + v_size2] """ """ 第三步: 由于二次拼接后需要进行线性变换为指定维度的输出 输出维度为output_size 因此第二个线性变换的输入输出维度确定 输入为output [1, q_size + v_size2] 输出为[1, output_size] self.attn_combine = nn.Linear(self.query_size + value_size2, output_size) 最后输出需要保证是三维张量 因此对output 进行维度扩展 即 output = self.attn_combine(output).unsqueeze(0) 最终 output形状为[1, 1, output_size] 返回 (output , attn_weights) 其中 output [1, 1, output_size] attn_weights [1, v_size1] """
- 调用:
query_size = 32 key_size = 32 value_size1 = 32 value_size2 = 64 output_size = 64 attn = Attn(query_size, key_size, value_size1, value_size2, output_size) Q = torch.randn(1, 1, 32) K = torch.randn(1, 1, 32) V = torch.randn(1, 32, 64) out = attn(Q, K, V) print(out) print(out[0]) # output print(out[0].size()) print(out[1]) # attn_weights print(out[1].size())
- 输出效果:
(tensor([[[ 0.9450, -0.0952, -0.0925, -0.1833, 0.0059, -0.4634, -0.4991, 0.0413, 0.3155, 0.7862, -0.2565, 0.1128, -0.2265, 0.0917, 0.2863, 0.2527, 0.2781, -0.2677, 0.5383, 0.1330, -0.0590, 0.3919, -0.5580, -0.3519, -0.1315, -0.4910, -0.4241, -0.4346, 0.1710, 0.1116, 0.0298, -0.0892, -0.8393, -0.1904, -0.5896, 0.2359, -0.2546, -0.3882, -0.2011, -0.2194, 0.0067, -0.1266, -0.8743, 0.1034, 0.6670, -0.2610, -0.1146, -0.2388, 0.1638, -0.1464, -0.2739, 0.5227, -0.4873, -0.2854, 0.4901, 0.2096, -0.1836, -0.3860, -0.0828, 0.2219, 0.2442, -0.2222, 0.4022, 0.4090]]], grad_fn=<UnsqueezeBackward0>), tensor([[0.0136, 0.0365, 0.0337, 0.0530, 0.0480, 0.0457, 0.0244, 0.0094, 0.0250, 0.0183, 0.0128, 0.0403, 0.0118, 0.0070, 0.0315, 0.0598, 0.0381, 0.0264, 0.0086, 0.0327, 0.0476, 0.0539, 0.0122, 0.0338, 0.0085, 0.0287, 0.0622, 0.0396, 0.0575, 0.0247, 0.0332, 0.0214]], grad_fn=<SoftmaxBackward>)) tensor([[[ 0.9450, -0.0952, -0.0925, -0.1833, 0.0059, -0.4634, -0.4991, 0.0413, 0.3155, 0.7862, -0.2565, 0.1128, -0.2265, 0.0917, 0.2863, 0.2527, 0.2781, -0.2677, 0.5383, 0.1330, -0.0590, 0.3919, -0.5580, -0.3519, -0.1315, -0.4910, -0.4241, -0.4346, 0.1710, 0.1116, 0.0298, -0.0892, -0.8393, -0.1904, -0.5896, 0.2359, -0.2546, -0.3882, -0.2011, -0.2194, 0.0067, -0.1266, -0.8743, 0.1034, 0.6670, -0.2610, -0.1146, -0.2388, 0.1638, -0.1464, -0.2739, 0.5227, -0.4873, -0.2854, 0.4901, 0.2096, -0.1836, -0.3860, -0.0828, 0.2219, 0.2442, -0.2222, 0.4022, 0.4090]]], grad_fn=<UnsqueezeBackward0>) torch.Size([1, 1, 64]) tensor([[0.0136, 0.0365, 0.0337, 0.0530, 0.0480, 0.0457, 0.0244, 0.0094, 0.0250, 0.0183, 0.0128, 0.0403, 0.0118, 0.0070, 0.0315, 0.0598, 0.0381, 0.0264, 0.0086, 0.0327, 0.0476, 0.0539, 0.0122, 0.0338, 0.0085, 0.0287, 0.0622, 0.0396, 0.0575, 0.0247, 0.0332, 0.0214]], grad_fn=<SoftmaxBackward>) torch.Size([1, 32]) Process finished with exit code 0
学习了什么是注意力计算规则:
常见的注意力计算规则:
学习了什么是注意力机制:
注意力机制的作用:
注意力机制实现步骤:
学习并实现了一种常见的注意力机制的类Attn.
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。