Django结合TensorFlow的股票预测系统设计与实现
1. 项目整体设计与架构拆解毕业设计做股票预测系统是我见过最多人选择的方向之一。每年都有大量计算机相关专业的学生把“基于深度学习的股票走势预测”作为课题但绝大多数人卡在同一个地方模型训出来了却不知道怎么变成一个能演示、能交差、能跑起来的完整系统。标题里给出的“Django TensorFlow”组合其实就是解决这个问题的经典方案——Django负责搭建Web应用TensorFlow负责深度学习预测两者一结合就是一个既有技术深度又有完整性的毕业设计项目。1.1 核心需求解析在动手之前先要把这个课题的需求拆开来看。表面上这个系统要做的事情很简单用户输入一只股票代码系统返回未来一段时间走势的预测结果。但真正落实到毕业设计答辩时评委看的是三个层面的东西。第一层是深度学习应用能力。这部分主要体现在TensorFlow的建模过程中包括数据的预处理、LSTM等时序模型的构建、超参数的调整、训练与评估。评委希望看到你对神经网络有基本理解而不是机械套用现成代码。第二层是Web系统开发能力。Django作为后端框架需要实现用户交互、股票查询、结果展示等功能。有些同学会用Flask但Django自带Admin后台、ORM数据库操作、表单处理等组件开发效率更高写进论文里的“系统架构”也更完整所以毕业设计选Django是明智的。第三层是系统整合与工程化能力。模型训练好之后如何保存、如何被Web后端调用、如何把预测结果实时传回前端展示这部分恰恰是教学课程里很少涉及、但工作中最常用的技能。这个项目的技术路线非常清晰就是把一个“离线训练模型”的任务和一个“在线提供服务”的任务拼成一个闭环。我来拆解一下这个闭环具体长什么样。1.2 总体架构与技术选型整个系统的典型架构分为四层层级技术选型职责说明前端展示层HTML CSS JavaScript ECharts股票历史K线展示、预测结果图表、用户交互界面Web服务层Django Django REST Framework接收请求、调用模型、返回JSON数据、用户管理模型服务层TensorFlowLSTM模型加载训练好的模型对输入序列执行预测数据层SQLite/MySQL Pandas存储用户信息、历史股票数据、预测记录选型背后是有讲究的。Django自带ORM操作数据库基本不用写SQL能省下大量开发时间。TensorFlow作为深度学习框架生态成熟Keras API对新手友好LSTM模型几十行代码就能搭出来。而ECharts在前端绘制K线和走势图效果比matplotlib生成的静态图好太多演示时视觉冲击力强。整个系统的数据流向是这样的用户在前端页面输入股票代码Django视图接收请求调用TensorFlow模型服务模型从数据源本地数据库或实时接口提取最近N个交易日的股票数据经预处理后交给LSTM网络做预测再把预测结果返回给前端渲染。这个流程里最核心的一句话是——模型只在训练阶段使用“历史数据标签”的形式学习规律在推理阶段只做前向传播计算这两者必须明确区分很多人在答辩被问倒就是没搞清这个基本概念。2. 核心功能模块详解与建模实操既然整体架构定了接下来要做的就是逐个模块实现。我以自己实际开发过的模拟项目X为参照一套完整的股票预测演示系统把每个核心模块的实现细节和关键代码思路展开讲。2.1 Django后端从项目骨架到接口设计Django后端是整个系统的中枢。首先是创建项目和应用django-admin startproject stock_project cd stock_project python manage.py startapp stock_app在settings.py中有几处需要特别注意。一是把stock_app注册到INSTALLED_APPS二是配置数据库开发环境直接用默认的SQLite即可省去环境配置成本三是设置静态文件和模板路径。这里我习惯先设计好数据模型再反向梳理接口。核心数据表有两张一张是StockHistory保存股票每日的交易数据开高低收、成交量等另一张是PredictionRecord保存每次预测的请求参数与结果既方便演示时展示历史预测记录也能在论文里体现“系统具备数据持久化能力”。# models.py from django.db import models class StockHistory(models.Model): code models.CharField(max_length10, db_indexTrue) date models.CharField(max_length20) open models.FloatField() high models.FloatField() low models.FloatField() close models.FloatField() volume models.FloatField() class Meta: unique_together (code, date) class PredictionRecord(models.Model): code models.CharField(max_length10) predict_date models.CharField(max_length20) predicted_price models.FloatField() actual_price models.FloatField(nullTrue, blankTrue) created_at models.DateTimeField(auto_now_addTrue)这种设计思路叫作“先定数据模型再写业务逻辑”。模型字段定义清楚了数据库表结构就固定了后续视图和模板的编写都会顺畅很多。接口设计方面我建议用前后端分离的思路即使模板渲染也能做但返回JSON接口的方式更方便前端做动态更新。核心接口有三个GET /api/stock/code/history/返回历史行情数据GET /api/stock/code/predict/返回预测结果POST /api/user/register/和POST /api/user/login/用户认证视图部分有一个细节值得注意加载TensorFlow模型不能在每次请求时执行因为模型加载耗时可达几秒会拖垮接口响应。正确做法是在Django启动时加载一次放在模块级全局变量里。例如在models.py同级新建ml_service.py# ml_service.py import tensorflow as tf from tensorflow.keras.models import load_model _model None def get_model(): global _model if _model is None: _model load_model(model/stock_lstm.h5) return _model这个设计在答辩时很容易成为加分项因为它体现了对“模型生命周期管理”的理解。顺便说一句模型加载路径建议用绝对路径或os.path.join(BASE_DIR, model, stock_lstm.h5)不要用相对路径避免运行时找不到文件。2.2 TensorFlow模型构建用LSTM建模时间序列股票数据本质上是时间序列所以深度学习模型的选择上LSTM长短期记忆网络是最自然、也是论文里最好写的方案。LSTM能通过门控机制记住长期依赖关系比普通全连接网络在时序预测上表现更好。搭建模型的核心代码如下from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout def build_model(input_shape): model Sequential([ LSTM(units64, return_sequencesTrue, input_shapeinput_shape), Dropout(0.2), LSTM(units64, return_sequencesFalse), Dropout(0.2), Dense(units32, activationrelu), Dense(units1) ]) model.compile(optimizeradam, lossmse, metrics[mae]) return model这个结构包含两层LSTM和两个全连接层参数选择基于常见经验第一层LSTM返回完整序列给第二层继续提取特征第二层仅返回最后一个时间步的输出接上全连接层输出预测价格。Dropout设置为0.2是为了防止过拟合因为股票数据噪声很大模型如果过强很容易死记硬背训练集。输入形状input_shape是一个二元组(time_steps, n_features)。time_steps表示用过去多少个交易日的特征序列来预测未来一天的价格我一般取60相当于3个月左右的交易日。n_features取5对应开盘价、最高价、最低价、收盘价、成交量这五个基本维度。这里要给准备答辩的人提个醒一定要能说清楚为什么取60、为什么用MSE损失函数。不能只会说“参考网上代码”。常见的回答逻辑是股票短中期趋势一般参考20日、60日均线60个交易日能让模型看到足够长的趋势信息MSE均方误差对大误差施加更大惩罚和价格预测这类回归任务天然契合Adam优化器自适应学习率在大多数场景下无需手动调节学习率对新手最友好。2.3 前端可视化K线图与预测曲线的实现前端部分我采用的是Bootstrap布局加ECharts图表。整个页面分左右结构左侧是股票代码输入框和查询按钮右侧是图表区域。图表区域放置两个图表一个显示历史K线叠加预测曲线另一个显示最近30日收盘价与未来7日预测值对比。ECharts的使用方式不复杂核心是引入JavaScript文件在初始化时配置图表类型和数据源const chart echarts.init(document.getElementById(chart)); $.get(/api/stock/${code}/history/, function(res) { // 构造K线数据: [open, close, lowest, highest] const klineData res.data.map(item [item.open, item.close, item.low, item.high]); const dates res.data.map(item item.date); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: dates }, yAxis: { scale: true }, series: [{ type: candlestick, data: klineData }] }); });预测结果则用一条单独的折线叠加在K线尾部用不同颜色区分真实数据和预测数据。这个视觉效果非常直接演示时一句话就能讲清楚“红色虚线是模型对接下来5个交易日的预测收盘价”。需要提醒的是前端请求路径中一定要处理好Django的CSRF防护。对于使用jQuery发起POST请求的场景最简单的方式是在模板中加入{% csrf_token %}并在Ajax请求头中带上令牌。我在第一次做的时候就踩过这个坑所有POST请求全部403排查了半天才想起来是CSRF的问题。3. 数据获取、预处理与特征工程数据是深度学习的粮食这个项目里也是最容易被忽略、却最影响最终效果的一环。很多同学拿到一份现成的CSV文件直接把数据丢给模型训练结果模型损失值低得离谱实际上是因为数据处理不当造成了数据泄露。3.1 股票数据获取方案数据获取有两条路径离线下载和实时接口。离线下载适合训练阶段一次获取多年历史数据存成CSV或写入数据库实时接口适合系统运行阶段预测时拉取最新行情。国内常用的免费数据源有akshare和tushare我建议用akshare因为它无需注册令牌开箱即用。import akshare as ak # 获取A股日线行情 df ak.stock_zh_a_hist(symbol000001, perioddaily, start_date20180101, end_date20231231, adjustqfq)参数adjustqfq表示前复权。这个参数很多人会忽略但它对模型训练极其重要。股票分红送股会导致价格跳空不复权的话模型会把这种非交易因素造成的价格变动当成规律来学习直接拉低预测准确率。获取到的数据包含日期、开盘、收盘、最高、最低、成交量、成交额、振幅、涨跌幅等字段我们建模只用前五个核心字段加上成交量共六个维度。多余的字段删掉减少数据冗余对训练速度的影响。3.2 数据清洗与序列化清洗环节做三件事处理缺失值、去除异常值、统一数据类型。缺失值一般用前向填充法也就是用上一个交易日的数据填补空缺因为停牌等场景会导致个别日期数据缺失直接用当日行情延续是最合理的近似。异常值处理方面如果出现涨幅超过20%或跌幅超过20%的数据多半是数据源错误或特殊事件直接删除更安全。然后是构建监督学习所需的数据集。核心思路是把时间序列转换成“滑动窗口”的数据结构def create_dataset(data, time_steps60): X, y [], [] for i in range(time_steps, len(data)): X.append(data[i - time_steps:i]) y.append(data[i, 3]) # 第3列是收盘价预测目标 return np.array(X), np.array(y)这里每一行样本都由过去60个交易日的六维数据组成标签是第61天的收盘价。用大白话讲就是给模型看过去两个多月的数据让它猜下一天收盘价是多少。数据标准化是必不可少的步骤。股票价格的绝对数值可能从几元到几百元不等成交量差异更大如果不归一化模型训练很容易不收敛。我用的是MinMaxScaler把所有特征压缩到[0, 1]区间from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(data)这里有一个必须记住的细节fit_transform只用在训练集上验证集和测试集只能用transform。如果整体数据一起做标准化测试集的信息就提前泄露给了模型训练出来的模型在真实场景中效果会大打折扣。这个细节在毕业设计论文里写出来是很加分的设计意识。3.3 训练集与测试集划分时间序列数据不能像普通分类数据一样随机打乱再划分因为顺序本身即信息。正确做法是按时间顺序切分。我习惯用前80%的数据做训练后20%做测试。train_size int(len(scaled_data) * 0.8) train_data scaled_data[:train_size] test_data scaled_data[train_size:]再进一步从训练集中再切出10%做验证集用于观察训练过程中的过拟合情况。完整比例为训练集72%、验证集8%、测试集20%。这个比例是经验值既保证了训练数据量充足又让验证集有足够的样本量来反映训练是否过拟合。4. 模型训练、指标评估与调优实战模型训练是这个项目里耗时最长、最磨人的阶段。我在模拟项目X的训练过程中跑了将近50组对比实验最终确定了比较稳定的参数组合。下面把整个过程拆开讲。4.1 训练流程与关键参数解析完整的训练代码大概长这样history model.fit( X_train, y_train, validation_data(X_val, y_val), epochs100, batch_size32, callbacks[ EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue), ReduceLROnPlateau(monitorval_loss, factor0.5, patience5) ] )两个回调函数是训练质量的保障。EarlyStopping监控验证集损失如果连续10轮不下降就停止训练并回滚到最优权重可以防止训练到后期模型开始过拟合ReduceLROnPlateau在损失陷入平台期时将学习率减半帮助模型跳出局部最优。关于batch_size我做过不同取值的对比实验。32的效果相对均衡64的训练速度更快但损失略高16则训练震荡明显。epochs设定为100但配合早停机制实际训练通常到30~40轮就停止了。这也说明了回调函数的意义——不用手动盯着训练曲线模型自己会在最合适的位置停下来。4.2 模型评估不只是看损失值训练完成后在测试集上评估模型test_loss, test_mae model.evaluate(X_test, y_test) # 预测并还原真实价格 predicted model.predict(X_test) predicted_prices scaler.inverse_transform( np.concatenate([np.zeros((len(predicted), 5)), predicted], axis1) )[:, 5]这里有一个反向标准化的技巧因为标准化时把六维特征一起归一化预测结果只是一维收盘价还原时需要补上其他五维的占位数据再取最后一列。很多人就在这里报错因为数组维度对不上。评估指标方面均方误差MSE、平均绝对误差MAE和平均绝对百分比误差MAPE是三个最常用的。对股票预测而言MAE和MAPE更直观因为价格单位是“元”误差多少元一眼就能看懂。指标模拟项目X测试集结果含义说明MSE0.0025标准化后均方误差对离群点敏感MAE0.031标准化后平均绝对误差MAPE约2.3%还原后平均误差占真实价格的比例MAPE在2%~3%左右看起来不错但要泼一盆冷水这只能说明模型在“整体趋势跟随”上有效不代表它能精准预测未来涨跌。股票预测的本质是概率性估计任何声称能精确预测价格的系统在学术上都是站不住脚的。毕业设计论文里如果明确写到这一点反而更显严谨客观。4.3 模型持久化与集成训练完成后模型需要导出为文件供Django调用model.save(stock_lstm.h5)TensorFlow 2.x推荐使用SavedModel格式保存为整个文件夹但h5格式在毕业设计场景下更简单一个文件即可管理。加载时使用之前提到的load_model即可。除了模型本身scaler也需要持久化。推理阶段对新数据进行预测时必须使用训练时拟合好的scaler做转换而不能重新计算。这个可以用joblib保存import joblib joblib.dump(scaler, scaler.pkl)模型和scaler的加载目录建议统一放在model/文件夹下与Django的manage.py同级路径清晰且不容易混淆。5. 系统集成、测试与部署模型训练好、Web后端写好两个模块就要联调了。这个阶段看似只是“拼起来”实际踩坑非常多。5.1 后端调用模型的完整链路前端发起预测请求后后端视图函数的逻辑大致如下def predict(request, code): # 1. 从数据库获取最近60个交易日的历史数据 history StockHistory.objects.filter(codecode).order_by(-date)[:60] history list(reversed(history)) # 2. 转换为模型输入格式 df pd.DataFrame([{ open: h.open, high: h.high, low: h.low, close: h.close, volume: h.volume } for h in history]) # 3. 标准化 scaled scaler.transform(df[[open, high, low, close, volume]]) X_input np.expand_dims(scaled, axis0) # 形状 (1, 60, 5) # 4. 预测并还原 pred model.predict(X_input)[0][0] pred_price scaler.inverse_transform( np.concatenate([np.zeros((1, 4)), [[pred]]], axis1) )[0, 4] return JsonResponse({code: code, predicted_price: float(pred_price)})第4步的还原逻辑和训练时的处理方式一致这是最容易出错的地方。我在第一次实现时直接在单个数值上调用inverse_transform结果报错说需要二维数组。后来才意识到scaler是根据六列数据拟合的必须有完整的特征矩阵才能转换。5.2 端到端测试要点联调完成之后需要走一遍完整的功能测试。测试用例至少要覆盖以下几个方面正常流程输入有效的股票代码返回历史数据和预测结果前端图表渲染正常。异常输入输入不存在的股票代码系统不能崩溃要返回友好的错误提示。数据不足新上市股票历史交易日不足60天系统应提示“数据不足无法预测”。并发请求多个用户同时查询Django开发服务器在单线程下可能阻塞但毕业设计场景一般不做高并发要求能正常处理即可。这些测试用例的截图放进毕业论文里就能展示完整的“系统测试”章节比空泛地写“系统运行稳定”要有说服力得多。5.3 本地部署注意事项毕业设计通常是在答辩现场演示部署环境一般是自己的笔记本电脑。这里有几个经验点TensorFlow是CPU版本就够用。训练时如果只用CPU100轮LSTM训练在小数据集上大约需要3~5分钟可以接受。如果装了GPU版反而容易因为显卡驱动版本不匹配出现CUDA错误浪费时间。Django开发服务器的运行命令保持不变python manage.py runserver启动后浏览器访问http://127.0.0.1:8000即可。在实际演示之前我强烈建议先完整跑一遍流程确认TensorFlow和Django的版本兼容性没问题。6. 常见问题与排查技巧实录最后这节我把在模拟项目X开发过程中遇到最典型的几类问题记录在这里这些问题在答辩前没解决的话现场演示翻车概率极高。6.1 版本兼容性问题TensorFlow与Python、NumPy之间的版本兼容是新手最容易卡住的环节。比如TensorFlow 2.10之后的版本在Windows上不再支持原生GPU训练NumPy版本过高会导致加载模型时报错module numpy has no attribute float。这个问题我刚遇到时头疼了半天后来总结出最稳妥的环境配置组合pip install tensorflow2.10.0 pip install numpy1.23.5 pip install django4.1.7 pip install pandas1.5.3 pip install scikit-learn1.2.2这个组合在2024年的Windows和macOS上都能正常跑各库之间的依赖兼容性经过大量项目验证。不要盲目安装最新版本尤其是TensorFlow新版本对依赖项的约束经常让人措手不及。6.2 预测结果严重偏高的数据泄露陷阱我最初训练出来的模型在测试集上MAPE只有0.8%但把模型接入系统后对真实新数据的预测值却明显偏离正常范围。排查了很久发现是数据预处理时我用全部数据包含测试集做了fit_transform再切分训练集和测试集。这就相当于模型在训练时“偷看”了测试集的价格区间测试表现自然虚高。而真实推理时新数据的分布和训练时的标准化范围不一致预测值就崩了。这个问题的修复方案很简单先用训练集拟合scaler然后用训练好的scaler去转换所有数据。这是股票预测毕业设计中最容易被忽视、也最具迷惑性的错误写论文时如果能把“避免数据泄露”单独作为一个设计要点展开会是非常亮眼的内容。6.3 LSTM训练震荡不收敛有同学会遇到训练loss曲线剧烈震荡、迟迟不降的情况。排查方向主要有三个第一数据有没有做标准化。没有标准化的原始价格数据和成交量数据量级差异巨大梯度更新方向被量级大的特征主导模型自然训练不好。第二学习率是否过大。Adam默认学习率是0.001一般不用改但如果数据量很小可以降到0.0001试试。第三时间步长是否过大。有些同学把time_steps设成240一年的交易日对LSTM来说输入过长且样本急剧减少反而让训练更困难。60~90是合理区间。6.4 模型加载后首次请求响应慢首次请求时Django进程需要加载h5模型文件这个动作可能要2~3秒。这在演示现场是非常尴尬的等待。解决思路是在Django启动时预加载模型。可以在apps.py中重写ready()方法或在视图模块被导入时触发加载。如果不想改代码架构还有一个土办法在运行系统前先手动执行一个小脚本把模型加载到内存里然后再启动Django。虽然不太优雅但在答辩演示场景中很实用。最后的经验分享做这个项目最大的体会是毕业设计不是模型效果越炫越好而是要把“数据→训练→部署→展示”整条链路打通。很多同学花大量时间调模型、刷指标却在系统演示环节翻车——不是Django起不来就是前端图表不显示反而得不偿失。如果想在原课题基础上做得更有亮点我建议可以增加一个“多股票对比预测”功能让用户同时输入两只股票代码前端并排展示预测走势曲线。这个功能实现难度不大但答辩时视觉冲击力更强也能引出“模型是否具有泛化能力”的讨论。另外一个实用的建议是把项目文件组织好模型、源码、论文、答辩PPT分目录存放README写清楚运行步骤。答辩现场老师大概率会要求现场操作一个条例清晰的工程目录本身就是加分项。最后再说一句所有预测结果仅供学习研究使用论文里记得加上这一句说明。