本文约3000字,建议阅读时间15~20分钟
关键字:AI,LLM,RAG,Retrieval-Augmented Generation,知识库,知识降噪,无关知识过滤,正则表达式,Regex,BeautifulSoup,Ollama
一、知识降噪的必要性
知识获取基于业务需求或者目标从不同模态(比如文本、图片、语音、视频等)提取了尽可能相关的文本信息,但常常因为涉及面广,且可能需要历史追溯,知识供给人员往往出于经验的判断给到一些“可能相关”的文档和知识载体。由于知识来源多样,既可能是规范的电子文档,也可能是手写笔记,既可能是一场专业培训的视频,也可能是一个多部门会议沟通的录音……。规范的文档和专业的视频可能对同一组织内同一术语进行了不同的描述,比如文档提到了用中文描述的某种运营方法全称,而培训视频则用了该方法英文首字母的简称;手写笔记可能只是记录了重要的一些点,内容连贯性有所欠缺;专业培训视频为了缓解培训的枯燥可能引入了与培训知识无关的某个网络笑话;会议录音可能由于多人同时发声导致识别准确率有所下降,且容易出现较多的“这个嗯嗯嗯”(这个……)、“那个嗯嗯嗯”(那个……)、“真的假的”(真的?假的?)等等与知识无关的信息。
知识获取只是取得了潜在的知识,可以理解为只是做了知识相关性的一个预判,因而有必要对知识进行进一步处理加工(即降噪),进一步提高“硬性知识”(比如真正与业务需求相关的知识/干货),降低不必要的“粘性知识”(比如一些在硬知识附近的中性知识),彻底删除“滞性知识”(比如完全不相关的冗语/废话),以保证入库的质量。知识降噪的最终目标仍是保证知识质量,提高后续检索的准确性和召回率。
二、知识降噪
知识降噪是一个通过基于多种知识源的统一知识加工规则和标准,降低知识冗余和无关性,增强知识的内聚性,从而建立高密度、低耦合的正式语境标准正式语境知识的过程。所谓标准正式语境知识是指以某种语言(如中文,英文可以作为中文的补充)及其标点符号为主且以正式语气形成的具有连贯性和专业性的知识。知识降噪主要包括无关知识的过滤、中性知识的筛选以及相关知识的增强等,既可以通过算法实现,也可以借助本地LLM(比如GLM、deepseek、Qwen等)实现。
1、无关知识的过滤
无关知识的过滤主要包括去除版式残留、清理格式符号、知识语义过滤等。
(1)去除版式残留主要通过正则表达式或规则匹配(如python标准库re、针对网页解析的BeautifulSoup库)来剔除:如word文档中包含重复或者具有规律的页眉/页脚/页码等内容、如pdf文档中出现的相同水印文字、如一些标准文件中特定位置(比如文档末的声明)、如html网页开始的导航栏或者面包屑路径以及特定位置的广告语等。
示例1:剔除页码(如文档中的页码标识:第X页)
''''''python
import re
# 使用re.sub()函数删除“第*页”
def page_number_clean(content:str):
return re.sub(r'第\d+页', '', content)
''''''python
示例2:剔除网页中的面包屑路径内容
''''''python
#pip install beautifulsoup4 lxml
from bs4 import BeautifulSoup
html_content = '''
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">首页</a></li>
<li class="breadcrumb-item"><a href="/category/electronics">电子产品</a></li>
<li class="breadcrumb-item"><a href="/category/electronics/phones">手机</a></li>
<li class="breadcrumb-item active" aria-current="page">智能手机</li>
</ol>
</nav>
'''
def breadcrumb_trail_clean(html_content:str):
# 1. 解析 HTML
soup = BeautifulSoup(html_text, "html.parser")
# 2. 查找包含面包屑的 <nav> 标签
# 这里通过 aria-label="breadcrumb" 属性来精准定位
breadcrumb_nav = soup.find("nav", attrs={"aria-label": "breadcrumb"})
# 3. 如果找到了该标签,则将其彻底删除
if breadcrumb_nav:
breadcrumb_nav.decompose()
# decompose() 会同时删除标签及其所有子内容
# 4. 打印处理后的结果
print(soup.prettify())
# 5. 还原为文本
return soup.decode()
''''''python
(2)清理格式符号亦可以通过正则表达式或者算法来去除乱码字符、无意义的Unicode符号、其他特殊符号(如注释符#)、一段文本中的空格、多余的连续换行符等。
示例1:去除乱码和无意义Unicode符号,仅保留中/英文、数字、中/英文标点、连字符、下划线
''''''python
# pip install regex
import regex
# import re
def clean_and_normalize_text(text:str):
"""
去除乱码和无意义Unicode符号,仅保留中文、英文、数字、中英文标点及下划线。
"""
if not isinstance(text, str):
return ""
# 定义白名单正则表达式
# 正则表达式解析:
# [^ ... ] : 取反,匹配不在括号内的所有字符
# a-zA-Z : 英文字母
# 0-9 : 数字
# \u4e00-\u9fa5 : 常用中文字符范围(re正则)
# \p{Han} :匹配所有 CJK 统一汉字,含古籍生僻字(regex正则)
# \u3000-\u303f : 中文标点符号范围(如:,。!?、;:“”等)
# \uff00-\uffef : 全角字符范围(包含全角英文字母、数字和标点,如:A,1,!)
# \u2000-\u206f : 通用标点符号 (General Punctuation,含英文引号、破折号等)
# \s : 保留空白字符(空格、换行等),防止文本粘连。如果不需要空格可去掉
# \- : 保留连字符(放在字符集开头或结尾,或转义)
# _ : 保留下划线
pattern=r'[^\p{Han}a-zA-Z0-9\u3000-\u303f\uff00-\uffef\u2000-\u206f\s\-_]+'
# pattern = r'[^a-zA-Z0-9\u4e00-\u9fa5\\u3000-\u303f\uff00-\uffef\u2000-\u206f\s\-_]+'
# 执行替换
cleaned_text = regex.sub(pattern, '', text)
# cleaned_text = re.sub(pattern, '', text)
return cleaned_text
''''''python
示例2:字符替换
''''''python
###去除一段文本中的空格
cleaned_text=text.replace(' ','')
###去除多余的连续换行符(保留一个换行符)
import re
# 匹配连续的 \r\n, \n, 或 \r
result = re.sub(r'(?:\r\n|\r|\n){2,}', '\n', text)
''''''python
(3)知识语义过滤则可以通过调用本地大模型(类似阅读理解的方式)来对文档块进行知识主题语义相关性判别。对于原始完整文档可采用较高层级(如小节)的文档块进行识别,如果原始文档没有明显的层级结构,建议采用大块(如确定文档分块大小的3倍)分割进行识别。
示例1:调用ollama本地模型进行主题相关判断
''''''python
import requests
import json
def llm_evaluate_relevance(text, topics, model="qwen2.5:0.5b"):
"""
调用本地通用大模型批量评估相关性并返回数值
"""
url = "http://localhost:11434/api/chat"
topics_str = "\n".join([f"{i+1}. {topic}" for i, topic in enumerate(topics)])
prompt = f"""你是一个专业的语义相关性评估专家。请评估以下文本与各个候选主题的相关性。
【文本内容】:
{text}
【候选主题】:
{topics_str}
【评估标准】:
1.0 - 高度相关:文本直接回答或包含该主题的核心信息
0.7-0.9 - 相关:文本与该主题相关,包含有用信息
0.4-0.6 - 部分相关:文本与该主题有一定关联,但不是核心
0.1-0.3 - 微弱相关:文本与该主题只有很少关联
0.0 -0.1不相关:文本与该主题完全无关
【输出要求】:
请仅返回一个合法的 JSON 数组,不要包含任何 Markdown 标记。
数组中每个对象包含 "topic" (主题) 和 "score" (0.0到1.0之间的浮点数) 两个字段。"""
payload = {
"model": model,
"messages": [{"role": "user", "content": prompt}],
"stream": False,
"temperature": 0.1
}
response = requests.post(url, json=payload)
response.raise_for_status()
result_text = response.json()["message"]["content"]
# 健壮的解析逻辑:提取 JSON 并校验分数范围
try:
match = re.search(r'\[.*\]', result_text, re.DOTALL)
results = json.loads(match.group()) if match else []
# 确保分数在 0.0 到 1.0 之间
for res in results:
res["score"] = max(0.0, min(1.0, float(res["score"])))
return results
except Exception as e:
print(f"解析大模型返回结果失败: {result_text}")
return []
# 注:输入文本块取决于调用大模型支持的Token大小
''''''python
夜雨聆风