尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
反绎学习(ABL)原理与代码实战:用符号规则修正神经网络伪标签
先说一个我踩过的坑。之前做一个手写公式识别的小Demo模型是常见的卷积网络输入一张“34”的图片期望输出答案。训了一段时间大多数普通样本都能过但每次遇到手写体稍微潦草一点的模型就会一本正经地输出“8”。仔细看预测分布它不是看不清而是把“4”看成了“5”然后非常自信地算出“8”。人眼扫一眼就知道不可能——因为图片上第二个数字明明是“4”。普通神经网络没有“常识”它只认得局部像素模式。也就是从那一刻开始我去认真研究了反绎学习Abductive Learning简称ABL并且用完整源码复现了一个最小可跑工程。这篇文章就是那次复现的全部记录从原理到代码再到我踩过的几个坑。适合对这个方向感兴趣、想亲手写出“神经符号计算”原型或者正在被“伪标签不收敛”“模型输出不符合逻辑”折磨的人。1. 反绎学习解决的问题与总体框架1.1 一个让神经网络“翻车”的小场景上面那个“34”的例子本质是神经网络缺乏结构化先验。传统深度学习做图像识别学到的其实是条件概率分布 P(y|x)模型只在乎“这张图的像素统计上更像哪个数字”至于数字之间是否满足加法规则它完全不关心。你可能会说那我在后处理里加一个校验器不就行了先让模型识别出所有数字再用加法规则去检查结果不对就重试。这是一个常用方案但它有个致命问题校验器没有参与训练模型并不知道“规则存在”这件事。错误一旦发生后处理只能亡羊补牢无法在源头降低错误率。更麻烦的是如果图片里的三个数字互相模糊模型给出的三个预测之间可能互相矛盾后处理根本不知道该信谁。ABL换了一个思路把符号规则看作一种“看不见的监督信号”在训练过程中直接反馈给模型。模型预测出一组候选符号符号逻辑引擎去判断“这组符号合不合理”如果不合理就反推“哪几个符号应该被修正”再把修正后的结果传回给模型。这就像一个老师不是直接告诉你答案是什么而是指着你写错的算式说“你看等号右边要是7左边就说不通了想想哪里算错了。”1.2 演绎、归纳与溯因三兄弟的分工要理解ABL先把三种推理方式分清楚。演绎推理Deduction是从一般规则推出具体结论。比如规则是“所有数字A加上0都等于A”那么“505”就是必然结论。这是符号系统最擅长的事严谨但死板。归纳推理Induction是从具体例子总结一般规则。给出一堆“112”“235”的样本模型学到“加法”的规律。机器学习大部分时候在做这件事。溯因推理Abduction则反过来从观测到的结果出发反推最可能的原因或前提。看到“A?5”结合加法规则反推出问号部分是“5−A”。它的结论不唯一也不保证绝对正确但它能生成“假设”供后续验证。Abductive Learning名字里的“Abductive”就是指这种反推机制。ABL的核心贡献是把溯因推理嵌入到机器学习训练循环里。它不是纯粹的符号逻辑系统也不是纯粹的神经网络而是两者的双向通信。神经网络负责从图像等原始输入中提取候选符号结构符号逻辑引擎负责根据背景知识筛选和修正结构两者交替迭代共同变好。1.3 ABL的模块拆解与优化目标一个典型的ABL系统有三个模块。第一个是子符号模型就是神经网络它把图像映射成符号的概率分布比如“第一个位置有80%概率是315%概率是8”。第二个是符号背景知识库就是我们能写出来的规则集合比如“加法满足交换律”“合取范式约束”等等。第三个是通信机制这是ABL最容易忽略也最难做的部分神经网络输出的软分布怎么送到推理器推理器修正后的结构怎么变成神经网络的监督信号。如果写成优化目标可以这样表示总损失 数据拟合损失(子符号模型输出, 可观测标签) λ * 规则一致性损失(子符号输出, 符号背景知识)数据拟合损失是常规的交叉熵规则一致性损失则是衡量模型输出在多大程度上能被符号规则接受。ABL的特殊之处在于规则一致性损失不是直接加一个正则项那么简单而是通过一个“假设修正”过程动态生成样本级别的伪标签再让神经网络去学习这些伪标签。这有点像半监督学习里的伪标签但伪标签不是从模型自己预测里来的而是从“模型预测符号规则联合推理”里来的。这套框架能解决的问题很明确当数据标注不完整、标注噪声大、或者任务本身有明确逻辑约束时ABL能比纯神经网络更稳健。2. 复现准备合成数据与项目结构2.1 为什么要自造公式图像真正的手写公式数据集很难获取而且字符切分、标注方式、符号类别都不一样。做最小复现最好用合成数据把开源手写数字库里的单字图像按固定排版拼成“数字运算符数字等号答案”的整图。这种做法的好处有几点。第一标签完全可控。我们可以故意构造“答案区域没有标注”的半监督场景模拟现实中“只标了部分字段”的情况。第二规则天然成立。合成的时候用脚本生成算式保证每个样本都满足数学规则。第三方便做消融实验。我可以在测试时把某一位置的图像替换成噪声或者手动改标签观察ABL的修正能力。工程结构很简单我按模块分开写abl_demo/ ├── data.py # 合成公式数据生成与加载 ├── logic.py # 符号规则引擎 ├── abduction.py # 溯因修正模块 ├── model.py # 子符号神经网络 ├── train.py # 训练主循环 └── evaluate.py # 评估脚本这个结构不复杂但每块职责很清楚。实际跑实验时我最常改的就是abduction.py因为溯因策略的每个细节都直接影响收敛质量。2.2 公式图像生成与数据加载合成方案的核心函数是“随机生成一个合法算式”。我要求答案必须是0到9之间的数字这样每个位置都是一个10分类问题规则引擎也好写。import random def sample_formula(): while True: a random.randint(0, 9) b random.randint(0, 9) op random.choice([0, 1]) # 0代表加法1代表减法 if op 0: c a b else: c a - b if 0 c 9: return a, op, b, c这里故意使用while True生成保证c始终落在合法范围内。实际构造数据集时我会预生成几千个算式避免训练过程中每次重新随机导致分布不一致。然后从手写数字库里取图按五个槽位拼到一张120像素宽、40像素高的画布上。拼接时做一个很小的随机平移和缩放让模型不能靠位置记忆。import numpy as np def render_formula(a_img, op_img, b_img, eq_img, c_img, width120, height40): canvas np.full((height, width), 255, dtypenp.float32) tile_w width // 5 canvas[:, 0:tile_w] a_img canvas[:, tile_w:2*tile_w] op_img canvas[:, 2*tile_w:3*tile_w] b_img canvas[:, 3*tile_w:4*tile_w] eq_img canvas[:, 4*tile_w:5*tile_w] c_img return canvas.astype(np.float32)构建Dataset时只返回a和b的标签c的标签置为-1。这样训练代码里就能明确区分“可监督位置”和“待反绎位置”。运算符在本次复现里作为已知信息传入逻辑引擎简化问题如果你想扩展成运算符也未知只需要把运算符也加入联合解码空间。2.3 切分策略与Batch读取因为合成数据的位置固定切分策略就是按坐标切五块。这里有一个值得注意的细节切分后用双线性插值把每个字符缩放到16x16比直接丢进大尺寸网络更快而且可以共享一个数字识别分支。import torch import torch.nn.functional as F def split_and_resize(formula_batch): # formula_batch: (B, 1, H, W) b, _, h, w formula_batch.shape tile_w w // 5 tiles [] for i in range(5): tile formula_batch[:, :, :, i*tile_w:(i1)*tile_w] tile F.interpolate(tile, size(16, 16), modebilinear, align_cornersFalse) tiles.append(tile) return tiles[0], tiles[1], tiles[2], tiles[3], tiles[4]严格说真实场景里手写公式不可能这么整齐字符会有偏移和大小变化。所以我额外在合成时加入宽度抖动五个槽位不是完全等宽而是围绕中心随机偏移。模型在这种扰动下学到的特征更接近真实识别任务。3. 代码实现逻辑引擎与溯因修正3.1 符号规则引擎的写法逻辑引擎在ABL里不需要很复杂它是一组“能判断输入是否合法”的函数。对数学公式场景就是一个检查加法和减法的函数。def apply_math_rule(op, a, b): if op 0: c a b elif op 1: c a - b else: return None if 0 c 9: return c return None这个函数有两个职责。第一给定a和b计算合法答案c。第二如果计算结果超出数字符号集范围返回None表示这条规则在这个候选组合下不成立。规则引擎的精髓是“返回合法候选集合”而不是单一答案。比如在加法规则下a2、b3合法答案只有5但如果你同时有另一个规则“a和b是可交换的”那么候选空间可能更大。复现时不要把所有逻辑塞进一个if-else里最好设计成可组合的函数后面换规则时只改一行。3.2 硬溯因与软溯因的实现规则引擎本身不能反推。所谓反绎是“给定部分观测和规则推断最可能的前提组合”。在公式任务里我们应该怎么做先看硬版本。模型对三个数字位置分别输出概率分布取每个位置概率最高的前K个候选组成候选集合。然后枚举这些候选的组合用规则函数过滤掉“不符合加法/减法”的组合剩下就是一致组合。最后从一致组合里挑一个联合概率最高的用它对应的标签去更新伪标签。这个方法的优点是干净、直接缺点是早期模型预测不准时硬选择会把错误组合当成真理导致伪标签震荡。软版本则温和得多。我不把候选集合剪枝成单一组合而是为答案位置计算一个“规则得分分布”对于答案的每个候选c统计所有能推导出c的(a,b)组合把它们的预测概率相乘后累加得到P(c | 规则, a预测, b预测)。def compute_rule_scores(logits_a, logits_b, op, temperature1.0): pa torch.softmax(logits_a / temperature, dim-1).detach() pb torch.softmax(logits_b / temperature, dim-1).detach() bsz pa.size(0) rule_scores torch.zeros(bsz, 10, devicepa.device) for i in range(bsz): for a in range(10): for b in range(10): c apply_math_rule(op[i].item(), a, b) if c is not None: rule_scores[i, c] pa[i, a] * pb[i, b] return rule_scores这里用两层循环枚举笛卡尔积10x10100种组合在合成数据这种小规模任务上完全够用。如果任务更大可以用矩阵运算加速。得到规则得分后把它和模型自己当前对答案图像的预测做一个加权融合形成“教师分布”def generate_teacher(logits_a, logits_b, logits_c, op, alpha0.5): rule_scores compute_rule_scores(logits_a, logits_b, op) rule_probs rule_scores / rule_scores.sum(dim-1, keepdimTrue).clamp(min1e-9) img_probs torch.softmax(logits_c.detach(), dim-1) teacher alpha * rule_probs (1 - alpha) * img_probs teacher teacher / teacher.sum(dim-1, keepdimTrue) return teacheralpha控制规则和图像证据的权重。我推荐从0.5开始调。alpha太高模型会忽视答案图像上的证据alpha太低规则约束形同虚设。3.3 子符号模型与通信反馈子符号模型我用一个共享卷积网络作为数字分支三个数字位置共用同一套参数。import torch.nn as nn class SharedDigitBranch(nn.Module): def __init__(self): super().__init__() self.conv nn.Sequential( nn.Conv2d(1, 32, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(32, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), ) self.cls nn.Linear(128 * 2 * 2, 10) def forward(self, x): x self.conv(x) x x.flatten(1) return self.cls(x) class FormulaNet(nn.Module): def __init__(self): super().__init__() self.branch SharedDigitBranch() def forward(self, tile_a, tile_b, tile_c): logits_a self.branch(tile_a) logits_b self.branch(tile_b) logits_c self.branch(tile_c) return logits_a, logits_b, logits_c这里有一个关键选择a、b、c三个位置共享同一套卷积权重。这样做有两个理由。一是手写数字的特征在所有位置是通用的共享能减少过拟合。二是ABL希望“符号修正”影响数字表示本身而不是为每个位置学习一套独立的表示。如果三个位置各自用独立网络反绎修正的信息就无法在位置之间迁移。通信反馈在训练循环里体现。每个batch算出三个位置的logits后a和b直接用真实标签计算交叉熵c位置的损失则用教师分布计算KL散度。注意计算规则得分时我要用detach()断开梯度否则梯度会从逻辑得分流回a和b位置导致优化目标变得非常绕。经典ABL的迭代式训练就是“先推理再学习”不是端到端求导。4. 训练主循环与结果分析4.1 训练伪代码与损失设计训练主循环我放在train.py里核心逻辑如下optimizer torch.optim.Adam(model.parameters(), lr1e-3) for epoch in range(60): for formula, op, a_gt, b_gt in loader: t0, t1, t2, t3, t4 split_and_resize(formula) tile_a, tile_b, tile_c t0, t2, t4 logits_a, logits_b, logits_c model(tile_a, tile_b, tile_c) loss_a F.cross_entropy(logits_a, a_gt) loss_b F.cross_entropy(logits_b, b_gt) teacher_c generate_teacher(logits_a, logits_b, logits_c, op, alpha0.5) log_p_c F.log_softmax(logits_c, dim-1) loss_c -(teacher_c * log_p_c).sum(dim-1).mean() loss loss_a loss_b loss_c optimizer.zero_grad() loss.backward() optimizer.step()你可能会问为什么c位置的损失不用交叉熵而用KL散度因为教师分布是软的直接取argmax当硬标签会丢失不确定性信息。用KL散度可以让模型输出的分布去逼近教师分布既保留规则分得清的主方向又保留图像证据带来的模糊性。另外一个会让新手困惑的点规则得分在训练早期方差很大随着a、b位置准确率提高规则得分会越来越准。所以伪标签质量是动态提升的。这也是ABL和普通伪标签训练的核心区别普通伪标签依赖模型自身输出容易在错误上越滚越远ABL的伪标签经过规则过滤有一个“外部校验器”在把关。4.2 三种方案的对比结果我在本地跑了三组实验来验证ABL的效果。第一组叫“普通多任务”a和b用真实标签c位置直接随机初始化但从未接收到任何监督信号。结果c位置准确率稳定在10%左右等于随机猜测。第二组叫“朴素自训练”不经过规则校验模型每步用自己的预测概率生成c位置的硬伪标签再拿伪标签当监督信号。一开始c位置准确率会慢慢涨但涨到60%上下就再也上不去了而且训练过程loss剧烈震荡。典型症状是模型对“看起来像3还是8”这种模糊样本完全失去判断力因为伪标签本身缺一个校准器。第三组是完整ABL用软溯因生成教师分布。c位置的识别准确率能到96%以上而且a、b位置的准确率也略微高于前两组。方法答案位置准确率训练稳定性是否需要答案标签普通多任务约10%稳定但无效需要朴素自训练约60%震荡不需要ABL完整版96%以上稳定不需要这个对比说明了一件事ABL看起来只是改了损失函数实际上是把“可验证的结构约束”变成了训练信号的稳定器。整个训练过程中模型从规则里获得的信息比从图像里获得的还要多。4.3 超参数选择与训练稳定性alpha是我最敏感的超参数。alpha0.3以下时规则影响太弱c位置准确率只能停在80%左右alpha0.7以上时模型开始对答案图像“视而不见”。我最后在0.5附近跑得很稳。温度参数也值得调。计算规则得分时我给softmax加了一个temperature当温度低于1时分布会更尖锐。经验是早期训练设温度0.8让规则得分更果断后期把温度调回1.0让模型关注图像细节。还有一个小细节我没有在每个step都重新生成教师分布而是在每个epoch结束后用当前模型重新扫描一遍数据替c位置生成“缓存的伪标签”然后下一个epoch固定使用。这个策略显著减少了标签震荡类似半监督里的持久伪标签。代价是伪标签滞后一个epoch但对收敛速度影响很小。5. 常见问题与排坑实录5.1 answer标签反复横跳loss不降我刚开始跑的时候c位置的loss在几个epoch之后开始飙升怎么调学习率都没用。打印出教师分布发现同样一张图的伪标签前一个epoch是“7”后一个epoch变成了“2”再下一个epoch又变回“7”。这个问题的根源是伪标签更新太激进。我改成每个epoch结束时统一更新伪标签并且用EMA平滑历史标签teacher_c 0.9 * teacher_c_old 0.1 * teacher_c_new这样就避免了单次波动带来的连锁反应。EMA系数0.9意味着新标签需要十个epoch才能完全替换旧标签虽然听起来很慢但稳定性提升非常明显。5.2 模型学会了“抄近路”另一个更隐蔽的坑模型识别a和b的准确率都正常c位置准确率也很高但我把c位置的图像替换成随机噪声再测试准确率居然没怎么掉。也就是说模型根本没有看c位置的图像它只是通过规则“抄了近路”——既然a2、b3答案必然是5那还看图像干什么这是ABL最容易出现的病态解逻辑约束主导了一切子符号模型退化成纯规则执行器。解决办法有二。一是降低alpha提高图像证据在教师分布里的权重强制模型必须从c位置图像获取信息。二是对c位置的输入做数据增强比如随机遮挡局部像素、添加噪声。如果模型真的在利用图像特征这些增强不会让性能崩掉如果模型在抄近路准确率会立刻跳水暴露问题。5.3 真实场景中切分不准怎么办合成数据切分是固定位置的但真实公式图里字符会重叠、倾斜、大小不一。如果直接把ABL套到真实OCR场景第一步切分就会出错。我的建议是先训练一个字符检测器或投影切分模块把公式图切成候选框列表。切分模块的误差可以模拟进合成数据随机给每个槽位加偏移甚至故意把两个字符切进同一个框。模型在对这种“脏切分”适应之后ABL的溯因模块反而能帮它纠正切分导致的识别错误。因为切分导致某个字符看不清时模型对其他位置的预测和规则约束会反过来提供证据。5.4 规则冲突时的优先级处理当背景知识库有多个规则时不是每条规则都适用于当前样本。例如加法规则要求结果在0到9之间但有些算式可能超出这个范围。如果样本本身非法规则引擎返回空集合此时溯因修正就无法进行。处理办法是给规则引擎增加一个“兜底”机制当所有规则都拒绝一个候选组合时直接退化为普通的图像自训练用模型自己的预测作为伪标签。这样至少保证每个样本都有一个可学习的信号不让规则冲突拖垮训练过程。6. 写在最后的一点个人体会反绎学习这个方向第一眼看上去有点绕但跑通代码之后会发现它的设计其实很朴素先让神经网络猜再用符号规则去验不对就反推反推完拿回去重新学。这个循环之所以有效是因为符号规则提供了一个“无论如何都不会错”的稳定锚点。我自己的经验是ABL最适合那种“规则明确、标注不全”的任务比如文档结构化抽取里的字段校验、自动表单里的逻辑核对、游戏AI里的行动合法性约束。这类任务里完全靠人工标注成本太高完全靠神经网络的统计规律又总在边界情况翻车。ABL刚好能把那部分已知的人类知识用起来而且实现成本没有想象中高一个逻辑引擎加一个伪标签生成器就够。如果只是想给模型加一个后处理校验那没必要上ABL。但如果希望模型从根上学会“遵守规则”或者你的标注预算真的撑不住全量监督那这套“预测、反绎、回传”的思路值得你在自己的数据上试一遍。最后再分享一个小技巧复现的时候先把所有规则写进一个纯Python函数用单元测试把规则引擎跑得滚瓜烂熟再和神经网络对接能省掉大量排查时间。
RELATED

相关推荐

网安复试编程集训Day3复盘:安全编码与边界处理实战

网安复试编程集训Day3复盘:安全编码与边界处理实战

复试集训走到第三天,教室里已经没有前两天那种“还在热身”的气氛了。我们这批准备网安方向复试的人,连续两天白天刷题、晚上复盘,到了第三天,题目量和难度都明显上了一个台阶。这所学校网络空间安全学院的复试,笔试上…

📅 2026/10/20 3:09:21
光学镜片缺陷检测:从数据采集到模型落地全流程解析

光学镜片缺陷检测:从数据采集到模型落地全流程解析

做工业视觉这些年,我越来越觉得光学镜片缺陷检测是所有质检场景里最磨人的一类。透明材质、高反光、缺陷又小又淡,有些划痕浅到人眼盯着看半天都拿不准,可良率考核就卡在那几个百分点上。最近在整理一套镜片缺陷检测数据集的持续迭代项目&…

📅 2026/10/20 3:09:21
开源大会参会不白跑:COSCon与Apache Pulsar全攻略

开源大会参会不白跑:COSCon与Apache Pulsar全攻略

就在明天。COSCon25 与 Pulsar 的专场落地海淀,一年一度的开源大会终于不用跨城赶路,周末坐几站地铁就能到。标题里那句"这么近,那么美"虽然是玩梗,但放在开源大会上是真没说错——海淀本来就是国内开源开发者密度最高的…

📅 2026/10/20 3:04:21
MORE NEWS

更多资讯

📰

法律AI融资背后的渠道逻辑:收银机与等保服务商为何下注

说实话,我是在一个很平常的工作日上午刷到这则融资消息的。某法律AI团队上线刚两个月,宣布完成A轮融资,具体金额没披露,但投资方名单里排头的不是任何一家VC,一个是做智能收银机的厂商,另一个是做等保测评的…

📰

JSP企业级实名投票系统部署与二次开发指南

简介:这是一套基于JSP技术实现的企业级电子投票系统完整源码,面向Java Web初学者与课程设计实践者,解决中小型组织在线投票管理、结果可视化与后台权限管控等实际需求。资源包共64个文件,含37个JSP页面(实现前后台交互…

📰

AI生成PowerShell脚本总报错?环境对齐与编码执行策略全解析

每次让 AI 帮忙写 PowerShell,我都觉得它是在给我出阅读理解题。需求发过去,AI 啪的一下甩出一段脚本,我复制进终端,结果不是$变量不展开,就是中文变成了乱码,甚至直接被执行策略拦下来,连跑的机…

📰

Java+SSM+Flask网上系统开发实战:从拆题到交付全流程

拿到“基于JavaSSMFlask网上系统”这个题目的第一反应,大多数人应该和我一样:这到底是个什么系统?Java一套、SSM一套、Flask又一套,后面还跟着源码、LW、调试文档、讲解这些词,乍看像大杂烩。但你把词拆开再看&#xf…

📰

AI写PowerShell命令总翻车?把环境信息喂给它,一次跑通

不是AI不行,是它看不见你屏幕上的报错,也摸不着你的服务器环境。它背熟了PowerShell的语法书,却不知道你当前这台机器的执行策略是Restricted还是RemoteSigned,更不知道你头顶上是不是开着管理员窗口。这个场景我太熟了&#xff1…

📰

HTTP大文件传输实战:从内存崩溃到断点续传与分片上传

1. 一个 200MB 导出文件让整台机器濒临崩溃:我的踩坑起点很久以前我维护一个内部数据平台,功能很简单:用户申请导出报表,后端从数据库查出数据,拼成一个 CSV 或压缩包返回给前端下载。最初代码简直质朴过头——查询数据…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬