利用人名语言分类案例演示RNN、LSTM和GRU的区别(基于PyTorch)

【NLP】第五章:长短期记忆网络LSTM RNN是一个时序意义上的深度网络,虽然从表达式上t时刻的loss与之前所有时刻的隐含态h都有关系,即按照时序展开,会发现已经有类似Residual Connection直接连接每个输入到当前的loss,但是与ResNet的巨大不同在于,这些连接反复使用的都是同一个W。但是对于RNN的输入来说,并不是必须等步长输入的,就是可以变步长输入,就是没有要求你所有的输入都是等步长,你有几步就循环几次好了,不是说我必须要求都要是比如10步,必须要循环10次,不是的,你有几步循环几次即可。,也是承载着记忆信息。 阅读详情

在自然语言处理领域,人名分类是一个有趣且实用的任务,例如可以根据人名推测其所属的语言或文化背景。本文将详细介绍如何使用 PyTorch 构建基于 RNN、LSTM 和 GRU 的人名分类模型,通过对代码的剖析,帮助大家理解这些循环神经网络在序列数据处理中的应用。

RNN家族介绍:网页链接

一、程序结构

1.1 程序整体结构

test.py
├── 导入依赖库
│   ├── json       - JSON序列化/反序列化
│   ├── torch      - PyTorch框架
│   ├── torch.nn   - 神经网络模块
│   ├── torch.optim- 优化器模块
│   ├── DataLoader/Dataset - 数据加载工具
│   ├── string     - 字符操作
│   ├── time       - 时间记录
│   ├── matplotlib.pyplot - 可视化绘图
│   └── tqdm       - 进度条显示
│
├── 数据预处理模块
│   ├── string_setting()          - 定义字符集与语言类别
│   ├── read_data(path)           - 读取并清洗训练数据
│   ├── word_to_tensor(x)         - 将姓名编码为one-hot张量
│   └── class MyDatasets(Dataset) - 自定义PyTorch数据集类
│       ├── __init__
│       ├── __len__
│       └── __getitem__
│
├── 模型定义模块
│   ├── RNN       - 简单循环神经网络
│   │   ├── __init__
│   │   ├── forward
│   │   └── init_hidden
│   │
│   ├── LSTM      - 长短期记忆网络
│   │   ├── __init__
│   │   ├── forward
│   │   └── init_hidden
│   │
│   └── GRU       - 门控循环单元
│       ├── __init__
│       ├── forward
│       └── init_hidden
│
├── 模型训练与测试模块
│   ├── test_def(*args)            - 测试模型前向传播
│   └── my_train_def(*args, n=100, m=1000)
│       ├── 训练流程控制
│       ├── 参数初始化
│       ├── 训练主循环
│       │   ├── 轮次迭代(epoch)
│       │   ├── 批次训练(batch)
│       │   ├── 前向传播、计算损失
│       │   ├── 反向传播、参数更新
│       │   └── 日志统计与保存模型
│       └── 保存训练日志到JSON文件
│
├── 结果可视化与对比模块
│   └── compare_results()
│       ├── 绘制 loss 曲线图(RNN / LSTM / GRU)
│       ├── 绘制 accuracy 曲线图
│       └── 绘制训练时间柱状图
│
├── 模型预测模块
│   └── predict_def(x, *args, t=3)
│       ├── 输入预处理(one-hot转换)
│       ├── 加载训练好的模型参数
│       └── 输出 top-k 预测结果
│
└── 主程序入口
    └── if __name__ == '__main__':
        ├── 超参数设置
        ├── 初始化字符集 & 语言类别
        ├── 构建模型字典 model_dict = {'RNN': RNN, 'LSTM': LSTM, 'GRU': GRU}
        ├── 开始训练三个模型
        ├── 绘图比较结果(loss.png / acc.png / time.png)
        └── 对输入姓名进行预测示例

1.2 各模块功能关系流程图

[数据准备] 
     ↓
[模型定义]
     ↓
[模型训练]
     ↓
[训练评估]
     ↓
[结果对比]
     ↓
[新样本预测]

二、数据预处理模块详解

2.1 定义字符集和语言类别

def string_setting():
    """
    定义模型所需的字符集和语言类别标签
    1. 构建包含字母和常用标点的字符集
    2. 定义目标语言类别列表(共18种语言)
    """
    # 构建字符集:包含26个大写字母、26个小写字母和常用标点符号
    al_letters = string.ascii_letters + " .,;'"
    n_letters = len(al_letters)  # 计算字符集大小(57个字符)
    print("字符数量为:", n_letters)

    # 定义目标语言类别列表(按顺序:意大利语、英语、阿拉伯语等18种语言)
    categorys = ['Italian', 'English', 'Arabic', 'Spanish', 'Scottish', 'Irish', 'Chinese',
                 'Vietnamese', 'Japanese', 'French', 'Greek', 'Dutch', 'Korean', 'Polish',
                 'Portuguese', 'Russian', 'Czech', 'German']
    categorys_len = len(categorys)  # 计算语言类别数量(18类)
    print("类别数量为:", categorys_len)
    return al_letters, categorys  # 返回字符集和语言类别列表

代码解析:

  • 字符集构建:string.ascii_letters包含所有大小写字母,加上常用标点符号(空格、逗号、句号、分号、单引号),共57个字符。这些字符基本覆盖了大多数语言人名中的特殊符号。
  • 语言类别:定义了18种目标语言,这些语言的人名在拼写和结构上有明显差异,适合作为分类任务的目标。

2.2 读取数据

def read_data(path):
    """
    从文本文件中读取人名分类数据
    参数:
        path (str): 数据文件路径,文件格式为每行"人名\t语言标签"
    返回:
        data_list_x (list): 人名列表(x数据集)
        data_list_y (list): 语言标签列表(y数据集)
    """
    data_list_x, data_list_y = [], []  # 初始化数据存储列表
    with open(path, encoding="utf-8") as f:  # 以UTF-8编码打开文件
        for line in f.readlines():  # 逐行读取数据
            # 数据清洗:过滤长度小于等于5的行(可能是无效数据)
            if len(line) <= 5:
                continue
            # 按制表符分割每行数据,前半部分为人名,后半部分为语言标签
            x, y = line.strip().split('\t')
            data_list_x.append(x)  # 保存人名到x列表
            data_list_y.append(y)  # 保存标签到y列表
    return data_list_x, data_list_y  # 返回清洗后的数据

代码解析:

  • 文件读取:使用with open语句确保文件资源正确关闭,指定encoding="utf-8"处理多语言字符。
  • 数据清洗:过滤长度小于等于5的行,避免无效数据(如空行或格式错误的行)影响模型训练。
  • 数据分割:假设文件格式为"人名\t语言标签",使用split('\t')分割每行数据。

2.3 人名转换为one-hot编码张量

def word_to_tensor(x):
    """
    将人名转换为one-hot编码张量
    参数:
        x (str): 输入人名(如"John")
    返回:
        tensor_x (torch.Tensor): 二维one-hot张量,形状为[len(x), len(al_letters)]
    示例:
        输入"John",输出形状为[4, 57]的张量,每个字符在对应位置置1
    """
    tensor_x = torch.zeros(len(x), len(al_letters))  # 初始化全零张量
    for i, letter in enumerate(x):  # 遍历人名中的每个字符
        # 在字符集中查找当前字符的索引,并在对应位置置1
        tensor_x[i][al_letters.find(letter)] = 1
    return tensor_x  # 返回one-hot编码张量

代码解析:

  • 张量初始化:创建一个形状为[len(x), len(al_letters)]的全零张量,其中len(x)是人名的字符数,len(al_letters)是字符集大小(57)。
  • 字符编码:遍历人名中的每个字符,使用al_letters.find(letter)查找字符在字符集中的索引,将对应位置的值设为1。
  • 为什么使用one-hot编码:人名是字符序列,每个字符是离散的类别,one-hot编码是表示离散类别最直接的方式,适合作为模型输入。

2.4 自定义数据集类

# 自定义数据集类,继承PyTorch的Dataset基类
class MyDatasets(Dataset):
    def __init__(self, data_list_x, data_list_y):
        """
        初始化数据集
        参数:
            data_list_x (list): 人名列表
            data_list_y (list): 语言标签列表
        """
        self.data_list_x = data_list_x  # 保存人名数据
        self.data_list_y = data_list_y  # 保存标签数据
        self.data_list_len = len(data_list_x)  # 数据集长度

    def __len__(self):
        """返回数据集大小"""
        return self.data_list_len

    def __getitem__(self, index):
        """
        获取指定索引的样本
        参数:
            index (int): 样本索引
        返回:
            tensor_x (torch.Tensor): one-hot编码的人名张量
            tensor_y (torch.Tensor): 语言标签的张量表示
        """
        # 处理索引越界:确保索引在有效范围内
        index = min(max(index, -self.data_list_len), self.data_list_len - 1)
        x = self.data_list_x[index]  # 获取人名
        y = self.data_list_y[index]  # 获取标签

        # 将人名转换为one-hot张量
        tensor_x = word_to_tensor(x)
        # 将标签转换为张量(使用类别索引,类型为long)
        tensor_y = torch.tensor(categorys.index(y), dtype=torch.long)
        return tensor_x, tensor_y  # 返回样本和标签

代码解析:

  • 继承Dataset类:PyTorch的Dataset是所有自定义数据集的基类,必须实现__len__和__getitem__方法。
  • 索引处理:__getitem__方法处理正负索引,确保索引在有效范围内。
  • 标签转换:将语言标签转换为对应的类别索引(如"English"对应索引1),使用torch.long类型(即int64),这是PyTorch分类任务中标签的标准类型。

2.5 数据加载器

def data_loader():
    """
    封装数据加载全流程
    返回:
        my_dataloader (DataLoader): 数据加载器对象
    """
    path = './name_classfication.txt'  # 数据文件路径
    my_list_x, my_list_y = read_data(path)  # 读取数据
    print(f'x数据集数量为:{len(my_list_x)}')  # 打印数据规模
    print(f'y数据集数量为:{len(my_list_y)}')

    # 实例化自定义数据集
    my_dataset = MyDatasets(my_list_x, my_list_y)
    # 实例化数据加载器:batch_size=1(单样本批量),shuffle=True(打乱数据顺序)
    my_dataloader = DataLoader(my_dataset, batch_size=1, shuffle=True)
    return my_dataloader  # 返回数据加载器

代码解析:

  • 数据加载流程:读取数据文件,创建自定义数据集,再用DataLoader封装。
  • batch_size=1:人名长度不一,难以组成批量(除非使用padding),因此每次只处理一个样本。
  • shuffle=True:打乱数据顺序,增加训练的随机性,避免模型学习到数据的顺序特征。

三、模型定义模块详解

3.1 RNN模型

class RNN(nn.Module):
    """简单循环神经网络模型,用于处理序列数据(人名)并分类语言"""

    def __init__(self, input_size, hidden_size, output_size, batch_size, num_layers):
        """
        初始化RNN模型
        参数:
            input_size (int): 输入维度(字符集大小,57)
            hidden_size (int): 隐藏层维度(128)
            output_size (int): 输出维度(语言类别数,18)
            batch_size (int): 批量大小(1)
            num_layers (int): RNN层数(1)
        """
        super(RNN, self).__init__()  # 继承父类初始化
        self.input_size = input_size  # 输入维度
        self.hidden_size = hidden_size  # 隐藏层维度
        self.output_size = output_size  # 输出维度
        self.batch_size = batch_size  # 批量大小
        self.num_layers = num_layers  # RNN层数

        # 定义RNN层:batch_first=True表示输入格式为[batch, seq, feature]
        self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
        # 定义输出层:将隐藏状态映射到语言类别空间
        self.output_layer = nn.Linear(hidden_size, output_size)
        # 定义LogSoftmax层:计算多分类概率(适用于NLLLoss)
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden):
        """
        前向传播过程
        参数:
            input (torch.Tensor): 输入张量,形状[batch, seq, input_size]
            hidden (torch.Tensor): 隐藏状态,形状[num_layers, batch, hidden_size]
        返回:
            output (torch.Tensor): 分类概率,形状[batch, output_size]
            hn (torch.Tensor): 新的隐藏状态,形状[num_layers, batch, hidden_size]
        """
        xn, hn = self.rnn(input, hidden)  # RNN前向传播
        # 取最后一个时间步的隐藏状态(捕获序列整体特征)
        tem_ten = xn[:, -1, :]
        # 通过线性层映射到类别空间
        output = self.output_layer(tem_ten)
        # 计算分类概率
        output = self.softmax(output)
        return output, hn  # 返回输出和新隐藏状态

    def init_hidden(self):
        """初始化隐藏状态为全零张量"""
        return torch.zeros(self.num_layers, self.batch_size, self.hidden_size)

代码解析:

  • 输入维度:input_size=57,对应one-hot编码的字符集大小。
  • 隐藏层维度:hidden_size=128,决定了模型的表示能力。
  • RNN层:nn.RNN是PyTorch的基础RNN实现,batch_first=True表示输入格式为[batch, seq, feature]。
  • 输出层:将最后一个时间步的隐藏状态映射到18个语言类别。
  • LogSoftmax层:将线性层的输出转换为对数概率分布,配合NLLLoss使用。
  • 为什么取最后一个时间步:人名是一个序列,最后一个时间步的隐藏状态包含了整个序列的信息,适合用于分类。

3.2 LSTM模型

class LSTM(nn.Module):
    """长短期记忆网络模型,解决RNN的长期依赖问题"""

    def __init__(self, input_size, hidden_size, output_size, batch_size, num_layers):
        """
        初始化LSTM模型
        参数与RNN类似,新增记忆单元状态
        """
        super(LSTM, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.batch_size = batch_size
        self.output_size = output_size

        # 定义LSTM层:包含隐藏状态和记忆单元
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
        self.output_layer = nn.Linear(hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden):
        """
        LSTM前向传播
        参数:
            input (torch.Tensor): 输入张量
            hidden (tuple): 包含隐藏状态(h0)和记忆单元(c0)
        """
        h0, c0 = hidden  # 解包隐藏状态和记忆单元
        # LSTM前向传播,返回输出序列和新的隐藏状态
        xn, (hn, cn) = self.lstm(input, (h0, c0))
        # 取最后一个时间步的隐藏状态
        tem_ten = xn[:, -1, :]
        # 映射到类别空间并计算概率
        output = self.output_layer(tem_ten)
        output = self.softmax(output)
        return output, (hn, cn)  # 返回输出和新的隐藏状态、记忆单元

    def init_hidden(self):
        """初始化隐藏状态和记忆单元为全零张量"""
        h0 = torch.zeros(self.num_layers, self.batch_size, self.hidden_size)
        c0 = torch.zeros(self.num_layers, self.batch_size, self.hidden_size)
        return h0, c0  # 返回元组(隐藏状态, 记忆单元)

代码解析:

  • LSTM与RNN的区别:LSTM引入了记忆单元c,通过门控机制解决了RNN的长期依赖问题。
  • 双重状态:LSTM的隐藏状态包含h(短期记忆)和c(长期记忆),初始化时需要分别创建。
  • 门控机制:LSTM内部的输入门、遗忘门和输出门控制信息的流动,使模型能够学习长期依赖关系。

3.3 GRU模型

class GRU(nn.Module):
    """门控循环单元模型,介于RNN和LSTM之间的简化结构"""

    def __init__(self, input_size, hidden_size, output_size, batch_size, num_layers):
        """初始化GRU模型,结构类似RNN但内部使用门控机制"""
        super(GRU, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.batch_size = batch_size
        self.num_layers = num_layers

        # 定义GRU层:包含更新门和重置门
        self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
        self.output_layer = nn.Linear(hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden):
        """GRU前向传播,流程类似RNN但隐藏状态更新方式不同"""
        xn, hn = self.gru(input, hidden)
        tem_ten = xn[:, -1, :]
        output = self.output_layer(tem_ten)
        output = self.softmax(output)
        return output, hn  # 返回输出和新隐藏状态

    def init_hidden(self):
        """初始化GRU隐藏状态为全零张量"""
        return torch.zeros(self.num_layers, self.batch_size, self.hidden_size)

代码解析:

  • GRU结构:GRU是LSTM的简化版本,只有两个门(更新门和重置门),计算效率更高。
  • 更新门:控制前一时间步的信息有多少被保留到当前时间步。
  • 重置门:控制忽略前一时间步的信息程度。
  • 适用场景:在序列较短或计算资源有限的情况下,GRU通常比LSTM表现更好。

四、模型训练与测试模块详解

4.1 测试模型基本功能

def test_def(*args):
    """
    测试模型基本功能(前向传播)
    参数:
        *args (str): 模型名称列表('RNN', 'LSTM', 'GRU')
    """
    for name in args:  # 遍历每个模型名称
        # 根据模型名称从字典中获取模型类并实例化
        my_model = model_dict[name](input_size, hidden_size, output_size, batch_size, num_layers)
        print(f'我的{model_dict[name]}模型:', my_model)  # 打印模型结构
        mydatasets = data_loader()  # 获取数据加载器

        # 前向传播测试(遍历一个批次)
        for i, (tx, ty) in enumerate(mydatasets):
            output, hidden = my_model(tx, my_model.init_hidden())
            if i >= 1: break  # 只测试一个样本

        # 打印输出结果和隐藏状态信息
        print(f'model_name:{name},output:{output}')
        print(f'model_name:{name},output.shape:{output.shape}')
        print(f'model_name:{name},hidden:{hidden}')
        print(f'model_name:{name},hidden.shape:{hidden[0].shape if type(hidden) == tuple else hidden.shape}')

代码解析:

  • 模型实例化:从模型字典中获取模型类并创建实例,验证模型是否能正确初始化。
  • 前向传播测试:通过一个样本的前向传播,检查模型的输入输出格式是否正确。
  • 形状验证:打印输出和隐藏状态的形状,确保与预期一致(输出应为[1, 18],隐藏状态应为[1, 1, 128])。

4.2 模型训练主函数

def my_train_def(*args, n=100, m=1000):
    """
    模型训练主函数
    参数:
        *args (str): 要训练的模型名称列表
        n (int): 记录损失的迭代间隔
        m (int): 打印日志的迭代间隔
    """
    n, m = int(n), int(m)  # 转换参数类型
    for name in args:  # 遍历每个模型
        my_datalooder = data_loader()  # 获取数据加载器

        # 实例化模型
        my_model = model_dict[name](input_size, hidden_size, output_size, batch_size, num_layers)
        print(f'我的{model_dict[name]}模型:', my_model)

        # 定义损失函数:负对数似然损失(适用于多分类问题)
        my_loss_fn = nn.NLLLoss()
        # 定义优化器:Adam优化器,学习率my_lr
        my_adam = optim.Adam(my_model.parameters(), lr=my_lr)

        # 训练日志参数初始化
        start_time = time.time()  # 记录开始时间
        total_iter_num = 0  # 总迭代次数
        total_loss = 0.0  # 总损失
        total_loss_list = []  # 损失记录列表
        total_acc_num = 0  # 总正确预测数
        total_acc_list = []  # 准确率记录列表
        m_loss = 0.0  # 每m次迭代的损失和
        m_acc = 0.0  # 每m次迭代的正确数和

        # 训练主循环:epochs轮次
        for epoch in range(epochs):
            print('第%d轮训练开始...' % (epoch + 1))  # 打印轮次信息

            # 遍历数据加载器中的每个批次(tqdm显示进度条)
            for tx, ty in tqdm(my_datalooder):
                h0 = my_model.init_hidden()  # 初始化隐藏状态
                # 前向传播
                xn, hn = my_model(tx, h0)
                # 计算损失
                my_loss = my_loss_fn(xn, ty)

                # 梯度清零
                my_adam.zero_grad()
                # 反向传播
                my_loss.backward()
                # 参数更新
                my_adam.step()

                # 累计统计信息
                total_iter_num += 1
                total_loss += my_loss.item()
                m_loss += my_loss.item()

                # 计算当前批次预测是否正确(argmax获取最大概率索引)
                i_predit_tag = 1 if torch.argmax(xn).item() == ty.item() else 0
                total_acc_num += i_predit_tag
                m_acc += i_predit_tag

                # 每n次迭代记录平均损失和准确率
                if (total_iter_num % n) == 0:
                    tmploss = total_loss / total_iter_num
                    total_loss_list.append(tmploss)
                    tmpacc = total_acc_num / total_iter_num
                    total_acc_list.append(tmpacc)

                # 每m次迭代打印训练日志
                if (total_iter_num % m) == 0:
                    tmploss = total_loss / total_iter_num
                    tmpacc = total_acc_num / total_iter_num
                    tmp_m_loss = m_loss / m
                    tmp_m_acc = m_acc / m
                    m_loss = 0.0
                    m_acc = 0
                    # 打印详细训练信息(轮次、迭代次数、损失、准确率、时间)
                    print(
                        f'轮次:{epoch + 1},迭代次数:{total_iter_num},总损失:{tmploss:.4f},当前损失:{tmp_m_loss:.4f},'
                        f'总准确率:{tmpacc:.4f},当前准确率:{tmp_m_acc:.4f},时间:{time.time() - start_time:.4f}s')

            # 每轮次结束保存模型参数
            torch.save(my_model.state_dict(), f'{path}/{name}+{epoch + 1}.pth')
            total_time = time.time() - start_time  # 计算总时间
            print(f'总时间:{total_time:.4f}s')

            # 计算本轮次平均损失和准确率
            total_loss = total_loss / total_iter_num
            print(f'总损失:{total_loss:.4f}')
            total_acc = total_acc_num / total_iter_num
            print(f'测试集准确率:{total_acc:.4f}')

            # 保存训练日志到JSON文件(包含轮次、损失、准确率、时间等)
            lstm_dict = {
                'epochs': epochs,
                'total_loss': total_loss,
                'total_acc': total_acc,
                'total_time': total_time,
                'total_loss_list': total_loss_list,
                'total_acc_list': total_acc_list
            }
            with open(f'{path}/{name}_dict.json', 'w', encoding='utf-8') as fw:
                fw.write(json.dumps(lstm_dict))

代码解析:

  • 损失函数:nn.NLLLoss(负对数似然损失)适用于多分类问题,配合LogSoftmax使用。
  • 优化器:Adam优化器自适应调整学习率,通常比SGD收敛更快。
  • 训练循环:
    1. 前向传播:计算模型输出。
    2. 损失计算:对比模型预测与真实标签。
    3. 梯度清零:避免梯度累积。
    4. 反向传播:计算梯度。
    5. 参数更新:根据梯度更新模型参数。
  • 统计信息:记录损失和准确率,用于监控训练过程。
  • 模型保存:每轮保存模型参数,防止训练中断导致数据丢失。
  • 日志记录:保存训练过程数据,用于后续分析和可视化。

五、结果可视化与对比模块详解

def compare_results():
    """
    对比不同模型的训练效果(损失、准确率、时间)
    """
    # 读取各模型的训练日志JSON文件
    with open(f'{path}/RNN_dict.json', 'r', encoding='utf-8') as fr:
        rnn_dict = json.loads(fr.read())
    with open(f'{path}/LSTM_dict.json', 'r', encoding='utf-8') as fl:
        lstm_dict = json.loads(fl.read())
    with open(f'{path}/GRU_dict.json', 'r', encoding='utf-8') as fg:
        gru_dict = json.loads(fg.read())

    # 绘制损失对比图
    plt.figure(0)
    plt.plot(rnn_dict['total_loss_list'], label='RNN', color='r')  # RNN损失曲线(红色)
    plt.plot(lstm_dict['total_loss_list'], label='LSTM', color='g')  # LSTM损失曲线(绿色)
    plt.plot(gru_dict['total_loss_list'], label='GRU', color='b')  # GRU损失曲线(蓝色)
    plt.legend(loc='upper left')  # 显示图例(左上角)
    plt.title('Comparison of Losses Among Different Models')  # 图表标题
    plt.savefig(f'{path}/loss.png')  # 保存图片
    plt.show()  # 显示图表

    # 绘制准确率对比图
    plt.figure(1)
    plt.plot(rnn_dict['total_acc_list'], label='RNN', color='r')
    plt.plot(lstm_dict['total_acc_list'], label='LSTM', color='g')
    plt.plot(gru_dict['total_acc_list'], label='GRU', color='b')
    plt.legend(loc='upper left')
    plt.title('Comparison of Accuracy Among Different Models')
    plt.savefig(f'{path}/acc.png')
    plt.show()

    # 绘制训练时间对比图(柱状图)
    plt.figure(2)
    x_data = ['RNN', 'LSTM', 'GRU']  # x轴标签
    y_data = [rnn_dict['total_time'], lstm_dict['total_time'], gru_dict['total_time']]  # 时间数据
    plt.bar(range(len(x_data)), y_data, tick_label=x_data)  # 绘制柱状图
    plt.savefig(f'{path}/time.png')
    plt.title('Comparison of Losses Among Different Models')
    plt.show()

代码解析:

  • 数据读取:从JSON文件中读取各模型的训练日志。
  • 损失对比图:直观展示三种模型的损失下降曲线,评估收敛速度和最终性能。
  • 准确率对比图:比较三种模型的准确率变化,分析学习效率。
  • 训练时间对比:柱状图展示训练时间差异,LSTM通常最慢,RNN最快。

六、模型预测模块详解

def predict_def(x, *args, t=3):
    """
    使用训练好的模型对新人名进行语言预测
    参数:
        x (str): 输入人名(如'John')
        *args (str): 要使用的模型名称列表
        t (int): 显示前t个预测结果
    """
    if len(args) == 0:  # 如果未指定模型,默认使用所有模型
        args = ['RNN', 'LSTM', 'GRU']
    for name in args:  # 遍历每个模型
        # 输入数据预处理:转换为one-hot张量并升维(添加batch维度)
        input_x = word_to_tensor(x).unsqueeze(0)

        # 实例化模型并加载训练好的参数
        my_model = model_dict[name](input_size, hidden_size, output_size, batch_size, num_layers)
        my_model.load_state_dict(torch.load(f'{path}/{name}+{epochs}.pth'))

        # 模型预测(不计算梯度,节省内存)
        with torch.no_grad():
            rnn_xn, hn = my_model(input_x, my_model.init_hidden())
            # 获取前t个最高概率的预测结果
            rnn_topv, topi = rnn_xn.topk(t, 1)

            print(f'{name}预测结果为:')
            for i in range(t):  # 打印前t个预测结果
                # topi[0][i]是预测类别的索引,categorys[索引]是对应的语言名称
                print(f'第{i + 1}可能的预测结果为:{categorys[topi[0][i]]}')

代码解析:

  • 输入预处理:将人名转换为one-hot张量,并添加batch维度(unsqueeze(0))。
  • 模型加载:实例化模型并加载训练好的参数。
  • 预测过程:
    1. with torch.no_grad():关闭梯度计算,提高推理速度。
    2. topk(t, 1):获取概率最高的t个类别及其概率值。
  • 结果展示:打印前t个预测结果及其对应的语言类别。

七、案例结果分析与模型对比

7. 1 本次案例结果

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

案例在本次阿中,我们对同一个输入人名"Otto von Habsburg"(德国人)进行了预测,结果如下:

RNN预测结果为:
第1可能的预测结果为:Russian
第2可能的预测结果为:German
第3可能的预测结果为:Korean

LSTM预测结果为:
第1可能的预测结果为:German
第2可能的预测结果为:Dutch
第3可能的预测结果为:Czech

GRU预测结果为:
第1可能的预测结果为:German
第2可能的预测结果为:English
第3可能的预测结果为:Czech

7.2 结果分析

  1. GRU表现最佳:

    • 正确预测出German作为第一可能性
    • 第二可能性English也是相关语言(与German同属日耳曼语系)
    • 训练时间最长但准确性最高
  2. LSTM表现次之:

    • 正确预测出German作为第一可能性
    • 但第二、三可能性Dutch和Czech属于不同语系
    • 训练时间居中
  3. RNN表现最差:

    • 将German预测为Russian(错误)
    • German仅作为第二可能性
    • 训练时间最短但准确性最低

7.3 与经典理论差异的原因

虽然经典理论认为LSTM通常优于GRU,但本次案例中GRU表现最好,可能原因包括:

  1. 任务特性:

    • 人名分类任务序列较短(通常不超过20个字符)
    • GRU的门控机制足以捕捉关键特征
    • LSTM的复杂结构可能带来过拟合风险
  2. 数据规模:

    • 训练数据量有限(约2万条)
    • GRU参数更少,在中小规模数据上泛化能力更强
  3. 超参数设置:

    • 所有模型使用相同的超参数(如隐藏层大小、学习率)
    • 可能未充分发挥LSTM的潜力
  4. 随机性因素:

    • 训练过程中的随机初始化
    • 数据加载顺序的随机性

7.4 三种模型的特点对比

模型优点缺点适用场景
RNN结构简单,计算效率高存在梯度消失/爆炸问题,难以学习长期依赖短序列任务,对计算资源敏感的场景
LSTM解决长期依赖问题,学习能力强结构复杂,训练时间长,参数多长序列任务,需要捕捉复杂时间依赖的场景
GRU计算效率高,参数少,训练速度快长期依赖能力略弱于LSTM中短序列任务(如人名分类),平衡计算效率和模型性能的场景

八、完整代码

数据集下载:百度网盘链接

注意:

  • 请在项目根目录放入数据集文件
  • 需要在项目根目录创建data文件夹

完整代码:

import json  # 用于JSON格式数据的序列化和反序列化,用于保存和读取训练日志
import torch  # PyTorch深度学习框架,提供张量操作和自动微分功能
import torch.nn as nn  # 包含神经网络模块和层的子包,用于构建模型
import torch.nn.functional as F  # 包含常用神经网络函数(如激活函数、损失函数)
import torch.optim as optim  # 包含优化算法(如SGD、Adam),用于模型参数更新
from torch.utils.data import DataLoader, Dataset  # 数据加载工具,用于批量处理数据
import string  # 包含字符串常量和操作函数,用于定义字符集
import time  # 用于记录训练时间,评估模型效率
import matplotlib.pyplot as plt  # 数据可视化库,用于绘制损失和准确率曲线
from tqdm import tqdm  # 用于生成进度条,显示训练过程



# ============================== 数据预处理模块 ==============================
def string_setting():
    """
    定义模型所需的字符集和语言类别标签
    1. 构建包含字母和常用标点的字符集
    2. 定义目标语言类别列表(共18种语言)
    """
    # 构建字符集:包含26个大写字母、26个小写字母和常用标点符号
    al_letters = string.ascii_letters + " .,;'"
    n_letters = len(al_letters)  # 计算字符集大小(57个字符)
    print("字符数量为:", n_letters)

    # 定义目标语言类别列表(按顺序:意大利语、英语、阿拉伯语等18种语言)
    categorys = ['Italian', 'English', 'Arabic', 'Spanish', 'Scottish', 'Irish', 'Chinese',
                 'Vietnamese', 'Japanese', 'French', 'Greek', 'Dutch', 'Korean', 'Polish',
                 'Portuguese', 'Russian', 'Czech', 'German']
    categorys_len = len(categorys)  # 计算语言类别数量(18类)
    print("类别数量为:", categorys_len)
    return al_letters, categorys  # 返回字符集和语言类别列表


def read_data(path):
    """
    从文本文件中读取人名分类数据
    参数:
        path (str): 数据文件路径,文件格式为每行"人名\t语言标签"
    返回:
        data_list_x (list): 人名列表(x数据集)
        data_list_y (list): 语言标签列表(y数据集)
    """
    data_list_x, data_list_y = [], []  # 初始化数据存储列表
    with open(path, encoding="utf-8") as f:  # 以UTF-8编码打开文件
        for line in f.readlines():  # 逐行读取数据
            # 数据清洗:过滤长度小于等于5的行(可能是无效数据)
            if len(line) <= 5:
                continue
            # 按制表符分割每行数据,前半部分为人名,后半部分为语言标签
            x, y = line.strip().split('\t')
            data_list_x.append(x)  # 保存人名到x列表
            data_list_y.append(y)  # 保存标签到y列表
    return data_list_x, data_list_y  # 返回清洗后的数据


def word_to_tensor(x):
    """
    将人名转换为one-hot编码张量
    参数:
        x (str): 输入人名(如"John")
    返回:
        tensor_x (torch.Tensor): 二维one-hot张量,形状为[len(x), len(al_letters)]
    示例:
        输入"John",输出形状为[4, 57]的张量,每个字符在对应位置置1
    """
    tensor_x = torch.zeros(len(x), len(al_letters))  # 初始化全零张量
    for i, letter in enumerate(x):  # 遍历人名中的每个字符
        # 在字符集中查找当前字符的索引,并在对应位置置1
        tensor_x[i][al_letters.find(letter)] = 1
    return tensor_x  # 返回one-hot编码张量


# 自定义数据集类,继承PyTorch的Dataset基类
class MyDatasets(Dataset):
    def __init__(self, data_list_x, data_list_y):
        """
        初始化数据集
        参数:
            data_list_x (list): 人名列表
            data_list_y (list): 语言标签列表
        """
        self.data_list_x = data_list_x  # 保存人名数据
        self.data_list_y = data_list_y  # 保存标签数据
        self.data_list_len = len(data_list_x)  # 数据集长度

    def __len__(self):
        """返回数据集大小"""
        return self.data_list_len

    def __getitem__(self, index):
        """
        获取指定索引的样本
        参数:
            index (int): 样本索引
        返回:
            tensor_x (torch.Tensor): one-hot编码的人名张量
            tensor_y (torch.Tensor): 语言标签的张量表示
        """
        # 处理索引越界:确保索引在有效范围内
        index = min(max(index, -self.data_list_len), self.data_list_len - 1)
        x = self.data_list_x[index]  # 获取人名
        y = self.data_list_y[index]  # 获取标签

        # 将人名转换为one-hot张量
        tensor_x = word_to_tensor(x)
        # 将标签转换为张量(使用类别索引,类型为long)
        tensor_y = torch.tensor(categorys.index(y), dtype=torch.long)
        return tensor_x, tensor_y  # 返回样本和标签


def data_loader():
    """
    封装数据加载全流程
    返回:
        my_dataloader (DataLoader): 数据加载器对象
    """
    path = './name_classfication.txt'  # 数据文件路径
    my_list_x, my_list_y = read_data(path)  # 读取数据
    print(f'x数据集数量为:{len(my_list_x)}')  # 打印数据规模
    print(f'y数据集数量为:{len(my_list_y)}')

    # 实例化自定义数据集
    my_dataset = MyDatasets(my_list_x, my_list_y)
    # 实例化数据加载器:batch_size=1(单样本批量),shuffle=True(打乱数据顺序)
    my_dataloader = DataLoader(my_dataset, batch_size=1, shuffle=True)
    return my_dataloader  # 返回数据加载器


# ============================== 模型定义模块 ==============================
class RNN(nn.Module):
    """简单循环神经网络模型,用于处理序列数据(人名)并分类语言"""

    def __init__(self, input_size, hidden_size, output_size, batch_size, num_layers):
        """
        初始化RNN模型
        参数:
            input_size (int): 输入维度(字符集大小,57)
            hidden_size (int): 隐藏层维度(128)
            output_size (int): 输出维度(语言类别数,18)
            batch_size (int): 批量大小(1)
            num_layers (int): RNN层数(1)
        """
        super(RNN, self).__init__()  # 继承父类初始化
        self.input_size = input_size  # 输入维度
        self.hidden_size = hidden_size  # 隐藏层维度
        self.output_size = output_size  # 输出维度
        self.batch_size = batch_size  # 批量大小
        self.num_layers = num_layers  # RNN层数

        # 定义RNN层:batch_first=True表示输入格式为[batch, seq, feature]
        self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)
        # 定义输出层:将隐藏状态映射到语言类别空间
        self.output_layer = nn.Linear(hidden_size, output_size)
        # 定义LogSoftmax层:计算多分类概率(适用于NLLLoss)
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden):
        """
        前向传播过程
        参数:
            input (torch.Tensor): 输入张量,形状[batch, seq, input_size]
            hidden (torch.Tensor): 隐藏状态,形状[num_layers, batch, hidden_size]
        返回:
            output (torch.Tensor): 分类概率,形状[batch, output_size]
            hn (torch.Tensor): 新的隐藏状态,形状[num_layers, batch, hidden_size]
        """
        xn, hn = self.rnn(input, hidden)  # RNN前向传播
        # 取最后一个时间步的隐藏状态(捕获序列整体特征)
        tem_ten = xn[:, -1, :]
        # 通过线性层映射到类别空间
        output = self.output_layer(tem_ten)
        # 计算分类概率
        output = self.softmax(output)
        return output, hn  # 返回输出和新隐藏状态

    def init_hidden(self):
        """初始化隐藏状态为全零张量"""
        return torch.zeros(self.num_layers, self.batch_size, self.hidden_size)


class LSTM(nn.Module):
    """长短期记忆网络模型,解决RNN的长期依赖问题"""

    def __init__(self, input_size, hidden_size, output_size, batch_size, num_layers):
        """
        初始化LSTM模型
        参数与RNN类似,新增记忆单元状态
        """
        super(LSTM, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.batch_size = batch_size
        self.output_size = output_size

        # 定义LSTM层:包含隐藏状态和记忆单元
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
        self.output_layer = nn.Linear(hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden):
        """
        LSTM前向传播
        参数:
            input (torch.Tensor): 输入张量
            hidden (tuple): 包含隐藏状态(h0)和记忆单元(c0)
        """
        h0, c0 = hidden  # 解包隐藏状态和记忆单元
        # LSTM前向传播,返回输出序列和新的隐藏状态
        xn, (hn, cn) = self.lstm(input, (h0, c0))
        # 取最后一个时间步的隐藏状态
        tem_ten = xn[:, -1, :]
        # 映射到类别空间并计算概率
        output = self.output_layer(tem_ten)
        output = self.softmax(output)
        return output, (hn, cn)  # 返回输出和新的隐藏状态、记忆单元

    def init_hidden(self):
        """初始化隐藏状态和记忆单元为全零张量"""
        h0 = torch.zeros(self.num_layers, self.batch_size, self.hidden_size)
        c0 = torch.zeros(self.num_layers, self.batch_size, self.hidden_size)
        return h0, c0  # 返回元组(隐藏状态, 记忆单元)


class GRU(nn.Module):
    """门控循环单元模型,介于RNN和LSTM之间的简化结构"""

    def __init__(self, input_size, hidden_size, output_size, batch_size, num_layers):
        """初始化GRU模型,结构类似RNN但内部使用门控机制"""
        super(GRU, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.output_size = output_size
        self.batch_size = batch_size
        self.num_layers = num_layers

        # 定义GRU层:包含更新门和重置门
        self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
        self.output_layer = nn.Linear(hidden_size, output_size)
        self.softmax = nn.LogSoftmax(dim=-1)

    def forward(self, input, hidden):
        """GRU前向传播,流程类似RNN但隐藏状态更新方式不同"""
        xn, hn = self.gru(input, hidden)
        tem_ten = xn[:, -1, :]
        output = self.output_layer(tem_ten)
        output = self.softmax(output)
        return output, hn  # 返回输出和新隐藏状态

    def init_hidden(self):
        """初始化GRU隐藏状态为全零张量"""
        return torch.zeros(self.num_layers, self.batch_size, self.hidden_size)


# ============================== 模型训练与测试模块 ==============================
def test_def(*args):
    """
    测试模型基本功能(前向传播)
    参数:
        *args (str): 模型名称列表('RNN', 'LSTM', 'GRU')
    """
    for name in args:  # 遍历每个模型名称
        # 根据模型名称从字典中获取模型类并实例化
        my_model = model_dict[name](input_size, hidden_size, output_size, batch_size, num_layers)
        print(f'我的{model_dict[name]}模型:', my_model)  # 打印模型结构
        mydatasets = data_loader()  # 获取数据加载器

        # 前向传播测试(遍历一个批次)
        for i, (tx, ty) in enumerate(mydatasets):
            output, hidden = my_model(tx, my_model.init_hidden())
            if i >= 1: break  # 只测试一个样本

        # 打印输出结果和隐藏状态信息
        print(f'model_name:{name},output:{output}')
        print(f'model_name:{name},output.shape:{output.shape}')
        print(f'model_name:{name},hidden:{hidden}')
        print(f'model_name:{name},hidden.shape:{hidden[0].shape if type(hidden) == tuple else hidden.shape}')


def my_train_def(*args, n=100, m=1000):
    """
    模型训练主函数
    参数:
        *args (str): 要训练的模型名称列表
        n (int): 记录损失的迭代间隔
        m (int): 打印日志的迭代间隔
    """
    n, m = int(n), int(m)  # 转换参数类型
    for name in args:  # 遍历每个模型
        my_datalooder = data_loader()  # 获取数据加载器

        # 实例化模型
        my_model = model_dict[name](input_size, hidden_size, output_size, batch_size, num_layers)
        print(f'我的{model_dict[name]}模型:', my_model)

        # 定义损失函数:负对数似然损失(适用于多分类问题)
        my_loss_fn = nn.NLLLoss()
        # 定义优化器:Adam优化器,学习率my_lr
        my_adam = optim.Adam(my_model.parameters(), lr=my_lr)

        # 训练日志参数初始化
        start_time = time.time()  # 记录开始时间
        total_iter_num = 0  # 总迭代次数
        total_loss = 0.0  # 总损失
        total_loss_list = []  # 损失记录列表
        total_acc_num = 0  # 总正确预测数
        total_acc_list = []  # 准确率记录列表
        m_loss = 0.0  # 每m次迭代的损失和
        m_acc = 0.0  # 每m次迭代的正确数和

        # 训练主循环:epochs轮次
        for epoch in range(epochs):
            print('第%d轮训练开始...' % (epoch + 1))  # 打印轮次信息

            # 遍历数据加载器中的每个批次(tqdm显示进度条)
            for tx, ty in tqdm(my_datalooder):
                h0 = my_model.init_hidden()  # 初始化隐藏状态
                # 前向传播
                xn, hn = my_model(tx, h0)
                # 计算损失
                my_loss = my_loss_fn(xn, ty)

                # 梯度清零
                my_adam.zero_grad()
                # 反向传播
                my_loss.backward()
                # 参数更新
                my_adam.step()

                # 累计统计信息
                total_iter_num += 1
                total_loss += my_loss.item()
                m_loss += my_loss.item()

                # 计算当前批次预测是否正确(argmax获取最大概率索引)
                i_predit_tag = 1 if torch.argmax(xn).item() == ty.item() else 0
                total_acc_num += i_predit_tag
                m_acc += i_predit_tag

                # 每n次迭代记录平均损失和准确率
                if (total_iter_num % n) == 0:
                    tmploss = total_loss / total_iter_num
                    total_loss_list.append(tmploss)
                    tmpacc = total_acc_num / total_iter_num
                    total_acc_list.append(tmpacc)

                # 每m次迭代打印训练日志
                if (total_iter_num % m) == 0:
                    tmploss = total_loss / total_iter_num
                    tmpacc = total_acc_num / total_iter_num
                    tmp_m_loss = m_loss / m
                    tmp_m_acc = m_acc / m
                    m_loss = 0.0
                    m_acc = 0
                    # 打印详细训练信息(轮次、迭代次数、损失、准确率、时间)
                    print(
                        f'轮次:{epoch + 1},迭代次数:{total_iter_num},总损失:{tmploss:.4f},当前损失:{tmp_m_loss:.4f},'
                        f'总准确率:{tmpacc:.4f},当前准确率:{tmp_m_acc:.4f},时间:{time.time() - start_time:.4f}s')

            # 每轮次结束保存模型参数
            torch.save(my_model.state_dict(), f'{path}/{name}+{epoch + 1}.pth')
            total_time = time.time() - start_time  # 计算总时间
            print(f'总时间:{total_time:.4f}s')

            # 计算本轮次平均损失和准确率
            total_loss = total_loss / total_iter_num
            print(f'总损失:{total_loss:.4f}')
            total_acc = total_acc_num / total_iter_num
            print(f'测试集准确率:{total_acc:.4f}')

            # 保存训练日志到JSON文件(包含轮次、损失、准确率、时间等)
            lstm_dict = {
                'epochs': epochs,
                'total_loss': total_loss,
                'total_acc': total_acc,
                'total_time': total_time,
                'total_loss_list': total_loss_list,
                'total_acc_list': total_acc_list
            }
            with open(f'{path}/{name}_dict.json', 'w', encoding='utf-8') as fw:
                fw.write(json.dumps(lstm_dict))


# ============================== 结果可视化与对比模块 ==============================
def compare_results():
    """
    对比不同模型的训练效果(损失、准确率、时间)
    """
    # 读取各模型的训练日志JSON文件
    with open(f'{path}/RNN_dict.json', 'r', encoding='utf-8') as fr:
        rnn_dict = json.loads(fr.read())
    with open(f'{path}/LSTM_dict.json', 'r', encoding='utf-8') as fl:
        lstm_dict = json.loads(fl.read())
    with open(f'{path}/GRU_dict.json', 'r', encoding='utf-8') as fg:
        gru_dict = json.loads(fg.read())

    # 绘制损失对比图
    plt.figure(0)
    plt.plot(rnn_dict['total_loss_list'], label='RNN', color='r')  # RNN损失曲线(红色)
    plt.plot(lstm_dict['total_loss_list'], label='LSTM', color='g')  # LSTM损失曲线(绿色)
    plt.plot(gru_dict['total_loss_list'], label='GRU', color='b')  # GRU损失曲线(蓝色)
    plt.legend(loc='upper left')  # 显示图例(左上角)
    plt.title('Comparison of Losses Among Different Models')  # 图表标题
    plt.savefig(f'{path}/loss.png')  # 保存图片
    plt.show()  # 显示图表

    # 绘制准确率对比图
    plt.figure(1)
    plt.plot(rnn_dict['total_acc_list'], label='RNN', color='r')
    plt.plot(lstm_dict['total_acc_list'], label='LSTM', color='g')
    plt.plot(gru_dict['total_acc_list'], label='GRU', color='b')
    plt.legend(loc='upper left')
    plt.title('Comparison of Accuracy Among Different Models')
    plt.savefig(f'{path}/acc.png')
    plt.show()

    # 绘制训练时间对比图(柱状图)
    plt.figure(2)
    x_data = ['RNN', 'LSTM', 'GRU']  # x轴标签
    y_data = [rnn_dict['total_time'], lstm_dict['total_time'], gru_dict['total_time']]  # 时间数据
    plt.bar(range(len(x_data)), y_data, tick_label=x_data)  # 绘制柱状图
    plt.savefig(f'{path}/time.png')
    plt.title('Comparison of Losses Among Different Models')
    plt.show()


# ============================== 模型预测模块 ==============================
def predict_def(x, *args, t=3):
    """
    使用训练好的模型对新人名进行语言预测
    参数:
        x (str): 输入人名(如'John')
        *args (str): 要使用的模型名称列表
        t (int): 显示前t个预测结果
    """
    if len(args) == 0:  # 如果未指定模型,默认使用所有模型
        args = ['RNN', 'LSTM', 'GRU']
    for name in args:  # 遍历每个模型
        # 输入数据预处理:转换为one-hot张量并升维(添加batch维度)
        input_x = word_to_tensor(x).unsqueeze(0)

        # 实例化模型并加载训练好的参数
        my_model = model_dict[name](input_size, hidden_size, output_size, batch_size, num_layers)
        my_model.load_state_dict(torch.load(f'{path}/{name}+{epochs}.pth'))

        # 模型预测(不计算梯度,节省内存)
        with torch.no_grad():
            rnn_xn, hn = my_model(input_x, my_model.init_hidden())
            # 获取前t个最高概率的预测结果
            rnn_topv, topi = rnn_xn.topk(t, 1)

            print(f'{name}预测结果为:')
            for i in range(t):  # 打印前t个预测结果
                # topi[0][i]是预测类别的索引,categorys[索引]是对应的语言名称
                print(f'第{i + 1}可能的预测结果为:{categorys[topi[0][i]]}')


if __name__ == '__main__':
    # 超参数设置
    my_lr = 1e-3  # 学习率:0.001
    epochs = 2  # 训练轮次:2轮(教学演示用,实际应增加轮次)
    al_letters, categorys = string_setting()  # 初始化字符集和语言类别
    input_size = len(al_letters)  # 输入维度:57
    hidden_size = 128  # 隐藏层维度:128
    num_layers = 1  # 网络层数:1
    batch_size = 1  # 批量大小:1
    output_size = len(categorys)  # 输出维度:18
    path = './data'  # 数据和模型保存路径

    # 模型字典:映射模型名称到模型类,便于统一管理和调用
    model_dict = {'RNN': RNN, 'LSTM': LSTM, 'GRU': GRU}

    # 执行训练、对比和预测流程
    my_train_def('RNN', 'LSTM', 'GRU')  # 注意:如果同时训练多个模型,可能会导致总训练时间不准(CPU或GPU的性能受运行时间影响较大)
    compare_results()  # 对比模型效果
    predict_def('Otto von Habsburg')  # 对'Otto von Habsburg'(德国人)进行语言预测
人工智能训练师三级全真模拟试卷(三)|65题+MLOps+Prompt工程+答案解析 本套模拟试卷(二)包含65道题目,总分100分,题型包括单选(30题)、多选(10题)、判断(20题)和简答(5题),覆盖机器学习进阶、深度学习框架、CV/NLP核心技术、数据标注、模型优化及AI治理等七大知识领域。试卷提供AutoGrader自动评分工具,建议在完成卷一后进行查漏补缺。重点考查概念辨析、流程规范、工具使用和方案设计等能力,不同题型侧重不同维度考核,及格线为60分。 阅读详情

相关推荐

RNN文本生成实战:从字符级建模到梯度裁剪的序列模型全解析

循环神经网络(RNN)是处理时序数据的经典深度学习模型,其核心思想是通过隐藏状态在时间步之间传递信息,从而捕捉序列中的依赖关系。在自然语言处理领域,文本天然具有前后顺序,因此RNN常被用于字符级的下一个词预测,这也是文本生成任务的最小可行实现。训练RNN时,反向传播沿时间步展开(BPTT),容易产生梯度爆炸,梯度裁剪则是保障训练稳定的关键手段。尽管Transformer凭借自注意力机制实现了并行计算和长距离建模,逐渐成为主流,但RNN在小规模、低资源场景下依然具备代码简单、推理快的优势。理解RNN的门控机制

weixin_29050513的博客 270

人名分类器(RNN、LSTM、GRU)

【代码】人名分类器(RNN、LSTM、GRU)

weixin_62761878的博客 373

NLP期末作业的工业级复现:从环境配置到误差归因

自然语言处理(NLP)是人工智能核心分支,其工程实践需兼顾算法原理、系统环境与评估严谨性。理解文本分类、序列标注等基础任务的技术逻辑,掌握CUDA版本对PyTorch GPU加速的影响,是构建可复现实验的前提;而Macro-F1、注意力可视化、消融实验等方法,则直接关联模型鲁棒性与结果可信度。在小样本、低算力约束下,LSTM等轻量模型常比BERT更具数据效率,其hidden_size调优、dropout设置与层间学习率分配,体现的是对深度学习底层机制的工程化把握。本文聚焦NLP课程作业中真实可复现的端到端闭

weixin_30521161的博客 382

NLP:人名分类器案例分享

前言:前面介绍了传统RNN、LSTM、GRU,本篇文章分享综合案例。

sulin123123的博客 1243

Python虚假新闻检测实战:从机器学习到BERT的全链路实现

自然语言处理(NLP)是人工智能领域的关键技术,其核心在于让计算机理解和生成人类语言。基于文本分类的原理,通过特征提取和模式识别,NLP技术能够自动对海量文本进行高效分析与归类。这一技术具有重要的工程价值,尤其在内容安全、舆情监控和社交媒体分析等场景中,能够帮助过滤有害信息、提升信息质量。虚假新闻检测正是NLP技术的一个典型应用,它融合了传统机器学习、深度学习和预训练模型BERT等多种方法。其中,特征工程是机器学习模型的基石,通过设计语言风格、心理修辞等特征让模型学会“察言观色”;而BERT模型凭借其强大的

weixin_32285357的博客 279

PyTorch 人名分类数据集

PyTorch官方提供的数据集,用于NLP任务。

用RNN构建人名分类器

即随机生成10个国家、10个国家里的名字,并将其转换成张量(对于国家名张量category_tensor,其为一维,方括号内为其在国家名列表中的位置;即,对于GRU模型输出结果,取最大值后,判断‘B’(输入)为国家类别中第13个国家Portuguese中的名字(准确率低):即x和y张量相加,x的每一个值与y的每一个值相加(a的第一行为x的每一个值分别于y的第一个值相加…:y先乘10再进行torch.add(x,y),即相当于torch.add(x,10y)不然会将其当作一个数据(之后被选中时会报错)

weixin_63685622的博客 1238

CAN通信:EB中配置CAN驱动,配置参数解析

以S32K314芯片说明,其他芯片不同,参数配置也有差异。

zhangxiaio1的博客 2167

RNN以及其改进版(附2个代码案列)

RNN(Recurrent Neural Network), 中文称作循环神经网络, 它一般以序列数据为输入, 通过网络内部的结构设计有效捕捉序列之间的关系特征, 一般也是以序列形式进行输出 两个黑点一起到达蓝色区域(并在之前形成整体) 于帮助调节流经网络的值, tanh函数将值压缩在-1和1之间 梯度计算 优点:LSTM的门结构能够有效减缓长序列问题中可能出现的梯度消失或爆炸, 虽然并不能杜绝这种现象, 但在更长的序列问题上表现优于传统RNN. 缺点:由于内部结构相对较复杂, 因此训练效率在同等算力下较传

GodGump的博客 2294

FFmpeg实战:SEI自定义数据在RTMP推流中的高效嵌入

本文深入探讨了如何利用FFmpeg在RTMP推流中高效嵌入SEI自定义数据。通过将SEI比作与视频帧同步的“小纸条”,文章详细解析了H.264码流中SEI的结构与位置,并提供了命令行与API编程两种实战方法,帮助开发者实现直播答题、AI元数据回传等场景下的帧级精准数据同步。

weixin_29304021的博客 325

使用 C# 开发海康设备实时视频流的监控与报警系统

本文介绍了如何使用C#结合海康威视SDK(HCNetSDK)开发安防监控系统,涵盖设备登录、视频流获取、报警处理和云台控制等核心功能。内容包括开发环境配置、系统架构设计、关键代码实现(如设备登录、实时视频播放、报警设置)以及用户界面设计。通过Windows Forms/WPF,开发者可构建完整的监控应用,实现设备管理、视频监控、报警响应等功能。系统采用模块化设计,便于扩展和维护,适用于各类安防场景。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 1355

【深度学习】循环神经网络实现文本预测生成

本文梳理了循环神经网络(RNN)的改进模型LSTM和GRU,以及字符级与词级文本处理的对比。LSTM通过门控机制解决长序列依赖问题,GRU是其简化版,参数量更少。字符级模型词汇表小、无OOV问题,适合文本生成;词级模型适合语义任务。文章以《傲慢与偏见》为例,详细介绍了字符级RNN的文本生成实现过程,包括数据预处理、模型构建和训练流程,并解析了关键代码逻辑和超参数设置。

2303_80634169的博客 1744

全志T527-TP9930-Camera

TP9930 驱动模块主要实现将 4 路的 AHD/TVI/CVBS Camera 的数据转换为 BT656/BT1120 数据,从而实现在 T527 端来对数据进行处理和送显。

Linux驱动开发工程师,熟悉Linux/Android底层驱动、TP、显示、IIC、网络等通用外设调试适配,Linux上层应用开发。常用平台瑞芯微、全志、恩智浦等。 3235

pytorch案例代码-3

pytorch 深度学习 双向循环神经网络BRNN GRU LSTM

weixin_43739821的博客 1292

YOLOv11改进-上采样 | EUCB高效上卷积块,实现特征图尺度匹配和高效上采样

本文介绍了一种在YOLOv11目标检测模型中引入高效解码器模块EMCAD的创新方法,以提升模型在资源受限场景下的性能与效率。EMCAD由多个模块构成,其中核心的EUCB(高效上卷积块)通过上采样、深度可分离卷积、激活归一化和通道调整等操作,兼顾了特征质量与计算成本。实验结果显示,该模块在显著减少参数与FLOPs的同时仍具备优异性能。文章还提供了完整的YOLOv11模型集成流程、配置和训练实战。

魔改工程师的博客 1540

亚马逊爬虫实战:中国商家信息(电话)爬取

import os), # 保存图片的路径# 搜索关键词"keywords":["毛巾","衣服"],"product_number": 1000000000000000, # 分页获取数量# 和url一一对应 个数要一样"remarks": ["备注1"],# 以下内容非专业人员请勿更改,请找开发人员更改. 对应的是amazon_product_url_setting文件中名称.},

youbingchen的博客 1524

LSTM原理详解与PyTorch实战:从梯度消失到时间序列预测

循环神经网络(RNN)是处理序列数据的基础模型,但其在训练长序列时面临梯度消失或爆炸的挑战,导致难以捕捉长期依赖关系。为解决这一问题,长短时记忆网络(LSTM)通过引入精密的门控机制,包括遗忘门、输入门和输出门,实现了对细胞状态(记忆)的选择性更新与读取,从而有效缓解了梯度问题,提升了模型对长距离信息的记忆能力。这一技术革新在自然语言处理、语音识别和时间序列预测等工程实践中展现出巨大价值。本文以时间序列预测为应用场景,结合PyTorch框架,详细解析了LSTM的内部结构、数据预处理、模型构建及调优技巧,帮助

Eric.Net的专栏 343

C++ getline函数详解与示例代码

每次调用函数时,会读取一行文本,并将其存储在字符串对象中。在循环中,我们检查读取到的文本是否为空行,如果是则跳出循环,否则输出读取到的文本。这个函数在处理用户输入、文件读取等场景中非常有用,希望能对您的C++编程有所帮助。函数是一个常用的字符串输入函数,可以用于从输入流中读取一行文本。首先,我们输出提示信息,要求用户输入一行文本。函数会等待用户输入,并将输入的文本存储在。函数从输入流中读取一行文本,并将其存储在字符串对象。这样,当用户输入制表符时,函数会停止读取。函数的用法,并提供相应的示例代码。

LogicGuruX的博客 827

PyTorch中文教程 | (13) 使用字符级别特征的RNN网络生成名字

Github地址 在上一篇博客中我们使用RNN网络对名字所属的语言进行分类。 这一次我们会反过来根据语言生成名字。 > python sample.py Russian RUS Rovakov Uantov Shavakov > python sample.py German GER Gerren Ereng Rosher > python sample.py Span...

sdu_hao的博客 844

气体放电管GDT选型解析

气体放电管(GDT)是电子设备电磁兼容设计中应对浪涌防护的核心器件,具有高能量吸收能力(10kA以上)和低漏电流特性。其工作原理分为击穿、导通和熄弧三阶段,通过电离气体泄放浪涌能量。GDT适用于雷击等大能量防护,但响应较慢(0.1-0.3μs),需搭配TVS/MOV组成多级防护。选型需匹配击穿电压(如220VAC系统选800V型号),并注意续流风险。典型应用包括电源端口、通信防雷模块的三极管共模防护或GDT-MOV串联方案,实现能量分流与快速钳位平衡。

硬核科技 208

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

从LSTM到Transformer:NLP入门实战与模型选择指南

自然语言处理(NLP)的核心在于让计算机理解和生成人类语言,其基础是序列建模。传统循环神经网络(RNN)及其改进型LSTM通过门控机制缓解了梯度消失问题,能够有效捕捉文本中的时序依赖关系,为情感分析、机器翻译等任务提供了基础解决方案。随着Transformer架构及其自注意力机制的提出,模型并行计算能力和长距离依赖建模能力得到显著提升,成为当前预训练模型和大语言模型(LLM)的基石。在实际工程中,开发者需要根据任务规模、数据特性和资源约束,在LSTM、Transformer、BERT等模型间做出合理选择。例

weixin_33874713的博客 405

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

05阶段:NLP自然语言处理基础(1)

one-hot编码的优劣势:劣势:完全割裂了词与词之间的联系,而且在大语料集下,每个向量的长度过大,占据大量内存.正因为one-hot编码明显的劣势,这种编码方式被应用的地方越来越少,取而代之的是接下来我们要学习的稠密向量的表示方法word2vec和word embedding.word2vec介绍 1- 是将文本变成词向量张量的一种方法,是一种无监督(自监督)的训练方法,本质是训练一个模型,将模型的参数矩阵当作所有词汇的词向量表示。基于one-hot的形式进行优化改造 2-

专注前端开发领域,深耕 Python AI Agent应用开发,热爱探索 Web 与人工智能的前沿技术。 338

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

NLP核心知识体系:从词向量到Transformer的演进与实战指南

自然语言处理(NLP)作为人工智能的关键分支,其核心在于如何让计算机理解和生成人类语言。从早期的符号表示方法,如词袋模型和TF-IDF,到革命性的分布式表示学习,如Word2Vec和GloVe词向量,NLP实现了从离散符号到连续语义空间的跨越。这一演进解决了传统方法无法捕捉语义关联和词序信息的局限。其技术价值在于为后续复杂的序列建模任务提供了高质量的语义表示基础。在工程实践中,预训练词向量常作为模型初始化的标准操作,能有效提升下游任务的性能。随着注意力机制和Transformer架构的出现,NLP进入了预训

weixin_30247781的博客 457

从零开始吃透 Seq2Seq:手把手用 PyTorch 搭建中英翻译模型

在 NLP 领域,“序列”通常指一个由单词、字符或子词组成的有序列表。一句话就是一个序列,每个字/词按顺序排列。Seq2Seq 模型的核心就是理解输入序列的顺序结构,并生成与之对应的输出序列。在训练 Seq2Seq 模型时,我们使用一种名为Teacher Forcing(教师强制)的巧妙技术。为什么需要 Teacher Forcing?回想一下,解码器在生成过程中,每一步的输入是上一步的输出。在训练初期,模型对什么都一窍不通,它的预测结果基本是随机的。如果让模型用自己的错误预测作为下一步的输入,那么。

闵浮龙的博客 874

DataScience-And-MachineLearning-Handbook-For-Coders:自然语言处理NLP实战,从文本分类到情感分析的完整解决方案

自然语言处理(NLP)是人工智能领域中最具挑战性和实用价值的技术之一。DataScience-And-MachineLearning-Handbook-For-Coders 项目为开发者提供了从基础到实战的完整自然语言处理解决方案,涵盖了文本分类、情感分析、机器翻译等多个核心应用场景。无论你是NLP新手还是希望深入学习的开发者,这个项目都能为你提供实用的指导和代码示例。 ## 🎯 为什么自然语

gitblog_00810的博客 783
上一篇: 人工智能概念之七:集成学习思想(Bagging、Boosting、Stacking)
下一篇: 人工智能概念:RNN中的基础Encoder-Decoder框架
.30-06Springfield
博客等级 码龄7年 321粉丝 38原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值