循环神经网络--LSTM模型

一、简单概述

LSTM(Long Short-Term Memory):长短期记忆网络。

1.1LSTM出现的原因

在LSTM之间也有一个用于循环神经网络的模型---RNN模型。

但是RNN模型存在明显的局限性,它只能处理短序列的文本内容,在处理长序列时容易出现梯度消失或梯度爆炸的问题,因此难以捕捉长期依赖关系。

举个栗子:对于一个长序列'他小时候养过一只猫,现在**_____**很喜欢小动物 ',此时RNN模型可能忘记'他'和'猫'的关联。模型无法判断横线上到底是哪个答案。RNN就相当于只是机械的记住短序列,但是它没有上下关联的能力。

1.2LSTM的优胜点

LSTM在RNN的基础上进行优化,此时LSTM就能处理长期依赖问题,因此成为处理序列数据的核心模型之一。

LSTM主要是通过三门一状态来解决长期依赖问题的:三门就是下面要描述的三个门控机制,一状态指的是细胞状态,细胞状态主要负责'长期记忆'的存储:

一条水平线贯穿于图像的上方,这些线上只有少量的线性操作(从而避免了剧烈的梯度变化),信息在上面很容易保持。

二、门控机制

每个门控单元由一个sigmoid激活函数和一个点积操作组成:

  • sigmoid 输出范围为 (0,1),表示 "信息通过的比例"(0 = 完全阻止,1 = 完全通过);

  • 点积操作则将 sigmoid 的输出与对应信息相乘,实现对信息的筛选。

2.1遗忘门

遗忘门决定的是保留什么信息以及遗忘什么信息

Ft:这是在时间步 ( t ) 的遗忘门的输出,它是一个向量,其中的每个元素都在0和1之间,对应于细胞状态中每个元素应该被保留的比例。若结果为0,则遗忘该信息,若为1,则保留该信息。

2.2输入门

输入门决定记住什么信息

2.3状态更新

2.4输出门

输出门决定输出什么信息

2.5小结

LSTM是一种特殊的循环神经网络,专为解决传统RNN的梯度消失或爆炸问题设计。

它通过引入细胞状态和三个门控机制(输入门、遗忘门、输出门)来有效捕捉序列数据中的长期依赖关系。输入门控制新信息的进入,遗忘门决定保留或丢弃历史信息,输出门则筛选当前需输出的内容。

LSTM在自然语言处理、时间序列预测等领域应用广泛,能处理变长序列,较好保留上下文信息,是处理序列数据的重要模型之一。

三、API

API:nn.LSTM (input_size, hidden_size, num_layers=1)。

input_size 为输入特征维度

hidden_size 是隐藏层大小

num_layers 指定 LSTM 层数

四、序列池化

序列池化(sequence pooling)是一种将变长序列转换为固定长度表示的方法。

在 LSTM 模型中,最大池化和平均池化通常用于处理序列输出,提取关键信息。

两者都能将变长序列转换为固定长度向量,便于后续处理,选择哪种取决于任务是否需强调极端值或整体趋势。

4.1最大池化

最大池化从序列的多个时间步输出中选取最大值,突出序列中最显著的特征或关键时刻的信息。

API:nn.AdaptiveMaxPool1d(1)

注意:要调整维度信息

调整形状以匹配池化层的输入要求

x = x.permute(0, 2, 1)

从 [batch_size, seq_len, feature_size] 变为 [batch_size, feature_size, seq_len]

4.2平均池化

平均池化则计算序列所有时间步输出的平均值,反映序列整体的平均特征,平滑局部波动。

API:nn.AdaptiveAvgPool1d(1)

调整形状以匹配池化层的输入要求

x = x.permute(0, 2, 1)

从 [batch_size, seq_len, feature_size] 变为 [batch_size, feature_size, seq_len]

五、梯度消失

5.1梯度消失

在以下这些方面中,LSTM可能仍会出现梯度消失的问题:

(1)长期依赖问题:若果序列很长很长,即使是LSTM仍然有可能会记不得有效信息;

(2)不适当的权重初始化;

(3)激活函数的寻找不同,梯度缩小程度也不同。

5.2梯度爆炸

(1)过长的序列长度;

(2)不适当的学习率;

(3)不适当的权重初始化。

六、中文情绪分析案例

6.1简单介绍

所用的数据集是关于酒店的中文评价,数据量为5265条,其中2822条好评,其余的均为差评。

我们目的就是利用LSTM和全连接实现自定义网络结构对数据集进行训练,然后实现中文评论的情感分析,分析评论是正面的还是负面的。

6.2实现步骤

6.2.1数据预处理

(1)对原本格式为csv的数据集进行处理,获取文字:索引的字典;

注意:这里的字典要除去标点符号,并且里面的关键字不能重复。

(2)解析当前字典;

(3)获得索引文本,这里的索引是csv里的文字信息的索引;

注意:文字索引与标签索引的分隔符号为英文逗号,所以可以用split拆分。

(4)对索引文本进行拆分处理,最后返回data和label;

(5)读取字典,获取字典大小即键值对的数量--文本的数量。

6.2.2数据加载

(1)获取输入数据inputs和标签labels;

(2)划分数据集,训练和测试的比例为8:2;

(3)将训练数据和训练标签先转换为数组,再转换为张量;

(4)将数据放入数据集中;

(5)创建数据加载器方法,最后返回加载器。

6.2.3模型训练

(1)训练之前先定义网络结构;

(2)LSTM的网络结构:embedding、LSTM、tanh、Linear

注意:前向传播的时候要用0填充初始化隐藏层和细胞层;

LSTM的模型变量承接是两个变量,如:x,_ = self.LSTM(x,(h0,c0))

(3)开始训练;

(4)保存模型。

6.2.5模型预测

(1)加载模型;

(2)加载数据加载器;

(3)梯度清理;

(4)模型预测。

6.3实现代码

数据处理:

python 复制代码
import os

#原数据位置
csv_path = os.path.relpath(os.path.join(os.path.dirname(__file__), './dataset', 'hotel_discuss2.csv'))
#处理之后的字典索引位置
word_index_path = os.path.relpath(os.path.join(os.path.dirname(__file__), './dataset', 'word_index.txt'))
#处理之后的索引位置
index_path = os.path.relpath(os.path.join(os.path.dirname(__file__), './dataset', 'index.txt'))
#文本数据中不要的标点符号,这里的符号要用中文符号
punk = ',。!?'

def test01():
    index = 1
    #关键字:索引 ------字典
    word_index = {}
    with open(csv_path, 'r', encoding='utf-8-sig') as f:
        with open(word_index_path, 'w', encoding='utf-8-sig') as f1:
            lines = f.readlines()
            for line in lines:
                line = line.replace('\n', '')
                for word in line:
                    if word not in punk:
                        if word not in word_index:
                            word_index[word] = index
                            index += 1
                        else:
                            continue
            f1.write(str(word_index))



def test02():
    word_index = test03()
    with open(csv_path, 'r', encoding='utf-8-sig') as f:
        with open(index_path, 'w', encoding='utf-8-sig') as f1:
            lines = f.readlines()
            for line in lines:
                line = line.replace('\n', '')
                line_indexs = []
                ls = line.split(",")
                for word in ls[1]:
                    if word not in punk:
                        line_indexs.append(word_index[word])
                f1.write(str(line_indexs) + '\t')
                f1.write(str(ls[0]) + '\n')



def test03():
    with open(word_index_path, 'r', encoding='utf-8-sig') as f:
        #eval()将读取的字符串作为 Python 代码执行,通常用于解析字典格式的字符串(如{'apple': 1, 'banana': 2})
        word_index = eval(f.read())
        return word_index



def test04():
    #对索引文本进行拆分处理,最后返回data和label;
    with open(index_path, 'r', encoding='utf-8-sig') as f:
        line_length_Threshold =200 #句子长度阈值
        lines = f.readlines()
        labels = []
        inputs = []
        for line in lines:
            ls = line.split("\t")
            input = []
            #这里的ls[0]是文字列表,ls[1]是标签
            if len(eval(ls[0])) > line_length_Threshold:
                #如果句子长度大于阈值,截断句子
                input = eval(ls[0])[:line_length_Threshold]
            if len(eval(ls[0])) <= line_length_Threshold:
                #如果句子长度小于等于阈值,差的补0
                input = eval(ls[0])+[0]*(line_length_Threshold-len(eval(ls[0])))
            inputs.append(input)
            labels.append(int(ls[1].replace('\n', '')))
        return inputs, labels

def test05():
    #读取并解析指定文件中的word_index字典,然后返回该字典中包含的键值对数量(即词汇表的大小)。
    with open(word_index_path, 'r', encoding='utf-8-sig') as f:
        word_index = eval(f.read())
        print(len(word_index))
        return len(word_index)


if __name__ == '__main__':
    # test01()
    # test02()
    # test04()
    test05()

数据加载器:

python 复制代码
from torch.utils.data import TensorDataset, DataLoader
from sklearn.model_selection import train_test_split
from DataProcess import test04
import numpy as np
import torch
#获取数据集
inputs,labels = test04()
#划分数据集
train_data,test_data,train_labels,test_labels = train_test_split(inputs,labels,
                                                                 test_size=0.2)
#将数据集转化为tensor
train_data = np.array(train_data,dtype = np.int64)
train_labels = np.array(train_labels,dtype = np.int64)
train_data = torch.from_numpy(train_data)
train_labels = torch.from_numpy(train_labels)

# test_data = np.array(test_data,dtype = np.int64)
# test_labels = np.array(test_labels,dtype = np.int64)
# test_data = torch.from_numpy(test_data)
# test_labels = torch.from_numpy(test_labels)
#定义数据集
train_dataset = TensorDataset(train_data,train_labels)

def get_loader():
    dataloader = DataLoader(dataset = train_dataset, batch_size = 32, shuffle = True)
    return dataloader

if __name__ == '__main__':
    dataloader = get_loader()
    for i,(data,label) in enumerate(dataloader):
        print(data.shape)  #(32, 100):32个样本,每个样本200个字符
        print(label.shape) #(32,):32个标签

模型训练和预测:

python 复制代码
import os
import torch
import torch.nn as nn
import torch.optim as optim
from dataloader import get_loader
from DataProcess import test05
#定义一个网络结构
class SimpleLSTM(nn.Module):
    def __init__(self,word_size,input_size=128,hidden_size=128,
                 output_size=2,num_layers=1,batch_first=True,device='cpu'):
        super(SimpleLSTM,self).__init__()
        self.word_size = word_size
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.num_layers = num_layers
        self.batch_first = batch_first
        self.device = device

        self.embed = nn.Embedding(word_size,input_size)
        self.lstm = nn.LSTM(input_size=input_size,hidden_size=hidden_size,
                            num_layers=num_layers,batch_first=batch_first)
        self.tanh = nn.Tanh()
        self.fc = nn.Linear(hidden_size,output_size)

    def forward(self,x):
        x =  self.embed(x)
        #初始化
        h0 = (torch.zeros(self.num_layers,x.shape[0],self.hidden_size))
        c0 = (torch.zeros(self.num_layers,x.shape[0],self.hidden_size))
        h0 = h0.to(self.device)
        c0 = c0.to(self.device)
        x,_ = self.lstm(x,(h0,c0))
        x = self.tanh(x)
        x = x[:,-1,:]
        out = self.fc(x)
        return out


def train():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

    # 获取词汇表大小并验证
    word_size = test05()
    print(f"词汇表大小: {word_size}")

    # 初始化模型
    model = SimpleLSTM(word_size=word_size)
    model.to(device)

    # 获取数据加载器
    loader = get_loader()

    # 验证数据索引范围
    for x, y in loader:
        max_idx = x.max().item()
        if max_idx >= word_size:
            print(f"警告: 发现超出范围的索引 {max_idx} (词汇表大小 {word_size})")
            # 可选:裁剪索引到有效范围
            x = torch.clamp(x, max=word_size - 1)

    # 训练设置
    optimizer = optim.Adam(model.parameters(), lr=0.001)  # 降低学习率
    criterion = nn.CrossEntropyLoss()
    epochs = 10  # 增加训练轮数

    for epoch in range(epochs):
        model.train()
        total_loss = 0
        correct = 0
        total = 0

        for i, (x, y) in enumerate(loader):
            x, y = x.to(device), y.to(device)

            # 确保索引在有效范围内
            x = torch.clamp(x, max=word_size - 1)

            out = model(x)
            loss = criterion(out, y)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            total_loss += loss.item()
            _, predicted = torch.max(out, 1)
            correct += (predicted == y).sum().item()
            total += y.size(0)

            if i % 10 == 0:
                print(f'Epoch [{epoch + 1}/{epochs}], Step [{i}/{len(loader)}], Loss: {loss.item():.4f}')

        print(f'Epoch [{epoch + 1}/{epochs}], Loss: {total_loss / len(loader):.4f}, Accuracy: {correct / total:.4f}')

    # 保存整个模型
    model_path = os.path.join(os.path.dirname(__file__), 'model', 'full_model.pth')
    torch.save(model, model_path)
    print('模型保存成功!')


def test():
    # 设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    # 数据加载器
    loader = get_loader()
    # 语料库的长度
    word_size = test05()
    # 模型
    model = SimpleLSTM(word_size=word_size, device=device)
    model.to(device)
    # 训练轮数
    epochs = 10
    # 损失函数
    loss_fn = nn.CrossEntropyLoss(reduction='sum')
    # 测试
    acc_total = 0
    loss_total = 0
    model.eval()
    with torch.no_grad():
        for i, (x, y) in enumerate(loader):
            x, y = x.to(device), y.to(device)
            # pre(32,2)
            pre = model(x)
            acc_total += torch.sum(torch.argmax(pre, dim=1) == y)
            loss = loss_fn(pre, y)
            loss_total += loss.item()

        print(f'loss:{loss_total / len(loader.dataset)}----->acc:{acc_total / len(loader.dataset)}')

if __name__ == '__main__':

    # train()
    test()
相关推荐
mrsk5 分钟前
用魔塔来体验一把NLP(机械学习)
前端·机器学习·面试
王上上26 分钟前
【论文阅读51】-CNN-LSTM-安全系数和失效概率预测
论文阅读·cnn·lstm
Eloudy26 分钟前
Schmidt 分解 ⚙️ 与 SVD 之间的本质联系
人工智能·机器学习·量子计算
盼小辉丶2 小时前
图机器学习(22)——图机器学习技术应用
人工智能·机器学习·图机器学习
2301_764441332 小时前
储粮温度预测新方案!FEBL模型用代码实现:LSTM+注意力+岭回归的完整流程
python·深度学习·机器学习
之之为知知2 小时前
Chromadb 1.0.15 索引全解析:从原理到实战的向量检索优化指南
人工智能·深度学习·机器学习·大模型·索引·向量数据库·chromadb
叫我:松哥2 小时前
优秀案例:基于python django的智能家居销售数据采集和分析系统设计与实现,使用混合推荐算法和LSTM算法情感分析
爬虫·python·算法·django·lstm·智能家居·推荐算法
王小王-1233 小时前
基于Transform、ARIMA、LSTM、Prophet的药品销量预测分析
lstm·arima·transform·prophet·药品销量预测·时序建模预测
慕婉03073 小时前
循环神经网络(RNN)详解:从原理到实践
人工智能·rnn·深度学习
胖达不服输4 小时前
「日拱一码」038 机器学习-数据量大小的影响
人工智能·机器学习·数据量大小的影响