找回密码
 立即注册
首页 业界区 业界 大模型基础补全计划(三)---RNN实例与测试 ...

大模型基础补全计划(三)---RNN实例与测试

颖顿庐 2025-7-5 15:49:39
PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明

  无
前言

   本文是这个系列第三篇,它们是:

  • 《大模型基础补全计划(一)---重温一些深度学习相关的数学知识》 https://www.cnblogs.com/Iflyinsky/p/18717317
  • 《大模型基础补全计划(二)---词嵌入(word embedding) 》 https://www.cnblogs.com/Iflyinsky/p/18775451
   在CV世界里,卷积神经网络一直是主流。在以前,NLP的世界里,循环神经网络是主流,站在今天大模型时代,Transformer 及相关变体,是当今的NLP的绝对主流。但是我们要了解Transformer提出的原因,还需要回到循环神经网络,了解其历史变迁。当然,在循环神经网络中,一些主流的概念当前也还在使用,例如:token、词表等等。
  因此,如本文题目所示,本文主要简单介绍一下RNN,并尝试用RNN训练一个简单的文本续写模型。




RNN (Recurrent Neural Network)

  


RNN的意义

  在提到rnn之前,我们还是有必要先提一下cnn,cnn的应用目标是指定一个输入,获得一个模型输出,多次输入之间是没有必然联系。然而,在日常生活中,我们还有许多其他的任务是多个输入之间是有前后关系的。例如:机翻、对话模型等等,这些任务都有明显的特征,那就是输入数据是一个序列,前面输入的数据会对后面的输出产生了影响,因此有了rnn模型结构。


RNN的结构

  如图(注意,此图找不到来源出处,看到网络大部分文章都引用了此图,若有侵权,联系删除)rnn的基础结构就三层:输入层、隐藏层、输出层,:
            
1.jpeg
         从图中可以知道,W是一个隐藏参数,是作为来至于上一次模型计算值\(S_{t-1}\)的参数。V是输出的参数,U是输入的参数。那么我们就可以简单定义模型结构是:\(S_t = U*X_t + W*S_{t-1} + b_i\)和  \(O_t = V*S_t + b_o\)
  对于输入层来说,其是一个输入序列,我们输出的内容也是一个序列。
  注意,这里的核心就是\(S_t\),前面的输入\(X_t\)对应一个\(S_t\),那么在计算\(O_{t+1}\)的时候,会用到\(S_t\)。这样对于这个模型来说,\(X_t\)对\(O_{t+1}\)是有影响的,也就意味着,模型可能可以学习到\(X_t\)和\(X_{t+1}\)的关系。




基于RNN训练一个简单的文字序列输出模型

  


文本预处理
  1. import collections
  2. # [
  3. #     [line0],
  4. #     [line1],
  5. #     .....
  6. # ]
  7. def read_data_from_txt():
  8.     with open('诛仙 (萧鼎).txt', 'r', encoding='utf-8') as f:
  9.         lines = f.readlines()
  10.    
  11.     return [line.strip() for line in lines]
  12. # 下面的tokenize函数将文本行列表(lines)作为输入, 列表中的每个元素是一个文本序列(如一条文本行)。
  13. # 每个文本序列又被拆分成一个词元列表,词元(token)是文本的基本单位。 最后,返回一个由词元列表组成的列表,
  14. # 其中的每个词元都是一个字符串(string)。
  15. # [
  16. #     [line0-char0, line0-char1, line0-char2, ....],
  17. #     [line1-char0, line1-char1, line1-char2, ....],
  18. #     .....
  19. # ]
  20. def tokenize(lines, token='char'):  #@save
  21.     """将文本行拆分为单词或字符词元"""
  22.     if token == 'word':
  23.         return [line.split() for line in lines]
  24.     elif token == 'char':
  25.         return [list(line) for line in lines]
  26.     else:
  27.         print('错误:未知词元类型:' + token)
  28. # 词元的类型是字符串,而模型需要的输入是数字,因此这种类型不方便模型使用。 现在,让我们构建一个字典,
  29. # 通常也叫做词表(vocabulary), 用来将字符串类型的词元映射到从开始的数字索引中。
  30. def count_corpus(tokens):  #@save
  31.     """统计词元的频率"""
  32.     # 这里的tokens是1D列表或2D列表
  33.     if len(tokens) == 0 or isinstance(tokens[0], list):
  34.         # 将词元列表展平成一个列表
  35.         tokens = [token for line in tokens for token in line]
  36.     return collections.Counter(tokens)
  37. # 返回类似{'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1}的一个字典
  38. class Vocab:
  39.     """文本词表"""
  40.     def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
  41.         if tokens is None:
  42.             tokens = []
  43.         if reserved_tokens is None:
  44.             reserved_tokens = []
  45.         # 按出现频率排序
  46.         # 对于Counter("hello world"),结果如下
  47.         # Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
  48.         counter = count_corpus(tokens)
  49.         self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
  50.                                    reverse=True)
  51.         # 未知词元的索引为0
  52.         self.idx_to_token = ['<unk>'] + reserved_tokens
  53.         self.token_to_idx = {token: idx
  54.                              for idx, token in enumerate(self.idx_to_token)}
  55.         for token, freq in self._token_freqs:
  56.             if freq < min_freq:
  57.                 break
  58.             if token not in self.token_to_idx:
  59.                 self.idx_to_token.append(token)
  60.                 self.token_to_idx[token] = len(self.idx_to_token) - 1
  61.     def __len__(self):
  62.         return len(self.idx_to_token)
  63.     def __getitem__(self, tokens):
  64.         if not isinstance(tokens, (list, tuple)):
  65.             return self.token_to_idx.get(tokens, self.unk)
  66.         return [self.__getitem__(token) for token in tokens]
  67.     def to_tokens(self, indices):
  68.         if not isinstance(indices, (list, tuple)):
  69.             return self.idx_to_token[indices]
  70.         return [self.idx_to_token[index] for index in indices]
  71.     @property
  72.     def unk(self):  # 未知词元的索引为0
  73.         return 0
  74.     @property
  75.     def token_freqs(self):
  76.         return self._token_freqs   
  77. # 将传入的数据集映射为一个索引表
  78. # 返回传入文本的索引、词表
  79. def load_dataset(max_tokens=-1):
  80.     lines = read_data_from_txt()
  81.     print(f'# 文本总行数: {len(lines)}')
  82.     # print(lines[0])
  83.     # print(lines[10])
  84.     tokens = tokenize(lines)
  85.     # for i in range(11):
  86.     #     print(tokens[i])
  87.     vocab = Vocab(tokens, reserved_tokens=['<pad>', '<bos>', '<eos>'])
  88.     # print(list(vocab.token_to_idx.items())[:10])
  89.     # for i in [0, 10]:
  90.     #     print('文本:', tokens[i])
  91.     #     print('索引:', vocab[tokens[i]])
  92.     corpus = [vocab[token] for line in tokens for token in line]
  93.     if max_tokens > 0:
  94.         corpus = corpus[:max_tokens]
  95.     return corpus, vocab
复制代码
  上面代码做了如下事情:

  • 首先我们随便找了一部中文小说,然后读取其所有的行,然后得到一个包含所有行的二维列表。
  • 然后我们对每一行进行文字切割,得到了一个二维列表,列表中的每一行又被分割为一个个中文文字,也就得到了一个个token。(特别注意,站在当前的时刻,这里的token和现在主流的大语言模型的token概念是一样的,但是不是一样的实现。)
  • 由于模型不能直接处理文字,我们需要将文字转换为数字,那么直接的做法就是将一个个token编号即可,这个时候我们得到了词表(vocabulary)。
  • 然后我们根据我们得到的词表,对原始数据集进行数字化,得到一个列表,列表中每个元素就是一个个token对应的索引。


构造数据集及加载器
  1. # 以num_steps为步长,从随机的起始位置开始,返回
  2. # x1=[ [random_offset1:random_offset1 + num_steps], ... , [random_offset_batchsize:random_offset_batchsize + num_steps] ]
  3. # y1=[ [random_offset1 + 1:random_offset1 + num_steps + 1], ... , [random_offset_batchsize + 1:random_offset_batchsize + num_steps + 1] ]
  4. def seq_data_iter_random(corpus, batch_size, num_steps):  #@save
  5.     """使用随机抽样生成一个小批量子序列"""
  6.     # 从随机偏移量开始对序列进行分区,随机范围包括num_steps-1
  7.     corpus = corpus[random.randint(0, num_steps - 1):]
  8.     # 减去1,是因为我们需要考虑标签
  9.     num_subseqs = (len(corpus) - 1) // num_steps
  10.     # 长度为num_steps的子序列的起始索引
  11.     # [0, num_steps*1, num_steps*2, num_steps*3, ...]
  12.     initial_indices = list(range(0, num_subseqs * num_steps, num_steps))
  13.     # 在随机抽样的迭代过程中,
  14.     # 来自两个相邻的、随机的、小批量中的子序列不一定在原始序列上相邻
  15.     random.shuffle(initial_indices)
  16.     def data(pos):
  17.         # 返回从pos位置开始的长度为num_steps的序列
  18.         return corpus[pos: pos + num_steps]
  19.     num_batches = num_subseqs // batch_size
  20.     for i in range(0, batch_size * num_batches, batch_size):
  21.         # 在这里,initial_indices包含子序列的随机起始索引
  22.         initial_indices_per_batch = initial_indices[i: i + batch_size]
  23.         X = [data(j) for j in initial_indices_per_batch]
  24.         Y = [data(j + 1) for j in initial_indices_per_batch]
  25.         yield torch.tensor(X), torch.tensor(Y)
  26. # 以num_steps为步长,从随机的起始位置开始,返回
  27. # x1=[:, random_offset1:random_offset1 + num_steps]
  28. # y1=[:, random_offset1 + 1:random_offset1 + num_steps + 1]
  29. def seq_data_iter_sequential(corpus, batch_size, num_steps):  #@save
  30.     """使用顺序分区生成一个小批量子序列"""
  31.     # 从随机偏移量开始划分序列
  32.     offset = random.randint(0, num_steps)
  33.     num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_size
  34.     # 重新根据corpus建立X_corpus, Y_corpus,两者之间差一位。注意X_corpus, Y_corpus的长度是batch_size的整数倍
  35.     Xs = torch.tensor(corpus[offset: offset + num_tokens])
  36.     Ys = torch.tensor(corpus[offset + 1: offset + 1 + num_tokens])
  37.     # 直接根据batchsize划分X_corpus, Y_corpus
  38.     Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)
  39.     # 计算出需要多少次才能取完数据
  40.     num_batches = Xs.shape[1] // num_steps
  41.     for i in range(0, num_steps * num_batches, num_steps):
  42.         X = Xs[:, i: i + num_steps]
  43.         Y = Ys[:, i: i + num_steps]
  44.         yield X, Y
  45. class SeqDataLoader:  #@save
  46.     """加载序列数据的迭代器"""
  47.     def __init__(self, batch_size, num_steps, use_random_iter, max_tokens):
  48.         if use_random_iter:
  49.             self.data_iter_fn = seq_data_iter_random
  50.         else:
  51.             self.data_iter_fn = seq_data_iter_sequential
  52.         self.corpus, self.vocab = dateset.load_dataset(max_tokens)
  53.         self.batch_size, self.num_steps = batch_size, num_steps
  54.     def __iter__(self):
  55.         return self.data_iter_fn(self.corpus, self.batch_size, self.num_steps)
  56.    
  57. def load_data_epoch(batch_size, num_steps,  #@save
  58.                            use_random_iter=False, max_tokens=10000):
  59.     """返回时光机器数据集的迭代器和词表"""
  60.     data_iter = SeqDataLoader(
  61.         batch_size, num_steps, use_random_iter, max_tokens)
  62.     return data_iter, data_iter.vocab
复制代码
  上面的代码主要作用是:在训练的时候,从我们在文本预处理数据中,以随机顺序或者相邻顺序抽取其中的部分数据作为随机批量数据。每次抽取的数据维度是:(batch_size, num_steps)


搭建RNN训练框架

  按照原来的经验,我们要设计一个训练框架,第一步就要搭建网络,此网络用于接收一个输入,输出一个输出。
  1. def rnn(inputs, state, params):
  2.     # inputs的形状:(时间步数量,批量大小,词表大小)
  3.     # inputs的形状:(num_steps,batch_size,词表大小)
  4.     # W_xh的形状: (词表大小, num_hiddens)
  5.     # W_hh的形状:(num_hiddens, num_hiddens)
  6.     # b_h 的形状:(num_hiddens)
  7.     # W_hq的形状:(num_hiddens, 词表大小)
  8.     # b_q 的形状:(词表大小)
  9.     W_xh, W_hh, b_h, W_hq, b_q = params
  10.     # H的形状:(batch_size, num_hiddens)
  11.     H, = state
  12.     outputs = []
  13.     # X的形状:(批量大小,词表大小)
  14.     # X的形状:(batch_size,词表大小)
  15.     for X in inputs:
  16.         # H是上一次预测的一个参数,每次计算隐藏层值后,更新H的值
  17.         # H = tanh(X*W_xh + H*W_hh + b_h)
  18.         H = torch.tanh(torch.mm(X, W_xh) + torch.mm(H, W_hh) + b_h)
  19.         # Y是输出值,每次rnn输出的时候,都会输出从开始到当前的所有值,因此我们需要保存所有的输出值
  20.         # Y = H * W_hq + b_q
  21.         # Y的形状:(batch_size,词表大小)
  22.         Y = torch.mm(H, W_hq) + b_q
  23.         outputs.append(Y)
  24.     return torch.cat(outputs, dim=0), (H,)
  25. class RNNModelScratch: #@save
  26.     """从零开始实现的循环神经网络模型"""
  27.     def __init__(self, vocab_size, num_hiddens, device,
  28.                  get_params, init_state, forward_fn):
  29.         self.vocab_size, self.num_hiddens = vocab_size, num_hiddens
  30.         # 初始化了隐藏参数 W_xh, W_hh, b_h,  W_hq, b_q
  31.         self.params = get_params(vocab_size, num_hiddens, device)
  32.         self.init_state, self.forward_fn = init_state, forward_fn
  33.     def __call__(self, X, state):
  34.         # X的形状:(batch_size, num_steps)
  35.         # X one_hot之后的形状:(num_steps,batch_size,词表大小)
  36.         X = F.one_hot(X.T, self.vocab_size).type(torch.float32)
  37.         return self.forward_fn(X, state, self.params)
  38.     def begin_state(self, batch_size, device):
  39.         return self.init_state(batch_size, self.num_hiddens, device)
  40. # 用框架
  41. #@save
  42. class RNNModel(nn.Module):
  43.     """循环神经网络模型"""
  44.     def __init__(self, rnn_layer, vocab_size, device, **kwargs):
  45.         super(RNNModel, self).__init__(**kwargs)
  46.         self.rnn = rnn_layer
  47.         self.vocab_size = vocab_size
  48.         self.num_hiddens = self.rnn.hidden_size
  49.         # 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1
  50.         if not self.rnn.bidirectional:
  51.             self.num_directions = 1
  52.             self.linear = nn.Linear(self.num_hiddens, self.vocab_size, device=device)
  53.         else:
  54.             self.num_directions = 2
  55.             self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size, device=device)
  56.     def forward(self, inputs, state):
  57.         X = F.one_hot(inputs.T.long(), self.vocab_size)
  58.         X = X.to(torch.float32)
  59.         Y, state = self.rnn(X, state)
  60.         # 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)
  61.         # 它的输出形状是(时间步数*批量大小,词表大小)。
  62.         output = self.linear(Y.reshape((-1, Y.shape[-1])))
  63.         return output, state
  64.     def begin_state(self, device, batch_size=1):
  65.         if not isinstance(self.rnn, nn.LSTM):
  66.             # nn.GRU以张量作为隐状态
  67.             return  torch.zeros((self.num_directions * self.rnn.num_layers,
  68.                                  batch_size, self.num_hiddens),
  69.                                 device=device)
  70.         else:
  71.             # nn.LSTM以元组作为隐状态
  72.             return (torch.zeros((
  73.                 self.num_directions * self.rnn.num_layers,
  74.                 batch_size, self.num_hiddens), device=device),
  75.                     torch.zeros((
  76.                         self.num_directions * self.rnn.num_layers,
  77.                         batch_size, self.num_hiddens), device=device))
复制代码
  上面主要是设计了两个网络类:RNNModelScratch、RNNModel。前者是手搓rnn实现。后者是借用torch框架来实现一个简单的rnn网络。他们的主要做了如下几个事情:

  • 接收(batch_size, num_steps)的输入,并将输入转换为one_hot向量模式,其shape是(num_steps,batch_size,词表大小)
  • 通过rnn的计算,然后通过变换,将最终输出映射到(batch_size * num_steps, 词表大小)
  其实我们观察输入和输出,就可以理解一个事情:输入的内容就是输入序列所有的字符对应的one_hot向量。输出的内容就是batch_size * num_steps个向量,代表输出的文字序列信息,每个向量里面的最大值就代表了网络预测的文字id。
  有了网络,对于部署角度来说,我们只需要实现预测过程即可:
  1. def predict_ch8(prefix, num_preds, net, vocab, device):  #@save
  2.     """在prefix后面生成新字符"""
  3.     state = net.begin_state(batch_size=1, device=device)
  4.     outputs = [vocab[prefix[0]]]
  5.     get_input = lambda: torch.tensor([outputs[-1]], device=device).reshape((1, 1))
  6.     for y in prefix[1:]:  # 预热期
  7.         _, state = net(get_input(), state)
  8.         outputs.append(vocab[y])
  9.     for _ in range(num_preds):  # 预测num_preds步
  10.         # y 包含从开始到现在的所有输出
  11.         # state是当前计算出来的隐藏参数
  12.         y, state = net(get_input(), state)
  13.         outputs.append(int(y.argmax(dim=1).reshape(1)))
  14.     return ''.join([vocab.idx_to_token[i] for i in outputs])
复制代码
  由于输出的信息就是batch_size * num_steps个向量,那么只需要计算每一个向量的最大值id就得到了网络输出的tokenid,然后通过词表反向映射回词表,完成了预测文字输出的功能。
  有了网络、预测过程,然后就可以搭建训练过程,训练过程最重要的一步就是通过网络得到输入对应的输出,然后根据输出计算loss信息,然后根据loss信息进行梯度下降(这就是通用流程)
  1. def train_epoch_ch8(net, train_iter, loss, updater, device, use_random_iter):
  2.     """训练网络一个迭代周期(定义见第8章)"""
  3.     state, timer = None, Timer()
  4.     metric = Accumulator(2)  # 训练损失之和,词元数量
  5.     # X的形状:(batch_size, num_steps)
  6.     # Y的形状:(batch_size, num_steps)
  7.     for X, Y in train_iter:
  8.         if state is None or use_random_iter:
  9.             # 在第一次迭代或使用随机抽样时初始化state
  10.             state = net.begin_state(batch_size=X.shape[0], device=device)
  11.         else:
  12.             if isinstance(net, nn.Module) and not isinstance(state, tuple):
  13.                 # state对于nn.GRU是个张量
  14.                 state.detach_()
  15.             else:
  16.                 # state对于nn.LSTM或对于我们从零开始实现的模型是个张量
  17.                 for s in state:
  18.                     s.detach_()
  19.         y = Y.T.reshape(-1)
  20.         X, y = X.to(device), y.to(device)
  21.         # y_hat 包含从开始到现在的所有输出
  22.         # y_hat的形状:(batch_size * num_steps, 词表大小)
  23.         # state是当前计算出来的隐藏参数
  24.         y_hat, state = net(X, state)
  25.         # 交叉熵损失函数,传入预测值和标签值,并求平均值
  26.         l = loss(y_hat, y.long()).mean()
  27.         if isinstance(updater, torch.optim.Optimizer):
  28.             updater.zero_grad()
  29.             l.backward()
  30.             grad_clipping(net, 1)
  31.             updater.step()
  32.         else:
  33.             l.backward()
  34.             grad_clipping(net, 1)
  35.             # 因为已经调用了mean函数
  36.             updater(batch_size=1)
  37.         # 这里记录交叉熵损失的值的和,以及记录对应交叉熵损失值的样本个数
  38.         metric.add(l * y.numel(), y.numel())
  39.     # 求交叉熵损失的平均值,再求exp,即可得到困惑度
  40.     return math.exp(metric[0] / metric[1]), metric[1] / timer.stop()
  41. def sgd(params, lr, batch_size):
  42.     """小批量随机梯度下降
  43.     Defined in :numref:`sec_linear_scratch`"""
  44.     with torch.no_grad():
  45.         for param in params:
  46.             param -= lr * param.grad / batch_size
  47.             param.grad.zero_()
  48. #@save
  49. def train_ch8(net, train_iter, vocab, lr, num_epochs, device,
  50.               use_random_iter=False):
  51.     """训练模型(定义见第8章)"""
  52.     loss = nn.CrossEntropyLoss()
  53.     # 新建一个连接客户端
  54.     # 指定 env=u'test1',默认端口为 8097,host 是 'localhost'
  55.     vis = visdom.Visdom(env=u'test1', server="http://127.0.0.1", port=8097)
  56.     animator = vis
  57.     # 初始化
  58.     if isinstance(net, nn.Module):
  59.         updater = torch.optim.SGD(net.parameters(), lr)
  60.     else:
  61.         updater = lambda batch_size: sgd(net.params, lr, batch_size)
  62.     predict = lambda prefix: predict_ch8(prefix, 30, net, vocab, device)
  63.     # 训练和预测
  64.     for epoch in range(num_epochs):
  65.         ppl, speed = train_epoch_ch8(
  66.             net, train_iter, loss, updater, device, use_random_iter)
  67.         
  68.         if (epoch + 1) % 10 == 0:
  69.             # print(predict('你是?'))
  70.             # print(epoch)
  71.             # animator.add(epoch + 1, )
  72.             if epoch == 9:
  73.                 # 清空图表:使用空数组来替换现有内容
  74.                 vis.line(X=np.array([0]), Y=np.array([0]), win='train_ch8', update='replace')
  75.             vis.line(
  76.                 X=np.array([epoch + 1]),
  77.                 Y=[ppl],
  78.                 win='train_ch8',
  79.                 update='append',
  80.                 opts={
  81.                     'title': 'train_ch8',
  82.                     'xlabel': 'epoch',
  83.                     'ylabel': 'ppl',
  84.                     'linecolor': np.array([[0, 0, 255]]),  # 蓝色线条
  85.                 }
  86.             )
  87.     print(f'困惑度 {ppl:.1f}, {speed:.1f} 词元/秒 {str(device)}')
  88.     print(predict('你是'))
  89.     print(predict('我有一剑'))
复制代码
  其实从上面的代码就可以看到,我们传入数据,得到输出,计算了交叉熵loss,然后使用sgd最小化loss,最终我们计算困惑度,得到了模型的质量。注意,这里面有关于梯度截断的计算,这个我们只需要它是避免梯度爆炸的一个方法即可。
  然后我们使用如下的代码就可以开始训练,注意使用net就是自定义rnn,net1就是使用框架的rnn。
  1. def try_gpu(i=0):
  2.     """如果存在,则返回gpu(i),否则返回cpu()
  3.     Defined in :numref:`sec_use_gpu`"""
  4.     if torch.cuda.device_count() >= i + 1:
  5.         return torch.device(f'cuda:{i}')
  6.     return torch.device('cpu')
  7. if __name__ == '__main__':
  8.     num_epochs, lr = 1000, 0.5
  9.     batch_size, num_steps = 32, 35
  10.     data_iter, vocab  = load_data_epoch(batch_size, num_steps)
  11.     num_hiddens = 512
  12.     device = try_gpu()
  13.     net = RNNModelScratch(len(vocab), num_hiddens, device, get_params,
  14.                         init_rnn_state, rnn)
  15.    
  16.     rnn_layer = nn.RNN(len(vocab), num_hiddens, device=device)
  17.     net1 = RNNModel(rnn_layer, vocab_size=len(vocab),  device=device)
  18.    
  19.     print(predict_ch8('你是', 30, net, vocab, device))
  20.     train_ch8(net, data_iter, vocab, lr, num_epochs, device)
复制代码
  我们分别使用手动构建的rnn和框架构建的rnn进行训练和测试,结果如下:
            
2.png
                  
3.png
                  
4.png
                  
5.png
         我们可以看到,模型未训练和训练后的对比,明显训练后能说两句人话,虽然感觉还是胡说八道,但是感觉还是有点效果。




后记

  总的来说,未训练的模型和已训练的模型的文字续写效果完全不一样,明显感觉训练之后的模型,文字续写给人一种可以读感觉。
参考文献


  • https://zhuanlan.zhihu.com/p/30844905
  • https://zh.d2l.ai/chapter_recurrent-neural-networks/rnn.html
  • https://zh.d2l.ai/chapter_recurrent-neural-networks/text-preprocessing.html


                    打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)               
6.jpeg
    PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。


来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册