fix: markdown post handel

This commit is contained in:
liuyuanchuang
2026-02-05 13:18:55 +08:00
parent 808d29bd45
commit 280a8cdaeb
9 changed files with 2108 additions and 24 deletions

View File

@@ -0,0 +1,320 @@
# 禁用微分规范化功能 - 防止破坏 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` ❌ |
即使添加了 `(?<![a-zA-Z])` 也只是部分解决,因为还有其他风险。
#### 3. 误判率极高
在数学表达式中,`d` + 字母的组合非常常见:
- 变量名:`dx`, `dy`, `dz`, `dr`, `ds`, `dt`, `du`, `dv`, `dw`
- 下标:`x_{d}`, `y_{dx}`
- 自定义符号:`d_1`, `d_2`
- 物理量:`dE` (能量变化), `dP` (压强变化)
无法可靠区分哪些是微分,哪些是变量名。
## 解决方案:禁用微分规范化
### 修改内容
**文件**: `app/services/ocr_service.py`
**修改 1**: 更新正则表达式(增加前后保护)
```python
# 旧版本(仍然有风险)
_DIFFERENTIAL_LOWER_PATTERN = re.compile(r"(?<!\\)(?<![a-zA-Z])d([a-z])")
# 新版本(增加后向保护,但仍然禁用)
_DIFFERENTIAL_LOWER_PATTERN = re.compile(r"(?<!\\)(?<![a-zA-Z])d([a-z])(?![a-zA-Z])")
```
**修改 2**: 禁用微分规范化
```python
def _postprocess_math(expr: str) -> 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** - 不确定的时候,不要修改。