
简介这份资源面向计算机相关专业正在做毕业设计的学生以及需要项目实战练习的学习者提供一套基于Python机器学习与深度学习实现股市评论情感分析的完整方案可用于毕业设计、课程设计或期末大作业。压缩包共12个文件约24.72MB包含4个py脚本、2个txt情感词典、2个csv行情与评分数据、2个png可视化图表以及说明文档与数据压缩包覆盖数据读取、模型训练、情感指数计算与结果绘图等环节。项目围绕股票评论的正负面情感判别展开通过机器学习与深度学习模型对比输出情感指数并与大盘走势进行关联分析配有可视化图表辅助理解。已有394人学习适合希望掌握文本情感分析全流程、积累金融文本挖掘经验的学习者参考可据此快速搭建实验环境并理解模型评估思路。1. 股市评论情感分析毕设包从一条股吧评论到一张情绪指数曲线很多人做金融文本挖掘时第一反应是直接上大模型跑推理结果发现本地显存不够、接口调用又贵最后连个能写进论文的完整链路都拼不出来。这份Stock_MarketSentiment_Analysis-master走的是另一条路用 Python 把「股吧评论 → 情感标签 → 每日情绪指数 → 与上证指数对比图」串成一条可复现的流水线机器学习model_ml.py和深度学习model_dl.py两套模型并存还配了sh000001.csv真实行情数据做验证。它解决的不是「情感分析准不准」这种玄学问题而是「一个计算机专业学生怎么在两周内交出一份有数据、有模型、有图表、有报告的毕业设计」。适合正在做毕设、课程设计或想练手完整 NLP 项目的人前提是你愿意把环境配好、把数据路径对齐。2. 拆包先看结构每个文件在流水线里干什么拿到压缩包别急着pip install先把目录结构过一遍。这个项目的文件命名很直白基本看名字就知道它在链路里的位置但有几个文件容易搞混比如compute_sent_idx.py和plot_sent_idx.py一个是算指数、一个是画图顺序不能反。2.1 核心文件清单与职责划分文件/目录类型在流水线中的职责compute_sent_idx.py脚本读取模型打分结果按日期聚合出每日情绪指数model_ml.py脚本传统机器学习模型训练与预测TF-IDF 分类器model_dl.py脚本深度学习模型训练与预测词向量 神经网络plot_sent_idx.py脚本把情绪指数与上证指数画在同一张图上data/sh000001.csv数据上证指数日线行情用于对比验证data/positive.txt数据正向情感词典/标注语料data/negative.txt数据负向情感词典/标注语料data/stock_comments.zip数据原始股市评论压缩包需解压后使用model_ml_scores.csv输出机器学习模型对每条评论的打分结果img/BI_and_Market.png输出情绪指数与大盘走势对比图img/BI.png输出情绪指数单独走势图README.md文档环境依赖与运行说明这张表建议你复制到自己的笔记里因为后面调参和排错时你会反复确认「我现在改的是哪一环」。常见做法是先把data/stock_comments.zip解压到data/目录下确认评论文件是.txt还是.csv再决定model_ml.py里的读取路径怎么写。2.2 环境依赖与版本选择理由项目基于 Python机器学习部分通常依赖scikit-learn深度学习部分常见做法是用tensorflow或pytorch二选一。从文件命名model_dl.py看不出具体框架但毕设场景下tensorflow的 Keras 接口对新手更友好pytorch则在调试时更直观。我一般会先建虚拟环境避免把系统 Python 搞乱# 创建虚拟环境Python 版本建议 3.8~3.10 python -m venv venv # 激活环境Windows venv\Scripts\activate # 激活环境macOS/Linux source venv/bin/activate # 安装基础依赖版本按 README 或实际报错调整 pip install numpy pandas scikit-learn matplotlib jieba这里jieba是中文分词的关键股市评论里「涨停」「割肉」「补仓」这类词如果被切碎情感极性就丢了。matplotlib负责画图注意中文字体要单独设置否则plot_sent_idx.py出来的图全是方框。参数上scikit-learn的版本差异会影响TfidfVectorizer的默认行为如果报AttributeError先查版本而不是改代码逻辑。提示虚拟环境激活后命令行前面会出现(venv)如果没出现说明激活失败后面所有pip install都会装到系统环境里这是新手最常见的翻车点之一。3. 机器学习路线跑通TF-IDF 加分类器的完整链路传统机器学习路线是这个项目里最容易先跑出结果的部分因为它不依赖 GPU训练速度快适合用来验证数据读取和标签映射是否正确。model_ml.py的核心思路是把中文评论分词后转成 TF-IDF 向量再喂给分类器最后输出每条评论的情感得分。3.1 中文分词与 TF-IDF 向量化股市评论和普通影评不一样里面夹杂大量数字、代码和口语缩写比如「600519 今天量能不错」「明天大概率低开」。直接按字符切会丢失语义按词切又需要自定义词典。常见做法是在jieba加载时补一个金融词典import jieba import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer # 加载自定义词典把股市术语加进去避免被切碎 jieba.load_userdict(data/finance_dict.txt) # 读取评论数据假设解压后是 csv 格式 comments pd.read_csv(data/stock_comments.csv) print(comments.columns) # 先确认列名常见是 content/label # 分词函数去掉空白和单字 def cut_text(text): words jieba.lcut(str(text)) return .join([w for w in words if len(w) 1]) comments[cut] comments[content].apply(cut_text) # TF-IDF 向量化max_features 控制维度ngram_range 捕捉短语 vectorizer TfidfVectorizer(max_features5000, ngram_range(1, 2)) X vectorizer.fit_transform(comments[cut]) y comments[label]max_features5000是权衡后的选择太小会丢特征太大容易过拟合且训练变慢。ngram_range(1, 2)让「涨停」和「涨停 打开」都能被捕捉到对股市评论这种短文本很有用。如果你的数据里标签是「正面/负面」中文需要先映射成 0/1否则分类器会报错。3.2 分类器训练与模型持久化向量化之后就是选分类器。毕设场景下MultinomialNB和LinearSVC都是常见选择前者快、可解释性强后者在文本分类上通常更准。我一般会两个都跑一遍把结果写进报告里做对比from sklearn.model_selection import train_test_split from sklearn.naive_bayes import MultinomialNB from sklearn.svm import LinearSVC from sklearn.metrics import classification_report import joblib # 划分训练集和测试集random_state 固定保证可复现 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) # 朴素贝叶斯 nb MultinomialNB() nb.fit(X_train, y_train) nb_pred nb.predict(X_test) print(NB 报告) print(classification_report(y_test, nb_pred)) # 线性 SVM svc LinearSVC() svc.fit(X_train, y_train) svc_pred svc.predict(X_test) print(SVC 报告) print(classification_report(y_test, svc_pred)) # 保存模型和向量器后面 compute_sent_idx.py 要用 joblib.dump(svc, model_ml.pkl) joblib.dump(vectorizer, tfidf.pkl)random_state42不是玄学是为了让你每次跑出来的划分一致方便对比不同分类器的效果。classification_report里的f1-score比准确率更能反映类别不平衡时的真实表现股市评论里负面样本往往偏少只看 accuracy 会被误导。保存模型时用joblib比pickle对 numpy 数组更友好加载速度也快一些。3.3 批量打分与结果落盘训练完模型下一步是对全量评论打分输出model_ml_scores.csv。这一步的坑在于训练时用了fit_transform预测时必须用同一个向量器的transform不能重新fit否则特征空间对不上。# 加载保存的模型和向量器 svc joblib.load(model_ml.pkl) vectorizer joblib.load(tfidf.pkl) # 对全量评论分词并向量化注意用 transform 不是 fit_transform comments[cut] comments[content].apply(cut_text) X_all vectorizer.transform(comments[cut]) # 预测并输出概率或决策值 comments[pred] svc.predict(X_all) comments[score] svc.decision_function(X_all) # 落盘列名要和 compute_sent_idx.py 里的读取逻辑对齐 comments[[date, content, pred, score]].to_csv( model_ml_scores.csv, indexFalse, encodingutf-8-sig )encodingutf-8-sig是为了 Excel 打开不乱码很多毕设报告需要截图展示结果乱码会直接影响观感。decision_function返回的是距离超平面的距离可以当作情感强度后面算情绪指数时比直接用 0/1 标签更有区分度。如果你的数据没有date列需要先从评论时间戳里提取日期否则compute_sent_idx.py聚合时会报 KeyError。4. 深度学习路线补强词向量加神经网络的训练细节机器学习路线跑通后深度学习路线是加分项。model_dl.py通常会用词向量把文本转成稠密矩阵再送进 CNN 或 LSTM。相比 TF-IDF深度学习能捕捉词序信息对「先涨后跌」和「先跌后涨」这种顺序敏感的表达更友好。4.1 词向量训练与序列填充深度学习的第一步是把词映射成向量。常见做法有两种用gensim自己训练 Word2Vec或者用 Keras 的Embedding层让模型自己学。毕设场景下自己训练词向量能让报告多一个「词向量可视化」的章节但要注意语料规模评论太少时词向量质量会很差。import numpy as np from tensorflow.keras.preprocessing.text import Tokenizer from tensorflow.keras.preprocessing.sequence import pad_sequences # 用全量评论训练 tokenizernum_words 控制词表大小 tokenizer Tokenizer(num_words8000, oov_tokenOOV) tokenizer.fit_on_texts(comments[cut]) # 转成序列 sequences tokenizer.texts_to_sequences(comments[cut]) # 填充到统一长度maxlen 取评论长度的 95 分位比较合理 maxlen 100 X_dl pad_sequences(sequences, maxlenmaxlen, paddingpost, truncatingpost) y_dl comments[label].valuesnum_words8000意味着只保留出现频率最高的 8000 个词低频词统一归到OOV。maxlen100是经验值你可以先统计评论长度分布再定太短会截断信息太长会拖慢训练。paddingpost表示在末尾补零和truncatingpost配合保证截断和填充方向一致。4.2 CNN 文本分类模型搭建文本分类里 CNN 的卷积核在词向量序列上滑动能捕捉局部 n-gram 特征训练速度比 LSTM 快适合毕设的时间预算。下面是一个可跑的基线结构from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, Conv1D, GlobalMaxPooling1D, Dense, Dropout model Sequential([ # 词嵌入层input_dim 要和 tokenizer 词表一致 Embedding(input_dim8000, output_dim128, input_lengthmaxlen), # 卷积层128 个卷积核窗口大小 5 Conv1D(filters128, kernel_size5, activationrelu), GlobalMaxPooling1D(), Dropout(0.5), Dense(64, activationrelu), Dense(1, activationsigmoid) ]) model.compile( optimizeradam, lossbinary_crossentropy, metrics[accuracy] ) model.summary()Embedding的output_dim128是词向量维度太小表达力不够太大容易过拟合。kernel_size5表示一次看 5 个词对中文短评论比较合适。Dropout(0.5)是防过拟合的常规操作如果训练集很小可以调到 0.3。最后一层sigmoid输出 0 到 1 之间的概率对应二分类情感。4.3 训练过程监控与早停深度学习最容易翻车的地方是过拟合训练集准确率一路涨验证集却开始掉。加EarlyStopping能自动在验证损失不再下降时停掉训练省时间也省显存。from tensorflow.keras.callbacks import EarlyStopping # 划分训练验证集 X_train_dl, X_val_dl, y_train_dl, y_val_dl train_test_split( X_dl, y_dl, test_size0.2, random_state42 ) early_stop EarlyStopping( monitorval_loss, patience3, restore_best_weightsTrue ) history model.fit( X_train_dl, y_train_dl, validation_data(X_val_dl, y_val_dl), epochs20, batch_size64, callbacks[early_stop] )patience3表示验证损失连续 3 轮不降就停restore_best_weightsTrue保证最后留下的是最优轮次的权重而不是最后一轮的。batch_size64是显存和训练稳定性的折中显存小就降到 32。训练完后可以把history.history[accuracy]和val_accuracy画成曲线放进报告这是评审老师爱看的内容。5. 情绪指数计算与大盘对比从打分到图表的最后一公里模型输出的是每条评论的情感得分但毕设报告里真正有说服力的是「情绪指数与上证指数走势对比图」。compute_sent_idx.py和plot_sent_idx.py就是干这个的这一步的坑集中在日期对齐和指数归一化上。5.1 按日期聚合情绪得分情绪指数的常见算法是把同一天所有评论的得分取平均再按日期排序。如果得分有正有负可以直接平均如果只有 0/1 标签需要先转成 1/-1 再平均。import pandas as pd # 读取模型打分结果 scores pd.read_csv(model_ml_scores.csv) # 确保 date 列是日期类型否则排序会按字符串排 scores[date] pd.to_datetime(scores[date]) # 按日期聚合取情感得分均值作为当日情绪指数 sent_idx scores.groupby(date)[score].mean().reset_index() sent_idx.columns [date, sentiment_index] # 保存中间结果方便 plot_sent_idx.py 读取 sent_idx.to_csv(data/sentiment_index.csv, indexFalse) print(sent_idx.head())pd.to_datetime这一步不能省否则2024-1-2和2024-01-02会被当成两个不同的日期。如果某天评论数为 0聚合后那天会直接缺失画图时曲线会断开常见做法是用前一天的指数填充或者插值具体在报告里说明即可。5.2 与上证指数对齐并归一化sentiment_index和sh000001.csv里的收盘价量纲完全不同直接画在一张图上会有一条线被压成直线。必须先按日期取交集再做归一化。import matplotlib.pyplot as plt import matplotlib.dates as mdates # 设置中文字体Windows 用 SimHeimacOS 用 Arial Unicode MS plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 读取行情数据 market pd.read_csv(data/sh000001.csv) market[date] pd.to_datetime(market[date]) # 按日期内连接只保留两边都有的交易日 merged pd.merge(sent_idx, market, ondate, howinner) merged merged.sort_values(date) # 归一化到 0-1 区间消除量纲差异 merged[sent_norm] (merged[sentiment_index] - merged[sentiment_index].min()) / \ (merged[sentiment_index].max() - merged[sentiment_index].min()) merged[close_norm] (merged[close] - merged[close].min()) / \ (merged[close].max() - merged[close].min()) # 画双线图 fig, ax plt.subplots(figsize(12, 5)) ax.plot(merged[date], merged[sent_norm], label情绪指数, colororange) ax.plot(merged[date], merged[close_norm], label上证指数, colorsteelblue) ax.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m)) ax.legend() plt.tight_layout() plt.savefig(img/BI_and_Market.png, dpi150)howinner保证只保留评论和行情都覆盖的日期避免出现大段空白。归一化用 min-max 是最简单的做法如果你想让报告更严谨可以用 z-score 标准化并在图注里说明。dpi150保证截图放进论文后不糊tight_layout防止日期标签被裁掉。注意如果merged为空说明两个数据的日期格式或范围完全对不上先打印各自的date.min()和date.max()排查而不是继续调画图参数。6. 避坑与排查这份代码跑不起来时先查这五处6.1 评论文件解压后路径不对现象model_ml.py报FileNotFoundError: data/stock_comments.csv。 原因stock_comments.zip解压后可能多了一层目录或者文件名不是stock_comments.csv。 解决先ls data/或dir data\确认实际文件名再改脚本里的读取路径不要凭猜。6.2 中文分词后全是单字现象TF-IDF 特征里出现大量单字模型效果很差。 原因jieba没有加载金融词典或者cut_text里过滤条件写成了len(w) 0。 解决补一个金融词典文件把len(w) 1作为过滤条件同时检查评论编码是不是utf-8。6.3 深度学习模型 loss 不下降现象训练几轮后loss一直在 0.69 附近准确率约 0.5。 原因标签没有转成 0/1或者Embedding的input_dim和 tokenizer 词表大小不一致。 解决打印y_dl[:10]确认标签范围检查Tokenizer(num_words8000)和Embedding(input_dim8000)是否对齐。6.4 情绪指数曲线断成几截现象BI.png里曲线中间有大段空白。 原因某些日期没有评论聚合后缺失画图时直接跳过了。 解决在sent_idx上做日期重采样resample(D).mean()再用interpolate()填充并在报告里说明填充方法。6.5 图表中文显示为方框现象plot_sent_idx.py输出的图里标题和标签全是方块。 原因matplotlib默认字体不支持中文。 解决设置plt.rcParams[font.sans-serif]Windows 用SimHeimacOS 用Arial Unicode MSLinux 需要先安装中文字体。7. 进阶技巧把情绪指数做成可复用的信号跑通全流程之后如果你想让这份毕设从「能交差」变成「有亮点」可以试试把情绪指数当成一个交易信号来验证。具体做法是对情绪指数做滚动平均当短期均值上穿长期均值时标记为看多信号下穿时标记为看空信号然后统计信号出现后 N 天的上证指数涨跌情况。# 滚动平均构造信号 merged[sent_ma5] merged[sentiment_index].rolling(5).mean() merged[sent_ma20] merged[sentiment_index].rolling(20).mean() # 金叉看多死叉看空 merged[signal] 0 merged.loc[merged[sent_ma5] merged[sent_ma20], signal] 1 merged.loc[merged[sent_ma5] merged[sent_ma20], signal] -1 # 统计信号后 5 日收益 merged[future_ret] merged[close].shift(-5) / merged[close] - 1 signal_perf merged.groupby(signal)[future_ret].mean() print(signal_perf)这段代码的价值不在于证明情绪指数能赚钱而在于让你的报告多一个「信号有效性初探」的章节。rolling(5)和rolling(20)是常见参数你可以换成 3 和 10 做对比。shift(-5)表示未来 5 日收益注意这会有未来函数的问题报告里要明确说明这是事后统计不是实盘策略。我自己的习惯是每次改完参数先把model_ml_scores.csv和sentiment_index.csv各备份一份再跑画图脚本。因为一旦覆盖了原始打分后面想复现之前的图就得重新训练时间成本很高。从那以后我每次调参前都强制走一遍备份希望帮到你。本文还有配套的精品资源点击获取