简介:面向计算机相关专业毕业生及需要项目实战的学习者,股市评论情感分析毕业设计包含完整代码与项目报告。项目基于Python机器学习与深度学习技术,对股市评论进行情感分类,覆盖数据读取、情感词典匹配、传统机器学习与深度学习模型构建、情感指数计算与可视化等完整实验链路,贴合金融商贸场景,既可用于毕设答辩,也可作为课程设计或期末大作业。资源共12个文件,以4个Python脚本为核心,对应两套建模方案,并配有CSV股价评论数据、正负情感词典、结果图表、README说明文档及原始评论压缩包,整体约24.72MB,目录结构清晰,便于按模块复现对比。已有394人学习下载,项目获导师高分认可(评审98分),对快速搭建同类情感分析系统、撰写实验报告具有较高参考价值。
1. 为什么选这个题目:从一条评论到一份可验收的毕设方案
做股市评论情感分析,最尴尬的翻车不是模型不收敛,而是答辩时被问一句"你分析出情绪了,然后呢?"。这个题目的真正价值,是把杂乱无章的股票论坛评论清洗、分词、建模,最终汇总成一条情感指数曲线,并拿它去对照大盘指数的实际走势,回答"市场情绪与行情走势是否同步"这个可验证的问题。整份方案以Python为核心,同时用机器学习和深度学习两条路线建模,再配合报告、图表和数据文件,形成一套从数据到结论都能自查的完整毕业设计。适合正在做毕设、课设的计算机相关专业学生,也适合想用NLP做金融商贸文本分析的实战学习者。
2. 需求分析与技术选型:机器学习与深度学习双路线的取舍
2.1 需求先立住:这份毕设到底要解决什么问题
先别急着写代码,把需求拆成四个能验收的问题:能不能准确判断单条评论的乐观与悲观倾向;能不能把成千上万条评论压缩成一条按日聚合的情感曲线;这条曲线和大盘走势是不是真的存在关联;结论能不能用图和数据表表达清楚。这四个问题分别对应数据预处理、文本分类、指数计算、结果可视化四个功能模块。
非功能需求同样重要。一是可复现,别人拿同一份数据跑同一份代码,要不报错地得到相同结论;二是运行成本可控,深度学习部分不能在普通笔记本上跑几个小时还不出结果;三是结果可解释,不能只有准确率一个数字,还要有样本、图表、相关性系数来支撑。这三点直接决定了答辩时评审愿不愿意给你高分。
把边界划清楚也很关键。这套方案做的是离线分析而不是实时盯盘,不需要流式处理,也不承诺预测明天的涨跌,它回答的是"过去一段时间里,评论情绪与行情走势的关联关系"。这样定义需求,后续的系统设计、模块划分、验收标准就都有了依据,答辩时也不容易被"你的系统能不能实时预警"这类问题带偏。
2.2 技术选型:为什么同时跑机器学习和深度学习两条路线
很多同学只选一条路线,跑通就完事,但评审看的是对比和工程权衡。这份方案把技术栈拆成两条线:model_ml.py走传统机器学习路线,用TF-IDF把文本转成向量,交给逻辑回归或支持向量机分类;model_dl.py走深度学习路线,用词嵌入加序列模型捕捉上下文语义。两条路线的结果都统一落到情感得分文件里,后续指数计算完全复用同一套聚合逻辑。
选传统机器学习当基线不是敷衍,而是因为它训练快、参数可解释,适合小样本场景。逻辑回归训练时间以秒计,方便反复调参,而且它的特征权重可以直观看到哪些词在推动"乐观"或"悲观"判断。深度学习路线选序列模型,能学到"虽然涨了但风险高"这类转折结构,对长句更友好,但需要更长的训练时间和更大的语料,在只有几千条标注评论时容易过拟合。
两条路线共存的直接价值是产出对比表。报告里放一张效果对比,写出准确率、F1、训练耗时、单条推理耗时,再说明在给定数据量下最终选择哪条路线作为主模型。这个对比本身就是毕设的核心工作量,也是答辩时最容易被提问的地方,提前想清楚"为什么这么选",比临时翻书回答要稳得多。
2.3 数据准备:日线行情、正负语料与原始评论怎么搭配
数据文件是整套方案的底座,三部分数据配合得当,才能支撑完整分析闭环。data/sh000001.csv保存大盘指数的日线行情,包含日期、收盘价、涨跌幅等字段,用于计算市场走势,也用于和情感指数做关联验证。data/positive.txt和data/negative.txt是标注语料,每一行一条评论,分别代表乐观和悲观样本,负责训练和评估分类模型。stock_comments.zip解压后是带日期的原始评论数据,用于最后生成每日情感指数。
我的习惯是先做字段清单再动手写代码。下面这张表列清楚三部分数据各自扮演的角色和关键字段,写代码时按这个约定处理,就能避免"日期格式不一样""文本里有特殊字符"这类低级问题:
| 数据文件 | 角色 | 关键字段 | 用途 |
|---|---|---|---|
| sh000001.csv | 大盘日线行情 | date, close, pct_change | 计算走势,与情感指数做相关性分析 |
| positive.txt | 正向语料 | 每行一条评论 | 训练分类器 |
| negative.txt | 负向语料 | 每行一条评论 | 训练分类器 |
| stock_comments.zip | 原始评论 | user, content, date | 生产环境下的真实评论,用于生成情感指数 |
预处理时踩过的坑很典型。原始评论里有HTML标签、@用户、转发标记、URL、表情符号,还有大量重复转发。通常的做法是先用正则统一去掉这些噪声,再做分词和去停用词;中文分词在特征工程时统一处理,避免机器学习和深度学习两条管线各搞一套,导致结果对不上。清洗后每条评论保留三部分:原始文本、清洗文本、发布日期,足够后续所有模块使用。
3. 总体架构设计:四个模块与一份能自查的文件结构
3.1 模块划分:数据层、模型层、指数层、可视化层
整个系统按数据流方向拆成四个模块,前一个模块的输出是后一个模块的输入,层级清楚,出问题时能快速定位。数据层负责读取行情、语料和原始评论,完成清洗、分词、标签转换,输出结构化的训练集和测试集;模型层包含model_ml.py和model_dl.py,分别训练两类分类器,输出每个句子的情感标签和概率;指数层由compute_sent_idx.py实现,把模型产出的逐条结果按日期聚合成每日情感指数BI;可视化层由plot_sent_idx.py和img目录下的图表承接,把情感指数与大盘走势画在同一张图上。
这个结构的核心设计原则是模块间只通过文件或标准接口传递数据,不强耦合。模型训练完把分数表写入model_ml_scores.csv,指数计算直接读这个文件;图表绘制读指数文件和大盘行情。这样每一步都能单独运行、单独验证,调试时不用从头跑一遍,交代码时导师也能按步骤复现,黑匣子环节就被拆成了几个可检查的分段。
3.2 文件结构:每个文件都对应一个可讲的故事
拿到压缩包的同学第一件事是看README.md,所以README里写了运行顺序:先装依赖,再跑数据预处理,然后训练两个模型,接着计算情感指数,最后出图。这套文件组织方式让"代码、报告、图表、数据"四类交付物各归其位,每一项都能在报告里找到对应章节,而不会出现代码和报告各说各话的情况。
| 文件/目录 | 职责 | 交付定位 |
|---|---|---|
| README.md | 环境依赖、运行顺序、文件说明 | 项目说明 |
| model_ml.py | 机器学习模型训练与评估 | 核心代码 |
| model_dl.py | 深度学习模型训练与评估 | 核心代码 |
| compute_sent_idx.py | 情感指数BI计算 | 核心代码 |
| plot_sent_idx.py | 双轴可视化出图 | 核心代码 |
| data/sh000001.csv | 大盘日线行情 | 输入数据 |
| data/positive.txt, negative.txt | 正负情感语料 | 输入数据 |
| stock_comments.zip | 原始评论数据 | 输入数据 |
| model_ml_scores.csv | 模型对评论的情感打分结果 | 中间结果 |
| img/BI.png, BI_and_Market.png | 情感指数曲线、情感指数与大盘对比图 | 报告配图 |
一个容易被忽视的设计是中间结果落盘。model_ml_scores.csv看起来只是一个中间产物,但它让整个流程变成可断点续跑:模型重训一次要几分钟,读取分数表重新算指数只要几秒。在答辩现场演示时,这个文件能让你从容地只展示关键步骤,不用干等训练过程,这也是工程化意识和单纯跑通代码之间的重要差别。
3.3 数据表设计:评论情感表和每日指数表
整个分析链路里有两张核心表,一张管明细,一张管聚合。评论情感表是模型输出的落点,每条评论一行,记录清洗后的文本、发布日期、预测标签和预测概率;每日指数表是聚合结果,按日期一行,记录当日评论数量、积极占比、净情绪值、平滑后的BI指数,以及对应的大盘收盘价和涨跌幅。
评论情感表的字段这样设计:comment_id作为主键,publish_date建立索引,sentiment_label取值0或1,probability保留小数点后四位。每日指数表以trade_date作为主键,bi_index是核心字段,由积极占比减去消极占比得到,取值范围在-1到1之间,大于0说明当日评论整体偏乐观,小于0说明偏悲观。两张表通过日期字段关联,执行一次按日期分组聚合就能得到指数表。
这样设计的好处是,无论后面要画图、做相关性分析还是导出结果,都只需要读一张聚合表,不需要反复扫描原始评论。排查问题时也能从聚合表一路下钻到具体某一天的某一条评论,比如发现某天BI异常偏低,可以直接定位到当天哪些评论贡献了极端分数,这种可追溯性在答辩展示时非常加分。
4. 核心实现:从TF-IDF到情感指数BI的三段关键代码
4.1 model_ml.py:机器学习基线的完整套路
机器学习路线的实现重心在特征工程和类别不平衡处理。文本先做分词和清洗,然后走TF-IDF向量化,最后用逻辑回归分类。下面这截代码是典型的实现骨架,直接对应model_ml.py中训练与评估的核心部分:
import jieba 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 def load_corpus(pos_file, neg_file): pos = [line.strip() for line in open(pos_file, encoding="utf-8") if line.strip()] neg = [line.strip() for line in open(neg_file, encoding="utf-8") if line.strip()] texts = pos + neg labels = [1] * len(pos) + [0] * len(neg) return texts, labels texts, labels = load_corpus("data/positive.txt", "data/negative.txt") # 分词并重新拼接,TF-IDF的输入是空格分隔的词序列 cutted = [" ".join(jieba.lcut(t)) for t in texts] vectorizer = TfidfVectorizer(max_features=5000, ngram_range=(1, 2)) X = vectorizer.fit_transform(cutted) X_train, X_test, y_train, y_test = train_test_split( X, labels, test_size=0.2, random_state=42, stratify=labels) clf = LogisticRegression(C=1.0, max_iter=1000, class_weight="balanced") clf.fit(X_train, y_train) print(classification_report(y_test, clf.predict(X_test)))这里的参数可以直接复现,也值得在报告里解释两句。max_features=5000表示只保留词频最高的5000个特征,避免稀疏矩阵过大;ngram_range=(1,2)同时考虑单个词和相邻两个词,能部分捕捉"不看好"这类连续表达;class_weight="balanced"是针对正负语料数量不一致的兜底方案,让模型不会一边倒地学多数类。逻辑回归收敛快,C=1.0是默认量级的正则强度,在小语料上表现稳定。
如果你想让报告更有层次,可以再跑一组支持向量机做对照,或者把max_features调整成1000和10000各跑一遍记录指标。我一般会保留这组实验记录,因为答辩时"为什么选5000而不选10000"这个问题的答案,比"我用了TF-IDF"更能体现你理解模型容量和计算成本的权衡。
4.2 model_dl.py:深度学习模型的实现与训练技巧
深度学习路线我用词嵌入加双向序列模型,对句子先做序列填充,再经过嵌入层和双向LSTM,最后接全连接层输出概率。下面这段代码对应model_dl.py的核心结构,框架用Keras风格的接口实现,模型规模控制在普通笔记本能跑完的范围内:
from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, Bidirectional, LSTM, Dense, Dropout MAX_LEN = 64 VOCAB_SIZE = 8000 EMBED_DIM = 128 tokenizer = Tokenizer(num_words=VOCAB_SIZE, filters="") tokenizer.fit_on_texts(cutted) X_seq = pad_sequences(tokenizer.texts_to_sequences(cutted), maxlen=MAX_LEN) model = Sequential([ Embedding(VOCAB_SIZE, EMBED_DIM, input_length=MAX_LEN), Bidirectional(LSTM(64, dropout=0.3, return_sequences=False)), Dense(16, activation="relu"), Dropout(0.3), Dense(1, activation="sigmoid"), ]) model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"]) model.fit(X_seq, labels, validation_split=0.2, epochs=10, batch_size=64, verbose=1)关键设计有三处。第一,Embedding在模型内部完成自适应训练,相当于让模型自己学每个词在这个金融语料里的语义,比直接套用公开词向量更贴数据;第二,双向LSTM同时看句子的前向和后向信息,"虽然涨了但风险高"这类转折句,两个方向都能被编码;第三,Dropout放在LSTM输出和分类层之前,防止小样本下过拟合,这是深度学习路线最需要警惕的地方。
深度模型在小语料上的表现很可能不如机器学习,这是正常现象,不要靠调高epoch硬刷训练集分数。报告里如实写对比结果更有说服力:深度学习胜在语义理解上限高,传统方法胜在稳定可复现。如果效果差距不明显,就增加早停回调观察验证集loss,记录最优epoch,证明你做了收敛性控制,而不是把模型扔在那儿跑完不管。
4.3 compute_sent_idx.py:把逐条情感汇总成每日指数
模型输出的是单条评论的标签和概率,但毕设的核心结论建立在"每日情绪"上,这一步必须做聚合。常规做法是按日期分组,计算当日积极评论占比,再减去消极占比,得到一个净情绪值,最后做一次移动平均消除周末和突发噪声的抖动,生成BI指数。
import pandas as pd scores = pd.read_csv("model_ml_scores.csv", parse_dates=["publish_date"]) scores["sentiment_value"] = scores["sentiment_label"] * 2 - 1 # 1 -> 1, 0 -> -1 daily = scores.groupby("publish_date").agg( comment_count=("sentiment_value", "count"), pos_ratio=("sentiment_label", "mean") ) # BI = 积极占比 - 消极占比,等价于当日 sentiment_value 的均值 daily["bi_index"] = daily["pos_ratio"] * 2 - 1 daily["bi_ma5"] = daily["bi_index"].rolling(5, min_periods=1).mean() market = pd.read_csv("data/sh000001.csv", parse_dates=["date"]) merged = daily.join(market.set_index("date"), how="inner") merged["corr"] = merged["bi_index"].corr(merged["pct_change"]) merged.to_csv("model_sent_daily.csv")这里的sentiment_value把二分类标签映射成1和-1,让"积极占比减消极占比"的计算变成一个简单的均值问题,代码更简洁。BI的取值天然落在-1到1之间,0是中性分界,大于0.2可以视为当日情绪明显偏乐观,小于-0.2偏悲观。计算corr这一行是整个项目的点睛之笔,它把情感指数和大盘涨跌幅的Pearson相关性直接算出来,答辩时这就是"你的分析有没有用"的数字证据。
BI和涨跌幅的相关系数不一定要高,关键是要能解释。哪怕只有0.2出头,也可以说"评论情绪与短期行情存在弱正相关,情绪滞后于行情",因为当日评论本身就包含收盘后的复盘情绪,这种解释在金融文本分析里完全立得住。怕的是算出来一个数却说不清含义,所以我在输出文件里保留字段说明,方便报告直接引用。
4.4 plot_sent_idx.py:双轴对比图让结论一眼可见
情感指数和指数走势是两个量纲完全不同的序列,直接画在同一张折线图里没有可比性,所以可视化模块采用双轴设计。左轴画BI指数,右轴画大盘收盘价,共享同一时间轴,一眼就能看出两段曲线的同步与背离。
import matplotlib.pyplot as plt import pandas as pd plt.rcParams["font.sans-serif"] = ["SimHei"] # 解决中文乱码 df = pd.read_csv("model_sent_daily.csv", parse_dates=["publish_date"]) fig, ax1 = plt.subplots(figsize=(12, 5)) ax1.plot(df["publish_date"], df["bi_ma5"], color="#d62728", label="BI情感指数(5日平滑)") ax1.set_ylabel("BI情感指数") ax2 = ax1.twinx() ax2.plot(df["publish_date"], df["close"], color="#1f77b4", label="大盘收盘价") ax2.set_ylabel("大盘收盘价") ax1.set_xlabel("日期") fig.legend(loc="upper left", frameon=False) plt.savefig("img/BI_and_Market.png", dpi=150, bbox_inches="tight")这里有个容易忽略的细节:中文字体必须显式指定,否则出图全是方框,报告里没法用。通常的做法是在绘图脚本开头设置rcParams,而不是在每一处绘图函数里重复处理。dpi设到150是为了保证放进论文里放大仍然清晰,bbox_inches="tight"避免坐标轴标签被裁掉。
出图之后,报告里配一段两百字左右的解读,描述某几个时间段BI与行情同向、某几个时间段背离,并给出原因推测。这比贴一堆准确率数字更能让评审快速理解整个项目的价值,也是img目录里BI.png与BI_and_Market.png两张图存在的意义。
5. 避坑指南:情感分析毕设最容易翻车的五个点
5.1 只做分类不做关联,答辩被问"然后呢"
现象:模型准确率刷到90%以上,但评审追问"你的分析和股票市场有什么关系"时,半天答不上来。这是这个题目最常见的翻车姿势。
原因:把情感分析做成了纯粹的文本分类任务,没有把分析结论接回到金融场景。分类器只是工具,项目的落点应该是"情绪与行情的关系",少了这一步,整个项目就塌成词袋分类演示。
解决:在方案里补上compute_sent_idx.py这一步,把逐条预测结果聚合为每日情感指数BI,再与sh000001.csv的涨跌幅做相关性分析,最后用双轴图呈现。答辩话术可以落在这句话上:"我不只判断单条评论的情绪,还构建了情绪指数验证它与行情的关系。"问题边界一完整,评审自然跟得上你的思路。
5.2 正负样本不均衡,模型学会了"全猜消极"
现象:训练集里消极评论是积极评论的三倍,逻辑回归输出了很高的整体准确率,但一看混淆矩阵,积极类几乎全错。整体指标被多数类带偏,这是小语料场景的典型陷阱。
原因:默认的准确率指标掩盖了类别不均衡问题,模型只要全猜多数类就能拿高分。尤其股市评论本身偏消极的多,正负比失衡几乎是必然的。
解决:训练时在模型里加class_weight="balanced",评估时以F1分数为主要指标而不是只看accuracy;如果两类数量差得太多,考虑对多数类做下采样,或者用SMOTE做轻度过采样。报告里放混淆矩阵而不是只放一行准确率,让评审看到每个类别各自的precision和recall,这一步在答辩中是实打实的加分细节。
5.3 中文语料编码与分词错误,悄悄带歪整个结果
现象:预处理后文本出现乱码,或者jieba分词把股票名称、涨跌术语拆成碎片,模型指标看起来正常,但实际预测结果完全没有逻辑。中文文本分析里的编码问题,比模型调参更容易让人心态崩掉。
原因:不同数据文件编码不统一,gbk和utf-8混用;分词没有加载金融领域自定义词典,导致"涨停""跌停""新能源"这类词被切成无意义的单字,特征表达直接降级。
解决:读文件时统一用encoding="utf-8"并加入异常兜底;准备一份自定义词典,包含股票名和常见涨跌术语,在分词前加载。清洗流程按照"去URL、去@、去HTML、去特殊符号、分词、去停用词"的固定顺序执行,每一步都统计剩余条数,确认没有把数据洗丢。
5.4 深度学习在小样本上过拟合,效果反而不如机器学习
现象:训练集准确率接近100%,验证集只有70%出头,深度学习模型的整体效果被机器学习基线反超。这是所有毕设做深度学习最不愿意面对却最常遇到的情况。
原因:评论语料只有几千条,LSTM的参数量远大于样本量,模型把训练集的噪声都记住了,泛化能力自然下降。深度学习不是万能药,数据量不够时它就是一把双刃剑。
解决:设定验证集和早停机制,监控验证集loss,当连续多个epoch不再下降就停止训练;同时把Dropout比例提到0.3以上,还可以用预训练词向量初始化Embedding,减少训练压力。如果仍然过拟合,就在报告里诚实说明"当前数据量下机器学习路线更稳定"并把它作为选型结论,这本身就是合理的工程判断,比强行美化深度学习结果更让评审信服。
5.5 日期对不齐,情绪与行情数据错位
现象:双轴图上BI曲线的峰值和行情转折点总是错开,相关性计算结果也不稳定,重跑一遍结果对不上。这是数据对齐问题,不是模型问题,但最容易让人误判为模型不行。
原因:评论发布日期和交易日不是一一对应,周末和节假日的评论没有行情可对应;pandas join时两个文件的日期字段格式不一致,默认合并还会产生大量空行和NaN。
解决:在compute_sent_idx.py里强制对日期做标准化,统一为YYYY-MM-DD格式,用how="inner"只保留两边都有的交易日。遇到周末评论的归属问题,简单的做法是顺延到下一个交易日,并在报告中注明这个处理规则。要知道,数据加工规则本身就是毕设的工作量,写清楚反而是加分项。
6. 验收与答辩:把98分的细节讲成可复现的工程
6.1 自查验收清单
交付前我做三件事。第一,环境复现:在干净的Python环境按README安装依赖,从头到尾跑一遍,确认每个脚本都能独立运行、无报错、中间产物完整生成。第二,数据完整性:确认img目录下有BI.png和BI_and_Market.png两张成品图,data目录下三份输入数据都在,model_ml_scores.csv等中间结果存在且字段齐全。第三,实验记录:报告里要有训练集与测试集划分方式、运行时间、模型参数、指标对比表和结果解读,不能只放代码和截图。
6.2 答辩提问的三个方向
评审喜欢从"为什么"入手。第一个方向是模型选型,准备好回答"为什么同时用机器学习和深度学习""最终建议用哪个模型,依据是什么";第二个方向是数据与特征,准备好回答"正负语料怎么标注""TF-IDF的max_features为什么取这个值""如果没有标注数据怎么办";第三个方向是结论可靠性,准备好回答"BI指数与行情相关系数不高怎么解释""情感指数能不能用于预测涨跌"。围绕这三个方向把代码和报告过一遍,心里就有底了。
6.3 我养成的验收习惯
那之后我每次交付毕设或项目方案,都会强制自己走一遍"干净环境重跑"的流程,绝不在自己电脑上跑通就交付。这个项目里最有价值的一条经验是:把中间结果落盘、把每一步做成独立脚本,出问题时能精准定位到是数据处理、模型训练还是聚合环节,而不是对着一段报错堆栈无从下手。希望这个方案能帮到你,拿到压缩包先按README跑通,再动手改模型参数和可视化,把它变成你自己能讲清楚的作品。
本文还有配套的精品资源,点击获取