本文参考

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
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```

做数据的导入

数据+代码

给必要的参数命名

# 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进行填充

加载文本数据


# 加载文本数据
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

读取数据返回参数

创建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激活函数来生成可能的输出值。
Pytorch实现基于LSTM的情感分析-LMLPHP

# 定义网络结构
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.获取训练数据
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)

模型训练

# 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')       

验证


 # 将输入句子转换为索引
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("正面")

弄成函数好调用

# 预测函数,用于对输入的文本进行情感分类预测
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)

测试

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

结果

负面
预测值 0
09-11 02:30