7.1.3.2、使用飞桨实现基于LSTM的情感分析模型的网络定义

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: 该文章详细介绍了如何使用飞桨框架实现基于LSTM的情感分析模型,包括网络定义、模型训练、评估和预测的完整流程,并提供了相应的代码实现。


7.1.3、使用飞桨实现基于LSTM的情感分析模型

3.2 网络定义

在讲解卷积神经网络的的章节,我们详细列出了每一种神经网络使用基础算子拼装的详细网络配置,但实际上对于一些常用的网络结构,飞桨框架提供了现成的中高层函数支持。下面用于情感分析的长短时记忆模型就使用paddle.nn.LSTMAPI实现。如果读者对使用基础算子拼装LSTM的内容感兴趣,可以查阅paddle.nn.LSTM类的源代码。

3.2.1 seq2vec的文本表示

在讲解情感分析网络搭建之前,我们先介绍一下seq2vec

句子情感分析的关键技术是如何将文本表示成一个携带语义的文本向量。随着深度学习技术的快速发展,目前常用的文本表示技术有LSTM,GRU,RNN等方法。 PaddleNLP提供了一系列的文本表示技术,集成在seq2vec模块中。

paddlenlp.seq2vec模块的作用是将输入的序列文本,表示成一个语义向量。

图10:paddlenlp.seq2vec示意图

本模块主要使用了seq2vec的LSTMEncoder部分,其核心结构是使用的LSTM,LSTMEncoder的核心是实现下面的内容:

  • LSTMEncoder:
  • get_input_dim : encoder 的输入的维度
  • get_output_dim : encoder 的输出维度
  • forward : 前向传播的逻辑

LSTMEncoder核心实现代码如下:

class LSTMEncoder(nn.Layer):
    def __init__(self,
                 input_size,
                 hidden_size,
                 num_layers=1,
                 direction="forward",
                 dropout=0.0,
                 pooling_type=None,
                 **kwargs):
        super().__init__()
        self._input_size = input_size
        self._hidden_size = hidden_size
        self._direction = direction
        self._pooling_type = pooling_type
        # LSTM层
        self.lstm_layer = nn.LSTM(
            input_size=input_size,
            hidden_size=hidden_size,
            num_layers=num_layers,
            direction=direction,
            dropout=dropout,
            **kwargs)
    def get_input_dim(self):
        # 获取LSTM的输入的维度
        return self._input_size
    def get_output_dim(self):
        # 获取LSTM的输出的维度
        if self._direction == "bidirect":
            return self._hidden_size * 2
        else:
            return self._hidden_size
    def forward(self, inputs, sequence_length):
        encoded_text, (last_hidden, last_cell) = self.lstm_layer(
            inputs, sequence_length=sequence_length)
        # 如果是双向的LSTM,则输出最后一个时刻
        if self._direction != 'bidirect':
            # 输出最后一层的向量
            output = last_hidden[-1, :, :]
        else:
            # 单向的LSTM的实现,把倒数第一层和倒数第二层的向量拼接后输出
            output = paddle.concat(
                    (last_hidden[-2, :, :], last_hidden[-1, :, :]), axis=1)
        return output

3.2.2 情感分析网络

图11:情感分析网络

上图显示的是情感分析网络,分为文本向量化,序列学习,特征学习,分类四部分:

1.文本向量化:这部分的作用就是把文本id的形式转换成稠密的向量的形式;

2.序列学习:序列学习是seq2vec层,具体使用的是双向LSTM用来学习文本序列的相关关系

3.特征学习:特征学习使用了全连接层,主要是对前面的序列特征进一步学习,得到全局的特征信息;

4.分类:分类是输出层,本质上是一个全连接层,主要是把特征转化成情感类别概率输出

情感分析网络代码实现如下:

import paddlenlp as ppnlp
class LSTMModel(nn.Layer):
    def __init__(self,
                 vocab_size,
                 num_classes,
                 emb_dim=128,
                 padding_idx=0,
                 lstm_hidden_size=198,
                 direction='forward',
                 lstm_layers=1,
                 dropout_rate=0.0,
                 pooling_type=None,
                 fc_hidden_size=96):
        super().__init__()
        # 文本向量化
        # 首先将输入word id 查表后映射成 word embedding
        self.embedder = nn.Embedding(
            num_embeddings=vocab_size,
            embedding_dim=emb_dim,
            padding_idx=padding_idx)
        # 序列学习
        # 将word embedding经过LSTMEncoder变换到文本语义表征空间中
        self.lstm_encoder = ppnlp.seq2vec.LSTMEncoder(
            emb_dim,
            lstm_hidden_size,
            num_layers=lstm_layers,
            direction=direction,
            dropout=dropout_rate,
            pooling_type=pooling_type)
        # 特征学习
        # LSTMEncoder.get_output_dim()方法可以获取经过encoder之后的文本表示hidden_size
        self.fc = nn.Linear(self.lstm_encoder.get_output_dim(), fc_hidden_size)
        # 输出层
        # 最后的分类器
        self.output_layer = nn.Linear(fc_hidden_size, num_classes)
     # forwad函数即为模型前向计算的函数,它有两个输入,分别为:
    # input为输入的训练文本,其shape为[batch_size, max_seq_len]
    # seq_len训练文本对应的真实长度,其shape维[batch_size]
    def forward(self, text, seq_len):
        # 输入的文本的维度(batch_size, num_tokens, embedding_dim)
        embedded_text = self.embedder(text)
        # lstm的输出的维度: (batch_size, num_tokens, num_directions*lstm_hidden_size)
        # 如果lstm是双向的,则num_directions = 2,如果是单向的则num_directions的维度是1
        text_repr = self.lstm_encoder(embedded_text, sequence_length=seq_len)
        # 全连接层的的维度是(batch_size, fc_hidden_size)
        fc_out = paddle.tanh(self.fc(text_repr))
        # 输出层的维度(batch_size, num_classes)
        logits = self.output_layer(fc_out)
        # probs 分类概率值
        probs = F.softmax(logits, axis=-1)
        return probs

3.3 模型训练

在完成模型定义之后,我们就可以开始训练模型了。当训练结束以后,我们可以使用测试集合评估一下当前模型的效果,代码如下:

# 定义训练参数
epoch_num = 4
batch_size = 128
learning_rate = 5e-5
dropout_rate = 0.2
num_layers = 1
hidden_size = 256
embedding_size = 256
vocab_size=len(vocab)
print(vocab_size)
# 实例化LSTM模型
model= LSTMModel(
        vocab_size,
        num_classes=2,
        emb_dim=embedding_size,
        lstm_layers=num_layers,
        direction='bidirectional',
        padding_idx=vocab['[PAD]'])
# 指定优化策略,更新模型参数
optimizer = paddle.optimizer.Adam(learning_rate=learning_rate, beta1=0.9, beta2=0.999, parameters= model.parameters())

W0402 11:50:48.497135 8808 device_context.cc:447] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 10.1, Runtime API Version: 10.1

W0402 11:50:48.502187 8808 device_context.cc:465] device: 0, cuDNN Version: 7.6.

paddle.seed(0)
random.seed(0)
np.random.seed(0)
# 定义训练函数
# 记录训练过程中的损失变化情况,可用于后续画图查看训练情况
losses = []
steps = []
def train(model):
    # 开启模型训练模式
    model.train()
    
    global_step =  0
    for epoch in range(epoch_num):
        for step, (sentences,valid_length, labels) in enumerate(train_loader):
        
            # 前向计算,将数据feed进模型,并得到预测的情感标签和损失
            logits = model(sentences,valid_length)
            # 计算损失
            loss = F.cross_entropy(input=logits, label=labels, soft_label=False)
            loss = paddle.mean(loss)
            # 后向传播
            loss.backward()
            # 更新参数
            optimizer.step()
            # 清除梯度
            optimizer.clear_grad()
            
            global_step+=1
            if global_step % 100 == 0:
                # 记录当前步骤的loss变化情况
                losses.append(loss.numpy()[0])
                steps.append(step)
                # 打印当前loss数值
                print("epoch %d, step %d, loss %.3f" % (epoch,global_step, loss.numpy()[0]))
#训练模型
train(model)
# 保存模型,包含两部分:模型参数和优化器参数
model_name = "sentiment_classifier"
# 保存训练好的模型参数
paddle.save(model.state_dict(), "checkpoint/{}.pdparams".format(model_name))
# 保存优化器参数,方便后续模型继续训练
paddle.save(optimizer.state_dict(), "checkpoint/{}.pdopt".format(model_name))

epoch 0, step 100, loss 0.690

epoch 1, step 200, loss 0.680

epoch 1, step 300, loss 0.560

epoch 2, step 400, loss 0.468

从上述的loss变化过程可以看到,loss是不断在降低的。

tips

使用GPU训练,设置epoch为20,训练2分钟左右,loss降低到0.313,大概1100个step之后就可以达到90.50%的准确率。训练的epoch越多,精度可能会更高,但不是绝对的会升高,会出现过拟合现象(训练集的loss降低,验证集的精度反而升高的现象)。

3.4 模型评估

在模型训练阶段,保存了训练完成的模型参数。因此在模型评估阶段,首先需要加载保存到磁盘的模型参数,在获得完整的模型之后,利用相应的测试集开始进行模型评估

@paddle.no_grad()
def evaluate(model):
    # 开启模型测试模式,在该模式下,网络不会进行梯度更新
    model.eval()
    # 定义以上几个统计指标
    tp, tn, fp, fn = 0, 0, 0, 0
    
    for sentences,valid_lens, labels in test_loader:
    
        # 获取模型对当前batch的输出结果
        logits = model(sentences,valid_lens)
        
        # 使用softmax进行归一化
        probs = F.softmax(logits)
        # 把输出结果转换为numpy array数组,比较预测结果和对应label之间的关系,并更新tp,tn,fp和fn
        probs = probs.numpy()
        for i in range(len(probs)):
            # 当样本是的真实标签是正例
            if labels[i][0] == 1:
                # 模型预测是正例
                if probs[i][1] > probs[i][0]:
                    tp += 1
                # 模型预测是负例
                else:
                    fn += 1
            # 当样本的真实标签是负例
            else:
                # 模型预测是正例
                if probs[i][1] > probs[i][0]:
                    fp += 1
                # 模型预测是负例
                else:
                    tn += 1
    # 整体准确率
    accuracy = (tp + tn) / (tp + tn + fp + fn)
    
    # 输出最终评估的模型效果
    print("TP: {}\nFP: {}\nTN: {}\nFN: {}\n".format(tp, fp, tn, fn))
    print("Accuracy: %.4f" % accuracy)
# 加载训练好的模型进行预测,重新实例化一个模型,然后将训练好的模型参数加载到新模型里面
state_dict=paddle.load('checkpoint/sentiment_classifier.pdparams')
model.load_dict(state_dict)
# 评估模型
evaluate(model)

TP: 512

FP: 99

TN: 508

FN: 81

Accuracy: 0.8500

从输出结果可以看到,模型的准确率达到了86%。TP数量是481,FP的数量是56,TN的数量是551,FN的数量是112.(每次运行结果稍有差异)

3.5 模型预测

模型预测部分就是把文本转换成Tensor形式的ID行时候,利用模型预测得到输出,然后选取概率最大值的索引就行了。

label_map = {0: 'negative', 1: 'positive'}
text=test_ds[0]['text']
# 文本转换成ID的形式
input_ids=tokenizer.encode(text)
valid_lens=len(input_ids)
# 转换成Tensor的形式
input_ids=paddle.to_tensor([input_ids])
valid_lens=paddle.to_tensor([valid_lens])
# 模型预测
probs=model(input_ids,valid_lens)
# 取概率最大值的ID
idx = np.argmax(probs, axis=-1)
idx = idx.tolist()
# 得到预测标签
labels = [label_map[i] for i in idx]
# 看看预测样例分类结果
print('Data: {} \t Label: {}'.format(text, labels[0]))

Data: 这个宾馆比较陈旧了,特价的房间也很一般。总体来说一般 Label: negative

四、思考一下

[1] 情感分析任务对你有什么启发?

[2] 除了LSTM,你还能想到那些其他方法,构造一个句子的向量表示?

[3] 对一个句子生成一个单一的向量表示有什么缺点,你还知道其他方式吗?

相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
相关文章
|
1月前
|
安全 网络安全 数据安全/隐私保护
访问控制列表(ACL)是网络安全中的一种重要机制,用于定义和管理对网络资源的访问权限
访问控制列表(ACL)是网络安全中的一种重要机制,用于定义和管理对网络资源的访问权限。它通过设置一系列规则,控制谁可以访问特定资源、在什么条件下访问以及可以执行哪些操作。ACL 可以应用于路由器、防火墙等设备,分为标准、扩展、基于时间和基于用户等多种类型,广泛用于企业网络和互联网中,以增强安全性和精细管理。
172 7
|
5天前
|
监控 安全 BI
什么是零信任模型?如何实施以保证网络安全?
随着数字化转型,网络边界不断变化,组织需采用新的安全方法。零信任基于“永不信任,永远验证”原则,强调无论内外部,任何用户、设备或网络都不可信任。该模型包括微分段、多因素身份验证、单点登录、最小特权原则、持续监控和审核用户活动、监控设备等核心准则,以实现强大的网络安全态势。
|
2月前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于BP神经网络的苦瓜生长含水量预测模型matlab仿真
本项目展示了基于BP神经网络的苦瓜生长含水量预测模型,通过温度(T)、风速(v)、模型厚度(h)等输入特征,预测苦瓜的含水量。采用Matlab2022a开发,核心代码附带中文注释及操作视频。模型利用BP神经网络的非线性映射能力,对试验数据进行训练,实现对未知样本含水量变化规律的预测,为干燥过程的理论研究提供支持。
|
28天前
|
安全 网络安全 数据安全/隐私保护
访问控制列表(ACL)是网络安全管理的重要工具,用于定义和管理网络资源的访问权限。
访问控制列表(ACL)是网络安全管理的重要工具,用于定义和管理网络资源的访问权限。ACL 可应用于路由器、防火墙等设备,通过设定规则控制访问。其类型包括标准、扩展、基于时间和基于用户的ACL,广泛用于企业网络和互联网安全中,以增强安全性、实现精细管理和灵活调整。然而,ACL 也存在管理复杂和可能影响性能的局限性。未来,ACL 将趋向智能化和自动化,与其他安全技术结合,提供更全面的安全保障。
87 4
|
1月前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
84 2
|
1月前
|
存储 数据安全/隐私保护 云计算
多云网络环境:定义、优势与挑战
多云网络环境:定义、优势与挑战
42 5
|
1月前
|
运维 网络协议 算法
7 层 OSI 参考模型:详解网络通信的层次结构
7 层 OSI 参考模型:详解网络通信的层次结构
132 1
|
1月前
|
运维 物联网 网络虚拟化
网络功能虚拟化(NFV):定义、原理及应用前景
网络功能虚拟化(NFV):定义、原理及应用前景
75 3
|
2月前
|
网络协议 前端开发 Java
网络协议与IO模型
网络协议与IO模型
123 4
网络协议与IO模型
|
2月前
|
机器学习/深度学习 网络架构 计算机视觉
目标检测笔记(一):不同模型的网络架构介绍和代码
这篇文章介绍了ShuffleNetV2网络架构及其代码实现,包括模型结构、代码细节和不同版本的模型。ShuffleNetV2是一个高效的卷积神经网络,适用于深度学习中的目标检测任务。
103 1
目标检测笔记(一):不同模型的网络架构介绍和代码