2、金融知识库构建:金融数据源类型、数据清洗与标准化、文档分块策略

金融RAG系统好不好用,一半的功夫在知识库上。

我见过太多团队,模型选得顶级,Prompt写得花里胡哨,结果一问三不知。为什么?因为喂进去的数据是垃圾。说白了,RAG的底层逻辑就是「垃圾进,垃圾出」。今天我们就聊聊怎么把金融数据这块硬骨头啃下来。

2.1 金融数据源类型:你得知道你在跟谁打交道

金融数据源五花八门,我习惯把它们分成四大类。每一类的脾气都不一样,处理方式也得跟着变。

数据源类型 典型格式 核心特点 处理难点
研报 PDF、Word 长文本、图表多、术语密集 表格提取、图表OCR
公告 PDF、HTML 格式规范、法律性强 冗余信息(免责声明)
新闻 HTML、JSON 时效性强、噪音大 去重、情感分析
财报 PDF、Excel 结构化数据、数字密集 数值对齐、单位统一

研报:我最头疼的就是它。一份几十页的PDF,里面全是图表和公式。直接扔给解析器?出来的文本乱成一锅粥。我建议先做版面分析,把标题、正文、表格、图片分开处理。

公告:这东西格式死板,但坑也不少。每份公告末尾都有一大段「本公司董事会及全体董事保证本公告内容不存在任何虚假记载...」——这种废话必须干掉。我在项目中专门写了个正则库来匹配这些模板。

新闻:量大管饱,但质量参差不齐。同一件事,十家媒体能写出十个版本。去重是个大问题。我一般用SimHash或者MinHash做近似去重,效果还行。

财报:数字是核心。但PDF里的表格经常跨页,数字格式也不统一。比如「1,234.56」和「1234.56」可能同时出现。嗯,这里要注意,必须做数值标准化。

2.2 数据清洗与标准化:把脏活干漂亮

数据清洗听着简单,做起来全是细节。我总结了一套流程,你可以参考。

核心原则:保留信息,去除噪音,统一格式。

2.2.1 文本清洗

  • 去除HTML标签:新闻数据里最常见。用BeautifulSoup或者正则都行。
  • 去除特殊字符:比如乱码、不可见字符。我遇到过一份研报,里面全是「\u200b」这种零宽空格,肉眼根本看不出来。
  • 统一换行符:Windows和Linux的换行不一样,必须统一成\n。
  • 去除页眉页脚:PDF解析后经常带页码和公司名,这些要干掉。

2.2.2 数值标准化

金融数据里数字格式五花八门。我建议做以下处理:

def normalize_number(text):
    # 统一千分位分隔符
    text = re.sub(r'(\d),(\d)', r'\1\2', text)
    # 统一小数点
    text = text.replace('.', '.')
    # 统一单位:万、亿
    text = re.sub(r'(\d+)万', lambda m: str(int(m.group(1)) * 10000), text)
    text = re.sub(r'(\d+)亿', lambda m: str(int(m.group(1)) * 100000000), text)
    return text

我曾经在处理一份财报时,发现「1.2亿」和「120,000,000」同时出现。如果不统一,检索时根本对不上。

2.2.3 日期标准化

金融数据里日期格式也是重灾区。「2024-01-15」、「2024/01/15」、「2024年1月15日」——必须统一成ISO 8601格式。

小技巧:用dateutil.parser.parse()可以自动识别大部分日期格式。但中文日期需要额外处理。

2.3 文档分块策略:切得好,检索才准

分块是知识库构建的核心。块切得太小,上下文丢失;切得太大,检索噪音多。我试过三种主流策略,各有优劣。

2.3.1 固定大小分块

最简单粗暴的方法。设定一个固定长度(比如512个token),然后按顺序切。

def fixed_size_chunk(text, chunk_size=512):
    tokens = tokenizer.encode(text)
    chunks = []
    for i in range(0, len(tokens), chunk_size):
        chunk = tokenizer.decode(tokens[i:i+chunk_size])
        chunks.append(chunk)
    return chunks

优点:实现简单,速度快。
缺点:容易切断语义。比如一句话被切成两半,检索时两边都不完整。

我个人不太推荐这种方法,除非你的数据非常规整,比如每段长度都差不多。

2.3.2 语义分块

这个就聪明多了。它根据语义边界来切,比如段落、句子、或者主题变化。

我常用的方法是:先用句号、问号、感叹号切句子,然后按段落合并。如果段落太长,再用主题模型判断是否需要进一步拆分。

def semantic_chunk(text):
    # 按段落切分
    paragraphs = text.split('\n\n')
    chunks = []
    for para in paragraphs:
        # 如果段落太长,按句子切
        if len(para) > 1000:
            sentences = re.split(r'[。!?]', para)
            chunks.extend(sentences)
        else:
            chunks.append(para)
    return chunks

注意:语义分块依赖文本质量。如果原始数据格式混乱,效果会大打折扣。我曾经处理过一份扫描版PDF,段落标记全是乱的,语义分块直接崩了。

2.3.3 递归分块

这是我最喜欢的方法。它先按大粒度切,如果块太大,再递归地按小粒度切。

比如:先按章节切,如果章节太长,按段落切;如果段落还长,按句子切。这样既能保证语义完整,又能控制块大小。

def recursive_chunk(text, max_size=512):
    # 按章节切
    sections = re.split(r'#+\s', text)
    chunks = []
    for section in sections:
        if len(section) <= max_size:
            chunks.append(section)
        else:
            # 按段落切
            paragraphs = section.split('\n\n')
            for para in paragraphs:
                if len(para) <= max_size:
                    chunks.append(para)
                else:
                    # 按句子切
                    sentences = re.split(r'[。!?]', para)
                    chunks.extend(sentences)
    return chunks

你想想看,这种方法是不是更符合人的阅读习惯?先看大标题,再看段落,最后看句子。

2.4 知识体系总览

下面这张图概括了金融知识库构建的核心流程。从数据源到最终可检索的向量库,每一步都有讲究。

金融知识库构建流程 金融数据源 研报 公告 新闻 财报 数据清洗与标准化 文本清洗 数值标准化 日期标准化 文档分块 固定大小 语义分块 递归分块 向量化与索引 可检索知识库 四大数据源 三大清洗步骤 三种分块策略

这张图把整个流程串起来了。从数据源开始,经过清洗标准化,再到分块,最后向量化入库。每一步都是下一环的基础。

我的建议:别一上来就追求完美。先用固定大小分块跑通流程,再逐步优化到语义分块或递归分块。迭代才是王道。

好了,金融知识库构建的核心内容就这些。数据源要分清楚,清洗要彻底,分块要灵活。这三件事做好了,你的RAG系统就成功了一半。

专注资料整理