金融文本情感分析实战:从NLP建模到情绪分数的Python实现
发布时间:2026/9/27 23:05:32来源:尧图网络
简介这是一份面向金融数据分析、NLP算法学习者的Python金融文本情感分析模型代码实现。资源围绕从金融新闻、公告等非结构化文本中提取情绪信号、辅助投资决策的核心任务完整覆盖文本清洗与分词、停用词移除、情感词典构建、TF-IDF特征工程以及朴素贝叶斯、支持向量机、逻辑回归等分类模型的训练与评估流程。压缩包共78个文件以Python脚本py、文本数据txt、pickle模型plk、JSON/XML配置等为主同时包含少量可视化图表与说明文档整体仅1.15MB便于快速下载并搭建实验环境。已有3350人学习使用。通过学习源码与数据集读者可以掌握金融文本预处理的完整思路理解情感词典与机器学习结合的情感判定策略并借助准确率、F1值等指标调优模型适合课程设计、论文复现或量化文本分析的入门实践。1. 金融文本情感分析从研报堆到情绪分数的第一步聊到基于 Python 的金融文本情感分析模型代码实现大多数人第一反应是把新闻标题丢进一个情感分析模型然后得到正/负/中性标签。但这个思路在金融语料上几乎必翻车——因为金融文本和日常社交媒体文本的“情感”根本不是一回事。研报里“维持增持评级”是正面“业绩略低于预期”是中性偏负面而“利好出尽”这种话普通模型根本读不懂。这篇笔记要解决的正是从数据标注、模型选型到代码落地的完整路径适合量化研究员、金融数据工程师以及想用 NLP 处理公告、新闻、研报文本的从业者。我会先把任务边界拆清楚再给出一套能跑通的最小实现最后把最常见的坑逐个点名。2. 任务定义与数据准备先搞清“金融情感”和日常情感不是一回事金融文本情感分析首先要回答一个前置问题你要分析的对象是什么粒度输出什么标签。常见粒度有三种——文档级、句子级、事件级。文档级适合给整篇研报打分句子级适合抽取关键表述事件级则要结合主体和事件类型做结构化判断。代码实现上句子级是最稳定的单元便于对齐标注也便于解释。2.1 标注体系三分类还是五分类买卖评级怎么映射金融情感分析的标签体系直接决定模型能力边界。最常用的是三分类体系正面、负面、中性。但在交易场景中三分类往往不够用——“中性”这个类别吞掉了太多有效信息“略超预期”和“大幅低于预期”在模型输出上可能都是中性但前者对应的事件冲击完全不同。我一般建议在项目起步阶段就采用五分类强正面、弱正面、中性、弱负面、强负面。理由有三第一金融公告的语言往往温和大多数负面信息用弱负面表述比如“增速放缓”“承压”第二五分类可以在后处理阶段很容易折叠成三分类或正负二分类反之却做不到第三标注人员对五分类的一致性并不比三分类差太多。下面是一个标注样例。{ text: 公司第四季度营收同比增长12%略低于市场预期主要受海外业务拖累但毛利率环比改善。, label: -1, label_name: 弱负面, source: annual_report, date: 2024-12-31 }这里的 label 编码我采用 -2 到 2 的离散值而不是 0 到 4。原因在于后续如果要计算情绪得分、做因子合成带符号的编码可以直接参与算术运算不需要再做一次映射表。数据来源上优先选公开财报、业绩预告、分析师研报摘要和交易所公告这些文本的格式相对固定、专业术语密度高。社交平台文本股吧、微博噪声太大建议放在第二阶段再扩展不要作为第一版模型的主语料。2.2 金融语料的预处理数值、专名和上下文抽离金融文本的预处理比通用文本多两个步骤数值归一化和专名保护。通用的 jieba 分词对“同比增长12.3%”这种片段处理得并不差但问题出在后续模型视角上——如果“12.3%”被切成普通 token 参与注意力计算模型很难学到“增长幅度越大越正面”这类数值梯度信息。我的做法是在文本进入模型前先把明显的数值结构替换成占位符同时记录替换前后的映射关系。这样既压缩了词表压力也让模型把注意力放在句法结构上。原始文本片段归一化后片段说明营收同比增长12.3%营收同比增长[NUM_PERCENT]百分比数值净利润为4.5亿元净利润为[NUM_AMOUNT]金额数值拟派发现金红利每股0.8元拟派发现金红利每股[NUM_PRICE]价格数值第三季度[PERIOD_QUARTER]时间周期进一步地公司名、人名、产品名这些专名如果被分词器切碎会直接破坏句子主谓结构。常见做法是先用领域词典做最长匹配把识别到的专名用标签包裹起来再进入分词和模型推理。需要特别注意的是标点符号不能一概删除。金融文本里的分号和逗号往往区隔多个并列事件句号则是天然的句子边界。我在做句子级切分时会以句号、感叹号、问号为硬边界以分号为软边界——软边界意味着如果前后两个子句都指向同一个事件主体就合并成一句否则拆开。这个细节对后续的事件级情感判断影响很大因为“营收增长但利润下滑”这种转折结构只有在同一句内才能被模型捕捉到。预处理这一层没有太多玄学核心是“保留结构压缩噪声”。如果你偷懒直接把文本丢进模型也能跑出结果但精度会稳定掉三到五个点尤其是带有数值比较的句子几乎都会出错。这一层是后面所有模型工作的地基值得花时间做扎实。3. 模型选型通用情感模型为什么在金融语料上集体“失聪”金融文本的句法特征和日常文本差异巨大。最典型的就是“转折才是信息”的语言习惯比如“公司业绩略低于预期但全年目标不变管理层维持乐观”——这里的核心情感是“乐观”但前半句的“略低于预期”才是真正影响股价的信息。这种双层语义结构通用情感模型几乎无法正确处理。3.1 金融文本的三类特殊句法条件句、反事实和“双关”表述我把金融文本里最容易让模型翻车的句子结构分成三类每类都有对应的处理思路。第一类是条件句。“若下游需求持续疲软公司可能下调全年指引”——这句话在字面上没有强烈的负面词但语义指向明确偏负面。通用模型往往把它判为中性因为“疲软”之前有个“若”字让它犹豫。有效的处理方式是让模型看到完整的条件-结果对而不是把子句拆开单独打分。第二类是反事实表述。“如果不是原材料涨价公司毛利率本可维持去年水平”——这句话核心在“本可”暗示实际没有维持住。这种结构需要模型理解虚拟语气而通用情感模型训练数据里这类句子占比极低。第三类是“双关”表述金融行业尤其常见。“利好兑现”“利空出尽”“靴子落地”——表面是中性陈述实际方向取决于上下文。这类句子即使是人也需要额外的背景知识才能判断模型做不了是正常的但你要在产品设计上预留人工复核通道而不是指望模型硬扛。3.2 三条技术路线对比词典法、通用预训练模型、领域预训练模型在代码实现之前先明确三条路线的选型逻辑。技术路线精度水平人工成本计算资源适用场景金融词典 规则打分低到中低极低快速原型、小样本探索通用预训练模型微调中中单卡即可标注数据 2000 条以上金融领域预训练模型微调中高中高单卡即可正式项目首选词典法最朴素的做法是统计句子中正负词的出现频次和强度再叠加否定词和前缀修饰进行纠偏。开源金融词典有很多但中文金融情感词典的覆盖面普遍不够——“略超”“小幅”“不及”这些量级副词会显著改变情感强度却经常不在词典里。它的最大价值在于冷启动比如第一周就能得到一个可解释的基线结果用来检验标注方案是否合理。通用预训练模型微调是最常见的入局方案。BERT、RoBERTa、ELECTRA 等模型在通用情感分析上表现优秀但直接拿来做金融文本效果会明显下降——一方面因为金融专业词汇的 OOV 问题严重另一方面因为这类模型对“转折结构”不敏感。微调则能在一定程度上让模型适应金融语料的分布前提是你的标注数据量足够且标注质量有把控。金融领域预训练模型是正式项目的首选。这类模型在通用模型的温启动基础上用大量金融语料做了继续预训练词表和权重都更贴合金融文本。在同等标注数据量下精度通常比通用模型微调高 3 到 5 个点尤其在研报、公告这类正式文体上优势明显。它的缺点是生态相对小但 PyTorch 生态的兼容性已经足够好。我的选型建议是如果你只有几百条标注数据不要一上来就微调大模型先用词典法跑基线再把深度模型作为替代方案做对比如果你有 2000 条以上标注数据直接走领域预训练模型微调路线把词典法保留作为可解释性分析的辅助工具。4. 用 Python 跑通最小情感分析流水线数据加载到模型推断这一章目标是让你在本地跑通从文本输入到情绪分数输出的完整链路。我会先给一个最小实现再扩展成批量流水线。环境依赖只需要 transformers、torch、pandas数据集我建议你用自己的文本文件格式保持 CSV 即可。4.1 最小实现加载预训练模型并输出单句概率分布先看一个可以直接运行的脚本。它做的事情是加载一个金融领域的情感分析模型、读入一句文本、输出五分类概率分布和对应的情绪分数。import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification MODEL_NAME your-finetuned-financial-model # 替换为你的模型路径或 HuggingFace 模型名 LABELS [-2, -1, 0, 1, 2] tokenizer AutoTokenizer.from_pretrained(MODEL_NAME) model AutoModelForSequenceClassification.from_pretrained(MODEL_NAME, num_labels5) model.eval() def predict_single(text: str): encoded tokenizer( text, truncationTrue, max_length128, return_tensorspt ) with torch.no_grad(): logits model(**encoded).logits probs torch.softmax(logits, dim-1).squeeze() prob_list probs.tolist() score sum(p * label for p, label in zip(prob_list, LABELS)) pred_label LABELS[torch.argmax(probs).item()] return pred_label, score, prob_list if __name__ __main__: text 公司四季度营收同比增长12%略低于市场预期但全年目标保持不变。 pred, score, probs predict_single(text) print(f预测标签: {pred}, 情绪分数: {score:.3f}) print(f概率分布: {probs})关于这段代码有三点需要说明。第一max_length128对大多数公告句子够用但如果你的文本是一整段研报摘要建议先做句子切分再逐句推理而不是靠truncationTrue硬截断——硬截断会把转折结构腰斩直接导致误判。第二score的计算方式是把五个类别的概率作为权重与标签值做加权求和这样得到的情绪分数是连续值比直接取 argmax 更细腻。第三模型推理阶段务必使用torch.no_grad()否则会为中间变量构建计算图内存开销成倍增长。如果你是第一次跑建议先拿 20 条你手工标注过的文本做冒烟测试重点看模型对“转折句”和“条件句”的输出是否合理。如果发现模型把所有句子都判成中性优先检查是不是模型加载了错误的权重文件或者标签映射顺序和训练时不一致。4.2 批量推理与结果落盘写出一个可复用的流水线单句推理只能演示真实场景下面临的是数百到数万条文本。批量推理的核心是控制批次大小和显存占用同时把流程封装成可复用的函数。下面给出一个带进度显示和结果落盘的版本。import pandas as pd from tqdm import tqdm from torch.utils.data import DataLoader, Dataset class TextDataset(Dataset): def __init__(self, texts, tokenizer, max_len128): self.texts texts self.tokenizer tokenizer self.max_len max_len def __len__(self): return len(self.texts) def __getitem__(self, idx): text self.texts[idx] encoded self.tokenizer( text, truncationTrue, max_lengthself.max_len, paddingmax_length, return_tensorspt ) return {k: v.squeeze() for k, v in encoded.items()} def batch_predict(texts, model, tokenizer, batch_size32, max_len128): dataset TextDataset(texts, tokenizer, max_len) loader DataLoader(dataset, batch_sizebatch_size, shuffleFalse) results [] model.eval() with torch.no_grad(): for batch in tqdm(loader, descPredicting): logits model(**batch).logits probs torch.softmax(logits, dim-1) scores (probs * torch.tensor(LABELS)).sum(dim-1) results.extend(scores.tolist()) return results df pd.read_csv(financial_texts.csv, encodingutf-8) df[sentiment_score] batch_predict(df[text].tolist(), model, tokenizer) df.to_csv(financial_texts_with_scores.csv, indexFalse, encodingutf-8-sig)批量推理里的batch_size是一个需要凭经验调参的量。显存紧张就调到 16 或 8显存充裕可以试 64但收益不大——因为推理瓶颈通常在 CPU 分词的耗时上GPU 计算反而是快的。另外需要注意paddingmax_length会强制把每条样本都补齐到 128 个 token短文本多的情况下浪费约 30% 到 50% 的算力。如果你有大量短文本可以改成paddingTrue动态填充代价是 batch 内的样本长度不一致偶尔会触发某些 CUDA 算子的性能抖动。落盘的encodingutf-8-sig是给 Excel 用户留的后路——如果直接写 UTF-8用 Excel 打开 CSV 会出现中文乱码。这个小细节在职场协作中特别实用能帮你省掉不必要的沟通成本。5. 避坑指南金融情感分析最常见的五个坑与排查思路模型跑通只是起点真正消耗时间的是上线后的精度问题。这组实践中的踩坑记录按“现象 → 原因 → 解决”组织建议直接对照排查。第一模型输出全是中性。这类现象最常出现在刚加载完预训练模型、还没微调就直接推理的场景。原因是模型对金融文本的分布不熟悉默认倾向保守预测。解决方法是先做小样本微调或者换用已经微调过的领域模型权重而不是直接在通用模型上推理。第二带有明确数值的句子判断错误。“净利润同比增长 35%创历史新高”被判成中性。原因在于上一章做了数值归一化后模型学到的规律是“数值出现趋近中性”这其实是归一化带来的副作用——模型确实很难只凭[NUM_PERCENT]这个占位符区分正负方向。解决思路是把数值区间信息显式编码到句子里比如把[NUM_PERCENT]拆成[NUM_PERCENT_HIGH]和[NUM_PERCENT_LOW]两个占位符让模型能感知到量级差异。第三长文档被截断后关键信息丢失。一段研报有 800 个 token硬截断到 128 个 token 后结尾处的风险提示被丢弃模型输出完全偏乐观。原因是 max_length 太小。解决方法是先用句号把文档切成子句列表逐句推理后再按子句的最大概率或加权平均聚合出整篇文档的情绪分数而不是一截了之。第四训练集和推理集的标签映射不一致。训练时标签编码是-2, -1, 0, 1, 2推理时模型头输出的次序是0, 1, 2, 3, 4两者如果错位会导致强负面样本被当成强正面。这个坑隐蔽在精度报告里几乎看不出来因为整体准确率看起来并不低。解决方法是保存模型时同时保存标签映射文件推理代码强制校验标签数量并在输出结果里打印一行 label 列表供人工核对。第五不平衡数据下的虚高精度。金融公告里中性样本占比极高经常超过 70%模型全预测中性也能拿到 70% 准确率。但这没有实用价值。解决方法是两组并行一组用加权损失函数CrossEntropyLoss(weight...)另一组把中性样本和正负样本按 1:2 的比例下采样分别训练并对比差异。如果正负类样本的 F1 没有提升说明模型并没有真正学到判别信息。6. 把情绪分数变成可用的信号从句子到日频因子得到句子级情绪分数后距离实际可用的投资信号还差最后一步聚合。我的习惯做法是先把同一只股票的当天所有句子按日期对齐以当日全部句子的情绪分数均值作为日频因子。但均值有个缺点——极端情绪会被大量中性句子稀释。所以在计算均值之外我还会记录当日“强正面和强负面的句子占比”用两者之差作为情绪的“动量修正项”。具体公式可以简化为daily_score mean(score) 0.2 * (pct_strong_positive - pct_strong_negative)。这个 0.2 的系数是经验值你可以用回测数据自己校准。验证情绪因子是否有效最直接的做法是算它与次日收益的秩相关。下面是一个最小验证脚本。import pandas as pd df pd.read_csv(sentiment_daily.csv, encodingutf-8-sig) df[date] pd.to_datetime(df[date]) df df.sort_values([stock_code, date]) df[next_return] df.groupby(stock_code)[close].pct_change().shift(-1) corr df.groupby(date).apply( lambda x: x[sentiment_score].corr(x[next_return], methodspearman) ).mean() print(f平均秩相关: {corr:.4f})这段代码的关键在groupby(date)这一步——它按自然日做截面分析避免让时间序列的自相关干扰结论。如果算出的平均秩相关在 0.02 以上说明情绪因子有边际预测能力如果在 0 附近摇摆优先检查是否有大量无效噪声文本混入或者情绪分数是否只在少数极端事件上有区分度。还有一个延伸方向把你做的文本情绪分数当成基础特征和其他量价因子一起进线性模型或者树模型看它是否提供增量信息。这一步才是真正验证“金融文本情感分析模型有没有价值”的环节。最后说一个我的习惯每次新跑一批数据我都会随机抽 50 条模型预测结果人工过一遍确认错误模式没有在新数据上变严重。模型上线不是终点而是持续校准的开始。希望这份踩坑笔记能帮你在金融文本情感分析这条路上少走一段弯路。本文还有配套的精品资源点击获取
网站建设高端定制企业官网