1

图片

一、引言

在之前的文章中,我们详细阐述了大模型文本审核模型优化项目的前两个阶段。第一阶段通过数据分析与初步优化,成功将误判率从 81.9% 降至 11.47%;第二阶段借助误判分类与提示词工程,进一步将误判率降低至 0%。然而,这些方法也暴露出系统复杂、维护成本高、扩展性差等问题。本文将聚焦于项目的第三阶段 —— 模型微调方案,介绍如何通过训练专门的文本分类模型,为审核系统打造更简洁、高效的长期解决方案。

二、第三阶段:模型微调方案

尽管第二阶段获得了更大的进步和提升,但客户希望探索更简洁、可扩展的解决方案。在第三阶段,我们转向了模型微调方法,通过训练专门的文本分类模型来替代复杂的规则系统。

1. 模型选择与比较

  • 在选择模型时,综合考虑多语言支持、处理长文本能力、推理速度和资源消耗等因素,我们选取了 XLM-RoBERTa Base 和 XLM-RoBERTa Large 两个模型进行对比实验。
  • 模型对比

(1)XLM-RoBERTa Base:

  • 模型参数量:2.7 亿
  • 支持语言:100 种
  • 模型大小:1.1 GB
  • 推理速度:快
  • 资源消耗:适中

(2)XLM-RoBERTa Large:

  • 模型参数量:5.5 亿
  • 理论上具有更强的表达和理解能力
  • 资源消耗:较大
  • 推理速度:相对较慢
  • 部署成本:较高

2. 数据准备与处理

数据质量和处理是模型微调成功的关键。数据来源包括客户提供的标注数据,涵盖多种语言、不同长度和内容类型。处理流程包括数据清洗,去除重复样本、修正标注错误、标准化文本格式、处理特殊字符和编码问题;数据标注,进行二分类标注并交叉验证标注质量;数据增强,对正样本生成轻微变体,增加数据多样性;数据分割,按 80% 训练集、20% 测试集划分,确保分布一致;数据转换,将数据转换为模型所需格式。

2.1 数据来源与构成

本项目采用分阶段数据使用策略,核心开发过程基于脱敏样例数据完成,确保技术方案的通用性和隐私合规性。具体数据构成如下:

  • 开发阶段:使用两类脱敏数据集用于模型训练与方法验证:正向样本集包含脱敏后的合规数据样本,用于训练模型学习符合审核标准的特征模式,帮助模型建立基础判断逻辑;负向样本集涵盖脱敏后的异常或违规数据,辅助模型识别审核不通过的特征,提升模型对异常情况的敏感度。
  • 测试阶段:由客户使用自有真实数据进行独立验证。

2.2 数据预处理

在模型训练之前,数据预处理是至关重要的步骤,它直接关系到后续模型微调的效果和性能。以下是具体的数据预处理代码:

def preprocess_data():
    approve_file = '*'
    reject_file = '*'
    test_approve_file = '*'
    test_reject_file = '*'
    print("Reading and labeling training data...")
    
    approve_df = read_and_label_data(approve_file, 1)
    reject_df = read_and_label_data(reject_file, 0)
    train_df = pd.concat([approve_df, reject_df], ignore_index=True)
    train_df = train_df.sample(frac=1, random_state=42).reset_index(drop=True)
    print("Reading and labeling test data...")
    
    test_approve_df = read_and_label_data(test_approve_file, 1)
    test_reject_df = read_and_label_data(test_reject_file, 0)
    test_df = pd.concat([test_approve_df, test_reject_df], ignore_index=True)
    test_df = test_df.sample(frac=1, random_state=42).reset_index(drop=True)
    os.makedirs('../data', exist_ok=True)
    print("Saving processed data...")
    
    train_df.to_csv('*/train.csv', index=False)
    test_df.to_csv('*/datatest.csv', index=False)
    
    print(f"Training data shape: {train_df.shape}")
    print(f"Test data shape: {test_df.shape}")
    print("Data preprocessing completed!")

上述代码实现了一个系统化的数据预处理流程,具体步骤如下:

  • 文件路径定义:明确指定了用于训练和测试的正、负样本文件路径,为后续的数据读取操作提供了清晰的指引。
  • 训练数据读取与标注:分别读取正、负样本训练数据文件,并为其添加相应的标签(正样本标记为 1,负样本标记为 0)。这一步骤确保了训练数据具有明确的分类信息,便于模型学习。
  • 训练数据合并与打乱:将正、负样本训练数据合并为一个数据集,并进行随机打乱操作,以保证数据的随机性和代表性。
  • 测试数据读取与标注:与训练数据处理类似,读取正、负样本测试数据文件并添加标签。
  • 测试数据合并与打乱:将正、负样本测试数据合并并打乱,确保测试数据的分布均匀,能够准确评估模型的泛化能力。
  • 数据保存:将处理好的训练数据和测试数据分别保存为 CSV 文件,方便后续模型训练和评估使用。同时,代码会输出训练数据和测试数据的形状信息,便于用户了解数据的规模和结构。

通过这种系统化的数据处理流程,我们有效地确保了训练数据的质量,为后续的模型微调工作奠定了基础。这不仅有助于提高模型的训练效果,还能增强模型在实际应用中的泛化能力和稳定性。

3. 模型训练架构与流程

(1)二分类模型架构设计:为有效应对文本审核任务,我们以 XLM – RoBERTa 预训练模型为基础,构建了一个经过特定优化的二分类模型。以下是该模型的具体实现代码:

class TextClassificationModel(nn.Module):
    def __init__(self, model_name, num_classes=2, dropout_rate=0.1):
        super(TextClassificationModel, self).__init__()
        self.encoder = XLMRobertaModel.from_pretrained(model_name)
        
        self.feature_extractor = nn.Sequential(
            nn.Linear(self.encoder.config.hidden_size, 512),
            nn.ReLU(),
            nn.Dropout(dropout_rate)
        )
        
        self.classifier = nn.Linear(512, num_classes)
        self._init_weights(self.feature_extractor)
        self._init_weights(self.classifier)
    
    def _init_weights(self, module):
        if isinstance(module, nn.Linear):
            module.weight.data.normal_(mean=0.0, std=0.02)
            if module.bias is not None:
                module.bias.data.zero_()
        
    def forward(self, input_ids, attention_mask, token_type_ids=None, labels=None):
        outputs = self.encoder(
            input_ids=input_ids,
            attention_mask=attention_mask
        )
        sequence_output = outputs.last_hidden_state[:, 0, :]
        features = self.feature_extractor(sequence_output)
        logits = self.classifier(features)
        loss = None
        if labels is not None:
            loss_fct = nn.CrossEntropyLoss()
            loss = loss_fct(logits.view(-1, 2), labels.view(-1))
            
        return ModelOutput(
            loss=loss,
            logits=logits
        )

这个模型架构具备以下特点:

  1. 广泛的多语言支持:以 XLM – RoBERTa 作为基础模型,该模型能够支持 100 多种语言,确保在处理不同语言文本审核任务时具备良好的通用性和适应性。
  2. 强化的特征提取能力:额外添加了特征提取层,通过非线性变换和 Dropout 机制,增强了模型对文本特征的表达能力,有助于提升模型的分类性能。
  3. 适配的权重初始化策略:采用了适合 Transformer 模型的初始化方法对线性层的权重进行初始化,有助于模型在训练初期保持稳定的收敛状态,加快训练速度。
  4. 灵活的输入兼容性:模型设计兼容不同格式的输入数据,能够适应多样化的文本输入场景,为实际应用提供了更大的灵活性。
  5. 结构化的输出设计:使用 ModelOutput 类提供结构化输出,将模型的预测结果(logits)和损失值(loss)进行清晰的封装,便于后续的评估、分析和处理工作。

(2)核心训练代码

在模型训练过程中,train_epoch 函数承担了一个训练周期(epoch)的核心计算任务,其代码实现如下:

def train_epoch(model, data_loader, optimizer, scheduler, device):
    model.train()
    losses = []
    progress_bar = tqdm(data_loader, desc="Training")
    for batch in progress_bar:
        optimizer.zero_grad()
        input_ids = batch['input_ids'].to(device)
        attention_mask = batch['attention_mask'].to(device)
        token_type_ids = batch.get('token_type_ids', None)
        if token_type_ids is not None:
            token_type_ids = token_type_ids.to(device)
        labels = batch['label'].to(device)
        outputs = model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            token_type_ids=token_type_ids,
            labels=labels
        )
        loss = outputs.loss
        losses.append(loss.item())
        loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        optimizer.step()
        scheduler.step()
        progress_bar.set_postfix({'loss': f'{loss.item():.4f}'})    
    return np.mean(losses)

该函数接收模型、数据加载器、优化器、学习率调度器以及计算设备作为输入参数。在训练过程中,它首先将模型设置为训练模式,初始化损失列表和进度条。在每个训练批次中,函数执行梯度清零操作,将数据转移到指定设备上,进行前向传播计算模型输出和损失值,接着通过反向传播计算梯度,利用梯度裁剪防止梯度爆炸,然后更新模型参数并调整学习率,同时更新进度条以展示训练进度。最后,函数返回当前训练周期的平均损失值,为模型训练效果的评估提供重要依据。

(3)训练主流程

完整的模型训练主流程涵盖了模型初始化、数据加载、训练循环以及模型保存等关键步骤,由 train_model 函数实现,具体如下:

def train_model(args):
    set_seed(42)
    train_df = pd.read_csv(args.train_file)
    test_df = pd.read_csv(args.test_file)
    tokenizer = XLMRobertaTokenizer.from_pretrained(args.model_name)
    train_dataset = TextClassificationDataset(
        train_df['text'].values,
        train_df['label'].values,
        tokenizer,
        max_length=args.max_length
    )
    
    test_dataset = TextClassificationDataset(
        test_df['text'].values,
        test_df['label'].values,
        tokenizer,
        max_length=args.max_length
    )
  
    train_loader = DataLoader(
        train_dataset,
        batch_size=args.batch_size,
        shuffle=True,
        num_workers=4
    )
    
    test_loader = DataLoader(
        test_dataset,
        batch_size=args.batch_size,
        shuffle=False,
        num_workers=4
    )
    
    model = TextClassificationModel(args.model_name, num_classes=2)
    model.to(device)
    optimizer = AdamW(
        model.parameters(),
        lr=args.learning_rate,
        weight_decay=args.weight_decay
    )
    total_steps = len(train_loader) * args.epochs
    scheduler = get_linear_schedule_with_warmup(
        optimizer,
        num_warmup_steps=args.warmup_steps,
        num_training_steps=total_steps
    )
    writer = SummaryWriter(log_dir=f'runs/{args.model_name}')

    best_accuracy = 0
    early_stopping_counter = 0
    
    for epoch in range(args.epochs):
        print(f"\nEpoch {epoch + 1}/{args.epochs}")

        train_loss = train_epoch(model, train_loader, optimizer, scheduler, device)
        print(f"Training loss: {train_loss:.4f}")
        writer.add_scalar('Loss/train', train_loss, epoch)
        metrics, cm = evaluate(model, test_loader, device)
        for key, value in metrics.items():
            print(f"{key}: {value:.4f}")
            writer.add_scalar(f'Metrics/{key}', value, epoch)
        if metrics['accuracy'] > best_accuracy:
            best_accuracy = metrics['accuracy']
            early_stopping_counter = 0
            print(f"New best accuracy: {best_accuracy:.4f}. Saving model...")
            model_path = os.path.join(args.output_dir, 'best_model')
            os.makedirs(model_path, exist_ok=True)
            model.save_pretrained(model_path)
            tokenizer.save_pretrained(model_path)
            with open(os.path.join(model_path, 'training_args.json'), 'w') as f:
                json.dump(vars(args), f, indent=2)
                
            plot_confusion_matrix(cm, ['Reject', 'Approve'], 
                                 os.path.join(model_path, 'confusion_matrix.png'))
        else:
            early_stopping_counter += 1
            
        if early_stopping_counter >= args.patience:
            print(f"Early stopping triggered after {epoch + 1} epochs")
            break
    
    print("\nTraining completed!")
    print(f"Best accuracy: {best_accuracy:.4f}")
    
    return model_path

在这个函数中,首先设置随机种子以保证实验结果的可重复性。接着加载训练数据和测试数据,初始化分词器、数据集和数据加载器,为模型训练提供数据支持。随后初始化模型、优化器和学习率调度器,配置训练环境。利用 TensorBoard 初始化日志记录工具,用于跟踪训练过程中的各项指标。在训练循环中,依次进行训练和评估操作,计算并记录训练损失和评估指标。如果当前模型在测试集上的准确率优于之前的最佳准确率,则保存当前模型及其相关参数,同时保存混淆矩阵。若连续多个训练周期模型准确率没有提升,触发早停策略,提前结束训练。最终返回保存最佳模型的路径,完成整个训练流程。

(4)训练参数与优化策略

通过大量实验对比,我们确定了一套最佳的训练参数组合,具体如下:

image.png

本次训练使用到了SageMaker Training Job, SageMaker Training Job 是亚马逊云服务 SageMaker 的核心组件,专为机器学习模型训练提供全方位支持。它不仅支持用户从内置算法库中灵活选择适配的算法,还能兼容 TensorFlow、PyTorch 等主流框架,满足多样化的训练需求。其资源配置极为灵活,无论是 CPU 还是 GPU 实例都能按需调配,特别在处理大规模数据训练时,分布式训练能力可显著缩短训练周期。而且 SageMaker Training Job 具备智能容错机制,当使用 Spot 实例训练时,若遇资源回收中断,系统会自动从最近的检查点恢复训练,确保进度不丢失。Warm Pool 功能则通过维护预热资源池,大幅减少新训练任务的启动等待时间。此外也可使用自带 Estimator 镜像,将自定义算法和依赖打包部署,进一步提升训练的灵活性。在训练过程中,实时监控功能可动态跟踪损失函数、准确率等关键指标,结合断点续训能力,为模型的高效迭代优化提供有力保障。

4. 微调模型测试性能对比

4.1 对比分析

在完成模型微调后,对 XLM – RoBERTa Base 和 XLM – RoBERTa Large 两个模型在测试集上进行了全面的性能评估,详细对比如下:

image.png

image.png

image.png

4.2 单个模型测试结果分析

1. 测试结果分析(Base 模型):

  • 准确率(Accuracy):94.95% 的样本被正确分类,表明模型整体表现优秀。
  • 精确率(Precision):92.21%,表示在模型预测为”通过”的样本中,真正应该通过的比例。这个指标较高,说明模型在判定通过时较为严格,误判率较低。
  • 召回率(Recall):98.20%,表示在所有实际应该通过的样本中,被模型正确识别的比例。这个指标非常高,说明模型几乎能捕捉到所有应该通过的样本,漏判率极低。
  • F1 分数:95.11%,精确率和召回率的调和平均,表明模型在精确率和召回率之间取得了很好的平衡。
  • 混淆矩阵分析 :

(1)真阴性(TN):917 个样本被正确预测为”拒绝”

(2)假阳性(FP):83 个实际应”拒绝”的样本被误判为”通过”

(3)假阴性(FN):18 个实际应”通过”的样本被误判为”拒绝”

(4)真阳性(TP):982 个样本被正确预测为”通过”

image.png

2. 测试结果分析(Large 模型):

  • 准确率(Accuracy):91.90%,虽然仍然很高,但低于 Base 模型。
  • 精确率(Precision):88.44%,明显低于 Base 模型,表明 Large 模型在判定”通过”时更为宽松,误判更多。
  • 召回率(Recall):96.40%,略低于 Base 模型,但仍然保持在很高水平。
  • F1 分数:92.25%,低于 Base 模型,表明整体性能略逊。
  • 混淆矩阵分析:

(1)真阴性(TN):874 个样本被正确预测为”拒绝”

(2)假阳性(FP):126 个实际应”拒绝”的样本被误判为”通过”

(3)假阴性(FN):36 个实际应”通过”的样本被误判为”拒绝”

(4) 真阳性(TP):964 个样本被正确预测为”通过”

与 Base 模型相比,Large 模型在两类错误上都表现更差,特别是假阳性错误(FP=126 vs 83)。

image.png

5. 性能差异原因分析

针对两个模型的性能差异,我们展开了深入剖析。经计算,Base 模型相较于 Large 模型,平均相对提升达到 3.04%。这一结果与通常认为 “更大模型更好” 的直觉相悖,主要原因归纳为以下几点可能因素:

(1)过拟合因素:在测试集上,Large 模型的表现逊于 Base 模型。这一现象暗示 Large 模型可能过度拟合了训练数据,导致其在面对新的测试数据时,泛化能力不足,无法准确做出判断。

(2)任务复杂度匹配:文本审核本质上属于二分类任务,相对而言复杂度较低。Base 模型的复杂度恰到好处,足以有效捕捉该任务所需的模式特征。而 Large 模型额外的复杂度不仅未带来性能提升,反而可能引入了不必要的干扰因素,影响了模型的判断准确性。

(3)数据规模因素:Large 模型要充分发挥其潜力,通常需要大量的数据支持。然而,在本项目中训练数据规模相对有限,这种情况下,较小的 Base 模型反而展现出更好的适应性,能够在有限数据条件下达到更优的性能表现。

(4)资源效率对比:在资源消耗方面,两个模型也存在显著差异。Base 模型训练时间为 17 小时(单 GPU),Large 模型训练时间为 15 小时(单 GPU)。微调后,Base 模型内存占用为 1.2GB,而 Large 模型内存占用高达 2.2GB。综合来看,Base 模型在资源利用效率上更具优势。

三、亚马逊云科技参考架构图

a3da5c4d963e6e0c99e3e0f60d465f0.png

训练及推理执行过程说明:

1. 数据上传与存储:

(1)用户上传训练和推理数据到 Amazon S3 存储桶;

(2)S3 存储桶用于存储中间结果和模型文件,按照不同前缀对文件进行区分。

2. 数据处理:在 Amazon SageMaker Notebook 实例上使用 Pre-Process 脚本对数据进行预处理。

3. 模型训练:在 SageMaker 中进行模型训练,训练完成后模型存储在 S3 存储桶中。

4. 模型推理:使用训练好的模型在 SageMaker Notebook 本地进行推理;实际应用中,也可将模型部署到 SageMaker 推理端点。

5. 任务结果通知:使用 Amazon SNS 通知将推理任务结果通过 Email 发送给用户。

四、结论与建议

通过三阶段的项目实践,我们成功将文本审核系统的误判率从 80% 以上大幅降低至接近 5%,同时深入探索了不同技术路径的优势与不足。每个阶段都发挥了独特且关键的作用:

  1. 段(数据分析与初步优化):运用定量分析方法,精准识别出主要误判来源,其中第三步审核的误判占比高达 45%。随后通过对提示词的优化调整,成功将误判率从 81.9% 降至 11.47%,初步验证了提示词工程在优化审核系统方面的有效性,但也暴露出其存在一定的局限性。
  2. 第二阶段(系统化提示词工程):构建了一套完整的误判分类和深度分析流程,通过精细化的提示词设计,将误判率进一步降低至 0%。然而,这种方法导致系统复杂度显著增加,随之而来的是高昂的维护成本。
  3. 第三阶段(模型微调方法):选用 XLM – RoBERTa Base 模型进行微调,最终实现了 95% 的准确率。这一结果有力证明了在特定场景下,较小规模的模型(2.7 亿参数)在性能上优于大规模模型(5.5 亿参数),为项目提供了简洁、高效的长期解决方案。

基于项目所取得的成果,我们提出以下建议:

  1. 案作为生产环境解决方案:推荐在生产环境中使用 XLM – RoBERTa Base 模型。该模型单次推理即可完成审核,处理时间仅为 5 – 15ms / 样本,极大地提高了审核效率。并且由于无需持续进行 API 调用,有效降低了运营成本,具备显著的成本效益优势。
  2. 保持人工审核补充机制:为确保审核结果的准确性,应保留人工审核作为补充手段。对模型判定为拒绝的内容进行人工复核,将人工审核结果反馈至训练数据中,以此实现系统的持续优化,不断提升审核系统的性能。
  3. 定期评估和更新:建立定期评估和更新机制,每月对系统表现和误判情况进行深入分析。同时,积极收集新的训练数据,用于模型更新迭代,持续监控不同语言和文化背景下的审核效果,以保证审核系统始终保持良好的性能状态。

本项目不仅成功解决了客户面临的实际问题,还为类似的内容审核系统提供了极具参考价值的优化思路和实践路径。通过综合运用数据分析、提示词工程和模型微调等技术手段,逐步提升系统性能,最终达成业务目标,为行业发展提供了有益的借鉴。

五、未来展望

随着自然语言处理技术的不断演进以及业务场景的日益复杂,文本审核系统的优化之路永无止境。此次项目的成功仅是一个起点,为未来的探索奠定了坚实基础。

在模型优化方面,虽然 XLM-RoBERTa Base 模型在当前表现出色,但仍有提升空间。未来我们将持续关注前沿研究成果,探索更先进的模型架构和训练方法。例如,尝试将新兴的多模态融合技术融入模型,使其不仅能处理文本信息,还能结合图像、语音等多模态数据进行综合审核,进一步提升审核的准确性和全面性。同时,随着数据的不断积累,我们计划采用更高效的增量学习算法,让模型能够自动学习新出现的语言模式和审核规则,减少人工干预,提高系统的自适应性和智能水平。

*前述特定亚马逊云科技生成式人工智能相关的服务仅在亚马逊云科技海外区域可用,亚马逊云科技中国仅为帮助您了解行业前沿技术和发展海外业务选择推介该服务。

本篇作者

image.png


亚马逊云开发者
3k 声望9.6k 粉丝

亚马逊云开发者社区是面向开发者交流与互动的平台。在这里,你可以分享和获取有关云计算、人工智能、IoT、区块链等相关技术和前沿知识,也可以与同行或爱好者们交流探讨,共同成长。