使用Pytorch从零开始构建GRU
门控循环单元 (GRU) 是 LSTM 的更新版本。让我们揭开这个网络的面纱并探索这两个兄弟姐妹之间的差异。
您听说过 GRU 吗?门控循环单元(GRU)是更流行的长短期记忆(LSTM)网络的弟弟,也是循环神经网络(RNN)的一种。就像它的兄弟一样,GRU 能够有效地保留顺序数据中的长期依赖性。此外,它们还可以解决困扰普通 RNN 的“短期记忆”问题。
考虑到序列建模和预测中循环架构的遗留问题,GRU 有望因其卓越的速度而超越其前辈,同时实现相似的准确性和有效性。
在本文中,我们将介绍 GRU 背后的概念,并将 GRU 与 LSTM 的机制进行比较。我们还将探讨这两种 RNN 变体的性能差异。如果您不熟悉 RNN 或 LSTM,您可以查看我之前介绍这些主题的文章:
- 使用Pytorch从零开始构建RNN
- 使用Pytorch从零开始构建LSTM
什么是 GRU?
门控循环单元(GRU),顾名思义,是RNN 架构的一种变体,它使用门控机制来控制和管理神经网络中单元之间的信息流。Cho 等人于 2014 年才引入 GRU 。可以被认为是一种相对较新的架构,特别是与Sepp Hochreiter 和 Jürgen Schmidhuber于 1997 年提出的广泛采用的 LSTM 相比。
GRU 的结构使其能够自适应地捕获大型数据序列的依赖性,而不会丢弃序列早期部分的信息。这是通过其门控单元实现的,类似于 LSTM 中的门控单元,它解决了传统 RNN 的梯度消失/爆炸问题。这些门负责调节每个时间步要保留或丢弃的信息。我们将在本文后面深入探讨这些门的工作原理以及它们如何克服上述问题的细节。
除了其内部门控机制之外,GRU 的功能就像 RNN 一样,其中顺序输入数据由 GRU 单元在每个时间步与内存一起消耗,或者称为隐藏状态。然后,隐藏状态与序列中的下一个输入数据一起重新输入到 RNN 单元中。这个过程像继电器系统一样持续进行,产生所需的输出。
GRU 的内部运作
GRU 保持长期依赖性或记忆的能力源于 GRU 单元内产生隐藏状态的计算。虽然LSTM在单元之间传递两种不同的状态——单元状态和隐藏状态,分别承载长期和短期记忆,但 GRU 在时间步之间仅传递一种隐藏状态。由于隐藏状态和输入数据所经历的门控机制和计算,该隐藏状态能够同时保持长期和短期依赖性。
GRU 单元仅包含两个门:更新门和重置门。就像 LSTM 中的门一样,GRU 中的这些门经过训练可以有选择地过滤掉任何不相关的信息,同时保留有用的信息。这些门本质上是包含0到1之间的值的向量,这些值将与输入数据和/或隐藏状态相乘。门向量中的0值表示输入或隐藏状态中的相应数据不重要,因此将返回零。另一方面,门向量中的值1意味着相应的数据很重要并将被使用。
在本文的其余部分中,我将交替使用术语“门”和“向量” ,因为它们指的是同一事物。
GRU单元的结构如下所示。
虽然由于存在大量连接,该结构可能看起来相当复杂,但其背后的机制可以分为三个主要步骤。
重置门
第一步,我们将创建重置门。该门是使用前一时间步的隐藏状态和当前时间步的输入数据导出和计算的。
从数学上讲,这是通过将先前的隐藏状态和当前输入与其各自的权重相乘并在将总和传递给sigmoid函数之前将它们相加来实现的。sigmoid函数会将值转换为介于0和1之间,从而允许门在后续步骤中在不太重要和更重要的信息之间进行过滤。
g a t e r e s e t = σ ( W i n p u t r e s e t ⋅ x t + W h i d d e n r e s e t ⋅ h t − 1 ) gate_{reset} = \sigma(W_{input_{reset}} \cdot x_t + W_{hidden_{reset}} \cdot h_{t-1}) gatereset=σ(Winputreset⋅xt+Whiddenreset⋅ht−1)
当整个网络通过反向传播进行训练时,方程中的权重将被更新,使得向量将学会仅保留有用的特征。
先前的隐藏状态将首先乘以可训练权重,然后与重置向量进行逐元素乘法(哈达玛乘积) 。 此操作将决定将先前时间步骤中的哪些信息与新输入一起保留。同时,当前输入还将乘以可训练权重,然后与上面的重置向量和先前隐藏状态的乘积相加。最后,将非线性激活tanh函数应用于最终结果,以获得下面等式中的r 。
r = t a n h ( g a t e r e s e t ⊙ ( W h 1 ⋅ h t − 1 ) + W x 1 ⋅ x t ) r = tanh(gate_{reset} \odot (W_{h_1} \cdot h_{t-1}) + W_{x_1} \cdot x_t) r=tanh(gatereset⊙(Wh1⋅ht−1)+Wx1⋅xt)
更新门
接下来,我们必须创建更新门。就像重置门一样,门是使用先前的隐藏状态和当前输入数据来计算的。
更新和重置门向量都是使用相同的公式创建的,但是,与输入和隐藏状态相乘的权重对于每个门来说是唯一的,这意味着每个门的最终向量是不同的。这使得大门能够满足其特定目的。
g a t e u p d a t e = σ ( W i n p u t u p d a t e ⋅ x t + W h i d d e n u p d a t e ⋅ h t − 1 ) gate_{update} = \sigma(W_{input_{update}} \cdot x_t + W_{hidden_{update}} \cdot h_{t-1}) gateupdate=σ(Winputupdate⋅xt+Whiddenupdate⋅ht−1)
然后,更新向量将与之前的隐藏状态进行逐元素相乘,以获得下面等式中的u ,该值将用于稍后计算我们的最终输出。
u = g a t e u p d a t e ⊙ h t − 1 u = gate_{update} \odot h_{t-1} u=gateupdate⊙ht−1
稍后在获得最终输出时,更新向量还将用于另一个操作。这里更新门的目的是帮助模型确定存储在先前隐藏状态中的过去信息有多少需要保留以供将来使用。
组合输出
在最后一步中,我们将重用更新门并获取更新的隐藏状态。
这次,我们将采用同一更新向量(1 -更新 门)的逐元素逆版本,并与重置门的输出r进行逐元素乘法。此操作的目的是让更新门确定新信息的哪一部分应存储在隐藏状态中。
最后,上述操作的结果将与上一步更新门的输出u相加。这将为我们提供新的和更新的隐藏状态。
h t = r ⊙ ( 1 − g a t e u p d a t e ) + u h_t = r \odot (1-gate_{update}) + u ht=r⊙(1−gateupdate)+u
我们也可以通过将这个新的隐藏状态传递给线性激活层来将其用作该时间步的输出。
解决梯度消失/爆炸问题
我们已经看到了大门的运作。我们知道它们如何转换我们的数据。现在让我们回顾一下它们在管理网络内存方面的整体作用,并讨论它们如何解决梯度消失/爆炸问题。
正如我们在上面的机制中所看到的,重置门负责决定将先前隐藏状态的哪些部分与当前输入组合以提出新的隐藏状态。
更新门负责确定要保留多少先前的隐藏状态,以及将新提出的隐藏状态(源自重置门)的哪一部分添加到最终隐藏状态。当更新门首先与先前的隐藏状态相乘时,网络会选择将先前隐藏状态的哪些部分保留在内存中,同时丢弃其余部分。随后,当它使用更新门的逆来从重置门过滤提议的新隐藏状态时,它会修补信息的缺失部分。
这使得网络能够保留长期的依赖关系。如果更新向量值接近 1,则更新门可以选择保留隐藏状态中的大部分先前记忆,而无需重新计算或更改整个隐藏状态。
在训练 RNN 时,反向传播过程中会出现梯度消失/爆炸问题,特别是当 RNN 处理长序列或具有多层时。训练期间计算的误差梯度用于以正确的方向和正确的幅度更新网络的权重。然而,这个梯度是用链式法则从网络末端开始计算的。因此,在反向传播过程中,梯度将连续进行矩阵乘法,并且对于长序列,梯度会呈指数收缩(消失)或爆炸(爆炸) 。梯度太小意味着模型无法有效更新其权重,而梯度太大会导致模型不稳定。
由于更新门的附加组件,LSTM 和 GRU 中的门有助于解决这个问题。传统 RNN 总是在每个时间步替换隐藏状态的全部内容,而 LSTM 和 GRU 保留大部分现有隐藏状态,同时在其上添加新内容。这允许误差梯度反向传播,而不会由于加法运算而消失或爆炸太快。
虽然 LSTM 和 GRU 是解决上述问题最广泛使用的方法,但梯度爆炸问题的另一种解决方案是梯度裁剪。Clipping 在梯度上设置了一个定义的阈值,这意味着即使在训练过程中梯度增加超过预定义值,其值仍然会被限制在设定的阈值内。这样,梯度的方向不受影响,仅梯度的大小发生变化。
GRU 与 LSTM
我们已经掌握了 GRU 的机制。但它与它的老兄弟(也更流行)的 LSTM 相比如何呢?
两者都是为了解决标准 RNN 面临的梯度消失/爆炸问题而创建的,并且这两种 RNN 变体都利用门控机制来控制网络内长期和短期依赖关系的流动。
但它们有什么不同呢?
-
结构性差异
虽然 GRU 和 LSTM 都包含门,但这两种结构之间的主要区别在于门的数量及其具体作用。GRU 中的更新门的作用与LSTM 中的输入门和遗忘门非常相似。然而,这两者对添加到网络的新内存内容的控制有所不同。
在 LSTM 中,遗忘门决定保留先前单元状态的哪一部分,而输入门则决定要添加的新内存量。这两个门是相互独立的,这意味着通过输入门 添加的新信息量完全独立于通过遗忘门保留的信息量。
对于GRU来说,更新门负责决定保留之前内存中的哪些信息,并 负责控制新内存的添加。这意味着 GRU 中先前内存的保留和向内存中添加新信息不是独立的。
如前所述,这些结构之间的另一个主要区别是 GRU 中缺少单元状态。LSTM 将其长期依赖关系存储在单元状态中,并将短期记忆存储在隐藏状态中,而 GRU 将两者存储在单个隐藏状态中。然而,就保留长期信息的有效性而言,两种架构都已被证明可以有效地实现这一目标。 -
速度差异
与 LSTM 相比,GRU 的训练速度更快,因为训练期间需要更新的权重和参数数量较少。这可以归因于与 LSTM 的三个门相比,GRU 单元中的门数量较少(两个门)。
在本文后面的代码演练中,我们将直接比较在完全相同的任务上训练 LSTM 和 GRU 的速度。 -
性能评估
模型的准确性,无论是通过误差幅度还是正确分类的比例来衡量,通常是决定使用哪种类型的模型来完成任务时的主要因素。GRU 和 LSTM 都是 RNNs 的变体,可以互换插入以获得类似的结果。
项目:使用 GRU 和 LSTM 进行时间序列预测
我们已经了解了 GRU 背后的理论概念。现在是时候将所学知识付诸实践了。
我们将在代码中实现 GRU 模型。为了进一步进行 GRU-LSTM 比较,我们还将使用 LSTM 模型来完成相同的任务。我们将根据一些指标评估这两个模型的性能。我们将使用的数据集是每小时能源消耗数据集,可以在Kaggle上找到。该数据集包含按小时记录的美国不同地区的电力消耗数据,你可以访问 GitHub 代码库。
该项目的目标是创建一个模型,可以根据历史使用数据准确预测下一小时的能源使用情况。我们将使用 GRU 和 LSTM 模型来训练一组历史数据,并在未见过的测试集上评估这两个模型。为此,我们将从特征选择和数据预处理开始,然后定义、训练并最终评估模型。
这将是我们项目的流程。
我们将使用 PyTorch 库以及数据分析中使用的其他常见 Python 库来实现这两种类型的模型。
import os
import timeimport numpy as np
import pandas as pd
import matplotlib.pyplot as pltimport torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoaderfrom tqdm import tqdm_notebook
from sklearn.preprocessing import MinMaxScaler# Define data root directory
data_dir = "./data/"
# Visualise how our data looks
pd.read_csv(data_dir + 'AEP_hourly.csv').head()
我们总共有12 个 .csv文件,其中包含上述格式的每小时能源趋势数据(未使用“est_hourly.paruqet”和“pjm_hourly_est.csv” )。在下一步中,我们将按以下顺序读取这些文件并预处理这些数据:
- 获取每个单独时间步的时间数据并对它们进行概括
- 一天中的某个小时,即 0 - 23
- 一周中的某一天,即。1 - 7
- 月份,即 1 - 12
- 一年中的某一天,即 1 - 365
- 将数据缩放到 0 到 1 之间的值
- 当特征具有相对相似的规模和/或接近正态分布时,算法往往会表现更好或收敛得更快
- 缩放保留了原始分布的形状并且不会降低异常值的重要性
- 将数据分组为序列,用作模型的输入并存储其相应的标签
- 序列长度或回顾周期是模型用于进行预测的历史数据点的数量
- 标签将是输入序列中最后一个数据点之后的下一个数据点
- 将输入和标签拆分为训练集和测试集
# The scaler objects will be stored in this dictionary so that our output test data from the model can be re-scaled during evaluation
label_scalers = {}train_x = []
test_x = {}
test_y = {}for file in tqdm_notebook(os.listdir(data_dir)): # Skipping the files we're not usingif file[-4:] != ".csv" or file == "pjm_hourly_est.csv":continue# Store csv file in a Pandas DataFramedf = pd.read_csv('{}/{}'.format(data_dir, file), parse_dates=[0])# Processing the time data into suitable input formatsdf['hour'] = df.apply(lambda x: x['Datetime'].hour,axis=1)df['dayofweek'] = df.apply(lambda x: x['Datetime'].dayofweek,axis=1)df['month'] = df.apply(lambda x: x['Datetime'].month,axis=1)df['dayofyear'] = df.apply(lambda x: x['Datetime'].dayofyear,axis=1)df = df.sort_values("Datetime").drop("Datetime",axis=1)# Scaling the input datasc = MinMaxScaler()label_sc = MinMaxScaler()data = sc.fit_transform(df.values)# Obtaining the Scale for the labels(usage data) so that output can be re-scaled to actual value during evaluationlabel_sc.fit(df.iloc[:,0].values.reshape(-1,1))label_scalers[file] = label_sc# Define lookback period and split inputs/labelslookback = 90inputs = np.zeros((len(data)-lookback,lookback,df.shape[1]))labels = np.zeros(len(data)-lookback)for i in range(lookback, len(data)):inputs[i-lookback] = data[i-lookback:i]labels[i-lookback] = data[i,0]inputs = inputs.reshape(-1,lookback,df.shape[1])labels = labels.reshape(-1,1)# Split data into train/test portions and combining all data from different files into a single arraytest_portion = int(0.1*len(inputs))if len(train_x) == 0:train_x = inputs[:-test_portion]train_y = labels[:-test_portion]else:train_x = np.concatenate((train_x,inputs[:-test_portion]))train_y = np.concatenate((train_y,labels[:-test_portion]))test_x[file] = (inputs[-test_portion:])test_y[file] = (labels[-test_portion:])
我们总共有 980,185 个训练数据序列。
为了提高训练速度,我们可以批量处理数据,这样模型就不需要频繁更新权重。Torch Dataset和DataLoader类对于将数据拆分为批次并对其进行混洗非常有用。
batch_size = 1024
train_data = TensorDataset(torch.from_numpy(train_x), torch.from_numpy(train_y))
train_loader = DataLoader(train_data, shuffle=True, batch_size=batch_size, drop_last=True)
我们还可以检查是否有 GPU 来加快训练时间。如果您使用带有 GPU 的 FloydHub 来运行此代码,训练时间将显着减少。
# torch.cuda.is_available() checks and returns a Boolean True if a GPU is available, else it'll return False
is_cuda = torch.cuda.is_available()# If we have a GPU available, we'll set our device to GPU. We'll use this device variable later in our code.
if is_cuda:device = torch.device("cuda")
else:device = torch.device("cpu")
接下来,我们将定义 GRU 和 LSTM 模型的结构。两种模型具有相同的结构,唯一的区别是循环层(GRU/LSTM)和隐藏状态的初始化。LSTM 的隐藏状态是包含单元状态和隐藏状态 的元组,而 GRU 仅具有单个隐藏状态。
class GRUNet(nn.Module):def __init__(self, input_dim, hidden_dim, output_dim, n_layers, drop_prob=0.2):super(GRUNet, self).__init__()self.hidden_dim = hidden_dimself.n_layers = n_layersself.gru = nn.GRU(input_dim, hidden_dim, n_layers, batch_first=True, dropout=drop_prob)self.fc = nn.Linear(hidden_dim, output_dim)self.relu = nn.ReLU()def forward(self, x, h):out, h = self.gru(x, h)out = self.fc(self.relu(out[:,-1]))return out, hdef init_hidden(self, batch_size):weight = next(self.parameters()).datahidden = weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().to(device)return hiddenclass LSTMNet(nn.Module):def __init__(self, input_dim, hidden_dim, output_dim, n_layers, drop_prob=0.2):super(LSTMNet, self).__init__()self.hidden_dim = hidden_dimself.n_layers = n_layersself.lstm = nn.LSTM(input_dim, hidden_dim, n_layers, batch_first=True, dropout=drop_prob)self.fc = nn.Linear(hidden_dim, output_dim)self.relu = nn.ReLU()def forward(self, x, h):out, h = self.lstm(x, h)out = self.fc(self.relu(out[:,-1]))return out, hdef init_hidden(self, batch_size):weight = next(self.parameters()).datahidden = (weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().to(device),weight.new(self.n_layers, batch_size, self.hidden_dim).zero_().to(device))return hidden
训练过程在下面的函数中定义,以便我们可以为两个模型重现它。两个模型在隐藏状态和层中将具有相同数量的维度,在相同数量的epoch和学习率上进行训练,并在完全相同的数据集上进行训练和测试。
为了比较两个模型的性能,我们将跟踪模型训练所需的时间,并最终比较两个模型在测试集上的最终准确性。对于我们的准确性测量,我们将使用对称平均绝对百分比误差(sMAPE)来评估模型。sMAPE是预测值和实际值之间的绝对差值除以预测值和实际值的平均值的总和,从而给出衡量误差量的百分比。这是sMAPE的公式:
$$
$$
def train(train_loader, learn_rate, hidden_dim=256, EPOCHS=5, model_type="GRU"):# Setting common hyperparametersinput_dim = next(iter(train_loader))[0].shape[2]output_dim = 1n_layers = 2# Instantiating the modelsif model_type == "GRU":model = GRUNet(input_dim, hidden_dim, output_dim, n_layers)else:model = LSTMNet(input_dim, hidden_dim, output_dim, n_layers)model.to(device)# Defining loss function and optimizercriterion = nn.MSELoss()optimizer = torch.optim.Adam(model.parameters(), lr=learn_rate)model.train()print("Starting Training of {} model".format(model_type))epoch_times = []# Start training loopfor epoch in range(1,EPOCHS+1):start_time = time.clock()h = model.init_hidden(batch_size)avg_loss = 0.counter = 0for x, label in train_loader:counter += 1if model_type == "GRU":h = h.dataelse:h = tuple([e.data for e in h])model.zero_grad()out, h = model(x.to(device).float(), h)loss = criterion(out, label.to(device).float())loss.backward()optimizer.step()avg_loss += loss.item()if counter%200 == 0:print("Epoch {}......Step: {}/{}....... Average Loss for Epoch: {}".format(epoch, counter, len(train_loader), avg_loss/counter))current_time = time.clock()print("Epoch {}/{} Done, Total Loss: {}".format(epoch, EPOCHS, avg_loss/len(train_loader)))print("Total Time Elapsed: {} seconds".format(str(current_time-start_time)))epoch_times.append(current_time-start_time)print("Total Training Time: {} seconds".format(str(sum(epoch_times))))return modeldef evaluate(model, test_x, test_y, label_scalers):model.eval()outputs = []targets = []start_time = time.clock()for i in test_x.keys():inp = torch.from_numpy(np.array(test_x[i]))labs = torch.from_numpy(np.array(test_y[i]))h = model.init_hidden(inp.shape[0])out, h = model(inp.to(device).float(), h)outputs.append(label_scalers[i].inverse_transform(out.cpu().detach().numpy()).reshape(-1))targets.append(label_scalers[i].inverse_transform(labs.numpy()).reshape(-1))print("Evaluation Time: {}".format(str(time.clock()-start_time)))sMAPE = 0for i in range(len(outputs)):sMAPE += np.mean(abs(outputs[i]-targets[i])/(targets[i]+outputs[i])/2)/len(outputs)print("sMAPE: {}%".format(sMAPE*100))return outputs, targets, sMAPE
lr = 0.001
gru_model = train(train_loader, lr, model_type="GRU")
Lstm_model = train(train_loader, lr, model_type="LSTM")
[Out]: Starting Training of GRU modelEpoch 1......Step: 200/957....... Average Loss for Epoch: 0.0070570480596506965Epoch 1......Step: 400/957....... Average Loss for Epoch: 0.0039001358837413135Epoch 1......Step: 600/957....... Average Loss for Epoch: 0.0027501484048358784Epoch 1......Step: 800/957....... Average Loss for Epoch: 0.0021489552696584723Epoch 1/5 Done, Total Loss: 0.0018450273993545988Time Elapsed for Epoch: 78.02232400000003 seconds...Total Training Time: 390.52727700000037 secondsStarting Training of LSTM modelEpoch 1......Step: 200/957....... Average Loss for Epoch: 0.013630141295143403...Total Training Time: 462.73371699999984 seconds
从两个模型的训练时间可以看出,我们的弟弟妹妹在速度方面绝对击败了哥哥。GRU 模型在这个维度上是明显的赢家;它比 LSTM 模型快 72 秒完成了 5 个训练周期。
继续测量两个模型的准确性,我们现在将使用评估()函数和测试数据集。
gru_outputs, targets, gru_sMAPE = evaluate(gru_model, test_x, test_y, label_scalers)
[Out]: Evaluation Time: 1.982479000000012sMAPE: 0.28081167222194775%
lstm_outputs, targets, lstm_sMAPE = evaluate(lstm_model, test_x, test_y, label_scalers)
[Out]: Evaluation Time: 2.602886000000126sMAPE: 0.27014616762377464%
有趣,对吧?虽然 LSTM 模型的误差可能较小,并且在性能准确度方面略微领先于 GRU 模型,但差异并不显着,因此尚无定论。
比较这两个模型的其他测试同样没有得出明显的胜利者,无法确定哪个是更好的整体架构。
最后,让我们对预测输出与实际消耗数据的随机集进行一些可视化。
plt.figure(figsize=(14,10))
plt.subplot(2,2,1)
plt.plot(gru_outputs[0][-100:], "-o", color="g", label="Predicted")
plt.plot(targets[0][-100:], color="b", label="Actual")
plt.ylabel('Energy Consumption (MW)')
plt.legend()plt.subplot(2,2,2)
plt.plot(gru_outputs[8][-50:], "-o", color="g", label="Predicted")
plt.plot(targets[8][-50:], color="b", label="Actual")
plt.ylabel('Energy Consumption (MW)')
plt.legend()plt.subplot(2,2,3)
plt.plot(gru_outputs[4][:50], "-o", color="g", label="Predicted")
plt.plot(targets[4][:50], color="b", label="Actual")
plt.ylabel('Energy Consumption (MW)')
plt.legend()plt.subplot(2,2,4)
plt.plot(lstm_outputs[6][:100], "-o", color="g", label="Predicted")
plt.plot(targets[6][:100], color="b", label="Actual")
plt.ylabel('Energy Consumption (MW)')
plt.legend()
plt.show()
看起来这些模型在预测能源消耗趋势方面基本上是成功的。虽然他们可能仍然会犯一些错误,例如延迟预测消耗下降,但预测非常接近测试集上的实际线。这是由于能源消耗数据的性质以及模型可以解释的模式和周期性变化这一事实造成的。更困难的时间序列预测问题,例如股票价格预测或销量预测,可能具有很大程度上随机的数据或没有可预测的模式,在这种情况下,准确性肯定会较低。
超越 GRU
正如我在LSTM 文章中提到的,多年来,RNN 及其变体在各种 NLP 任务中已被取代,不再是NLP 标准架构。预训练的Transformer 模型(例如 Google 的 BERT、OpenAI 的 GPT和最近推出的 XLNet)已经产生了最先进的基准和结果,并将下游任务的迁移学习引入到 NLP。
至此,GRU 的所有事情就暂时结束了。这篇文章完成了我涵盖 RNN 基础知识的系列文章;未来,我们将探索更先进的概念,例如注意力机制、Transformers 和 NLP 中最先进的技术。
本博客译自 Gabriel Loye的博文。
相关文章:

使用Pytorch从零开始构建GRU
门控循环单元 (GRU) 是 LSTM 的更新版本。让我们揭开这个网络的面纱并探索这两个兄弟姐妹之间的差异。 您听说过 GRU 吗?门控循环单元(GRU)是更流行的长短期记忆(LSTM)网络的弟弟,也是循环神经网络&#x…...

【尚跑】2023宝鸡马拉松安全完赛,顺利PB达成
1、赛事背景 千年宝地,一马当先!10月15日7时30分,吉利银河2023宝鸡马拉松在宝鸡市行政中心广场鸣枪开跑。 不可忽视的是,这次赛事的卓越之处不仅在于规模和参与人数,还在于其精心的策划和细致入微的组织。为了确保每位…...
Mac nginx安装,通过源码安装教程
第一部分 安装参考网址: https://blog.csdn.net/a1004084857/article/details/128512612; 以上步骤执行完,进入找到sbin目录,查看下面是不是有nginx可执行文件,如果有在当前sbin下执行./nginx,就会发现NGINX已启动 第…...
TypeScript中的枚举是什么?
在TypeScript中,枚举(Enum)是一种用于定义一组有命名的常量值的数据类型。它们可以提供更具可读性和可维护性的代码。 枚举的作用是为一组相关的值提供一个易于理解和使用的命名空间。它们可以用于代表一系列可能的选项、状态或标志…...
进程并发-信号量经典例题-面包师问题
1 题目描述 面包师有很多面包和蛋糕,由N个销售人员销售。每个顾客进店后先取一个号,并且等着叫号。当一个销售人员空闲下来,就叫下一个号。试用信号量的P、V操作设计该问题的同步算法,给出所用共享变量(如果需要&…...
c语言练习12周(11~15)
编写double fun(int a[],int n)函数,计算返回评分数组a中,n个评委打分,去掉一个最高分去掉一个最低分之后的平均分 题干编写double fun(int a[],int n)函数,计算返回评分数组a中,n个评委打分,去掉一…...
Java 实现视频转音频功能
在实际开发中,我们经常需要处理各种多媒体文件。本文将介绍如何使用 Java 语言实现将视频文件转换为音频文件的功能。我们将使用 FFmpeg 工具来进行视频转换操作,并通过 Java 的 ProcessBuilder 实现调用系统命令执行 FFmpeg 的功能。 准备工作 首先,我们需要确保系统中已安…...

可以在Playgrounds或Xcode Command Line Tool开始学习Swift
一、用Playgrounds 1. App Store搜索并安装Swift Playgrounds 2. 打开Playgrounds,点击 文件-新建图书。然后就可以编程了,如下: 二、用Xcode 1. 安装Xcode 2. 打开Xcode,选择Creat New Project 3. 选择macOS 4. 选择Comman…...

IDC最新报告,增速减缓+AI增势,阿里云视频云中国市场第一
国际权威数据公司IDC发布 《中国视频云市场跟踪(2023 H1)》报告 自2018年至今,阿里云持续保持 中国视频云整体市场第一 整体市场占比达24.4% 01 第一之外,低谷之上 近期,国际权威数据公司IDC最新发布了《中国视频…...
常见状态码
欢迎大家到我的博客浏览。常见状态码 | YinKais Blog 常见状态码<!--more--> 1、200 200:服务器已经接收了请求,但处理还没有完成。 204:服务器已经成功处理了请求,但相应中没有任何返回内容。比如 DELETE 请求。 206&…...

Spring原理——基于xml配置文件创建IOC容器的过程
Spring框架的核心之一是IOC,那么我们是怎么创建出来的Bean呢? 作者进行了简单的总结,希望能对你有所帮助。 IOC的创建并不是通过new而是利用了java的反射机制,利用了newInstance方法进行的创建对象。 首先,我们先定义…...
CUDA initialization failure with error: 999
ubuntu20.04,安装tensorRT, 执行example里面的./sample_char_rnn程序,测试时候报了如标题的一个错误,居然如下两行代码这样解决了,这两行命令好像是重新加载nvidia内核模块,有点玄学: sudo rmmod nvidia_u…...
一些权限方面的思考
一些权限方面的思考 背景说明自定义注解解析自定义注解 背景 鉴权可以通过切面做抽取 说明 都是一些伪代码, 不能直接使用, 提供一种思路. 都是一些伪代码, 不能直接使用, 提供一种思路. 都是一些伪代码, 不能直接使用, 提供一种思路. 自定义注解 自定义注解: Permission …...

NX二次开发UF_CURVE_add_faces_ocf_data 函数介绍
文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan UF_CURVE_add_faces_ocf_data Defined in: uf_curve.h int UF_CURVE_add_faces_ocf_data(tag_t face_tag, UF_CURVE_ocf_data_p_t uf_offset_data ) overview 概述 Add a face col…...

MacM1(ARM)安装Protocol Buffers
MacM1(ARM)安装Protocol Buffers 本文目录 MacM1(ARM)安装Protocol Buffers3.21之前版本安装使用configure3.22之后版本安装使用cmake使用编译后的版本 protobuf下载地址:https://github.com/protocolbuffers/protobuf/releases 在运行./autogen.sh或./configure命…...

使用C++从0到1实现人工智能神经网络及实战案例
引言 既然是要用C来实现,那么我们自然而然的想到设计一个神经网络类来表示神经网络,这里我称之为Net类。由于这个类名太过普遍,很有可能跟其他人写的程序冲突,所以我的所有程序都包含在namespace liu中,由此不难想到我…...

React Router
一、简介 react router是一个构建基于react应用的路由管理库。允许你在程序中定义不同的路由和导航规则。以实现不同的url路径显示不同的组件。 二、相关技术 <Router><div><ul id "menu"><li><Link to "/home">Home<…...
https 是否真的安全,https攻击该如何防护,https可以被抓包吗?如何防止呢?
首先解释一下什么是HTPPS 简单来说, https 是 http ssl,对 http 通信内容进行加密,是HTTP的安全版,是使用TLS/SSL加密的HTTP协议 Https的作用: 内容加密 建立一个信息安全通道,来保证数据传输的安全&am…...
C++中声明友元
C中声明友元 不能从外部访问类的私有数据成员和方法,但这条规则不适用于友元类和友元函数。要声明友元 类或友元函数,可使用关键字 friend,如以下示例程序所示: 使用关键字 friend 让外部函数 DisplayAge( )能够访问私有数据成员…...

【GPT-3.5】通过python调用ChatGPT API与ChatGPT对话交流
文章目录 一、引言二、AIGC简介三、OpenAI介绍四、GPT-3.5介绍五、获得OpenAI API Key六、调用ChatGPT API实现与ChatGPT对话七、参考链接 一、引言 ChatGPT 的火爆,成功带火了AIGC,让它进入大众的视野。 ChatGPT 和Whisper API 开发者现在可以通过API将…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

深入浅出深度学习基础:从感知机到全连接神经网络的核心原理与应用
文章目录 前言一、感知机 (Perceptron)1.1 基础介绍1.1.1 感知机是什么?1.1.2 感知机的工作原理 1.2 感知机的简单应用:基本逻辑门1.2.1 逻辑与 (Logic AND)1.2.2 逻辑或 (Logic OR)1.2.3 逻辑与非 (Logic NAND) 1.3 感知机的实现1.3.1 简单实现 (基于阈…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...