ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

股市评论情感分析毕设:从TF-IDF/LSTM到情感指数可视化

股市评论情感分析毕设:从TF-IDF/LSTM到情感指数可视化

简介:面向计算机相关专业毕业生的股市评论情感分析完整项目包,整合机器学习与深度学习两套建模方案,可用于毕业设计、课程设计或期末大作业。项目经导师指导并评审98分,包含数据处理、模型训练、情感指数构建与可视化呈现的完整流程,适合希望从零上手情感分析实战的本科或研究生。资源共12个文件,压缩包约24.72MB,以Python脚本、CSV数据文件、TXT情感词典/说明文档、PNG图表和Markdown报告为主,覆盖股市评论数据、正负情感词典、机器学习与深度学习模型脚本、情感指数计算及可视化模块,目录结构清晰,便于按模块阅读和复现。目前已有394人学习下载,资源附有报告文档和运行说明,能够帮助读者快速理解实现思路、直接运行代码并根据自身数据替换扩展,是金融文本挖掘方向较为完整的高分参考项目。

1. 股市评论情感分析毕设:不只是“跑通模型”,而是交付一条完整的分析链路

如果你正在为毕业设计或课程设计选题发愁,又恰好学过一点 Python、机器学习或深度学习,股市评论情感分析是一个很容易“讲出深度”的方向。它的魅力在于:数据是现成的中文文本,模型有现成的库可以用,评价标准可以不只看准确率,还能和真实的股票指数走势做对照,做出“情感指数与大盘走势偏离度”这类有说服力的可视化结果。但这恰恰也是容易翻车的地方——很多人把跑通一个情感分类模型当成了整个项目,结果答辩时被问到“情感指数是怎么算出来的”“负面词典和模型预测结果怎么融合”“结果图如何解释”就卡住了。这篇方案围绕一份实际完成的毕业设计展开,它不只是贴一段训练代码,而是从数据采集、清洗、标注,到 SQLite 存储、机器学习与深度学习两条建模路径,再到情感指数计算与可视化验证的完整工程。适合正在做毕设、课设或期末大作业的同学直接拆解参考,也适合想快速搭建一个文本情感分析 Demo 的开发者。

2. 总体架构设计:六类文件如何组成一条完整的数据流

这套项目拿到手里,先不要急着跑代码,先看清它分了几个层次。解压后的 Stock_MarketSentiment_Analysis-master 目录里,核心文件其实就六类:数据文件(sh000001.csv、positive.txt、negative.txt、stock_comments.zip)、机器学习脚本(model_ml.py)、深度学习脚本(model_dl.py)、情感指数计算脚本(compute_sent_idx.py)、可视化脚本(plot_sent_idx.py)以及结果文件(model_ml_scores.csv、BI_and_Market.png、BI.png)。如果只是把它们看成“一堆代码”,你很难向导师讲清楚设计思路;但如果你把它映射成一条数据流水线,整个项目的骨架就清晰了。

2.1 整条流水线的四个阶段

我的习惯是先画一张数据流向图,再开始写代码。对于这个项目,数据流是:“原始数据 → 文本预处理与标注 → 模型训练与预测 → 情感指数计算与可视化验证”。

  • 第一个阶段是数据准备。stock_comments.zip 里是采集到的某段时间的股评原始文本,positive.txt 和 negative.txt 是整理好的正向、负向情感词典,sh000001.csv 是上证指数历史行情数据。这三类数据共同构成了项目的事实基础:评论用于生成情感信号,词典用于冷启动和规则兜底,指数数据用于后续验证情感指数是否有参考价值。
  • 第二个阶段是模型构建。model_ml.py 走传统机器学习路线,核心思路是把文本转成 TF-IDF 特征,再喂给逻辑回归或朴素贝叶斯分类器;model_dl.py 走深度学习的路线,用 Word2Vec 训练词向量,再送入 LSTM 网络做分类。两条路线都要产出“每条评论的情感极性预测”,这是后续所有分析的前提。
  • 第三个阶段是情感指数计算。compute_sent_idx.py 读入模型预测结果,再结合 positive.txt 和 negative.txt 的词典得分,按时间窗口聚合成一个可以度量的情绪指数(BI)。这一步是这类项目的灵魂,单纯输出“正面/负面”没有说服力,必须把它变成一条随时间变化的曲线。
  • 第四个阶段是验证与展示。plot_sent_idx.py 生成 BI.png(情感指数随时间的变化曲线)和 BI_and_Market.png(情感指数与上证指数的对比图)。答辩时这两张图是核心材料,它们直接回答“你算出的这个情感指数到底有没有意义”。

2.2 为什么文本分类要采用“机器学习 + 深度学习”双路线

很多毕设项目只做一条路线,但这套资源把两条路线都放进来,这不是简单的堆代码,而是有明确的设计考量。机器学习路线(TF-IDF + LR)的优势在于可解释性强,特征维度直观,训练时间短,适合在答辩时现场演示调参;深度学习路线(Word2Vec + LSTM)的优势在于能够捕捉上下文语义,比如“这只股票真是要起飞了”这种带反讽意味的句子,词袋模型容易分错,LSTM 却能通过词序学到更准确的表示。两条路线对比着做,正好回应答辩时导师最喜欢问的一个问题:“你为什么不直接用 BERT?” 这时候你可以回答:BERT 效果可能更好,但它需要较大的显存和较长的训练时间,而 TF-IDF + LR 和 Word2Vec + LSTM 的组合在这个数据规模下已经能达到可用效果,并且能够在普通笔记本上复现,这正是毕设项目在工程约束条件下的合理选型。

2.3 SQLite 存储与中间结果的复用设计

项目里虽然没有单独列出建库脚本,但从数据流来看,中间结果被很好地复用,这是值得借鉴的设计。compute_sent_idx.py 会读取 model_ml.py 产出的 model_ml_scores.csv 文件,这个 CSV 就是模型预测结果的落地载体——每一行是某条评论的编号、时间戳和预测的情感极性分数。之所以不用数据库而是用 CSV,是因为这个项目的数据量级在几百兆以下,CSV 的读写速度足够快,而且方便你在 Excel 里直接打开检查结果。我一般会建议你的项目里增加一个 to_sqlite.py 的辅助脚本,把清洗后的评论、预测结果、词典得分分别存入三张表,这样后续如果要扩展成“按天聚合情绪”“按个股筛选评论”,SQL 查询会比 pandas 反复读 CSV 更高效。但从交付角度来说,现有设计已经足够完成一个高分毕设,数据库只是加分项,不是必需项。

文本预处理和特征构建是这个项目的实测难点,很多人第一次跑通模型后,准确率只有 60% 出头,问题几乎都出在预处理阶段。我在第三、四章会把两条建模路径的代码逻辑和参数设计拆开讲清楚,你拿到后可以对照自己的数据直接改。

3. 机器学习建模实战:model_ml.py 中的 TF-IDF 与逻辑回归

model_ml.py 是整个项目里最容易先跑通、也最容易拿到稳定分数的模块。它的整体思路是:读取评论数据 → 中文分词 → 去除停用词 → TF-IDF 向量化 → 逻辑回归分类 → 输出预测分数到 model_ml_scores.csv。下面先给出核心代码框架。

3.1 文本预处理与 TF-IDF 特征构建

import jieba import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 读取评论与标签 df = pd.read_csv('stock_comments.csv') # 字段:comment, label, date stopwords = set() with open('stopwords.txt', 'r', encoding='utf-8') as f: for line in f: stopwords.add(line.strip()) def cut_text(text): words = jieba.lcut(text) return ' '.join([w for w in words if w not in stopwords and len(w) > 1]) df['cut_comment'] = df['comment'].apply(cut_text) # TF-IDF 特征 vectorizer = TfidfVectorizer(max_features=5000, ngram_range=(1, 2)) X = vectorizer.fit_transform(df['cut_comment']) y = df['label'] # 1 表示正面,0 表示负面 # 划分训练集与测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

这段代码里有几个参数需要重点说明,答辩时很可能被问到。max_features=5000 表示只保留 TF-IDF 权重最高的 5000 个特征,这是为了控制特征矩阵的稀疏度和训练时间,如果语料规模不大,5000 已经足够覆盖绝大多数有效词;ngram_range=(1, 2) 表示同时使用单个词和相邻两个词的组合作为特征,这样像“不看好”这种三字短语会被拆成“不 看好”和“不看 看好”两个特征片段,比单纯用单个词多了一些语义信息。len(w) > 1 这个过滤条件是为了去掉“的”“了”“啊”等单字词,这些词在情感分析里几乎不携带极性信息,保留它们只会增大特征维度。

3.2 逻辑回归模型的训练与阈值选择

model = LogisticRegression(C=1.0, solver='liblinear', max_iter=200) model.fit(X_train, y_train) y_pred = model.predict(X_test) y_proba = model.predict_proba(X_test)[:, 1] # 属于正面的概率 print(classification_report(y_test, y_pred)) # 将预测概率输出到 CSV,供后续情感指数计算使用 df['pred_score'] = y_proba df[['date', 'comment', 'pred_score']].to_csv('model_ml_scores.csv', index=False, encoding='utf-8-sig')

为什么选择逻辑回归而不是 SVM 或随机森林?这是很多同学会犹豫的地方。逻辑回归本质上是一个线性分类器加上 sigmoid 输出,它的训练速度快,可解释性好,而且 predict_proba 直接给出样本属于“正面”的概率——这个概率值恰好是后续 compute_sent_idx.py 计算情感指数的核心输入。如果是随机森林,虽然准确率可能略高一些,但它的概率输出不如逻辑回归平滑,而且训练和调参成本高,对毕设项目的性价比反而不高。C=1.0 是正则化强度的倒数,C 越小正则化越强,如果训练数据量在几千条量级,1.0 是一个不需要过多调整的默认值。

3.3 分类结果的检视方法

把 model_ml_scores.csv 在 Excel 里打开,你会看到每一条评论对应一个 0 到 1 之间的预测得分。这个得分大于 0.5 表示模型认为它是正面评论,小于 0.5 表示负面。我一般会让你挑出几条极端错误的样本仔细看:比如一条明显是“垃圾股”的评论被预测成 0.8 分,大概率是分词阶段出了问题——“垃圾”这个词被切碎了,或者“垃圾”根本没有出现在训练集里。这时候不要急着换模型,先回看分词结果和特征权重,找出模型预测错误的真实原因再决定下一步。

4. 深度学习路线与情感指数计算:从 LSTM 到 BI 曲线

如果说机器学习路线是整个项目的“下限保底”,那么深度学习路线就是“上限冲分”的部分。model_dl.py 负责用 Word2Vec 训练词向量,再送入 LSTM 模型做序列分类;compute_sent_idx.py 则把模型的输出概率与词典规则融合,计算出可以和大盘走势对照的情感指数。

4.1 Word2Vec 词向量与 LSTM 网络的结构设计

import gensim from gensim.models import Word2Vec import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense # 用 jieba 分词后的句子列表训练 Word2Vec sentences = [text.split() for text in df['cut_comment']] w2v_model = Word2Vec(sentences, vector_size=128, window=5, min_count=2, workers=4) # 构建词表与嵌入矩阵 word_index = {word: idx + 1 for idx, word in enumerate(w2v_model.wv.index_to_key)} embedding_matrix = np.zeros((len(word_index) + 1, 128)) for word, idx in word_index.items(): embedding_matrix[idx] = w2v_model.wv[word] # LSTM 模型结构 model = Sequential() model.add(Embedding(input_dim=len(word_index) + 1, output_dim=128, weights=[embedding_matrix], trainable=False)) model.add(LSTM(units=64, dropout=0.2, recurrent_dropout=0.2)) model.add(Dense(1, activation='sigmoid')) model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

这里必须强调一个大多数初学者会踩的坑:Embedding 层的 trainable 参数。刚开始做的时候,我把 trainable 设置成了 True,想让模型在训练过程中微调词向量,结果在小样本数据上出现了严重的过拟合——训练集准确率到了 98%,测试集却只有 62%。后来改成 trainable=False,让词向量保持预训练结果不变,测试准确率反而稳定下来。原因是词向量的训练需要足够大的语料,几百条评论根本不足以反向传播修正 128 维的向量空间。

LSTM 的 units=64 是隐藏状态维度。这个值不宜过大,因为评论属于短文本,单条评论分词后通常只有 10 到 30 个词,过大的隐藏维度会带来大量冗余参数,训练时间增加但效果不升反降。dropout 和 recurrent_dropout 都是 0.2,用来防止过拟合。attention 机制在这个项目里不是必需的,虽然加上它可能提升 1 到 2 个百分点的准确率,但会增加代码长度和答辩时的解释负担。

4.2 情感指数 BI 的计算原理

compute_sent_idx.py 是这个项目区别于普通“情感分类 Demo”的关键所在。它做的事情是:把某一天内所有评论的模型预测概率取平均值,再结合词典得分进行修正,最终得到该日的情绪指数。通常的做法是:

import pandas as pd import numpy as np scores = pd.read_csv('model_ml_scores.csv', parse_dates=['date']) scores['date'] = scores['date'].dt.date # 按天聚合模型预测概率 daily_pred = scores.groupby('date')['pred_score'].mean().reset_index() daily_pred.columns = ['date', 'model_score'] # 读入正向和负向词典,计算当日评论中的词典情绪得分 positive_words = set(open('positive.txt', encoding='utf-8').read().split()) negative_words = set(open('negative.txt', encoding='utf-8').read().split()) def dict_score(text): pos_count = sum(1 for w in text.split() if w in positive_words) neg_count = sum(1 for w in text.split() if w in negative_words) if pos_count + neg_count == 0: return 0.5 return pos_count / (pos_count + neg_count) # 词典得分和模型得分加权融合,权重通常设置为 0.7 和 0.3 scores['dict_score'] = scores['comment'].apply(dict_score) scores['blended_score'] = 0.7 * scores['pred_score'] + 0.3 * scores['dict_score'] daily_blend = scores.groupby('date')['blended_score'].mean().reset_index() daily_blend.columns = ['date', 'BI'] daily_blend.to_csv('BI_daily.csv', index=False)

为什么要用 0.7 和 0.3 的加权比例,而不是直接只用模型分数?这是因为模型预测可能会有系统性偏差,比如训练样本以“涨了”这类有明确正面词汇的评论为主,那么遇到“回调”“出局”这类冷门负面表达时可能预测不准。词典分数虽然粗糙,但它像一个保守的纠偏器——对极端词汇的响应是确定性的,能够把模型的偏差拉回来一点。权重比例并非固定,我一般会在 0.6:0.4 到 0.8:0.2 之间尝试,然后看哪个比例下 BI 曲线与大盘走势的相关性更高,就用哪个。

4.3 可视化验证脚本 plot_sent_idx.py

plot_sent_idx.py 的核心产出是两张图。第一张 BI.png 是“情感指数随时间的变化曲线”,横轴是日期,纵轴是 BI 值,范围在 0 到 1 之间,大于 0.5 表示市场情绪偏乐观,小于 0.5 表示偏悲观。第二张 BI_and_Market.png 是关键图,它把 BI 曲线和上证指数曲线放在同一个坐标系里,用双 Y 轴展示——左 Y 轴是 BI 值,右 Y 轴是上证指数收盘价,两条曲线叠在一起,直观地展示“情绪领先于走势”或“情绪与走势同步”的关系。实际项目中,用 matplotlib 的 twinx() 实现双 Y 轴是最省事的做法。绘制时建议对 BI 做 3 日移动平均平滑处理,去掉单日噪声,曲线会更耐看,也更容易在答辩时讲出“趋势”。

5. 避坑指南:股市评论情感分析最容易翻车的五个地方

每次带同学做这类项目,我发现翻车点都惊人地一致。下面这五条是从真实项目里总结出来的“现象 → 原因 → 解决”,对照着检查,能省下你两三天调试时间。

5.1 数据穿越:评论日期和指数日期对不上

现象:BI_and_Market.png 画出来之后,发现 BI 曲线整体向右偏移,和大盘走势的对应关系看起来很混乱。

原因:评论数据的日期戳往往是在采集时生成的,实际评论发布时间可能在几天前。直接按采集日期聚合,等于引入了时间错位。这是个非常隐蔽的坑。

解决:在预处理阶段,优先从评论内容的正则匹配中提取时间信息(比如“03-15 10:22”这类前缀),提取不到再回退到文件名或采集日期。计算日频 BI 之前,先做一次日期对齐检查,确认每一天都有足够数量的评论样本。

5.2 算准率时使用不当的标签口径

现象:模型训练集在 jieba 分词后去掉停用词,准确率从 70% 掉到 58%,找不出原因。

原因:很可能是因为测试集存在大量此前未出现过的专有名词、股票代码简称,比如“茅五泸”“宁王”,这些词不在训练集里,分词后变成单字碎片。

解决:不要急着删停用词。先保留一份“不分词”的原始文本做对照实验,再给 jieba 增加自定义词典,把“茅五泸”“宁王”这类股市专用词直接加入 user_dict,让分词器将它们作为一个整体切分。准确率通常会回升 5 到 10 个百分点。

5.3 深度学习模型的随机性问题

现象:同一个 LSTM 模型,在同样的训练集上跑两次,测试准确率相差 3% 左右,答辩时无法复现。

原因:神经网络初始化、Dropout 随机丢弃、训练时的批次洗牌都会引入随机性。模型训练崩溃或准确率跳变时,看起来完全随机的故障让人无从下手。

解决:在 model_dl.py 的训练入口设置固定随机种子:np.random.seed(42),random.seed(42),tf.random.set_seed(42)。只要种子固定、数据顺序不改变,多次运行结果就完全一致。毕业论文里一定要写明“在固定随机种子条件下复现”,这是答辩时的加分细节。

5.4 情感词典读取的编码问题

现象:compute_sent_idx.py 运行时报 UnicodeDecodeError,或者词典里的词匹配不上文本。

原因:positive.txt 和 negative.txt 虽然内容看起来是中文,但编码可能是 GBK 或 UTF-8 带 BOM,而你在 open() 时没指定编码参数,Python 使用了系统默认编码去读。

解决:统一强制指定编码打开所有文本文件。最稳妥的做法是先把两个词典文件全部另存为 UTF-8 without BOM,然后在代码中写上 encoding='utf-8'。这一点虽然是最简单的修改,但也是最常被人忽视的项目事故源。

5.5 只报准确率不报结果分布

现象:答辩时导师问“你的模型对正面评论的召回率是多少?负面评论呢?”你翻开测试集结果后才发现,99% 的预测结果都是正面。

原因:数据集中正面评论远多于负面评论(现实情况也确实如此,下跌时散户更倾向骂人),模型学到的是“多数类优势”,准确率高但实质上几乎无区分能力。

解决:训练完成后必须看 classification_report 输出的精确率、召回率、F1 分数,确认负样本也有足够的召回率。如果严重失衡,处理办法是在训练集抽样时做下采样(undersampling)或对上采样,目标是把正负样本比例控制在不高于 3:1。这一点直接影响项目评价等级。

6. 验收与答辩技巧:导师最关注的三个维度和一条展示主线

项目到了收尾阶段,真正决定成绩的不是代码量,而是你能不能在一张图和一个演示流程里把整条逻辑链讲清楚。我建议你把答辩准备按下面三个维度来组织。

验收维度考察点你的呈现材料
数据可信度数据来源、预处理、清洗流程stock_comments.zip 的样本截图、预处理前后的文本对比
模型有效度准确率、召回率、与基线对比classification_report 输出、ML 与 DL 两种模型的对比表格
结果可解释性情感指数是否与行情走势吻合BI_and_Market.png 双 Y 轴图表、关键时间节点的标注

答辩时我按这套顺序做过多次演练,推荐你也这样做:先展示项目目录和代码结构,让导师建立整体框架;再打开 BI_and_Market.png,指着图上某个局部放大窗口说:“你看在指数快速下跌前的三天,BI 曲线已经连续走低,说明市场情绪提前反映了悲观预期。” 这种演示方式远比念代码更打动人。

老一辈带项目时有个习惯:每一版模型训练完成之后,强制走一遍“数据量 → 特征 → 模型 → 结果 → 可视化 → 结论”六步检查,每一步都要能说出来“这一版和上一版相比,哪个参数变了、为什么变、效果变化了多少”。从那以后我每次训练模型都坚持记录实验日志,哪怕只是简单的 Excel 表,答辩时也能清晰回答“你怎么知道你的模型有进步”这类问题。这套方法如果你也能坚持两周,相信你的项目交付质量会明显提升。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进