当前位置: 首页 > news >正文

基于LSTM的文本分类3——模型训练

前言

之前已经完成了模型搭建和文本数据处理,现在做一下模型训练。

源码

# -*- coding: UTF-8 -*-
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from sklearn import metrics  # 导入评估指标
import time
from utils import get_time_dif  # 自定义时间计算工具

# 权重初始化函数
def init_network(model, method='xavier', exclude='embedding', seed=123):
    """初始化神经网络权重
    Args:
        model: 待初始化模型
        method: 初始化方法,可选xavier/kaiming/normal
        exclude: 不需要初始化的层名称包含的关键字
        seed: 随机种子
    """
    # 设置随机种子保证可重复性
    torch.manual_seed(seed)
    np.random.seed(seed)
    torch.cuda.manual_seed_all(seed)
    torch.backends.cudnn.deterministic = True  # 保证CUDA卷积运算结果确定
    
    # 遍历模型所有参数
    for name, w in model.named_parameters():
        if exclude not in name:  # 排除指定层的参数
            if 'weight' in name:
                # 权重初始化策略
                if method == 'xavier':
                    nn.init.xavier_normal_(w)  # Xavier正态分布初始化
                elif method == 'kaiming':
                    nn.init.kaiming_normal_(w)  # Kaiming正态分布初始化
                else:
                    nn.init.normal_(w)  # 普通正态分布初始化
            elif 'bias' in name:
                nn.init.constant_(w, 0)  # 偏置项初始化为0
            else:
                pass  # 其他参数保持默认

def train(config, model, train_iter, dev_iter, test_iter, writer):
    """模型训练函数
    Args:
        config: 配置参数对象
        model: 待训练模型
        train_iter: 训练数据迭代器
        dev_iter: 验证数据迭代器
        test_iter: 测试数据迭代器
        writer: TensorBoard写入对象
    """
    start_time = time.time()
    model.train()  # 设置模型为训练模式
    optimizer = torch.optim.Adam(model.parameters(), lr=config.learning_rate)  # Adam优化器

    # 初始化训练状态跟踪变量
    total_batch = 0  # 全局batch计数器
    dev_best_loss = float('inf')  # 最佳验证集损失
    last_improve = 0  # 上一次提升的batch数
    flag = False  # 早停标志

    # 训练主循环
    for epoch in range(config.num_epochs):
        print('Epoch [{}/{}]'.format(epoch + 1, config.num_epochs))
        
        # 遍历训练数据
        for i, (trains, labels) in enumerate(train_iter):
            outputs = model(trains)  # 前向传播
            model.zero_grad()  # 梯度清零
            loss = F.cross_entropy(outputs, labels)  # 计算交叉熵损失
            loss.backward()  # 反向传播
            optimizer.step()  # 参数更新

            # 每100个batch进行验证和记录
            if total_batch % 100 == 0:
                # 计算训练集准确率
                true = labels.data.cpu()
                predic = torch.max(outputs.data, 1).cpu()
                train_acc = metrics.accuracy_score(true, predic)

                # 在验证集评估
                dev_acc, dev_loss = evaluate(config, model, dev_iter)

                # 保存最佳模型
                if dev_loss < dev_best_loss:
                    dev_best_loss = dev_loss
                    torch.save(model.state_dict(), config.save_path)  # 保存模型参数
                    last_improve = total_batch  # 更新最后提升位置
                    improve = '*'  # 标记有提升
                else:
                    improve = ''

                # 打印训练信息
                time_dif = get_time_dif(start_time)
                msg = 'Iter: {0:>6}, Train Loss: {1:>5.2}, Train Acc: {2:>6.2%}, Val Loss: {3:>5.2}, Val Acc: {4:>6.2%}, Time: {5} {6}'
                print(msg.format(total_batch, loss.item(), train_acc, dev_loss, dev_acc, time_dif, improve))

                # 记录TensorBoard数据
                writer.add_scalar("loss/train", loss.item(), total_batch)
                writer.add_scalar("loss/dev", dev_loss, total_batch)
                writer.add_scalar("acc/train", train_acc, total_batch)
                writer.add_scalar("acc/dev", dev_acc, total_batch)

                model.train()  # 重置模型为训练模式(evaluate会设为eval模式)

            total_batch += 1  # 更新全局batch计数

            # 早停检查(超过指定batch数没有提升)
            if total_batch - last_improve > config.require_improvement:
                print("No optimization for a long time, auto-stopping...")
                flag = True
                break
        if flag:
            break  # 终止外层循环

    writer.close()  # 关闭TensorBoard写入器
    test(config, model, test_iter)  # 训练结束后进行最终测试

def test(config, model, test_iter):
    """模型测试函数
    Args:
        config: 配置参数对象
        model: 已训练模型
        test_iter: 测试数据迭代器
    """
    # 加载最佳模型参数
    model.load_state_dict(torch.load(config.save_path))
    model.eval()  # 设置评估模式
    start_time = time.time()
    
    # 在测试集上评估
    test_acc, test_loss, test_report, test_confusion = evaluate(config, model, test_iter, test=True)
    
    # 输出测试结果
    msg = 'Test Loss: {0:>5.2}, Test Acc: {1:>6.2%}'
    print(msg.format(test_loss, test_acc))
    print("Precision, Recall and F1-Score...")
    print(test_report)  # 分类报告(精确率、召回率、F1值)
    print("Confusion Matrix...")
    print(test_confusion)  # 混淆矩阵
    
    # 打印时间消耗
    time_dif = get_time_dif(start_time)
    print("Time usage:", time_dif)

def evaluate(config, model, data_iter, test=False):
    """模型评估函数
    Args:
        config: 配置参数
        model: 待评估模型
        data_iter: 数据迭代器
        test: 是否为测试模式
    Returns:
        测试模式:返回准确率、损失、分类报告、混淆矩阵
        验证模式:返回准确率、损失
    """
    model.eval()  # 设置评估模式
    loss_total = 0
    predict_all = np.array([], dtype=int)
    labels_all = np.array([], dtype=int)

    with torch.no_grad():  # 禁用梯度计算
        for texts, labels in data_iter:
            outputs = model(texts)
            loss = F.cross_entropy(outputs, labels)
            loss_total += loss  # 累计损失
            
            # 转换数据为numpy数组
            labels = labels.data.cpu().numpy()
            predic = torch.max(outputs.data, 1).cpu().numpy()
            
            # 收集预测结果和真实标签
            labels_all = np.append(labels_all, labels)
            predict_all = np.append(predict_all, predic)

    # 计算整体准确率
    acc = metrics.accuracy_score(labels_all, predict_all)
    
    # 测试模式返回详细报告
    if test:
        report = metrics.classification_report(
            labels_all, predict_all, 
            target_names=config.class_list,  # 类别名称
            digits=4  # 小数点精度
        )
        confusion = metrics.confusion_matrix(labels_all, predict_all)
        return acc, loss_total / len(data_iter), report, confusion
    
    return acc, loss_total / len(data_iter)  # 验证模式返回平均损失

 权重初始化

def init_network(model, method='xavier', exclude='embedding', seed=123):
    for name, w in model.named_parameters():
        if exclude not in name:
            if 'weight' in name:
                if method == 'xavier':
                    nn.init.xavier_normal_(w)
                elif method == 'kaiming':
                    nn.init.kaiming_normal_(w)
                else:
                    nn.init.normal_(w)
            elif 'bias' in name:
                nn.init.constant_(w, 0)

不同网络层的初始化策略

层类型推荐初始化方法适用场景
全连接层Xavier普通前馈网络
卷积层Kaiming使用ReLU激活的CNN
Embedding层保留预训练参数迁移学习场景

 在代码里将embedding层不做处理,仅处理其他层参数。

前向与反向传播

outputs = model(trains)  # 前向传播
model.zero_grad()  # 梯度清零
loss = F.cross_entropy(outputs, labels)  # 计算交叉熵损失
loss.backward()  # 反向传播
optimizer.step()  # 参数更新

在每一个批次中左前向传播与反向传播,需要注意在反向传播前要做梯度清零,避免收到上一批次的梯度影响。

训练监控

            if total_batch % 100 == 0:
                # 每多少轮输出在训练集和验证集上的效果
                true = labels.data.cpu()
                predic = torch.max(outputs.data, 1)[1].cpu()
                train_acc = metrics.accuracy_score(true, predic)
                dev_acc, dev_loss = evaluate(config, model, dev_iter)

每一百个批次的训练之后要进行一次模型评估(调用评估函数),将最好的网络参数保留下来。

评估函数

def evaluate(config, model, data_iter, test=False):
    loss_total = 0
    predict_all = np.array([])
    labels_all = np.array([])
    
    with torch.no_grad():
        for texts, labels in data_iter:
            outputs = model(texts)
            loss = F.cross_entropy(outputs, labels)
            # 结果收集
            labels_all = np.append(labels_all, labels.cpu())
            predic = torch.max(outputs, 1).cpu()
            predict_all = np.append(predict_all, predic)
    
    acc = metrics.accuracy_score(labels_all, predict_all)
    if test:
        report = metrics.classification_report(labels_all, predict_all)
        confusion = metrics.confusion_matrix(labels_all, predict_all)
        return acc, loss_total/len(data_iter), report, confusion
    return acc, loss_total/len(data_iter)

评估函数里有一句代码比较别扭,是:

predic = torch.max(outputs.data, 1)[1].cpu().numpy()

这一句代码做了不少事情,需要拆分做分析。

首先outputs.data是模型输出的原始张量。

从断点中看,output的张量维度是(128,10),其中128是该批次的数据量,10是某个数据在10分类任务中每个分类中的得分。

torch.max函数在这里的作用是找出每个样本预测得分最高的类别。

torch.max有两个参数,第一个是输入张量,第二个是维度。这里维度设为1,表示在每一行(即每个样本)的各个类别中找最大值。torch.max返回的是一个元组,包含两个张量:第一个是最大值,第二个是最大值的索引。

取第二个元组,里面就是每个数据的分类预测值。

使用np.array将张量转换成数组,在pycharm里面点击“作为Array查看”,可以直观看到128个数据的分类预测值。

 

充电-torch.max

补习下torch.max的知识

torch.max(input, dim=None, keepdim=False, out=None)
参数类型作用
inputTensor输入张量(必选)
dimint (可选)指定计算维度,默认返回全局最大值
keepdimbool (可选)是否保持输出维度与原张量一致,默认False
outtuple (可选)输出元组 (max_values, max_indices)

 torch.max主要支持以下玩法:

  • 全局最大值‌:返回张量中所有元素的最大值
  • 维度最大值‌:沿指定维度计算最大值及对应索引

求全局最大值

x = torch.tensor([[1, 5], [8, 2]])
max_val = torch.max(x)  # 返回 tensor(8) 

可以看到,torch.max最简单的用法就是求出一个张量里的最大元素。

求维度最大值

x = torch.tensor([[1, 5], [8, 2]])
values, indices = torch.max(x, dim=1)
# values = tensor([5, 8]), indices = tensor([1, 0]) ‌

上面的代码遵循维度计算规则,元组的第一个元素是每行最大值的集合,分别是5和8;元组的第二个元素是每行最大值在该行的序号,即0和1。

维度计算规则

对 ‌N维张量‌ 的维度解释(以三维张量 (B, C, H) 为例):

dim计算方向输出形状
0沿批次维度B计算(C, H)
1沿通道维度C计算(B, H)
2沿空间维度H计算

(B, C)

二维张量中,dim=0是按列计算,dim=1是按行计算。将dim=1,意思是求二维张量每行的最大值。

相关文章:

  • 如何实现元素随滚动平滑上升
  • C++ 数据结构优化实战:突破性能瓶颈,提升应用效率
  • 无数字字母RCE
  • 【智能设备-点数据问题排查】
  • JavaScript函数柯里化
  • kubectl修改资源时添加注解
  • Vim 使用全攻略:从入门到精通
  • 蓝牙测试中 PRBS9 数据包类型
  • Docker Swarm 集群
  • 信息安全管理与评估2019年国赛正式卷以及十套国赛卷答案截图
  • 机器学习的一百个概念(12)学习率
  • VisionTransformer 有效涨点改进:添加Star_Block模块 (2024改进方法)
  • 【01】Arduino编程基础知识
  • 音视频学习(三十三):GOP详解
  • mac安装python
  • 五、adb常用命令
  • 基于web的民宿信息系统(源码+lw+部署文档+讲解),源码可白嫖!
  • 中间件--ClickHouse-2--OLAP和OLTP
  • c++:构造函数(Constructor)与析构函数(Destructor)
  • 基于 LSTM 的多特征序列预测-SHAP可视化!
  • 找网络公司建网站每年收维护费/必应搜索推广
  • 红色ppt模板免费下载网站/关键词竞价排名
  • 网上购物的网站有哪些/推广软文200字
  • 泰安诚信的企业建站公司/全网投放广告的渠道有哪些
  • 比较好的做外贸网站/做推广哪个平台好
  • python做网站有优势/seo主要是指优化