惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
腾讯CDC
G
Google Developers Blog
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
美团技术团队
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志

博客园 - ITman彪叔

赋能UE运行态编辑平台: 网络图片下载的插件改造与复盘 基于FastMCP打通业务系统与AI:构建智能协作新范式 在 Dify 中通过 MCP 集成本地 FastMCP 服务避坑指南 从分形到森林——使用 Three.js 创建逼真的 3D 树木 Threejs 视频融合 UE5 小部件 组合框(键)ComboBox (Key) Tesseract OCR 页面分割模式解析 Java MQTT 主流开发方案对比 Java Modbus主流开发方案对比 Java OPC UA 开发方案全对比:开源、商业、集成与官方标准 Nodejs打包 Webpack 中 __dirname 的正确配置与行为解析 强制 pnpm 使用 IPv4 而不是 IPv6 的终极解决方案指南 uniapp 微信小程序 调试 UE Websocket 通信 UE Geometry_Script 程序化建模 UE MultiLineTraceByChannel函数返回只有一个对象的问题 UE Spline 样条网格体组件添加碰撞 Three光源Target位置改变光照方向不变的问题及解决方法 UE5 射线检测排除隐藏的Actor UE5打包SDK未正确安装的问题 判断两个矩形是否相交(Rect Intersection) 电力布局三维编辑器功能设计 UE4 WebUI使用指南2-通信
召回率暴涨!“层级化分片”与“降噪”拯救 RAG 系统
ITman彪叔 · 2026-06-01 · via 博客园 - ITman彪叔

背景:AI 大潮下的中台建设

在 AI 浪潮席卷而来的当下,我们团队紧跟技术趋势,近期致力于构建企业级 AI 中台。我们的技术栈涵盖了 OCR 识别智能语音交互语音数字人知识库 以及 AI Agent 等核心模块。

AI中台和数字孪生系统的结合,赋予数字孪生系统 语音交互,智能播报,业务助手等能力。 **从“可视”到“可听、可说、可思” **

最近我在处理一个孪生资产管理的知识库时,就遇到了一个非常典型的痛点:当用户搜索“1#楼”时,系统经常召回不到任何有效信息;或者即使召回了,也是一堆重复啰嗦的设备列表,导致 LLM 被噪声淹没,回答质量极差。

做 RAG(检索增强生成)的朋友都知道,最让人头秃的往往不是大模型不会回答,而是它根本“找不着北”。

经过一番排查和优化,我通过“层级化分片”配合“高频重复噪声去重”策略,将召回率和最终回答的准确率提升了几个量级。今天就来复盘一下这个过程,并拆解背后的技术原理与代码实现。

痛点复盘:为什么你的 RAG 总是“断章取义”且“废话连篇”?

在优化之前,我的数据是典型的“扁平化表格”结构。向量数据库里存储的是一条条独立的记录。这种传统的“一刀切”分片方式,带来了两个致命问题:

  1. 语义鸿沟与上下文断裂:用户搜的是“1#楼”,但数据库里存的是 lou1。虽然人类知道它们是一回事,但 Embedding 模型很难完美对齐。更致命的是,假设用户问“1#楼1层有哪些温湿度传感器?”,系统可能召回了传感器的记录,但这条记录里根本没有“它在1#楼”的信息。模型就像拿到了拼图的一块,却死活拼不出全貌。
  2. 高频重复噪声(High-Frequency Repetitive Noise):这是极其隐蔽的“大坑”。在工业或运维场景中,大量数据伴随着重复字段(比如 100 个传感器,它们的 type 都是“温湿度传感器”,status 都是“正常”)。如果按行切分,这些高频重复的词汇会极大掩盖掉真正有区分度的词(如具体编号、位置),导致向量在空间里挤成一团,区分度极低,检索时全是“伪高分”的废话。

深度剖析:为什么“重复语言”会严重干扰召回?

你可能会有疑问,多写几遍“温湿度传感器”和“状态正常”,难道不是更能强调它的特征吗?实际上恰恰相反,这种重复语言会从底层严重破坏 RAG 的检索效果:

  • 向量空间的“平庸化”塌缩:Embedding 模型本质上是在计算文本的语义重心。如果你的分片里充斥着大量重复的“正常、温湿度传感器、某某科技”,这些高频词的语义权重会极大掩盖掉真正有区分度的词(比如具体的设备编号、位置)。导致的结果是:所有传感器的向量在空间里挤成了一团,区分度极低。
  • 余弦相似度的“伪高分”:当用户提问时,如果问题稍微沾一点边,向量数据库可能会召回一大堆“长得都很像”的重复数据。比如你查“1#楼传感器”,系统可能把 2#楼、3#楼的传感器也召回了,因为它们的描述文本相似度高达 99%。
  • 浪费 LLM 的上下文窗口:召回了一堆废话,不仅干扰模型判断,还白白消耗了宝贵的 Token 配额。

破局之道:层级化分片 + 智能去重抽象

为了解决这两个问题,我放弃了扁平化的单行切分,设计了一套组合拳:

  1. 层级化分片:把散落的数据按“园区 -> 楼宇 -> 楼层 -> 房间 -> 设备”的树状结构重新组装,打包成语义完整的 Markdown 文本块。
  2. 去重与抽象:在生成分片时,自动提取子节点的公共属性(如相同的厂商、状态),并将大量重复的 ID 进行折叠展示。

优化后的分片效果长这样:

### 🏢 1#大楼 (ID: lou1)
**层级路径**:park -> lou1 (1#大楼)
**别名**:1号楼, 1楼, 1栋, 1#楼

#### 1. 包含的子级资产
- **floor**
  - 编号: IDC1-1, IDC1-2, IDC1-3
### 🏢 1F (ID: IDC1-1)
**层级路径**:lou1 -> IDC1-1 (1F)
**别名**:无

#### 1. 包含的子级资产
- **device** (公共属性: status: 正常, vendor: 海康威视)
  - 编号列表: SENS1, SENS2, SENS3... (共 10 个)

原理拆解:为什么这样做能大幅提升效果?

  1. 显式的同义词注入(对抗语义鸿沟):在层级化分片中,我们直接在文本块里写入了 别名: 1号楼, 1楼。无论用户输入哪种叫法,Embedding 模型都能在该文本块中找到高度匹配的语义特征,相当于给向量加了一个“强力磁铁”。
  2. 上下文的前置打包(解决信息孤岛):我们将“父节点”(如楼宇信息)和“子节点”(如具体设备)合并在同一个 Chunk(分片)里。LLM 在阅读时,天然就能理解 SENS1 是属于 lou1 的,消除了跨片段推理的难度。
  3. 向量特征的极度纯净(解决噪声干扰):通过提取公共属性(如“公共属性: status: 正常”)和折叠 ID 列表,我们去掉了大量重复的“背景噪音”。向量模型能更专注于捕捉高价值的层级和位置信息,不仅节省 Token,还让检索更加敏捷。

核心代码实现:一键生成层级化去重分片

以下是完整的 Python 代码实现,包含数据预处理、同义词注入、层级聚合以及智能去重抽象的核心逻辑:

import pandas as pd

def generate_optimized_hierarchical_chunks(df: pd.DataFrame) -> list:
    """
    将扁平的 DataFrame 转化为层级化且去重的 Markdown 分片列表
    """
    chunks = []
    # 预先建立索引,方便快速查找子节点
    children_map = df.groupby("belong_to")
    
    for index, row in df.iterrows():
        asset_id = row['model_id']
        asset_name = row['model_name']
        asset_type = row['type']
        belong_to = row['belong_to']
        
        # 1. 动态生成同义词(解决“1#楼”召回难的问题)
        synonyms = []
        if asset_type == 'building' and '#' in asset_name:
            num = asset_name.split('#')[0]
            synonyms = [f"{num}号楼", f"{num}楼", f"{num}栋"]
        
        # 2. 获取当前节点的所有直接子节点
        children = children_map.get_group(asset_id) if asset_id in children_map.groups else pd.DataFrame()
        
        # 3. 【核心优化】处理子节点:去重、抽象与折叠
        children_text_list = []
        if not children.empty:
            # 按类型分组
            for child_type, group in children.groupby("type"):
                # 提取该类型下所有子节点的公共属性(去重)
                common_attrs = {}
                for col in group.columns:
                    if col not in ['asset_name', 'model_id', 'model_name', 'belong_to']:
                        unique_vals = group[col].dropna().unique()
                        if len(unique_vals) == 1:
                            common_attrs[col] = unique_vals[0]
                
                # 构建紧凑的展示文本
                type_header = f"- **{child_type}**"
                if common_attrs:
                    attrs_str = ", ".join([f"{k}: {v}" for k, v in common_attrs.items()])
                    type_header += f" (公共属性: {attrs_str})"
                
                children_text_list.append(type_header)
                
                # 折叠展示 ID,大幅减少重复文本量
                id_list = group['model_id'].tolist()
                if len(id_list) > 5:
                    display_ids = ", ".join(id_list[:3])
                    children_text_list.append(f"  - 编号列表: {display_ids}... (共 {len(id_list)} 个)")
                else:
                    children_text_list.append(f"  - 编号: {', '.join(id_list)}")
        
        # 4. 组装最终 Markdown 文本块
        chunk_text = f"""### 🏢 {asset_name} (ID: {asset_id})
**层级路径**:{belong_to} -> {asset_id} ({asset_name})
**别名**:{', '.join(synonyms) if synonyms else '无'}

#### 1. 包含的子级资产
"""
        if children_text_list:
            chunk_text += "\n".join(children_text_list)
        else:
            chunk_text += "- 无直接子级资产\n"
            
        chunks.append({
            "id": asset_id,
            "text": chunk_text.strip(),
            "type": asset_type
        })
        
    return chunks

# --- 模拟带有层级关系和大量重复描述的数据 ---
raw_data = [
    {"asset_name": "park", "belong_to": "", "model_id": "park", "model_name": "园区", "type": "park"},
    {"asset_name": "lou1", "belong_to": "park", "model_id": "lou1", "model_name": "1#大楼", "type": "building"},
    {"asset_name": "IDC1-1", "belong_to": "lou1", "model_id": "IDC1-1", "model_name": "1F", "type": "floor"},
    # 假设这一层有10个传感器,它们的 type, status, vendor 全都一样(高频重复噪声)
    *[{"asset_name": f"SENS{i}", "belong_to": "IDC1-1", "model_id": f"SENS{i}", "model_name": "温湿度传感器", "type": "device", "status": "正常", "vendor": "海康威视"} for i in range(1, 11)]
]
df = pd.DataFrame(raw_data)

# 生成优化后的分片
optimized_chunks = generate_optimized_hierarchical_chunks(df)

# 打印查看 1F 楼层这一级的完美去重分片效果
for chunk in optimized_chunks:
    if chunk['id'] == 'IDC1-1':
        print(chunk['text'])

总结与建议

经过上面处理,现实测试,召回率从原来的50% 提高了90%多,效果非常明显。

RAG 系统的效果,三分靠模型,七分靠数据治理。如果你也面临召回率低、模型经常“胡说八道”的困境,不妨试试这套组合拳:

  1. 拒绝原始数据:不要把原始数据直接扔给rag系统,效果往往极差。
  2. 拒绝无脑切分:不要只用固定的字符数去切文档。
  3. 保留层级结构:将父子关系、同义词显式地写入分片文本中,解决上下文断裂。
  4. 智能去重降噪:提取公共属性,折叠重复 ID,让向量特征极度纯净。

召回率上来只是第一步,接下来你还可以引入 Rerank(重排序)模型来进一步过滤噪声,提升最终的回答准确率。继续冲!

最后,关注公号“ITMan彪叔” 可以添加作者微信进行交流,及时收到更多有价值的文章。