ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-06-20 11:02:28  阅读:207  来源: 互联网

标签:category RNN RNN1 Pytorch input output hidden txt line


文章目录

1 数据与说明

数据下载

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

D:.
│  eng-fra.txt
│  
└─names
        Arabic.txt
        Chinese.txt
        Czech.txt
        Dutch.txt
        English.txt
        French.txt
        German.txt
        Greek.txt
        Irish.txt
        Italian.txt
        Japanese.txt
        Korean.txt
        Polish.txt
        Portuguese.txt
        Russian.txt
        Scottish.txt
        Spanish.txt
        Vietnamese.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 表示所有字母的数量。这次多加了一个特殊符号 。因为是文本生成,所以需要有一个符号来结束文本生成的过程。我们设定,当生成 的时候,就结束RNN的循环。

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

from io import open
import glob
import os
import unicodedata
import string

all_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] = lines

n_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> ,和xt、ht-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_letters 到 n_letters 的多分类任务。比如:

  • (“A”, “B”) = ( [ 1 , 0 , 0 , 0 , … , 0 , 0 ] , 1 )
  • (“B”, “C”) = ( [ 0 , 1 , 0 , 0 , … , 0 , 0 ] , 2 )
  • (“C”, “D”) = ( [ 0 , 0 , 1 , 0 , … , 0 , 0 ] , 3 )
  • (“D”, “<EOS>”) = ( [ 0 , 0 , 0 , 1 , … , 0 , 0 ],4 )

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

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]
  • hidden = self.i2h(input_combined):ht = Wh[category, xt, ht-1]
  • output = self.i2o(input_combined):ot = Wo1[category, xt, ht-1]
  • output_combined = torch.cat((hidden, output), 1):o’t = [ht, ot]
  • output = self.o2o(output_combined) :ot = Wo2o’t = Wo2[ht, ot]
  • output = self.dropout(output) :用dropout防止过拟合
  • output = self.softmax(output) :用softmax把ot转化成预测字母的概率分布yt
import torch.nn as nn

class RNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(RNN,self).__init__() 
        self.hidden_size = hidden_size
        self.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, hidden
    
    def 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.0005

def train(category_tensor, input_line_tensor, target_line_tensor):
    target_line_tensor.unsqueeze_(-1)
    hidden = rnn.initHidden()

    rnn.zero_grad()

    loss = 0

    for 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 time

def timeSince(since):
    now = time.time()
    s = now-since
    return '%dm %ds'%(s//60,s%60)

n_iters = 100000
print_every = 5000
plot_every = 500

all_losses = []
total_loss = 0

n_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 += loss

    if 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:

0m 23s (5000 5%) 2.5188
0m 45s (10000 10%) 3.0014
1m 8s (15000 15%) 2.3518
1m 33s (20000 20%) 2.8295
2m 0s (25000 25%) 3.4643
2m 24s (30000 30%) 2.1880
2m 52s (35000 35%) 2.6564
3m 19s (40000 40%) 2.5555
3m 47s (45000 45%) 2.3225
4m 15s (50000 50%) 2.5692
4m 40s (55000 55%) 2.6630
5m 4s (60000 60%) 2.9264
5m 28s (65000 65%) 2.2237
5m 52s (70000 70%) 2.5134
6m 16s (75000 75%) 1.9850
6m 40s (80000 80%) 1.5491
7m 4s (85000 85%) 2.4384
7m 27s (90000 90%) 2.4045
7m 50s (95000 95%) 1.7542
8m 15s (100000 100%) 2.1884

画图

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

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

plt.figure()
plt.plot(all_losses)

在这里插入图片描述

预测

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

  • 创建输入类别category的tensor,开始字母的 tensor 和 初始化隐藏层状态 h0
  • 用首字母生成一个字符串 output_name
  • 在到达最大输出长度前:
    • 给模型输入当前的字母
    • 模型生成下一个字母,和下一个隐藏层状态
    • 如果生成的字母是 “<EOS>”标记符, 停止生成
    • 如果生成的字母是一个常规的字母,把它加入 output_name,并且继续生成
  • 返回最终生成的名字 output_nam
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_letter

    for 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 :
        break
      else:
        letter = all_letters[topi]
        output_name += letter
      input = 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')

Out:

Rovakov
Uantonov
Shalovev

Garterr
Eerter
Roure

Santan
Parer
Alanan

Chan
Han
Iun

标签:category,RNN,RNN1,Pytorch,input,output,hidden,txt,line
来源: https://blog.csdn.net/weixin_44491423/article/details/118065974

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有