
简介基于YOLOv5s与LPRNet构建的车牌检测与识别项目使用CCPD数据集训练支持蓝牌和绿牌新能源车牌等中国车牌识别。适用于计算机、电子信息工程、数学等专业的学生作为课程设计、期末大作业或毕业设计的参考资料也适合对OCR和目标检测感兴趣的开发者入门学习。资源包共61个文件容量16.75MB主要包含27个JPG图片样张、22个Python脚本、3个YAML配置文件以及模型权重文件.pt/.pth等脚本覆盖数据集划分、YOLOv5训练、LPRNet训练与测试全流程。目前已有483人学习浏览。通过该资源可获得完整的车牌识别项目代码、预训练权重及测试样例作者为资深算法工程师在计算机视觉、目标检测和智能优化等领域经验丰富项目结构清晰便于二次开发和后续微调扩展。1. 车牌检测识别方案选型为什么是 YOLOv5 LPRNet CCPD停车场闸机、门禁、高速稽查这类场景里识别系统真正要回答的问题有两个车牌在画面哪里以及这块车牌上的字符串是什么。基于 YOLOv5 和 LPRNet 做车牌检测识别是目前离线部署里非常成熟的一路分解方案YOLOv5 先把车牌目标框出来LPRNet 再把裁剪下来的小图转成文本训练数据用 CCPD 数据集打底。相比端到端模型一把梭两段式的最大优势是每个环节都能单独调参、单独换模型出问题也容易定位。这套方案适合手上有一张 GPU 卡、想训练自己的车牌识别模型并做私有化离线部署的团队或个人。2. CCPD 数据集用起来从目录结构到 YOLO 标签与裁剪图2.1 先认清 CCPD 的目录与文件名编码CCPD 全称 Chinese City Parking Dataset是一个大规模真实道路场景车牌数据集。官方按拍摄难度分为 ccpd_base、ccpd_blur、ccpd_challenge、ccpd_rotate、ccpd_tilt、ccpd_weather 等子集base 是相对清晰的场景rotate 和 tilt 带倾斜weather 覆盖雨雪天气。我一般会把 base、rotate、tilt、weather 各拿一部分混合训练只用 base 训练出来的模型一遇到倾斜和恶劣天气就会明显变脆。这个数据集最特别的地方是文件名本身就是标注。每个文件名用-分隔成多段里面顺次编码了车牌目标的外接框、四个角点、倾斜角度、亮度、模糊程度等信息。网上有些资料说车牌字符也能从文件名里解码出来但我的实际操作经验是与其花时间研究官方字符解码不如先用文件名里的 bbox 和四角点把检测、识别两个模型的训练数据准备好。LPRNet 的字符标签用合成车牌数据补齐效果反而更可控。动手之前先跑一遍探勘脚本把当前这批文件的字段顺序打印出来。不同渠道下载的 CCPD 命名可能略有差异下面的代码按最常见的字段顺序写第 3 个-段是外接框第 4 个-段是四个角点。# inspect_ccpd.py import glob import os imgs sorted(glob.glob(ccpd/ccpd_base/*.jpg))[:5] for p in imgs: name os.path.basename(p).split(.)[0] fields name.split(-) for i, f in enumerate(fields): print(i, f) print(---)每个子集都建议先跑一遍这段探勘不要默认所有子集字段顺序完全一致。打印出来以后你很容易看到类似128213_435365这种坐标对格式其中_左右是两个点分隔 x 和 y。确认坐标落在哪个字段再写正式转换脚本能省掉不少返工。提示CCPD 的图像尺寸并不统一转换 YOLO 标签时不能按固定宽高算必须从原图读取 H 和 W这一点在后面的脚本里会体现。2.2 解析 bbox 生成 YOLO 标签YOLOv5 的标签格式是class cx cy w h且 cx、cy、w、h 都是相对图片宽高的归一化值。CCPD 给的是绝对像素坐标所以要做一次换算把左上角和右下角两个点转成中心点加宽高的形式。下面这段脚本把 ccpd_base 的所有图片转成 YOLO 标签输出目录是labels_ccpd_base。# ccpd_to_yolo.py import os import glob import cv2 src_dir ccpd/ccpd_base label_dir labels_ccpd_base os.makedirs(label_dir, exist_okTrue) imgs glob.glob(os.path.join(src_dir, *.jpg)) def parse_bbox(fields): # 第 3 段格式x1y1_x2y2 lt, rb fields[2].split(_) x1, y1 map(int, lt.split()) x2, y2 map(int, rb.split()) return x1, y1, x2, y2 for img_path in imgs: name os.path.basename(img_path).split(.)[0] fields name.split(-) x1, y1, x2, y2 parse_bbox(fields) img cv2.imread(img_path) H, W img.shape[:2] cx ((x1 x2) / 2) / W cy ((y1 y2) / 2) / H bw (x2 - x1) / W bh (y2 - y1) / H label_path os.path.join(label_dir, name .txt) with open(label_path, w) as f: f.write(f0 {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}\n)逻辑很简单按-切出字段取出外接框坐标再用 OpenCV 读原图宽高做归一化最后写成一个一行 txt。class 固定写 0因为整个项目只检测一个类别「车牌」。如果你以后要区分蓝牌、绿牌、黄牌就需要把 class 改成多类再额外解析文件名里的颜色字段但在多数业务场景下单类别检测已经够用。几个参数细节cx、cy、bw、bh都必须是 0 到 1 之间的小数。YOLOv5 训练时如果读到小于 0 或大于 1 的坐标会直接当成损坏样本跳过。如果解析出来的宽高明显异常多半是字段顺序判断错了把fields[2]原样打印出来核对。还有一个容易踩的地方部分 CCPD 图片里 bbox 越出图像边界转换脚本里最好加一个 clip把 x1、y1 保底到 0x2、y2 顶到 W、H防止训练过程出现坐标越界。2.3 用四角点裁出归一化车牌图YOLOv5 训练只需要 bbox但 LPRNet 识别模型不希望输入里带着大块车身背景。CCPD 文件名里的四个角点正好能把倾斜车牌拉成一个水平矩形。这个透视矫正不仅在训练识别模型时必要部署阶段同样关键先把角点数据抽取出来。# extract_plate_quads.py import os import glob import cv2 import numpy as np src_dir ccpd/ccpd_base out_dir plate_crops os.makedirs(out_dir, exist_okTrue) imgs glob.glob(os.path.join(src_dir, *.jpg)) def parse_quads(fields): # 第 4 段格式x1y1_x2y2_x3y3_x4y4 parts fields[3].split(_) pts np.array( [list(map(int, p.split())) for p in parts], dtypenp.float32 ) return pts dst_pts np.array([ [0, 0], [93, 0], [93, 23], [0, 23] ], dtypenp.float32) for img_path in imgs: name os.path.basename(img_path).split(.)[0] fields name.split(-) src parse_quads(fields) img cv2.imread(img_path) M cv2.getPerspectiveTransform(src, dst_pts) warp cv2.warpPerspective(img, M, (94, 24)) cv2.imwrite(os.path.join(out_dir, name .jpg), warp)透视变换用cv2.getPerspectiveTransform计算单应矩阵再把原图映射到 94x24 的固定尺寸。dst_pts 定义了输出矩形的四个顶点顺序必须和 src 点一一对应否则裁出来的图是翻转或者扭曲的。CCPD 文件里角点顺序一般是连续排列的如果发现裁出来的图是镜像就调整 dst_pts 的排列顺序。这一步值得花时间做对。LPRNet 的设计输入是水平车牌图直接把倾斜的检测框裁剪图送进去识别准确率会掉得很难看。在训练 LPRNet 时用这批透视矫正后的 crop后面能省掉大量「为什么识别不准」的排查时间。存下来的 crop 图就是后续识别模型的训练语料。2.4 训练/验证划分与运行检查检测和识别的数据划分逻辑不同。YOLOv5 需要按图划分保证同一个场景的 train 和 val 不重叠LPRNet 按上一节生成的 crop 图做随机划分即可。这里先把 YOLOv5 的划分写好生成 train.txt 和 val.txt。# split_train_val.py import os import random random.seed(42) img_dir ccpd/ccpd_base names [p[:-4] for p in os.listdir(img_dir) if p.endswith(.jpg)] random.shuffle(names) val_n int(len(names) * 0.1) with open(train.txt, w) as f: for n in names[val_n:]: f.write(os.path.join(img_dir, n .jpg) \n) with open(val.txt, w) as f: for n in names[:val_n]: f.write(os.path.join(img_dir, n .jpg) \n)固定随机种子 42保证每次复现划分一致。train.txt 里每行是图片路径YOLOv5 训练时会按路径前缀找到对应的 label。label 目录的命名要符合 YOLOv5 约定图片在ccpd/ccpd_base/xxx.jpglabel 最好放在labels/ccpd_base/xxx.txt然后在数据配置里把 label 路径对应清楚否则训练日志里会出现大量 warning。所有脚本跑完后随机挑 20 张图把 YOLO 标签画回原图上检查一遍。这步是质检别省。标签和角点解析错位的问题在这种可视化检查下会立刻现形比训练完再发现翻车成本低太多。3. 用 YOLOv5 训练车牌检测模型配置、超参数与命令3.1 数据配置与模型配置YOLOv5 的训练从数据配置开始。这里要写一个ccpd.yaml告诉训练脚本训练集、验证集、类别数和类别名。类别只有一个plate配置非常简单。# ccpd.yaml train: train.txt val: val.txt nc: 1 names: [plate]train 和 val 指向第 2 章生成的 txt 文件。路径写相对路径时训练命令的工作目录必须和这个 yaml 所在目录一致或者干脆写绝对路径。我见过不少人在这一步把路径写错训练半天数据没加载进去loss 在初始水平来回晃排查起来非常恼火。模型结构配置方面默认的yolov5s.yaml按 COCO 的 80 类设计的需要把nc改成 1。更省事的做法是复制出一份yolov5s_plate.yaml只改nc: 1其他参数先不动。如果不想改文件也可以命令行传--cfg但我不推荐留一个独立配置文件日后追踪实验方便。车牌检测的模型尺寸不需要太大。yolov5s在绝大多数场景够用yolov5n可以留给树莓派 4B 这类低算力设备。如果业务里同时存在近距离大车牌和远处小车牌可以先上yolov5m但参数量和显存占用涨得很快不要一上来就选大模型。先跑 s验证集指标不够再决定要不要加大。3.2 启动训练命令与关键超参数数据就绪后训练命令没有太多玄学。参考命令如下python train.py \ --data ccpd.yaml \ --cfg yolov5s_plate.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch-low.yaml--weights yolov5s.pt从 COCO 预训练权重起步迁移学习能显著缩短收敛时间。车牌检测虽然和 COCO 类别无关但底层对边缘、纹理的提取能力是通用的。--img 640是训练输入尺寸如果后续部署设备算力有限可以降到 480但小目标检测能力会下降。--epochs 100是起步值实际我一般先跑 50 轮看 loss 曲线再决定要不要加跑。超参数文件里最值得动的是 mosaic。YOLOv5 默认在前几个 epoch 开启 mosaic 数据增强把 4 张图拼在一起训练这对车牌这种中小目标帮助很大。车牌在整幅画面里占比通常不大mosaic 能模拟出更多尺度变化。如果验证集上小目标 recall 不够可以把 mosaic 概率维持在高位同时把mixup稍微调低避免增强过猛导致收敛变慢。学习率也值得说一句。hyp.scratch-low.yaml的lr0默认 0.01配合 batch-size 16 时还算稳定。如果显存有限batch-size 只能开到 8最好把lr0同步降一半否则大学习率加小 batch 容易在前期震荡。这个调整没有绝对公式我的习惯是看前 10 轮 loss 曲线如果 loss 先跳上去再慢慢下来就是学习率偏大。3.3 训练产物与评估指标训练结束后results 目录下会生成best.pt和last.pt另外还有confusion_matrix.png、results.png、val_batch*.jpg这些可视化产物。评估时看三个指标mAP0.5、mAP0.5:0.95、PR 曲线。对车牌检测来说mAP0.5 应该跑到 0.98 以上才算及格因为车牌目标形态很固定背景复杂度远低于 COCO。单独跑一遍验证集用 val.py 看单个类别的 APpython val.py \ --data ccpd.yaml \ --weights runs/train/exp/weights/best.pt \ --img 640 \ --conf-thres 0.25 \ --iou-thres 0.45--conf-thres 0.25是推理时的置信度阈值。在车牌场景里部署时我习惯把阈值放到 0.3 到 0.4 之间。阈值太低会把车身上的反光区域当成车牌阈值太高会漏掉远处的小车牌。如果部署后漏检严重优先调低 conf而不是急着重新训练。YOLOv5 在训练过程中会自动做 anchors 自适应计算所以不需要手动改 anchors。如果你关闭了 autoanchor或者想手动指定可以用 YOLOv5 自带的 kmeans_anchors 脚本基于我们自己的标签重新聚类。但在默认开着 autoanchor 的情况下这个环节不用折腾。3.4 训练自己的数据集常见误用与调整方向说到训练自己的数据集最容易踩的坑有三个。第一直接拿 COCO 预训练模型来推理不重新训练效果必然差因为车牌在 COCO 里不是独立类别。第二把所有 CCPD 子集混在一起时不看数量分布blur 和 weather 子集样本量少容易被 base 淹没训练权重偏向清晰场景。我一般每个子集抽样让 base 占总样本量 60% 到 70%其余子集占 30% 到 40%。第三忽略输入分辨率对车牌这种小目标的影响。如果原图里车牌只有 20x40 像素训练输入是 640 且没开多尺度增强模型基本学不到细节。YOLOv5 默认开启多尺度训练但如果你用--img 640的同时手动关掉了--multi-scale等于放弃低分辨率下小目标泛化的机会。车牌检测我一般保持多尺度默认开启。如果训练完发现验证集 F1 不理想不要急着换网络结构先检查标签有没有错位再看数据分布。车牌检测模型的结构非常成熟绝大多数精度问题出在数据和后处理上而不是模型本身。4. 训练 LPRNet 识别模型字符集、CTC Loss 与数据预处理4.1 字符集与 CTC 对齐原理LPRNet 是典型的轻量级车牌识别网络核心思路是输入一张不定长车牌图输出一个序列概率再用 CTC 把序列和真实字符串对齐。相比「先切字符再逐字识别」的老方案它不需要精确的字符分割训练和部署都省很多事。中国民用车牌字符包括省份汉字、字母和数字。省份简称 31 个字母排除 I 和 O 后剩 24 个数字 10 个加起来 65 类再加一个 CTC blank模型输出维度就是 66。有些业务要额外加挂车、使馆、警用等特殊字符看你的范围这里先按普通民用车牌算。CTC 的 blank 用来吸收字符之间的空白和重复。比如车牌「皖A12345」网络可能预测出类似「皖皖AAA11223445」的冗余序列CTC 解码会把重复字符合并再删掉 blank最终还原成目标字符串。这也是 LPRNet 能处理不定长字符的根本原因。4.2 数据加载与预处理LPRNet 的输入不是原图透视矫正后的 94x24 crop。预处理管线是转灰度、缩放到 94x24、归一化到 [-1, 1]。这里的归一化用全局 0.5 附近的值即可不需要像 ImageNet 那样精确统计均值方差。数据集类如下# lpr_dataset.py import os import cv2 import numpy as np import torch from torch.utils.data import Dataset class LPRDataset(Dataset): def __init__(self, img_dir, label_file, char2idx): self.paths [] self.labels [] with open(label_file, r) as f: for line in f: path, text line.strip().split(,) self.paths.append(os.path.join(img_dir, path)) self.labels.append(text) self.char2idx char2idx def __len__(self): return len(self.paths) def __getitem__(self, idx): img cv2.imread(self.paths[idx], cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (94, 24)) img (img / 255.0 - 0.5) / 0.5 tensor torch.from_numpy(img).unsqueeze(0).float() label_text self.labels[idx] label_ids [self.char2idx[c] for c in label_text] label_tensor torch.tensor(label_ids, dtypetorch.long) return tensor, label_tensor, len(label_ids)label_file 的格式是每行图片名,车牌文字。如果拿不到 CCPD 的官方字符标签可以对第 2 章裁出的 crop 人工标注几千张或者用合成车牌生成器批量生成。注意 LPRNet 对字符集之外的字符无能为力训练集里出现过哪些字符模型才有可能识别哪些字符。cv2.resize用默认的 INTER_LINEAR 即可。网上有说法认为车牌字符边缘要保持锐利建议缩小用 INTER_AREA但在 94x24 这个尺寸下差异很小追求速度就用默认。4.3 训练循环与 CTC 解码训练使用torch.nn.CTCLoss。LPRNet 输出的时间步数 T 由网络结构决定常见配置下是 18 步每一步输出 66 类的 logits。CTCLoss 需要模型输出的 log 概率、真实标签序列、输入长度、真实标签长度。# lpr_train.py 关键片段 import torch import torch.nn as nn # model 按 LPRNet 原论文结构搭建backbone 序列层 model LPRNet(class_num66) criterion nn.CTCLoss(blank0) optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(epochs): model.train() for images, labels, label_lens in loader: images images.to(device) logits model(images) # (T, B, 66) log_probs logits.log_softmax(dim2) input_lens torch.full( (images.size(0),), logits.size(0), dtypetorch.long ) loss criterion(log_probs, labels, input_lens, label_lens) optimizer.zero_grad() loss.backward() optimizer.step()注意blank0必须和模型输出维度的第 0 类对应。也就是说字符集里第 0 号要留给 blank真实字符从 1 开始索引。如果字符编码让真实字符占用了 0 号CTCLoss 会报错或者训练出乱识别模型。这个问题排查起来很隐蔽先确认char2idx里 0 不是任何真实字符。训练到 loss 不再下降时用贪心解码验证。贪心解码是取每个时间步概率最大的类别再合并重复、删掉 blank。它虽然不是最优解但足以反映模型是否学到位部署阶段也够用。def greedy_decode(log_probs, idx2char): pred log_probs.argmax(dim2).permute(1, 0) # (B, T) plates [] for row in pred.cpu().numpy(): chars [] prev None for t in row: if t ! 0 and t ! prev: chars.append(idx2char[t]) prev t plates.append(.join(chars)) return plates这段解码逻辑要挂在每轮验证集上跑统计字符级准确率。另外注意车牌长度不一致时PyTorch 的 DataLoader 默认不支持在一个 batch 里放不同长度序列需要自己写collate_fn把 label 拼成 padded 张量同时维护每个样本的真实长度。这个细节漏掉的话训练循环会一直报 shape mismatch。合成车牌数据的字符边缘和真实拍摄图有明显差异只拿合成数据训练 LPRNet部署到真实场景会掉点。我的做法是「合成数据打底 几百张真实 crop 微调」模型对真实光照、污渍、铆钉干扰的鲁棒性会好很多。5. 车牌检测识别常见问题排查5 个典型翻车现场5.1 检测框差一个边识别结果直接乱码现象YOLOv5 框出来的车牌看起来挺准但裁剪后送进 LPRNet输出车牌号几乎全错。原因LPRNet 的输入必须是一张基本水平的车牌图。检测框「准」只代表它贴合车牌可如果车牌在画面里是倾斜的直接按轴对齐 bbox 裁剪会把背景和相邻车身带进来字符比例被压缩序列对不上。解决部署阶段不要直接拿 bbox 裁剪而是先对检测区域做透视矫正。第 2 章里 CCPD 有角点标注实际推理时没有角点可以对检测框上下各扩 10%再按车牌长宽比直接 resize 到 94x24如果倾斜严重需要额外训练一个角点回归模型。大部分场景下工程上的扩边加 resize 能解决多数问题别急着加网络。5.2 省简称总是识别错现象粤、闽、桂这类省份简称频繁被识别成字形相近的字比如「闽」变「门」「皖」变「晚」。原因LPRNet 是序列模型汉字字符的训练样本如果太少或者和某些字形接近的字符共享了太多视觉特征就容易混淆。CCPD 本身是安徽车牌为主「皖」出现多其他省份简称出现少模型对少见汉字的先验不足。解决训练数据里做省份简称均衡。把 31 个省份简称做成合成样本确保每个简称在训练集里至少出现几百次。另外在解码后加一步字典校验输出第一位强制映射到合法省份简称第二位强制映射到合法字母。这个后处理成本极低但能挡掉相当多低级错误。5.3 夜间反光把车牌打成「无牌」现象夜间场景下 YOLOv5 漏检率高或者模型检出了但车牌有反光LPRNet 输出为空字符串。原因夜间图像对比度低车灯照射下车牌区域局部过曝字符边缘被高光抹掉。如果训练集里夜间样本比例小模型自然学不到这种分布。解决从数据和工程两头补。数据层面把 ccpd_nature、ccpd_weather 等子集按比例混进训练再加随机亮度、对比度、高斯噪声增强。工程层面部署前对输入图像做自适应直方图均衡化提高暗部细节YOLOv5 推理置信度阈值从 0.5 降到 0.3 附近。如果还不行检查摄像头曝光参数车牌区域过曝通常是因为摄像头 HDR 没开启。5.4 训练初期显存溢出或者 loss 一直不降现象YOLOv5 或 LPRNet 训练一开始就 OOM或者 loss 前 10 轮纹丝不动。原因OOM 大多是 batch-size 太大或输入尺寸太高。loss 不降的原因更复杂常见三类标签文件是空的或路径没对上、学习率设置太高导致震荡、字符集索引和 CTCLoss blank 冲突。解决OOM 先减 batch-size从 16 降到 8 或 4或者把--img从 640 降到 512。loss 不降时先用python detect.py跑一张训练集图片确认模型能检出东西再单独打开 label 文件检查标签内容是否正常如果训练 LPRNet先单独打印一个 batch 的输入和 loss核对输入序列长度是否和标签对得上。先排除数据问题和索引问题不要一上来就玄学调参。5.5 车身反光和高亮区域被当成车牌现象检测模型把保险杠上的镀铬条、车灯边缘、甚至白色车身反光区域框出来置信度还挺高。原因单类别检测器只能靠视觉特征区分目标车牌和这些高反光区域的边缘、形状有相似之处。CCPD 虽然全是真实车牌但负样本类型不够丰富模型没有见过足够多「长得像车牌但不是车牌」的硬负样本。解决在训练数据里加入一批纯负样本图片这些图片里根本没有车牌标签文件保持空让 YOLOv5 的 loss 学会抑制这类输出。部署端再给每个候选框加一道 HSV 颜色校验普通蓝牌的主色调集中在蓝色区间绿色新能源牌集中在绿色区间饱和度明显不对的候选框直接丢弃。这个颜色校验逻辑极快放在 NMS 之后能把误检率压掉一大截。6. 让识别更稳串联后处理与视频流部署验证检测和识别模型单独都准串起来不一定会稳。完整推理流程是YOLOv5 输出候选框并做 NMS再对每个候选框做裁剪和透视矫正然后灰度化、resize 到 94x24、归一化最后送 LPRNet 解码。串接代码看起来很短但每一步顺序都不能乱。def plate_ocr(frame, det_model, rec_model): preds det_model(frame)[0] if preds is None or len(preds.boxes) 0: return [] out [] for box in preds.boxes.data.cpu().numpy(): x1, y1, x2, y2, conf, cls box crop frame[int(y1):int(y2), int(x1):int(x2)] crop cv2.cvtColor(crop, cv2.COLOR_BGR2GRAY) crop cv2.resize(crop, (94, 24)) crop (crop / 255.0 - 0.5) / 0.5 t torch.from_numpy(crop).unsqueeze(0).unsqueeze(0).float() logits rec_model(t.to(device)) plate greedy_decode(logits, idx2char)[0] out.append(([x1, y1, x2, y2], plate)) return out视频流里单帧识别结果很容易抖动。我的做法是加一个帧间投票窗口连续 3 帧出现相同车牌号才输出输出后如果后续帧出现不同结果再进入新的投票周期。这个机制能滤掉单帧噪点尤其适合夜间闪烁场景代价是多等约 100ms 延迟对闸机和门禁场景完全可以接受。部署阶段模型导出的优先级是 ONNX再按设备走 TensorRT 或 RKNN。YOLOv5 自带 export.py 可以导出 ONNXLPRNet 结构简单转 ONNX 也不难。如果在 RK3568 这类边缘盒子上做量化先做 fp16 验证精度再尝试 int8。车牌识别对字符边缘敏感int8 量化后掉点常常超过 5 个点量化时必须用代表性车牌图做校准集不要拿空白图凑数。验证时不要只看整牌准确率一个字符错就是整牌错。我会同时统计字符级编辑距离和整牌准确率整牌准确率反映业务成功率编辑距离告诉你离修好还差几个字符。调优时优先处理编辑距离为 1 的样本这类往往是最容易救回来的。我当初把注意力全放在整牌准确率上改了半个月没进展改成看编辑距离后才发现大量错误只是「0」和「O」的替换一个后处理规则就解决大半。这套方案做到最后瓶颈通常不在模型结构而在数据均衡和后处理防抖上希望帮到你。本文还有配套的精品资源点击获取