ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

中文命名实体识别实战:BERT+BiLSTM+CRF完整项目解析

中文命名实体识别实战:BERT+BiLSTM+CRF完整项目解析 简介面向中文命名实体识别任务融合BERT、BiLSTM与CRF的Python实现方案可对中文文本中的人名、地名、组织机构等实体进行端到端识别包含完整源码、项目说明、预训练模型与标注数据适合计算机、人工智能等专业学生用于毕业设计、课程设计或NLP实战练习。压缩包共58个文件以16个py源码、19个pyc编译文件、9个txt说明为主辅以png结构图、md笔记、xml工程配置和csv数据整体13.75MB目录按模型、数据处理、训练等模块划分便于按图索骥。从现有浏览数据看已有1209人学习下载。工程内提供BILSTM_CRF、IDCNN_CRF、BILSTM_Attention_CRF、BERT_BILSTM_CRF等多种对比模型附带人民日报和MSRA数据集预处理脚本可直接运行训练与测试项目说明详细且经测试运行成功既适合小白逐步练习也可作为课设、毕设或初期项目立项演示能帮助深入理解NER中序列标注、特征融合与解码的完整流程。1. 一个zip里装着的成熟技术栈BERTBiLSTMCRF 到底是做什么的看到“基于BERTBILSTMCRF进行中文命名实体识别python源码项目说明模型数据.zip”这个打包名字懂行的人会先松一口气这不是什么玩具 demo而是目前中文命名实体识别NER领域最经典、最不容易翻车的三层结构。BERT负责把每个字的语义编码成向量BiLSTM在整句话里再做一轮上下文精修CRF在最后给标签序列套上合规约束。三者的关系有点像“阅读理解 上下文润色 校对排版”。这套技术栈能解决的具体问题很直接从一段中文文本里自动抽出人名、地名、机构名、时间、药品名、案件名等实体片段。对毕业设计、法律文书抽取、医疗病历结构化、客服工单打标这些场景它都是最稳的起点方案。在动手之前我要先帮你看清楚这个结构为什么值得搭、每一步怎么落地、以及哪些地方会白白浪费你两三天时间。2. 三层神经网络各司其职为什么非要把 BERT、BiLSTM、CRF 拼在一起2.1 中文NER难在哪边界、歧义和嵌套中文命名实体识别和英文最大的差别在于“字”和“词”的关系。英文里词天然有空格分隔实体边界基本等于词边界中文没有这个天然分隔符所以中文NER通常退化成“序列标注”问题——每个字给一个标签连起来才构成实体。比如“我 在 北 京 上 班”这六个字需要给“北”“京”分别打上“B-LOC”“I-LOC”模型才能拼出“北京”这个地名。边界还不是唯一难点。中文的歧义很要命“南京市长江大桥”既可以切成“南京市/长江大桥”也可以理解为“南京市长/江大桥”“小米上市了”里的“小米”是公司还是粮食要看句子上下文。更麻烦的是嵌套实体比如“北京大学附属医院”里同时存在“北京大学”和“北京大学附属医院”两个实体但扁平式标注只能选一层。所以一个能干活的NER系统必须同时拥有“理解语义”和“利用上下文修正”的能力这正是三层结构存在的理由。如果把NER比作流水线BERT是看懂了全文语义的阅读者BiLSTM是把前后文线索反复咀嚼的校稿人CRF则是最后那道不许标签乱跳的质检闸门。三层各管一段去掉任何一层效果都会肉眼可见地变差。2.2 为什么不能只用 BERT也不能只用 BiLSTMCRF先看只用BERT的情况。BERT经过预训练之后每个字会得到一个带着整句上下文信息的向量理论上可以直接把这些向量喂给一个全连接层输出每个字属于每种标签的概率。这样做能出结果但在长句上问题很明显BERT的解码端没有“记住刚才那个字是什么标签”的能力。比如“李华在北京工作”中“李”被识别成B-PER后下一个“华”大概率也应该是I-PER但如果只看BERT输出的概率向量“华”也可能被误判成B-PER因为词向量层面它并没有“我是不是前一个实体的延续”这个信息。再看只用BiLSTMCRF的情况。BiLSTM能从两个方向读取序列信息CRF能约束标签转移这套经典组合在十年前是标配。但它的天花板在于输入特征如果还是用随机初始化的字向量模型对语义的理解非常浅碰到“苹果发布新手机”这种句子它很难把“苹果”归到ORG还是MISC因为没有足够的预训练知识告诉它“苹果”在科技新闻里指公司。在实际项目中我见过不少团队把这三层拆开做对比实验。结论很一致BERT单层F1大约在82-86BiLSTMCRF单跑大概在75-80三层叠加能上到90左右。差异来源主要就是“语义理解”和“标签序列约束”这两个能力缺一不可。另外BERT内部其实包含12层Transformer已经具备很强的特征提取能力所以叠加的BiLSTM通常可以做得浅一些单层即可作用是对BERT输出做一次序列级别的平滑聚合而不是重新提取特征。2.3 CRF的转移矩阵没有它标签会乱跳CRF条件随机场在NER里的核心作用是一个词约束。它会学习一个标签转移矩阵比如“B-PER后面只能跟I-PER或者O不能直接跟B-LOC”“I-LOC前面必须是B-LOC或者I-LOC”。这种约束用代码很难手写全因为标签组合太多而CRF会从训练数据里自动统计这些转移概率。具体来说BERTBiLSTM输出的每个字的标签概率会被送入CRF层CRF会结合“当前字的概率”和“前一个字的标签”这两部分信息利用维特比算法找出整条句子最优的标签路径。举个例子“我 在 北 京 上 班”如果没有CRF模型可能给“北”打B-LOC、给“京”打O因为单独看每个字“京”被识别成O的概率也不低但CRF知道“B-LOC 后面接 O”的转移概率极低所以会把“京”强行纠正为I-LOC。注意CRF不是用来提升实体识别概率的它是用来压制不合法序列的。训练过程中你是否正确实现了“损失 真实路径分数 - log(所有路径分数和)”这个公式直接决定了最终实体边界是否整齐。理解了这个原理你在调参时才不会把“CRF的学习率”和“BERT的学习率”混为一谈——这是后面实操里非常关键的坑。3. 本地跑通最小源码环境、数据格式与第一个训练循环3.1 环境准备依赖与预训练模型下载在你打开那个zip之前先确认机器上已经有了最基本的Python环境。这里的要求不算苛刻Python 3.8以上就可以显卡有的话最好没有也能用CPU硬跑只是训练速度会慢很多。下面是我常用的创建环境命令# 创建独立环境并激活避免把系统Python搞乱 conda create -n ner python3.8 -y conda activate ner # 安装核心依赖按顺序执行 pip install torch --index-url https://download.pytorch.org/whl/cu118 pip install transformers4.21.3 pip install datasets pip install seqeval pip install tqdm numpy pandas说一下每个依赖为什么要装torch是BERT和BiLSTM的底层框架transformers负责加载预训练模型比如bert-base-chinese注意版本不要追新4.2x系列足够新版本API差异反而容易让你从网上抄来的训练代码报错datasets用来把原始标注文件转成pytorch能读的Dataset对象seqeval是专门做序列标注评估的库比手写F1计算省心得多。预训练模型这块是很多人卡住的地方。HuggingFace上的bert-base-chinese大约400M如果网络不稳定建议设置镜像环境变量# Linux/Mac下执行 pip install -U huggingface_hub export HF_ENDPOINThttps://hf-mirror.com python -c from transformers import BertTokenizer, BertModel; BertTokenizer.from_pretrained(bert-base-chinese); BertModel.from_pretrained(bert-base-chinese)提示镜像环境变量只对当前终端有效。如果你用pycharm跑代码需要在运行配置里同样把HF_ENDPOINT加进去否则代码里from_pretrained会重新走默认源卡在下载。判断预训练模型有没有下载成功看缓存目录即可。Windows默认在C:\Users\你的用户名\.cache\huggingface\Linux在~/.cache/huggingface/。看到模型文件完整躺在那里后面训练就不会再为下载来回折腾。3.2 数据标注格式BIOES是什么意思你的数据要长什么样中文NER的数据格式通常是“字标签”逐行排列不同句子之间用空行隔开。标签体系有BIO和BIOES两种BIO标签将所有实体的第一个字标为B-后续字标为I-非实体标为OBIOES在BIO基础上多了E和S分别表示实体的结束字和单字实体。我在项目里优先推荐BIOES因为它在实体边界上的信息量更明确。举个例子“我在北京上班”这句话的BIOES标注长这样我 O 在 O 北 B-LOC 京 E-LOC 上 O 班 O而“王小明去北京”这种含单字实体的句子就必须用S来标单字人名王 S-PER 小 I-PER 明 E-PER 去 O 北 B-LOC 京 E-LOC如果你拿到的是BIO格式可以用一段小脚本转成BIOES但转换逻辑要小心处理“连续实体紧挨着”的情况BIO里连续两个实体“北京上海”的标注是“B-LOC I-LOC B-LOC I-LOC”转BIOES时不能只看实体首尾必须判断上一个字的标签是B还是I否则会把“北京上”误标成一个地名。这个转换函数建议写完以后用“北京市朝阳区”这种带连续实体的句子手动验一遍。3.3 数据读取与标签对齐数据格式读完下一步是把文本转成模型能吃的张量。这一步是整个项目里最容易出错的地方核心原因是BERT的tokenizer给每个汉字可能拆成多个subword片段而你的标注是按“字”给的两者对不上。我先展示一个最常见的数据读取代码然后解释为什么要注意对齐# -*- coding: utf-8 -*- from transformers import BertTokenizer import torch MAX_LEN 128 def load_data(file_path): sentences, labels [], [] with open(file_path, r, encodingutf-8) as f: sentence, label [], [] for line in f: line line.strip() if line : if sentence: sentences.append(sentence.copy()) labels.append(label.copy()) sentence, label [], [] else: parts line.split() # 文件可能是“字 空格 标签”也可能用制表符 sentence.append(parts[0]) label.append(parts[-1] if len(parts) 1 else O) return sentences, labels class NERDataset(torch.utils.data.Dataset): def __init__(self, sentences, labels, tokenizer, label2id, max_len128): self.examples [] for sent, tag in zip(sentences, labels): # 关键点设置return_offsets_mappingTrue, 才能拿到每个subword对应原文字符的映射 encoding tokenizer( sent, is_split_into_wordsTrue, max_lengthmax_len, truncationTrue, paddingmax_length, return_tensorspt, return_offsets_mappingTrue, ) # 标签对齐到subword word_ids encoding.word_ids() aligned_label [] prev_word None for word_id in word_ids: if word_id is None: aligned_label.append(-100) # 特殊token不参与loss计算 elif word_id ! prev_word: aligned_label.append(label2id[tag[word_id]]) else: # 同一个word被拆成多个subword沿用第一个标签 aligned_label.append(label2id[tag[word_id]]) prev_word word_id self.examples.append({ input_ids: encoding[input_ids].squeeze(), attention_mask: encoding[attention_mask].squeeze(), labels: torch.tensor(aligned_label, dtypetorch.long), }) def __len__(self): return len(self.examples) def __getitem__(self, idx): return self.examples[idx]这段代码的关键在标签对齐逻辑。word_ids()会返回每个token对应的原始字索引None表示CLS、SEP、PAD这些特殊token。遇到特殊token时我把标签设为-100这样在计算交叉熵损失的时候PyTorch会自动忽略-100所在的位置。遇到同一个字被拆成多个subword的情况后续subword直接用第一个subword的标签保证标签不串位。注意上面的代码里有一个容易埋雷的小地方——tokenizer的is_split_into_wordsTrue参数。如果不把句子传成词列表而是直接传字符串BERT会按自己的规则重新分词原本的“字-标签”对应关系就被打乱了。我在刚开始做项目时就在这里吃过亏换句话说是“标签对不上”里最高频的原因。数据集做好后按照8:1:1的比例划分训练集、验证集和测试集。划分的时候要按句子为单位随机打乱不能按字符打乱否则会造成同一句子的标签泄漏到不同集合里评估结果会虚高。4. 训练参数这样调batch size、max_len 与学习率的三方权衡4.1 一套默认能跑的参数模板模型搭建和训练循环看起来代码量不小但结构是固定的。下面是一段可以直接套用的训练核心代码也是大多数NER项目的通用写法# -*- coding: utf-8 -*- import torch from torch.utils.data import DataLoader from transformers import BertModel, BertTokenizer, AdamW, get_linear_schedule_with_warmup import torch.nn as nn class BertBiLSTMCRF(nn.Module): def __init__(self, bert_path, num_labels, lstm_hidden256): super().__init__() self.bert BertModel.from_pretrained(bert_path) self.bilstm nn.LSTM( input_sizeself.bert.config.hidden_size, hidden_sizelstm_hidden, num_layers1, bidirectionalTrue, batch_firstTrue, ) self.fc nn.Linear(lstm_hidden * 2, num_labels) self.crf nn.CRF(num_labels) # 如果使用pytorch-crf库 def forward(self, input_ids, attention_mask, labelsNone): bert_out self.bert(input_idsinput_ids, attention_maskattention_mask) sequence_output bert_out.last_hidden_state lstm_out, _ self.bilstm(sequence_output) emissions self.fc(lstm_out) if labels is not None: loss -self.crf(emissions, labels, maskattention_mask.bool()) return loss else: return self.crf.decode(emissions, maskattention_mask.bool())这段代码的层次关系很清晰BERT的输出维度是768BiLSTM的hidden_size设为256双向拼接后是512维再通过全连接层映射到标签数量。注意CRF层直接放在了模型内部loss计算时取负对数似然解码时直接调用decode方法。如果你用的不是pytorch-crf库也可以手写维特比算法但既然有成熟的库我建议直接用pip install pytorch-crf装上省时又不爱出错。提示上面对CRF的调用基于pytorch-crf这个库。如果你拿到手的zip里是自己实现的手写CRF类注意检查它的decode是否接收了mask参数有些旧版实现不接收mask在padding部分会出现幻觉标签这类问题特别隐蔽。4.2 学习率与BERT层冻结微调的重心应该放在哪训练参数的首选清单看这张表参数推荐值说明batch_size16GPU/ 8CPU显存不够就减半不要硬扛learning_rateBERT2e-5微调预训练模型的标准学习率太大容易灾难性遗忘learning_rateBiLSTMCRF1e-3下游随机初始化的层需要用更大的学习率max_len128超过128的真实句子占少数长了切掉短了补齐epochs20-30存best模型不需要训满全部warmup_ratio0.1前10%的step线性升学习率稳定BERT微调有关键设计是分组学习率。BERT层用2e-5BiLSTM和CRF这些随机初始化的层用1e-3这两组参数放进同一个优化器却要用不同学习率。常规做法是在构造optimizer时传一个参数组列表# 构造分组参数BERT层和下游层用不同学习率 optimizer_grouped_parameters [ {params: [p for n, p in model.named_parameters() if bert in n], lr: 2e-5}, {params: [p for n, p in model.named_parameters() if bert not in n], lr: 1e-3}, ] optimizer AdamW(optimizer_grouped_parameters, weight_decay0.01)还有一个常被忽略的选项是“冻结BERT的一部分层”。前几层Transformer学习到的是通用字面特征对领域迁移帮助不大微调预算有限的话把第1到第8层的参数requires_gradFalse只训练后几层和下游结构往往能省一半显存、速度提升30%以上效果只掉零点几个点。第一次把项目跑通时我不建议冻结先全量训练拿到一个基线成绩再说。4.3 训练过程的监控指标直接点说光看loss是不够的。BERTBiLSTMCRF这类模型在训练前期loss下降很快到了第3个epoch以后loss就“玄学”了震荡到面无波澜但实体F1还在涨。原因是CRF的loss分母是所有可能路径的分数和这个值天然很大loss虽然不再好看但序列约束正在慢慢起作用。正确的监控方式是每个epoch结束后在验证集上计算实体级F1而不是整个标签级准确率。标签级准确率会被“O”标签主导模型全部预测成O也能有接近80%的准确率数值毫无意义。我习惯在每个epoch跑一遍from seqeval.metrics import classification_report, f1_score def evaluate(model, dataloader, id2label): model.eval() all_true, all_pred [], [] with torch.no_grad(): for batch in dataloader: input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) true_labels batch[labels].numpy() pred model(input_ids, attention_mask) # 去掉特殊token和padding只看实际标签 for b_idx, word_idx in enumerate(true_labels): true_seq, pred_seq [], [] for i in range(len(word_idx)): if word_idx[i] ! -100: true_seq.append(id2label[word_idx[i]]) pred_seq.append(id2label[pred[b_idx][i]]) all_true.append(true_seq) all_pred.append(pred_seq) print(classification_report(all_true, all_pred, digits4)) return f1_score(all_true, all_pred)注意这里有两个细节一是all_true和all_pred里存的是每句话的标签序列不是单个样本的平铺数组因为seqeval要求对每个句子分别计算实体匹配二是跑模型前先model.eval()再包torch.no_grad()否则显存会被推理时保存的梯度撑爆。如果你希望训练中途自动存最好的模型可以维护一个best_f1变量每轮结束后对比一次超过就torch.save(model.state_dict(), best_model.bin)。靠这个机制我几乎从不需要为“epoch设多少”纠结。5. 避坑记录中文NER最常见的四个翻车现场5.1 标签错位预测结果全是对的但实体全都凑不齐现象模型在训练集上loss正常下降验证集F1却一直徘徊在20分以下。把预测结果打印出来发现每个字的标签看起来都有道理但组合成的实体要么缺头、要么缺尾甚至有“I-PER”开头的序列。原因这就是“标签对齐”翻车了。最常见的情况是is_split_into_words参数漏传tokenizer把“北京”这种连续字符当成了一个词导致word_ids映射错位原本“北B-LOC 京I-LOC”的标签被错误地跟BERT的piece一一对应整个序列往后错一位。还有一个变体是用了return_offsets_mappingTrue却完全没用它等于白拿映射做装饰。解决在数据读取阶段把encoding[word_ids()]打印出来对照原始句子人工检查前3条样本。看到[None, 0, 1, 1, 2, None]这种结构就该意识到第1和第2号字被合并成了同一个word。标签对齐那一段代码不要直接复制要逐行理解后敲一遍。5.2 显存OOM一跑就爆batch减半还是爆现象显存8G的卡batch_size设16直接OOM改成8还在OOM最后改到2才勉强跑起来速度又慢得让人想放弃。原因BERT本身占用不小尤其是max_len设成512时attention矩阵是平方级增长。8G显存下batch_size16、max_len512几乎是必爆配置。另外还有两个隐形凶手一是paddingmax_length把短句也全补齐到128长度浪费大量显存二是训练时没有设置torch.cuda.empty_cache()碎片累积偶尔也会顶爆显存。解决把max_len降到128batch_size先设8短句padding策略临时改成paddingTrue让每个batch内部对齐不全局补齐。如果想提速可以尝试在模型里用gradient_accumulation_steps2相当于batch_size16但显存占用只有8。注意梯度累积时loss要除以累积步数再回传。5.3 预训练模型下载失败每次跑到一半就卡住现象第一次运行BertModel.from_pretrained(bert-base-chinese)进度条走到99%就超时或者直接报ConnectionError。重试几次大概率仍失败偶尔成功一次又会发现下载不完整。原因从HuggingFace官方源下载连接不稳定文件接近400M中断后缓存里的临时文件会把后续下载也污染掉。这个问题的根源是网络环境跟代码没有任何关系。解决换镜像源下载在运行代码前先执行export HF_ENDPOINThttps://hf-mirror.comWindows在命令行里用set HF_ENDPOINThttps://hf-mirror.compycharm需要重启kernel让环境变量生效。如果镜像也出问题先手动把模型文件下载到本地目录然后在代码里指定本地路径model BertModel.from_pretrained(./chinese_bert_wwm_ext_pytorch/) tokenizer BertTokenizer.from_pretrained(./chinese_bert_wwm_ext_pytorch/)用本地路径加载时目录下面至少要包含config.json、pytorch_model.bin、vocab.txt三个文件缺一不可。这也是你从别人那里拷来预训练模型时最爱踩的坑——他们给你的是一个压缩包你解压后发现缺了vocab.txt代码跑不了一点。5.4 每个类别的F1差距离谱O标签主导真实实体几乎被淹没现象训练20个epoch验证集标签准确率96%但entity-level的F1只有55。翻看classification_report发现模型把几乎所有token都预测成了O。原因数据里实体占比太少。一个典型的法律文书里“当事人”段落人名集中但全文正文大量是“O”标签如果按句子随机划分数据集可能出现实体稀疏到没法学的局面。更深层的原因是类别不均衡在序列标注里被滚动放大了O标签在每个位置都成为超高先验概率。解决这个现象要从数据配比入手。先把实体密度实体字符数/总字符数打印出来如果低于10%要考虑换数据源或调整验证集划分方式。其次可以在损失函数里给非O标签更大的权重但我直接说结果——调权重只能提升1-2个点治标不治本。更正经的办法是引入真实实体词典做注入或者在预测阶段限制维特比解码器禁止出现I开头和孤立的E标签。用代码实现的话前者是在训练前标记词典命中词的位置并替换那些位置的loss权重后者是自定义CRF约束条件普通项目用不到那么深。6. 从训练到推理把模型接进真实文本的批量预测链路6.1 加载权重做单句预测训练结束后best_model.bin就是全部成果。加载它做推理时最容易被坑的一点是必须用和训练时完全一样的tokenizer配置和标签映射表。下面这段代码是我的标准推理模板# -*- coding: utf-8 -*- from transformers import BertTokenizer import torch def predict_single(text, model, tokenizer, id2label, max_len128): model.eval() # 逐字拆成列表和训练时的is_split_into_words保持完全一致 chars list(text) encoding tokenizer( chars, is_split_into_wordsTrue, max_lengthmax_len, truncationTrue, return_tensorspt, ) with torch.no_grad(): pred_ids model(encoding[input_ids], encoding[attention_mask])[0] # pred_ids是token级别的需要过滤掉特殊token再映射回原文 word_ids encoding.word_ids() entities [] current_entity None for token_id, word_id in enumerate(word_ids): if word_id is None: continue label id2label[pred_ids[token_id]] char chars[word_id] if label.startswith(B-): if current_entity is not None: entities.append(current_entity) current_entity {text: char, label: label[2:], start: word_id, end: word_id} elif label.startswith(I-) and current_entity is not None and current_entity[label] label[2:]: current_entity[text] char current_entity[end] word_id elif label.startswith(E-) and current_entity is not None and current_entity[label] label[2:]: current_entity[text] char current_entity[end] word_id entities.append(current_entity) current_entity None else: if current_entity is not None: entities.append(current_entity) current_entity None if current_entity is not None: entities.append(current_entity) # 去重避免S和B-E并存时重复输出 return {text: text, entities: entities}这段推理逻辑最核心的地方在于按B-、I-、E-的标签顺序来拼装实体。注意几个边界条件遇到O必须立刻结束当前实体遇到新实体紧挨着旧实体比如一个B-PER后面直接跟一个B-LOC要先把旧实体落盘再开新实体。如果你的标签体系是BIO而不是BIOES格式判断逻辑要相应简化——但要小心“I实体跨界”的问题也就是I-PER后面接I-LOC这种情况只能靠CRF在训练时从数据里学到转移惩罚推理代码里没法彻底兜底。6.2 把实体序列映射回原文字符位置训练时的tokenizer给定的是一个list输出predict_single里的start/end已经是原始字符下标了。但如果你想对一整段长文本做批量识别先切句再逐句推理必须记录每个句子在原段落里的偏移量否则所有实体的start/end都是从0开始的。# 简单的切句与偏移量对齐策略 import re def segment_with_offset(text): segments [] offset 0 for sent in re.split(r([。]), text): if not sent.strip(): continue segments.append((sent, offset)) offset len(sent) return segments def predict_long_text(text, model, tokenizer, id2label): result [] for sent, offset in segment_with_offset(text): pred predict_single(sent, model, tokenizer, id2label) for ent in pred[entities]: result.append({ text: ent[text], label: ent[label], start: ent[start] offset, end: ent[end] offset, }) return result这个方案只是按标点切句如果你处理的文本是PDF抽取出来的注意整段文本因为换行符被切碎的问题——遇到换行符时先做拼接再切句。还有offset的累加逻辑必须基于原始字符串的字符数不能基于去空格后的文本否则最终位置全部错位。6.3 用测试集衡量真实召回最后一件事不是写代码而是用你的测试集做一次全量推理并看一眼错误样例。我见过太多人训练完只看训练集F1这一看不要紧上线发现真实文本里大量实体漏检。正确的姿势是把测试集的预测结果和真实标签逐句打印出来对照阅读漏检的句子大多长什么样是实体长度普遍超过10个字还是大量嵌套结构误报的实体集中在哪几个类别比如把“华为”预测成PER还是ORG模型在短句上的表现和长句上的差异是否巨大对于长实体识别率低的现象最简单的补救是提高max_len上限到192或256因为BERT在长文本上的注意力分配会稀释尾部的字符特征。如果仍然无法根治就要考虑对实体做重叠窗口推理——把长文本切出部分重叠的窗口分别预测再合并结果合并时以重叠区域的投票结果为准。我自己的习惯是每个实验跑完后都把最好的checkpoint单独保存一份把对应的tokenizer和label映射表打个zip放在旁边日期写清楚。因为几周后再回来你大概率会忘记自己的label2id顺序是什么忘掉的后果就是推理结果整个乱了套而且很难定位。保留好一份完整的“模型配置数据划分seed”是这个方向里最实在的后悔药。这个技术栈看起来很重但从数据准备好到训出一个能用的模型通常只需要一个晚上。你踩过的坑会让下一个跑这个项目的人少走很多弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表