资讯动态

Transformer自然语言处理实战:法律文本多任务微调与部署

发布时间:2026/10/15 22:30:38 来源:尧图企业网站定制
1. 项目概述为什么“Transformer 自然语言处理四”这个标题值得深挖如果你最近在翻技术社区、刷论文笔记或者只是随手点开几篇NLP方向的教程大概率会看到类似“Transformer 自然语言处理一”“二”“三”这样的系列标题。而“四”这个编号绝不是随便加上的序号——它往往意味着前三讲已经铺完了基础架构、注意力机制原理、位置编码实现和典型模型如BERT、GPT-1/2的结构复现到了第四讲真正要动手干“活儿”了微调、部署、推理优化、长文本适配、领域迁移这些一线工程中绕不开的硬骨头全在这里集中爆发。我带过不少刚从学校过渡到工业场景的开发者他们常卡在这样一个节点能手推Self-Attention公式能画出Encoder-Decoder结构图但一拿到真实业务数据比如客服对话日志、电商商品描述、医疗问诊记录就发现预训练模型直接finetune效果拉胯、显存爆满、推理延迟高得没法上线、甚至模型对领域术语完全“视而不见”。这些问题恰恰是“四”阶段的核心战场。这个标题背后实际指向的是Transformer从学术范式走向生产落地的关键跃迁环节。它不讲“什么是QKV”而是直面“为什么我的LoRA微调后loss震荡剧烈”不解释“为什么需要LayerNorm”而是告诉你“在FP16混合精度下哪一层的Norm参数最容易溢出”不演示“如何加载Hugging Face模型”而是拆解“如何把32GB的Llama-2-70B压缩到8GB显存内稳定跑batch4的实时问答”。关键词“Transformer”“自然语言处理”“四”三者叠加构成一个强信号这是面向实践者的进阶手册不是入门导览。适合三类人一是已跑通BERT微调流程、正准备接手真实NLP项目的工程师二是做毕业设计/科研实验需要把模型真正用起来而非仅复现指标的学生三是技术负责人想快速评估团队当前NLP能力是否具备支撑多轮对话、文档摘要、合规审查等复杂任务的工程水位。接下来的内容不会重复教你怎么写model AutoModel.from_pretrained(bert-base-chinese)也不会堆砌论文里的数学推导。我会以一个真实可复现的端到端案例为锚点——基于中文法律文书的细粒度事实抽取判决结果预测双任务联合微调完整还原从数据清洗、Prompt工程、高效微调策略选择、显存监控、到轻量化部署的每一步。所有代码、配置、参数取值、报错截图文字化还原、以及我踩过的坑都会摊开来讲。你不需要懂PyTorch底层源码但读完能立刻在自己服务器上跑通一套可交付的NLP流水线。2. 整体设计思路与方案选型逻辑2.1 为什么放弃“单任务微调拼接后处理”的老路在“一至三”阶段大家习惯把NLP任务切得极细先用BERT做命名实体识别NER再用另一个模型做关系抽取RE最后用规则或分类器整合结果。这种“管道式”pipeline做法看似清晰实则存在三个致命缺陷误差累积放大NER识别错一个“原告”实体后续所有基于该实体的关系抽取、事件判断全部失效。我们在某次法律文书测试中统计过单任务NER准确率92%RE准确率85%但最终“原告-被告-诉讼请求”三元组整体准确率跌到63%——不是简单相乘而是链式失败。上下文割裂NER模型只看局部token窗口无法感知“本院认为”段落中对前文事实的否定性重述例如“前述XX公司主张……本院不予采纳”。而判决预测需要全局语义一致性判断。工程维护成本高每个模块独立训练、独立部署、独立监控。当上游NER模型更新下游RE必须重新校准阈值整个链路需全量回归测试。所以“四”的设计起点就是强制打破任务边界让模型在一个统一框架内学习多目标协同。我们选择多任务学习Multi-Task Learning, MTL而非级联核心依据有三点共享底层语义表征Transformer的底层Layer0–6主要捕获词法、句法基础特征上层7–12才专注任务特异性。MTL天然共享底层避免重复学习提升小样本领域泛化能力。法律文书标注数据稀缺这点尤为关键。隐式引入归纳偏置判决结果如“驳回诉讼请求”“支持部分诉请”高度依赖事实要素如“违约金约定是否明确”“证据是否充分”的完整性。MTL通过梯度反向传播迫使模型在学习判决标签时自动强化对关键事实片段的关注——这比人工设计注意力mask更鲁棒。部署极简一个模型文件、一套API接口、一次GPU资源申请。运维同学不用半夜被NER服务OOM告警叫醒再去查RE服务的日志。提示有人会问“为什么不直接用大模型做Zero-Shot”——实测在法律垂直场景7B级别模型对“举证责任倒置”“诉讼时效中断”等专业概念的理解仍不稳定且生成不可控。而微调后的768维小模型在限定任务上准确率反超大模型12个百分点且响应时间稳定在350ms内A10 GPU。2.2 为什么选DeBERTa-v3而非RoBERTa或ELECTRA模型主干的选择不是看排行榜SOTA而是看与任务特性的咬合度。我们对比了5个主流中文基座模型在相同数据、相同超参下的验证集表现模型事实抽取F1判决预测Acc显存占用batch8训练速度steps/secRoBERTa-base78.283.114.2 GB2.1ELECTRA-small74.580.39.8 GB3.8DeBERTa-v3-base82.786.915.6 GB1.9MacBERT-base79.184.214.8 GB2.0Chinese-BERT-wwm76.882.514.0 GB2.2DeBERTa-v3胜出的关键在于其增强的解码器注意力机制Enhanced Mask Decoder Attention。传统BERT的MLM任务只预测被mask的token而DeBERTa-v3额外建模了“被mask token与其上下文token的相对位置关系”。法律文书极度依赖指代消解如“其”指代前文哪个主体、长距离依赖如“根据《民法典》第584条……本院认定……”这种显式的位置感知能力直接提升了事实要素边界的识别精度。更关键的是DeBERTa-v3的绝对位置编码Absolute Position Embedding与相对位置编码Relative Position Bias是解耦设计的。这意味着我们在微调时可以单独冻结位置编码层model.embeddings.position_embeddings只训练任务头从而大幅降低过拟合风险——法律文书长度方差极大起诉状平均280字判决书可达12000字固定长度位置编码极易在长文本上失效。注意DeBERTa-v3的官方中文版microsoft/deberta-v3-base-zh并非简单翻译而是基于100GB中文网页法律语料百科文本重新预训练的。其分词器Tokenizer对法律术语如“连带责任保证”“善意取得制度”做了子词合并优化避免出现连##带##责##任这类割裂切分这对实体识别至关重要。2.3 为什么采用“共享Encoder 双任务Head”的结构而非Adapter或LoRA参数高效微调PEFT是“四”的标配但Adapter、LoRA、IA³各有适用场景。我们最终选择共享Encoder 独立任务Head即全参数微调Encoder但Head层从零初始化原因如下法律任务对底层表征敏感度高Adapter通常插入在FFN层后仅微调0.5%参数。但在法律文本中“合同无效”与“合同效力待定”仅一字之差语义鸿沟极大。Adapter的浅层适配无法重构底层语义空间导致判决预测混淆率上升17%。LoRA的秩rank难以设定LoRA通过低秩矩阵分解注入增量权重。我们尝试rank4/8/16在验证集上F1波动达±3.2%且rank4时事实抽取召回率暴跌漏掉大量“利息计算方式”等长尾要素。而法律业务不能接受“偶尔漏掉关键条款”。Head层从零初始化强制Encoder输出通用表征如果Head也继承预训练权重模型会倾向于“走捷径”——用预训练时学过的通用分类模式应付新任务。从零初始化Head相当于给Encoder出了一道考题“你必须输出足够鲁棒的中间表示让我这个新Head能直接学会判案逻辑。”实测该设计使跨案由如借贷纠纷→房屋买卖的泛化准确率提升9.4%。当然全参数微调带来显存压力。我们的应对策略是梯度检查点Gradient Checkpointing 混合精度训练AMP 梯度裁剪Clip Norm1.0。这套组合拳将显存峰值压到15.6GBA10且未观察到数值不稳定。3. 核心细节解析与实操要点3.1 数据构建不是“标注越多越好”而是“标注越准越省”法律文书数据清洗90%的工作量不在模型而在数据。我们拿到的原始数据是某地方法院脱敏后的裁判文书XML包含titlecontentjudgement等标签。但直接喂给模型不行。问题如下噪声污染严重文书含大量页眉页脚“××市中级人民法院民事判决书2023×民终×号”、法官签名区“审判长××× 审判员×××”、空白行、乱码字符OCR识别错误。事实与判决混杂同一段落中“原告主张……”是事实“本院认为……”是推理“判决如下……”是结果。模型若不分段会学习到错误的因果关联如把“原告主张”当成判决依据。标注粒度失衡原始标注只标出“当事人”“案由”“诉讼请求”三级但判决预测需要“违约行为是否成立”“损失金额是否合理”等四级细粒度事实。我们的清洗流水线分四步结构化解析用正则匹配section typefact、section typereason、section typejudgement等自定义标签非原始XML是我们用规则引擎重打的标签。对无标签文书用BiLSTM-CRF模型做篇章分割准确率98.7%将全文切分为“原告诉称”“被告辩称”“法院查明”“法院认为”“判决主文”五块。噪声过滤删除所有含“本页无正文”“此页无正文”的段落用jieba分词停用词表过滤纯符号行如“————————————————————”对连续空格5个的行替换为单个[SEP]标记作为段落分隔符。事实要素标准化我们定义了12类法律事实要素如OBLIGATION_BREACH、EVIDENCE_SUFFICIENCY、INTEREST_CALCULATION并编写规则模板“原告主张被告未按约支付货款构成违约” →OBLIGATION_BREACH: True“被告提交了微信聊天记录及转账凭证” →EVIDENCE_SUFFICIENCY: True“双方约定违约金为日万分之五” →INTEREST_CALCULATION: 0.0005/day规则引擎覆盖83%的常见表述剩余17%由标注员人工校验。重点在于每条事实标注必须附带原文span起始/结束字符索引而非仅打标签。这为后续Span-based NER提供监督信号。判决结果映射原始判决文本如“驳回原告的诉讼请求”“支持原告要求被告支付货款30万元的诉讼请求”需映射为结构化标签。我们设计了3层映射L1判决类型ACCEPT_FULL/ACCEPT_PARTIAL/REJECT/OTHERL2金额相关AMOUNT_SPECIFIED/AMOUNT_UNSPECIFIEDL3法律依据CIVIL_CODE_ART_584/CONTRACT_LAW_ART_113此设计使模型可同时输出粗粒度决策和细粒度依据满足业务系统“可解释性”需求。实操心得不要迷信“大数据”。我们最初用10万份文书训练F1仅79.3%。后来精选3000份高质量标注覆盖借贷、买卖、租赁三大高频案由F1反升至82.7%。法律NLP的核心是“精标”不是“海量”。3.2 Prompt工程不是写指令而是构造“法律思维框架”很多人以为Prompt就是写“请提取以下文本中的当事人和诉讼请求”这在法律场景完全失效。模型需要的不是指令而是法律推理的思维脚手架。我们设计的输入Prompt格式如下以一份借贷纠纷为例[LEGAL_ROLE] 原告张三被告李四 [FACT_CONTEXT] 原告主张2022年3月1日被告向原告借款50万元约定2022年9月1日前归还月利率1.5%。被告仅于2022年6月1日支付利息2.25万元本金至今未还。 [CLAIM] 原告请求1. 被告偿还本金50万元2. 支付利息以50万元为基数自2022年6月2日起至实际清偿之日止按月利率1.5%计算3. 承担本案诉讼费。 [REASONING_FRAME] - 违约行为是否成立 [MASK] - 利息计算方式是否符合《民法典》第680条 [MASK] - 诉讼费承担是否合理 [MASK] [JUDGEMENT_TARGET] [MASK]关键设计点[LEGAL_ROLE]强制角色锚定避免模型混淆“原告主张”与“法院认定”。法律文书本质是多方陈述博弈角色信息是理解前提。[FACT_CONTEXT]与[CLAIM]分离防止模型将“原告请求”误当作“既定事实”。我们实测合并输入时模型对“被告辩称已还款”的识别准确率下降22%。[REASONING_FRAME]是核心创新这不是让模型填空而是显式暴露法律推理链条。每个[MASK]对应一个细粒度事实要素如OBLIGATION_BREACH模型必须先完成事实判断才能输出最终判决。这相当于把“黑箱决策”拆解为“白箱步骤”大幅提升可解释性。[JUDGEMENT_TARGET]用结构化Token不输出自然语言判决而是输出ACCEPT_PARTIALAMOUNT_SPECIFIEDCIVIL_CODE_ART_680这样的Token序列。下游系统可直接解析无需NLU二次处理。注意[MASK]位置必须严格对齐。我们用tokenizer.encode_plus确保每个[MASK]占位符被编码为单个[MASK]tokenid103而非被分词器切碎。否则模型无法学习到“此处需填入布尔值”的语义。3.3 多任务Loss设计不是简单加权而是动态平衡双任务Loss若直接相加Loss α * Loss_NER β * Loss_Class会因任务难度差异导致训练崩塌。事实抽取是序列标注每个token一个label判决预测是句子级分类整段一个label二者梯度量级天然不等。我们采用Uncertainty WeightingKendall et al., 2018动态调整权重$$ \mathcal{L} \frac{1}{2\sigma_1^2}\mathcal{L}{NER} \frac{1}{2\sigma_2^2}\mathcal{L}{Class} \log\sigma_1 \log\sigma_2 $$其中$\sigma_1, \sigma_2$是可学习的标量参数分别代表NER任务和分类任务的“不确定性”。模型会自动学习当NER任务难loss大$\sigma_1$增大降低其loss权重避免拖垮整体训练反之当分类任务置信度高$\sigma_2$减小加大其监督强度。实操中我们将$\sigma_1, \sigma_2$初始化为1.0并添加nn.Parameter到模型中。训练初期$\sigma_1$快速升至1.8说明NER确实更难$\sigma_2$稳定在0.7分类任务更易收敛。最终验证集F1比固定权重α0.5, β0.5高2.3个百分点。提示Uncertainty Weighting需配合梯度归一化。我们对每个任务的梯度单独计算torch.nn.utils.clip_grad_norm_再按权重融合。否则$\sigma$参数会因梯度爆炸而发散。4. 实操过程与核心环节实现4.1 环境搭建与依赖安装避开CUDA版本陷阱我们使用Python 3.9 PyTorch 2.0.1 Transformers 4.30.2。关键避坑点CUDA版本必须严格匹配A10 GPU需CUDA 11.7但torch2.0.1官方wheel仅支持CUDA 11.7/11.8。若误装CUDA 12.x会出现RuntimeError: CUDA error: no kernel image is available for execution on the device。解决方案pip uninstall torch torchvision torchaudio pip install torch2.0.1cu117 torchvision0.15.2cu117 torchaudio2.0.2cu117 -f https://download.pytorch.org/whl/torch_stable.htmlTransformers版本锁定4.30.2修复了DeBERTa-v3在DataCollatorForTokenClassification中的padding bug旧版会导致attention_mask与labels长度不一致。升级到4.31反而引入新的flash_attn兼容问题。安装deepspeed加速可选但强烈推荐pip install deepspeed0.10.3 # 注意deepspeed 0.11 需要PyTorch 2.1与当前环境冲突4.2 模型定义与多任务Head实现核心代码model.pyfrom transformers import DebertaV2Model, DebertaV2PreTrainedModel from torch import nn class LegalMTLModel(DebertaV2PreTrainedModel): def __init__(self, config): super().__init__(config) self.deberta DebertaV2Model(config) # 共享Encoder # 事实抽取HeadSpan-based self.dropout_ner nn.Dropout(config.hidden_dropout_prob) self.ner_classifier nn.Linear(config.hidden_size, config.num_labels_ner) # 12类要素 # 判决预测Head多标签分类 self.dropout_cls nn.Dropout(config.hidden_size * 0.1) # 更小的dropout率 self.cls_pooler nn.Linear(config.hidden_size, config.hidden_size) self.judgement_classifier nn.Linear(config.hidden_size, config.num_labels_judge) # 9类组合标签 # 可学习的Uncertainty参数 self.log_var_ner nn.Parameter(torch.zeros(())) self.log_var_judge nn.Parameter(torch.zeros(())) self.init_weights() def forward( self, input_idsNone, attention_maskNone, labels_nerNone, labels_judgeNone, **kwargs ): outputs self.deberta(input_ids, attention_maskattention_mask) sequence_output outputs.last_hidden_state pooled_output outputs.pooler_output # 事实抽取分支 ner_logits self.ner_classifier(self.dropout_ner(sequence_output)) # 判决预测分支用[CLS] token Pooler pooled_output self.cls_pooler(pooled_output) pooled_output nn.functional.gelu(pooled_output) judge_logits self.judgement_classifier(self.dropout_cls(pooled_output)) loss None if labels_ner is not None and labels_judge is not None: loss_fct_ner nn.CrossEntropyLoss(ignore_index-100) loss_fct_judge nn.BCEWithLogitsLoss() # 多标签用BCE loss_ner loss_fct_ner( ner_logits.view(-1, self.config.num_labels_ner), labels_ner.view(-1) ) loss_judge loss_fct_judge(judge_logits, labels_judge.float()) # Uncertainty Weighting precision_ner torch.exp(-self.log_var_ner) precision_judge torch.exp(-self.log_var_judge) loss ( precision_ner * loss_ner precision_judge * loss_judge self.log_var_ner self.log_var_judge ) return { loss: loss, ner_logits: ner_logits, judge_logits: judge_logits, log_var_ner: self.log_var_ner, log_var_judge: self.log_var_judge }关键细节labels_ner是(batch, seq_len)的整数Tensor-100表示忽略位置如[SEP]、[PAD]labels_judge是(batch, 9)的float Tensor每行是one-hot或multi-hot向量如[1,0,1,0,0,0,0,0,0]self.cls_pooler是额外的非线性变换避免直接用pooled_output导致梯度饱和。4.3 训练脚本与超参配置使用Hugging FaceTrainer但重写compute_loss以支持多任务# trainer.py from transformers import Trainer class MultiTaskTrainer(Trainer): def compute_loss(self, model, inputs, return_outputsFalse): outputs model( input_idsinputs[input_ids], attention_maskinputs[attention_mask], labels_nerinputs.get(labels_ner), labels_judgeinputs.get(labels_judge) ) return (outputs[loss], outputs) if return_outputs else outputs[loss] # training_args.py training_args TrainingArguments( output_dir./legal_mtl_model, num_train_epochs8, per_device_train_batch_size8, # A10显存极限 per_device_eval_batch_size16, warmup_ratio0.1, weight_decay0.01, logging_steps50, save_steps500, evaluation_strategysteps, eval_steps500, load_best_model_at_endTrue, metric_for_best_modeleval_combined_score, # 自定义指标 greater_is_betterTrue, fp16True, # 必开节省显存提速40% gradient_checkpointingTrue, # 必开显存降35% report_tonone, seed42, )关键超参选择依据per_device_train_batch_size8A10 24GB显存下DeBERTa-v3-base的forward峰值显存约14.8GB。batch_size16会OOMbatch_size4则梯度噪声过大loss震荡剧烈。fp16True开启后loss计算从float32转为float16但optimizer.step()仍用float32AMP自动管理。实测训练速度提升1.4倍且未出现梯度下溢inf/nan。gradient_checkpointingTrue在forward时丢弃中间激活值backward时重计算。牺牲20%时间换35%显存绝对划算。warmup_ratio0.1法律任务收敛慢warmup过短如0.05会导致early epoch loss spike模型“学歪”。4.4 推理部署从PyTorch到ONNX再到TensorRT线上服务要求单次请求500msQPS50。PyTorch原生模型无法满足。Step 1导出ONNX# onnx_export.py import torch from model import LegalMTLModel model LegalMTLModel.from_pretrained(./legal_mtl_model/checkpoint-4000) model.eval() dummy_input { input_ids: torch.randint(0, 10000, (1, 512)), attention_mask: torch.ones(1, 512, dtypetorch.long) } torch.onnx.export( model, (dummy_input[input_ids], dummy_input[attention_mask]), legal_mtl.onnx, input_names[input_ids, attention_mask], output_names[ner_logits, judge_logits, log_var_ner, log_var_judge], dynamic_axes{ input_ids: {0: batch, 1: seq}, attention_mask: {0: batch, 1: seq}, ner_logits: {0: batch, 1: seq}, judge_logits: {0: batch} }, opset_version14, do_constant_foldingTrue )Step 2TensorRT优化trtexec --onnxlegal_mtl.onnx \ --saveEnginelegal_mtl.trt \ --fp16 \ --workspace2048 \ --minShapesinput_ids:1x128,attention_mask:1x128 \ --optShapesinput_ids:1x512,attention_mask:1x512 \ --maxShapesinput_ids:1x1024,attention_mask:1x1024 \ --shapesinput_ids:1x512,attention_mask:1x512--fp16启用半精度推理速度提升2.1倍--workspace2048分配2GB显存用于优化避免编译失败dynamic_shapes支持变长输入适配不同长度文书。Step 3FastAPI服务封装# app.py from fastapi import FastAPI import tensorrt as trt import pycuda.driver as cuda import numpy as np app FastAPI() # 加载TRT引擎预加载避免每次请求初始化 engine None context None app.on_event(startup) async def load_engine(): global engine, context with open(legal_mtl.trt, rb) as f: runtime trt.Runtime(trt.Logger(trt.Logger.WARNING)) engine runtime.deserialize_cuda_engine(f.read()) context engine.create_execution_context() # 绑定shape context.set_binding_shape(0, (1, 512)) context.set_binding_shape(1, (1, 512)) app.post(/predict) def predict(text: str): # tokenizer - input_ids, attention_mask inputs tokenizer(text, truncationTrue, paddingmax_length, max_length512, return_tensorsnp) # GPU内存分配 d_input_ids cuda.mem_alloc(inputs[input_ids].nbytes) d_attention_mask cuda.mem_alloc(inputs[attention_mask].nbytes) d_ner_logits cuda.mem_alloc(512 * 12 * 4) # float32 d_judge_logits cuda.mem_alloc(9 * 4) # 同步拷贝 cuda.memcpy_htod(d_input_ids, inputs[input_ids].astype(np.int32)) cuda.memcpy_htod(d_attention_mask, inputs[attention_mask].astype(np.int32)) # 执行推理 bindings [int(d_input_ids), int(d_attention_mask), int(d_ner_logits), int(d_judge_logits)] context.execute_v2(bindings) # 拷贝结果 ner_logits np.empty((1, 512, 12), dtypenp.float32) judge_logits np.empty((1, 9), dtypenp.float32) cuda.memcpy_dtoh(ner_logits, d_ner_logits) cuda.memcpy_dtoh(judge_logits, d_judge_logits) return {ner: ner_logits.tolist(), judgement: judge_logits.tolist()}实测A10单卡QPS达62P99延迟412ms满足SLA。5. 常见问题与排查技巧实录5.1 典型问题速查表问题现象可能原因排查命令/方法解决方案RuntimeError: expected scalar type Half but found FloatAMP与自定义loss不兼容在compute_loss中打印outputs[ner_logits].dtype将loss_fct_ner的ignore_index设为-100int而非-100.0float训练loss持续为nanlog_var参数发散print(model.log_var_ner.item(), model.log_var_judge.item())在Trainer的training_step中添加torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)ONNX导出后TRT推理结果全为0dynamic_axes未正确设置trtexec --onnxmodel.onnx --verbose | grep shape检查--minShapes是否覆盖了最小输入长度如128否则TRT默认用0填充cudaErrorInvalidValuefrom pycudaCUDA context未初始化nvidia-smi确认GPU可见import pycuda.autoinit在app.on_event(startup)中添加pycuda.autoinit.context.detach()后立即pycuda.autoinit.context.attach()微调后模型对“本院认为”段落完全忽略位置编码未冻结print([n for n, p in model.named_parameters() if position in n and p.requires_grad])for name, param in model.named_parameters(): if position_embeddings in name: param.requires_grad False5.2 独家避坑技巧技巧1用torch.compile加速训练PyTorch 2.0在Trainer初始化前加入model torch.compile(model, modereduce-overhead, fullgraphTrue)实测在A10上steps/sec从1.9提升至2.7且不增加显存。注意modereduce-overhead专为小batch优化fullgraphTrue避免动态图开销。技巧2Label Smoothing防过拟合法律判决标签存在主观性如“部分支持”边界模糊直接用CrossEntropyLoss易过拟合。我们在compute_loss中加入loss_ner label_smoothing_loss(ner_logits.view(-1, 12), labels_ner.view(-1), smoothing0.1)其中label_smoothing_loss是自定义函数将真实label概率设为1-smoothing其余类别均分smoothing。验证集F1提升1.2个百分点。技巧3早停Early Stopping的法律特化版标准早停看eval_loss但法律任务loss下降不等于业务指标上升。我们改用双指标早停主指标eval_combined_score 0.6 * eval_ner_f1 0.4 * eval_judge_acc辅助指标eval_fact_coverage模型识别出的关键事实要素占比需人工抽样100条计算只有当主指标连续3轮不升且辅助指标未降才触发早停。避免模型“学乖”——只优化loss不提升事实覆盖率。技巧4显存泄漏终极定位法当nvidia-smi显示显存缓慢上涨怀疑dataloader或tokenizer泄漏import gc import torch # 在每个epoch末尾强制清理 gc.collect() torch.cuda.empty_cache() # 并检查是否有未释放的tensor for obj in gc.get_objects(): try: if torch.is_tensor(obj) and obj.is_cuda: print(type

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑