循环神经网络--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()
相关推荐
Moshow郑锴6 小时前
机器学习的特征工程(特征构造、特征选择、特征转换和特征提取)详解
人工智能·机器学习
C++、Java和Python的菜鸟7 小时前
第六章 统计初步
算法·机器学习·概率论
Jina AI10 小时前
回归C++: 在GGUF上构建高效的向量模型
人工智能·算法·机器学习·数据挖掘·回归
试剂界的爱马仕14 小时前
胶质母细胞瘤对化疗的敏感性由磷脂酰肌醇3-激酶β选择性调控
人工智能·科技·算法·机器学习·ai写作
AI波克布林15 小时前
发文暴论!线性注意力is all you need!
人工智能·深度学习·神经网络·机器学习·注意力机制·线性注意力
张子夜 iiii15 小时前
机器学习算法系列专栏:主成分分析(PCA)降维算法(初学者)
人工智能·python·算法·机器学习
Blossom.11815 小时前
把 AI 推理塞进「 8 位 MCU 」——0.5 KB RAM 跑通关键词唤醒的魔幻之旅
人工智能·笔记·单片机·嵌入式硬件·深度学习·机器学习·搜索引擎
2502_9271612817 小时前
DAY 40 训练和测试的规范写法
人工智能·深度学习·机器学习
赵英英俊18 小时前
Python day46
python·深度学习·机器学习
Monkey PilotX18 小时前
机器人“ChatGPT 时刻”倒计时
人工智能·机器学习·计算机视觉·自动驾驶