# 禁用微分规范化功能 - 防止破坏 LaTeX 命令 ## 问题根源 用户发现 LaTeX 命令被错误拆分: - `\vdots` → `\vd ots` ❌ - `\lambda_{1}` → `\lambd a_{1}` ❌ 根本原因是 **Stage 2 的微分规范化功能过于激进**,会匹配和修改任何 `d` + 字母的组合。 ## 设计缺陷分析 ### 原始设计意图 微分规范化的目标是处理 OCR 识别的微分符号,例如: - `dx` → `d x` (添加空格) - `dy` → `d y` - `dV` → `\mathrm{d} V` (大写用 mathrm) ### 为什么这个设计有问题 #### 1. 无法区分上下文 `dx` 可能是: - ✅ 微分符号:`\int f(x) dx` - ❌ 变量名:`let dx = x_2 - x_1` - ❌ 下标:`x_{dx}` - ❌ 函数名的一部分 正则表达式无法理解语义,只能盲目匹配。 #### 2. 破坏 LaTeX 命令 任何包含 `d` + 字母的 LaTeX 命令都会被破坏: | 命令 | 内部匹配 | 破坏结果 | |-----|---------|---------| | `\vdots` | `do` | `\vd ots` ❌ | | `\lambda` | `da` | `\lambd a` ❌ | | `\delta` | `de` | `\d elta` ❌ | | `\cdots` | `do` | `\cd ots` ❌ | | `\ldots` | `do` | `\ld ots` ❌ | | `\iddots` | `do` | `\idd ots` ❌ | 即使添加了 `(? str: """Postprocess a *math* expression (already inside $...$ or $$...$$).""" # stage0: fix OCR number errors expr = _fix_ocr_number_errors(expr) # stage1: split glued command tokens expr = _COMMAND_TOKEN_PATTERN.sub( lambda m: _split_glued_command_token(m.group(0)), expr ) # stage2: differential normalization - DISABLED # (commented out to avoid false positives) return expr ``` ### 为什么选择禁用而不是修复 #### 成本收益分析 **如果启用**: - ✅ 小收益:某些微分符号格式更规范 - ❌ 高风险:破坏 LaTeX 命令、变量名、下标等 **如果禁用**: - ❌ 小损失:微分符号可能没有空格(但仍然是有效的 LaTeX) - ✅ 高收益:所有 LaTeX 命令和变量名都安全 **结论**: 禁用是更安全、更保守的选择。 #### 微分符号即使不加空格也是有效的 ```latex \int dx % 有效 \int d x % 有效(规范化后) ``` 两者在渲染时效果相同,OCR 输出 `dx` 不加空格完全可以接受。 ## 保留的功能 ### Stage 0: 数字错误修复 ✅ 保留 修复 OCR 数字识别错误: - `2 2. 2` → `22.2` - `1 5 0` → `150` **保留原因**: 这是明确的错误修复,误判率极低。 ### Stage 1: 拆分粘连命令 ✅ 保留 修复 OCR 识别的粘连命令: - `\intdx` → `\int dx` - `\cdotdS` → `\cdot dS` **保留原因**: - 基于白名单,只处理已知的命令 - 粘连是明确的 OCR 错误 - 误判率低 ### Stage 2: 微分规范化 ❌ 禁用 **禁用原因**: - 无法区分微分和变量名 - 破坏 LaTeX 命令 - 误判率高 - 收益小 ## 替代方案(可选) 如果确实需要微分规范化,我们提供了一个上下文感知的版本: ```python def _normalize_differentials_contextaware(expr: str) -> str: """Context-aware differential normalization. Only normalizes in specific safe contexts: 1. After integral symbols: \\int dx → \\int d x 2. In fraction denominators: \\frac{dy}{dx} → \\frac{dy}{d x} """ # Pattern 1: After integral commands integral_pattern = re.compile( r'(\\i+nt|\\oint)\s*([^\\]*?)\s*d([a-zA-Z])(?![a-zA-Z])' ) expr = integral_pattern.sub(r'\1 \2 d \3', expr) # Pattern 2: In fraction denominators frac_pattern = re.compile( r'(\\frac\{[^}]*\}\{[^}]*?)d([a-zA-Z])(?![a-zA-Z])([^}]*\})' ) expr = frac_pattern.sub(r'\1d \2\3', expr) return expr ``` **特点**: - 只在明确的数学上下文中应用(积分后、分式分母) - 仍然有风险,但比全局匹配安全得多 - 默认不启用,用户可自行决定是否启用 ## 测试验证 ### 测试 1: LaTeX 命令不被破坏 ✅ ```python test_cases = [ r"\vdots", r"\lambda_{1}", r"\delta", r"\cdots", r"\ldots", ] # 预期:全部保持不变 for expr in test_cases: result = _postprocess_math(expr) assert result == expr # ✅ 通过 ``` ### 测试 2: 变量名不被修改 ✅ ```python test_cases = [ r"dx", r"dy", r"x_{dx}", r"f(x)dx", ] # 预期:全部保持不变(因为微分规范化已禁用) for expr in test_cases: result = _postprocess_math(expr) assert result == expr # ✅ 通过 ``` ### 测试 3: OCR 错误修复仍然工作 ✅ ```python # 数字错误修复 assert _fix_ocr_number_errors("2 2. 2") == "22.2" # 粘连命令拆分 assert _postprocess_math(r"\intdx") == r"\int dx" ``` ## 受影响的 LaTeX 命令列表 禁用微分规范化后,以下命令现在都是安全的: ### 包含 `d` 的希腊字母 - `\delta` (δ) - `\Delta` (Δ) - `\lambda` (λ) - 通过下标间接受影响 ### 包含 `d` 的省略号 - `\vdots` (⋮) - 垂直省略号 - `\cdots` (⋯) - 中间省略号 - `\ldots` (…) - 水平省略号 - `\ddots` (⋱) - 对角省略号 - `\iddots` (⋰) - 反对角省略号 ### 其他包含 `d` 的命令 - 任何自定义命令 - 包含 `d` 的变量名或函数名 ## 部署步骤 1. **代码已修改**: ✅ `app/services/ocr_service.py` 已更新 2. **验证语法**: ✅ 无 linter 错误 3. **重启服务**: 重启 FastAPI 服务 4. **测试验证**: ```bash python test_disabled_differential_norm.py ``` 5. **前端测试**: 测试包含 `\vdots` 和 `\lambda` 的图片识别 ## 性能影响 **禁用微分规范化后**: - ✅ 减少正则表达式匹配次数 - ✅ 处理速度略微提升 - ✅ 代码更简单,维护成本更低 ## 向后兼容性 **对现有用户的影响**: - ✅ LaTeX 命令不再被破坏(改进) - ✅ 变量名不再被修改(改进) - ⚠️ 微分符号不再自动规范化(可能的退化,但实际影响很小) **评估**: 总体上是正向改进,风险降低远大于功能损失。 ## 总结 | 方面 | 状态 | |-----|------| | LaTeX 命令保护 | ✅ 完全保护 | | 变量名保护 | ✅ 完全保护 | | 数字错误修复 | ✅ 保留 | | 粘连命令拆分 | ✅ 保留 | | 微分规范化 | ❌ 禁用(可选的上下文感知版本可用) | | 误判风险 | ✅ 大幅降低 | | 代码复杂度 | ✅ 降低 | **修复状态**: ✅ **完成** **建议**: 1. 重启服务使修改生效 2. 测试包含 `\vdots`, `\lambda`, `\delta` 等命令的图片 3. 验证不再出现命令拆分问题 4. 如果确实需要微分规范化,可以评估启用上下文感知版本 ## 附录:设计哲学 在 OCR 后处理中,应该遵循的原则: ### ✅ 应该做什么 1. **修复明确的错误** - OCR 数字识别错误(`2 2. 2` → `22.2`) - 命令粘连错误(`\intdx` → `\int dx`) 2. **基于白名单/黑名单** - 只处理已知的情况 - 避免泛化的模式匹配 3. **保守而不是激进** - 宁可不改也不要改错 - 错误的修改比不修改更糟糕 ### ❌ 不应该做什么 1. **依赖语义理解** - 无法区分微分和变量名 - 无法理解数学上下文 2. **全局模式匹配** - 匹配所有 `d[a-z]` 过于宽泛 - 误判率不可接受 3. **"智能"猜测** - 除非有明确的规则,否则不要猜 - 猜错的代价太高 **核心原则**: **Do No Harm** - 不确定的时候,不要修改。