文章目录

  • 0 前言
  • 1 数据与说明
    • 数据下载
    • 任务说明
  • 2 代码
    • 模型
    • 训练
    • 画图
    • 预测

0 前言

来自官方教程,对于萌新学习用Pytorch做NLP任务有很大的帮助,就翻译过来,顺便自己Mark一下,因为打开官网有时候太慢了,还是看自己写的Blog比较快。另外,之前在做⭐ 李宏毅2020机器学习作业4-RNN:句子情感分类的时候,代码看起来有些难度。之前的几个作业都还能看懂,但是作业4实在跳跃度太大了,就先拿这几个练个手。

这是官方教程中同一个系列的文章,总共有3篇:

  • 第一篇,教你搭建一个字母级别(character-level)的RNN,对名字进行分类,是一个分类的任务。
  • 第二篇,教你搭建一个字母级别(character-level)的RNN,生成名字,是一个自然语言生成的任务。
  • 第三篇,教你搭建一个Seq2Seq的RNN,进行机器翻译,也是一个自然语言生成的任务。Seq2Seq是序列到序列的模型,类似于单词级别(word-level)的RNN。

博主更新完的本系列文章:

  • 第一篇:【Pytorch官方教程】从零开始自己搭建RNN1 - 字母级RNN的分类任务
  • 第二篇:【Pytorch官方教程】从零开始自己搭建RNN2 - 字母级RNN的生成任务
  • 第三篇:【Pytorch官方教程】从零开始自己搭建RNN3 - 含注意力机制的Seq2Seq机器翻译模型

这是第二篇。关于RNN的基础原理等,请见第一篇。

1 数据与说明

数据下载

数据下载链接:点击下载

数据是一个data.zip压缩包,解压后的目录树如下所示:

D:.
│  eng-fra.txt
│
└─namesArabic.txtChinese.txtCzech.txtDutch.txtEnglish.txtFrench.txtGerman.txtGreek.txtIrish.txtItalian.txtJapanese.txtKorean.txtPolish.txtPortuguese.txtRussian.txtScottish.txtSpanish.txtVietnamese.txt

eng-fra.txt 是第三篇翻译任务中要用到的,这次我们只用到 /name 这个文件夹下的18个文件,每个文件以语言命名,格式为:[Language].txt。打开后,里面是该语言中常用的姓/名。

比如:打开我们最熟悉的 Chinese.txt,可以看到每一行是一个姓或者名(有一些姓/名确实有点点奇怪,但整体来说问题不大)。

Ang
Au-Yong
Bai
Ban
Bao
Bei
Bian
Bui
Cai
Cao
Cen
……

任务说明

这次任务的目标是:输入一个国家的语言名,和名字的首字母缩写,模型自动生成名字。

比如:

> python sample.py Russian RUS
Rovakov
Uantov
Shavakov> python sample.py German GER
Gerren
Ereng
Rosher> python sample.py Spanish SPA
Salla
Parer
Allan> python sample.py Chinese CHI
Chan
Hang
Iun

这次,我们仍然要自己搭建一个RNN,由一些线性的全连接层组成。和第一篇预测类别不同之处在于,这次我们要输入一个类别,然后每次输出一个字母。这样一个循环预测下一个字母,生成一种语言的模型通常叫做语言模型(language model)。

2 代码

与第一篇相同,首先是数据预处理。这次仍然是字母级别的RNN,因此是对字母进行one-hot编码。

把所有的 /name/[Language].txt 文件读进来。

n_letters 表示所有字母的数量。这次多加了一个特殊符号 <EOS> 。因为是文本生成,所以需要有一个符号来结束文本生成的过程。我们设定,当生成 <EOS> 的时候,就结束RNN的循环。

因为某些语言的字母和常见的英文字母不太一样,所以我们需要把它转化成普普通通的英文字母,用到了 unicodeToAscii() 函数。

from io import open
import glob
import os
import unicodedata
import stringall_letters = string.ascii_letters + " .,;'-"
n_letters = len(all_letters) + 1 # 加上一个 EOS 标记def findFiles(path): return glob.glob(path)# Turn a Unicode string to plain ASCII, thanks to https://stackoverflow.com/a/518232/2809427
def unicodeToAscii(s):return ''.join(c for c in unicodedata.normalize('NFD', s)if unicodedata.category(c) != 'Mn'and c in all_letters)# 读入文件 filename, 分行
def readLines(filename):lines = open(filename, encoding='utf-8').read().strip().split('\n')return [unicodeToAscii(line) for line in lines]# 建立一个词典 category_lines = {category: lines} , lines = [names...]
category_lines = {}
all_categories = []
for filename in findFiles('data/names/*.txt'):category = os.path.splitext(os.path.basename(filename))[0]all_categories.append(category)lines = readLines(filename)category_lines[category] = linesn_categories = len(all_categories)if n_categories == 0 :raise RuntimeError('Data not found. Make sure that you downloaded data ''from https://download.pytorch.org/tutorial/data.zip and extract it to ''the current directory.')print('# categories:', n_categories, all_categories)
print(unicodeToAscii("O'Néàl"))

Out:

# categories: 18 ['Greek', 'Dutch', 'Irish', 'Arabic', 'Korean', 'French', 'Spanish', 'German', 'Portuguese', 'Italian', 'Vietnamese', 'Russian', 'Scottish', 'Chinese', 'English', 'Japanese', 'Czech', 'Polish']
O'Neal

和第一篇一样,需要把所有的值变成 Tensor :

  • inputTensor()函数:对输入的单词 line 进行one-hot 编码,大小为 < line_length × 1 × n_letters >

  • categoryTensor()函数:对类别进行 one-hot 编码,大小为 <1 x n_categories> ,和xtx_txt​、ht−1h_{t-1}ht−1​ 拼接到一起 [category,xt,ht−1][category, x_t, h_{t-1}][category,xt​,ht−1​]作为RNN的输入

  • targetTensor()函数:把目标值转换成Tensor,目标值不是 one-hot 编码,只是一个存储索引的序列

文本生成的过程:每一步,根据当前输入的字母,预测下一步输出的字母。在这里,预测得到的字母就是生成的字母

根据训练集,我们需要创建样本,组成 input - target 对。比如,训练集中的一个词是 “ABCD”,首先,我们给它加上结束标记 “<EOS>” ,变成 “ABCD<EOS>”。然后,前一个词是input,后一个词是target,就可以创建成 (“A”, “B”), (“B”, “C”), (“C”, “D”), (“D”, “<EOS>”) 的样本对。 input 是one-hot 编码,target 则是普通的索引,可以看成是一个从 n_lettersn_letters 的多分类任务。比如:

  • (“A”, “B”) = ([1,0,0,0,…,0,0],1)( [1,0,0,0,\ldots,0,0], 1)([1,0,0,0,…,0,0],1)
  • (“B”, “C”) = ([0,1,0,0,…,0,0],2)( [0,1,0,0,\ldots,0,0], 2)([0,1,0,0,…,0,0],2)
  • (“C”, “D”) = ([0,0,1,0,…,0,0],3)( [0,0,1,0,\ldots,0,0], 3)([0,0,1,0,…,0,0],3)
  • (“D”, “<EOS>”) = ([0,0,0,1,…,0,0],4)( [0,0,0,1,\ldots,0,0], 4)([0,0,0,1,…,0,0],4)
import torch
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)# category 的 one-hot编码
def categoryTensor(category):li = all_categories.index(category)tensor = torch.zeros(1, n_categories)tensor[0][li] = 1return tensor.to(device)# input 的 one-hot编码 ,从第一个字母到最后一个字母(不包含 EOS)
def inputTensor(line):tensor = torch.zeros(len(line), 1, n_letters)for li in range(len(line)):letter = line[li]tensor[li][0][all_letters.find(letter)] = 1return tensor.to(device)# output 的 one-hot编码 ,从第二个字母到最后的 EOS 符
def targetTensor(line):letter_indexes = [all_letters.find(line[li])for li in range(1,len(line))]letter_indexes.append(n_letters - 1) # EOS的索引return torch.LongTensor(letter_indexes).to(device)

与第一篇一样,从训练集中随机采样。

import random# 从数组 l 中随机选一个元素
def randomChoice(l):return l[random.randint(0,len(l)-1)]# 随机采样一个 category,从该 category 中随机采样一个姓名line
def randomTrainingPair():category = randomChoice(all_categories)line = randomChoice(category_lines[category])return category, line# 从一个随机采样的 category-line 对中构建训练样本,
# 包含 category 的tensor, input 的 tensor, 和 target 的 tensors
def randomTrainingExample():category, line = randomTrainingPair()category_tensor = categoryTensor(category)input_line_tensor = inputTensor(line)target_line_tensor = targetTensor(line)return category_tensor, input_line_tensor, target_line_tensor

模型

搭建本次任务的RNN模型,与第一篇不同的是,这次多了一个 o2o 层,并且用一个 dropout 层来防止过拟合。

  • input_combined = torch.cat((category, input, hidden),1):拼接得到 [category,xt,ht−1][category, x_t, h_{t-1}][category,xt​,ht−1​]
  • hidden = self.i2h(input_combined) : ht=Wh[category,xt,ht−1]h_t= W_h[category, x_t, h_{t-1}]ht​=Wh​[category,xt​,ht−1​]
  • output = self.i2o(input_combined):ot=Wo1[category,xt,ht−1]o_t=W_{o1}[category, x_t, h_{t-1}]ot​=Wo1​[category,xt​,ht−1​]
  • output_combined = torch.cat((hidden, output), 1):ot′=[ht,ot]o'_{t} = [h_t, o_t]ot′​=[ht​,ot​]
  • output = self.o2o(output_combined):ot=Wo2ot′=Wo2[ht,ot]o_t = W_{o2}o'_{t}=W_{o2}[h_t, o_t]ot​=Wo2​ot′​=Wo2​[ht​,ot​]
  • output = self.dropout(output):用 dropout 防止过拟合
  • output = self.softmax(output):用 softmax 把 oto_tot​ 转化成预测字母的概率分布 yty_tyt​
import torch.nn as nnclass RNN(nn.Module):def __init__(self, input_size, hidden_size, output_size):super(RNN,self).__init__() self.hidden_size = hidden_sizeself.i2h = nn.Linear(n_categories + input_size + hidden_size, hidden_size)self.i2o = nn.Linear(n_categories + input_size + hidden_size, output_size)self.o2o = nn.Linear(hidden_size + output_size, output_size)self.dropout = nn.Dropout(0.1)self.softmax = nn.LogSoftmax(dim=1)def forward(self, category, input, hidden):input_combined = torch.cat((category, input, hidden),1)hidden = self.i2h(input_combined)output = self.i2o(input_combined)output_combined = torch.cat((hidden, output), 1)output = self.o2o(output_combined)output = self.dropout(output)output = self.softmax(output)return output, hiddendef initHidden(self):return torch.zeros(1,self.hidden_size).to(device)

训练

在分类任务中,我们只用到了最后一步的 output ,但是在本次的文本生成任务中,要用到每一步的 output ,所以,我们会在每一步都计算损失loss.

因为 output 最后一层经过了 LogSoftmax,所以对应的损失函数依然是NLLLoss(),学习率设置为0.0005

criterion = nn.NLLLoss()learning_rate = 0.0005def train(category_tensor, input_line_tensor, target_line_tensor):target_line_tensor.unsqueeze_(-1)hidden = rnn.initHidden()rnn.zero_grad()loss = 0for i in range(input_line_tensor.size(0)):output, hidden = rnn(category_tensor, input_line_tensor[i], hidden)loss += criterion(output, target_line_tensor[i])loss.backward()for p in rnn.parameters():p.data.add_(p.grad.data, alpha=-learning_rate)return output, loss.item() / input_line_tensor.size(0)

下面正式开始训练模型。

timeSince() 可以计算出训练时间。总共训练n_iters次,每次用1个样本作为训练。每 print_every 次打印当前的训练损失,每 plot_every 次把损失保存到 all_losses 数组中,便于之后画图。

import timedef timeSince(since):now = time.time()s = now-sincereturn '%dm %ds'%(s//60,s%60)n_iters = 100000
print_every = 5000
plot_every = 500all_losses = []
total_loss = 0n_hidden = 128
rnn = RNN(n_letters, n_hidden, n_letters)
rnn = rnn.to(device)start = time.time()for iter in range(1, n_iters + 1):output, loss = train(*randomTrainingExample())total_loss += lossif iter % print_every == 0:print('%s (%d %d%%) %.4f' % (timeSince(start),iter, iter/n_iters*100,loss))if iter % plot_every == 0:all_losses.append(total_loss/plot_every)total_loss = 0

Out:

1m 28s (5000 5%) 2.8628
2m 48s (10000 10%) 3.2724
4m 6s (15000 15%) 1.9587
5m 24s (20000 20%) 2.3308
6m 44s (25000 25%) 2.3424
8m 1s (30000 30%) 3.5255
9m 19s (35000 35%) 1.2786
10m 37s (40000 40%) 1.8138
11m 56s (45000 45%) 2.5911
13m 14s (50000 50%) 2.0981
14m 33s (55000 55%) 3.1198
15m 51s (60000 60%) 2.3281
17m 9s (65000 65%) 2.2316
18m 29s (70000 70%) 3.0411
19m 48s (75000 75%) 3.0554
21m 7s (80000 80%) 2.0455
22m 25s (85000 85%) 2.3849
23m 43s (90000 90%) 2.3806
25m 0s (95000 95%) 1.4392
26m 23s (100000 100%) 2.6739

画图

画出损失函数随着训练的变化情况:

import matplotlib.pyplot as plt
import matplotlib.ticker as tickerplt.figure()
plt.plot(all_losses)

预测

给定模型一个首字母,然后模型生成下一个字母,不断重复,直到遇到 “<EOS>”标记符,停止生成。

  • 创建输入类别category的tensor,开始字母的 tensor 和 初始化隐藏层状态 h0h_0h0​
  • 用首字母生成一个字符串 output_name
  • 在到达最大输出长度前:
    • 给模型输入当前的字母
    • 模型生成下一个字母,和下一个隐藏层状态
    • 如果生成的字母是 “<EOS>”标记符, 停止生成
    • 如果生成的字母是一个常规的字母,把它加入 output_name,并且继续生成
  • 返回最终生成的名字 output_name
max_length = 20def sample(category, start_letter = 'A'):with torch.no_grad():category_tensor = categoryTensor(category)input = inputTensor(start_letter)hidden = rnn.initHidden()output_name = start_letterfor i in range(max_length):output, hidden = rnn(category_tensor,input[0],hidden)topv, topi = output.topk(1)topi = topi[0][0]if topi == n_letters - 1 :breakelse:letter = all_letters[topi]output_name += letterinput = inputTensor(letter)return output_namedef samples(category, start_letters='ABC'):for start_letter in start_letters:print(sample(category,start_letter))samples('Russian', 'RUS')samples('German', 'GER')samples('Spanish', 'SPA')samples('Chinese', 'CHI')

Out:

Rovakov
Uantonov
ShalovevGarterr
Eerter
RoureSantan
Parer
AlananChan
Han
Iun

【Pytorch官方教程】从零开始自己搭建RNN2 - 字母级RNN的生成任务相关推荐

  1. PyTorch 1.0 中文官方教程:使用字符级别特征的RNN网络生成姓氏

    译者:hhxx2015 作者: Sean Robertson 在上一个 例子 中我们使用RNN网络对名字所属的语言进行分类. 这一次我们会反过来根据语言生成姓氏. > python sample ...

  2. PyTorch官方教程大更新:增加标签索引,更加新手友好

    点击上方↑↑↑"视学算法"关注我 来源:公众号 量子位 授权 PyTorch官方教程,现已大幅更新: 提供标签索引,增加主题分类,更加新手友好. 不必再面对一整页教学文章茫然无措, ...

  3. 撒花!PyTorch 官方教程中文版正式上线,激动人心的大好事!

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 什么是 PyTorch?其实 PyTorch 可以拆成两部分:Py+Torch.Py 就是 P ...

  4. PyTorch官方教程大更新:增加标签索引,新手更加友好

    点上方蓝字计算机视觉联盟获取更多干货 在右上方 ··· 设为星标 ★,与你不见不散 仅作分享,不代表本公众号立场,侵权联系删除 转载于:量子位 AI博士笔记系列推荐 周志华<机器学习>手推 ...

  5. pytorch官方教程中文版(一)PyTorch介绍

    pytorch编程环境是1.9.1+cu10.2 建议有能力的直接看官方网站英文版! 下面所示是本次教程的主要目录: pytorch官方教程中文版: PyTorch介绍 学习PyTorch 图像和视频 ...

  6. PyTorch 官方教程发布,限时免费开放!

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 PyTorch 如今已经称为最受欢迎的深度学习框架之一了!2019年1月到6月底,在arXiv ...

  7. PyTorch官方教程《Deep Learning with PyTorch》开源分享,LeCun力荐,通俗易懂

    1 前言 谈到深度学习框架,就不得不谈TensorFlow 和 PyTorch .目前来看,TensorFlow 和 PyTorch 框架是业界使用最为广泛的两个深度学习框架, TensorFlow在 ...

  8. PyTorch-Tutorials【pytorch官方教程中英文详解】- 1 Quickstart

    在PyTorch深度学习实践概论笔记5-课后练习2:pytorch官方教程[中英讲解]中跟着刘老师课后练习给的链接学习了pytorch官方教程,后来发现现在有更新版的教程,有时间正好也一起学习一下. ...

  9. Dynamic Quantization PyTorch官方教程学习笔记

    诸神缄默不语-个人CSDN博文目录 本文是PyTorch的教程Dynamic Quantization - PyTorch Tutorials 1.11.0+cu102 documentation的学 ...

  10. pytorch官方教程中文版(二)学习PyTorch

    pytorch编程环境是1.9.1+cu10.2 建议有能力的直接看官方网站英文版! 下面所示是本次教程的主要目录: pytorch官方教程中文版: PyTorch介绍 学习PyTorch 图像和视频 ...

最新文章

  1. Apache Hudi的写时复制和读时合并
  2. Java 理论与实践: 流行的原子——新原子类是 java.util.concurrent 的隐藏精华(转载)...
  3. jQuery学习笔记(四)——表单选择
  4. 印度首富之女大婚,贫穷限制了我的想象……
  5. 【音频处理】IIR滤波器设计(一)Biquad 滤波器
  6. linux显示3个字符,Linux驱动学习笔记(3)字符设备驱动
  7. int转char数组_C语言学习第22篇---数组和指针的关系剖析
  8. 计算机考试只读,计算机基础考试试题-20210710011550.docx-原创力文档
  9. dsp 链接命令文件的写法
  10. pytorch可视化
  11. 计算机系统管理内存的大小是由什么决定的,计算机内存容量大小由什么决定
  12. 领域驱动设计(DDD)入门概要
  13. 《SpringMVC视频教程》(p2~p3)
  14. 仓库管理系统c#语言代码,C#仓库管理系统+完整源代码
  15. 用python计算工程量_总算懂了工程造价工程量计算方法
  16. SMT操作手册V1.0 模板
  17. 深度学习目标检测---使用labelimg对自己的数据集进行标记(windows系统)
  18. 离散数学:n个相同的小球,可以放入m个相同的盒子里,允许有空盒,问有多少种不同方法
  19. 怀旧服私聊显示服务器后缀,聊天窗口相关设置:有爱怀旧服聊天增强插件简易指南...
  20. Matlab坐标修改 gca

热门文章

  1. PHP 实现微信公众号网页授权登录
  2. @Lookup注解用法
  3. 固态硬盘替换机械硬盘
  4. Canny边缘检测非极大值抑制法在双立方插值(Bicubic)图像边缘优化
  5. PHP 网页支付支付宝支付接口对接
  6. excel锁定单元格不能修改_锁定单元格不被任意修改和删除
  7. 大图书馆 #5 纳瓦尔宝典
  8. spyder缩进快捷键
  9. ADNI介绍与数据下载
  10. 《中國哲學書電子化計劃》網頁文本處理[Word VBA]