当前位置: 首页 > news >正文

经典循环神经网络(一)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())

在这里插入图片描述

相关文章:

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

经典循环神经网络(一)RNN及其在歌词数据集上的应用 1 RNN概述 在深度学习兴起之前&#xff0c;NLP领域一直是统计模型的天下&#xff0c;例如词对齐算法GIZA&#xff0c;统计机器翻译开源框架MOSES等等。在语言模型方向&#xff0c;n-gram是当时最为流行的语言模型方法。n-gr…...

docker+mysql+flask+redis+vue3+uwsgi+docker部署

首先拉取mysql的镜像&#xff0c;这里用的mysql5.7.6 docker pull mysql:5.7.6 镜像拉取完成后启动&#xff1a; docker run --name my-mysql -d -p 3306:3306 -v /usr/local/my-mysql/conf:/etc/mysql/conf.d -v /usr/local/my-mysql/data:/var/lib/mysql -e MYSQL_ROOT_PA…...

Spring boot接收zip包并获取其中excel文件的方法

1、问题 工作中遇到一个需求&#xff0c;接收一个zip包&#xff0c;读取其中的excel文件并处理&#xff0c;减少用户多次选择目录和文件的痛点&#xff0c;该zip包包含多级目录 2、依赖 需要用到apache的Workbook类来操作Excel&#xff0c;引入以下依赖 <dependency>&l…...

Ubuntu镜像源cn.arichinve.ubuntu.com不可用原因分析和解决

文章目录 Ubuntu查看系统版本Ubuntu更新系统不能更新Ubuntu查看APT更新源配置cn.archive.ubuntu.com已经自动跳转到清华镜像站Ubuntu变更镜像源地址备份原文件批量在VIM中变更 Ubuntu国内镜像站推荐推荐阅读 今天想要在Ubuntu环境下搭建一个测试环境&#xff0c;进入Ubuntu系统…...

Java基础面试,String,StringBuffer,StringBuilder区别以及使用场景

简单的几句 String是final修饰的&#xff0c;不可变&#xff0c;每次操作都会产生新的对象。StringBuffer和StringBuilder都是在原对象上进行操作StringBuffer是线程安全的&#xff0c;StringBuilder是线程不安全的。StringBuffer方法是被synchronized修饰的 所以在性能方面大…...

基于SpringBoot的高校学科竞赛平台

目录 前言 一、技术栈 二、系统功能介绍 竞赛题库管理 竞赛信息管理 晋级名单管理 往年成绩管理 参赛申请管理 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步…...

excel如何让线条消失,直接设置网格即可,碰到不方便的地方优先百度,再采取蛮干

怎么将excel表格中的隐形线条去掉...

抖音短视频seo矩阵系统源代码开发系统架构及功能解析

短视频seo源码&#xff0c;短视频seo矩阵系统底层框架上支持了从ai视频混剪&#xff0c;视频批量原创产出&#xff0c;云存储批量视频制作&#xff0c;账号矩阵&#xff0c;视频一键分发&#xff0c;站内实现关键词、短视频批量搜索排名&#xff0c;数据统计分类多功能细节深度…...

在pycharm中弹出图后,需要关闭才会显示Process finished with exit code 0

在pycharm中弹出图后&#xff0c;需要关闭才会显示Process finished with exit code 0 在PyCharm中&#xff0c;当你运行一个Python程序并弹出一个图形窗口时&#xff0c;程序会等到图形窗口关闭后才会显示 “Process finished with exit code 0” 的消息。 这是 由于代码执行…...

【计算机网络笔记六】应用层(三)HTTP 的 Cookie、缓存控制、代理服务、短连接和长连接

HTTP 的 Cookie HTTP 的 Cookie 机制要用到两个字段&#xff1a;响应头字段 Set-Cookie 和请求头字段 Cookie。 Cookie 可以设置多个 key-value 对&#xff0c; 响应头中可以设置多个 Set-Cookie 字段&#xff0c;请求头Cookie后面可以设置多个键值对&#xff0c;用分号隔开&a…...

Vue中的数据分页与分页组件设计

Vue中的数据分页与分页组件设计 在前端开发中&#xff0c;数据分页是一个常见的需求&#xff0c;特别是当处理大量数据时。Vue作为一款流行的JavaScript框架&#xff0c;提供了强大的工具和生态系统来实现数据分页。本文将介绍如何在Vue中进行数据分页&#xff0c;以及如何设计…...

TCP串流场景剖析

在TCP&#xff08;传输控制协议&#xff09;中&#xff0c;串流场景指的是数据通过TCP连接以流&#xff08;stream&#xff09;的方式传输。TCP是一种可靠的、面向连接的传输协议&#xff0c;它将数据切分为多个报文段&#xff0c;通过网络传输&#xff0c;并在接收端进行重组&…...

Windows历史版本下载

1、微PE工具箱&#xff08;非广告本人常用&#xff09; 常用安装Windows系统的微PE工具 地址&#xff1a;https://www.wepe.com.cn/download.html 2、Windows系统下载地址&#xff08;非微软官方&#xff09; 地址&#xff1a;MSDN, 我告诉你 - 做一个安静的工具站 下载&…...

企业级磁盘阵列存储系统由硬到软全析

企业级磁盘阵列是由一组设备构成的存储系统,主要包括两种类型的设备,分别是控制器和扩展柜,其中控制器只有一台,扩展柜可以没有,也可以有多台。在EMC的Unity中分别称为DPE(Disk Processor Enclosure)和DAE(Disk Array Enclosure),在华为的OceanStor里面称为控制框和硬…...

V4L2 驱动架构介绍

V4L2 简介 Video for Linux two(Video4Linux2)简称 V4L2&#xff0c;是 V4L 的改进版。V4L2 是 linux操作系统下用于视频和音频数据采集设备的驱动框架&#xff0c;为驱动和应用程序提供了一套统一的接口规范。 在 Linux 下&#xff0c;所有外设都被看成一种特殊的文件&#xf…...

掌握这些技巧,让Excel批量数据清洗变得简单高效!

什么是数据清洗 数据清洗是指在数据处理过程中对原始数据进行筛选、转换和修正&#xff0c;以确保数据的准确性、一致性和完整性的过程。它是数据预处理的一部分&#xff0c;旨在处理和纠正可能存在的错误、缺失值、异常值和不一致性等数据质量问题。 为什么要数据清洗 Exce…...

成都瀚网科技:抖音上线地方方言自动翻译功能

为了让很多方言的地域历史、文化、习俗能够以短视频的形式生产、传播和保存&#xff0c;解决方言难以被更多用户阅读和理解的问题&#xff0c;平台正式上线推出当地方言自动翻译功能。创作者可以利用该功能&#xff0c;将多个方言视频“一键”转换为普通话字幕供大众观看。 具体…...

【k8s】【docker】web项目的部署

yaml配置文件 后端&#xff1a;springboot项目 前端&#xff1a;vue项目&#xff0c;之前镜像封装的nginx.conf反向代理配置直接使用了docker-compose.yml中services的名称&#xff0c;无法代理成功&#xff0c;可修改为127.0.0.1 # 后端 apiVersion: apps/v1 kind: Deployment…...

【视频去噪】基于全变异正则化最小二乘反卷积是最标准的图像处理、视频去噪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…...

国庆day3---网络编程知识点脑图整合

...

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

visual studio 2022更改主题为深色

visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中&#xff0c;选择 环境 -> 常规 &#xff0c;将其中的颜色主题改成深色 点击确定&#xff0c;更改完成...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

Linux-07 ubuntu 的 chrome 启动不了

文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了&#xff0c;报错如下四、启动不了&#xff0c;解决如下 总结 问题原因 在应用中可以看到chrome&#xff0c;但是打不开(说明&#xff1a;原来的ubuntu系统出问题了&#xff0c;这个是备用的硬盘&a…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

OD 算法题 B卷【正整数到Excel编号之间的转换】

文章目录 正整数到Excel编号之间的转换 正整数到Excel编号之间的转换 excel的列编号是这样的&#xff1a;a b c … z aa ab ac… az ba bb bc…yz za zb zc …zz aaa aab aac…; 分别代表以下的编号1 2 3 … 26 27 28 29… 52 53 54 55… 676 677 678 679 … 702 703 704 705;…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!

本文介绍了一种名为AnomalyAny的创新框架&#xff0c;该方法利用Stable Diffusion的强大生成能力&#xff0c;仅需单个正常样本和文本描述&#xff0c;即可生成逼真且多样化的异常样本&#xff0c;有效解决了视觉异常检测中异常样本稀缺的难题&#xff0c;为工业质检、医疗影像…...