Day15上 - RNN的使用,评论分析,情感识别

RNN的使用

1. RNN 要解决什么问题?

  • 时序信号的抽取特征
  • 顺序

2. RNN 的解决方法

  • 循环
  • 逐个处理
  • 前后依赖
    • 后一项直接依赖前一项,间接依赖前面所有的项
    • 前后进行【手递手】传递
  • 中间隐藏状态

3. RNN API 如何调用?

  • nn.RNN
    • 自动挡(自动循环)
    • 用于编码器
  • nn.RNNCell
    • 手动挡(手动循环)
    • 用于解码器
python 复制代码
import torch
from torch import nn

"""
1. Simple RNN
"""

"""
    时序类数据结构:[seq_len, batch_size, embedding_dim]
    seq_len:序列长度
    batch_size:批量数据有多少个
    embedding_dim:嵌入维度,也就是每个词是多大的向量
"""
# 比如一个短信70个字,3个短信,每个字是256个维度
X = torch.randn(70, 3, 256)
h0 = torch.zeros(1, 3, 512, dtype=torch.float32)
X.shape, h0.shape

# 构建一个循环神经网络,输入层维度256,隐藏层维度512
rnn = nn.RNN(input_size=256, hidden_size=512)

# 调用RNN
out, hn = rnn(X, h0)

# [seq_len, batch_size, hidden_size]
out.shape

# [1, batch_size, hidden_size]
hn.shape

4. LSTM 博客

http://colah.github.io/posts/2015-08-Understanding-LSTMs/

python 复制代码
"""
2、 LSTM 长短期记忆网络
    Long 长
    Short 短
    Term 期
    Memory 记忆
"""

"""
    导入必要的模块
    导入 PyTorch 库及其神经网络模块 nn
"""
import torch
from torch import nn

"""
(1)定义 LSTM 层
"""
# Inputs: input, (h_0, c_0)
# Outputs: output, (h_n, c_n)
# 创建一个 LSTM 层,输入维度为 256,隐藏层维度为 512
lstm = nn.LSTM(input_size=256, hidden_size=512)


"""
(2)准备输入数据和初始状态
"""
# 准备输入数据(70个时间步,每个时间步有3个样本,每个样本的特征维度为256)
X = torch.randn(70, 3, 256)
# 初始化隐藏状态h0和细胞状态c0,1个LSTM层,3个样本,每个样本的隐藏状态维度为512
h0 = torch.zeros(1, 3, 512, dtype=torch.float32)
c0 = torch.zeros(1, 3, 512, dtype=torch.float32)

"""
(3)前向传播
"""
#out是LSTM的输出,hn是最后一个时间步的隐藏状态,cn是最后一个时间步的细胞状态
out, (hn, cn) = lstm(X, (h0, c0))

"""
(4)检查输出的形状
"""
# [70, 3, 512]
out.shape

# [1, 3, 512]
hn.shape

# [1, 3, 512]
cn.shape

"""
(1)定义 LSTMCell 层
"""
# Inputs: input, (h_0, c_0)
# Outputs: (h_1, c_1)
#创建一个 LSTMCell 层,输入维度为 256,隐藏层维度为 512
lstm_cell = nn.LSTMCell(input_size=256, hidden_size=512)

"""
(2)准备输入数据和初始状态
"""
# 准备输入数据
X = torch.randn(70, 3, 256)
# 初始化隐藏状态 h0 和细胞状态 c0
h0 = torch.zeros(3, 512, dtype=torch.float32)
c0 = torch.zeros(3, 512, dtype=torch.float32)

"""
(3)处理第一个时间步的数据
"""
X0 = X[0, :, :]
X0.shape

"""
(4)前向传播
"""
hn, cn = lstm_cell(X0, (h0, c0))

"""
(5)检查输出的形状
"""
hn.shape
cn.shape
X.size(0)

"""
(6)循环处理所有时间步的数据
"""
out = []
for x in X:
    h0, c0 = lstm_cell(x, (h0, c0))
    out.append(h0)

"""
(7)拼接所有时间步的输出
"""
# 最终所有步的短期状态
out = torch.stack(tensors=out, dim=0)

"""
(8)获取最后一步的状态
"""
hn = h0
cn = c0

"""
总结:
    LSTM 层:处理整个序列数据,返回所有时间步的输出和最后一个时间步的隐藏状态及细胞状态。
    LSTMCell 层:逐个时间步处理数据,返回每个时间步的隐藏状态和细胞状态。
"""

5. 信息

  • CRUD工程师
      1. 增删改查
  • LSTM
      1. 门控思想
      1. CRUD思想
      • (1)遗忘门:先丢掉一部分不重要信息
      • (2)输入门:增加重要的信息
  • RGU
    • 核心思想:吃LSTM的红利,化简LSTM
    • 调用层面:跟Simple RNN是一样的

所有的网络都是在处理特征,这是人工智能的核心问题,不论是全链接、卷积、循环和transformer,它们都是在处理特征,所以我们只关注特征的维度。

情感识别 sentiment analysis

1. 业务本质:
  • 从一段文本中,判断说话人的情感色彩
  • 正面:positive
  • 负面:negative
  • 中立:neutral
2. 技术本质:
  • 文本分类
3. 流程:

(1)了解数据

(2)构建词典

分词

jieba

(3)搭建模型

① 向量化

Embedding

② RNN 抽特征

③ Linear 分类

(4)训练模型

(5)评估模型

(6)预测结果

python 复制代码
"""
    需求:酒店评论分析
"""

"""
1. 样本路径和类别读取
"""
"""
    训练数据聚合
"""
import os
train_root = os.path.join("hotel", "train")
train_texts = []
train_labels = []
for label in os.listdir(train_root):
    label_root = os.path.join(train_root, label)
    for file in os.listdir(label_root):
        file_path = os.path.join(label_root, file)
        # 聚合结果
        train_texts.append(file_path)
        train_labels.append(label)
# 打印数据
len(train_texts), len(train_labels)


"""
    测试数据聚合
"""
test_root = os.path.join("hotel", "test")
test_texts = []
test_labels = []
for label in os.listdir(test_root):
    label_root = os.path.join(test_root, label)
    for file in os.listdir(label_root):
        file_path = os.path.join(label_root, file)
        # 聚合结果
        test_texts.append(file_path)
        test_labels.append(label)
# 打印数据
len(test_texts), len(test_labels)
python 复制代码
"""
2. 构建分词器
    分词,把句子变 token
    把所有不同的token聚在一起
    做 0 ~ N-1 的编码
"""
SEQ_LEN = 30

import jieba
# pip install opencc -U
import opencc

class Tokenizer(object):
    """
        定义一个分词器
    """
    def __init__(self, X, y):
        """
            训练的语料
        """
        self.X = X
        self.y = y
        self.t2s = opencc.OpenCC(config="t2s")
        self._build_dict()

    def _build_dict(self):
        """
            构建字典
        """
        # 1. 获取所有的 token
        words = {"<PAD>", "<UNK>"}
        for file in self.X:
            # 1. 打开文件
            with open(file=file, mode="r", encoding="gbk", errors="ignore") as f:
                text = f.read().replace("\n", "")
                text = self.t2s.convert(text=text)
                words.update(set(jieba.lcut(text)))
        # 2. 构建文本字典
        self.word2idx = {word: idx for idx, word in enumerate(words)}
        self.idx2word = {idx: word for word, idx in self.word2idx.items()}
        # 3. 删掉 数据集
        del self.X
        # 4. 构建标签字典
        labels = set(train_labels)
        self.label2idx = {label: idx for idx, label in enumerate(labels)}
        self.idx2label = {idx: label for label, idx in self.label2idx.items()}
        # 5. 删除 数据集
        del self.y
        

    def encode(self, text, seq_len=SEQ_LEN):
        """
            text --> tokens --> ids

            自我扩展:
                - 右侧截断或填充
                - 左边?
                - 随机?
        """
        # 1. 繁体转简体
        text = text.replace("\n", "")
        text = self.t2s.convert(text=text)
        # 2. 分词
        text = jieba.lcut(text)
        # 3. 统一长度
        text = (text + ["<PAD>"] * seq_len)[:seq_len]
        # 4. 转 id
        ids = [self.word2idx.get(word, self.word2idx.get("<UNK>")) for word in text]
        
        return ids
        
    def decode(self, ids):
        """
            ids --> tokens --> text
        """
        text = "".join([self.idx2word.get(_id, "") for _id in ids])
        return text

    def __str__(self):
        """
            输: 分词器基本信息
        """
        return f"""
        Tokenizer Info: 
            --> Num of Tokens: {len(self.word2idx)}
            --> Num of Labels: {len(self.label2idx)}
        """
    def __repr__(self):
        return self.__str__()
python 复制代码
"""
3. 打包数据
"""
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
import torch

class HotelCommentDataset(Dataset):
    """
        自定义数据集
    """
    def __init__(self, X, y, seq_len=SEQ_LEN):
        """
            初始化
        """
        self.X = X
        self.y = y
        self.seq_len = seq_len

    def __getitem__(self, idx):
        """
            索引操作
                返回第idx个样本
        """
        # 1. 文本
        file = self.X[idx]
        with open(file=file, mode="r", encoding="gbk", errors="ignore") as f:
            text = f.read()
            ids = tokenizer.encode(text=text, seq_len=self.seq_len)
            ids = torch.tensor(data=ids, dtype=torch.long)
                
        # 2. 标签
        label = self.y[idx]
        label = tokenizer.label2idx.get(label)
        label = torch.tensor(data=label, dtype=torch.long)
        
        return ids, label

    def __len__(self):
        return len(self.X)

# 1. 定义一个分词器
tokenizer = Tokenizer(X=train_texts, y=train_labels)
tokenizer

# 打包数据
train_dataset = HotelCommentDataset(X=train_texts, y=train_labels)
train_dataloader = DataLoader(dataset=train_dataset, shuffle=True, batch_size=128)
test_dataset = HotelCommentDataset(X=test_texts, y=test_labels)
test_dataloader = DataLoader(dataset=test_dataset, shuffle=False, batch_size=256)

for X, y in test_dataloader:
    print(X.shape)
    print(y.shape)
    break
问题
  1. 输入:65个词
  • 时序信号
  1. 输出:2个分类

  2. 模型:

  • 能否用卷积做?
    • 能用什么算法模型来做,取决于数据类型。
  • 使用卷积如何搭建?
    • 一维:[N, C, L]
    • 二维:[N, C, H, W]
    • 三维:[N, C, H, W, L]
python 复制代码
"""
4. 搭建模型
"""
import torch
from torch import nn

"""
    每句话65个词,分为2类
    
"""
class TextCNN(nn.Module):
    """
        搭建模型
            - 卷积
            # [N, C, L]
            nn.Conv1d()
    """
    def __init__(self, dict_len=len(tokenizer.word2idx), embedding_dim = 256):
        super().__init__()
        # 向量化
        self.embed = nn.Embedding(num_embeddings=dict_len, 
                                  embedding_dim=embedding_dim, 
                                  padding_idx=tokenizer.word2idx.get("<PAD>"))
        # 特征抽取
        self.feature_extractor = nn.Sequential(
            nn.Conv1d(in_channels=256, out_channels=512, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm1d(num_features=512),
            nn.ReLU(),
            nn.MaxPool1d(kernel_size=2, stride=2, padding=0),
            nn.Conv1d(in_channels=512, out_channels=1024, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm1d(num_features=1024),
            nn.ReLU(),
            nn.MaxPool1d(kernel_size=2, stride=2, padding=0)
        )
        # 分类
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(in_features=1024 * (SEQ_LEN // 2 // 2), out_features=128),
            nn.ReLU(),
            nn.Linear(in_features=128, out_features=2)
        )
        
        
    def forward(self, x):
        # 向量化
        x = self.embed(x)
        x = torch.permute(input=x, dims=(0, 2, 1))
        # 抽特征
        x = self.feature_extractor(x)
        # 做分类
        x = self.classifier(x)
        return x

class TextRNN(nn.Module):
    """
        搭建模型
            - 卷积
            # [N, C, L]
            nn.Conv1d()
    """
    def __init__(self, dict_len=len(tokenizer.word2idx), embedding_dim = 256):
        super().__init__()
        # 向量化
        self.embed = nn.Embedding(num_embeddings=dict_len, 
                                  embedding_dim=embedding_dim, 
                                  padding_idx=tokenizer.word2idx.get("<PAD>"))
        # 特征抽取
        self.feature_extractor = nn.RNN(input_size=256, hidden_size=512, num_layers=1, bidirectional=False)
        # 分类
        self.classifier = nn.Sequential(
            nn.Linear(in_features=512, out_features=128),
            nn.ReLU(),
            nn.Linear(in_features=128, out_features=2)
        )
        
    def forward(self, x):
        # 向量化
        x = self.embed(x)
        x = torch.permute(input=x, dims=(1, 0, 2))
        # 特征抽取
        out, hn = self.feature_extractor(x)
        # 分类输出
        x = self.classifier(out.sum(dim=0))
        return x

model= TextRNN()
for X, y in train_dataloader:
    y_pred = model(X)
    print(y_pred.shape)
    break
python 复制代码
"""
5. 训练模型
"""
# 检测设备
device = "cuda" if torch.cuda.is_available() else "cpu"
# 实例化模型
model = TextRNN().to(device=device)
# 损失函数
loss_fn = nn.CrossEntropyLoss()
# 优化器
optimizer = torch.optim.Adam(params=model.parameters(), lr=1e-3)
# 轮次
epochs = 20
python 复制代码
"""
    6. 评估
"""
import time

def get_acc(dataloader):
    model.eval()
    accs = []
    with torch.no_grad():
        for X, y in dataloader:
            # 0. 数据搬家
            X = X.to(device=device)
            y = y.to(device=device)
            # 1. 正向传播
            y_pred = model(X)
            # 2. 计算结果
            y_pred = y_pred.argmax(dim=1)
            # 3. 计算准确率
            acc = (y_pred == y).to(dtype=torch.float32).mean().item()
            # 4. 保存结果
            accs.append(acc)
    final_acc = round(number=sum(accs) / len(accs), ndigits=6)
    return final_acc

def train():
    train_acc = get_acc(dataloader=train_dataloader)
    test_acc = get_acc(dataloader=test_dataloader)
    print(f"初始 Train_acc: {train_acc}, Test_acc: {test_acc}")
    
    for epoch in range(epochs):
        model.train()
        start_time = time.time()
        for X, y in train_dataloader:
            # 0. 数据搬家
            X = X.to(device=device)
            y = y.to(device=device)
            # 1. 正向传播
            y_pred = model(X)
            # 2. 计算误差
            loss = loss_fn(y_pred, y)
            # 3. 反向传播
            loss.backward()
            # 4. 优化一步
            optimizer.step()
            # 5. 清空梯度
            optimizer.zero_grad()
        
        stop_time = time.time()
        # 每轮结束后测试一下
        train_acc = get_acc(dataloader=train_dataloader)
        test_acc = get_acc(dataloader=test_dataloader)
        
        print(f"Epoch: {epoch + 1}, Train_acc: {train_acc}, Test_acc: {test_acc}, Train_time: {stop_time-start_time}")

train()
相关推荐
机智的小神仙儿16 分钟前
Query Processing——搜索与推荐系统的核心基础
人工智能·推荐算法
AI_小站22 分钟前
RAG 示例:使用 langchain、Redis、llama.cpp 构建一个 kubernetes 知识库问答
人工智能·程序人生·langchain·kubernetes·llama·知识库·rag
Doker 多克24 分钟前
Spring AI 框架使用的核心概念
人工智能·spring·chatgpt
Guofu_Liao25 分钟前
Llama模型文件介绍
人工智能·llama
思通数科多模态大模型1 小时前
10大核心应用场景,解锁AI检测系统的智能安全之道
人工智能·深度学习·安全·目标检测·计算机视觉·自然语言处理·数据挖掘
数据岛1 小时前
数据集论文:面向深度学习的土地利用场景分类与变化检测
人工智能·深度学习
学不会lostfound1 小时前
三、计算机视觉_05MTCNN人脸检测
pytorch·深度学习·计算机视觉·mtcnn·p-net·r-net·o-net
红色的山茶花1 小时前
YOLOv8-ultralytics-8.2.103部分代码阅读笔记-block.py
笔记·深度学习·yolo
龙的爹23331 小时前
论文翻译 | RECITATION-AUGMENTED LANGUAGE MODELS
人工智能·语言模型·自然语言处理·prompt·gpu算力
白光白光1 小时前
凸函数与深度学习调参
人工智能·深度学习