☰
PyTorch加载BioBERT实现电子病历实体关系抽取实战
2026/9/30 2:58:09 网站建设 项目流程

简介:面向医疗文本分析与自然语言处理初学者,这份PDF资源以PyTorch与BioBERT为基础,完整讲解电子病历实体关系抽取中的迁移学习应用,适合希望在实际项目中快速上手预训练模型微调的开发者参考。资源以单个PDF文件形式提供,大小仅273KB,内容覆盖环境搭建、数据清洗、分词标注转换、训练集验证集测试集划分,以及通过transformers库加载BioBERT模型并进行序列分类微调等环节,并配有可直接运行的PyTorch代码示例。作者结合电子病历场景,具体演示了从分词器调用、文本编码、自定义数据集类到模型训练循环的完整流程,能够帮助读者理解迁移学习如何减少对大规模标注数据的依赖,并提升医疗文本中实体与关系的抽取效果。目前该资料已有129人学习,文档结构清晰、篇幅精简,既适合初学者按步骤复现实验,也可作为医疗NLP项目起步时的参考资料。

1. 医疗文本分析实战:为什么 BioBERT 迁移学习是电子病历实体关系抽取的最短路径

电子病历里躺着大量未被利用的信息:诊断、用药、检查结果、过敏史,都散落在半结构化文本中。要把这些文本变成可查询的知识,核心工作是做实体关系抽取——先捞出疾病、症状、药物这些实体,再判断它们之间的关系。医疗文本分析实战里最常用的落地路线,就是用 PyTorch 加载 BioBERT 做迁移学习:BioBERT 在生物医学语料上预训练过,直接在电子病历上微调,比从零训练收敛快得多,效果也稳。这篇文章把我实际跑通这套方案的过程写出来,包括标注格式、模型结构、训练参数和几个容易绕进去的坑。准备入手的读者可以照着一步步搭,已经在做的可以对比一下自己的边界设定。

2. 从 BioBERT 到 PyTorch:选型原理与迁移学习的两个关键设定

2.1 BioBERT 相比通用 BERT 在医学文本上的三个优势

BioBERT 不是一个新的模型架构,而是在 BERT 基础上用 PubMed 摘要和 PMC 全文做了继续预训练。它的网络结构和 BERT base 完全一致:12 层 Transformer、768 维隐藏状态、约 1.1 亿参数。差别全在权重上——BioBERT 在训练时见过的医学文本远多于通用 BERT,所以它对“心梗”“房颤”“阿司匹林”这些词的语义表示更准确,而这恰恰是电子病历里的高频词。

具体到电子病历场景,BioBERT 有三个可感知的优势。第一是词汇覆盖更好。通用 BERT 训练时见过的医学词有限,遇到“阿托伐他汀钙片”这种专业名词,切出来的子词分布会很碎,语义信息被稀释。BioBERT 在医学语料里见过大量这类词的上下文,切分后每个子词携带的信息更集中。第二是上下文表示更精准。医生写病历大量使用缩写和模糊表达,“T”在体温、甲状腺素、肿瘤分期里都出现,BioBERT 对这类歧义词的上下文建模更稳,实体边界判断更准。第三是微调收敛更快。预训练阶段已经学好了医学文本的表达习惯,下游任务只需要在任务头上做少量调整,训练 3 到 5 个 epoch 就能看到稳定的 F1 提升,而从零训练的模型至少跑 20 个 epoch 还未必能稳住。

这里要提一个对比对象:ClinicalBERT。它是在 MIMIC-III 临床笔记上继续预训练的,风格比 BioBERT 更接近电子病历。实际使用时两者各有优劣,ClinicalBERT 在入院记录、护理记录上的领域距离更近,BioBERT 在覆盖疾病术语和药物术语上更全。我的做法是两个都下下来跑一遍对比,如果没有充足算力做对比实验,优先用 BioBERT,因为它的发布更早、社区使用量大、出问题的概率低,而且它沿用了 BERT 的词表,不用担心词表不兼容的问题。

2.2 迁移学习在实体关系抽取上的两种落地方式

实体关系抽取在电子病历上一般拆成两个子任务:实体识别(NER)和关系分类(RE)。NER 给每个 token 打一个 BIO 标签,标出实体边界和类型;RE 给定句子里的两个实体,判断它们之间是什么关系,比如“阿司匹林”和“胸痛”是“治疗”关系。迁移学习要解决的核心问题,就是如何把 BioBERT 在生物医学语料上学到的语言表示迁移到这两个任务上。

迁移学习本身有归纳式和直推式之分。直推式迁移学习要求目标域的无标注数据在训练阶段一起参与进来,这在 NLP 里组织起来很别扭,因为文本样本无法像图结构那样随意连接,所以 BERT 系的预训练加微调路线基本都是归纳式:预训练学通用表示,微调阶段用标注数据把表示调整到任务分布上。电子病历场景下不需要纠结这个概念,理解成“加载权重 + 换任务头”就够了。

任务落地有两种结构。管道式做法是先把 NER 模型训好,识别出句子里的所有实体,再把实体两两配对送进关系分类器。优点是每个环节可以单独调试,NER 错了容易定位。缺点是误差会传播,NER 漏掉一个实体,关系分类根本拿不到这个候选对。联合式做法是一个模型同时出实体和关系,结构复杂、上限更高,但对标注质量和数据量要求都高。电子病历标注数据通常只有几千条,我一般建议先做管道式,把链路跑通,等数据积累到一定规模再考虑联合模型。

2.3 PyTorch 环境准备:anaconda 配置与 BioBERT 权重下载

先搭环境。我习惯用 anaconda 建独立环境,Python 版本和 PyTorch 版本对应关系要提前确认,PyTorch 2.x 配 Python 3.9 或 3.10 都是稳妥选择。GPU 版本按自己机器的 CUDA 版本装,没有 GPU 就装 CPU 版先把流程跑通。

# 创建独立环境,避免污染其他项目 conda create -n biobert python=3.9 -y conda activate biobert # CPU 版示例,适合先跑通流程;GPU 版按官网命令选择 cu118/cu121 等版本 pip install torch==2.1.0 --index-url https://download.pytorch.org/whl/cpu # HuggingFace 生态:模型加载、tokenizer、数据集处理 pip install transformers==4.36.0 datasets tokenizers

这段命令的逻辑是先建环境再装包,防止 pytorch 和其他项目依赖互相干扰。安装 PyTorch 时不要直接pip install torch,那会默认装 CPU 版或者与机器 CUDA 不匹配的版本,最好到官网按系统配置复制对应安装命令。transformers 版本不用追新,4.3x 系列足够稳定,API 也熟悉。

环境装好后验证 BioBERT 能不能正常加载:

from transformers import AutoTokenizer, AutoModel model_name = "dmis-lab/biobert-base-cased-v1.1" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModel.from_pretrained(model_name) print(tokenizer.tokenize("患者因胸痛入院,口服阿司匹林治疗。")) print(model.config.hidden_size)

BioBERT 的权重在 HuggingFace 上的仓库名是dmis-lab/biobert-base-cased-v1.1,首次加载会自动下载到本地缓存。注意选 cased 版本,电子病历里大小写敏感很重要,比如“ACE”是血管紧张素转化酶,小写“ace”可能是别的含义,uncased 模型会把这些信息丢掉。跑通这段代码说明 PyTorch、transformers、模型权重三层都没问题,可以进入数据准备环节。

3. 数据准备:把电子病历转成 BioBERT 能吃的训练样本

3.1 BIO 序列标注与关系三元组

电子病历的实体类型一般包括疾病、症状、药物、检查、解剖部位五类。关系类型根据业务目标定,常见的有“治疗”“诊断”“副作用”“并发症”几种。标注工作要在文本层面完成,每个句子需要标注实体边界和实体间的关系三元组。

BIO 标注是最常见的序列标注格式:B 表示实体开头,I 表示实体中间或结尾,O 表示非实体。一个样例:

患 者 因 胸 痛 入 院 , 口 服 阿 司 匹 林 治 疗 。 O O O B-Symptom I-Symptom O O O O B-Drug I-Drug O O O

关系三元组则是{head, relation, tail}的集合,上面这句可以抽出一个三元组:{阿司匹林, 治疗, 胸痛}。训练 NER 模型需要 BIO 序列,训练关系分类模型需要句子加实体对。标注数据最好用专业的标注工具来做,标注规范里要提前约定边界模糊时的处理方式,比如“急性心肌梗死”整体是一个实体还是拆成“急性”加“心肌梗死”两个实体,这会直接影响模型的边界学习。

数据量上,电子病历场景 2000 到 3000 个句子是起步,实体标注质量比数量更重要。我见过有人拿 5000 句但标注前后不一致,效果不如 2000 句标注严格的数据。标注时每类实体至少要覆盖 100 个样本,关系类型更要控住,少于 50 个正例的关系类型建议直接去掉,否则模型学到的是噪声。

3.2 Tokenizer 对齐标签:为什么子词切分会让标注翻车

文本标注是按“词”为单位做的,但 BioBERT 的 tokenizer 会把词切成子词。比如“阿司匹林”可能被切为两个 token,如果标签还按词级别对齐,token 数量和标签数量就对不上了。这是新手最容易翻车的地方,处理逻辑如下:

from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("dmis-lab/biobert-base-cased-v1.1") label_map = { "O": 0, "B-Disease": 1, "I-Disease": 2, "B-Symptom": 3, "I-Symptom": 4, "B-Drug": 5, "I-Drug": 6, "X": -1 # X 表示该 token 不参与损失计算 } def encode_with_labels(text, word_labels, max_len=128): tokens, label_ids = [], [] for word, label in zip(text.split(), word_labels): word_tokens = tokenizer.tokenize(word) if not word_tokens: continue for i, wt in enumerate(word_tokens): tokens.append(wt) # 词的第一个子词保留原始 BIO 标签,后续子词标 X label_ids.append(label_map[label] if i == 0 else -1) # 加入 [CLS] 和 [SEP],超出 max_len 的部分直接截断 tokens = ["[CLS]"] + tokens[: max_len - 2] + ["[SEP]"] label_ids = [-1] + label_ids[: max_len - 2] + [-1] input_ids = tokenizer.convert_tokens_to_ids(tokens) attention_mask = [1] * len(input_ids) return input_ids, label_ids, attention_mask

这段代码的关键逻辑是“词的第一个子词继承原标签,其余子词标为 -1”。如果“阿司匹林”被切成两个子词,第一个保留 B-Drug,第二个不参与损失计算。为什么不标 I-Drug?因为第二个子词并不是新实体的一部分,标成 I-Drug 会让模型误以为实体在后面延续,边界学习会被扰动。用 -1 掩码让模型只看第一个子词的监督信号,这个技巧在短实体多的电子病历场景里效果最好。

训练时 DataLoader 做 padding 也要注意,标签的 padding 值必须设为 -1,而不是 0。0 是 O(非实体)的标签,如果 padding 也填 0,模型会在无效位置上学到“非实体”的信号,拉低实体召回。用DataCollatorForTokenClassification时把label_pad_token_id设成 -1。

3.3 关系分类样本构造:实体对与上下文

关系分类的输入是一个句子加两个实体边界。最直接的做法是在实体首尾插入特殊 marker token,比如[E1] 阿司匹林 [/E1]和[E2] 胸痛 [/E2],让模型明确知道要对哪两个实体做关系判断。但这种方式需要扩展 tokenizer 词表,而且 marker token 的位置编码是随机初始化的,对冻结层方案不友好。

我一般用实体池化:先跑 NER 拿到实体边界位置,然后对 BioBERT 输出的隐藏状态按实体边界做平均池化,得到两个实体的向量表示,拼上[CLS]向量一起送入分类器。这种方式不需要改词表,对实体长度不敏感,而且在管道式流程里 NER 输出的边界直接就能用。构造训练样本时,正样本是标注出的真实关系三元组对应的实体对,负样本则要采样句子里的其他实体对,控制好正负比例,这一点在第 5 章展开说。

4. 模型搭建与训练:PyTorch 实现电子病历实体关系抽取的两个任务头

4.1 实体抽取头:线性层加交叉熵,先别上 CRF

NER 任务头有两种常见选择:线性层加交叉熵,或者 CRF。CRF 能建模标签转移约束,比如“B-Drug 后面不能直接跟 B-Symptom”,对边界约束有帮助,但它训练慢、对数据量要求高。电子病历标注通常只有几千条,CRF 的收益不稳定,我建议先用线性层加交叉熵把流程跑通,F1 上不去了再换 CRF。

import torch import torch.nn as nn from transformers import AutoModel class BioBERTNER(nn.Module): def __init__(self, model_name: str, num_labels: int): super().__init__() self.bert = AutoModel.from_pretrained(model_name) self.dropout = nn.Dropout(0.1) self.classifier = nn.Linear(self.bert.config.hidden_size, num_labels) self.loss_fn = nn.CrossEntropyLoss(ignore_index=-1) def forward(self, input_ids, attention_mask, labels=None): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) sequence_out = self.dropout(outputs.last_hidden_state) logits = self.classifier(sequence_out) # (batch, seq_len, num_labels) loss = None if labels is not None: loss = self.loss_fn( logits.view(-1, logits.size(-1)), labels.view(-1) ) return loss, logits

代码里ignore_index=-1是核心参数,它让 CrossEntropyLoss 跳过所有标签值为 -1 的位置——包括 X 子词、CLS、SEP 和 padding。计算 loss 时把 logits 和 labels 都展平成二维,形状分别为(batch*seq_len, num_labels)和(batch*seq_len,),PyTorch 会自动忽略-1的位置。

num_labels 就是 BIO 标签的总数,按第 3 章的 label_map 来,O 加各个 B-/I- 类型。dropout 0.1 是 BERT 微调的标准值,数据量特别少时可以提到 0.2 防过拟合。

4.2 关系分类头:CLS 向量与实体边界池化

关系分类模型的输入需要同时包含两个实体的信息。我的实现是:BioBERT 编码完整句子,提取 CLS 向量作为句子级特征,对 head 实体和 tail 实体分别做平均池化得到两个实体级向量,三者拼接后过 MLP 分类。

class BioBERTRE(nn.Module): def __init__(self, model_name: str, num_relations: int): super().__init__() self.bert = AutoModel.from_pretrained(model_name) self.classifier = nn.Sequential( nn.Linear(self.bert.config.hidden_size * 3, 256), nn.ReLU(), nn.Dropout(0.2), nn.Linear(256, num_relations) ) def forward(self, input_ids, attention_mask, head_pos, tail_pos): outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) hidden = outputs.last_hidden_state # (batch, seq_len, hidden) def entity_pool(positions): # positions: (batch, 2),每行是 [start, end],均为闭区间 pooled = [] for i in range(positions.size(0)): s, e = positions[i, 0], positions[i, 1] pooled.append(hidden[i, s:e + 1].mean(dim=0)) return torch.stack(pooled) head_vec = entity_pool(head_pos) tail_vec = entity_pool(tail_pos) cls_vec = hidden[:, 0] # CLS 向量 features = torch.cat([cls_vec, head_vec, tail_vec], dim=-1) logits = self.classifier(features) return logits

实体池化的好处是不依赖 marker token,实体的起始位置是整数索引,直接切段做平均。head_pos 和 tail_pos 在训练时来自标注,预测时来自 NER 模型的输出。这个设计里实体向量的信息是“边界内 token 的平均”,对实体内部 token 数量的变化不敏感,一个 2 个字的药物实体和一个 6 个字的检查实体都能得到稳定的 768 维向量。

MLP 的中间维度 256 是从hidden_size * 3 = 2304压缩下来的,加 ReLU 和 dropout 0.2 防止过拟合。对分类任务来说这个东西比直接用 2304 维过 softmax 稳定得多。

4.3 微调训练循环:学习率、batch size 与梯度裁剪

迁移学习场景下训练配置和从零训练完全不同。BioBERT 的权重已经有很好的语言表示,学习率大了会把预训练学到的知识冲掉,小了又训不动任务头。参数设置可以参考下表:

参数推荐值说明
学习率2e-5 ~ 5e-5标注数据少于 1000 条用 2e-5,数据多可放宽到 5e-5
batch size8 ~ 16序列长度 512 时 16 需要约 16GB 显存,不够就降到 8
warmup 比例10%训练前 10% 的步数学习率线性上升
权重衰减0.01对全连接层和 embedding 层加,防止过拟合
epoch3 ~ 5观察验证集 F1,连续 2 个 epoch 不涨就停
from torch.optim import AdamW from transformers import get_linear_schedule_with_warmup optimizer = AdamW(model.parameters(), lr=5e-5, weight_decay=0.01) total_steps = len(train_loader) * epochs scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=int(total_steps * 0.1), num_training_steps=total_steps ) for epoch in range(epochs): for step, batch in enumerate(train_loader): loss, _ = model(**batch) loss.backward() # 梯度裁剪是必选项,防止个别长样本把梯度撑爆 torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() scheduler.step() optimizer.zero_grad()

AdamW 和 Adam 的区别在于权重衰减的实现方式,AdamW 不把 weight decay 混进梯度里,对预训练模型微调更友好。warmup 的意义是让模型在训练刚开始时用小学习率适应,避免大步长直接踩坏预训练权重。梯度裁剪设 1.0 是经验值,电子病历里句子长短差异大,个别超长句子的梯度范数会突然暴涨,不裁剪的话一个 batch 就能让 loss 跳到原来的三倍。

4.4 冻结策略:先冻结前几层,等 loss 稳了再全量解冻

迁移学习不是一定要全量微调。数据量小的时候,BioBERT 底层的通用语言知识很容易被少量标注数据带偏。常见做法是先用少量数据只训任务头和最后两层,跑两三个 epoch 后再解冻全量模型。这个策略能降低 cold start 阶段的波动。

def freeze_bert_layers(model, freeze_count: int): """冻结 BERT 最前面的 freeze_count 层,任务头始终保持可训练""" for name, param in model.bert.named_parameters(): layer_idx = None if name.startswith("encoder.layer."): layer_idx = int(name.split(".")[2]) if layer_idx is not None and layer_idx < freeze_count: param.requires_grad = False freeze_bert_layers(model, freeze_count=8) # 优化器只接收 requires_grad=True 的参数,加快训练并避免冻结参数被误更新 optimizer = AdamW( filter(lambda p: p.requires_grad, model.parameters()), lr=5e-5 )

冻结的层数看数据量:数据少于 1500 句可以冻结前 8 层(总共 12 层),数据在 3000 句以上就只冻结前 4 层。冻结的参数不参与前向计算吗?注意requires_grad=False只是不计算梯度,前向传播仍然会经过这些层,所以推理速度不受影响,反向传播时这些层不更新权重、不保存中间激活的梯度。解冻的时候把所有参数的requires_grad重新设为 True,然后把优化器重新初始化一遍,因为优化器里的参数分组还是按旧的requires_grad状态生成的。

5. 避坑:电子病历实体关系抽取的 5 个高频问题与排查

5.1 实体只识别出半个:子词切分把 BIO 标签搞丢了

现象:训练 loss 下降了,但预测结果里很多实体只有一半长度,比如“心肌梗死”只识别出“梗死”,前面“心肌”丢了。验证集 F1 卡在 60% 上下不去。

原因:tokenizer 把“心肌梗死”切成了多个子词,标签对齐时后面的子词被标成 -1,监督信号只落在首子词上。如果模型预测时对非首子词位置的输出没有做合并规则约束,模型可能输出不完整的实体边界。

解决:把标签对齐策略从“后续子词标 -1”改成“后续子词标对应实体的 I- 类型”,让每个子词都有监督信号。这样会引入轻微偏置——模型可能学着把实体边界延后一个子词,但对短实体多的电子病历来说收益更大。改完后要在验证时做合并:相邻且类型相同的 B- 和 I- 片段拼成一个实体,并用seqeval库做实体级别的精确匹配评估,不要只看 token 级别准确率。

5.2 关系预测几乎全是“无关系”:正负样本严重失衡

现象:关系分类模型准确率 95%,看起来很高,但看混淆矩阵发现“无关系”类别占了 93%,其他关系类型召回全面崩溃。训练 loss 下降平稳,但 F1 非常难看。

原因:管道式关系抽取里,负样本来自句子中所有非标注实体对组合。一个句子有 5 个实体,正样本可能就 2 对,负样本却有 18 对,训练集正负比到 1:9 甚至更深。模型学了个偏置分类器,全部预测为“无关系”就能拿到很低的 loss。

解决:构造训练数据时把正负比控制在 1:3 到 1:5。具体做法是每个句子只保留一个负实体对,其余候选对放进一个池子,用全局随机抽样而不是逐句全量生成。再配合给“无关系”类别的 logits 乘一个 0.5 的权重,或者直接改用 Focal Loss,让模型把注意力放到困难的正样本上。

5.3 长病历尾部实体全被截断:max_len 不是越长越好

现象:电子病历里“医嘱”或“用药记录”部分的药物实体总是识别不出来,单独看这些实体标注没错,但模型永远预测不到。

原因:max_len 设了 128,BERT 的截断策略默认保留句子开头,而电子病历的关键信息经常在句子中后段。BERT 的位置编码上限是 512,超过这个长度模型完全没有位置表示,所以不能靠无脑加长解决。

解决:改用分句策略。把电子病历按句号、逗号切分成短句,每句独立做 NER,再用实体后处理把跨句的同一实体合并。这样每个样本都能落在 512 以内,而且减少了 padding 浪费。更简单的补救是把 max_len 提到 256,对大多数主诉和现病史够用,但超过 512 的文本仍然要配合分句。

5.4 一调大 batch 就 OOM:显存不够时的三个退路

现象:batch size 调到 16,跑第一个 step 就报CUDA out of memory,换 8 也一样,序列长度 512 的样本占显存超预期。

原因:BERT base 在序列长度 512 时,单条样本的前向中间激活就要占接近 1GB 显存,加上每个 token 的梯度,batch 16 轻松超过 16GB。很多人的显存只有 8GB,直接撞墙。

解决:三个退路。第一是 batch size 降到 4 或 2。第二是加梯度累积,每 4 个 batch 更新一次,等价于 batch size 16 的效果,显存还是只用 4 的量。第三是关键——动态 padding,按 batch 内最长序列 padding,而不是全数据集 pad 到 512。电子病历句子长短差异大,全 pad 到 512 的显存浪费可以到 70%。动态 padding 的实现只需要在 collate 函数里按当前 batch 的最大长度截断和 padding。

5.5 模型加载报参数不匹配:任务头与预训练权重的认知误区

现象:用AutoModel.from_pretrained加载 BioBERT 后接自己的分类头,保存模型时torch.save,下次加载报size mismatch。

原因:BERT 权重里含有一个随机初始化的 pooler 层(用于预训练时的 NSP 任务),这个层接分类头时通常不用。自己定义的分类头是随机权重,如果直接对整个模型做torch.save(model.state_dict())再加载,分类头的 shape 变了自然报 mismatch。

解决:分清楚两条加载路径。如果用自己的nn.Module包了 BioBERT,加载时用AutoModel.from_pretrained拿 BERT 权重,任务头是新建的随机权重,不需要也不应该去加载预训练的 pooler。保存时只存模型权重但不存 optimizer 状态,加载时先构造模型再load_state_dict,并忽略掉 BERT 自带的 pooler 层参数。排查 mismatch 时看具体报错键名,凡是带pooler的都可以直接删掉。

6. 验证与进阶:用严格 F1 和错误分析决定模型能否上线

模型训完别急着看 token 准确率,那个数字欺骗性太强。实体识别用seqeval做实体级别的精确匹配评估:预测实体的首尾位置和类型全部和标注一致才算对,差一个子词都算错。关系抽取用三元组级别的 F1:head、relation、tail 三个字段全部匹配才算对,任何一个错了都计为假阳性。这种严格评估下实体 F1 能到 0.85 以上,关系 F1 能到 0.75 以上,基本可以进入真实病历的试运行。

from seqeval.metrics import classification_report true_entities = [["O", "B-Symptom", "I-Symptom", "O"]] # 标注 pred_entities = [["O", "B-Symptom", "I-Symptom", "O"]] # 预测 print(classification_report(true_entities, pred_entities))

验证之外要做错误分析。我习惯把预测错误的样本按实体类型和关系类型分组统计,如果某个类型错误占比异常高,大概率是标注不一致,而不是模型不行。比如“检查”实体经常和“症状”混淆,回去看标注规范,往往发现同样的文本在不同标注员手里答案不同,这个不改,模型怎么调都没用。

数据量不足还有一个实用技巧:用训练好的模型在无标注电子病历上做预测,抽出高置信度的预测结果人工抽检,合格后进入训练集。这就是伪标注,本质上是半监督迁移学习的一种,能有效扩大训练规模。我自己的经验是伪标注数据占训练集三分之一以内是安全的,超出后模型会把自己犯过的错误当作正确答案反复强化。

踩过这么多坑之后,我的习惯是:数据准备阶段花的时间比模型训练多一倍,标签对齐、样本构造、负采样这些环节任何一个出问题,后面的所有工作都会被放大。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询