智能匹配算法设计文档.md 9.6 KB

智能匹配算法设计文档

🎯 算法目标

实现检查项目名称的智能匹配,能够处理:

  1. 空格差异:"CT 上腹部平扫" vs "CT上腹部平扫"
  2. 词序不同:"CT 上腹部平扫" vs "上腹部CT平扫"
  3. 同义词识别:"头部" vs "颅脑"
  4. 简写/缩写:"MR" vs "MRI"

📐 算法设计

核心思想

将检查项目名称分解为三个维度:

  1. 模态(Modality):CT、MR、DR等
  2. 身体部位(Body Part):头、胸、腹、四肢等
  3. 扫描方式(Scan Method):平扫、增强、造影等

然后针对每个维度分别计算相似度,最后加权汇总。

算法流程

输入:"CT 上腹部平扫"
     ↓
【步骤1】规范化处理
  - 去除多余空格
  - 提取模态:CT
  - 提取扫描方式:平扫
  - 提取身体部位:上腹部
  - 提取关键词:{C, T, 上, 腹, 部, 平扫, 上腹, 腹部}
     ↓
【步骤2】维度匹配
  - 模态匹配:CT == CT → 0.3分
  - 扫描方式:平扫 == 平扫 → 0.2分
  - 身体部位相似度计算
  - 关键词重合度计算
     ↓
【步骤3】相似度计算
  总分 = 0.3(模态) + 0.2(扫描) + 0.3(部位) + 0.2(关键词)
  = 0.3 + 0.2 + 0.3*部位相似度 + 0.2*关键词重合度
     ↓
【步骤4】阈值判断
  if (总分 >= 0.7) → 匹配成功 ✅
  else → 匹配失败 ❌

相似度计算公式

相似度 = 0.3 × 模态匹配得分
       + 0.2 × 扫描方式匹配得分
       + 0.3 × 身体部位相似度
       + 0.2 × 关键词重合度

1. 模态匹配(权重0.3)

if (modality1.equals(modality2)) {
    return 0.3;  // 完全匹配
} else {
    return -0.5;  // 不匹配,大幅降低分数
}

示例

  • CT vs CT → 0.3 ✅
  • CT vs MR → -0.5 ❌

2. 扫描方式匹配(权重0.2)

if (scanMethod1.equals(scanMethod2)) {
    return 0.2;
}

示例

  • 平扫 vs 平扫 → 0.2 ✅
  • 平扫 vs 增强 → 0.0
  • 增强 vs CTA → 0.0(不同但相关,可优化)

3. 身体部位相似度(权重0.3)

使用多种算法计算:

a. 精确匹配

if (part1.equals(part2)) {
    return 1.0;
}

b. 同义词匹配

// 同义词映射
{"头", "颅", "脑", "头部"}  // 返回 0.9
{"胸", "胸部", "肺"}        // 返回 0.9
{"腹", "腹部", "上腹"}      // 返回 0.9

c. 包含关系

if (part1.contains(part2) || part2.contains(part1)) {
    return 0.8;
}

d. 编辑距离(Levenshtein Distance)

distance = "上腹" vs "上腹部" → 1
maxLen = 3
similarity = 1 - 1/3 = 0.67

4. 关键词重合度(权重0.2)

使用Jaccard相似系数:

交集 / 并集

示例:
关键词1 = {C, T, 上, 腹, 平扫}
关键词2 = {C, T, 上, 腹部, 平}

交集 = {C, T, 上} → 3个
并集 = {C, T, 上, 腹, 平扫, 腹部, 平} → 7个
相似度 = 3/7 = 0.43

匹配示例

示例1:空格差异

目标:"CT 上腹部平扫"
候选:"CT上腹部平扫"

规范化:
  目标 → CT+上腹部+平扫
  候选 → CT+上腹部+平扫

维度匹配:
  模态:CT == CT → 0.3
  扫描:平扫 == 平扫 → 0.2
  部位:上腹部 == 上腹部 → 0.3
  关键词:完全重合 → 0.2

总分:0.3 + 0.2 + 0.3 + 0.2 = 1.0 ✅

示例2:词序不同

目标:"CT 上腹部平扫"
候选:"上腹部CT平扫"

规范化:
  目标 → {模态:CT, 部位:上腹部, 扫描:平扫}
  候选 → {模态:CT, 部位:上腹部, 扫描:平扫}

维度匹配:
  模态:CT == CT → 0.3
  扫描:平扫 == 平扫 → 0.2
  部位:上腹部 == 上腹部 → 0.3
  关键词:{C,T,上,腹,平扫} vs {上,腹,C,T,平扫} → 0.2

总分:0.3 + 0.2 + 0.3 + 0.2 = 1.0 ✅

示例3:同义词

目标:"CT 上腹部平扫"
候选:"CT 腹部平扫"

规范化:
  目标 → {模态:CT, 部位:上腹部, 扫描:平扫}
  候选 → {模态:CT, 部位:腹部, 扫描:平扫}

维度匹配:
  模态:CT == CT → 0.3
  扫描:平扫 == 平扫 → 0.2
  部位:上腹部 ⊂ 腹部(包含关系)→ 0.3 × 0.8 = 0.24
  关键词:部分重合 → 0.2 × 0.7 = 0.14

总分:0.3 + 0.2 + 0.24 + 0.14 = 0.88 ✅

示例4:完全不匹配

目标:"CT 上腹部平扫"
候选:"MR 头部增强"

规范化:
  目标 → {模态:CT, 部位:上腹部, 扫描:平扫}
  候选 → {模态:MR, 部位:头部, 扫描:增强}

维度匹配:
  模态:CT != MR → -0.5
  扫描:平扫 != 增强 → 0.0
  部位:上腹部 != 头部 → 0.0
  关键词:无重合 → 0.0

总分:-0.5 + 0.0 + 0.0 + 0.0 = -0.5 ❌

🔧 关键技术实现

1. 字符串规范化

// 去除多余空格
text.replaceAll("\\s+", "")

// 提取关键词(支持中文)
- 单字:{C, T, 上, 腹, 部, 平, 扫}
- 双字:{CT, 上腹, 腹部, 平扫}
- 三字:{上腹部, 平扫}

2. 编辑距离算法

// Levenshtein Distance
int distance = "上腹部" vs "上腹" → 1
int maxLen = 3
similarity = 1 - 1/3 = 0.67

3. Jaccard相似系数

Jaccard(A, B) = |A ∩ B| / |A ∪ B|

示例:
A = {CT, 上, 腹, 平扫}
B = {CT, 上, 腹部, 平}

|A ∩ B| = 3  // {CT, 上, 腹}
|A ∪ B| = 7  // {CT, 上, 腹, 平扫, 腹部, 平}

Jaccard = 3/7 = 0.43

📊 算法参数

权重分配

维度 权重 说明
模态匹配 0.3 最重要,不同模态不匹配
扫描方式 0.2 较重要,增强vs平扫区别大
身体部位 0.3 最重要,不同部位不匹配
关键词重合 0.2 辅助判断,提升准确率
总计 1.0

阈值设置

阈值 含义 应用场景
0.9 - 1.0 完全匹配 几乎相同,可直接使用
0.7 - 0.9 良好匹配 可以接受,人工确认后使用
0.5 - 0.7 一般匹配 需要人工审核
< 0.5 不匹配 不推荐使用

当前阈值:0.7(相似度 >= 0.7 才匹配成功)

🎯 匹配优先级(完整版)

优先级1:study_info.exam_item_name 精确匹配
  示例:"CT 上腹部平扫" == "CT 上腹部平扫" → 1.0
  触发条件:exam_item完全一致

优先级2:DICOM.examItem 精确匹配
  示例:"CT ABDOMEN..." == "CT ABDOMEN..." → 1.0
  触发条件:exam_item完全一致

优先级3:study_info.exam_item_name 智能匹配 ← NEW!
  示例:"CT 上腹部平扫" ~ "CT上腹部平扫" → 0.95
  示例:"CT 上腹部平扫" ~ "上腹部CT平扫" → 0.92
  触发条件:相似度 >= 0.7

优先级4:DICOM.examItem 智能匹配 ← NEW!
  示例:使用复杂DICOM标签进行智能匹配
  触发条件:相似度 >= 0.7

优先级5:bodyPart 模糊匹配
  示例:使用"ABDOMEN"等部位信息
  触发条件:所有上述都失败

🧪 测试用例

测试集

序号 目标 候选 预期相似度 结果
1 CT 上腹部平扫 CT上腹部平扫 >= 0.9
2 CT 上腹部平扫 上腹部CT平扫 >= 0.9
3 CT 上腹部平扫 CT腹部平扫 >= 0.8
4 CT 上腹部平扫 CT胸部平扫 < 0.5
5 MR 颅脑平扫 MR头部平扫 >= 0.9
6 CT 冠脉CTA CT冠状动脉增强 >= 0.8
7 DR 胸部后前位 DR胸部PA >= 0.7

⚙️ 优化建议

1. 性能优化

当前实现:每次匹配都查询所有标准

List<QcStandard> standards = qcStandardMapper.selectList(...);

优化方案:缓存质控标准

@Cacheable("qc_standards")
public List<QcStandard> getAllActiveStandards() {
    return qcStandardMapper.selectList(...);
}

2. 准确率优化

增加更多同义词映射

BODY_PART_SYNONYMS.put("冠状动脉", Arrays.asList("冠脉", "冠状动脉", "冠"));
BODY_PART_SYNONYMS.put("脑血管", Arrays.asList("脑血管", "脑血", "颈动脉"));

增加扫描方式识别

SCAN_METHOD_KEYWORDS.addAll(Arrays.asList(
    "CTA", "MRA", "SWI", "DWI", "FLAIR", "VR", "MIP"
));

3. 可解释性优化

返回匹配详情

public class MatchDetail {
    private String candidate;
    private double similarity;
    private String matchReason;  // 匹配原因说明
    private Map<String, Double> dimensionScores;  // 各维度得分
}

📝 使用示例

Java代码

// 注入匹配器
@Resource
private ExamItemMatcher examItemMatcher;

// 使用匹配
String target = "CT 上腹部平扫";
List<String> candidates = Arrays.asList(
    "CT上腹部平扫",
    "上腹部CT平扫",
    "CT胸部平扫"
);

MatchResult result = examItemMatcher.findBestMatch(target, candidates);

if (result != null && result.getSimilarity() >= 0.7) {
    System.out.println("匹配成功:" + result.getCandidate());
    System.out.println("相似度:" + result.getSimilarity());
}

SQL查询验证

-- 查看实际的exam_item数据
SELECT exam_item_name, COUNT(*)
FROM study_info
WHERE exam_item_name LIKE '%上腹部%'
GROUP BY exam_item_name;

-- 查看质控标准配置
SELECT exam_item, standard_name
FROM qc_standard
WHERE modality = 'CT'
  AND exam_item LIKE '%上腹部%';

🎁 算法优势

  1. 容错性强:处理空格、词序、同义词等问题
  2. 可解释性好:每个维度都有明确的得分
  3. 易于扩展:可以添加新的维度或调整权重
  4. 性能可控:时间复杂度 O(n),n为候选标准数量
  5. 准确率高:多维度综合判断,减少误匹配

算法版本:v1.0 创建时间:2026-01-27 适用场景:质控标准自动匹配