如何使用递归神经网络分析文本。
自然语言处理(NLP)与深度学习相结合,已成为人工智能研究领域中最性感的主题之一。
有了正确的体系结构,就可以使用文本数据库(经过适当预处理)来训练神经网络,并从人类语言的语义中创建抽象,这对于提取见解和自动决策非常有用。
在本文中,我们将介绍递归神经网络(RNN)与自然语言处理(NLP)之间的关系。然后,使用Amazon Fine Food Reviews数据集,我们将训练使用PyTorch的LSTM将分析分类为积极还是消极。
最后,要检查一切是否顺利,让我们在测试数据集中的另一部分中查看该模型的准确性。
自然语言处理和递归神经网络
我们可以根据数据将文本定义为序列(单词的序列,或者在较低抽象级别的字符)。重要的是,为了在建模此数据集时提取尽可能多的信息,我们需要在模型中放置字符串的顺序。
这是递归神经网络出现的地方:在序列处理中,它们将部分输出保留为“状态”,以便随着时间的推移保留信息:
为了对数据集进行建模,我们将使用特定的RNN体系结构LSTM。
数据集
我们的数据集是Amazon Fine Food Reviews,我们收到的文件是.csv文件,其中我们将使用“文本”和“分数”列,它们分别包含评论文本和分数,范围从1星到5星。
为了将其与我们的神经网络一起使用,我们将文本编码(即变换)为数字序列,其中每个数字都对应于词汇表中单词的索引,索引是一个设置的数据结构(组)包含数据集中存在的所有单词。
预处理
为了用文本数据训练神经网络,我们需要以某种方式将文本编码为数字。有几种方法可以做到这一点,在我们的分析中,我们将使用文本到序列。
在文本到序列方法中,文本编码分两个阶段进行:首先,创建词汇表:它是一个包含所有文本中存在的所有单词的集合,而无需重复。
此后,我们创建一个数据集,该数据集具有一个数字向量,该数字向量带有词汇表中每个单词的索引。最后,对于每个文本,我们的神经网络的输入如下所示:
有一个名为TorchNLP的库,可以解决这个问题。而且,它还将解决在NLP中组装张量数据集时出现的另一个小问题,即字符串的大小:如果它们的大小不同,它们将无法成为张量并且无法最佳地通过我们的模型。
解决方案是填充:我们确定序列的最大。对于较小的那些,我们将0放在最后,直到它们达到大小为止;我们刚裁掉的最大的。
让我们转到预处理代码,我们将从导入库开始。
import torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optim#pip install pytorch-nlpfrom torchnlp.encoders.text import SpacyEncoder, pad_tensorfrom sklearn.model_selection import train_test_splitimport pandas as pdimport numpy as npimport seaborn as snsimport matplotlib.pyplot as plt%matplotlib inlinefrom tqdm.notebook import tqdm
然后,我们将导入数据框并从中删除所有非文本和标签的内容。因为分数是1到5星,所以我们将3和4除去:1和2星为消极,5星为积极。
我们还将通过肯定分析从文本中随机删除,直到数据集平衡为止。最后,我们借此机会在同一个框中将其转换为列表-这将有助于在后续步骤中对这些数据进行操作。
我们删除不需要的列,df.drop以不同的方式删除3至4,以显示可以用pandas完成的方式,然后将它们变成列表,以便对它们进行编码。
#importdf = pd.read_csv('D:/Jupyter/dataset/amazon-fine-food-reviews/reviews.csv')#drop useless datadf = df.drop(['Id', 'ProductId', 'UserId', 'ProfileName', 'HelpfulnessNumerator', 'HelpfulnessDenominator', 'Time', 'Summary',], axis=1)#remove ambiguous 3 and 4 stars for balancing#df = df[df['Score'] != 3]#create labels and preprocessdf['Score'] = df['Score'].apply(lambda i: 'positive' if i > 4 else 'negative')df['Text'] = df['Text'].apply(lambda x:x.lower())#set names for beautiful dfdf.columns = ['labels', 'text']
现在,我们将对数据进行编码。SpacyEncoder将遍历数据集并创建词汇表。
encoder = SpacyEncoder(text_as_list)encoded_texts = []for i in tqdm(range(len(text_as_list))): encoded_texts.append(encoder.encode(text_as_list[i]))
还需要分析具有很长的文本(文本大小分布中的异常值)的可能性,如果出现这种情况,则将其消除。否则,填充数为0的文本将使预测和渐变倾斜。
我们还将绘制文本的分布图,看是否正确。
lengths = [len(i) for i in tqdm(encoded_texts)]length_as_series = pd.Series(lengths)plt.title("Probability Density Function for text lengths")sns.distplot(length_as_series)max_pad_length = length_as_series.quantile(0.9)
我们还看到该分布的第90个百分位数为191。我们将删除大小超过此值的文本,以确保我们的RNN以较少偏见的方式进行训练。
reviews = []labels = []for i in tqdm(range(len(encoded_texts))): if len(encoded_texts[i]) < max_pad_length: reviews.append(encoded_texts[i]) labels.append(1 if labels_as_list[i] == "positive" else 0) assert len(reviews) == len(labels), "The labels and feature lists should have the same time"
最后,我们可以进行填充并创建数据集。让我们也划分训练和测试数据集:
padded_dataset = []for i in tqdm(range(len(reviews))): padded_dataset.append(pad_tensor(reviews[i], int(max_pad_length)))#preparing the final dataset:X = torch.stack(padded_dataset)y = torch.tensor(labels)X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.25, random_state=42)X_train, y_train = torch.tensor(X_train), torch.tensor(y_train)X_test, y_test = torch.tensor(X_test), torch.tensor(y_test)
完成后,我们可以继续创建模型。
使用PyTorch创建模型
对于我们的模型,我们将使用layer nn.Embedding,它将对词汇索引序列进行矢量化nn.LSTM处理,然后再一层进行序列处理,再一层nn.Linear处理模型的复杂性。
class Net(nn.Module): def __init__(self): super().__init__() self.embedding = nn.Embedding(len(encoder.vocab)+1, 32) self.lstm = nn.LSTM(32, 32, batch_first=True) self.fc1 = nn.Linear(32, 2) def forward(self, x): x_ = self.embedding(x) x_, (h_n, c_n) = self.lstm(x_) x_ = (x_[:, -1, :]) x_ = self.fc1(x_) return x_
在通过LSTM之后,我们仅对每个序列进行线性层的最后预测。也就是说,我们可以实例化我们的对象(网络,优化器,DataLoader等),然后进入训练循环。
准备训练循环
我们将进入最后一步。在准备训练和测试循环时,我们将创建将在其中使用的对象。
此外 ,以网络,分类和损失的对象,我们将创建从一个的数据加载程序 torch.TensorDataset,它将由我们之前安装的X_train, y_train, X_test, y_test构建
ds_train = torch.utils.data.TensorDataset(X_train, y_train)train_loader = torch.utils.data.DataLoader(ds_train, batch_size=64, shuffle=True)ds_test = torch.utils.data.TensorDataset(X_test, y_test)test_loader = torch.utils.data.DataLoader(ds_test, batch_size=64, shuffle=True)classifier = Net()device = torch.device('cpu')optimizer = optim.Adam(classifier.parameters(), lr=.01)#0.002 dives 85% acccriterion = nn.CrossEntropyLoss()
我们终于可以进入训练循环了。看到我们使用tqdm获得更漂亮的输出和进度条。
以0.02(Adam优化器)的学习率,我们将训练网络10个周期,批大小为64。
epoch_bar = tqdm(range(10), desc="Training", position=0, total=2)acc = 0for epoch in epoch_bar: batch_bar = tqdm(enumerate(train_loader), desc="Epoch: {}".format(str(epoch)), position=1, total=len(train_loader)) for i, (datapoints, labels) in batch_bar: optimizer.zero_grad() preds = classifier(datapoints.long()) loss = criterion(preds, labels) loss.backward() optimizer.step() if (i + 1) % 500 == 0: preds = classifier(X_test) acc = (preds.argmax(dim=1) == y_test).float().mean().cpu().item() batch_bar.set_postfix(loss=loss.cpu().item(), accuracy="{:.2f}".format(acc), epoch=epoch) batch_bar.update() epoch_bar.set_postfix(loss=loss.cpu().item(), accuracy="{:.2f}".format(acc), epoch=epoch) epoch_bar.update()
结果与结论
使用我们的体系结构和超参数,您应该达到接近85%的精度。
考虑到我们拥有庞大的词汇量和一些节点很少的复杂网络体系结构,可以说我们取得了很好的结果。
最后,作为以后的工作,基于预处理数据和模型的简单性,考虑如何部署数据并创建用于分析文本情感的API可能会很有趣。