4. 金融文本清洗:去噪、标准化、实体对齐与同义词替换
各位同学,咱们今天聊点实在的。金融文本清洗,说白了就是给数据「搓澡」。你从交易所、财报、新闻里扒下来的原始文本,那叫一个脏——广告满天飞,页眉页脚乱入,日期格式五花八门。我刚开始做金融RAG的时候,就吃过这个亏。模型检索出来的结果,因为文本没洗干净,召回率直接腰斩。嗯,今天咱们就把这块硬骨头啃下来。
4.1 去噪:干掉那些没用的东西
金融文本里的噪声,我归纳为三类:广告、页眉页脚、以及各种无意义的HTML标签。你想想看,一篇研报里突然冒出「开户送佣金」的广告,检索系统要是把这玩意儿当正文,那不就乱套了?
核心原则:保留金融语义,剔除营销与格式噪声。
我个人习惯用正则表达式做第一轮清洗。比如页眉页脚,通常有固定模式——「第X页 / 共Y页」、「来源:某某财经」。我写过一个通用规则集:
import re
def clean_financial_noise(text):
# 去掉页眉页脚:匹配 "第1页/共10页" 这类模式
text = re.sub(r'第\d+页\s*/\s*共\d+页', '', text)
# 去掉广告关键词
ad_keywords = ['开户', '佣金', '免费领取', '点击链接']
for kw in ad_keywords:
text = re.sub(rf'.*?{kw}.*?[\n。!]', '', text)
# 去掉多余空白
text = re.sub(r'\s+', ' ', text).strip()
return text
我的经验:别一上来就用深度学习模型去噪。正则表达式跑得快,而且可解释性强。我曾经在一个项目里,用50行正则就干掉了90%的噪声,剩下的10%才交给NER模型处理。
4.2 标准化:让日期、金额、股票代码说同一种语言
金融数据里,日期格式最让人头疼。有的写「2024-01-15」,有的写「2024年1月15日」,还有写「01/15/2024」的。检索系统如果不管这些,那「2024-01-15」和「2024年1月15日」就会被当成两个不同的实体。你想想看,这召回率能高吗?
我建议统一转为ISO 8601格式。代码示例如下:
from dateutil import parser
def standardize_date(text):
# 匹配常见中文日期格式
date_pattern = r'(\d{4})年(\d{1,2})月(\d{1,2})日'
text = re.sub(date_pattern, r'\1-\2-\3', text)
# 再用dateutil兜底解析
try:
return parser.parse(text).strftime('%Y-%m-%d')
except:
return text
金额标准化也是重灾区。「1.2亿」、「120,000,000」、「1.2×10^8」——这些都得转成统一数值。我习惯转成浮点数,保留两位小数:
def standardize_amount(text):
# 处理 "1.2亿" -> 120000000.00
text = re.sub(r'(\d+\.?\d*)\s*亿', lambda m: str(float(m.group(1)) * 1e8), text)
# 去掉千分位逗号
text = re.sub(r'(\d),(\d)', r'\1\2', text)
return text
注意:股票代码标准化要小心。A股是6位数字,港股是5位,美股是字母+数字。我曾经遇到过把「600519」和「600519.SH」当成两个不同实体的情况。建议统一加后缀:A股加「.SH」或「.SZ」,港股加「.HK」。
4.3 实体对齐:把「阿里」和「阿里巴巴」认成一家
实体对齐,说白了就是解决「同名不同指」和「同指不同名」的问题。金融文本里,公司名、人名、产品名经常有缩写、别名。比如「腾讯」和「Tencent」,「茅台」和「贵州茅台」。
我常用的方法是构建一个实体映射表。用字典存起来,查询时直接替换:
entity_map = {
'阿里': '阿里巴巴',
'腾讯': '腾讯控股',
'茅台': '贵州茅台',
'平安': '中国平安',
'招行': '招商银行'
}
def align_entities(text):
for alias, standard in entity_map.items():
text = text.replace(alias, standard)
return text
避坑指南:我曾经直接对整个文本做全局替换,结果把「阿里巴巴」里的「阿里」又替换了一遍,变成了「阿里巴巴巴巴」。正确的做法是:先分词,再对每个词做映射。或者用正则加边界匹配。
更高级的做法是用向量相似度。把实体名转成embedding,然后找最相似的标准化实体。但说实话,对于金融RAG,字典映射法已经够用了。除非你的实体库有几万个,那才需要上向量检索。
4.4 同义词替换:让检索更聪明
同义词替换,目的是扩大召回。用户搜「净利润」,你希望也能匹配到「归母净利润」、「净利」、「纯利」。我习惯维护一个同义词词典:
synonym_dict = {
'净利润': ['归母净利润', '净利', '纯利', 'net profit'],
'营收': ['营业收入', '总收入', 'revenue'],
'市盈率': ['PE', '市盈率倍数']
}
def expand_synonyms(text):
for word, synonyms in synonym_dict.items():
for syn in synonyms:
text = text.replace(syn, word)
return text
这里有个细节:替换方向要统一。我建议把所有同义词都映射到同一个标准词上。比如「净利」和「纯利」都映射到「净利润」。这样检索时,不管用户输入哪个词,都能命中同一批文档。
核心逻辑:同义词替换不是为了让文本更丰富,而是为了让检索更聚焦。标准词越少,检索的命中率越高。
4.5 本章知识体系
下面这张图,是我自己画的清洗流程。你看一眼,心里就有谱了:
你看,整个流程是线性的。从原始文本进去,经过去噪、标准化、实体对齐、同义词替换,最后出来的是干净、统一、可检索的文本。每一步都不可或缺。
4.6 实战建议
最后,我给大家三个实战建议:
- 先做小批量验证。 别一上来就全量清洗。拿100条文本跑一遍,看看清洗效果。我经常发现正则写漏了,或者替换错了。
- 保留原始文本。 清洗后的文本用于检索,但原始文本要留着。万一清洗逻辑有bug,你还能回滚。
- 监控清洗质量。 我习惯在清洗后统计「实体数量」、「日期格式一致性」等指标。如果某天指标突然下降,说明清洗逻辑可能出了问题。
一个小技巧:清洗代码一定要写单元测试。我吃过亏——改了一行正则,结果把「2024年1月」变成了「2024-1-」,少了月份。单元测试能帮你抓住这种低级错误。
好了,文本清洗这块就讲到这里。记住一句话:脏数据进,脏结果出。清洗花的时间,会在检索效果上十倍地回报你。