• Pytorch实现基于LSTM的情感分析


    本文参考

    PyTorch深度学习项目实战100例

    https://weibaohang.blog.csdn.net/article/details/127154284?spm=1001.2014.3001.5501

    这段代码是一个基于PyTorch实现的情感分析模型,使用了LSTM(长短时记忆网络)作为核心结构。情感分析是一个自然语言处理任务,旨在确定给定文本的情感或情感极性,例如正面、负面

    导入必要的包

    介绍torchnet

    Torchnet 是一个轻量级框架,旨在为 PyTorch 提供一些抽象和实用工具,以简化常见的深度学习研究任务。Torchnet 的设计是模块化和扩展性的,这使得研究者可以更轻松地尝试新的思路和方法。

    !pip install torchnet
    !pip install keras
    
    • 1
    • 2
    import pickle
    import numpy as np
    import pandas as pd
    import torch
    import torch.nn as nn
    from tensorflow.keras.preprocessing.sequence import pad_sequences
    from sklearn.model_selection import train_test_split
    from torch.utils.data import TensorDataset
    from torch import optim
    from torchnet import meter
    from tqdm import tqdm```
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    做数据的导入

    数据+代码

    给必要的参数命名

    # config file
    #模型输入参数,需要自己根据需要调整
    num_layers = 3 # LSTM的层数
    hidden_dim = 100 # LSTM中的隐层大小
    epochs = 20 # 迭代次数
    batch_size = 32 # 每个批次样本大小
    embedding_dim = 20 # 每个字形成的嵌入向量大小
    output_dim = 2 # 输出维度,因为是二分类
    lr = 0.003 # 学习率
    device = 'cuda:0'
    file_path = 'data_single.csv' # 数据路径
    input_shape = 180 # 每句话的词的个数,如果不够需要使用0进行填充
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    加载文本数据

    
    # 加载文本数据
    def load_data(file_path, input_shape=20):
        df = pd.read_csv(file_path)
    
        # 标签及词汇表
        labels, vocabulary = list(df['label'].unique()), list(df['evaluation'].unique())
    
        # 构造字符级别的特征
        string = ''
        for word in vocabulary:
            string += word
    
        # 所有的词汇表
        vocabulary = set(string)
    
        # word2idx 将字映射为索引
        word_dictionary = {word: i + 1 for i, word in enumerate(vocabulary)}
        with open('word_dict.pk', 'wb') as f:
            pickle.dump(word_dictionary, f)
        # idx2word 将索引映射为字
        inverse_word_dictionary = {i + 1: word for i, word in enumerate(vocabulary)}
        # label2idx 将正反面映射为0和1
        label_dictionary = {label: i for i, label in enumerate(labels)}
        with open('label_dict.pk', 'wb') as f:
            pickle.dump(label_dictionary, f)
        # idx2label 将0和1映射为正反面
        output_dictionary = {i: labels for i, labels in enumerate(labels)}
    
        # 训练数据中所有词的个数
        vocab_size = len(word_dictionary.keys())  # 词汇表大小
        # 标签类别,分别为正、反面
        label_size = len(label_dictionary.keys())  # 标签类别数量
    
        # 序列填充,按input_shape填充,长度不足的按0补充
        # 将一句话映射成对应的索引 [0,24,63...]
        x = [[word_dictionary[word] for word in sent] for sent in df['evaluation']]
        # 如果长度不够input_shape,使用0进行填充
        x = pad_sequences(maxlen=input_shape, sequences=x, padding='post', value=0)
        # 形成标签0和1
        y = [[label_dictionary[sent]] for sent in df['label']]
        #     y = [np_utils.to_categorical(label, num_classes=label_size) for label in y]
        y = np.array(y)
    
        return x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary,word_dictionary,vocabulary
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    读取数据返回参数

    变量名描述
    x输入数据
    y标签数据
    output_dictionary标签的反向映射
    vocab_size词汇表大小
    label_size标签类别数量
    inverse_word_dictionary字符索引的反向映射
    word_dictionary字符到索引的映射
    vocabulary所有可能的字符集合

    创建LSTM 网路结构

    LSTM(Long Short-Term Memory)是一种循环神经网络(Recurrent Neural Network,RNN)的变种,它在处理序列数据时具有很好的性能,特别是在长序列上能够更好地捕捉长期依赖关系。下面是关于LSTM网络结构的说明:

    背景:LSTM是为了解决传统RNN中的梯度消失和梯度爆炸问题而提出的。它引入了特殊的记忆单元来维护和控制信息的流动,以更好地捕捉序列数据中的长期依赖关系。

    LSTM单元:LSTM网络的基本构建单元是LSTM单元。每个LSTM单元包括以下组件:

    • 输入门(Input Gate):控制新信息的输入。
    • 遗忘门(Forget Gate):控制过去信息的遗忘。
    • 输出门(Output Gate):控制输出的生成。
    • 细胞状态(Cell State):用于维护长期依赖关系的记忆。

    记忆细胞:LSTM单元内部的细胞状态是其核心。它可以看作一个传送带,可以在不同时间步骤上添加或删除信息。通过输入门、遗忘门和输出门来控制信息的读取、写入和遗忘,以保持对序列中重要信息的长期记忆。

    输入门:输入门决定了在当前时间步骤中,新的输入信息中哪些部分将会更新细胞状态。输入门通常由一个Sigmoid激活函数和一个tanh激活函数组成,用于产生0到1之间的权重和-1到1之间的新候选值。

    遗忘门:遗忘门决定了哪些信息应该从细胞状态中丢弃。它使用Sigmoid激活函数来产生0到1之间的权重,控制细胞状态中哪些信息应该保留。

    输出门:输出门决定了基于当前细胞状态和输入信息,LSTM单元应该输出什么。它使用Sigmoid激活函数来确定输出的哪些部分应该激活,并使用tanh激活函数来生成可能的输出值。
    在这里插入图片描述

    # 定义网络结构
    class LSTM(nn.Module):
        def __init__(self, vocab_size, hidden_dim, num_layers, embedding_dim, output_dim):
            super(LSTM, self).__init__()
            self.hidden_dim = hidden_dim  # 隐层大小
            self.num_layers = num_layers  # LSTM层数
            # 嵌入层,会对所有词形成一个连续型嵌入向量,该向量的维度为embedding_dim
            # 然后利用这个向量来表示该字,而不是用索引继续表示
            self.embeddings = nn.Embedding(vocab_size + 1, embedding_dim)
            # 定义LSTM层,第一个参数为每个时间步的特征大小,这里就是每个字的维度
            # 第二个参数为隐层大小
            # 第三个参数为lstm的层数
            self.lstm = nn.LSTM(embedding_dim, hidden_dim, num_layers)
            # 利用全连接层将其映射为2维,即正反面的概率
            self.fc = nn.Linear(hidden_dim, output_dim)
    
        def forward(self, x):
            # 1.首先形成嵌入向量
            embeds = self.embeddings(x)
            # 2.将嵌入向量导入到lstm层
            output, (h_n, c_n) = self.lstm(embeds)
            timestep, batch_size, hidden_dim = output.shape
            output = output.reshape(-1, hidden_dim)
            # 3.将其导入全连接层
            output = self.fc(output)  # 形状为batch_size * timestep, 2
            output = output.reshape(timestep, batch_size, -1)
            return output[-1]  # 返回最后一个时间片的输出,维度为 batch_size, 2
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    数据前处理

    # 1.获取训练数据
    x, y, output_dictionary, vocab_size, label_size, inverse_word_dictionary,word_dictionary,vocabulary = load_data(file_path, input_shape)
    
    # 2.划分训练、测试数据
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.1, random_state=42)
    
    # 3.将numpy转成tensor
    x_train = torch.from_numpy(x_train).to(torch.int32)
    y_train = torch.from_numpy(y_train).to(torch.float32)
    x_test = torch.from_numpy(x_test).to(torch.int32)
    y_test = torch.from_numpy(y_test).to(torch.float32)
    
    # 4.形成训练数据集
    train_data = TensorDataset(x_train, y_train)
    test_data = TensorDataset(x_test, y_test)
    
    # 5.将数据加载成迭代器
    train_loader = torch.utils.data.DataLoader(train_data,
                                               batch_size,
                                               True)
    
    test_loader = torch.utils.data.DataLoader(test_data,
                                              batch_size,
                                              False)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    模型训练

    # 6.模型训练
    model = LSTM(vocab_size=len(inverse_word_dictionary), hidden_dim=hidden_dim, num_layers=num_layers,
                 embedding_dim=embedding_dim, output_dim=output_dim)
    
    Configimizer = optim.Adam(model.parameters(), lr=lr) # 优化器
    criterion = nn.CrossEntropyLoss() # 多分类损失函数
    
    model.to(device)
    loss_meter = meter.AverageValueMeter()
    
    best_acc = 0 # 保存最好准确率
    best_model = None # 保存对应最好准确率的模型参数
    
    for epoch in range(epochs):
        model.train() # 开启训练模式
        epoch_acc = 0 # 每个epoch的准确率
        epoch_acc_count = 0 # 每个epoch训练的样本数
        train_count = 0 # 用于计算总的样本数,方便求准确率
        loss_meter.reset()
    
        train_bar = tqdm(train_loader)  # 形成进度条
        for data in train_bar:
            x_train, y_train = data  # 解包迭代器中的X和Y
    
            x_input = x_train.long().transpose(1, 0).contiguous()
            x_input = x_input.to(device)
            Configimizer.zero_grad()
    
            # 形成预测结果
            output_ = model(x_input)
    
            # 计算损失
            loss = criterion(output_, y_train.long().view(-1))
            loss.backward()
            Configimizer.step()
    
            loss_meter.add(loss.item())
    
            # 计算每个epoch正确的个数
            epoch_acc_count += (output_.argmax(axis=1) == y_train.view(-1)).sum()
            train_count += len(x_train)
    
        # 每个epoch对应的准确率
        epoch_acc = epoch_acc_count / train_count
    
        # 打印信息
        print("【EPOCH: 】%s" % str(epoch + 1))
        print("训练损失为%s" % (str(loss_meter.mean)))
        print("训练精度为%s" % (str(epoch_acc.item() * 100)[:5]) + '%')
    
        # 保存模型及相关信息
        if epoch_acc > best_acc:
            best_acc = epoch_acc
            best_model = model.state_dict()
    
        # 在训练结束保存最优的模型参数
        if epoch == epochs - 1:
            # 保存模型
            torch.save(best_model, './best_model.pkl')       
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    验证

    
     # 将输入句子转换为索引
    sent = "商品很满意,我还会再光临的"
    indexed_sent = [word_dictionary[char] for char in sent if char in word_dictionary]
    input_data = torch.tensor(indexed_sent)
    input_data = input_data.view(len(input_data), 1)
    model = LSTM(vocab_size=len(inverse_word_dictionary), hidden_dim=hidden_dim, num_layers=num_layers,
                 embedding_dim=embedding_dim, output_dim=output_dim)
    
    with torch.no_grad():
      predictions = model(input_data)
      predicted_class = torch.argmax(predictions, dim=1).item()
    
    	if predicted_class == 0:
    	  print("负面")
    	else:
    	  print("正面")
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    弄成函数好调用

    # 预测函数,用于对输入的文本进行情感分类预测
    def predictions(sent):
        # 将输入文本的字符转换为对应的索引,忽略不在词汇表中的字符
        indexed_sent = [word_dictionary[char] for char in sent if char in word_dictionary]
        
        # 将索引化后的输入数据转换为PyTorch张量
        input_data = torch.tensor(indexed_sent)
        
        # 调整张量的形状,将其变成列向量
        input_data = input_data.view(len(input_data), 1)
        
        # 创建LSTM模型,使用预定义的参数
        model = LSTM(vocab_size=len(inverse_word_dictionary), hidden_dim=hidden_dim, num_layers=num_layers,
                     embedding_dim=embedding_dim, output_dim=output_dim)
        
        # 将模型设置为评估模式,不进行梯度计算
        model.eval()
        
        # 使用不计算梯度的上下文进行模型的前向传播,获取预测结果
        with torch.no_grad():
            predictions = model(input_data)
        
        # 根据预测结果选择具体的类别
        predicted_class = torch.argmax(predictions, dim=1).item()
        
        # 打印预测结果,0代表负面,1代表正面
        if predicted_class == 0:
            print("负面")
        else:
            print("正面")
        
        # 返回预测的类别,并打印出来
        return print("预测值", predicted_class)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    测试

     sent = "电视刚安装好,说实话,画质不怎么样,很差!"
     predictions(sent)
    
    • 1
    • 2

    结果

    负面
    预测值 0
    
    • 1
    • 2
  • 相关阅读:
    统计信号处理基础 习题解答6-10
    Elasticsearch 8.X 路径检索的企业级玩法
    React中如何在事件处理的时候传参(详解)
    文件的读取和写入
    P2392 kkksc03考前临时抱佛脚——dfs+剪枝
    Java学习笔记——final关键字
    基本微信小程序的电影票务系统-电影票预订系统
    PSO算法(优化与探索四*DDPG与GAN)
    进程的状态与转换以及组织方式
    Vue源码阅读笔记—— 数组是如何做到响应式的
  • 原文地址:https://blog.csdn.net/weixin_55982578/article/details/132570336