
简介这份PDF文献面向数字人文、古籍数字化及OCR方向的研究者与工程实践者聚焦卷积神经网络在古籍汉字识别中的落地方法。古籍汉字属书法字体笔画黏连、模糊缺失且风格各异传统OCR难以套用固定模板资源正是围绕这一痛点展开。内容涵盖CNN基本概念、古籍识别挑战以及将识别问题转化为分类问题的完整实践借助数据生成技术构建训练集在TensorFlow平台上对773个汉字生成约24万个样本并让模型自行判定不可识别图片转由人工复核形成半自动化元数据加工工具。资源包共1个PDF文件大小约6.39MB便于直接阅读与存档。目前已有222人学习适合希望了解深度学习在古籍汉字识别中应用思路、数据增强与模型可靠性设计的读者参考。1. 古籍汉字识别为什么不能直接套现成 OCR一份 773 类 CNN 分类实践做过古籍数字化的人多半有过这种经历拿一套成熟的印刷体 OCR 去跑古籍扫描件现代铅印文本识别率能到 99%换成刻本、抄本、信札结果惨不忍睹。原因不复杂——古籍汉字属于书法字体笔画不横平竖直没有固定模板可匹配同一字在不同作品里粗细不同、笔画黏连纸张老化还带来笔画模糊与缺失。这些因素叠加传统 OCR 的特征工程路线基本走不通。这份文献给出的思路是把古籍汉字识别转换成卷积神经网络的分类问题用数据生成技术造训练集在 TensorFlow 上训练一个 773 类含简繁体加一个「不可识别」类的 CNN 分类器再拿真实古籍图片做测试。它面向的是数字人文场景下的元数据加工适合图书馆、档案馆做古籍字频统计、智能标点、语义分析的前置环节也适合想入门 CNN 图像分类的工程师拿一个真实约束缺训练集、样本噪声大来练手。2. 训练集从哪来字体生成 噪声模拟的完整参数真实古籍汉字标注样本极难获取公开的手写数据库HCL2000、CASIA-HWDB、SCUT-COUCH 等集中在简体标准体和现代手写体跟书法字形差距太大。这份实践的做法是绕开采集直接用字体文件生成训练样本再用图像处理模拟古籍的退化现象。2.1 字体选择与目标字表构建选取 10 种字体文件含细明体、黑体、宋体、隶书、楷书等TTF 与 TTC 格式每个汉字分简体、繁体两种写法去重后得到 773 个目标汉字。注意这里「去重」指的是简繁体相同的字只算一个类但训练时简繁体仍作为独立样本生成——这一点后面会引出简繁体误识的坑。字体文件建议覆盖三类风格印刷体宋体、黑体提供清晰结构书法体楷书、隶书提供笔画变形再补一两种名家字体文献中后期加入了王羲之、颜真卿提升多样性。字体越杂模型对笔画细节的泛化越好。2.2 单字图片生成与随机形变每个字每种字体生成 160 个带随机噪声的样本整体规模约 160 × 10 × 2 × 773 ≈ 247 万实际因部分字体缺繁体字形会略少。生成流程用 Python PIL/OpenCV 就能搭起来from PIL import Image, ImageDraw, ImageFont import numpy as np import cv2 def render_char(char, font_path, size48): 把单个汉字渲染成指定大小的灰度图 img Image.new(L, (size, size), color255) # 白底 draw ImageDraw.Draw(img) font ImageFont.truetype(font_path, int(size * 0.8)) # 居中绘制bbox 用于计算偏移 bbox draw.textbbox((0, 0), char, fontfont) w, h bbox[2] - bbox[0], bbox[3] - bbox[1] draw.text(((size - w) / 2 - bbox[0], (size - h) / 2 - bbox[1]), char, fill0, fontfont) return np.array(img) def augment(img): 随机形变 噪声模拟古籍退化 h, w img.shape # 随机旋转 ±10 度 angle np.random.uniform(-10, 10) M cv2.getRotationMatrix2D((w / 2, h / 2), angle, 1.0) img cv2.warpAffine(img, M, (w, h), borderValue255) # 随机缩放 0.9~1.1 scale np.random.uniform(0.9, 1.1) M cv2.getRotationMatrix2D((w / 2, h / 2), 0, scale) img cv2.warpAffine(img, M, (w, h), borderValue255) # 椒盐噪声 if np.random.rand() 0.5: noise np.random.rand(h, w) img[noise 0.02] 0 img[noise 0.98] 255 # 高斯噪声 if np.random.rand() 0.5: gauss np.random.normal(0, 8, (h, w)) img np.clip(img.astype(float) gauss, 0, 255).astype(np.uint8) return imgrender_char里字号取size * 0.8是给形变留边距避免旋转后笔画被裁掉augment的旋转、缩放、椒盐、高斯四类操作对应文献里提到的平移、尺度缩放、旋转、水平垂直拉升、椒盐噪声、高斯噪声。参数不是死的——古籍刻本笔画偏细可以把高斯噪声标准差从 8 调到 5 左右抄本墨迹晕染重椒盐比例可以提到 0.03。生成完统一 resize 到 48×48 灰度图和网络输入对齐。提示生成脚本跑之前先确认字体文件授权商用字体不要直接打包进训练集分发。2.3 为什么不用阈值筛选而加「不可识别」类文献里有个值得单独拎出来的设计。常规做法是给分类概率设一个阈值 TP ≥ T 才输出结果但 T 靠先验和实验拍脑袋定通用性差。这份实践在网络输出层直接加了一个「不可识别」分类让模型自己学会判断哪些图片它拿不准。这样阈值变成动态的精确率上去了被标为「不可识别」的样本直接转人工系统整体更可靠。对元数据加工这种「宁可漏判不可错判」的场景这个设计比调阈值实用得多。3. 网络结构与训练7 卷积 3 全连接的取舍训练集造好了接下来是网络怎么搭、怎么训。文献试了多种结构在收敛速度和准确率之间权衡后定了一版 7 卷积层 3 全连接层的结构输入 48×48。3.1 结构参数逐层拆解文献用「Cx Sy」表示卷积核 x×x、步长 y「Mx Sy」表示最大池化 x×x、步长 ypadding 表示边界填充像素数。按这个记法还原出的结构大致如下层类型参数说明输入48×48×1灰度图统一尺寸卷积 C3 S13×3, 步长1浅层提取笔画边缘池化 M2 S22×2, 步长2降维保留主特征卷积 C3 S13×3, 步长1组合边缘成笔画段池化 M2 S22×2, 步长2继续降维卷积堆叠3×3 为主提取偏旁、结构特征全连接 ×3—特征映射到 774 类输出774773 汉字 不可识别选 48×48 而不是更大的输入直接原因是算力——文献只有一块 NVIDIA GeForce 1080 Ti输入越大显存和计算量涨得越快。48×48 在古籍单字这个粒度上够用因为识别的是切割后的单字不是整页版面。如果你的场景是整页识别得先做版面分析和单字切割那是另一套流程。3.2 TensorFlow 训练脚本骨架用 TensorFlow 搭这个分类器核心是卷积堆叠 全连接 softmax 输出 774 类。下面是一个可跑通的骨架import tensorflow as tf def build_model(num_classes774, input_shape(48, 48, 1)): model tf.keras.Sequential([ tf.keras.layers.Input(shapeinput_shape), # 第1组浅层边缘特征 tf.keras.layers.Conv2D(32, 3, paddingsame, activationrelu), tf.keras.layers.MaxPooling2D(2, 2), # 第2组笔画段特征 tf.keras.layers.Conv2D(64, 3, paddingsame, activationrelu), tf.keras.layers.MaxPooling2D(2, 2), # 第3组偏旁结构特征 tf.keras.layers.Conv2D(128, 3, paddingsame, activationrelu), tf.keras.layers.Conv2D(128, 3, paddingsame, activationrelu), tf.keras.layers.MaxPooling2D(2, 2), # 第4组高层语义 tf.keras.layers.Conv2D(256, 3, paddingsame, activationrelu), tf.keras.layers.Conv2D(256, 3, paddingsame, activationrelu), tf.keras.layers.GlobalAveragePooling2D(), # 3 层全连接 tf.keras.layers.Dense(1024, activationrelu), tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(512, activationrelu), tf.keras.layers.Dropout(0.5), tf.keras.layers.Dense(num_classes, activationsoftmax), ]) model.compile( optimizertf.keras.optimizers.Adam(1e-3), losscategorical_crossentropy, metrics[accuracy] ) return model model build_model() model.summary()Dropout(0.5)放在全连接层之间是防过拟合的关键——训练集是生成的分布和真实古籍有差距不加 dropout 很容易在生成集上刷高、在真实图上翻车。GlobalAveragePooling2D替代 Flatten 能减少全连接层参数量对 774 类这种大分类头尤其重要。优化器用 Adam、学习率 1e-3 是常见起点文献迭代 20 轮实际训练时建议加EarlyStopping监控验证集别硬跑满。3.3 训练集加载与迭代247 万样本不可能一次性塞进显存用tf.data做流式加载def load_dataset(tfrecord_path, batch_size128): dataset tf.data.TFRecordDataset(tfrecord_path) dataset dataset.map(parse_example, num_parallel_calls4) dataset dataset.shuffle(10000).batch(batch_size).prefetch(2) return dataset # 训练时监控验证集防止过拟合 callbacks [ tf.keras.callbacks.EarlyStopping(patience3, restore_best_weightsTrue), tf.keras.callbacks.ModelCheckpoint(best_model.h5, save_best_onlyTrue), ] model.fit(train_ds, validation_dataval_ds, epochs20, callbackscallbacks)shuffle(10000)的缓冲区要够大否则同类样本扎堆会让梯度震荡prefetch(2)让数据加载和 GPU 计算重叠避免 GPU 空等。文献用 20 轮迭代实际训练时如果验证集准确率连续 3 轮不涨就停省时间也防过拟合。4. 测试与结果61% 准确率背后的四类错误文献的测试集来自上海图书馆「我的图书馆」登录验证码图片均源自「盛宣怀档案」人工标注并二次审核后共 660 个字、1213 张图片。测试前统一预处理成 48×48 灰度图这一步既统一尺寸也抑制了背景噪声。4.1 评价指标怎么算文献用了两个指标。准确率 Racc 正确识别数 / 总样本数反映整体识别能力精确率 Rprecision 正确识别数 / (正确识别数 错误识别数)反映预测结果的可靠程度。注意精确率的分母不含「不可识别」样本——被模型主动拒识的样本不计入错误这正是加「不可识别」类的价值所在。初始结果1213 张测试图正确 741错误 210不可识别 262Racc 61.09%Rprecision 77.92%。准确率不算高但精确率接近 78%意味着模型给出的识别结果里近八成是对的剩下两成多拿不准的直接转人工对元数据加工来说这个可靠性可以接受。4.2 增加字体多样性后的变化在原有样本基础上新增王羲之、颜真卿书法字体每字每字体仍生成 160 个样本同样 20 轮迭代后正确 804错误 290不可识别 119Racc 升到 66.28%Rprecision 降到 73.49%。准确率涨了 5 个百分点精确率降了约 4.4 个百分点。这个此消彼长很典型——模型见得多了敢认的字多了但认错的也多了。文献的结论是训练样本字体多样性有助于提升识别率实际落地时要根据业务容忍度调元数据加工宁可多转人工就保留高精确率配置追求自动化率就接受精确率下降。4.3 四类错误逐条拆文献对错误样本做了归因这几条对复现的人最有参考价值第一类是预处理失真。批量转灰度时部分样本失真严重模型把它判成「不可识别」。这类错误虽然拉低准确率但对精确率有正面作用——模型知道自己不认识。第二类是相近字形误识。手写连笔产生的多余信息会让模型认错比如「化」被认成「他」。这说明 CNN 对笔画细节的区分能力是准确率的天花板想再往上走得在网络结构或训练样本上强化细节特征。第三类是简繁体误识。有 27 个繁体字被认成简体占比约 10%。训练时简繁体是两个独立类模型却把它们混了。文献说暂时无法解释但给了个工程解法建简繁体映射表识别后做一次后处理映射。这个坑很实际复现时建议直接在输出层加映射逻辑。第四类是其他无规律错误归因困难只能靠提升笔画细节识别能力和「不可识别」判断能力来压。5. 避坑与排查复现这份实践最容易翻车的五个点5.1 生成样本和真实样本分布对不上现象模型在生成测试集上准确率 95%换真实古籍图片掉到 50% 以下。原因字体渲染出的笔画太干净噪声模拟再全也补不上真实扫描的纸张纹理、墨迹扩散、装订阴影。解决生成时把噪声参数往真实样本靠用少量真实标注样本做微调或者干脆把真实样本混进训练集做迁移学习。文献自己也提到下一步要扩大训练集并结合标注图片调整。5.2 简繁体混类导致误识现象繁体字被识别成简体占比约一成。原因训练时简繁体作为两个独立类但字形高度相似模型学到的特征区分度不够。解决建简繁体映射表做后处理或者在损失函数里对简繁体对加约束让模型学到区分特征。5.3 输入尺寸和切割质量不匹配现象整页古籍直接喂进网络识别率极低。原因网络输入是 48×48 单字图整页图没做版面分析和单字切割模型看到的是整页噪声。解决先做版面分析定位文字区域再切割单字、统一尺寸、转灰度最后才送进网络。切割质量直接决定识别上限。5.4 算力不够硬上大输入现象显存爆了或者训练慢到跑不完。原因输入尺寸从 48×48 提到 96×96计算量翻四倍单卡 1080 Ti 扛不住。解决先用 48×48 跑通流程确认效果后再考虑加输入尺寸或者用混合精度训练、梯度累积降低显存占用。5.5 过拟合生成集现象训练集准确率 99%验证集 60% 就上不去了。原因生成样本同质化高模型记住了生成规律而非字形特征。解决加大形变和噪声的随机范围加 Dropout 和权重衰减用 EarlyStopping 盯着验证集别只看训练集指标。6. 从 61% 到可用阈值后处理与简繁映射的实操技巧文献的 61% 准确率、78% 精确率放在论文里是阶段性成果但要真用在元数据加工流水线上还得做几件事把结果兜住。第一件是简繁体映射表。27 个繁体误识成简体的样本占比约 10%这个量级不能忽略。做法很简单在模型输出后加一层映射# 简繁体映射表key 为繁体value 为简体 TRAD_TO_SIMP {國: 国, 學: 学, 書: 书} # 按实际字表补全 def postprocess(pred_char, prob, threshold0.7): 识别结果后处理低置信度转人工繁体映射到简体 if prob threshold: return 不可识别 return TRAD_TO_SIMP.get(pred_char, pred_char)threshold是二次筛选的置信度门槛和模型自带的「不可识别」类配合使用——模型判为不可识别的直接转人工模型给出结果但置信度低于 0.7 的也转人工。这样精确率还能再提一截。第二件是「不可识别」样本的闭环利用。被模型拒识的 262 张图不是废数据人工标注后回灌训练集下一轮训练就能覆盖这批难例。文献提到下一步要结合标注汉字图片调整模型说的就是这个闭环。我一般会按「拒识 → 人工标注 → 回灌 → 重训」跑两三轮准确率通常能再涨几个点。第三件是验证方法。别只看整体准确率要按字体风格、按简繁体、按笔画复杂度分桶统计。比如把测试集按「楷书 / 隶书 / 行草」分组看模型在哪类风格上掉得最狠针对性补样本。文献里增加王羲之、颜真卿字体后准确率提升本质就是补了书法体这个短板。第四件是输入预处理的稳定性。文献踩过批量转灰度导致失真的坑我的习惯是预处理后随机抽 50 张肉眼过一遍确认没有整体偏暗、笔画断裂、对比度丢失的问题再送进网络。预处理脚本里加一步直方图均衡化对低对比度的古籍扫描件帮助明显。最后说个心态上的事。这份实践的价值不在于 61% 这个数字而在于它验证了一条在缺训练集场景下用数据生成 CNN 分类 主动拒识跑通古籍汉字识别的路径。准确率不够高没关系把「不可识别」转人工的环节设计好系统整体就是可用的半自动化工具。从那以后我每次做类似的小样本分类项目都会先把「模型拿不准怎么办」这条兜底逻辑设计好再回头调模型——后悔药不如提前留好退路。希望帮到你。本文还有配套的精品资源点击获取