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()
相关推荐
知来者逆20 分钟前
计算机视觉——为什么 mAP 是目标检测的黄金标准
图像处理·人工智能·深度学习·目标检测·计算机视觉
MobiCetus28 分钟前
Deep Reinforcement Learning for Robotics翻译解读2
人工智能·深度学习·神经网络·机器学习·生成对抗网络·计算机视觉·数据挖掘
师范大学生1 小时前
基于LSTM的文本分类2——文本数据处理
人工智能·rnn·lstm
搬砖的阿wei1 小时前
跳跃连接(Skip Connection)与残差连接(Residual Connection)
深度学习·residual·skip connection
Listennnn1 小时前
自动化网络架构搜索(Neural Architecture Search,NAS)
人工智能·深度学习·自动化
zhz52141 小时前
Zapier MCP:重塑跨应用自动化协作的技术实践
运维·人工智能·ai·自动化·ai编程·ai agent·智能体
怎么全是重名1 小时前
OFP--2018
人工智能·神经网络·目标检测
欲掩2 小时前
神经网络与深度学习:案例与实践——第三章(3)
人工智能·深度学习·神经网络
新知图书2 小时前
OpenCV销毁窗口
人工智能·opencv·计算机视觉
Blossom.1182 小时前
大数据时代的隐私保护:区块链技术的创新应用
人工智能·深度学习·自动化·区块链·智能合约