经典循环神经网络(一)RNN及其在歌词数据集上的应用

经典循环神经网络(一)RNN及其在歌词数据集上的应用

1 RNN概述

在深度学习兴起之前,NLP领域一直是统计模型的天下,例如词对齐算法GIZA++,统计机器翻译开源框架MOSES等等。在语言模型方向,n-gram是当时最为流行的语言模型方法。n-gram的问题是其捕捉句子中长期依赖的能力非常有限。另外n-gram算法过于简单,其是否有能力取得令人信服的效果的确要打一个大的问号。

循环神经网络是为更好地处理时序信息而设计的。它引入状态变量来存储过去的信息,并⽤其与当前的输入共同决定当前的输出
循环神经网络常用于处理序列数据,如⼀段文字或声音、购物或观影的顺序,甚⾄是图像中的⼀行或⼀列像素。因此,循环神经网络有着极为广泛的实际应用,如语言模型、文本分类、机器翻译、语音识别、图像分析等。

1.1 RNN理解

下面图像,来自台大李宏毅老师课件

1.1.1 RNN的引入

对于2句话,都有Taipei这个词,但是一个是目的地一个是出发地

如果神经网络有记忆力,能够根据上下文对同样的input词汇产生不同的输出,我们就能解决这个问题

像下面两句话,同样输入Taipei,一个输出“目的地”,一个输出“出发地”

arrive Taipei on November 2leave Taipei on November 2

在这里插入图片描述

1.1.2 结合例子理解

在这里插入图片描述

首先,网络接收第一个单词(arrive)的输入,经过网络得到一个输出,并保存隐藏层的输出。

然后接收第二个单词(Taipei)的输入,经过网络得到输出,……,用同样的网络结构不断重复这个行为

所以当两句不同的话输入的时候,一个Taipei前面是leave,一个是arrive,而这两个的vector是不一样的,所以存在memory中的值不同,这样就会得到不同的输出。

在这里插入图片描述

更一般地,网络可以不止有一个隐藏层,而是有许多个隐藏层,每个单词输入的时候,各个隐藏层都会考虑之前存在memory中的值。

在这里插入图片描述

1.2 RNN网络架构

在这里插入图片描述

上图展示了循环神经网络在三个相邻时间步的计算逻辑。

其中:
隐状态中 X t W x h + H t − 1 W h h 的计算,相当于 X t 和 H t − 1 的拼接与 W x h 和 W h h 的拼接的矩阵乘法。 隐状态中 X_tW_{xh} + H_{t-1}W_{hh}的计算,相当于X_t和H_{t-1}的拼接与W_{xh}和W_{hh}的拼接的矩阵乘法。 隐状态中XtWxh+Ht1Whh的计算,相当于XtHt1的拼接与WxhWhh的拼接的矩阵乘法。
这里使用代码验证一下:

在这里插入图片描述

RNN的实现非常简单,如下:

'''
定义了如何在【⼀个时间步内】计算隐状态和输出。循环神经⽹络模型通过inputs最外层的维度实现循环,以便逐时间步更新⼩批量数据的隐状态H。
此外,这⾥使⽤tanh函数作为激活函数。
'''
def rnn(inputs, state, params):""":param inputs: inputs的形状:(时间步数量,批量⼤⼩,词表⼤⼩):param state:  隐状态:param params: 初始化的权重及偏置参数:return:"""W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []# X的形状:(批量⼤⼩,词表⼤⼩)for X in inputs:# 隐藏层激活函数是tanh。H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)  # 隐变量state# 输出层没有激活函数,就是隐变量的线性变换Y = torch.mm(H, W_hq) + b_qoutputs.append(Y)return torch.cat(outputs, dim=0), (H,)

2 RNN在歌词数据集上的应用

2.1 歌词数据集的预处理

歌词数据集下载地址:https://gitee.com/inkiinki/data20201205/blob/master/Data20201205/jaychou_lyrics.txt.zip

2.1.1 读取歌词数据集

这是⼀个相当小的语料库,但足够我们测试,现实中的文档集合可能会包含数十亿个单词。

import collections
import random
import torch
import re# 读取歌词数据集
def read_jaychou(file_loc = '../data/jaychou_lyrics.txt'):with open(file=file_loc, mode='r', encoding='utf8') as f:lines = f.readlines()return lines
lines = read_jaychou()
print(f'⽂本总⾏数: {len(lines)}')
print(lines[0])
print(lines[1])
⽂本总⾏数: 5819
想要有直升机
想要和你飞到宇宙去

2.1.2 词元化

tokenize函数将文本行列表(lines)作为输入,列表中的每个元素是⼀个文本序列(如⼀条文本

行)。每个文本序列又被拆分成⼀个词元列表,词元(token)是文本的基本单位。最后,返回⼀个由词元列

表组成的列表。这里为了方便,一个中文字为一个词元。

def tokenize(lines):tokens = []for line in lines:line = line.replace("\n", " ").replace("\r", " ")fileters = ['!', '"', '#', '$', '%', '&', '\(', '\)', '\*', '\+', ',', '-', '\.', '/', ':', ';', '<', '=', '>','\?', '@', '\[', '\\', '\]', '^', '_', '`', '\{', '\|', '\}', '~', '\t', '\n', '\x97', '\x96', '”','“', ]line = re.sub("|".join(fileters), "", line)tokens.append(list(line))return tokens

在这里插入图片描述

2.1.3 词表

  • 我们先将训练集中的所有文档合并在⼀起,对它们的唯⼀词元进行统计,得到的统计结果称之为语料(corpus)。

  • 然后根据每个唯⼀词元的出现频率,为其分配⼀个数字索引。很少出现的词元通常被移除,这可以降低复杂性。

  • 另外,语料库中不存在或已删除的任何词元都将映射到⼀个特定的未知词元“”。

  • 我们可以选择增加⼀个列表,用于保存那些被保留的词元,例如:填充词元(“”);序列开始词元(“”);序列结束词元(“”)。

def count_corpus(tokens):"""统计词元的频率"""# 这⾥的tokens是1D列表或2D列表if len(tokens) == 0 or isinstance(tokens[0], list):# 将词元列表展平成⼀个列表tokens = [token for line in tokens for token in line]return collections.Counter(tokens)class Vocab:"""⽂本词表类"""def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):if tokens is None:tokens = []if reserved_tokens is None:reserved_tokens = []# 按出现频率排序counter = count_corpus(tokens)self._token_freqs = sorted(counter.items(), key=lambda x: x[1], reverse=True)# 未知词元的索引为0self.idx_to_token = ['<unk>'] + reserved_tokensself.token_to_idx = {token: idx for idx, token in enumerate(self.idx_to_token)}for token, freq in self._token_freqs:if freq < min_freq:breakif token not in self.token_to_idx:self.idx_to_token.append(token)self.token_to_idx[token] = len(self.idx_to_token) - 1self.vocab_size = len(self.idx_to_token)def __len__(self):return len(self.idx_to_token)def __getitem__(self, tokens):if not isinstance(tokens, (list, tuple)):return self.token_to_idx.get(tokens, self.unk)return [self.__getitem__(token) for token in tokens]def to_tokens(self, indices):if not isinstance(indices, (list, tuple)):return self.idx_to_token[indices]return [self.idx_to_token[index] for index in indices]@propertydef unk(self): # 未知词元的索引为0return 0@propertydef token_freqs(self):return self._token_freqs
vocab = Vocab(tokens)print(vocab.vocab_size)
print(vocab.idx_to_token[:10])
print(list(vocab.token_to_idx.items())[:10])

在这里插入图片描述

最后,将上面3步进行封装

def load_corpus_jaychou(max_tokens=-1):"""返回歌词数据集的词元索引列表和词表"""lines = read_jaychou()tokens = tokenize(lines)vocab = Vocab(tokens)# 将所有文本行展平到一个列表中corpus = [vocab[token] for line in tokens for token in line]if max_tokens > 0:corpus = corpus[:max_tokens]return corpus, vocab

在这里插入图片描述

2.1.4 读取序列数据

随机采样

'''
随机采样:在随机采样中,每个样本都是在原始的⻓序列上任意捕获的⼦序列。1、在迭代过程中,来⾃两个相邻的、随机的、⼩批量中的⼦序列不⼀定在原始序列上相邻。
例如:
[15., 16., 17., 18., 19.] 和  [5., 6., 7., 8., 9.]2、对于语⾔建模,⽬标是基于到⽬前为⽌我们看到的词元来预测下⼀个词元,因此标签label是移位了⼀个词元的原始序列。
例如:
[15., 16., 17., 18., 19.]对应的标签label是[16., 17., 18., 19., 20.]下⾯的代码每次可以从数据中随机⽣成⼀个⼩批量。
参数batch_size指定了每个⼩批量中⼦序列样本的数⽬
参数num_steps是每个⼦序列中预定义的时间步数
'''
def seq_data_iter_random(corpus, batch_size, num_steps):"""使⽤随机抽样⽣成⼀个⼩批量⼦序列"""# 从随机偏移量开始对序列进⾏分区,随机范围包括num_steps-1corpus = corpus[random.randint(0, num_steps - 1):]# 减去1,是因为我们需要考虑标签num_subseqs = (len(corpus) - 1) // num_steps# ⻓度为num_steps的⼦序列的起始索引initial_indices = list(range(0, num_subseqs * num_steps, num_steps))# 在随机抽样的迭代过程中,# 来自两个相邻的、随机的、⼩批量中的⼦序列不⼀定在原始序列上相邻random.shuffle(initial_indices)def data(pos):# 返回从pos位置开始的⻓度为num_steps的序列return corpus[pos: pos + num_steps]num_batches = num_subseqs // batch_sizefor i in range(0, batch_size * num_batches, batch_size):# 在这⾥,initial_indices包含⼦序列的随机起始索引initial_indices_per_batch = initial_indices[i: i + batch_size]X = [data(j) for j in initial_indices_per_batch]Y = [data(j + 1) for j in initial_indices_per_batch]yield torch.tensor(X), torch.tensor(Y)

在这里插入图片描述

顺序分区

'''
顺序分区:在迭代过程中,除了对原始序列可以随机抽样外,我们还可以保证
两个相邻的⼩批量中的⼦序列在原始序列上也是相邻的。例如:
[ 0.,  1.,  2.,  3.,  4.] 和 [ 5.,  6.,  7.,  8.,  9.]
'''
def seq_data_iter_sequential(corpus, batch_size, num_steps):"""使用顺序分区生成⼀个小批量子序列"""# 从随机偏移量开始划分序列offset = random.randint(0, num_steps)num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_sizeXs = torch.tensor(corpus[offset: offset + num_tokens])Ys = torch.tensor(corpus[offset + 1: offset + 1 + num_tokens])Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)num_batches = Xs.shape[1] // num_stepsfor i in range(0, num_steps * num_batches, num_steps):X = Xs[:, i: i + num_steps]Y = Ys[:, i: i + num_steps]yield X, Y

在这里插入图片描述

将上⾯的两个采样函数包装到⼀个类中,以便稍后可以将其用作数据迭代器

class SeqDataLoader:"""加载序列数据的迭代器"""def __init__(self, batch_size, num_steps, use_random_iter, max_tokens):if use_random_iter:self.data_iter_fn = seq_data_iter_randomelse:self.data_iter_fn = seq_data_iter_sequentialself.corpus, self.vocab = load_corpus_jaychou(max_tokens)self.batch_size, self.num_steps = batch_size, num_stepsdef __iter__(self):return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)

将预处理全部进行封装

def load_data_jaychou(batch_size, num_steps, use_random_iter=False, max_tokens=10000):"""返回歌词数据集的迭代器和词表"""data_iter = SeqDataLoader(batch_size, num_steps, use_random_iter, max_tokens)return data_iter, data_iter.vocab

2.2 RNN的简单实现

2.2.1 加载歌词数据集

import torch
from torch import nn
import math
from _0_Vocab import load_data_jaychou
batch_size, num_steps = 32, 35
train_iter, vocab = load_data_jaychou(batch_size, num_steps)

2.2.2 封装通用的RNN模型

import torch.nn as nn
import torch
from torch.nn import functional as Fclass RNNModel(nn.Module):"""循环神经网络模型"""def __init__(self, rnn_layer, vocab_size, **kwargs):super(RNNModel, self).__init__(**kwargs)self.rnn = rnn_layerself.vocab_size = vocab_sizeself.num_hiddens = self.rnn.hidden_size# 如果RNN是双向的,num_directions应该是2,否则应该是1if not self.rnn.bidirectional:self.num_directions = 1self.linear = nn.Linear(self.num_hiddens, self.vocab_size)else:self.num_directions = 2self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)def forward(self, inputs, state):# inputs的shape为(batch_size,num_steps)# one-hot编码后,shape变为 (num_steps, batch_size, 词表大小)X = F.one_hot(inputs.T.long(), self.vocab_size)X = X.to(torch.float32)# Y的shape(num_steps, batch_size, 隐藏单元数)# state的shape(num_layers, batch_size, 隐藏单元数)Y, state = self.rnn(X, state)# 全连接层# 首先,将Y的形状改为(时间步数*批量⼤⼩, 隐藏单元数)# 它的输出形状是(时间步数*批量⼤⼩,词表大小)。output = self.linear(Y.reshape((-1, Y.shape[-1])))return output, statedef begin_state(self, device, batch_size=1):if not isinstance(self.rnn, nn.LSTM):# nn.GRU以张量作为隐状态return torch.zeros((self.num_directions * self.rnn.num_layers, batch_size, self.num_hiddens),device=device)else:# nn.LSTM以元组作为隐状态return (torch.zeros((self.num_directions * self.rnn.num_layers,batch_size, self.num_hiddens), device=device),torch.zeros((self.num_directions * self.rnn.num_layers,batch_size, self.num_hiddens), device=device))if __name__ == '__main__':# 1、构造⼀个具有256个隐藏单元的单隐藏层的循环神经⽹络层rnn_layernum_hiddens = 256rnn_layer = nn.RNN(input_size=28, hidden_size=num_hiddens,num_layers=1,bidirectional=True)# 2、构建rnn模型,词表大小为28net = RNNModel(rnn_layer, vocab_size=28)print(net)# 3、构建测试数据num_steps = 10batch_size = 5x = torch.rand(size=(batch_size, num_steps))state = net.begin_state(batch_size=x.shape[0], device='cpu')print(net(x, state)[0].shape)print(net(x, state)[1].shape)
RNNModel((rnn): RNN(28, 256, bidirectional=True)(linear): Linear(in_features=512, out_features=28, bias=True)
)
torch.Size([50, 28])
torch.Size([2, 5, 256])

2.2.3 定义rnn模型

num_hiddens = 256# 构造⼀个具有256个隐藏单元的单隐藏层的循环神经网络层rnn_layer
rnn_layer = nn.RNN(len(vocab), num_hiddens)
# 使用张量来初始化隐状态,
# 它的形状是(隐藏层数,批量大小,隐藏单元数)
state = torch.zeros((1, batch_size, num_hiddens))
print(state.shape)'''
通过⼀个隐状态和⼀个输⼊,我们就可以⽤更新后的隐状态计算输出。需要强调的是,rnn_layer的“输出”(Y)不涉及输出层的计算:它是指每个时间步的隐状态,这些隐状态可以⽤作后续输出层的输⼊。
'''
X = torch.rand(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)
Y.shape, state_new.shape
torch.Size([1, 32, 256])(torch.Size([35, 32, 256]), torch.Size([1, 32, 256]))
from _0_RNNModel import RNNModeldef try_gpu(i=0):if torch.cuda.device_count() >= i + 1:return torch.device(f'cuda:{i}')return torch.device('cpu')device = try_gpu()net = RNNModel(rnn_layer, vocab_size=vocab.vocab_size)
net = net.to(device)

在训练前,先预测一波

'''
首先,net(get_input(), state)目的就是用【前一次预测得到的输出】和【前一次时刻的隐藏状态】 得到 【当前时刻的输出和当前时刻的隐藏状态】。然后第一次for循环,意在得到一个比较准确的,可供进行prefix这句话以后的字的预测的一个初始时刻隐藏状态,所以使用了完整的一句话去得到。
比如说 `你好不好` 是一句话,传入作为 `prefix` ,然后用 `你` 初始化 `outputs` 作为第0时刻的输出,首先预热整句话,从第1时刻开始,循环 `好不好` 来获得初始隐藏状态 `state` ,这期间连续的更新state直至最后一个 `好` 字;第1时刻默认用0时刻输出 `你` 和0时刻默认的 `state` 输入模型得到预测结果和1时刻的隐藏状态 `state` ,不用接收模型使用 `你` 预测得到的结果,因为标准答案 `好` 可以直接作为下一时刻的输入。剩下的循环以此类推。所以第二个for循环,才是正式做预测,预测 `你好不好` 这句话的后面 `num_preds` 个词是什么,所以初始时刻就传入 `你好不好` 的最后一个词 `好` 和之前预热的 `state` 隐藏状态,得到第4时刻的预测结果 `y` 和当前时刻隐藏状态。
'''
def predict_ch(prefix, num_preds, net, vocab, device):"""在prefix后⽣成新字符"""state = net.begin_state(batch_size=1, device=device)outputs = [ vocab[str(prefix[0])] ]get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))for y in prefix[1:]: # 预热期_, state = net(get_input(), state)outputs.append(vocab[y])for _ in range(num_preds): # 预测num_preds步y, state = net(get_input(), state)outputs.append(int(y.argmax(dim=1).reshape(1)))return ''.join([vocab.idx_to_token[i] for i in outputs])# 很明显,没有训练,模型根本不能输出好的结果
predict_ch('分开', 10, net, vocab, device)
'分开万枯幅枯幅掉氧枯氧枯'

2.2.4 模型的训练

梯度裁剪

对于长度为T的序列,我们在迭代中计算这T个时间步上的梯度,将会在反向传播过程中产生长度为O(T)的

矩阵乘法链。当T较大时,它可能导致数值不稳定,例如可能导致梯度爆炸或梯度消失。因此,

循环神经网络模型往往需要额外的方式来支持稳定训练。梯度裁剪提供了⼀个快速修复梯度爆炸的方法。

在这里插入图片描述

def grad_clipping(net, theta):"""裁剪梯度"""if isinstance(net, nn.Module):params = [p for p in net.parameters() if p.requires_grad]else:params = net.params# ||g||norm = torch.sqrt(sum(torch.sum((p.grad ** 2)) for p in params))if norm > theta:for param in params:param.grad[:] *= theta / norm

评价指标——困惑度

我们可以通过⼀个序列中所有的n个词元的交叉熵损失的平均值来衡量。

由于历史原因,自然语言处理的科学家更喜欢使用⼀个叫做困惑度(perplexity)的量。其实,就是交叉熵损失的平均值的指数。

• 在最好的情况下,模型总是完美地估计标签词元的概率为1。在这种情况下,模型的困惑度为1。

• 在最坏的情况下,模型总是预测标签词元的概率为0。在这种情况下,困惑度是正无穷大。

from AccumulatorClass import Accumulator
from AnimatorClass import Animator
from TimerClass import Timer'''
循环神经⽹络模型的训练函数既⽀持从零开始实现,也可以使⽤⾼级API来实现。1. 序列数据的不同采样⽅法(随机采样和顺序分区)将导致隐状态初始化的差异。
2. 在更新模型参数之前裁剪梯度。这样的操作的⽬的是:即使训练过程中某个点上发⽣了梯度爆炸,也能保证模型不会发散。
3. ⽤困惑度来评价模型。
'''
def train_epoch(net, train_iter, loss, updater, device, use_random_iter):"""训练网络⼀个迭代周期"""state, timer = None, Timer()metric = Accumulator(2) # 训练损失之和,词元数量for X, Y in train_iter:if state is None or use_random_iter:# 在第⼀次迭代或使用随机抽样时初始化statestate = net.begin_state(batch_size=X.shape[0], device=device)else:if isinstance(net, nn.Module) and not isinstance(state, tuple):# state对于nn.GRU是个张量state.detach_()else:# state对于nn.LSTM或对于我们从零开始实现的模型是个张量for s in state:s.detach_()y = Y.T.reshape(-1)X, y = X.to(device), y.to(device)y_hat, state = net(X, state)l = loss(y_hat, y.long()).mean()if isinstance(updater, torch.optim.Optimizer):updater.zero_grad()l.backward()grad_clipping(net, 1)  # 裁剪梯度后,再进行梯度更新updater.step()else:# 在训练函数中,如果没有用torch提供的优化器,就先不进行梯度清零# 自定义的updater。在更新参数后,也清零了。l.backward()grad_clipping(net, 1)  # 裁剪梯度后,再进行梯度更新# 因为已经调⽤了mean函数updater(batch_size=1)metric.add(l * y.numel(), y.numel())return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()def sgd(params, lr, batch_size):with torch.no_grad():for param in params:param -= lr * param.grad / batch_sizeparam.grad.zero_()def train(net, train_iter, vocab, lr, num_epochs, device,use_random_iter=False):"""训练模型"""loss = nn.CrossEntropyLoss()animator = Animator(xlabel='epoch', ylabel='perplexity',legend=['train'], xlim=[10, num_epochs])# 初始化if isinstance(net, nn.Module):updater = torch.optim.SGD(net.parameters(), lr)else:updater = lambda batch_size: sgd(net.params, lr, batch_size)predict = lambda prefix: predict_ch(prefix, 50, net, vocab, device)# 训练和预测for epoch in range(num_epochs):ppl, speed = train_epoch(net, train_iter, loss, updater, device, use_random_iter)if (epoch + 1) % 10 == 0:print(predict('分开'))animator.add(epoch + 1, [ppl])print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')print(predict('分开'))print(predict('不分开'))
num_epochs, lr = 1000, 1
train(net, train_iter, vocab, lr, num_epochs, device)

在这里插入图片描述

2.3 RNN的手动实现

2.3.1 加载歌词数据集

import torch
from torch import nn
import math
from _0_Vocab import load_data_jaychou
from torch.nn import functional as F
batch_size, num_steps = 32, 35
train_iter, vocab = load_data_jaychou(batch_size, num_steps)

2.3.2 初始化模型参数

'''
隐藏单元数num_hiddens是⼀个可调的超参数。注意:
当训练语⾔模型时,输⼊和输出来⾃相同的词表。因此,它们具有相同的维度,即词表的⼤⼩。
'''
def get_params(vocab_size, num_hiddens, device):num_inputs = num_outputs = vocab_sizedef normal(shape):# 随机生成标准正态分布的一组数据return torch.randn(size=shape, device=device) * 0.01# 隐藏层参数W_xh = normal((num_inputs, num_hiddens))W_hh = normal((num_hiddens, num_hiddens))b_h = torch.zeros(num_hiddens, device=device)  # 随机初始化为0# 输出层参数W_hq = normal((num_hiddens, num_outputs))b_q = torch.zeros(num_outputs, device=device)  # 随机初始化为0# 附加梯度params = [W_xh, W_hh, b_h, W_hq, b_q]for param in params:param.requires_grad_(True)return params

2.3.3 创建rnn模型

'''
init_rnn_state函数在初始化时返回隐状态。这个函数的返回是⼀个张量,张量全⽤0填充,形状为(批量⼤⼩,隐藏单元数)
'''
def init_rnn_state(batch_size, num_hiddens, device):return (torch.zeros((batch_size, num_hiddens), device=device), )'''
定义了如何在【⼀个时间步内】计算隐状态和输出。循环神经⽹络模型通过inputs最外层的维度实现循环,以便逐时间步更新⼩批量数据的隐状态H。
此外,这⾥使⽤tanh函数作为激活函数。
'''
def rnn(inputs, state, params):""":param inputs: inputs的形状:(时间步数量,批量⼤⼩,词表⼤⼩):param state:  隐状态:param params: 初始化的权重及偏置参数:return:"""W_xh, W_hh, b_h, W_hq, b_q = paramsH, = stateoutputs = []# X的形状:(批量⼤⼩,词表⼤⼩)for X in inputs:# 隐藏层激活函数是tanh。H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)  # 隐变量state# 输出层没有激活函数,就是隐变量的线性变换Y = torch.mm(H, W_hq) + b_qoutputs.append(Y)return torch.cat(outputs, dim=0), (H,)

封装通用函数

class RNNModelScratch:"""从零开始实现的循环神经网络模型"""def __init__(self, vocab_size, num_hiddens, device, get_params, init_state, forward_fn):self.vocab_size, self.num_hiddens = vocab_size, num_hiddensself.params = get_params(vocab_size, num_hiddens, device)self.init_state, self.forward_fn = init_state, forward_fndef __call__(self, X, state):'''如果第0维是批量大小,那么假如每次取一个样本 即(x1 x2 x3...)这样根据rnn计算规则,先算预测的x2 然后后才能算x3, 这样只能一个个算,不便于程序的并行执行。如果第0纬换成了时间序列,那么每拿一个出来,得到的就是(第一个x1 第二个x1...)这些是可以并行计算的,转置一下的好处就是把能够并行计算的东西 使得他们在空间存储上尽量相邻 提高计算效率。'''X = F.one_hot(X.T, self.vocab_size).type(torch.float32)return self.forward_fn(X, state, self.params)def begin_state(self, batch_size, device):return self.init_state(batch_size, self.num_hiddens, device)
num_hiddens = 512
net = RNNModelScratch(len(vocab), num_hiddens, try_gpu(), get_params,init_rnn_state, rnn)

2.3.4 预测

预测函数和2.2一致

# 鉴于我们还没有训练⽹络,它会⽣成荒谬的预测结果。
predict_ch('分开', 10, net, vocab, try_gpu())

2.3.5 模型的训练

训练函数和2.2一致

# 因为我们在数据集中只使⽤了10000个词元,所以模型需要更多的迭代周期来更好地收敛。
num_epochs, lr = 1000, 1
train(net, train_iter, vocab, lr, num_epochs, try_gpu())

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/97653.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机竞赛 题目:基于卷积神经网络的手写字符识别 - 深度学习

文章目录 0 前言1 简介2 LeNet-5 模型的介绍2.1 结构解析2.2 C1层2.3 S2层S2层和C3层连接 2.4 F6与C5层 3 写数字识别算法模型的构建3.1 输入层设计3.2 激活函数的选取3.3 卷积层设计3.4 降采样层3.5 输出层设计 4 网络模型的总体结构5 部分实现代码6 在线手写识别7 最后 0 前言…

Sql和NoSql

Sql和NoSql SQL使用&#xff1a;如果有大量的更新操作&#xff0c;一定要使用事务&#xff0c;效率高。大数据情况下&#xff0c;要对表字段建索引。比nosql好的地方&#xff1a;有事务&#xff0c;能回滚。 SQL遇到的瓶颈&#xff1a;水平扩展 场景&#xff1a;不同设备&am…

全面解析UDP协议(特点、报文格式、UDP和TCP的区别)

了解UDP&#xff08;User Datagram Protocol&#xff09; UDP是无连接通信协议&#xff0c;即在数据传输时&#xff0c;数据的发送端和接收端不建立逻辑连接。简单来说&#xff0c;当一台计算机向另外一台计算机发送数据时&#xff0c;发送端不会确认接收端是否存在&#xff0…

【洛谷】P1114 “非常男女”计划

思路&#xff1a;思路和上一篇一模一样哒~&#xff08;这里就不多解释啦&#xff09; ACcode: #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N 2e510; int n,a[N],f[N]; int main() { ios::sync_with_st…

[洛谷]P1440 求m区间内的最小值(线段树)

板子题~ ACcode: #include<bits/stdc.h> using namespace std; const int N 2e610; typedef long long ll; #define int long long struct node{int l,r;int minv; }tr[N*4]; int n,m,w[N]; void pushup(int u){tr[u].minvmin(tr[u<<1].minv,tr[u<<1|1].m…

【uniapp】小程序开发6:自定义状态栏

一、自定义状态栏 可以设置某个页面的状态栏自定义或者全局状态栏自定义。 这里以首页状态栏为例。 1&#xff09;pages.json 中配置"navigationStyle": "custom"&#xff0c;代码如下&#xff1a; {"pages": [ {"path": "pa…

如何使用Git和GitHub进行版本控制

如何使用Git和GitHub进行版本控制 版本控制是软件开发过程中的重要组成部分&#xff0c;它允许开发者跟踪和管理代码的变化&#xff0c;以确保团队协作顺畅&#xff0c;并帮助在需要时回溯到以前的代码状态。Git和GitHub是最流行的版本控制工具之一&#xff0c;本文将介绍如何…

Bridge Champ助力我国桥牌阔步亚运, Web3游戏为传统项目注入创新活力

本届杭州亚运会,中国桥牌队表现杰出,共斩获1金1银1铜佳绩,其中女子团体夺得冠军,混合团体获得亚军。这充分展现了我国桥牌的实力,也彰显了桥牌作为亚运会体育竞技项目的影响力。与此同时,Web3游戏Bridge Champ为传统桥牌项目带来创新模式,将有望推动桥牌运动在亚运舞台上焕发新…

SpringMVC的请求映射:路由请求的精准导航

SpringMVC的请求映射&#xff1a;路由请求的精准导航 SpringMVC是一个用于构建Web应用程序的强大框架&#xff0c;它提供了众多的特性和组件来简化开发过程。其中&#xff0c;请求映射是SpringMVC中的一个关键特性&#xff0c;用于将HTTP请求映射到具体的处理方法。本文将深入…

【Python】基于OpenCV人脸追踪、手势识别控制的求生之路FPS游戏操作

【Python】基于OpenCV人脸追踪、手势识别控制的求生之路FPS游戏操作 文章目录 手势识别人脸追踪键盘控制整体代码附录&#xff1a;列表的赋值类型和py打包列表赋值BUG复现代码改进优化总结 py打包 视频&#xff1a; 基于OpenCV人脸追踪、手势识别控制的求实之路FPS游戏操作 手…

Mysql 分布式序列算法

接上文 Mysql分库分表 1.分布式序列简介 在分布式系统下&#xff0c;怎么保证ID的生成满足以上需求&#xff1f; ShardingJDBC支持以上两种算法自动生成ID。这里&#xff0c;使用ShardingJDBC让主键ID以雪花算法进行生成&#xff0c;首先配置数据库&#xff0c;因为默认的注…

BootstrapBlazor企业级组件库:前端开发的革新之路

作为一名Web开发人员&#xff0c;开发前端我们一般都是使用JavaScript&#xff0c;而Blazor就是微软推出的基于.Net平台交互式客户Web UI 框架&#xff0c;可以使用C#替代JavaScript&#xff0c;减少我们的技术栈、降低学习前端的成本。 而采用Blazor开发&#xff0c;少不了需…

React核心原理与实际开发

学习目标 React是啥&#xff1f; 官方定义&#xff1a;将前端请求获取到的数据渲染为HTML视图的JavaScript库。 一、React入门 1、React项目创建 直接创建react&#xff0c;使用初始化会创建package.json npm init -y再安装 2、React基本使用 使用纯JS创建ReactDOM&#…

Flink学习笔记(二):Flink内存模型

文章目录 1、配置总内存2、JobManager 内存模型3、TaskManager 内存模型4、图形化展示5、实际案例计算内存分配 1、配置总内存 Flink JVM 进程的进程总内存&#xff08;Total Process Memory&#xff09;包含了由 Flink 应用使用的内存&#xff08;Flink 总内存&#xff09;以…

iTunes更新iOS17出现发生未知错误4000的原因和解决方案

有不少人使用iTunes更新iOS 17时出现「无法更新iPhone发生未知的错误4000」的错误提示&#xff0c;不仅不知道iTunes升级失败的原因&#xff0c;也无从解决iPhone无法更新4000的问题。 小编今天就分享iPhone更新iOS系统出现4000错误提示的原因和对应的解决方案。 为什么iPhone…

MySQL Cluster 简介

文章目录 1.简介2.组成参考文献 1.简介 MySQL Cluster 是官方推出的基于 NDB&#xff08;Network DataBase&#xff09;存储引擎的高可用和可伸缩的分布式数据库系统。 以下是 MySQL NDB Cluster 的主要特点和能力&#xff1a; 高可用&#xff1a;MySQL Cluster 具有内置的高…

clickonce 程序发布到ftp在使用cnd 加速https 支持下载,会不会报错

ClickOnce 是一种用于发布和部署.NET应用程序的技术&#xff0c;通常用于本地部署或通过网络分发应用程序。将 ClickOnce 程序发布到 FTP 服务器并使用 CDN&#xff08;内容分发网络&#xff09;进行加速是可能的&#xff0c;但要确保配置正确以避免出现错误。 在使用 CDN 加速…

AR人脸美颜特效解决方案,打造全方位美颜美妆新时代

随着科技的不断发展&#xff0c;人们对美的追求也日益增长。在这个看脸的时代&#xff0c;一张完美的脸庞无疑是吸引眼球的最佳法宝。为了满足广大用户对美颜的需求&#xff0c;美摄AR人脸美颜特效解决方案应运而生&#xff0c;凭借其强大的功能&#xff0c;为用户带来全新的美…

Python大数据之PySpark(七)SparkCore案例

文章目录 SparkCore案例PySpark实现SouGou统计分析 总结后记 SparkCore案例 PySpark实现SouGou统计分析 jieba分词&#xff1a; pip install jieba 从哪里下载pypi 三种分词模式 精确模式&#xff0c;试图将句子最精确地切开&#xff0c;适合文本分析&#xff1b;默认的方…

洗地机怎么选?2023年洗地机推荐

洗地机结合洗地、拖地、扫地的功能&#xff0c;在日常生活中备受关注&#xff0c;他能帮助我们更加节省时间和节省体力&#xff0c;但是面对参差不齐的洗地机市场如何选到适合自己的呢&#xff0c;下文整理了几款非常值得入手的性价比型号&#xff0c;供大家选择参考。 一、CE…