Deep Learning with Sequence Data and Text
在本章中,我们将介绍以下主题:
- 用于构建深度学习模型的文本数据的不同表示
- 理解RNN和RNN的不同实现,例如Long Short-Term Memory(LSTM)和Gated Recurrent Unit (GRU),它们为文本和顺序数据的大多数深度学习模型提供了动力。
- 对序列数据使用一维卷积
可以使用RNN构建的一些应用程序包括:
- Document classifiers:识别推特或评论的情绪,对新闻文章进行分类
- Sequence-to-sequence learning:用于诸如语言翻译、将英语转换为法语等任务
- Time-series forecasting:根据前几天商店销售的详细信息,预测商店的销售情况
处理文本数据
文本是常用的顺序数据类型之一,文本数据可以看作是字符序列,也可以是单词序列。对于大多数问题来说,把文本看作一个词序列是很常见的。诸如RNN之类的深度学习顺序模型及其变体能够从文本数据中学习重要的模式,该文本数据可以解决诸如这样的区域中的问题:
- 自然语言理解
- 文档分类
- 情感分类
这些顺序模型也是各种系统(如问答系统)的重要组成部分。虽然这些模型在构建这些应用程序时非常有用,但由于其固有的复杂性,它们对人类语言无法理解。这些顺序模型能够成功地找到有用的模式,然后用于执行不同的任务。将深度学习应用于文本是一个快速发展的领域,每个月都会出现许多新的技术。我们将覆盖最重要的组件,这些组件最适合现代的深度学习应用。深度学习模型和其他机器学习模型一样,不理解文本,因此我们需要将文本转换为数字表示,将文本转换为数字表示的过程称为矢量化,可以以不同的方式进行,如下所述:
- 将文本转换为单词,并将每个单词表示为向量。
- 将文本转换为字符,并将每个字符表示为向量。
- 创建单词的n-gram,并将它们表示为向量
文本数据可以分解为以下表示之一。每一个较小的文本单元称为令牌,将文本分解为令牌的过程称为标记化。Python中有许多强大的库可以帮助我们进行标记化。一旦我们将文本数据转换成令牌,我们就需要将每个令牌映射到一个向量中。One-hot编码和字嵌入是将令牌映射到向量的两种最流行的方法。下图总结了将文本转换为向量表示的步骤。
标记化
给定一个句子,将其分割成字符或单词都称为标记化。有些库(如spacy)为标记化提供了复杂的解决方案。让我们使用简单的Python函数,如split和list将文本转换为令牌。
N-gram表示
我们已经看到了文本如何被表示为字符和单词。有时,将两个、三个或更多的单词放在一起是有用的。n-gram是从给定文本中提取的单词组。在n-gram中,n表示可以一起使用的单词的数量。ngrams函数接受一个单词序列作为其第一个参数,并将要分组的单词数作为第二个参数。许多有监督的机器学习模型,例如朴素贝叶斯,使用n-grams图来改进它们的特征空间,n-grams也用于拼写更正和文本摘要任务。n-gram表示的一个挑战是它失去了文本的顺序性。它常与浅层机器学习模型一起使用。这种技术很少用于深度学习,因为诸如RNN和Conv1D这样的体系结构会自动学习这些表示。
向量化
有两种常用的将生成的标记映射到数字向量的方法,称为 one-hot encoding和word embedding。
-
One-hot编码
在One-hot编码中,每个令牌由长度N的向量表示,其中N是词汇表的大小。词汇是文档中独特词的总数。
class Dictionary(object): def __init__(self): self.word2idx = {} # 生成一个字典,字典内每个单词对应一个索引: {'the': 1, 'action': 2, 'scenes': 3, 'were': 4, 'top': 5, 'notch': 6, 'in': 7, 'this': 8, 'movie.': 9, 'Thor': 10, 'has': 11, 'never': 12, 'been': 13, 'epic': 14, 'MCU.': 15, 'He': 16, 'does': 17, 'some': 18, 'pretty': 19, 'sh*t': 20, 'movie': 21, 'and': 22, 'he': 23, 'is': 24, 'definitely': 25, 'not': 26, 'under-powered': 27, 'anymore.': 28, 'unleashed': 29, 'this,': 30, 'I': 31, 'love': 32, 'that.': 33} self.idx2word = [] # 生成一个字符串数组,是字典内单词的集合 self.length = 0 # 字典或字符串的长度 def add_word(self,word): if word not in self.idx2word: # 将没有出现的过单词加入到字典及字符串中 self.idx2word.append(word) self.word2idx[word] = self.length + 1 # 这一步同时将单词匹配索引加入字典 self.length += 1 # 字典长度加1 return self.word2idx[word] # 返回的是当前单词在字典内的索引 def __len__(self): return len(self.idx2word) def onehot_encoded(self,word): vec = np.zeros(self.length) # 把所有元素归零 vec[self.word2idx[word]] = 1 # 把索引所在处置为1 return vec
上面的代码提供了三个重要的功能:
1、初始化函数__init__,创建一个word2idx字典,它将存储所有唯一的单词以及索引。idx2word列表存储所有唯一的单词,length变量包含文档中唯一单词的总数。
2、add_word函数接受一个单词并将其添加到word2idx和idx2word中,并增加词汇表的长度,前提是单词是不同的。
3、onehot-encoded函数接受一个单词,并返回长度为N的向量,除单词的索引处不外,即如果传递的单词的索引为2,则索引2处的向量值为1,其余的值将为零。one-hot编码的缺点是数据太稀疏。并且随着词汇中唯一字的数量增加,向量的大小迅速增长,这被认为是一个限制,因此很少与深度学习一起使用。
-
word embedding
对于一些是通过深层学习算法来解决的问题,word embedding是表示文本数据的非常流行的方式。word embedding提供了一个由浮点数填充的单词的密集表示。向量维度根据词汇大小而变化。通常使用尺寸为50、100、256、300、有时为1000的word embedding。维大小是我们需要在训练阶段训练的Hyper-参数。
如果我们试图在one-hot表示中表示20,000的词汇,那么我们将以20,000x20,000的数字结束,其中大多数将为零。相同的词汇表可以用字嵌入表示为20,000 x dimension大小,其中dimension尺寸大小可以是10,50,300,等等。
创建word embedding的一种方法是从包含随机数的每个令牌的密集向量开始,然后训练一个模型,例如文档分类器或情感分类器。表示令牌的浮点数将被调整,使语义上更接近的单词具有类似的表示。
建立情感分类器训练word embedding
在上一节中,我们简要地学习了word embedding而没有实现它。在本节中,我们将下载一个名为IMDB的数据集,其中包含评论,并构建一个情感分类器。计算评审的情绪是积极的、消极的还是未知的。在构建过程中,我们还将为IMDB数据集中的单词进行单词嵌入训练。我们将使用一个名为torchtext的库,它使下载、文本矢量化和批处理等许多进程更加容易。训练情感分类器将包括以下步骤:
-
下载IMDB数据并执行文本标记化
-
建立词汇表
-
生成成批向量
-
使用嵌入创建网络模型
-
训练模型
-
下载IMDB数据并执行文本标记化
对于与计算机视觉有关的应用程序,我们使用了torchvision库,为我们提供了大量的实用程序函数,有助于构建计算机视觉应用。同样,有一个名为torchtext的库,它是PyTorch的一部分,它的构建是为了与PyTorch一起工作,通过为文本提供不同的数据加载器和摘要,简化了许多与自然语言处理(NLP)相关的活动。在编写本报告时,torchtext不附带PyTorch安装,需要单独安装。您可以在计算机的命令行中运行以下代码以获取torchtext安装包:conda install torchtext
。torchtext.data实例定义了一个名为Field的类,它帮助我们定义数据必须如何读取和标记。
-
-
词嵌入训练的完整代码
import torch
import numpy as np
import os
from nltk import ngrams
from torch import nn, optim
import torch.nn as nn
from torch.autograd import Variable
from torch.optim import optimizer
from torchtext import data, datasets
from torchtext.vocab import GloVe
import torch.nn.functional as F
import matplotlib.pyplot as plt
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
device = torch.device('cuda')
class EmbNet(nn.Module):
def __init__(self, emb_size, hidden_size1, hidden_size2=200):
super().__init__()
self.embedding = nn.Embedding(emb_size, hidden_size1)
self.fc = nn.Linear(hidden_size2, 3)
def forward(self, x):
y = x.size(0) # 128
y1 = x.size(1) # 20
# y2 = x.size(2) # IndexError: Dimension out of range (expected to be in range of [-2, 1], but got 2)
embeds = self.embedding(x).view(x.size(0), -1)
out = self.fc(embeds)
D_F = F.log_softmax(out, dim=-1)
return F.log_softmax(out, dim=-1)
def fit(epoch,model,data_loader,phase = 'training',volatile = False):
if phase == 'training':
model.train()
if phase == 'validation':
model.eval()
volatile = True
running_loss = 0.0
running_correct = 0
optimizer = optim.Adam(model.parameters(), lr=0.001) # 训练效果比SGD好
# optimizer = optim.SGD(model.parameters(), lr=0.001)
# optimizer = optim.SGD([param for param in model.parameters() if param.requires_grad == True], lr=0.001)
for batch_idx,batch in enumerate(data_loader):
text,target = batch.text,batch.label
text,target = text.cuda(),target.cuda()
if phase == 'training':
optimizer.zero_grad()
output = model(text)
loss = F.nll_loss(output,target)
# running_loss += F.nll_loss(output,target,size_average=False).data[0] # IndexError: invalid index of a 0-dim tensor. Use tensor.item() to convert a 0-dim tensor to a Python number
running_loss += F.nll_loss(output,target,size_average=False).item()
preds = output.data.max(dim = 1,keepdim = True)[1]
running_correct += preds.eq(target.data.view_as(preds)).cpu().sum()
if phase == 'training':
loss.backward()
optimizer.step()
loss = running_loss/len(data_loader.dataset)
accuracy = 100. * running_correct/len(data_loader.dataset)
print(f'{phase} loss is {loss:{5}.{2}} and {phase} accuracy is {running_correct}/{len(data_loader.dataset)}{accuracy:{10}.{4}}')
return loss,accuracy
def achieve_():
TEXT = data.Field(lower=True, batch_first=True, fix_length=20)
LABEL = data.Field(sequential=False)
train,test = datasets.IMDB.splits(TEXT,LABEL)
TEXT.build_vocab(train,vectors = GloVe(name = '6B',dim = 300),max_size = 10000,min_freq = 10)
LABEL.build_vocab(train)
train_iter,test_iter = data.BucketIterator.splits((train,test),batch_size = 128,device = -1,shuffle = True)
lens = len(TEXT.vocab.stoi)
model = EmbNet(len(TEXT.vocab.stoi), 10).to(device) # model 必须在构建字典后定义
# model = EmbNet(len(TEXT.vocab.stoi), 300, 12000).to(device)
# model.embedding.weight.data = TEXT.vocab.vectors # requires_grad = True
# model.embedding.weight.requires_grad = False
# optimizer = optim.SGD([param for param in model.parameters() if param.requires_grad == True], lr=0.001)
train_losses,train_accuracy = [],[]
val_losses,val_accuracy = [],[]
train_iter.repeat = False
test_iter.repeat = False
for epoch in range(1,10):
epoch_loss,epoch_accuracy = fit(epoch,model,train_iter,phase='training')
val_epoch_loss,val_epoch_accuracy = fit(epoch,model,test_iter,phase='validation')
train_losses.append(epoch_loss)
train_accuracy.append(epoch_accuracy)
val_losses.append(val_epoch_loss)
val_accuracy.append(val_epoch_accuracy)
# plot the trainging and test loss
plt.figure(1)
plt.plot(range(1, len(train_losses) + 1), train_losses, 'bo', label='training loss')
plt.plot(range(1, len(val_losses) + 1), val_losses, 'r', label='validation loss')
plt.legend()
plt.savefig('Text_loss.jpg')
# plots the training and test accuracy
plt.figure(2)
plt.plot(range(1, len(train_accuracy) + 1), train_accuracy, 'bo', label='training accuracy')
plt.plot(range(1, len(val_accuracy) + 1), val_accuracy, 'r', label='val accuracy')
plt.legend()
plt.savefig('Text_accuracy')
def main():
# dic_()
# IMDB_()
achieve_()
# pretrain_()
# achieve_RNN()
pass
if __name__ == '__main__':
main()
训练结果
training loss is 0.84 and training accuracy is 12189/25000 48.76
validation loss is 0.73 and validation accuracy is 12958/25000 51.83
training loss is 0.7 and training accuracy is 13515/25000 54.06
validation loss is 0.7 and validation accuracy is 13409/25000 53.64
training loss is 0.69 and training accuracy is 14002/25000 56.01
validation loss is 0.69 and validation accuracy is 13838/25000 55.35
training loss is 0.67 and training accuracy is 14577/25000 58.31
validation loss is 0.68 and validation accuracy is 14274/25000 57.1
training loss is 0.66 and training accuracy is 15054/25000 60.22
validation loss is 0.67 and validation accuracy is 14621/25000 58.48
training loss is 0.65 and training accuracy is 15554/25000 62.22
validation loss is 0.67 and validation accuracy is 14906/25000 59.62
training loss is 0.64 and training accuracy is 15974/25000 63.9
validation loss is 0.66 and validation accuracy is 15164/25000 60.66
training loss is 0.62 and training accuracy is 16360/25000 65.44
validation loss is 0.66 and validation accuracy is 15458/25000 61.83
training loss is 0.61 and training accuracy is 16699/25000 66.8
validation loss is 0.65 and validation accuracy is 15725/25000 62.9
Process finished with exit code 0
附:
卷积神经网络学到的模式具有平移不变性(translation invariant)。卷积神经网络在图像
右下角学到某个模式之后,它可以在任何地方识别这个模式,比如左上角。对于密集连
接网络来说,如果模式出现在新的位置,它只能重新学习这个模式。这使得卷积神经网
络在处理图像时可以高效利用数据(因为视觉世界从根本上具有平移不变性),它只需
要更少的训练样本就可以学到具有泛化能力的数据表示。