[翻译Pytorch教程]NLP从零开始:使用字符级RNN进行名字生成
翻译自官网手册:NLP From Scratch: Generating Names with a Character-Level RNN
Author: Sean Robertson
原文github代码
这是NLP从零开始三个教程的第二个。在第一个教程char_rnn_classification_tutorial中,使用RNN将名字按照其来源的语言进行了分类。本教程将进行名字生成。
> 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
)”
推荐阅读
本教程需要你至少安装了PyTorch、了解Python、了解张量(Tensors):
- https://pytorch.org/ 安装说明
- Deep Learning with PyTorch: A 60 Minute Blitz 初识PyTorch
- Learning PyTorch with Examples 广泛深入了解
- PyTorch for Former Torch Users 之前使用过Lua Torch
了解RNNs及其原理可以阅读:
- 循环神经网络Recurrent Neural
Networks()不可思议的效果展示了一些真实的样本。
shows a bunch of real life examples - 理解LSTM网络是关于LSTMs的,也包含了RNNs的说明。
前一个教程也可以学习下:char_rnn_classification_tutorial
准备数据(Preparing the Data)
注意:
从此处下载数据并解压到当前目录。
这个过程的详细信息查看上一个教程。简之,数据包括一些每行是一个名字的纯文本文件 data/names/[Language].txt
。我们将行分割进数组、将Unicode编码转化为ASCII编码、最后创建了一个字典 {language: [names ...]}
。
from __future__ import unicode_literals, print_function, division
from io import open
import glob
import os
import unicodedata
import stringall_letters = string.ascii_letters + " .,;'-"
n_letters = len(all_letters) + 1 # Plus EOS marker#返回文件夹下文件列表
def findFiles(path): return glob.glob(path)# 将Unicode字符串转换为简单的ASCII字符,鸣谢 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)# 读取一个文件并按行分割
def readLines(filename):lines = open(filename, encoding='utf-8').read().strip().split('\n')return [unicodeToAscii(line) for line in lines]#创建分类字典(category_lines), 每种语言对应一个名字列表
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"))
输出:
# categories: 18 ['Italian', 'Dutch', 'German', 'Arabic', 'Czech', 'Greek', 'Vietnamese', 'French', 'Spanish', 'English', 'Portuguese', 'Scottish', 'Chinese', 'Irish', 'Russian', 'Japanese', 'Korean', 'Polish']
O'Neal
创建网络(Creating the Network)
本网络是在上一教程RNN网络上的基础上,增加了额外的类别张量,其与其它输入张量拼接在一起。类别张量与字母输入相同都是独热向量。
网络输出将作为下一个字母的概率。生成时,可能性最大的输出字母将作为下一步的输入字母。
本教程增加了第二个线性层 o2o
(将隐藏状态和输出合并后)使模型更有效。模型还添加了一个dropout
层,该层会根据设定的概率(本文中为0.1)随机将一部分输入置零,模糊输入从而避免过度拟合。这里将dropout
层放在网络最后,从而增加一些混乱并增加生成的多样性。
import torch
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)
训练(Training)
训练准备(Preparing for Training)
首先,创建可以随机获取类别-名字对(category, line)的辅助函数。
import random# 从列表中随机取一个条目
def randomChoice(l):return l[random.randint(0, len(l) - 1)]# 随机选取一个类别并从该类别中随机选取一个名字
def randomTrainingPair():category = randomChoice(all_categories)line = randomChoice(category_lines[category])return category, line
对于每个时间步(即正在训练的单词的每个字母)网络的输入为类别、当前字母、隐藏状态的组合 (category, current letter, hidden state)
输出为下个字母、下个隐藏状态 (next letter, next hidden state)
。因此对于每个训练组合需要类别、一组输入字母和一组输出/目标字母。
由于每个时间步是通过当前字母预测下个字母,字母对是名字中连续字母的组合,比如,对于 "ABCD<EOS>"
,创建("A", "B"), ("B", "C"), ("C", "D"), ("D", "EOS")
作为输入。
类别张量是大小为 <1 x n_categories>
的独热张量。训练时将其在每个时间步传递给网络 - 这个是可选设计,也可以将其作为初始状态的一部分或者其他策略。
# 类别独热向量
def categoryTensor(category):li = all_categories.index(category)tensor = torch.zeros(1, n_categories)tensor[0][li] = 1return tensor# 作为输入的从第一个到最后一个字母(不包括EOS)的独热One-hot矩阵
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# 作为目标的第二个字母到结尾(EOS)的长张量(LongTensor)
def targetTensor(line):letter_indexes = [all_letters.find(line[li]) for li in range(1, len(line))]letter_indexes.append(n_letters - 1) # EOSreturn torch.LongTensor(letter_indexes)
为了训练方便,创建了一个 randomTrainingExample
函数,其随机提取类别-名字对 (category, line)
并将其转换为要求的类别-输入-目标张量(category, input, target)
。
# 随机选取类别-名字对并将其转换为类别-输入-目标张量
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
训练网络(Training the Network)
与分类只使用最后一个输出不同,生成在每个步骤都做出预测,因此需要在每一步就是你损失。
自动求导方法很神奇的可以让你简单的将每一步的损失累加起来在最后调用后向传导。
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)l = criterion(output, target_line_tensor[i])loss += lloss.backward()for p in rnn.parameters():p.data.add_(-learning_rate, p.grad.data)return output, loss.item() / input_line_tensor.size(0)
为了记录训练需要多久,这里增加了timeSince(timestamp)
函数,其可以返回可读的字符串。
import time
import mathdef timeSince(since):now = time.time()s = now - sincem = math.floor(s / 60)s -= m * 60return '%dm %ds' % (m, s)
训练与之前一样,调用多次调用训练函数并等待几分钟,每个print_every
样本会输出当前时间和损失,每个 plot_every
样本在 all_losses
中记录一个平均损失为绘图做准备。
rnn = RNN(n_letters, 128, n_letters)n_iters = 100000
print_every = 5000
plot_every = 500
all_losses = []
total_loss = 0 # 重置每个 plot_every 迭代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
输出:
0m 28s (5000 5%) 2.6901
0m 56s (10000 10%) 2.4971
1m 24s (15000 15%) 2.4370
1m 52s (20000 20%) 2.6624
2m 20s (25000 25%) 2.9531
2m 48s (30000 30%) 3.6187
3m 16s (35000 35%) 3.6624
3m 45s (40000 40%) 2.4471
4m 12s (45000 45%) 2.5432
4m 40s (50000 50%) 2.0523
5m 8s (55000 55%) 1.9411
5m 36s (60000 60%) 3.3368
6m 4s (65000 65%) 2.2312
6m 32s (70000 70%) 3.4389
7m 0s (75000 75%) 2.8141
7m 28s (80000 80%) 3.5611
7m 56s (85000 85%) 2.2557
8m 24s (90000 90%) 1.9750
8m 52s (95000 95%) 2.3979
9m 19s (100000 100%) 2.2186
绘制损失(Plotting the Losses)
绘制all_losses
中历史损失,查看网络学习情况:
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.ticker as tickerplt.figure()
plt.plot(all_losses)
输出图像:
网络生成(Sampling the Network)
为了生成名字,传递给网络一个字母并得到下一个字母,将所得字母再次传递给网络知道出现终止标记EOS
。具体流程如下:
创建输入类别、起始字母、空隐藏状态张量
使用起始字母生成名字
output_name
字符串到最大输出长度之前:
- 将当前字母传递给网络
- 根据概率最大的输出获取下一个字母,和下一个隐藏状态
- 如果该字符是EOS,停止循环
- 如果是普通字母,将其添加到
output_name
继续
返回最终名字
注意: 除了给定起始字母,另一种策略是包含训练时的“起始字符”让网络自动选择起始字母。
max_length = 20
# 根据类别和起始字母生成名字
def 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_name# 从一个类别和多个起始字母生成多个名字
def 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')
输出:
Rovako
Uakinov
Sakin
Gerra
Eren
Rangerre
Salla
Para
Allan
Chan
Han
Iun
练习(Exercises)
使用不同的类别-行
category -> line
数据集,比如:- 系列小说 -> 角色名字(Fictional series -> Character name)
- 词性 -> 单词(Part of speech -> Word)
- 国家 -> 城市(Country -> City)
使用“起始标记”,这样生成时不需要选择起始字母
使用规模更大或者设计更好的模型优化结果:
- 尝试
nn.LSTM
和nn.GRU
层 - 将多个这种RNNs组合为更高级的网络
- 尝试
[翻译Pytorch教程]NLP从零开始:使用字符级RNN进行名字生成相关推荐
- [翻译Pytorch教程]NLP从零开始:使用序列到序列网络和注意力机制进行翻译
翻译自官网手册:NLP From Scratch: Translation with a Sequence to Sequence Network and Attention Author: Sean ...
- [翻译Pytorch教程]NLP部分:使用TorchText进行文本分类
本教程展示如何在torchtext中调用文本分类数据集,包括: AG_NEWS, SogouNews, DBpedia, YelpReviewPolarity, YelpReviewFull, Yah ...
- pytorch dropout_手把手带你使用字符级RNN生成名字 | PyTorch
作者 | News 编辑 | 奇予纪 出品 | 磐创AI团队出品 [磐创AI 导读]:本篇文章讲解了PyTorch专栏的第五章中的使用字符级RNN生成名字.查看专栏历史文章,请点击下方蓝色字体进入相应 ...
- 实现字符级的LSTM文本生成
实现字符级的LSTM文本生成 1.对于不同的softmax温度,对概率分布进行重新加权 #对于不同的softmax温度,对概率分布进行重新加权 import numpy as npdef reweig ...
- 快来生成你专属的英文名吧(使用字符级RNN)!
目录 一.前言 二.准备数据 三.构造神经网络 四.训练 五.网络采样(预测) 一.前言 数据集为18个国家的姓氏,任务是根据训练得到的模型,在给定国家类别和首字母后,能得到一个与该国人名非常相似的一 ...
- 深度学习算法--python实现用TensorFlow构建字符级RNN语言建模(源码+详细注释)
语言建模是一个迷人的应用,它使机器能完成与人类语言相关的任务,如生成英语句子.现在要构建的模型中,输入为文本文档(纯文本格式的威廉·莎 士比亚的悲剧<哈姆雷特>),目标是研发可以生成与输入 ...
- hot编码 字符one_使用字符级RNN生成名字
1. 准备数据 下载数据(https://download.pytorch.org/tutorial/data.zip)并将其解压到当前文件夹. 有关此过程的更多详细信息,请参阅上一个教程.简而言之, ...
- 字符级Seq2Seq-英语粤语翻译的简单实现
个人博客:http://www.chenjianqu.com/ 原文链接:http://www.chenjianqu.com/show-40.html 前一篇文章中使用简单的seq2seq搭建了单词级 ...
- 使用Pytorch实现NLP深度学习
原文链接:https://pytorch.org/tutorials/beginner/deep_learning_nlp_tutorial.html 本文将会帮助你了解使用Pytorch进行深度学习 ...
最新文章
- 将BST转换为有序的双向链表!
- City of Angels
- 行人识别学习资料整理2018
- 深入php内核一(概述)
- 学习笔记四-信息收集
- 如何在WebLogic Server中创建MySQL数据源
- Java Socket实现客户端服务端之间的通信
- Canal Mysql同步至ES/Hbase只有新增时生效,修改删除不生效
- 【算法学习】线性时间排序-计数排序、基数排序和桶排序详解与编程实现
- 前后端分离重复提交_java+react前后端分离项目处理重复提交问题
- Python进阶:并发编程之Asyncio
- 记一次 CORS 跨域请求出现 OPTIONS 请求的问题及解决方法
- 技术人观点:开发人员在处理云应用时该注意什么?
- 图论及其应用 2007年期末考试答案 总结
- rk3399 调试一款新的摄像头驱动
- 大学网课查题接口平台
- 图片阴影效果和影子效果
- 蚂蚁java一二三面面经
- 现代微服务拆分与设计
- LabWindows中Table控件的下拉列的设置