在pytorch中,张量维度和批大小的概念让你感到困惑了吗?

6

我对PyTorch和神经网络非常陌生,尤其在创建一个通过姓名分类性别的神经网络方面遇到了一些问题。
我参考了PyTorch教程中按国籍分类姓名的RNN,但我决定不采用循环的方法...如果这个想法不正确,请立即停止我!
然而,每当我试图通过网络运行输入时,它告诉我:

RuntimeError: matrices expected, got 3D, 2D tensors at /py/conda-bld/pytorch_1493681908901/work/torch/lib/TH/generic/THTensorMath.c:1232

我知道这与PyTorch始终期望有批量大小有关,而我已经按照这种方式设置了我的张量,但你可能可以从这一点看出来,我不知道我在说什么。

这是我的代码:

from future import unicode_literals, print_function, division
from io import open
import glob
import unicodedata
import string
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
import random
from torch.autograd import Variable
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

"""------GLOBAL VARIABLES------"""

all_letters = string.ascii_letters + " .,;'"
num_letters = len(all_letters)
all_names = {}
genders = ["Female", "Male"]

"""-------DATA EXTRACTION------"""

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

def unicodeToAscii(s):
    return ''.join(
        c for c in unicodedata.normalize('NFD', s)
        if unicodedata.category(c) != 'Mn'
        and c in all_letters
    )

# 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 file in findFiles("/home/andrew/PyCharm/PycharmProjects/CantStop/data/names/*.txt"):
    gender = file.split("/")[-1].split(".")[0]
    names = readLines(file)
    all_names[gender] = names

"""-----DATA INTERPRETATION-----"""

def nameToTensor(name):
    tensor = torch.zeros(len(name), 1, num_letters)
    for index, letter in enumerate(name):
        tensor[index][0][all_letters.find(letter)] = 1
    return tensor

def outputToGender(output):
    gender, gender_index = output.data.topk(1)
    if gender_index[0][0] == 0:
        return "Female"
    return "Male"

"""------NETWORK SETUP------"""

class Net(nn.Module):
    def __init__(self, input_size, output_size):
        super(Net, self).__init__()
        #Layer 1
        self.Lin1 = nn.Linear(input_size, int(input_size/2))
        self.ReLu1 = nn.ReLU()
        self.Batch1 = nn.BatchNorm1d(int(input_size/2))
        #Layer 2
        self.Lin2 = nn.Linear(int(input_size/2), output_size)
        self.ReLu2 = nn.ReLU()
        self.Batch2 = nn.BatchNorm1d(output_size)
        self.softMax = nn.LogSoftmax()

    def forward(self, input):
        output1 = self.Batch1(self.ReLu1(self.Lin1(input)))
        output2 = self.softMax(self.Batch2(self.ReLu2(self.Lin2(output1))))
        return output2

NN = Net(num_letters, 2)

"""------TRAINING------"""

def getRandomTrainingEx():
    gender = genders[random.randint(0, 1)]
    name = all_names[gender][random.randint(0, len(all_names[gender])-1)]
    gender_tensor = Variable(torch.LongTensor([genders.index(gender)]))
    name_tensor = Variable(nameToTensor(name))
    return gender_tensor, name_tensor, gender

def train(input, target):
    loss_func = nn.NLLLoss()

    optimizer = optim.SGD(NN.parameters(), lr=0.0001, momentum=0.9)

    optimizer.zero_grad()

    output = NN(input)

    loss = loss_func(output, target)
    loss.backward()
    optimizer.step()

    return output, loss

all_losses = []
current_loss = 0

for i in range(100000):
    gender_tensor, name_tensor, gender = getRandomTrainingEx()
    output, loss = train(name_tensor, gender_tensor)
    current_loss += loss

    if i%1000 == 0:
        print("Guess: %s, Correct: %s, Loss: %s" % (outputToGender(output), gender, loss.data[0]))

    if i%100 == 0:
        all_losses.append(current_loss/10)
        current_loss = 0

# plt.figure()
# plt.plot(all_losses)
# plt.show()

请帮助一个初学者!


在我看来,为了将文本分类为两个类别而进行递归神经网络似乎有些过度设计;普通的神经网络应该足够了。递归神经网络更适合需要“短期记忆”的事物,并且非常适用于视频和图像分析等应用。此外,如果采用“简单”方法,如贝叶斯分类(如朴素贝叶斯),也不会出现问题。 - DarkCygnus
@GrayCygnus 我也是这么认为的,那么,在这种情况下,我的网络和输入/输出结构是否设置正确了呢? - Andrew Kirillov
我也看到了你可能采用的方法存在一个很大的问题...神经网络具有固定的输入大小,也就是说,你需要设计和训练它来接收特定形状的输入向量,并输出分类结果。但是,由于名称的长度不同,你将如何从数据中获取该特征呢? - DarkCygnus
您可能想在这里使用 RNN,因为它们擅长处理序列,而字符串可以被视为字符序列。我会考虑在这里使用双向 LSTM。还有一些关于学习单词表示的工作已经学习了性别;例如 word2vec - finbarr
1
@GrayCygnus,你比我领先一步。昨天我才意识到这个输入大小的问题,现在看起来,输入张量的第一个维度,即名称中字母的数量,被PyTorch识别为输入的批处理大小,这意味着输出和目标张量应该具有相同大小的第一个维度...我需要巧妙地创建适用的目标张量,或者以某种方式将输出压缩为1x2张量...我想。 - Andrew Kirillov
说实话(并没有打击你的积极性的意图),如果我是你,我会采取不同的方法,因为神经网络需要适应不同大小的输入尺寸,你将不得不每次获得一个大小为N的输入时设计和训练即时模型,或者重复使用以前的模型,这是非常难以实现的(你将不得不获取所有N的训练和测试数据...你明白我的意思吗?)。有一些选项可以做,我会写一个答案来解释这个问题,因为这是一个重要的考虑因素。 - DarkCygnus
2个回答

2
  1. 调试你的bug:

Pycharm是一个有用的Python调试器,它可以让您设置断点并查看张量的维度。
为了更轻松地调试,请不要将事情堆积在一起。

output1 = self.Batch1(self.ReLu1(self.Lin1(input)))

相反,

h1 = self.ReLu1(self.Lin1(input))
h2 = self.Batch1(h1)

关于堆栈跟踪,Pytorch也提供了Python方式的错误堆栈跟踪。我相信在此之前

RuntimeError: matrices expected, got 3D, 2D tensors at /py/conda-bld/pytorch_1493681908901/work/torch/lib/TH/generic/THTensorMath.c:1232

有一些 Python 错误堆栈跟踪指向您的代码。为了更轻松地进行调试,正如我所说,不要向前堆叠。

您可以使用 Pycharm 在崩溃点之前创建断点。在调试器监视器中,然后使用 Variable(torch.rand(dim1, dim2)) 来测试前向传递输入、输出维度以及是否存在维度错误。与输入的维度进行比较。在调试器监视器中调用 input.size()

例如,self.ReLu1(self.Lin1(Variable(torch.rand(10, 20)))).size()。如果它显示红色文本(错误),那么输入维度是不正确的。否则,它将显示输出的大小。

pycharm

  1. 阅读文档

Pytorch文档中,它指定了输入/输出维度。它还提供了一个示例代码片段。

>>> rnn = nn.RNN(10, 20, 2)
>>> input = Variable(torch.randn(5, 3, 10))
>>> h0 = Variable(torch.randn(2, 3, 20))
>>> output, hn = rnn(input, h0)

你可以在 PyCharm 调试器中使用代码片段来探索你感兴趣的特定层(RNN、Linear、BatchNorm1d)的输入、输出维度。

0
首先,关于您的错误,正如其他答案所说以及您的异常,很可能是因为您的输入参数格式不正确。您可以尝试调试以隔离导致错误的行,然后编辑您的问题,以便我们确切知道问题的原因并进行更正(没有完整的堆栈跟踪,很难知道问题所在)。
现在,您正在尝试实现一个通过性别对姓名进行分类的神经网络,正如您所指出的那样。我们可以看到,这个任务需要以某种方式输入一个姓名(具有不同的大小),并输出一个性别(一个二进制变量:男性、女性)。然而,一般来说,神经网络是建立和训练用于分类输入(向量)的,这些输入具有固定数量的特征,就像他们在pytorch docs中提到的那样:

参数:input_size-输入x中期望的特征数

...

看了你提到的tutorial,他们确实考虑了这种情况,因为在他们的案例中,网络的输入是一个单个字母转换成“one-hot向量”,正如他们所指出的:

要运行此网络的一步,我们需要传递一个输入(在我们的情况下,是当前字母的张量)和一个先前的隐藏状态(我们首先将其初始化为零)。我们将得到输出(每种语言的概率)和下一个隐藏状态(我们将其保留到下一步)。

并且甚至给出了一个例子(请记住,张量在pytorch中是Variable):
input = Variable(letterToTensor('A'))
hidden = Variable(torch.zeros(1, n_hidden))
output, next_hidden = rnn(input, hidden)

注意:话虽如此,你还可以做一些其他的事情来适应可变大小的输入。根据我的经验,并且也得到了thisthis other这两个很好的问题的补充,你可以:

  1. 预处理数据以提取新特征并将其转换为固定大小的输入。这通常是最常用的方法,但需要经验和耐心才能获得良好的特征。使用的一些技术包括PCA(主成分分析)和LDA(潜在狄利克雷分配)。

    例如,您可以从数据中提取特征,如:名称的长度,名称中字母a的数量(女性名称倾向于有更多的a),名称中字母e的数量(男性名称可能也是如此),等等...,以便生成新特征,如[name_length,a_found,e_found,...]。然后,您可以按照常规方法处理您的新固定大小向量。请注意,这些特征必须有意义;我只是举了一些例子(尽管它们可能有效)。

  2. 将输入名称拆分为固定大小的子字符串(或使用滑动窗口迭代它们),然后您可以使用专为该大小设计的网络对其进行分类,并以集合方式组合输出以获得最终分类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接