defpreprocess_string(s):# Remove all non-word characters (everything except numbers and letters)
s = re.sub(r"[^\w\s]",'', s)# Replace all runs of whitespaces with no space
s = re.sub(r"\s+",'', s)# replace digits with no space
s = re.sub(r"\d",'', s)return s
deftokenize(x_train,y_train,x_val,y_val):
word_list =[]
stop_words =set(stopwords.words('english'))for sent in x_train:for word in sent.lower().split():
word = preprocess_string(word)if word notin stop_words and word !='':
word_list.append(word)
corpus = Counter(word_list)# sorting on the basis of most common words
corpus_ =sorted(corpus,key=corpus.get,reverse=True)[:1000]# creating a dict
onehot_dict ={w:i+1for i,w inenumerate(corpus_)}# tokenize
final_list_train,final_list_test =[],[]for sent in x_train:
final_list_train.append([onehot_dict[preprocess_string(word)]for word in sent.lower().split()if preprocess_string(word)in onehot_dict.keys()])for sent in x_val:
final_list_test.append([onehot_dict[preprocess_string(word)]for word in sent.lower().split()if preprocess_string(word)in onehot_dict.keys()])
encoded_train =[1if label =='positive'else0for label in y_train]
encoded_test =[1if label =='positive'else0for label in y_val]return np.array(final_list_train), np.array(encoded_train),np.array(final_list_test), np.array(encoded_test),onehot_dict
x_train,y_train,x_test,y_test,vocab = tokenize(x_train,y_train,x_test,y_test)技术支持
我们来分析一下 x_train 中的标记长度。
rev_len =[len(i)for i in x_train]
pd.Series(rev_len).hist()技术支持
classSentimentRNN(nn.Module):def__init__(self,no_layers,vocab_size,hidden_dim,embedding_dim,drop_prob=0.5):super(SentimentRNN,self).__init__()
self.output_dim = output_dim
self.hidden_dim = hidden_dim
self.no_layers = no_layers
self.vocab_size = vocab_size
# embedding and LSTM layers
self.embedding = nn.Embedding(vocab_size, embedding_dim)#lstm
self.lstm = nn.LSTM(input_size=embedding_dim,hidden_size=self.hidden_dim,
num_layers=no_layers, batch_first=True)# dropout layer
self.dropout = nn.Dropout(0.3)# linear and sigmoid layer
self.fc = nn.Linear(self.hidden_dim, output_dim)
self.sig = nn.Sigmoid()defforward(self,x,hidden):
batch_size = x.size(0)# embeddings and lstm_out
embeds = self.embedding(x)# shape: B x S x Feature since batch = True#print(embeds.shape) #[50, 500, 1000]
lstm_out, hidden = self.lstm(embeds, hidden)
lstm_out = lstm_out.contiguous().view(-1, self.hidden_dim)# dropout and fully connected layer
out = self.dropout(lstm_out)
out = self.fc(out)# sigmoid function
sig_out = self.sig(out)# reshape to be batch_size first
sig_out = sig_out.view(batch_size,-1)
sig_out = sig_out[:,-1]# get last batch of labels# return last sigmoid output and hidden statereturn sig_out, hidden
definit_hidden(self, batch_size):''' Initializes hidden state '''# Create two new tensors with sizes n_layers x batch_size x hidden_dim,# initialized to zero, for hidden state and cell state of LSTM
h0 = torch.zeros((self.no_layers,batch_size,self.hidden_dim)).to(device)
c0 = torch.zeros((self.no_layers,batch_size,self.hidden_dim)).to(device)
hidden =(h0,c0)return hidden 技术支持
现在我们将用所需参数初始化上面定义的 SentimentRNN 类。
no_layers =2
vocab_size =len(vocab)+1#extra 1 for padding
embedding_dim =64
output_dim =1
hidden_dim =256
model = SentimentRNN(no_layers,vocab_size,hidden_dim,embedding_dim,drop_prob=0.5)#moving to gpu
model.to(device)print(model)技术支持
# loss and optimization functions
lr=0.001
criterion = nn.BCELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)# function to predict accuracydefacc(pred,label):
pred = torch.round(pred.squeeze())return torch.sum(pred == label.squeeze()).item()技术支持
defpredict_text(text):
word_seq = np.array([vocab[preprocess_string(word)]for word in text.split()if preprocess_string(word)in vocab.keys()])
word_seq = np.expand_dims(word_seq,axis=0)
pad = torch.from_numpy(padding_(word_seq,500))
inputs = pad.to(device)
batch_size =1
h = model.init_hidden(batch_size)
h =tuple([each.data for each in h])
output, h = model(inputs, h)return(output.item())
index =30print(df['review'][index])print('='*70)print(f'Actual sentiment is : {df["sentiment"][index]}')print('='*70)
pro = predict_text(df['review'][index])
status ="positive"if pro >0.5else"negative"
pro =(1- pro)if status =="negative"else pro
print(f'Predicted sentiment is {status} with a probability of {pro}')技术支持
输出:
这个完整笔记本是在 DataLab 中开发的,可在此工作簿访问。请注意,如果您使用 CPU 执行,代码运行可能耗时较长;而使用 GPU 则可显著缩短训练时间。
NLP 与 PyTorch 简介
自然语言处理(NLP)是现代人工智能的重要组成部分,使机器能够理解并回应人类语言。随着数字交互的激增,NLP 的重要性与日俱增。PyTorch 作为广受欢迎的开源机器学习库,凭借其灵活性和高效的张量计算,为 NLP 任务提供了强大的工具。其动态计算图也便于轻松修改和构建复杂模型,非常适合本教程的使用场景。
如果您想进一步学习 NLP,欢迎查看我们的 Python 自然语言处理技能路径;如果您更愿意用 R 学习 NLP 的方法,请查看 DataCamp 上的 R 语言自然语言处理入门课程。
环境配置
由于操作系统、包管理器偏好、编程语言以及计算平台等因素,配置 PyTorch 环境有时会较具挑战性。安装过程可能会因这些因素略有不同,需要您运行相应的特定命令。
要获取合适的安装命令,您可以访问 PyTorch 官方的本地安装页面,在那里选择您的偏好设置并获取所需的安装说明。
本教程将使用 DataLab。若您希望跟着实操,完整的基于 PyTorch 训练情感分析模型的代码可在此 DataLab 工作簿中获取。
张量简介
张量(Tensor)是数学和物理中用于推广标量、向量和矩阵的基础数据结构。它是能够高效存储和处理大量数值数据的多维数组。张量具有明确的形状、大小和数据类型,适用于多种计算操作。
在 PyTorch 中,张量是主要的构建块和数据表示对象。PyTorch 的张量类似于 NumPy 数组,但拥有专为深度学习计算设计的额外功能与优化。PyTorch 的张量操作可利用硬件加速(如 GPU)来高效计算复杂的神经网络。
由于语言数据天然具有序列性和层次性,张量在自然语言处理(NLP)任务中发挥着关键作用。
NLP 涉及对文本信息的处理与理解。通过将词、句子或文档编码为数值向量,张量使文本数据的表示与操作成为可能。
这种数值表示允许运用深度学习模型高效处理并从文本数据中学习。张量支持对大规模语言数据集的高效处理,促进神经网络的训练,并使注意力机制等先进技术得以应用,从而构建更准确的 NLP 模型。
词向量
词向量(Word Embeddings)是在连续向量空间中对词语进行的稠密向量表示。它旨在捕捉词语之间的语义和句法关系,帮助更好地理解和语境化文本数据。通过将词表示为数值向量,词向量能够反映语义相似与差异,使算法可以将词作为有意义的数值输入来处理。
通俗来说,嵌入是一种把词变成数字的巧妙方法。这些数字带有特殊含义,能捕捉词与词之间的关系。就像一套“密码”,帮助计算机更轻松地理解和处理词语。
Word2Vec 和 GloVe 是两种常见的词向量生成方法。Word2Vec 是一种基于神经网络的模型,它通过在给定目标词的情况下预测其上下文(连续词袋——CBOW),或根据上下文预测目标词(Skip-gram)来学习词表示。
GloVe(Global Vectors for Word Representation)是一种基于计数的方法,它利用大规模语料中的词共现统计来构建词向量。它能够捕捉全局的词关系,在词类比任务中通常具有更好的表现。
NLP 模型架构
情感分析任务的示例模型架构。
情感分析是 NLP 中的常见任务,目标是理解一段文本中表达的情感,通常分为正向、负向或中性。要完成此任务,可以使用简单的循环神经网络(RNN)或更高级的变体长短期记忆网络(LSTM)。
RNN
RNN 架构按序处理文本,将每个词依次输入。网络维护一个随每次输入而更新的隐藏状态,用于捕捉到目前为止处理过的序列信息。
该隐藏状态相当于网络的记忆。然而,标准 RNN 在处理长序列时会遇到“梯度消失”问题,即信息贡献会随时间呈几何级衰减,使网络遗忘较早的输入。您可以在我们的RNN 教程中了解更多关于循环神经网络的内容。
LSTM
为解决这一问题,研发了 RNN 的一种变体——LSTM。LSTM 通过在隐藏状态中引入更复杂的内部结构来维持更长的上下文或“记忆”。它包含一系列“门”(输入门、遗忘门和输出门),用于控制信息在记忆状态中的流入与流出。
输入门决定应在多大程度上将新信息存入记忆状态;遗忘门决定需要丢弃哪些信息;输出门决定向序列中下一个 LSTM 单元暴露多少内部状态。
RNN 或 LSTM 模型通常将句子或文档中的词序列作为输入。每个词通常表示为一个稠密向量(嵌入),用于捕捉词的语义。
网络按词逐个处理序列,根据当前词及前一状态更新其内部状态。
最终的网络状态用于预测情感。该状态会传入全连接层,并经由 softmax 激活函数输出情感类别(如正向、负向、中性)的概率分布。
概率最高的类别即为模型的预测结果。
这是一个基础设置,还可以通过双向 LSTM(双向处理序列)、注意力机制(使模型聚焦于序列中重要部分)等技术进一步增强。
在 PyTorch 中训练用于情感分析的 LSTM 模型
使用 PyTorch 构建情感分析模型的端到端 Python 代码示例
1. 加载数据集
在本示例中,我们将使用 IMDB 5 万条影评数据集。目标是训练一个 LSTM 模型来预测情感。可能的取值有两个:"positive" 和 "negative"。因此,这是一个二分类任务。
2. 探索性数据分析
输出:
3. 文本预处理
文本预处理和分词是关键的第一步。首先,我们通过去除标点、多余空格和数字来清洗文本数据。
然后将句子拆分为单词,移除常见词(即“停用词”),并统计数据集中最常用的 1000 个词。随后为这些词分配唯一标识符,形成用于独热编码的词典。
本质上,这段代码将原始文本句子转换为这些唯一标识符的序列,把人类语言翻译成机器学习模型能够理解的格式。
我们来分析一下
x_train中的标记长度。4. 为模型准备数据
鉴于每条评论的标记长度不一,需要将其标准化以保持一致性。由于大多数评论少于 500 个标记,我们将 500 设为所有评论的固定长度。
接下来,我们使用
DataLoader类创建用于模型训练的最终数据集。输出:
5. 定义 LSTM 模型
这部分代码定义了一个使用循环神经网络(RNN)架构的情感分析模型,具体来说是我们上文提到的长短期记忆网络(LSTM)。SentimentRNN 类是一个 PyTorch 模型,其起始为嵌入层,用于将词索引转换为能捕捉词语语义的稠密表示。随后是处理词嵌入序列的 LSTM 层。
LSTM 的隐藏状态会经过一个 dropout 层(用于正则化模型并防止过拟合)和一个全连接层,将 LSTM 的输出映射到最终预测。预测再通过 sigmoid 激活函数,将原始输出值转换为概率。forward 方法定义了数据在网络中的前向传播,而 init_hidden 方法将 LSTM 层的隐藏状态初始化为零。
现在我们将用所需参数初始化上面定义的
SentimentRNN类。开始训练之前的最后一步是定义损失函数和优化器。本部分侧重于为我们的情感分析模型定义损失函数、优化方法以及用于计算准确率的辅助函数。
损失函数使用二元交叉熵损失(nn.BCELoss),这在本类二分类任务中十分常用。优化方法为 Adam(torch.optim.Adam),因其高效且内存占用低而广泛采用。此处 Adam 的学习率设为 0.001。
acc 函数是用于计算模型预测准确率的辅助函数。它将预测概率四舍五入到最接近的整数(0 或 1),与真实标签对比,然后计算正确预测的比例。
6. 开始训练
这部分代码对情感分析模型进行训练与验证。每个 epoch(迭代)包含训练阶段和验证阶段。训练阶段通过调整参数以最小化损失来让模型学习。
在验证阶段,使用独立数据集评估模型表现,以确保其学习到的是可泛化的模式,而非仅记住训练数据。
训练循环从初始化 LSTM 的隐藏状态并将模型置于训练模式开始。对于每个批次的数据,将模型预测与真实标签进行比较以计算损失,然后反向传播以更新模型参数。
为防止梯度过大(RNN 和 LSTM 训练中的常见问题),会对梯度进行最大值裁剪。
在验证循环中,模型被置于评估模式,使用验证数据评估其性能而不更新任何参数。在训练与验证阶段,代码都会跟踪每个 epoch 的损失和准确率。
如果验证损失得到改善,则保存当前模型参数,以记录训练过程中表现最好的模型。
最后,在每个 epoch 结束后打印该轮的平均损失和准确率,以便了解模型的学习进展。
输出:
7. 模型评估
这部分代码生成两幅图,直观展示模型训练过程中训练与验证的准确率和损失。第一幅子图为每个 epoch 后训练与验证准确率的折线图,有助于观察模型的学习效果与泛化情况。
第二幅子图为训练与验证损失的折线图,帮助我们判断模型是过拟合、欠拟合还是拟合得恰到好处。
输出:
8. 推理 / 预测
在这部分,我们创建了一个
predict_text函数用于预测给定文本的情感,并演示其用法。predict_text函数接收一段文本字符串作为输入,按照预定义词表将其转换为词索引序列,并通过填充与重整形准备为模型输入。随后函数初始化 LSTM 的隐藏状态,将输入馈送给模型,并返回该原始文本的输出概率。输出:
这个完整笔记本是在 DataLab 中开发的,可在此工作簿访问。请注意,如果您使用 CPU 执行,代码运行可能耗时较长;而使用 GPU 则可显著缩短训练时间。
后续步骤 / 模型改进
改进 NLP 模型通常需要多种策略,需根据具体任务的需求与约束进行定制。超参数调优是一种常见方法,涉及调整学习率、批量大小或神经网络层数等参数。
这些超参数会显著影响模型性能,通常通过网格搜索或随机搜索等技术进行优化。
迁移学习,尤其是基于 BERT 或 GPT 的模型,已在改进 NLP 任务方面展现出巨大潜力。这些模型在大规模文本语料上进行预训练,然后在特定任务上进行微调,从而利用预训练阶段获得的一般语言理解能力。该方法在包括情感分析在内的广泛 NLP 任务中持续取得业界领先的效果。
使用 PyTorch 的 NLP 真实应用
自然语言处理(NLP)模型,特别是基于 PyTorch 等框架实现的模型,已在真实场景中得到广泛应用,重塑了我们数字生活的诸多方面。
聊天机器人已成为客户服务平台不可或缺的一部分,它们利用 NLP 模型理解并响应用户问题。这些模型能够处理自然语言输入、推断意图并生成类人回复,提供流畅的交互体验。
在推荐系统领域,NLP 模型可分析用户评论与反馈,理解用户偏好,从而提升推荐的个性化程度。
情感分析工具同样高度依赖 NLP。此类工具可审视社交媒体帖子、客户评论或任意文本数据,并推断其背后的情感。企业常据此进行市场研究或评估公众对其产品或服务的看法,从而做出数据驱动的决策。
在这篇自然语言处理教程中,了解更多使用 Google BERT 的真实用例。
结语
PyTorch 为构建 NLP 模型提供了强大而灵活的平台。在本教程中,我们演示了使用 LSTM 架构开发情感分析模型的全过程,重点涵盖文本数据预处理、模型构建、训练与验证,以及对未见数据的预测。
这只是使用 NLP 与 PyTorch 的冰山一角。NLP 的应用极为广泛,从聊天机器人与推荐系统到情感分析工具,远不止于此。
该领域持续演进,尤其是随着 BERT 和 GPT 等迁移学习模型的出现,为未来探索带来更多令人兴奋的可能性。掌握基于 PyTorch 的 NLP 虽具挑战,但回报丰厚,它将为您开启理解并与周遭世界交互的新维度。
如果您想深入学习 PyTorch,请查看我们的 Deep Learning with PyTorch 课程。在这里,您将从 PyTorch 入门开始,探索其在神经网络与深度学习中的应用。随后,您将学习人工神经网络以及如何用真实数据对其进行训练。