PyTorch翻译官网教程-NLP FROM SCRATCH: CLASSIFYING NAMES WITH A CHARACTER-LEVEL RNN

官网链接

NLP From Scratch: Classifying Names with a Character-Level RNN --- PyTorch Tutorials 2.0.1+cu117 documentation

使用CHARACTER-LEVEL RNN 对名字分类

我们将建立和训练一个基本的字符级递归神经网络(RNN)来分类单词。本教程以及另外两个"from scratch"的自然语言处理(NLP)教程NLP From Scratch: Generating Names with a Character-Level RNNNLP From Scratch: Translation with a Sequence to Sequence Network and Attention,演示如何预处理数据以建立NLP模型。特别是,这些教程没有使用torchtext的许多便利功能,因此您可以看到如何简单使用预处理模型NLP。

字符级RNN将单词作为一系列字符来读取 ,每一步输出一个预测和"隐藏状态",将之前的隐藏状态输入到下一步。我们把最后的预测作为输出,即这个词属于哪个类。

具体来说,我们将训练来自18种语言的几千个姓氏,并根据拼写来预测一个名字来自哪种语言:

复制代码
$ python predict.py Hinton
(-0.47) Scottish
(-1.52) English
(-3.57) Irish

$ python predict.py Schmidhuber
(-0.19) German
(-2.48) Czech
(-2.68) Dutch

建议准备

在开始本教程之前,建议您安装PyTorch,并对Python编程语言和张量有基本的了解:

了解rnn及其工作原理也很有用:

准备数据

从这里下载数据并将其解压缩到当前目录。here

"data/names "目录下包含18个文本文件,文件名为"[Language].txt"。每个文件包含一堆名称,每行一个名称,大多数是罗马化的(但我们仍然需要从Unicode转换为ASCII)。

我们最终会得到一个包含每种语言名称列表的字典,{language: [names ...]}。通用变量"category"和"line"(在本例中表示语言和名称)用于以后的可扩展性。

复制代码
from io import open
import glob
import os

def findFiles(path): return glob.glob(path)

print(findFiles('data/names/*.txt'))

import unicodedata
import string

all_letters = string.ascii_letters + " .,;'"
n_letters = len(all_letters)

# 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
    )

print(unicodeToAscii('Ślusàrski'))

# Build the category_lines dictionary, a list of names per language
category_lines = {}
all_categories = []

# Read a file and split into lines
def readLines(filename):
    lines = open(filename, encoding='utf-8').read().strip().split('\n')
    return [unicodeToAscii(line) for line in lines]

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)

输出

复制代码
['data/names/Arabic.txt', 'data/names/Chinese.txt', 'data/names/Czech.txt', 'data/names/Dutch.txt', 'data/names/English.txt', 'data/names/French.txt', 'data/names/German.txt', 'data/names/Greek.txt', 'data/names/Irish.txt', 'data/names/Italian.txt', 'data/names/Japanese.txt', 'data/names/Korean.txt', 'data/names/Polish.txt', 'data/names/Portuguese.txt', 'data/names/Russian.txt', 'data/names/Scottish.txt', 'data/names/Spanish.txt', 'data/names/Vietnamese.txt']
Slusarski

现在我们有了category_lines ,这是一个将每个类别(语言)映射到行(名称)列表的字典。我们还记录了all_categories (只是一个语言列表)和n_categories,以供以后参考。

复制代码
print(category_lines['Italian'][:5])

输出

复制代码
['Abandonato', 'Abatangelo', 'Abatantuono', 'Abate', 'Abategiovanni']

把名字变成张量

现在我们已经组织好了所有的名字,我们需要把它们变成张量来使用它们。

为了表示单个字母,我们使用大小为**<1** x n_letters> 的 "one-hot vector"。一个独热向量被0填充,除了当前字母所以处是1。例如:"b" = <0 1 0 0 0 ...> .

为了组成一个单词,我们将一堆这样的单词连接到一个二维矩阵中**<line_length** x 1 x n_letters> .

额外的1维度是因为PyTorch假设所有的东西都是分批的------我们在这里只是使用1的批大小。

复制代码
import torch

# Find letter index from all_letters, e.g. "a" = 0
def letterToIndex(letter):
    return all_letters.find(letter)

# Just for demonstration, turn a letter into a <1 x n_letters> Tensor
def letterToTensor(letter):
    tensor = torch.zeros(1, n_letters)
    tensor[0][letterToIndex(letter)] = 1
    return tensor

# Turn a line into a <line_length x 1 x n_letters>,
# or an array of one-hot letter vectors
def lineToTensor(line):
    tensor = torch.zeros(len(line), 1, n_letters)
    for li, letter in enumerate(line):
        tensor[li][0][letterToIndex(letter)] = 1
    return tensor

print(letterToTensor('J'))

print(lineToTensor('Jones').size())

输出

复制代码
tensor([[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.]])
torch.Size([5, 1, 57])

创建网络

在autograd之前,在Torch中创建循环神经网络涉及到在几个时间步上克隆一层的参数。图层包含隐藏状态和梯度,现在完全由图形本身处理。这意味着你可以以一种非常"纯粹"的方式实现RNN,作为常规的前馈层。

这个RNN模块(主要是从the PyTorch for Torch users tutorial复制的)只有2个线性层,在输入和隐藏状态上操作,在输出之后有一个LogSoftmax层。

复制代码
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(input_size + hidden_size, hidden_size)
        self.h2o = nn.Linear(hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim=1)

    def forward(self, input, hidden):
        combined = torch.cat((input, hidden), 1)
        hidden = self.i2h(combined)
        output = self.h2o(hidden)
        output = self.softmax(output)
        return output, hidden

    def initHidden(self):
        return torch.zeros(1, self.hidden_size)

n_hidden = 128
rnn = RNN(n_letters, n_hidden, n_categories)

为了运行这个网络的一个步骤,我们需要传递一个输入(在我们的例子中,是当前字母的张量)和一个先前的隐藏状态(我们一开始将其初始化为零)。我们将返回输出(每种语言的概率)和下一个隐藏状态(我们将其保留到下一步)。

复制代码
input = letterToTensor('A')
hidden = torch.zeros(1, n_hidden)

output, next_hidden = rnn(input, hidden)

为了提高效率,我们不想为每一步都创建一个新的张量,所以我们将使用lineToTensor 而不是letterToTensor并使用切片。这可以通过预计算张量批次来进一步优化。

复制代码
input = lineToTensor('Albert')
hidden = torch.zeros(1, n_hidden)

output, next_hidden = rnn(input[0], hidden)
print(output)

输出

复制代码
tensor([[-2.9083, -2.9270, -2.9167, -2.9590, -2.9108, -2.8332, -2.8906, -2.8325,
         -2.8521, -2.9279, -2.8452, -2.8754, -2.8565, -2.9733, -2.9201, -2.8233,
         -2.9298, -2.8624]], grad_fn=<LogSoftmaxBackward0>)

正如您所看到的,输出是一个**<1** x **n_categories>**张量,其中每个项目是该类别的可能性(越高越有可能)。

训练

训练准备

在开始训练之前,我们应该编写一些辅助函数。首先是解释网络的输出,我们知道这是每个类别的可能性。我们可以用Tensor.topk得到最大值的索引:

复制代码
def categoryFromOutput(output):
    top_n, top_i = output.topk(1)
    category_i = top_i[0].item()
    return all_categories[category_i], category_i

print(categoryFromOutput(output))

输出

复制代码
('Scottish', 15)

我们还需要一种快速获取训练示例(名称及其语言)的方法:

复制代码
import random

def randomChoice(l):
    return l[random.randint(0, len(l) - 1)]

def randomTrainingExample():
    category = randomChoice(all_categories)
    line = randomChoice(category_lines[category])
    category_tensor = torch.tensor([all_categories.index(category)], dtype=torch.long)
    line_tensor = lineToTensor(line)
    return category, line, category_tensor, line_tensor

for i in range(10):
    category, line, category_tensor, line_tensor = randomTrainingExample()
    print('category =', category, '/ line =', line)

输出

复制代码
category = Chinese / line = Hou
category = Scottish / line = Mckay
category = Arabic / line = Cham
category = Russian / line = V'Yurkov
category = Irish / line = O'Keeffe
category = French / line = Belrose
category = Spanish / line = Silva
category = Japanese / line = Fuchida
category = Greek / line = Tsahalis
category = Korean / line = Chang

训练网络

现在训练这个网络所需要做的就是给它看一堆例子,让它猜测,然后告诉它是否错了。

对于损失函数nn.NLLLoss 是合适的,因为RNN的最后一层是nn.LogSoftmax .

复制代码
criterion = nn.NLLLoss()

每个训练循环将:

  • 创建输入张量和目标张量
  • 创建一个零初始隐藏状态
  • 读取每个字母
    • 为下一个字母保存隐藏状态
  • 将最终输出与目标进行比较

  • 反向传播

  • 返回输出和损失

    learning_rate = 0.005 # If you set this too high, it might explode. If too low, it might not learn

    def train(category_tensor, line_tensor):
    hidden = rnn.initHidden()

    复制代码
      rnn.zero_grad()
    
      for i in range(line_tensor.size()[0]):
          output, hidden = rnn(line_tensor[i], hidden)
    
      loss = criterion(output, category_tensor)
      loss.backward()
    
      # Add parameters' gradients to their values, multiplied by learning rate
      for p in rnn.parameters():
          p.data.add_(p.grad.data, alpha=-learning_rate)
    
      return output, loss.item()

现在我们只需要用一堆例子来运行它。由于train 函数返回输出和损失,我们可以打印它的猜测并跟踪损失以便绘制。由于有1000个示例,我们只打印每个print_every示例,并取损失的平均值。

复制代码
import time
import math

n_iters = 100000
print_every = 5000
plot_every = 1000



# Keep track of losses for plotting
current_loss = 0
all_losses = []

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

start = time.time()

for iter in range(1, n_iters + 1):
    category, line, category_tensor, line_tensor = randomTrainingExample()
    output, loss = train(category_tensor, line_tensor)
    current_loss += loss

    # Print ``iter`` number, loss, name and guess
    if iter % print_every == 0:
        guess, guess_i = categoryFromOutput(output)
        correct = '✓' if guess == category else '✗ (%s)' % category
        print('%d %d%% (%s) %.4f %s / %s %s' % (iter, iter / n_iters * 100, timeSince(start), loss, line, guess, correct))

    # Add current loss avg to list of losses
    if iter % plot_every == 0:
        all_losses.append(current_loss / plot_every)
        current_loss = 0

输出

复制代码
5000 5% (0m 33s) 2.6379 Horigome / Japanese ✓
10000 10% (1m 5s) 2.0172 Miazga / Japanese ✗ (Polish)
15000 15% (1m 39s) 0.2680 Yukhvidov / Russian ✓
20000 20% (2m 12s) 1.8239 Mclaughlin / Irish ✗ (Scottish)
25000 25% (2m 45s) 0.6978 Banh / Vietnamese ✓
30000 30% (3m 18s) 1.7433 Machado / Japanese ✗ (Portuguese)
35000 35% (3m 51s) 0.0340 Fotopoulos / Greek ✓
40000 40% (4m 23s) 1.4637 Quirke / Irish ✓
45000 45% (4m 57s) 1.9018 Reier / French ✗ (German)
50000 50% (5m 30s) 0.9174 Hou / Chinese ✓
55000 55% (6m 2s) 1.0506 Duan / Vietnamese ✗ (Chinese)
60000 60% (6m 35s) 0.9617 Giang / Vietnamese ✓
65000 65% (7m 9s) 2.4557 Cober / German ✗ (Czech)
70000 70% (7m 42s) 0.8502 Mateus / Portuguese ✓
75000 75% (8m 14s) 0.2750 Hamilton / Scottish ✓
80000 80% (8m 47s) 0.7515 Maessen / Dutch ✓
85000 85% (9m 20s) 0.0912 Gan / Chinese ✓
90000 90% (9m 53s) 0.1190 Bellomi / Italian ✓
95000 95% (10m 26s) 0.0137 Vozgov / Russian ✓
100000 100% (10m 59s) 0.7808 Tong / Vietnamese ✓

绘制结果

绘制all_losses的历史损失图显示了网络的学习情况:

复制代码
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

plt.figure()
plt.plot(all_losses)

输出

复制代码
[<matplotlib.lines.Line2D object at 0x7f16606095a0>]

评估结果

为了了解网络在不同类别上的表现如何,我们将创建一个混淆矩阵,表示网络猜测(列)的每种语言(行)。为了计算混淆矩阵,使用evaluate() , 在网络中运行一堆样本,这与train() 去掉反向传播相同。

复制代码
# Keep track of correct guesses in a confusion matrix
confusion = torch.zeros(n_categories, n_categories)
n_confusion = 10000

# Just return an output given a line
def evaluate(line_tensor):
    hidden = rnn.initHidden()

    for i in range(line_tensor.size()[0]):
        output, hidden = rnn(line_tensor[i], hidden)

    return output

# Go through a bunch of examples and record which are correctly guessed
for i in range(n_confusion):
    category, line, category_tensor, line_tensor = randomTrainingExample()
    output = evaluate(line_tensor)
    guess, guess_i = categoryFromOutput(output)
    category_i = all_categories.index(category)
    confusion[category_i][guess_i] += 1

# Normalize by dividing every row by its sum
for i in range(n_categories):
    confusion[i] = confusion[i] / confusion[i].sum()

# Set up plot
fig = plt.figure()
ax = fig.add_subplot(111)
cax = ax.matshow(confusion.numpy())
fig.colorbar(cax)

# Set up axes
ax.set_xticklabels([''] + all_categories, rotation=90)
ax.set_yticklabels([''] + all_categories)

# Force label at every tick
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(1))

# sphinx_gallery_thumbnail_number = 2
plt.show()

输出

复制代码
/var/lib/jenkins/workspace/intermediate_source/char_rnn_classification_tutorial.py:445: UserWarning:

FixedFormatter should only be used together with FixedLocator

/var/lib/jenkins/workspace/intermediate_source/char_rnn_classification_tutorial.py:446: UserWarning:

FixedFormatter should only be used together with FixedLocator

你可以从主轴上挑出亮点,显示它猜错了哪些语言,例如中文猜错了韩语,西班牙语猜错了意大利语。它似乎在希腊语上表现得很好,而在英语上表现得很差(可能是因为与其他语言重叠)。

运行用户输入

复制代码
def predict(input_line, n_predictions=3):
    print('\n> %s' % input_line)
    with torch.no_grad():
        output = evaluate(lineToTensor(input_line))

        # Get top N categories
        topv, topi = output.topk(n_predictions, 1, True)
        predictions = []

        for i in range(n_predictions):
            value = topv[0][i].item()
            category_index = topi[0][i].item()
            print('(%.2f) %s' % (value, all_categories[category_index]))
            predictions.append([value, all_categories[category_index]])

predict('Dovesky')
predict('Jackson')
predict('Satoshi')

输出

复制代码
> Dovesky
(-0.57) Czech
(-0.97) Russian
(-3.43) English

> Jackson
(-1.02) Scottish
(-1.49) Russian
(-1.96) English

> Satoshi
(-0.42) Japanese
(-1.70) Polish
(-2.74) Italian

in the Practical PyTorch repo中脚本的最终版本将上述代码拆分为几个文件:

运行train.py来训练和保存网络。

运行predict.py并输入一个名称来查看预测:

复制代码
$ python predict.py Hazaki
(-0.42) Japanese
(-1.39) Polish
(-3.51) Czech

运行server.py 并访问http://localhost:5533/Yourname以获得预测的JSON输出。

练习

尝试使用不同的数据集 ->类别,例如:

  • 任何单词->语言
  • 名字->性别
  • 角色名称->作家
  • 页面标题 -> 博客或社交新闻网站子版块

使用一个更大的和/或更好的形状网络,可以获得更好的结果

  • 添加更多线性图层
  • 试试 nn.LSTMnn.GRU 网络层
  • 将这些RNNs组合成一个更高级的网络
相关推荐
数据智能老司机1 天前
PyTorch 深度学习——使用神经网络来拟合数据
pytorch·深度学习
数据智能老司机1 天前
PyTorch 深度学习——用于图像的扩散模型
pytorch·深度学习
数据智能老司机1 天前
PyTorch 深度学习——Transformer 是如何工作的
pytorch·深度学习
数据智能老司机2 天前
PyTorch 深度学习——使用张量表示真实世界数据
pytorch·深度学习
数据智能老司机2 天前
PyTorch 深度学习——它始于一个张量
pytorch·深度学习
Narrastory4 天前
明日香 - Pytorch 快速入门保姆级教程(三)
pytorch·深度学习
Narrastory7 天前
明日香 - Pytorch 快速入门保姆级教程(一)
人工智能·pytorch·深度学习
Narrastory7 天前
明日香 - Pytorch 快速入门保姆级教程(二)
人工智能·pytorch·深度学习
盼小辉丶12 天前
PyTorch实战(30)——使用TorchScript和ONNX导出通用PyTorch模型
人工智能·pytorch·深度学习·模型部署
NGBQ1213812 天前
Imgflip社交媒体表情包数据集-202208条多模板meme数据-包含完整图片URL和文本说明-适用于NLP模型训练和社交媒体分析
人工智能·自然语言处理·媒体