深度学习实战(基于pytroch)系列(四十一)长短期记忆(LSTM)pytorch简洁实现

长短期记忆(LSTM)pytorch简洁实现

上一节我们已经"从零开始实现了LSTM",本节将使用PyTorch来更简洁地实现长短期记忆(LSTM)语言模型。

首先,我们读取周杰伦专辑歌词数据集。这一步和上节的代码基本一致。

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的循环神经网络层_layer 。

python 复制代码
num_hiddens = 256

lstm_layer = nn.LSTM(input_size=vocab_size, hidden_size=num_hiddens)

接下来我们初始化隐藏状态。这里和我前面学的rnn和gru模型不同,因为LSTM需要两个状态:隐藏状态(hidden state)和细胞状态(cell state)。

python 复制代码
batch_size = 2
num_steps = 35

h_0 = torch.zeros((1, batch_size, num_hiddens))  # 隐藏状态
c_0 = torch.zeros((1, batch_size, num_hiddens))  # 细胞状态
state = (h_0, c_0)

下面的代码我们可以看到经过lstm_layer层后得到的输出形状。

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

输出

torch.Size([35, 2, 256])

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

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(gru_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 20.578601, time 0.03 sec
  • 分开 我不不 你不要再我 我不不 不不不 我不不不 我不不不 不不再 我不不再 我不不再 我不不不
  • 不分开 我不不 不知道 我想要你 我不不 我不不不 不不再 我不不再 我不不不 我不不不 我不不不 我不
  • epoch 100, perplexity 2.377903, time 0.03 sec
  • 分开 我的可爱 你 我的没有你 你的没有 有一直 对不 我想要你 你的我不 不要再我 我不要 你的我不
  • 不分开 我不起 你知道 我想要你 汉堡 我想要你这样打我妈妈 我的你爱你 我不要再想要 我不要 我不 不
  • epoch 150, perplexity 1.290544, time 0.03 sec
  • 分开 我的可 却为人看着我 别发抖 快给我抬起头 有话去对医药箱说 别怪我 别怪我 说你怎么面对我 甩
  • 不分开我想要你 想要再想要 我想要 你想要 我想 回回忆 爱上的让我说 说你 什么我不懂 说你了其实我的
  • epoch 200, perplexity 1.089689, time 0.03 sec
  • 分开 我的可 你为什么我打我 爸爸你的那 你怎么我 想要和你的宙去 想要和你融化在一起 融化在宇宙里
  • 不分开 我的可 不要再想 我想要 你 我的回 不要再这样打的手 我的可爱你 你想要我想想你 想要 你对我
  • epoch 250, perplexity 1.051081, time 0.03 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)系列(四十四) 优化与深度学习

相关推荐
油泼辣子多加3 分钟前
【DL】Transformer算法应用
人工智能·深度学习·算法·机器学习·transformer
剑穗挂着新流苏3129 分钟前
108_深度学习中的“瘦身术”:最大池化层(MaxPool2d)原理与实战
pytorch·深度学习·计算机视觉
qq_5710993510 分钟前
学习周报三十七
人工智能·深度学习·学习
智算菩萨12 分钟前
与AI一起记忆:从分布式记忆到AI策划记忆与人机共忆——文献精读
论文阅读·人工智能·分布式·深度学习·ai·文献
此方ls17 分钟前
机器学习深度学习二——GAN网络
深度学习·机器学习·生成对抗网络
grant-ADAS9 小时前
记录paddlepaddleOCR从环境到使用默认模型,再训练自己的数据微调模型再推理
人工智能·深度学习
软件算法开发9 小时前
基于海象优化算法的LSTM网络模型(WOA-LSTM)的一维时间序列预测matlab仿真
算法·matlab·lstm·一维时间序列预测·woa-lstm·海象优化
云和数据.ChenGuang9 小时前
魔搭社区 测试AI案例故障
人工智能·深度学习·机器学习·ai·mindstudio
小锋学长生活大爆炸9 小时前
【工具】无需Token!WebAI2API将网页AI转为API使用
人工智能·深度学习·chatgpt·openclaw
_张一凡10 小时前
【多模态模型学习】从零手撕一个Vision Transformer(ViT)模型实战篇
人工智能·深度学习·transformer