简介:面向深度学习与图像处理开发者的文字语种识别项目,基于卷积神经网络构建,解决多语言文字自动分类问题,可用于跨语言文档处理与智能审核等场景。压缩包共12个文件,其中9个Python脚本覆盖模型搭建(含VGG、ResNet、LSTM及SPP层)、训练与评估流程;2个txt文件提供配置说明或数据指引;1个Markdown文档便于快速上手。资源包整体仅16KB,结构紧凑,适合有一定神经网络基础的读者学习参考。项目融合多种模型对比与数据增强思路,可从核心脚本中掌握图像预处理、特征提取到语种判别的完整实现路径,尤其有助于理解CNN在文字形态与笔画结构识别上的应用。当前已有155人学习,值得作为语种识别算法的入门范例与扩展基础。
1. 这个压缩包在解决什么问题:一张文字图像怎么被 CNN 认出来是哪国话
做文档 OCR 的人都有过这种经历:拿到一张扫描件,先得搞清楚里面印的是中文还是英文、是俄文还是阿拉伯文。OCR 引擎是按语种拆的,你拿一个中英文混合模型去认泰文,输出全是乱码。最笨的办法是每个语种引擎各跑一遍再比对得分,慢不说,误判率还高。所以我第一次看到“基于卷积神经网络文字语种识别算法.zip”这个压缩包时,第一反应就是:这不就是我要的预分类器吗。它把文字图像直接送进卷积神经网络,输出一个语种概率分布,不用先做字符切分,也不依赖词典。这篇笔记我就按自己的实操顺序,把里面涉及的原理、代码、参数和踩过的坑完整写出来,给同样在做 OCR 前置处理或文档分类的工程师做参考。
2. 为什么语种识别首选 CNN:从字符形状到全局纹理,卷积在学什么
2.1 语种识别是分类任务,但输入不是字符串而是图像
文字语种识别听起来像是文本分类,实际上在图像场景里,它更像“看”字形。中文文本里汉字一大堆,笔画横平竖直,结构方方正正;英文单词由 26 个字母拼成,高度基本一致,有大量圆弧和竖向笔画;阿拉伯文从右往左连写,字符高度落差大;日文满屏平假名、片假名,韩文则大量出现圈圈和横竖组合。这些差异在图像上非常明显,比直接分析字符串更直观。
所以常见的做法是把语种识别当作图像分类任务:输入一张文本行截图,输出语种标签。这个方案的好处是,不需要先 OCR 出一串字符,也不依赖字体编码和语言模型;坏处是,你要处理好字型、字号、背景噪声这些无关因素。另一种路线是文本分类,比如用 TextCNN 或 LSTM 对字符序列建模,但它只适合已经拿到干净字符串的场景。压缩包里的算法走的是图像路线,这也是落地中最常遇到的输入形态:扫描件、手机拍照、截图,都是图像。
这里有一个容易混淆的点:语种识别不是 OCR。OCR 要回答“图里有哪几个字、它们的排列顺序”,语种识别只回答“这段文字是什么语言”。前者是序列标注问题,后者是分类问题。分类问题用卷积神经网络天然合适,因为它不需要建模字符之间的长程依赖,只需要抓住文字整体外观上的强烈统计特征。换句话说,就算一张图中的字一个都认不出来,只要字形风格和标点特征在,CNN 就能把语种猜个八九不离十。
2.2 输入张量怎么构造:尺寸、通道和归一化
CNN 的输入是固定尺寸的张量。语种识别通常用灰度图就够,因为颜色本身与语种无关,反而可能让模型学到“黄色背景 = 中文”这种假特征。如果数据来源是彩色 PDF,我一般会在预处理里先做灰度化,再按需保留 RGB 做对照实验。通道数量不是越多越好,灰度加简单对比度增强往往比三通道更稳,尤其在老式扫描件上,彩色信息常常是噪声。
尺寸的坑在于宽高比。文本行图像天然是长条形的,如果强行缩放到 64×64 正方形,字会被压扁,中文的撇捺和英文的弧线都变形。我的做法是固定一个高度,比如 32,宽度按文本长度裁剪后在 32 到 128 之间浮动;如果网络要求固定尺寸,就把宽高比超过一定范围的图先缩放再补边,保持字形不被压。下表是我在实验中常用的三组参数:
| 图像尺寸 (H×W) | 通道 | 适用场景 | 备注 |
|---|---|---|---|
| 64×64 | 单通道 | 印刷体字符截图 | 正方形输入,网络最简单,适合快速验证 |
| 32×128 | 单通道 | 文档文本行 | 保持文本行长条比例,需要宽卷积核 |
| 96×320 | 单通道 | 含复杂背景的拍照文本 | 分辨率高,模型更重,训练慢 |
归一化直接用除以 255 再做 z-score 也行,但要注意推理时一定要复用训练时的均值方差,否则灰度分布差一点就翻车。数据增强方面,随机加高斯噪声、轻微透视变形、模糊,对语种识别都有帮助。我见过一个项目因为少了“随机亮度扰动”,换了个暗的扫描仪后准确率直接从 97% 掉到 85%,原因就是模型学会了绝对亮度而不是相对纹理。
2.3 网络结构:从 LeNet-5 到 ResNet,语种识别需要多深的网络
卷积神经网络的核心是局部感受野和权值共享。语种识别里,第一层卷积学到的是横竖撇捺、圆弧、角点这些基础笔画;第二层卷积把笔画组合成部首、字母连笔甚至单词轮廓;再往上就是全局的文本纹理。所以关键不是“网络多深”,而是感受野能不能覆盖到一个字符或者几个字符。LeNet-5 这种经典结构的感知范围对 64×64 的输入就够用了,原本是识 MNIST 手写数字的,简单迁移到语种分类上效果也不差。如果换成 ResNet-18,精度会高一点,但训练时间会长很多;对于 6 到 10 类别的语种识别,一个大致规律是没必要超过 ResNet-18,更深的网络容易在中小数据集上过拟合。
关于“卷积神经网络的汇聚层”,也就是池化层,它在语种识别里的作用是降低特征图冗余、提供平移不变性。文字在图像中的位置不一定居中,同一个汉字偏左或偏右都正常,池化能让模型对这种位移不那么敏感。MaxPooling 比 AveragePooling 在笔画边缘提取上更锐利,但全局平均池化(AdaptiveAvgPool2d)作为全连接层之前的最后一层,往往比直接 Flatten 更稳,因为它把每个通道压缩成一个统计量,大大减少参数。
我实际用的一个小网络结构如下,它类似简化版 VGG,三层卷积加全局平均池化。注意这里的“汇聚层”就是池化层,参数选 kernel=2, stride=2,这是最常见的选择,特征图尺寸直接减半。
| 层 | 输出尺寸 | 参数 |
|---|---|---|
| Conv1 + ReLU + BN | 64×64×32 | 3×3, padding=1 |
| MaxPool | 32×32×32 | 2×2, stride=2 |
| Conv2 + ReLU + BN | 32×32×64 | 3×3, padding=1 |
| MaxPool | 16×16×64 | 2×2, stride=2 |
| Conv3 + ReLU + BN | 16×16×128 | 3×3, padding=1 |
| MaxPool | 8×8×128 | 2×2, stride=2 |
| AdaptiveAvgPool | 1×1×128 | 全局池化 |
| Dropout + FC | 6 | 全连接分类 |
这种结构参数量在百万级,单张 64×64 的图在 CPU 上跑一次预测只需几毫秒,很适合作为 OCR 流水线的第一个前置模块。
3. 复现这个算法的最小方案:数据、训练与推理代码
3.1 数据准备:用合成数据和公开语料生成多语种图文样本
训练语种识别模型,最怕的是数据集里只有一种字体。真实文档会用宋体、黑体、Helvetica、Times New Roman,还有手写体。我不建议直接去爬网图,而是先用合成数据把模型基础打稳,再用真实样本微调。合成数据的做法很简单:用 Python 的 PIL 在随机背景上渲染文字。
下面这段代码生成 6 个语种的文本图像,顺便把字体路径和示例字符集列出来。注意,每种语言要使用独立字体,否则模型会偷懒,通过“同一个字体”来猜语种。
import numpy as np from PIL import Image, ImageDraw, ImageFont, ImageFilter # 语种对应的字体路径和示例字符集,实际项目里请换成你自己的字体文件 LANG_FONTS = { 'zh': ('fonts/NotoSansCJK-Regular.ttc', '中文语种识别测试,混合汉字与标点。'), 'en': ('fonts/arial.ttf', 'The quick brown fox jumps over the lazy dog.'), 'ja': ('fonts/NotoSansJP-Regular.ttf', '日本語の文章を生成してモデルを訓練します。'), 'ko': ('fonts/NotoSansKR-Regular.ttf', '한국어 텍스트를 사용하여 모델을 학습합니다.'), 'ru': ('fonts/NotoSans-Regular.ttf', 'Это русский текст для проверки распознавания.'), 'ar': ('fonts/NotoNaskhArabic-Regular.ttf', 'هذا نص عربي لاختبار التعرف على اللغة.'), } def render_sample(lang, output_size=(64, 64)): font_path, text = LANG_FONTS[lang] # 随机选一个字体大小,模拟不同字号 font_size = np.random.randint(18, 28) font = ImageFont.truetype(font_path, font_size) fg = (np.random.randint(0, 80),) * 3 # 深色前景 bg = np.random.randint(180, 256) # 浅色背景 img = Image.new('L', (256, 64), bg) draw = ImageDraw.Draw(img) draw.text((8, 8), text, font=font, fill=fg) # 随机加一点噪声和模糊,模拟扫描件 img = img.filter(ImageFilter.GaussianBlur(radius=np.random.uniform(0, 0.8))) img = img.resize(output_size) return np.array(img, dtype=np.float32) / 255.0逻辑说明:render_sample先创建一个 256×64 的灰度图,背景色是随机浅色,文字是固定深色。高斯模糊半径在 0 到 0.8 之间随机,这一步能让模型对扫描模糊更鲁棒。最后统一缩放到 64×64,并除以 255 归一化到 [0,1]。
参数说明:output_size必须与模型输入一致,后面训练和推理都要用同一个值。font_size范围 18-28 是为了让字符在 64×64 的图里占 50%-80% 面积;如果字号太小,图像里大片空白,模型只能学到背景。fg取深色 (0-80) 是为了保证前景与浅色背景的对比度足够;如果你要模拟白底黑字的扫描件,这个范围没问题。
生成数据时,不要只生成一批就固定下来。我建议每个 epoch 都重新调用render_sample生成新样本,相当于无限量数据增强。配合torch.utils.data.Dataset可以这样写:
from torch.utils.data import Dataset class SynthLangDataset(Dataset): def __init__(self, lang_list, samples_per_epoch=5000): self.lang_list = lang_list self.samples_per_epoch = samples_per_epoch def __len__(self): return self.samples_per_epoch def __getitem__(self, idx): # 每次随机选一个语种,从对应字符集里随机截取一段文本 lang = np.random.choice(self.lang_list) # 这里为了简化,直接用 render_sample,实际可以传入随机文本 x = render_sample(lang) y = self.lang_list.index(lang) return torch.tensor(x).unsqueeze(0), torch.tensor(y, dtype=torch.long)Dataset每次__getitem__都调用render_sample,所以同一个 epoch 内不会出现重复样本。这样你甚至可以省掉单独的验证集,因为模型永远看不到重复样本。但要注意,验证阶段要换用固定种子或固定一批真实样本,否则无法稳定评估。
3.2 定义 CNN 模型:一个能跑通的可调网络
模型定义我采用三层卷积加全局平均池化的结构,适合输入尺寸不固定的情况。全局平均池化会把最后一层特征图压缩成 1×1,这样全连接层尺寸不会绑定输入分辨率,以后想换 32×128 的输入也不用改全连接层。
import torch import torch.nn as nn class LangNet(nn.Module): def __init__(self, num_classes=6): super().__init__() self.features = nn.Sequential( nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(32), nn.MaxPool2d(2), nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(64), nn.MaxPool2d(2), nn.Conv2d(64, 128, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(128), nn.MaxPool2d(2), ) self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.classifier = nn.Sequential( nn.Dropout(0.5), nn.Linear(128, 256), nn.ReLU(inplace=True), nn.Dropout(0.3), nn.Linear(256, num_classes), ) def forward(self, x): x = self.features(x) x = self.avgpool(x) x = x.flatten(1) return self.classifier(x)逻辑说明:输入是单通道灰度图,所以第一个nn.Conv2d的in_channels=1。三层卷积的通道数从 32 加到 128,每层后面都接 BatchNorm,因为语种识别数据是合成图像,分布波动大,BN 能稳定训练。MaxPool2d(2)把特征图尺寸减半,三层池化后 64×64 输入变成 8×8 特征图,再经过全局平均池化得到 128 维向量。
参数说明:num_classes是语种数量,如果是 6 类就传 6。Dropout 0.5 和 0.3 是调过的值,如果你发现训练集准确率远高于验证集,可以提高到 0.6 和 0.4;如果欠拟合,降到 0.3 和 0.2。AdaptiveAvgPool2d((1,1))不挑输入尺寸,所以这里不需要你算全连接层的输入维度。
这里有一个容易踩坑的点:如果你要使用 ImageNet 预训练模型,比如 ResNet18,需要把模型的第一个卷积层从 3 通道改成 1 通道,或者在预处理时把灰度图复制成 3 通道。很多开源项目没有处理这一步,直接加载预训练权重会报 shape 错误。我一般用上面这个小型自定义网络,因为语种识别任务简单,没必要引入大模型,而且自定义网络在 CPU 上推理也更快。
3.3 训练与评估:交叉熵、Adam 和早停的配套设置
训练部分的核心是:损失函数用交叉熵,优化器用 Adam 并加一点 weight decay,学习率用 1e-3 起步,每个 epoch 后计算验证集准确率并做早停。不要小看早停,语种识别模型在合成数据上很容易在某个 epoch 突然过拟合,停晚一点就白练了。
def train_epoch(model, loader, optimizer, criterion, device): model.train() total, correct, loss_sum = 0, 0, 0.0 for x, y in loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() out = model(x) loss = criterion(out, y) loss.backward() optimizer.step() total += y.size(0) correct += (out.argmax(1) == y).sum().item() loss_sum += loss.item() * y.size(0) return loss_sum / total, correct / total逻辑说明:标准训练循环。out.argmax(1)取概率最大的类作为预测,与标签y比较计算准确率。损失loss_sum用loss.item() * y.size(0)还原批量总损失,避免不同 batch 大小对平均 loss 造成偏差。
主训练循环里,我建议使用ReduceLROnPlateau或固定步长衰减的学习率调度器。如果验证准确率连续 3 个 epoch 不提升,就把学习率除以 10。下面是一个带早停的训练入口:
def train_model(model, train_loader, val_loader, epochs=30, lr=1e-3): device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model.to(device) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=lr, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau( optimizer, mode='max', factor=0.5, patience=3 ) best_acc = 0.0 for epoch in range(epochs): train_loss, train_acc = train_epoch(model, train_loader, optimizer, criterion, device) val_loss, val_acc = evaluate(model, val_loader, criterion, device) scheduler.step(val_acc) print(f"epoch {epoch+1}: train_acc={train_acc:.3f} val_acc={val_acc:.3f}") if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), "langnet_best.pt") if optimizer.param_groups[0]['lr'] < 1e-5: break # 学习率太小就停止参数说明:weight_decay=1e-4是 L2 正则,能抑制过拟合。ReduceLROnPlateau的mode='max'表示监控验证集准确率,当连续 3 个 epoch 不上升时,学习率减半。factor=0.5是减半速率。早停条件用学习率低于 1e-5 或 epoch 耗尽,简单直接。
evaluate函数和train_epoch的区别是不做梯度更新:
def evaluate(model, loader, criterion, device): model.eval() total, correct, loss_sum = 0, 0, 0.0 with torch.no_grad(): for x, y in loader: x, y = x.to(device), y.to(device) out = model(x) loss = criterion(out, y) total += y.size(0) correct += (out.argmax(1) == y).sum().item() loss_sum += loss.item() * y.size(0) return loss_sum / total, correct / total注意model.eval()会关闭 Dropout 和 BatchNorm 的统计量更新,这是必须的。很多人推理时忘记调用.eval(),导致每次预测结果都不一样,原因就是 Dropout 还在随机丢弃。
3.4 推理部署:单张预测和批量输出的实现
推理阶段最需要注意的是“预处理一致性”。训练时用 PIL 的Image.new('L')生成灰度图,推理时打开一张彩色图也要先转'L',再 resize 到同一个output_size,最后除以 255。如果你用 OpenCV 读图并 resize,得到的 BGR 通道和像素插值方式可能与训练不一致,准确率会掉 1-2 个点。
def predict(model, img_path, output_size=(64, 64)): from PIL import Image img = Image.open(img_path).convert('L') img = img.resize(output_size) # 与训练时的 resize 一致 x = np.array(img, dtype=np.float32) / 255.0 x = torch.from_numpy(x).unsqueeze(0).unsqueeze(0) # (1, 1, 64, 64) model.eval() with torch.no_grad(): out = model(x) probs = torch.softmax(out, dim=1).squeeze().tolist() return probs # 长度为语种类别的概率列表逻辑说明:unsqueeze(0)两次,第一次加 batch 维,第二次加 channel 维。因为训练时数据 shape 是(batch, 1, 64, 64),所以这里必须是四维张量。torch.softmax把输出变成概率,概率之和为 1。返回的probs列表可以让你根据置信度决定是否交给下游 OCR。
如果是批量推理,比如一次处理 1000 张文档图像,我建议把数据堆成 batch 一起送进模型,而不是写 Python 循环逐张预测。GPU 上的 batch 推理能快几十倍,CPU 上也有向量化加速。一个简单的做法是把所有图片预处理后的 numpy 数组堆在一起,用torch.from_numpy(np.stack(images))一次前向。
4. 语种识别模型训练常见问题排查:5 个让你翻车的坑
4.1 现象一:模型把所有输入都识别成同一语种
现象:训练了 20 个 epoch,准确率也有 80%,但拿出来测任何图,预测结果都集中在一个语种,比如全是中文。
原因:最常见的是类别样本不均衡,中文样本是其他语种的 3 倍,模型学会“放弃思考”直接输出先验概率最高的类别。另一个原因是最后一个全连接层的 bias 初始化过大,导致输出 logit 偏向某一类。学习率过大时也可能出现梯度爆炸,让分类层的权重全部退化为一个方向。
解决:先统计数据集中每个语种的样本数,用torch.utils.data.WeightedRandomSampler重采样,让每个语种每个 epoch 出现的次数接近。然后给CrossEntropyLoss传入weight参数,常用取值是1 / 类别样本数。最后把学习率降到 1e-4 重新训练几次,观察验证集准确率是否立刻变化。如果还是不行,手动把最后一个 Linear 的 bias 初始化为 0,再训练。
4.2 现象二:中文、日文、韩文互相混淆
现象:测试集上其他地方都准,只有中日韩三类之间频繁误判,中文图被预测成日文,韩文图被预测成中文。
原因:这三种语言共享大量汉字字形。中文“語”和日文“語”几乎长得一样,韩文汉字也经常出现。CNN 在局部感受野内很难判断一个字到底是中文还是日文,只能靠上下文中的假名或谚文来识别。如果训练样本里日文假名和韩文谚文占比太少,模型就直接用汉字字形猜了。
解决:训练数据生成时,日文文本不能只放汉字,要保证平假名、片假名占比不低于 40%。韩文文本一定要用谚文为主,汉字词占比降到 20% 以下。另一个有效技巧是把标点符号也作为特征保留,中文漏出来的句号“。”和日文的句号“。”还是有差别,英文的句点在图像上高度和位置上都不一样。我在数据集里专门加入了 20% 的“全角标点”样本,让模型学会关注标点。
4.3 现象三:训练损失直线下降,验证准确率卡在 70% 不上涨
现象:训练集准确率已经 99%,验证集准确率在 70% 附近怎么调都不动。
原因:这是典型的过拟合 + 分布不匹配。合成数据里的字体和验证集真实扫描件的字体不一样,模型记住了合成字体的纹理,比如衬线、笔画粗细,而这些纹理在真实图像上不存在。另一个原因是 Dropout 和 weight decay 太轻,正则没有起到作用。
解决:先加大正则强度,Dropout 从 0.5 调到 0.7,weight_decay从 1e-4 调到 5e-4。然后把合成数据的背景从纯色改成真实纸张纹理,可以在数据生成时叠加一个随机噪声图层。如果验证集约 3000 张,且标注成本可接受,可以额外收集 2000 张真实样本做微调,用合成数据预训练,真实数据微调,lr降到 1e-4 以下。这一步是解决分布不匹配最有效的手段,没有之一。
4.4 现象四:长文本和短文本的表现差异巨大
现象:对单行短文本(5 个字符以内)识别准确率 95%,对整段长文本(50 个字符以上)准确率掉到 70%,越长的文本越容易误判。
原因:固定 resize 到 64×64 时,长文本的每个字符被压缩成几个像素,笔画糊成一团,CNN 只能看到模糊的横线,原始语言结构全丢了。短文本则相反,每个字符都很清晰,模型可以按单字形状判断。这是固定输入分辨率方案最典型的坑。
解决:改成“固定高度、可变宽度”的输入策略。比如统一高度 32,宽度在 32 到 128 之间按文本原始比例缩放,然后用AdaptiveAvgPool2d去除全连接层对宽度敏感的依赖。如果网络只支持固定尺寸,那就对长文本做滑窗切割,每 64 像素切一块,分别预测语种,再对概率取平均。这个做法的代价是增加了推理时间,但长文本的准确率能回弹 10 个点以上。
4.5 现象五:换了一个字体,准确率崩了
现象:训练时只用了 Arial,测试时遇到 Times New Roman,英文语种准确率从 98% 跌到 50%。换成另一种中文字体,中文直接没了。
原因:模型在偷懒。它发现“Arial 的无衬线字形 → 英文”这个捷径,于是根本没有学习英文单词的结构特征,而是直接匹配字体轮廓。语种识别里,字体是比语种更强的视觉信号,不处理好字体,模型永远在学字体分类而不是语种分类。
解决:生成训练数据时,每个语种至少用 5 种不同字体,并且字体之间不能有交叉。比如中文字体用宋体、黑体、楷体;英文用 Arial、Times New Roman、Courier New。关键是要做到“同一个语种有多个字体”,让模型被迫忽略字体差异。验证集单独留出 2 种训练时没见过的字体,专门看泛化能力。如果换字体后准确率还崩,说明你的数据增强还不够,继续加旋转、透视、缩放等扰动。
5. 验证与进阶:用混淆矩阵和 CRNN 把语种识别准确率再往上提
验证模型是否真正学到语种特征,最直接的手段是混淆矩阵。你可以把测试集上所有预测结果和真实标签交给 sklearn,一行代码打印出矩阵。
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay # y_true 是真实标签,y_pred 是模型 argmax 后的预测 cm = confusion_matrix(y_true, y_pred) disp = ConfusionMatrixDisplay(confusion_matrix=cm) disp.plot()观察矩阵时重点看“偏斜”的地方。如果中日韩互相混淆,就去查那些错例图像里是不是汉字占比太大;如果英文和法文混淆,就看是不是因为法文重音字符在低分辨率下看不清。混淆矩阵能告诉你下一个加数据的方向,而不是盲目调网络。这个习惯我一直保留:先诊断再动手,绝不在看不见错误分布的情况下瞎调参数。
进阶方向我推荐把 CNN 和 LSTM 结合的 CRNN 架构。CNN 负责从图像中提取特征序列,LSTM 对序列建模上下文,再用 CTC 对齐到字符序列。这样不仅输出语种,还能顺带输出文字内容。训练时语种分类分支和字符识别分支可以共享主干网络,损失函数是两个任务的加权和。代价是训练复杂度上升,需要更多样本,但如果你最终目标是做小语种 OCR,这条路线值得投入。
部署加速方面,训练好的 PyTorch 模型可以转成 ONNX,用torch.onnx.export做一次导出,然后交给 ONNX Runtime 推理。在我的测试里,CPU 上单张 64×64 图像的推理时间可以从 3 毫秒降到 1 毫秒以下。要注意导出时的固定输入尺寸和训练保持一致,动态轴只在 batch 维度上开。
我现在拿到这类语种识别压缩包,会先不看模型结构,去找它的数据生成脚本和类别列表。因为语种识别七成功夫在数据上,网络结构只要别太离谱都能 work。这个方向确定值得做,尤其配合 OCR 流水线,能省下大量小语种适配成本。希望帮到你。
本文还有配套的精品资源,点击获取