字符级RNN分类!🌞

使用字符级RNN对名称进行分类

1.准备数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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)

tips:

2.将名称转换为张量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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())

tips:

这一步构建了两个方法,letterToTensor将单个字符转换为了一个二维张量,lineToTensor将一个单词转换为了三维张量,其中torch.zeros(len(line), 1, n_letters)中的1是因为 PyTorch 假设一切都是批量的 - 我们在这里只使用批处理大小 1。

3.创建网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)

tips:

该网络首先将输入层和隐藏层结合,然后通过线性层输出一个1*128的隐藏层,再将该层通过一个线性层转换为输出层,经过激活函数后输出。

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

1
2
3
4
input = letterToTensor('A')
hidden = torch.zeros(1, n_hidden)

output, next_hidden = rnn(input, hidden)

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

1
2
3
4
5
input = lineToTensor('Albert')
hidden = torch.zeros(1, n_hidden)

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

4.训练

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

1
2
3
4
5
6
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))

tips:

topk返回值和索引。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)

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

1
criterion = nn.NLLLoss()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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()

tips:

rnn.zero_grad():在每个训练步骤之前清零模型参数的梯度。

p.data.add_(p.grad.data, alpha=-learning_rate):这行代码是针对循环神经网络(RNN)模型中的参数进行梯度下降更新的一种常见方式。

  1. for p in rnn.parameters()::这个循环遍历了RNN模型中的所有参数。在PyTorch中,通过 parameters() 方法可以获取模型中所有需要进行学习的参数。

  2. p.data.add_(p.grad.data, alpha=-learning_rate):这行代码是对每个参数进行更新的核心部分。让我们分解它:

    • p.data:表示参数 p 的数据值。
    • p.grad.data:表示参数 p 的梯度值。
    • alpha=-learning_rate:表示更新的步长,即学习率的负数。

    实际上,这行代码执行了以下操作:

    • 首先,它获取了参数 p 的数据值 p.data
    • 然后,它将参数的梯度值 p.grad.data 乘以学习率的负数 -learning_rate
    • 最后,它将这个乘以后的梯度值添加到参数的数据值中,实现了参数的更新。

这个过程基本上是梯度下降法的一步,其中学习率 learning_rate 控制着参数更新的步长。通常,这行代码会在优化器(如随机梯度下降优化器或Adam优化器)中的参数更新步骤中被使用。

值得注意的是,这种方式在更新参数时直接修改了参数的数据值,而没有通过优化器对象来处理。因此,在使用这种方式时需要手动设置学习率、考虑动量等优化技巧。一般情况下,更推荐使用PyTorch提供的优化器,例如 torch.optim.SGDtorch.optim.Adam,这些优化器会自动管理参数更新的细节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

字符级RNN分类!🌞
https://yangchuanzhi20.github.io/2024/02/13/人工智能/Pytorch/项目实战/RNN分类/
作者
白色很哇塞
发布于
2024年2月13日
许可协议