使用Pytorch从零开始构建RNN

在这篇文章中,我们将了解 RNN(即循环神经网络),并尝试通过 PyTorch 从头开始​​实现其中的部分内容。是的,这并不完全是从头开始,因为我们仍然依赖 PyTorch autograd 来计算梯度并实现反向传播,但我仍然认为我们也可以从这个实现中收集到有价值的见解。

有关 RNN 的简要介绍性概述,我建议您查看上一篇文章,其中我们不仅探讨了 RNN 是什么及其工作原理,还探讨了如何使用 Keras 实现 RNN 模型。这次,我们将使用 PyTorch,但采取更实际的方法从头开始构建一个简单的 RNN。

完全免责声明,这篇文章很大程度上改编自PyTorch 教程这个 PyTorch 教程。我修改并改变了预处理和训练中涉及的一些步骤。我仍然建议您将其作为补充材料查看。考虑到这一点,让我们开始吧。

数据准备

任务是建立一个简单的分类模型,可以根据名字正确确定一个人的国籍。更简单地说,我们希望能够分辨出特定名称的来源。

下载

我们将使用 PyTorch 教程中的一些标记数据。我们只需输入即可下载

!curl -O https://download.pytorch.org/tutorial/data.zip; unzip data.zip

此命令将下载文件并将其解压到当前目录中,文件夹名称为data.

现在我们已经下载了所需的数据,让我们更详细地看一下数据。首先,这是我们需要的依赖项。

import os
import random
from string import ascii_lettersimport torch
from torch import nn
import torch.nn.functional as F
from unidecode import unidecode_ = torch.manual_seed(42)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

我们首先指定一个目录,然后尝试打印出其中的所有标签。然后我们可以构建一个字典,将语言映射到数字标签。

data_dir = "./data/names"lang2label = {file_name.split(".")[0]: torch.tensor([i], dtype=torch.long)for i, file_name in enumerate(os.listdir(data_dir))
}

我们看到一共有18种语言。我将每个标签包装为张量,以便我们可以在训练期间直接使用它们。

lang2label
{'Czech': tensor([0]),'German': tensor([1]),'Arabic': tensor([2]),'Japanese': tensor([3]),'Chinese': tensor([4]),'Vietnamese': tensor([5]),'Russian': tensor([6]),'French': tensor([7]),'Irish': tensor([8]),'English': tensor([9]),'Spanish': tensor([10]),'Greek': tensor([11]),'Italian': tensor([12]),'Portuguese': tensor([13]),'Scottish': tensor([14]),'Dutch': tensor([15]),'Korean': tensor([16]),'Polish': tensor([17])}

让我们将语言数量存储在某个变量中,以便稍后在模型声明中使用它,特别是当我们指定最终输出层的大小时。

num_langs = len(lang2label)

预处理

现在,让我们对名称进行预处理。我们首先要用来unidecode标准化所有名称并删除任何锐利符号或类似符号。例如,

unidecode("Ślusàrski")
'Slusarski'

一旦我们有了解码后的字符串,我们就需要将其转换为张量,以便模型可以处理它。这可以首先通过构建映射来完成char2idx,如下所示。

char2idx = {letter: i for i, letter in enumerate(ascii_letters + " .,:;-'")}
num_letters = len(char2idx); num_letters
59

我们看到我们的字符词汇表中共有 59 个标记。这包括空格和标点符号,例如 .,:;-' . This also means that each name will now be expressed as a tensor of size (num_char, 59) ; in other words, each character will be a tensor of size (59,)。我们现在可以构建一个完成此任务的函数,如下所示:

def name2tensor(name):tensor = torch.zeros(len(name), 1, num_letters)for i, char in enumerate(name):tensor[i][0][char2idx[char]] = 1return tensor

如果你仔细阅读代码,你会发现输出张量的大小是(num_char, 1, 59),这与上面的解释不同。嗯,这个额外维度的原因是我们在本例中使用的批量大小为 1。在 PyTorch 中,RNN 层期望输入张量的大小为(seq_len, batch_size, input_size)。由于每个名称都有不同的长度,因此为了简单起见,我们不会对输入进行批处理,而只是将每个输入用作单个批处理。有关更详细的讨论,请查看此论坛讨论。

name2tensor()让我们使用虚拟输入快速验证函数的输出。

name2tensor("abc")
tensor([[[1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0.]],[[0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0.]],[[0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.,0., 0., 0., 0., 0., 0., 0., 0.]]])

数据集创建

现在我们需要构建包含所有预处理步骤的数据集。让我们将所有解码和转换的张量收集在一个列表中,并附上标签。可以从文件名轻松获取标签,例如german.txt.

tensor_names = []
target_langs = []for file in os.listdir(data_dir):with open(os.path.join(data_dir, file)) as f:lang = file.split(".")[0]names = [unidecode(line.rstrip()) for line in f]for name in names:try:tensor_names.append(name2tensor(name))target_langs.append(lang2label[lang])except KeyError:pass

我们可以将其包装在 PyTorchDataset类中,但为了简单起见,我们只使用一个好的旧for循环将这些数据输入到我们的模型中。由于我们处理的是普通列表,因此我们可以轻松地使用sklearn’strain_test_split()将训练数据与测试数据分开。

from sklearn.model_selection import train_test_splittrain_idx, test_idx = train_test_split(range(len(target_langs)), test_size=0.1, shuffle=True, stratify=target_langs
)train_dataset = [(tensor_names[i], target_langs[i])for i in train_idx
]test_dataset = [(tensor_names[i], target_langs[i])for i in test_idx
]

让我们看看我们有多少训练和测试数据。请注意,我们使用的 test_size为 0.1。

print(f"Train: {len(train_dataset)}")
print(f"Test: {len(test_dataset)}")
Train: 18063
Test: 2007

模型

我们将构建两个模型:一个简单的 RNN(将从头开始构建)和一个使用 PyTorch 层的基于 GRU 的模型。

简单循环神经网络

现在我们可以构建我们的模型了。这是一个非常简单的 RNN,它采用单个字符张量表示作为输入,并产生一些预测和隐藏状态,可在下一次迭代中使用。请注意,它只是一些在隐藏状态计算期间应用了 sigmoid 非线性的全连接层。

class MyRNN(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(MyRNN, self).__init__()self.hidden_size = hidden_sizeself.in2hidden = nn.Linear(input_size + hidden_size, hidden_size)self.in2output = nn.Linear(input_size + hidden_size, output_size)def forward(self, x, hidden_state):combined = torch.cat((x, hidden_state), 1)hidden = torch.sigmoid(self.in2hidden(combined))output = self.in2output(combined)return output, hiddendef init_hidden(self):return nn.init.kaiming_uniform_(torch.empty(1, self.hidden_size))

我们init_hidden()在每个新批次开始时都会打电话。为了更容易训练和学习,我决定使用kaiming_uniform_()来初始化这些隐藏状态。

我们现在可以构建模型并开始训练它。

hidden_size = 256
learning_rate = 0.001model = MyRNN(num_letters, hidden_size, num_langs)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

我意识到训练这个模型非常不稳定,正如你所看到的,损失上下跳跃了很多。尽管如此,我不想为难我的 13 英寸 MacBook Pro,所以我决定在两个epochs停止。

num_epochs = 2
print_interval = 3000for epoch in range(num_epochs):random.shuffle(train_dataset)for i, (name, label) in enumerate(train_dataset):hidden_state = model.init_hidden()for char in name:output, hidden_state = model(char, hidden_state)loss = criterion(output, label)optimizer.zero_grad()loss.backward()nn.utils.clip_grad_norm_(model.parameters(), 1)optimizer.step()if (i + 1) % print_interval == 0:print(f"Epoch [{epoch + 1}/{num_epochs}], "f"Step [{i + 1}/{len(train_dataset)}], "f"Loss: {loss.item():.4f}")
Epoch [1/2], Step [3000/18063], Loss: 0.0390
Epoch [1/2], Step [6000/18063], Loss: 1.0368
Epoch [1/2], Step [9000/18063], Loss: 0.6718
Epoch [1/2], Step [12000/18063], Loss: 0.0003
Epoch [1/2], Step [15000/18063], Loss: 1.0658
Epoch [1/2], Step [18000/18063], Loss: 1.0021
Epoch [2/2], Step [3000/18063], Loss: 0.0021
Epoch [2/2], Step [6000/18063], Loss: 0.0131
Epoch [2/2], Step [9000/18063], Loss: 0.3842
Epoch [2/2], Step [12000/18063], Loss: 0.0002
Epoch [2/2], Step [15000/18063], Loss: 2.5420
Epoch [2/2], Step [18000/18063], Loss: 0.0172

现在我们可以测试我们的模型。我们可以查看其他指标,但准确性是迄今为止最简单的,所以我们就这样吧。

num_correct = 0
num_samples = len(test_dataset)model.eval()with torch.no_grad():for name, label in test_dataset:hidden_state = model.init_hidden()for char in name:output, hidden_state = model(char, hidden_state)_, pred = torch.max(output, dim=1)num_correct += bool(pred == label)print(f"Accuracy: {num_correct / num_samples * 100:.4f}%")
Accuracy: 72.2471%

该模型的准确率高达 72%。这非常糟糕,但考虑到模型非常简单,而且我们只训练了两个 epoch 的模型,我们可以放松下来,享受短暂的快乐,因为知道简单的 RNN 模型至少能够学到一些东西。

让我们通过一些具体示例来看看我们的模型表现如何。下面是一个接受字符串作为输入并输出解码预测的函数。

label2lang = {label.item(): lang for lang, label in lang2label.items()}def myrnn_predict(name):model.eval()tensor_name = name2tensor(name)with torch.no_grad():hidden_state = model.init_hidden()for char in tensor_name:output, hidden_state = model(char, hidden_state)_, pred = torch.max(output, dim=1)model.train()    return label2lang[pred.item()]

我不知道这些名字是否真的在训练或测试集中;这些只是我想出的一些随机名称,我认为这些名称相当合理。瞧,结果是有希望的。

myrnn_predict("Mike")
'English'
myrnn_predict("Qin")
'Chinese'
myrnn_predict("Slaveya")
'Russian'

该模型似乎已将所有名称分类为正确的类别!

PyTorch GRU

这很酷,我可能可以停在这里,但我想看看这个自定义模型与使用 PyTorch 层的模型相比如何。对于我们简单的 RNN 来说,GRU 可能不太公平,但让我们看看它的表现如何。

class GRUModel(nn.Module):def __init__(self, num_layers, hidden_size):super(GRUModel, self).__init__()self.num_layers = num_layersself.hidden_size = hidden_sizeself.gru = nn.GRU(input_size=num_letters, hidden_size=hidden_size, num_layers=num_layers,)self.fc = nn.Linear(hidden_size, num_langs)def forward(self, x):hidden_state = self.init_hidden()output, hidden_state = self.gru(x, hidden_state)output = self.fc(output[-1])return outputdef init_hidden(self):return torch.zeros(self.num_layers, 1, self.hidden_size).to(device)

让我们声明模型和与之配套的优化器。请注意,我们使用的是两层 GRU,它已经比我们当前的 RNN 实现多了一层。

model = GRUModel(num_layers=2, hidden_size=hidden_size)
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for epoch in range(num_epochs):random.shuffle(train_dataset)for i, (name, label) in enumerate(train_dataset):output = model(name)loss = criterion(output, label)optimizer.zero_grad()loss.backward()optimizer.step()if (i + 1) % print_interval == 0:print(f"Epoch [{epoch + 1}/{num_epochs}], "f"Step [{i + 1}/{len(train_dataset)}], "f"Loss: {loss.item():.4f}")
Epoch [1/2], Step [3000/18063], Loss: 1.8497
Epoch [1/2], Step [6000/18063], Loss: 0.4908
Epoch [1/2], Step [9000/18063], Loss: 1.0299
Epoch [1/2], Step [12000/18063], Loss: 0.0855
Epoch [1/2], Step [15000/18063], Loss: 0.0053
Epoch [1/2], Step [18000/18063], Loss: 2.6417
Epoch [2/2], Step [3000/18063], Loss: 0.0004
Epoch [2/2], Step [6000/18063], Loss: 0.0008
Epoch [2/2], Step [9000/18063], Loss: 0.1446
Epoch [2/2], Step [12000/18063], Loss: 0.2125
Epoch [2/2], Step [15000/18063], Loss: 3.7883
Epoch [2/2], Step [18000/18063], Loss: 0.4862

训练一开始看起来比较稳定,但我们确实在第二个时期结束时看到了奇怪的跳跃。部分原因是我没有对此 GRU 模型使用梯度裁剪,并且应用裁剪后我们可能会看到更好的结果。

让我们看看这个模型的准确性。

num_correct = 0model.eval()with torch.no_grad():for name, label in test_dataset:output = model(name)_, pred = torch.max(output, dim=1)num_correct += bool(pred == label)print(f"Accuracy: {num_correct / num_samples * 100:.4f}%")
Accuracy: 81.4150%

我们得到的这个模型的准确率约为 80%。这比我们的简单 RNN 模型要好,这在某种程度上是预料之中的,因为它有一个附加层并且使用了更复杂的 RNN 单元模型。

让我们看看这个模型如何预测给定的一些原始名称字符串。

def pytorch_predict(name):model.eval()tensor_name = name2tensor(name)with torch.no_grad():output = model(tensor_name)_, pred = torch.max(output, dim=1)model.train()return label2lang[pred.item()]
pytorch_predict("Jake")
'English'
pytorch_predict("Qin")
'Chinese'
pytorch_predict("Fernando")
'Spanish'
pytorch_predict("Demirkan")
'Russian'

最后一个很有趣,因为这是我一位土耳其好朋友的名字。该模型显然无法告诉我们这个名字是土耳其语,因为它没有看到任何标记为土耳其语的数据点,但它告诉我们这个名字可能属于它所训练的 18 个标签中的哪个国籍。这显然是错误的,但在某些方面也许相差并不远。例如,至少它没有说日语。对于该模型来说,这也不是完全公平的游戏,因为有许多名字可能被描述为跨国的:也许有一个俄罗斯人的名字叫 Demirkan。

结论

通过实现这个 RNN,我学到了很多关于 RNN 的知识。诚然,它很简单,并且与 PyTorch 基于层的方法有所不同,因为它需要我们手动循环每个字符,但它的低级性质迫使我更多地思考张量维度以及具有张量维度的目的。隐藏状态和输出之间的划分。这也很好地提醒了我们 RNN 是如何难以训练的。

在接下来的文章中,我们将研究序列到序列模型,简称 seq2seq。自从听说 seq2seq 以来,我就对将一种数据形式转换为另一种数据形式的力量着迷。尽管由于本地机器的限制,这些模型无法在 CPU 上进行实际训练,但我认为实现它们本身将是一个令人兴奋的挑战。

本博文译自Jake Tae的博文。

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

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

相关文章

Apache访问控制

服务器相关的访问控制 Options指令 Options指令是Apache服务器配置文件中的一个重要指令,它可以用于控制特定目录启用哪些服务器特性。Options指令可以在Apache服务器的核心配置、虚拟主机配置、特定目录配置以及.htaccess文件中使用。 以下是一些常用的服务器特性选项: N…

Django(九、cookie与session)

文章目录 一、cookie与session的介绍HTTP四大特性 cookiesession Django操作cookie三板斧基于cookie的登录功能 一、cookie与session的介绍 在讲之前我们先来回忆一下HTTP的四大特性 HTTP四大特性 1.基于请求响应 2.基于TIC、IP作用于应用层上的协议 3.无状态 保存…

二叉查找(排序)树你需要了解一下

简介 二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树,是一种重要的数据结构。 它有以下特性: 若左子树不空,则左子树上所有结点的…

目标检测YOLO系列从入门到精通技术详解100篇-【图像处理】目标检测

目录 几个高频面试题目 如何在超大分辨率的图片中检测目标? 1当超大分辨率图像邂逅目标检测任务 2You Only Look Twice

边缘计算多角色智能计量插座 x 资产显示标签:实现资产追踪与能耗管理的无缝结合

越来越多智慧园区、智慧工厂、智慧医院、智慧商业、智慧仓储物流等企业商家对精细化、多元化智能生态应用场景的提升,顺应国家节能减排、环保的时代潮流,设计一款基于融合以太网/WiFi/蓝牙智能控制的智能多角色插座应运而生,赋予智能插座以遥…

大数据学习(23)-hive on mapreduce对比hive on spark

&&大数据学习&& 🔥系列专栏: 👑哲学语录: 承认自己的无知,乃是开启智慧的大门 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言📝支持一下博主哦&#x1f91…

uniapp实现表单弹窗

uni.showModal({title: 删除账户,confirmColor:#3A3A3A,cancelColor:#999999,confirmText:确定,editable:true,//显示content:请输入“delete”删除账户,success: function (res) {console.log(res)if(res.confirm){if(res.contentdelete){console.log(123123123213)uni.setSto…

PCIE链路训练-状态跳转1

A:12ms超时,或者再任何lane上检测到Electrical Idle Exit; B: 1.发送“receiver detection”之后没有一个lane的接收逻辑被rx检测到 2.不满足条件c,比如两次detection出现差别; C:发送端在没…

凸优化基础与应用

诸神缄默不语-个人CSDN博文目录 文章目录 1. 线性规划用SciPy求解 2. 二次规划3. 半定规划4. 锥规划 凸优化是数学优化的一个重要分支,广泛应用于各种工程和科学领域。它的核心特征在于优化问题的目标函数和约束条件是凸的,这使得找到全局最优解变得可行…

Ps:背景橡皮擦工具抠图实例

背景橡皮擦工具 Background Eraser Tool由于是一个破坏性的工具(直接删除像素)而少被人使用。 其实,它不仅是一个功能强大的抠图工具,也是可以转换为非破坏性运用的。 原图(注:图片来自网络) 效…

微软离Altman越近,离OpenAI就越远!

大数据产业创新服务媒体 ——聚焦数据 改变商业 在OpenAI这场连续剧中(之所以说是连续剧,这个事情肯定没完,后面肯定还会出续集),让我倍感意外的是,Altman刚跟OpenAI分手,“离婚手续”都还没办…

使用Pytorch从零开始构建WGAN

引言 在考虑生成对抗网络的文献时,Wasserstein GAN 因其与传统 GAN 相比的训练稳定性而成为关键概念之一。在本文中,我将介绍基于梯度惩罚的 WGAN 的概念。文章的结构安排如下: WGAN 背后的直觉;GAN 和 WGAN 的比较;…

selenium新版使用find_element/find_elements函数锁定元素(替换原有find_element_by_xx)

css选择器请参考:网络爬虫之css选择器 原来的find_element_by_xx都被修改为find_element(返回匹配到的第一个元素)或find_elements(返回全部的匹配元素) from selenium.webdriver.common.by import By示例程序 选择…

【Q3——30min】

1、介绍一下数据库的三大范式 第一范式(1NF):属性不可分割,即每个属性都是不可分割的原子项。(实体的属性即表中的列) 第二范式(2NF):满足第一范式;且不存在部分依赖,即非主属性必须完全依赖于主属性。(主属性即主键&a…

minio集群部署(k8s内)

一、前言 minio的部署有几种方式,分别是单节点单磁盘,单节点多磁盘,多节点多磁盘三种方式,本次部署使用多节点多磁盘的方式进行部署,minio集群多节点部署最低要求需要4个节点,集群扩容时也是要求扩容的节点…

2、数仓理论概述与相关概念

1、问:数据仓库 建设过程中 经常会遇到那些问题? 模型(逻辑)重复建设 数据不一致性 维度不一致:命名、维度属性值、维度定义 指标不一致:命名、计算口径 数据不规范(字段命名、表名、分层、主题命名规范) 2、OneData数据建设核心方…

python爬虫HMAC加密案例:某企业信息查询网站

声明: 该文章为学习使用,严禁用于商业用途和非法用途,违者后果自负,由此产生的一切后果均与作者无关 一、找出需要加密的参数 js运行 atob(‘aHR0cHM6Ly93d3cucWNjLmNvbS93ZWIvc2VhcmNoP2tleT0lRTQlQjglODclRTglQkUlQkUlRTklOUI…

飞桨——总结PPOCRLabel中遇到的坑

操作系统:win10 python环境:python3.9 paddleocr项目版本:2.7 1.报错:ModuleNotFoundError: No module named Polygon(已解决) 已解决所以没有复现报错内容 尝试方法一:直接使用pip命令安装&…

oracle rac 19.3安装补丁19.19

使用opatchauto apply DIR来进行安装 1.升级之前先备份一下GRID_HOME和ORACLE_HOME 2.现在新的opatch安装不需要先停止集群和数据库,在升级过程中,他会自动关闭和启动集群 3.先将OPatch(P6880880)包拷贝到$GRID_HOME和$ORACLE_HOM…

【Web安全】sqlmap的使用笔记及示例

【Web安全】sqlmap的使用笔记 文章目录 【Web安全】sqlmap的使用笔记1. 目标2. 脱库2.1. 脱库(补充) 3. 其他3.1. 其他(补充) 4. 绕过脚本tamper讲解 1. 目标 操作作用必要示例-u指定URL,检测注入点sqlmap -u http://…