网站首页 > 技术文章 正文
我们经常使用深度学习框架来构建模型所需的所有操作。然而,理解底层使用的一些基本矩阵操作是很有用的。在本教程中,将介绍GRU如何工作所需的简单矩阵操作。
什么是门控循环单元(GRU)?
门控循环单元(如下图所示)是一种循环神经网络,它解决了长期依赖关系问题。GRU通过存储来自前一个时间点的“memory”来解决这个问题,从而为网络的未来预测提供信息。
GRU的控制方程是:
其中z,r和h分别代表update和reset gates,而h_tilde和h分别代表intermediate memory和output 。
方法
为了进一步说明RNN的优雅性,我将向您介绍理解GRU内部工作原理所需的线性代数的基础知识。为了做到这一点,我们将使用一个字符串来说明矩阵计算是如何使用预打包的wrapper器函数来创建许多常见的深度学习(DL)框架的。本教程的重点是帮助我们更深入地理解RNN是如何使用线性代数工作的。
使用以下字符串作为输入数据的示例:
`text = MathMathMathMathMath`
算法本质上是某种数学方程,因此我们的原始文本在呈现给GRU层之前必须以数字形式表示。这是在下面的预处理步骤中完成的。
数据预处理
首先导入Python库
import torch import torch.nn as nn from torch.autograd import Variable from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence import torch.nn.functional as F import numpy as np import itertools import pickle %autosave 180
创建一个包含所有唯一字符的字典,将每个字母映射到一个唯一整数:
字符字典:{'h':0,'a':1,'t':2,'M':3}
我们的编码输入现在变为:
MathMath = [3,1,2,0,3,1,2,0]
# This will be our input ---> x text = 'MathMathMathMathMath' character_list = list(set(text)) # get all of the unique letters in our text variable vocabulary_size = len(character_list) # count the number of unique elements character_dictionary = {'h': 0, 'a': 1, 't': 2, 'M': 3} # {char:e for e, char in enumerate(character_list)} # create a dictionary mapping each unique char to a number encoded_chars = [character_dictionary[char] for char in text] #integer representation of our vocabulary
步骤1:创建数据batches
此步骤通过用户指定我们要创建的给定词汇表(V)的batches数(B)或序列长度(S)来实现。下图演示了如何创建和编码batches。
假设我们需要以下参数:
- 1.Batch size(B)= 2
- 2.序列长度(S)= 3
- 3.词汇表(V)= 4
- 4.输出(O)= 4
- 5.样本数量(NS) = 2
那么是什么时间序列呢?
如果您对RNN进行基本搜索,你通常会看到下面的图像。x_(t-1),x_(t)和x_(t + 1)(以红色突出显示)对我们的batches意味着什么呢?
对于我们的mini-batch,时间序列表示每个序列,信息从左到右流动,如下图所示。
数据集的维度
用Python代码说明
def one_hot_encode(encoded, vocab_size): result = torch.zeros((len(encoded), vocab_size)) for i, idx in enumerate(encoded): result[i, idx] = 1.0 return result # One hot encode our encoded charactes batch_size = 2 seq_length = 3 num_samples = (len(encoded_chars) - 1) // seq_length # time lag of 1 for creating the labels vocab_size = 4 data = one_hot_encode(encoded_chars[:seq_length*num_samples], vocab_size).reshape((num_samples, seq_length, vocab_size)) num_batches = len(data) // batch_size X = data[:num_batches*batch_size].reshape((num_batches, batch_size, seq_length, vocab_size)) # swap batch_size and seq_length axis to make later access easier X = X.transpose(1, 2) # +1 shift the labels by one so that given the previous letter the char we should predict would be or next char labels = one_hot_encode(encoded_chars[1:seq_length*num_samples+1], vocab_size) y = labels.reshape((num_batches, batch_size, seq_length, vocab_size)) y = y.transpose(1, 2) # transpose the first and second index y,y.shape
reshaping后,如果你检查X的形状你会发现你得到一个3阶张量的形状:3×3×2×4。
数据现在可以建模了。我们将演示在batch 1(如下所示)中为第一个序列执行的矩阵操作(红色高亮显示)。其思想是理解来自第一个序列的信息如何传递到第二个序列,以此类推。
为此,我们需要首先回想一下这些batches是如何输入算法的。
更具体地说,我们将遍历在序列1的GRU cell中执行的所有矩阵操作,计算得到y_(t-1)和h_t的输出结果如下图所示:
步骤2:定义权重矩阵和偏差向量
在这一步中,我们将引导您完成用于计算z门的矩阵运算,因为其余三个方程的计算完全相同。为了更好地说明这一点,我们将通过将内部方程分解为三个部分来遍历reset gate z的点积,最后我们将sigmoid激活函数应用到输出(0到1之间的值)中:
但首先让我们定义网络参数:
torch.manual_seed(1) # reproducibility #### Define the network parameters: hiddenSize = 2 # network size, this can be any number (depending on your task) numClass = 4 # this is the same as our vocab_size #### Weight matrices for our inputs Wz = torch.randn(vocab_size, hiddenSize) Wr = torch.randn(vocab_size, hiddenSize) Wh = torch.randn(vocab_size, hiddenSize) ## Intialize the hidden state # this is for demonstration purposes only, in the actual model it will be initiated during training a loop over the # the number of bacthes and updated before passing to the next GRU cell. h_t_demo = torch.zeros(batch_size, hiddenSize) #### Weight matrices for our hidden layer Uz = torch.randn(hiddenSize, hiddenSize) Ur = torch.randn(hiddenSize, hiddenSize) Uh = torch.randn(hiddenSize, hiddenSize) #### bias vectors for our hidden layer bz = torch.zeros(hiddenSize) br = torch.zeros(hiddenSize) bh = torch.zeros(hiddenSize) #### Output weights Wy = torch.randn(hiddenSize, numClass) by = torch.zeros(numClass)
我们来分解网络参数的维数。
什么是隐藏大小(hidden size)?
上面定义的隐藏大小,是学习参数的数量或简单地说,网络内存。此参数通常由用户根据手头的问题定义,因为使用更多单位可能使其更有可能过度拟合训练数据。在我们的例子中,我们选择隐藏的大小为2。这些值通常被初始化为来自正态分布的随机数,当我们执行forward passes和反向传播时,这些随机数可以训练和更新。
我们的权重大小
在开始上述任何矩阵操作之前,让我们先讨论一个名为广播(broadcasting)的导入概念。如果我们看batch 1(3×2×4)张量和Wz(4×2)张量的形状,首先想到的是,我们如何对这两个形状不同的张量进行元素矩阵乘法呢?
答案是我们使用一个名为“Broadcasting”的过程。Broadcasting用于使这两个张量的形状兼容,这样我们就可以执行元素矩阵运算。这意味着Wz将被广播到非矩阵维度,在我们的例子中,我们的序列长度为3.这意味着更新方程式z中的所有其他项也将被广播。因此,我们的最终等式将如下所示:
在我们执行实际的矩阵运算之前,让我们看一下batch 1中的序列1:
The update gate z
update gate决定了过去的信息对当前状态的有用程度。在这里,使用sigmoid函数会导致更新0到1之间的gate值。因此,这个值越接近1,我们吸收过去的信息就越多,而接近0的值意味着只保留新的信息。
注意当两个矩阵相乘时,执行行和列的点积。这里,第一矩阵(x_t)的每一行(以黄色突出显示)逐个元素地乘以第二矩阵(Wz)的每列(以蓝色突出显示)。
1:应用于输入的权重
torch.matmul(X[0][0], Wz)
2:隐藏权重
torch.matmul(Uz, h_t_demo), Uz
3:偏差向量
把它们放在一起:z_inner
z_inner = torch.matmul(X,Wz) + torch.matmul(Uz,h_t_demo) + bz
然后使用sigmoid激活函数将得到的矩阵中的值压缩到0到1之间:
z = torch.sigmoid(z_inner) z[0][0]
reset gate: r
Reset gate允许模型忽略在未来时间步骤中可能不相关的过去信息。在每个batch中,Reset gate将重新评估先前输入和新输入的组合性能,并根据新输入的需要进行Reset。同样由于sigmoid激活函数,接近0的值意味着我们会忽略之前的隐藏状态,而接近1的值则相反。
Intermediate Memory: h_tilde
intermediate memory单元或候选隐藏状态将来自先前隐藏状态的信息与输入组合。由于第一项和第三项所需的矩阵运算与我们在z中所做的相同。
torch.matmul(r[0][0] * h_t_demo, Uh)
偏差向量
把它们放在一起:h_tilde
然后使用sigmoid激活函数将得到的矩阵中的值压缩到0到1之间:
最后:
h_inner_tilde = torch.matmul(X,Wh) + r*torch.matmul(Uh,h_t_demo) + bh h_tilde = torch.tanh(h_inner_tilde)
在时间步t输出隐藏层:h_(t-1)
第一项:
第二项:
ht_1 = z *h_t_demo + (1-z)* h_tilde
最终:
batch 1 (time step x_t) 中的第二个序列如何从此隐藏状态中获取信息呢?
回想一下,h_(t - n)首先被初始化为零(在本教程中使用)或随机噪声以开始训练,此后网络将学习和适应该训练。但是在第一次迭代之后,新的隐藏状态h_t现在将被用作我们的新隐藏状态,并且在时间步(x_t)对序列2重复上述计算。下图显示了如何完成此操作。
这个新的隐藏状态h_(t-1)将不会用于计算批处理中第二个时间步的隐藏状态h_(t)和输出(y_(t + 1)),依此类推。
下面我们将演示如何使用新的隐藏状态h_(t-1)来计算后续隐藏状态。这通常使用循环完成。该循环迭代每个给定batch中的所有元素以计算h_(t-1)。
Python代码实现:Batch 1 输出:h(t-1),h(t)和h(t + 1)
# h gets updated and then we calculate for the next h_t_1 = [] h = h_t_demo for i,sequence in enumerate(X[0]): # iterate over each sequence in the batch to calculate the hidden state h z = torch.sigmoid(torch.matmul(sequence, Wz) + torch.matmul(h, Uz) + bz) r = torch.sigmoid(torch.matmul(sequence, Wr) + torch.matmul(h, Ur) + br) h_tilde = torch.tanh(torch.matmul(sequence, Wh) + torch.matmul(r * h, Uh) + bh) h = z * h + (1 - z) * h_tilde h_t_1.append(h) print(f'h{i}:{h}') h_t_1 = torch.stack(h_t_1)
使用batch 1中的第二个序列,我们可以演示如何可视化地获得上面第二个序列的h_1。
h_t_minus_1 = torch.tensor([[ 0.7565, -0.3472],[-0.1355, -0.2040]]) h = h_t_minus_1 z = torch.sigmoid(torch.matmul(X[0][1], Wz) + torch.matmul(h, Uz) + bz) r = torch.sigmoid(torch.matmul(X[0][1], Wr) + torch.matmul(h, Ur) + br) h_tilde = torch.tanh(torch.matmul(X[0][1], Wh) + torch.matmul(r * h, Uh) + bh) hh = z * h + (1 - z) * h_tilde hh
tensor([[-0.1536, -0.5713],
[ 0.7664, -0.5062]])
第二个batch的隐藏状态是什么?
它可以看作是h_(t + 1)的输出序列,然后将被送到下一batch,整个过程再次开始。
Python代码实现:跨批传递隐藏状态
ht_2 = [] # stores the calculated h for each input x h = torch.zeros(batch_size, hiddenSize) # intitalizes the hidden state for i in range(num_batches): # this loops over the batches x = X[i] for i,sequence in enumerate(x): # iterates over the sequences in each batch z = torch.sigmoid(torch.matmul(sequence, Wz) + torch.matmul(h, Uz) + bz) r = torch.sigmoid(torch.matmul(sequence, Wr) + torch.matmul(h, Ur) + br) h_tilde = torch.tanh(torch.matmul(sequence, Wh) + torch.matmul(r * h, Uh) + bh) h = z * h + (1 - z) * h_tilde ht_2.append(h) ht_2 = torch.stack(ht_2) ht_2
步骤3:计算每个时间步的输出预测
要获得对每个时间步的预测,我们首先必须使用线性层转换输出。回想一下隐藏状态中列的维度h_(t+n),本质上是network size/hidden size。但是,我们有4个惟一的输入,并且我们希望输出的大小也为4。因此,我们使用所谓的dense层或全连接层将输出转换回所需的维度。根据需要的输出,这个全连接层然后被传递到一个激活函数(本教程使用softmax)中。
fully_connected = torch.matmul(ht_2, Wy) + by
最后,我们应用Softmax激活函数将输出归一化为概率分布,总和为1。 Softmax函数:
根据教科书的不同,您可能会看到softmax的不同风格,特别是使用softmax max技巧,它减去整个数据集的最大值,以防止y_lineary / fully_connected的大值爆炸。 在我们的例子中,这意味着在应用softmax方程之前,我们的最大值0.9021将首先从y_linear中减去。
让我们分解一下,请注意我们不能像前面那样对序列进行子集化,因为求和需要整个batch中的所有元素。
1.从全连接层中的所有元素中减去整个数据集的最大值:
ylin_max = (fully_connected - fully_connected.max()) # first sequence in batch 1 exp = ylin_max.exp()
2.找出指数矩阵中所有元素的总和
exp_sum = exp[0].sum(dim=1,keepdim=True).reshape(-1,1)
3.将矩阵中第1步的每个元素除以第2步中相应行的值
exp[0]/exp_sum
Python代码实现:Softmax
ht_2 = [] # stores the calculated h for each input x outputs = [] h = torch.zeros(batch_size, hiddenSize) # intitalizes the hidden state for i in range(num_batches): # this loops over the batches x = X[i] for i,sequence in enumerate(x): # iterates over the sequences in each batch z = torch.sigmoid(torch.matmul(sequence, Wz) + torch.matmul(h, Uz) + bz) r = torch.sigmoid(torch.matmul(sequence, Wr) + torch.matmul(h, Ur) + br) h_tilde = torch.tanh(torch.matmul(sequence, Wh) + torch.matmul(r * h, Uh) + bh) h = z * h + (1 - z) * h_tilde # Linear layer y_linear = torch.matmul(h, Wy) + by # Softmax activation function y_t = F.softmax(y_linear, dim=1) ht_2.append(h) outputs.append(y_t) ht_2 = torch.stack(ht_2) outputs = torch.stack(outputs) outputs[0]
tensor([[0.4342, 0.1669, 0.1735, 0.2254],
[0.2207, 0.2352, 0.3322, 0.2119]])
训练我们的网络(forward only)
在这里,我们通过在网络中多次运行每个batch理来训练输入batchs上的网络,这称为epoch。这使得网络可以多次学习序列。然后进行损失计算和反向传播以最小化损失。在本节中,我们将一次性实现上面显示的所有代码片段。由于输入量较小,我们将只演示前向传递。
def gru(x, h): outputs = [] for i in range(num_batches): # this loops over the batches x = X[i] for i,sequence in enumerate(x): # iterates over the sequences in each batch z = torch.sigmoid(torch.matmul(sequence, Wz) + torch.matmul(h, Uz) + bz) r = torch.sigmoid(torch.matmul(sequence, Wr) + torch.matmul(h, Ur) + br) h_tilde = torch.tanh(torch.matmul(sequence, Wh) + torch.matmul(r * h, Uh) + bh) h = z * h + (1 - z) * h_tilde # Linear layer y_linear = torch.matmul(h, Wy) + by # Softmax activation function y_t = F.softmax(y_linear, dim=1) outputs.append(y_t) return torch.stack(outputs), h
此函数将向网络提供字母的引子,帮助创建初始状态并避免进行随机猜测。如下所示,生成的前两个字符串有点不稳定,但是经过几次传递之后,它似乎至少正确地获得了后面两个字符。然而,由于词汇量较小,这个网络很可能是过拟合的。
def sample(primer, length_chars_predict): word = primer primer_dictionary = [character_dictionary[char] for char in word] test_input = one_hot_encode(primer_dictionary, vocab_size) h = torch.zeros(1, hiddenSize) for i in range(length_chars_predict): outputs, h = gru(test_input, h) choice = np.random.choice(vocab_size, p=outputs[-1][0].numpy()) word += character_list[choice] input_sequence = one_hot_encode([choice],vocab_size) return word max_epochs = 10 # passes through the data for e in range(max_epochs): h = torch.zeros(batch_size, hiddenSize) for i in range(num_batches): x_in = X[i] y_in = y[i] out, h = gru(x, h) print(sample('Ma',20))
最后
本教程的目的是提供GRU内部工作的演练,演示如何组合简单的矩阵操作可以制作如此强大的算法。
猜你喜欢
- 2024-10-12 一文了解人工智能该如何入门 学人工智能的步骤
- 2024-10-12 微信公众号文章质量评分算法详解 公众号文章质量怎么提高
- 2024-10-12 深度学习视频理解(分类识别)算法梳理
- 2024-10-12 「网易云音乐」歌单推荐算法:技术同学体验反推
- 2024-10-12 深度神经网络GRU模型实战:教你两小时打造随身AI翻译官
- 2024-10-12 基于GWO灰狼优化的CNN-GRU-Attention
- 2024-10-12 基于PSO优化的CNN-GRU-Attention的时间序列
- 2024-10-12 时域卷积网络TCN详解:使用卷积进行序列建模和预测
- 2024-10-12 计算机,通信,算法 通信算法和计算机算法
- 2024-10-12 基于GA优化的CNN-GRU-Attention的时间序列
你 发表评论:
欢迎- 最近发表
-
- 在 Spring Boot 项目中使用 activiti
- 开箱即用-activiti流程引擎(active 流程引擎)
- 在springBoot项目中整合使用activiti
- activiti中的网关是干什么的?(activiti包含网关)
- SpringBoot集成工作流Activiti(完整源码和配套文档)
- Activiti工作流介绍及使用(activiti工作流会签)
- SpringBoot集成工作流Activiti(实际项目演示)
- activiti工作流引擎(activiti工作流引擎怎么用)
- 工作流Activiti初体验及在数据库中生成的表
- Activiti工作流浅析(activiti6.0工作流引擎深度解析)
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)