深度学习实战(基于pytroch)系列(三十六)循环神经网络的pytorch简洁实现

循环神经网络的pytorch简洁实现

上一节我们已经从零开始实现了循环神经网络,本节将使用PyTorch来更简洁地实现基于循环神经网络的语言模型。首先,我们读取周杰伦专辑歌词数据集。这一步和上节的代码基本一致。

python 复制代码
import torch
import torch.nn as nn
import torch.optim as optim
import math
import random
import time
import zipfile

def load_data_jay_lyrics():
    with zipfile.ZipFile('../data/jaychou_lyrics.txt.zip') as zin:
        with zin.open('jaychou_lyrics.txt') as f:
            corpus_chars = f.read().decode('utf-8')
    corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
    corpus_chars = corpus_chars[0:10000]
    idx_to_char = list(set(corpus_chars))
    char_to_idx = {char: i for i, char in enumerate(idx_to_char)}
    vocab_size = len(char_to_idx)
    corpus_indices = [char_to_idx[char] for char in corpus_chars]
    return corpus_indices, char_to_idx, idx_to_char, vocab_size


(corpus_indices, char_to_idx, idx_to_char, vocab_size) = load_data_jay_lyrics()

定义模型

PyTorch的nn模块提供了循环神经网络的实现。下面构造一个含单隐藏层、隐藏单元个数为256的循环神经网络层rnn_layer。

python 复制代码
num_hiddens = 256
rnn_layer = nn.RNN(input_size=vocab_size, hidden_size=num_hiddens)

接下来我们初始化隐藏状态。它有一个形状为(层数, 批量大小, 隐藏单元个数)的元素。

python 复制代码
batch_size = 2
state = torch.zeros((1, batch_size, num_hiddens))
state.shape

输出

torch.Size(1, 2, 256)

与之前实现的循环神经网络不同,这里rnn_layer的输入形状为(时间步数, 批量大小, 输入维度)。其中输入维度即one-hot向量长度(词典大小)。此外,rnn_layer在前向计算后会分别返回输出和隐藏状态,其中输出指的是隐藏层在各个时间步上计算并输出的隐藏状态,它们通常作为后续输出层的输入。需要强调的是,该"输出"本身并不涉及输出层计算,形状为(时间步数, 批量大小, 隐藏单元个数)。而RNN实例在前向计算返回的隐藏状态指的是隐藏层在最后时间步的可用于初始化下一时间步的隐藏状态。

python 复制代码
num_steps = 35
X = torch.rand(num_steps, batch_size, vocab_size)
Y, state_new = rnn_layer(X, state)
Y.shape, state_new.shape

输出

(torch.Size(35, 2, 256), torch.Size(1, 2, 256))

接下来我们继承Module类来定义一个完整的循环神经网络。它首先将输入数据使用one-hot向量表示后输入到rnn_layer中,然后使用全连接输出层得到输出。输出个数等于词典大小vocab_size。

python 复制代码
from torch.nn import functional as F
class RNNModel(nn.Module):
    def __init__(self, rnn_layer, vocab_size, **kwargs):
        super(RNNModel, self).__init__(**kwargs)
        self.rnn = rnn_layer
        self.vocab_size = vocab_size
        self.dense = nn.Linear(num_hiddens, vocab_size)

    def forward(self, inputs, state):
        # 将输入转置成(num_steps, batch_size)后获取one-hot向量表示
        X = F.one_hot(inputs.T.long(), self.vocab_size)
        X = X.to(torch.float32)
        Y, state = self.rnn(X, state)
        # 全连接层会首先将Y的形状变成(num_steps * batch_size, num_hiddens),它的输出
        # 形状为(num_steps * batch_size, vocab_size)
        output = self.dense(Y.reshape(-1, Y.shape[-1]))
        return output, state

    def begin_state(self, batch_size, device):
        if not isinstance(self.rnn, nn.LSTM):
            # nn.RNN以张量作为隐藏状态
            return torch.zeros((self.rnn.num_layers, batch_size, self.rnn.hidden_size), device=device)
        else:
            # nn.LSTM以元组作为隐藏状态
            return (torch.zeros((self.rnn.num_layers, batch_size, self.rnn.hidden_size), device=device),
                    torch.zeros((self.rnn.num_layers, batch_size, self.rnn.hidden_size), device=device))

训练模型

同之前一样,下面定义一个预测函数。这里的实现区别在于前向计算和初始化隐藏状态的函数接口。

python 复制代码
def predict_rnn_pytorch(prefix, num_chars, model, vocab_size, device, idx_to_char,
                      char_to_idx):
    # 使用model的成员函数来初始化隐藏状态
    state = model.begin_state(batch_size=1, device=device)
    output = [char_to_idx[prefix[0]]]
    for t in range(num_chars + len(prefix) - 1):
        X = torch.tensor([output[-1]], device=device).reshape((1, 1))
        (Y, state) = model(X, state)  # 前向计算不需要传入模型参数
        if t < len(prefix) - 1:
            output.append(char_to_idx[prefix[t + 1]])
        else:
            output.append(int(Y.argmax(dim=1).item()))
    return ''.join([idx_to_char[i] for i in output])

让我们使用权重为随机值的模型来预测一次。

python 复制代码
def try_gpu():
    """If GPU is available, return torch.device('cuda'); else return torch.device('cpu')."""
    if torch.cuda.is_available():
        return torch.device('cuda')
    else:
        return torch.device('cpu')

device = try_gpu()
model = RNNModel(rnn_layer, vocab_size)
model = model.to(device)
predict_rnn_pytorch('分开', 10, model, vocab_size, device, idx_to_char, char_to_idx)

输出

分开 运秋城短型疲狠丘所'

接下来实现训练函数。算法同之前的一样,但这里只使用了相邻采样来读取数据。

python 复制代码
def grad_clipping(params, theta, device):
    norm = torch.tensor([0.0], device=device)
    for param in params:
        norm += (param.grad.data ** 2).sum()
    norm = norm.sqrt().item()
    if norm > theta:
        for param in params:
            param.grad.data *= (theta / norm)


def data_iter_consecutive(corpus_indices, batch_size, num_steps, device=None):
    corpus_indices = torch.tensor(corpus_indices, device=device)
    data_len = len(corpus_indices)
    batch_len = data_len // batch_size
    indices = corpus_indices[0: batch_size * batch_len].reshape(
        batch_size, batch_len)
    epoch_size = (batch_len - 1) // num_steps
    for i in range(epoch_size):
        i = i * num_steps
        X = indices[:, i: i + num_steps]
        Y = indices[:, i + 1: i + num_steps + 1]
        yield X, Y


def train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
                                corpus_indices, idx_to_char, char_to_idx,
                                num_epochs, num_steps, lr, clipping_theta,
                                batch_size, pred_period, pred_len, prefixes):
    loss = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=lr)
    
    for epoch in range(num_epochs):
        l_sum, n, start = 0.0, 0, time.time()
        data_iter = data_iter_consecutive(
            corpus_indices, batch_size, num_steps, device)
        state = model.begin_state(batch_size=batch_size, device=device)
        for X, Y in data_iter:
            if isinstance(state, tuple):  # LSTM, state:(h, c)  
                state = (state[0].detach(), state[1].detach())
            else:
                state = state.detach()
            
            (output, state) = model(X, state)
            y = Y.T.reshape(-1)
            l = loss(output, y.long())
            
            optimizer.zero_grad()
            l.backward()
            # 梯度裁剪
            grad_clipping(model.parameters(), clipping_theta, device)
            optimizer.step()
            l_sum += l.item() * y.numel()
            n += y.numel()

        if (epoch + 1) % pred_period == 0:
            print('epoch %d, perplexity %f, time %.2f sec' % (
                epoch + 1, math.exp(l_sum / n), time.time() - start))
            for prefix in prefixes:
                print(' -', predict_rnn_pytorch(
                    prefix, pred_len, model, vocab_size, device, idx_to_char,
                    char_to_idx))

使用和之前实验中一样的超参数来训练模型。

python 复制代码
num_epochs, batch_size, lr, clipping_theta = 250, 32, 1e-3, 1e-2
pred_period, pred_len, prefixes = 50, 50, ['分开', '不分开']
train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
                            corpus_indices, idx_to_char, char_to_idx,
                            num_epochs, num_steps, lr, clipping_theta,
                            batch_size, pred_period, pred_len, prefixes)

输出

  • epoch 50, perplexity 13.913522, time 0.02 sec
  • 分开 我不能的可 你的那里 我不能 不多 我 我不了 不多 没 我想你 我不我 爱你你你在我 我想要你
  • 不分开 我想你你你 我不 我想你 你不我 我想要你想你 我不要再想 不要再这样我不妈 想你你的爱我 我
  • epoch 100, perplexity 1.312056, time 0.02 sec
  • 分开不了再想就像不要再想多我不 这样 我不了我可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女
  • 不分开 我这样牵着你的我不想这样 爸你的我爱你 没有你烦 我想多 穿你都痛 我一定着 对着我去 我想带你
  • epoch 150, perplexity 1.069133, time 0.02 sec
  • 分开不了 让不知不觉 你已经离开我 不知不觉 我跟了这节奏 后知后觉 又过了一个秋 后知后觉 我该好好生
  • 不分开不了我想就想不多 你 我不是你不会 我不能为爱你 我打我有多爸穿你 没我 一定会痛 融你在一个悲
  • epoch 200, perplexity 1.033015, time 0.02 sec
  • 分开不了 让不知不 如果说 别怪我的爸膀 你的在我胸口睡著 对这里的美怎么沟通 痛是谁说的空 是人都没
  • 不分开不了我让就是我的到你来 这样你吗 我的妈都难像 为和在这里的 深著我已无能为力的可爱 我想要你的陪
  • epoch 250, perplexity 1.022188, time 0.02 sec
  • 分开不了 让不知不觉 你已经离开我 不知不觉 我跟了这节奏 后知后觉 又过了一个秋 后知后觉 我该好好生
  • 不分开 我不知不觉 我跟了这节奏 后知后觉 又过了一个秋 后知后觉 我该好好生活 我该好好生活 不知不觉

本系列目录链接

深度学习实战(基于pytroch)系列(一)环境准备
深度学习实战(基于pytroch)系列(二)数学基础
深度学习实战(基于pytroch)系列(三)数据操作
深度学习实战(基于pytroch)系列(四)线性回归原理及实现
深度学习实战(基于pytroch)系列(五)线性回归的pytorch实现
深度学习实战(基于pytroch)系列(六)softmax回归原理
深度学习实战(基于pytroch)系列(七)softmax回归从零开始使用python代码实现
深度学习实战(基于pytroch)系列(八)softmax回归基于pytorch的代码实现
深度学习实战(基于pytroch)系列(九)多层感知机原理
深度学习实战(基于pytroch)系列(十)多层感知机实现
深度学习实战(基于pytroch)系列(十一)模型选择、欠拟合和过拟合
深度学习实战(基于pytroch)系列(十二)dropout
深度学习实战(基于pytroch)系列(十三)权重衰减
深度学习实战(基于pytroch)系列(十四)正向传播、反向传播
深度学习实战(基于pytroch)系列(十五)模型构造
深度学习实战(基于pytroch)系列(十六)模型参数
深度学习实战(基于pytroch)系列(十七)自定义层
深度学习实战(基于pytroch)系列(十八) PyTorch中的模型读取和存储
深度学习实战(基于pytroch)系列(十九) PyTorch的GPU计算
深度学习实战(基于pytroch)系列(二十)二维卷积层
深度学习实战(基于pytroch)系列(二十一)卷积操作中的填充和步幅
深度学习实战(基于pytroch)系列(二十二)多通道输入输出
深度学习实战(基于pytroch)系列(二十三)池化层
深度学习实战(基于pytroch)系列(二十四)卷积神经网络(LeNet)
深度学习实战(基于pytroch)系列(二十五)深度卷积神经网络(AlexNet)
深度学习实战(基于pytroch)系列(二十六)VGG
深度学习实战(基于pytroch)系列(二十七)网络中的网络(NiN)
深度学习实战(基于pytroch)系列(二十八)含并行连结的网络(GoogLeNet)
深度学习实战(基于pytroch)系列(二十九)批量归一化(batch normalization)
深度学习实战(基于pytroch)系列(三十) 残差网络(ResNet)
深度学习实战(基于pytroch)系列(三十一) 稠密连接网络(DenseNet)
深度学习实战(基于pytroch)系列(三十二) 语言模型
深度学习实战(基于pytroch)系列(三十三)循环神经网络RNN
深度学习实战(基于pytroch)系列(三十四)语言模型数据集(周杰伦专辑歌词)
深度学习实战(基于pytroch)系列(三十五)循环神经网络的从零开始实现
深度学习实战(基于pytroch)系列(三十六)循环神经网络的pytorch简洁实现
深度学习实战(基于pytroch)系列(三十七)通过时间反向传播
深度学习实战(基于pytroch)系列(三十八)门控循环单元(GRU)从零开始实现
深度学习实战(基于pytroch)系列(三十九)门控循环单元(GRU)pytorch简洁实现
深度学习实战(基于pytroch)系列(四十)长短期记忆(LSTM)从零开始实现
深度学习实战(基于pytroch)系列(四十一)长短期记忆(LSTM)pytorch简洁实现
深度学习实战(基于pytroch)系列(四十二)双向循环神经网络pytorch实现
深度学习实战(基于pytroch)系列(四十三)深度循环神经网络pytorch实现
深度学习实战(基于pytroch)系列(四十四) 优化与深度学习

相关推荐
阳明山水32 分钟前
销量预测2025—2026:从模型竞赛到系统融合的范式转型
人工智能·深度学习·算法·机器学习·架构
克里斯蒂亚诺更新35 分钟前
Transformer(注意力机制)不按顺序-全盘扫描
人工智能·深度学习·transformer
147API1 小时前
蒸馏项目什么时候该停,怎样切换到RAG、微调或模型路由
人工智能·深度学习·机器学习·数据挖掘
YOLO视觉与编程2 小时前
YOLO / Labelme目标分割数据集增强扩充软件v1.0.0适用于YOLO全版本
人工智能·深度学习·yolo·计算机视觉
茗鹤APS和MES2 小时前
工业排产:AI可赋能,APS不可替代
人工智能·深度学习·机器学习
zx_741484813 小时前
【机器学习入门】PyTorch 神经网络、卷积神经网络 CNN 实现矿物分类
pytorch·神经网络·机器学习
程序猿编码3 小时前
基于GGML的C++17轻量化语音推理引擎:说话人识别与语音分析技术全解析
开发语言·c++·pytorch·深度学习·神经网络·大模型
Thomas.Sir4 小时前
第26课:TensorFlow|循环神经网络RNN原理【时序数据处理、序列依赖关系讲解】
人工智能·rnn·tensorflow
jay神4 小时前
深度学习如何确定Batch Size的大小?
人工智能·深度学习·计算机视觉·毕业设计·课程设计·batch
AI人工智能+4 小时前
证件阅读机,通过光学扫描、AI大脑、芯片感应三双“眼睛”协同工作,借助深度学习OCR引擎完成证卡识别与结构化输出
深度学习·ocr·智能硬件·证件阅读机