源代码续:
src\prediction_service.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
预测服务模块
该模块负责加载训练好的模型,并提供预测服务
"""
import os
import sys
import json
import logging
import pandas as pd
import numpy as np
import joblib
from datetime import datetime
from flask import Flask, request, jsonify, render_template, Blueprint
from sklearn.preprocessing import StandardScaler, MinMaxScaler
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger('prediction_service')
class PredictionService:
"""
预测服务类
负责加载模型、处理输入数据并返回预测结果
"""
def __init__(self, model_dir=None):
"""
初始化预测服务
参数:
model_dir (str, optional): 模型目录路径,如果为None则使用默认路径
"""
if model_dir is None:
# 使用默认模型目录
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
model_dir = os.path.join(base_dir, 'model')
self.model_dir = model_dir
self.model = None
self.feature_names = None
self.preprocessor = None
self.metadata = None
# 尝试加载模型
self._load_model()
def _load_model(self):
"""
加载最佳模型及其相关组件
"""
try:
# 查找模型目录中的模型文件
model_files = [f for f in os.listdir(self.model_dir) if f.endswith('_model.pkl')]
if not model_files:
logger.warning(f"在 {self.model_dir} 中未找到模型文件")
return False
# 加载元数据以确定最佳模型
metadata_path = os.path.join(self.model_dir, 'model_metadata.json')
if os.path.exists(metadata_path):
with open(metadata_path, 'r') as f:
self.metadata = json.load(f)
logger.info(f"已加载模型元数据: {self.metadata['model_name']}")
# 确定要加载的模型文件
if self.metadata and 'model_name' in self.metadata:
model_file = f"{self.metadata['model_name'].replace(' ', '_')}_model.pkl"
model_path = os.path.join(self.model_dir, model_file)
if not os.path.exists(model_path):
# 如果找不到指定的模型,使用第一个可用的模型
model_path = os.path.join(self.model_dir, model_files[0])
logger.warning(f"未找到指定的模型 {model_file},使用 {model_files[0]} 代替")
else:
# 如果没有元数据,使用第一个可用的模型
model_path = os.path.join(self.model_dir, model_files[0])
logger.info(f"使用模型: {model_files[0]}")
# 加载模型
self.model = joblib.load(model_path)
logger.info(f"已加载模型: {model_path}")
# 加载特征名称
feature_names_path = os.path.join(self.model_dir, 'feature_names.pkl')
if os.path.exists(feature_names_path):
self.feature_names = joblib.load(feature_names_path)
logger.info(f"已加载特征名称,共 {len(self.feature_names)} 个特征")
# 加载预处理器
preprocessor_path = os.path.join(self.model_dir, 'preprocessor.pkl')
if os.path.exists(preprocessor_path):
self.preprocessor = joblib.load(preprocessor_path)
logger.info("已加载预处理器")
return True
except Exception as e:
logger.error(f"加载模型失败: {str(e)}")
return False
def _preprocess_data(self, data):
"""
预处理输入数据
参数:
data (DataFrame): 输入数据
返回:
DataFrame: 预处理后的数据
"""
try:
# 检查是否有特征名称列表
if self.feature_names is not None:
# 检查输入数据是否包含所有必需的特征
missing_features = [f for f in self.feature_names if f not in data.columns]
if missing_features:
logger.warning(f"输入数据缺少以下特征: {missing_features}")
# 为缺失的特征填充0
for feature in missing_features:
data[feature] = 0
# 确保数据只包含模型需要的特征,并按正确顺序排列
data = data[self.feature_names]
# 应用预处理器(如果存在)
if self.preprocessor is not None:
data = self.preprocessor.transform(data)
logger.info("已应用预处理器")
return data
except Exception as e:
logger.error(f"预处理数据失败: {str(e)}")
return None
def predict(self, data):
"""
对输入数据进行预测
参数:
data (DataFrame): 输入数据
返回:
array: 预测结果
"""
try:
# 检查模型是否已加载
if self.model is None:
logger.error("模型未加载,无法进行预测")
return None
# 预处理数据
processed_data = self._preprocess_data(data)
if processed_data is None:
return None
# 进行预测
predictions = self.model.predict(processed_data)
logger.info(f"已完成对 {len(data)} 条数据的预测")
return predictions
except Exception as e:
logger.error(f"预测失败: {str(e)}")
return None
def predict_single(self, input_data):
"""
对单条输入数据进行预测
参数:
input_data (dict): 输入数据字典
返回:
float: 预测结果
"""
try:
# 将输入数据转换为DataFrame
df = pd.DataFrame([input_data])
# 确保所有数值字段都是浮点型
numeric_fields = ['attendance_rate', 'homework_completion', 'class_participation',
'study_hours_per_week', 'previous_exam_score', 'difficulty_level']
for field in numeric_fields:
if field in df.columns:
df[field] = df[field].astype(float)
# 进行预测
predictions = self.predict(df)
if predictions is None:
return None
# 返回单个预测值
return float(predictions[0])
except Exception as e:
logger.error(f"单条预测失败: {str(e)}")
return None
def predict_batch(self, file_path):
"""
对批量数据进行预测
参数:
file_path (str): 输入数据文件路径,支持CSV格式
返回:
tuple: (预测结果DataFrame, 成功标志)
"""
try:
# 加载数据
if file_path.endswith('.csv'):
data = pd.read_csv(file_path)
else:
logger.error(f"不支持的文件格式: {file_path}")
return None, False
# 保存原始数据的ID或索引列
id_column = None
for col in ['id', 'ID', 'student_id', 'student_ID', 'index']:
if col in data.columns:
id_column = col
break
if id_column is None:
# 如果没有ID列,使用行索引
original_indices = data.index
else:
original_indices = data[id_column].values
# 移除非特征列
feature_data = data.copy()
if id_column:
feature_data = feature_data.drop(id_column, axis=1)
# 进行预测
predictions = self.predict(feature_data)
if predictions is None:
return None, False
# 创建结果DataFrame
results = pd.DataFrame({
'ID': original_indices,
'Predicted_Score': predictions
})
logger.info(f"批量预测完成,共 {len(results)} 条结果")
return results, True
except Exception as e:
logger.error(f"批量预测失败: {str(e)}")
return None, False
def get_model_info(self):
"""
获取模型信息
返回:
dict: 模型信息
"""
if self.model is None:
return {"error": "模型未加载"}
info = {
"model_type": type(self.model).__name__,
"feature_count": len(self.feature_names) if self.feature_names else "未知",
"loaded_time": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
# 添加元数据信息(如果存在)
if self.metadata:
info.update({
"model_name": self.metadata.get('model_name', '未知'),
"training_samples": self.metadata.get('training_samples', '未知'),
"creation_date": self.metadata.get('creation_date', '未知'),
"performance": self.metadata.get('performance', {})
})
return info
# 创建Flask Blueprint
prediction_bp = Blueprint('prediction', __name__)
# 全局预测服务实例
prediction_service = None
def init_prediction_service(app, model_dir=None):
"""
初始化预测服务
参数:
app (Flask): Flask应用实例
model_dir (str, optional): 模型目录路径
"""
global prediction_service
prediction_service = PredictionService(model_dir)
# 注册Blueprint
app.register_blueprint(prediction_bp, url_prefix='/prediction')
logger.info("预测服务已初始化")
@prediction_bp.route('/info', methods=['GET'])
def model_info():
"""获取模型信息的API端点"""
if prediction_service is None:
return jsonify({"error": "预测服务未初始化"}), 500
info = prediction_service.get_model_info()
return jsonify(info)
@prediction_bp.route('/predict', methods=['POST'])
def predict():
"""单条预测的API端点"""
if prediction_service is None:
return jsonify({"error": "预测服务未初始化"}), 500
try:
# 获取JSON输入数据
input_data = request.json
if not input_data:
return jsonify({"error": "未提供输入数据"}), 400
# 进行预测
prediction = prediction_service.predict_single(input_data)
if prediction is None:
return jsonify({"error": "预测失败"}), 500
# 返回预测结果
return jsonify({
"prediction": prediction,
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S')
})
except Exception as e:
logger.error(f"API预测失败: {str(e)}")
return jsonify({"error": str(e)}), 500
@prediction_bp.route('/single', methods=['GET'])
def predict_single():
"""单条预测页面"""
return render_template('single_prediction.html')
@prediction_bp.route('/batch', methods=['GET', 'POST'])
def batch_prediction():
"""批量预测页面和API端点"""
if prediction_service is None:
return jsonify({"error": "预测服务未初始化"}), 500
if request.method == 'GET':
# 返回批量预测页面
return render_template('batch_prediction.html')
elif request.method == 'POST':
try:
# 检查是否有文件上传
if 'file' not in request.files:
return jsonify({"error": "未上传文件"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "未选择文件"}), 400
# 保存上传的文件
upload_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'uploads')
if not os.path.exists(upload_dir):
os.makedirs(upload_dir)
file_path = os.path.join(upload_dir, file.filename)
file.save(file_path)
# 进行批量预测
results, success = prediction_service.predict_batch(file_path)
if not success:
return jsonify({"error": "批量预测失败"}), 500
# 保存预测结果
results_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'results')
if not os.path.exists(results_dir):
os.makedirs(results_dir)
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
result_file = os.path.join(results_dir, f'prediction_results_{timestamp}.csv')
results.to_csv(result_file, index=False)
# 返回结果
return jsonify({
"message": "批量预测成功",
"result_file": result_file,
"row_count": len(results)
})
except Exception as e:
logger.error(f"批量预测API失败: {str(e)}")
return jsonify({"error": str(e)}), 500
def main():
"""
主函数,用于测试预测服务
"""
# 创建预测服务实例
service = PredictionService()
# 打印模型信息
print("\n模型信息:")
print(json.dumps(service.get_model_info(), indent=4, ensure_ascii=False))
# 测试单条预测
test_data = {
'attendance_rate': 0.85,
'homework_completion': 0.9,
'class_participation': 0.7,
'previous_exam_score': 75,
'study_hours_per_week': 10
}
print("\n测试单条预测:")
prediction = service.predict_single(test_data)
if prediction is not None:
print(f"预测分数: {prediction:.2f}")
# 测试批量预测
print("\n测试批量预测:")
# 创建测试数据
test_batch = pd.DataFrame([
{
'student_id': 1,
'attendance_rate': 0.85,
'homework_completion': 0.9,
'class_participation': 0.7,
'previous_exam_score': 75,
'study_hours_per_week': 10
},
{
'student_id': 2,
'attendance_rate': 0.95,
'homework_completion': 0.95,
'class_participation': 0.9,
'previous_exam_score': 85,
'study_hours_per_week': 15
}
])
# 保存测试数据
test_file = 'test_batch.csv'
test_batch.to_csv(test_file, index=False)
# 进行批量预测
results, success = service.predict_batch(test_file)
if success:
print("批量预测结果:")
print(results)
# 清理测试文件
if os.path.exists(test_file):
os.remove(test_file)
if __name__ == "__main__":
main()
src\visualization.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据可视化模块
生成直观的图表和报告,帮助教师理解预测结果和影响因素
"""
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import mean_squared_error, r2_score, mean_absolute_error
import joblib
import logging
import io
import base64
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
class DataVisualizer:
"""
数据可视化类,负责生成各种图表和报告
"""
def __init__(self, config=None):
"""
初始化数据可视化器
参数:
config (dict, optional): 配置参数字典
"""
self.config = config or {}
self.data = None
self.features = None
self.target = None
self.predictions = None
self.model = None
self.feature_importance = None
# 设置可视化样式
sns.set_style("whitegrid")
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号
def load_data(self, data_path):
"""
加载数据
参数:
data_path (str): 数据文件路径
返回:
pandas.DataFrame: 加载的数据
"""
logger.info(f"正在加载数据: {data_path}")
if not os.path.exists(data_path):
raise FileNotFoundError(f"数据文件不存在: {data_path}")
file_ext = os.path.splitext(data_path)[1].lower()
if file_ext == '.csv':
self.data = pd.read_csv(data_path)
elif file_ext == '.xlsx' or file_ext == '.xls':
self.data = pd.read_excel(data_path)
else:
raise ValueError(f"不支持的文件格式: {file_ext}")
logger.info(f"成功加载数据,共 {len(self.data)} 条记录")
return self.data
def load_model_data(self, features_path, target_path=None, model_path=None):
"""
加载模型相关数据
参数:
features_path (str): 特征数据文件路径
target_path (str, optional): 目标数据文件路径
model_path (str, optional): 模型文件路径
返回:
tuple: (特征, 目标变量, 模型)
"""
logger.info(f"正在加载模型数据")
# 加载特征
self.features = pd.read_csv(features_path)
logger.info(f"成功加载特征数据,共 {len(self.features)} 条记录,{self.features.shape[1]} 个特征")
# 加载目标变量(如果提供)
if target_path and os.path.exists(target_path):
target_data = pd.read_csv(target_path)
if isinstance(target_data, pd.DataFrame) and target_data.shape[1] == 1:
self.target = target_data.iloc[:, 0]
else:
self.target = target_data
logger.info(f"成功加载目标数据,共 {len(self.target)} 条记录")
# 加载模型(如果提供)
if model_path and os.path.exists(model_path):
self.model = joblib.load(model_path)
logger.info(f"成功加载模型: {type(self.model).__name__}")
# 使用模型进行预测
if self.features is not None:
self.predictions = self.model.predict(self.features)
logger.info(f"已生成预测结果,共 {len(self.predictions)} 条记录")
return self.features, self.target, self.model
def plot_grade_distribution(self, output_path=None):
"""
绘制成绩分布图
参数:
output_path (str, optional): 输出文件路径
返回:
matplotlib.figure.Figure: 图表对象
"""
logger.info("正在绘制成绩分布图")
if self.target is None:
logger.warning("目标变量未加载,无法绘制成绩分布图")
return None
plt.figure(figsize=(10, 6))
# 绘制成绩分布直方图
sns.histplot(self.target, kde=True, bins=20)
plt.title("学生成绩分布")
plt.xlabel("成绩")
plt.ylabel("频数")
plt.grid(True, linestyle='--', alpha=0.7)
# 添加统计信息
mean_val = self.target.mean()
median_val = self.target.median()
std_val = self.target.std()
plt.axvline(mean_val, color='r', linestyle='--', label=f'平均值: {mean_val:.2f}')
plt.axvline(median_val, color='g', linestyle='--', label=f'中位数: {median_val:.2f}')
plt.legend()
# 添加统计文本
stats_text = f"平均值: {mean_val:.2f}\n中位数: {median_val:.2f}\n标准差: {std_val:.2f}"
plt.annotate(stats_text, xy=(0.05, 0.95), xycoords='axes fraction',
bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="gray", alpha=0.8),
va='top')
plt.tight_layout()
# 保存图表
if output_path:
plt.savefig(output_path)
logger.info(f"成绩分布图已保存至: {output_path}")
return plt.gcf()
def plot_feature_correlation(self, target_col='final_grade', top_n=10, output_path=None):
"""
绘制特征与目标变量的相关性图
参数:
target_col (str): 目标列名
top_n (int): 显示前N个相关性最高的特征
output_path (str, optional): 输出文件路径
返回:
matplotlib.figure.Figure: 图表对象
"""
logger.info(f"正在绘制特征相关性图,显示前 {top_n} 个特征")
if self.data is None:
logger.warning("数据未加载,无法绘制特征相关性图")
return None
if target_col not in self.data.columns:
logger.warning(f"目标列 {target_col} 不在数据中")
return None
# 计算相关性
corr = self.data.corr()[target_col].drop(target_col)
# 按绝对值排序
corr_abs = corr.abs().sort_values(ascending=False)
# 获取前N个特征
top_features = corr_abs.head(top_n).index
corr_values = corr[top_features]
plt.figure(figsize=(12, 8))
# 创建条形图
colors = ['green' if x > 0 else 'red' for x in corr_values]
bars = plt.barh(top_features, corr_values, color=colors)
# 添加数值标签
for i, v in enumerate(corr_values):
plt.text(v + 0.01 if v > 0 else v - 0.06, i, f'{v:.3f}',
va='center', color='black' if v > 0 else 'white')
plt.title(f"特征与{target_col}的相关性(前{top_n}个)")
plt.xlabel("相关系数")
plt.ylabel("特征")
plt.axvline(x=0, color='black', linestyle='-', alpha=0.3)
plt.grid(True, linestyle='--', alpha=0.3)
# 添加图例
plt.legend([plt.Rectangle((0,0),1,1, color='green'),
plt.Rectangle((0,0),1,1, color='red')],
['正相关', '负相关'])
plt.tight_layout()
# 保存图表
if output_path:
plt.savefig(output_path)
logger.info(f"特征相关性图已保存至: {output_path}")
return plt.gcf()
def plot_correlation_matrix(self, output_path=None):
"""
绘制相关性矩阵热图
参数:
output_path (str, optional): 输出文件路径
返回:
matplotlib.figure.Figure: 图表对象
"""
logger.info("正在绘制相关性矩阵热图")
if self.data is None:
logger.warning("数据未加载,无法绘制相关性矩阵热图")
return None
# 计算相关性矩阵
corr_matrix = self.data.corr()
# 创建掩码,只显示下三角部分
mask = np.triu(np.ones_like(corr_matrix, dtype=bool))
plt.figure(figsize=(14, 12))
# 绘制热图
sns.heatmap(corr_matrix, mask=mask, annot=False, cmap='coolwarm',
vmin=-1, vmax=1, center=0, linewidths=0.5, cbar_kws={"shrink": .8})
plt.title("特征相关性矩阵")
plt.tight_layout()
# 保存图表
if output_path:
plt.savefig(output_path)
logger.info(f"相关性矩阵热图已保存至: {output_path}")
return plt.gcf()
def plot_feature_importance(self, model=None, top_n=10, output_path=None):
"""
绘制特征重要性图
参数:
model (object, optional): 模型对象,如果为None则使用已加载的模型
top_n (int): 显示前N个重要特征
output_path (str, optional): 输出文件路径
返回:
matplotlib.figure.Figure: 图表对象
"""
logger.info(f"正在绘制特征重要性图,显示前 {top_n} 个特征")
# 确定要使用的模型
if model is None:
model = self.model
if model is None:
logger.warning("模型未加载,无法绘制特征重要性图")
return None
if self.features is None:
logger.warning("特征数据未加载,无法绘制特征重要性图")
return None
# 获取特征名称
feature_names = self.features.columns
# 获取特征重要性
importance = None
# 对于线性模型
if hasattr(model, 'coef_'):
if len(model.coef_.shape) == 1:
importance = np.abs(model.coef_)
else:
importance = np.abs(model.coef_[0])
# 对于树模型
elif hasattr(model, 'feature_importances_'):
importance = model.feature_importances_
else:
logger.warning(f"模型 {type(model).__name__} 不支持特征重要性分析")
return None
# 创建特征重要性DataFrame
self.feature_importance = pd.DataFrame({
'Feature': feature_names,
'Importance': importance
})
# 按重要性排序
self.feature_importance = self.feature_importance.sort_values('Importance', ascending=False)
# 获取前N个重要特征
top_features = self.feature_importance.head(top_n)
plt.figure(figsize=(12, 8))
# 创建条形图
sns.barplot(x='Importance', y='Feature', data=top_features)
plt.title(f"特征重要性(前{top_n}个)")
plt.xlabel("重要性")
plt.ylabel("特征")
plt.grid(True, linestyle='--', alpha=0.7)
plt.tight_layout()
# 保存图表
if output_path:
plt.savefig(output_path)
logger.info(f"特征重要性图已保存至: {output_path}")
return plt.gcf()
def plot_prediction_vs_actual(self, output_path=None):
"""
绘制预测值与实际值对比图
参数:
output_path (str, optional): 输出文件路径
返回:
matplotlib.figure.Figure: 图表对象
"""
logger.info("正在绘制预测值与实际值对比图")
if self.predictions is None:
logger.warning("预测结果未生成,无法绘制预测值与实际值对比图")
return None
if self.target is None:
logger.warning("目标变量未加载,无法绘制预测值与实际值对比图")
return None
plt.figure(figsize=(10, 8))
# 绘制散点图
plt.scatter(self.target, self.predictions, alpha=0.6)
# 添加对角线(完美预测)
min_val = min(self.target.min(), self.predictions.min())
max_val = max(self.target.max(), self.predictions.max())
plt.plot([min_val, max_val], [min_val, max_val], 'r--')
plt.title("预测成绩 vs 实际成绩")
plt.xlabel("实际成绩")
plt.ylabel("预测成绩")
plt.grid(True, linestyle='--', alpha=0.7)
# 计算评估指标
mse = mean_squared_error(self.target, self.predictions)
rmse = np.sqrt(mse)
mae = mean_absolute_error(self.target, self.predictions)
r2 = r2_score(self.target, self.predictions)
# 添加评估指标文本
metrics_text = f"RMSE: {rmse:.2f}\nMAE: {mae:.2f}\nR²: {r2:.2f}"
plt.annotate(metrics_text, xy=(0.05, 0.95), xycoords='axes fraction',
bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="gray", alpha=0.8),
va='top')
plt.tight_layout()
# 保存图表
if output_path:
plt.savefig(output_path)
logger.info(f"预测值与实际值对比图已保存至: {output_path}")
return plt.gcf()
def plot_residuals(self, output_path=None):
"""
绘制残差图
参数:
output_path (str, optional): 输出文件路径
返回:
matplotlib.figure.Figure: 图表对象
"""
logger.info("正在绘制残差图")
if self.predictions is None:
logger.warning("预测结果未生成,无法绘制残差图")
return None
if self.target is None:
logger.warning("目标变量未加载,无法绘制残差图")
return None
# 计算残差
residuals = self.target - self.predictions
plt.figure(figsize=(12, 10))
# 创建子图
plt.subplot(2, 1, 1)
# 绘制残差散点图
plt.scatter(self.predictions, residuals, alpha=0.6)
plt.axhline(y=0, color='r', linestyle='--')
plt.title("残差图")
plt.xlabel("预测成绩")
plt.ylabel("残差 (实际 - 预测)")
plt.grid(True, linestyle='--', alpha=0.7)
# 绘制残差分布图
plt.subplot(2, 1, 2)
sns.histplot(residuals, kde=True, bins=20)
plt.title("残差分布")
plt.xlabel("残差")
plt.ylabel("频数")
plt.grid(True, linestyle='--', alpha=0.7)
# 添加统计信息
mean_val = residuals.mean()
std_val = residuals.std()
plt.axvline(mean_val, color='r', linestyle='--', label=f'平均值: {mean_val:.2f}')
plt.legend()
# 添加统计文本
stats_text = f"平均值: {mean_val:.2f}\n标准差: {std_val:.2f}"
plt.annotate(stats_text, xy=(0.05, 0.95), xycoords='axes fraction',
bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="gray", alpha=0.8),
va='top')
plt.tight_layout()
# 保存图表
if output_path:
plt.savefig(output_path)
logger.info(f"残差图已保存至: {output_path}")
return plt.gcf()
def plot_feature_scatter(self, feature_name, target_col='final_grade', output_path=None):
"""
绘制特征与目标变量的散点图
参数:
feature_name (str): 特征名称
target_col (str): 目标列名
output_path (str, optional): 输出文件路径
返回:
matplotlib.figure.Figure: 图表对象
"""
logger.info(f"正在绘制特征 {feature_name} 与 {target_col} 的散点图")
if self.data is None:
logger.warning("数据未加载,无法绘制特征散点图")
return None
if feature_name not in self.data.columns:
logger.warning(f"特征 {feature_name} 不在数据中")
return None
if target_col not in self.data.columns:
logger.warning(f"目标列 {target_col} 不在数据中")
return None
plt.figure(figsize=(10, 6))
# 绘制散点图
sns.regplot(x=feature_name, y=target_col, data=self.data,
scatter_kws={'alpha': 0.6}, line_kws={'color': 'red'})
plt.title(f"{feature_name} vs {target_col}")
plt.xlabel(feature_name)
plt.ylabel(target_col)
plt.grid(True, linestyle='--', alpha=0.7)
# 计算相关系数
corr = self.data[[feature_name, target_col]].corr().iloc[0, 1]
# 添加相关系数文本
corr_text = f"相关系数: {corr:.3f}"
plt.annotate(corr_text, xy=(0.05, 0.95), xycoords='axes fraction',
bbox=dict(boxstyle="round,pad=0.3", fc="white", ec="gray", alpha=0.8),
va='top')
plt.tight_layout()
# 保存图表
if output_path:
plt.savefig(output_path)
logger.info(f"特征散点图已保存至: {output_path}")
return plt.gcf()
def plot_dashboard(self, output_dir=None):
"""
生成仪表板图表
参数:
output_dir (str, optional): 输出目录
返回:
dict: 图表路径字典
"""
logger.info("正在生成仪表板图表")
if output_dir and not os.path.exists(output_dir):
os.makedirs(output_dir)
# 生成各种图表
charts = {}
# 成绩分布图
if self.target is not None:
output_path = os.path.join(output_dir, 'grade_distribution.png') if output_dir else None
charts['grade_distribution'] = self.plot_grade_distribution(output_path)
# 特征重要性图
if self.model is not None and self.features is not None:
output_path = os.path.join(output_dir, 'feature_importance.png') if output_dir else None
charts['feature_importance'] = self.plot_feature_importance(output_path=output_path)
# 预测值与实际值对比图
if self.predictions is not None and self.target is not None:
output_path = os.path.join(output_dir, 'prediction_vs_actual.png') if output_dir else None
charts['prediction_vs_actual'] = self.plot_prediction_vs_actual(output_path)
# 残差图
if self.predictions is not None and self.target is not None:
output_path = os.path.join(output_dir, 'residuals.png') if output_dir else None
charts['residuals'] = self.plot_residuals(output_path)
# 相关性矩阵热图
if self.data is not None:
output_path = os.path.join(output_dir, 'correlation_matrix.png') if output_dir else None
charts['correlation_matrix'] = self.plot_correlation_matrix(output_path)
logger.info(f"仪表板图表生成完成,共 {len(charts)} 个图表")
return charts
def get_base64_chart(self, fig):
"""
将图表转换为base64编码
参数:
fig: matplotlib图表对象
返回:
str: base64编码的图表
"""
if fig is None:
return None
# 将图表转换为base64编码
buffer = io.BytesIO()
fig.savefig(buffer, format='png')
buffer.seek(0)
image_png = buffer.getvalue()
buffer.close()
# 关闭图表,释放内存
plt.close(fig)
return base64.b64encode(image_png).decode('utf-8')
def get_dashboard_base64(self):
"""
获取仪表板图表的base64编码
返回:
dict: 图表base64编码字典
"""
logger.info("正在获取仪表板图表的base64编码")
# 生成图表
charts = self.plot_dashboard()
# 转换为base64编码
base64_charts = {}
for name, fig in charts.items():
base64_charts[name] = self.get_base64_chart(fig)
return base64_charts
def main():
"""
主函数,用于测试数据可视化模块
"""
# 设置数据路径
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
data_dir = os.path.join(base_dir, 'data')
raw_data_path = os.path.join(data_dir, 'raw', 'student_data.csv')
processed_data_dir = os.path.join(data_dir, 'processed')
model_dir = os.path.join(base_dir, 'model')
vis_dir = os.path.join(base_dir, 'static', 'images')
# 创建可视化目录
if not os.path.exists(vis_dir):
os.makedirs(vis_dir)
# 创建数据可视化器
visualizer = DataVisualizer()
# 加载原始数据
visualizer.load_data(raw_data_path)
# 生成基础图表
visualizer.plot_grade_distribution(os.path.join(vis_dir, 'grade_distribution.png'))
visualizer.plot_feature_correlation(output_path=os.path.join(vis_dir, 'feature_correlation.png'))
visualizer.plot_correlation_matrix(output_path=os.path.join(vis_dir, 'correlation_matrix.png'))
# 尝试加载模型数据(如果存在)
model_file = None
for file in os.listdir(model_dir):
if file.endswith('_model.pkl'):
model_file = os.path.join(model_dir, file)
break
if model_file:
features_file = os.path.join(processed_data_dir, 'processed_features.csv')
target_file = os.path.join(processed_data_dir, 'processed_target.csv')
# 加载模型数据
visualizer.load_model_data(features_file, target_file, model_file)
# 生成模型相关图表
visualizer.plot_feature_importance(output_path=os.path.join(vis_dir, 'feature_importance.png'))
visualizer.plot_prediction_vs_actual(output_path=os.path.join(vis_dir, 'prediction_vs_actual.png'))
visualizer.plot_residuals(output_path=os.path.join(vis_dir, 'residuals.png'))
print("\n数据可视化完成!")
print(f"图表已保存至: {vis_dir}")
if __name__ == "__main__":
main()
templates\base.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}学生成绩预测系统{% endblock %}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<!-- Custom CSS -->
<style>
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
z-index: 100;
padding: 48px 0 0;
box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);
background-color: #f8f9fa;
}
.sidebar-sticky {
position: relative;
top: 0;
height: calc(100vh - 48px);
padding-top: .5rem;
overflow-x: hidden;
overflow-y: auto;
}
.nav-link {
font-weight: 500;
color: #333;
}
.nav-link.active {
color: #007bff;
}
.nav-link:hover {
color: #0056b3;
}
.main-content {
margin-left: 240px;
padding: 20px;
}
@media (max-width: 767.98px) {
.sidebar {
position: static;
padding: 15px;
}
.main-content {
margin-left: 0;
}
}
.flash-messages {
margin-top: 20px;
}
.card {
margin-bottom: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.card-header {
background-color: #f8f9fa;
font-weight: bold;
}
.btn-primary {
background-color: #007bff;
border-color: #007bff;
}
.btn-primary:hover {
background-color: #0069d9;
border-color: #0062cc;
}
.footer {
padding: 20px 0;
margin-top: 30px;
background-color: #f8f9fa;
text-align: center;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- 侧边栏 -->
<nav id="sidebar" class="col-md-3 col-lg-2 d-md-block sidebar">
<div class="sidebar-sticky">
<div class="text-center mb-4">
<h4>学生成绩预测系统</h4>
</div>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link {% if request.path == '/' %}active{% endif %}" href="{{ url_for('index') }}">
<i class="fas fa-home me-2"></i>首页
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/upload' %}active{% endif %}" href="{{ url_for('upload_file') }}">
<i class="fas fa-upload me-2"></i>上传数据
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if '/data_processing/' in request.path %}active{% endif %}" href="{{ url_for('upload_file') }}">
<i class="fas fa-cogs me-2"></i>数据处理
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/feature_engineering' %}active{% endif %}" href="{{ url_for('feature_engineering') }}">
<i class="fas fa-tools me-2"></i>特征工程
</a>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/model_training' %}active{% endif %}" href="{{ url_for('model_training') }}">
<i class="fas fa-brain me-2"></i>模型训练
</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="predictionDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-chart-line me-2"></i>成绩预测
</a>
<ul class="dropdown-menu" aria-labelledby="predictionDropdown">
<li>
<a class="dropdown-item {% if request.path == '/prediction' %}active{% endif %}" href="{{ url_for('prediction') }}">
<i class="fas fa-chart-line me-2"></i>预测概览
</a>
</li>
<li>
<a class="dropdown-item {% if request.path == '/prediction/single' %}active{% endif %}" href="{{ url_for('prediction.predict_single') }}">
<i class="fas fa-user me-2"></i>单条预测
</a>
</li>
<li>
<a class="dropdown-item {% if request.path == '/batch_prediction' %}active{% endif %}" href="{{ url_for('batch_prediction') }}">
<i class="fas fa-tasks me-2"></i>批量预测
</a>
</li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link {% if request.path == '/visualization' %}active{% endif %}" href="{{ url_for('visualization') }}">
<i class="fas fa-chart-bar me-2"></i>可视化分析
</a>
</li>
</ul>
</div>
</nav>
<!-- 主要内容 -->
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4 main-content">
<!-- 闪现消息 -->
<div class="flash-messages">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category if category != 'message' else 'info' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<!-- 页面内容 -->
<div class="content">
{% block content %}{% endblock %}
</div>
</main>
</div>
</div>
<!-- 页脚 -->
<footer class="footer">
<div class="container">
<span class="text-muted"> 2025 学生成绩预测系统 | 基于机器学习的教育数据分析平台</span>
</div>
</footer>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Custom JS -->
{% block extra_js %}{% endblock %}
</body>
</html>
templates\batch_prediction.html
{% extends "base.html" %}
{% block title %}批量预测 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">批量成绩预测</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
上传包含多名学生数据的文件,系统将为所有学生生成预测结果。
</div>
<form action="{{ url_for('prediction.batch_prediction') }}" method="post" enctype="multipart/form-data">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">上传数据文件</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="batch_file" class="form-label">选择文件</label>
<input class="form-control" type="file" id="batch_file" name="batch_file" accept=".csv, .xlsx, .xls">
<div class="form-text">支持的文件格式:CSV, Excel (.xlsx, .xls)</div>
</div>
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>注意:</strong> 上传的文件必须包含与训练数据相同的特征列。文件必须有表头行,且列名必须与训练数据一致。
</div>
<div class="text-center mt-3">
<a href="{{ url_for('prediction.download_template') }}" class="btn btn-outline-secondary">
<i class="fas fa-download me-2"></i>下载数据模板
</a>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">预测选项</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="model_select" class="form-label">选择模型</label>
<select class="form-select" id="model_select" name="model_select">
<option value="best">最佳模型(推荐)</option>
{% for model in available_models %}
<option value="{{ model }}">{{ model }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="output_format" class="form-label">输出格式</label>
<select class="form-select" id="output_format" name="output_format">
<option value="csv">CSV</option>
<option value="excel">Excel</option>
</select>
</div>
</div>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="include_confidence" name="include_confidence" checked>
<label class="form-check-label" for="include_confidence">包含预测置信区间</label>
</div>
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="include_explanations" name="include_explanations" checked>
<label class="form-check-label" for="include_explanations">包含预测解释</label>
</div>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-tasks me-2"></i>开始批量预测
</button>
</div>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-header bg-light">
<h4 class="mb-0">批量预测说明</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h5><i class="fas fa-file-alt me-2 text-primary"></i>文件要求</h5>
<ul>
<li>文件必须是CSV或Excel格式</li>
<li>必须包含表头行,且列名与训练数据一致</li>
<li>每行代表一名学生的数据</li>
<li>不应包含目标列(即学生成绩列)</li>
<li>所有必要特征列都必须存在</li>
</ul>
</div>
<div class="col-md-6">
<h5><i class="fas fa-cogs me-2 text-primary"></i>预测结果</h5>
<ul>
<li>系统将为每名学生生成预测成绩</li>
<li>结果将包含原始数据和预测成绩</li>
<li>可选择包含预测置信区间</li>
<li>可选择包含简要的预测解释</li>
<li>结果可下载为CSV或Excel格式</li>
</ul>
</div>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-lightbulb me-2"></i>
<strong>提示:</strong> 批量预测功能适用于需要同时预测多名学生成绩的场景,如班级期末成绩预测、学年成绩预测等。您可以根据预测结果识别可能需要额外支持的学生,并提前制定干预策略。
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// 文件验证
$('#batch_file').change(function() {
const file = this.files[0];
if (file) {
const fileName = file.name;
const fileExt = fileName.split('.').pop().toLowerCase();
if (!['csv', 'xlsx', 'xls'].includes(fileExt)) {
alert('请上传CSV或Excel格式的文件');
$(this).val('');
}
}
});
// 表单验证
$('form').submit(function(e) {
const file = $('#batch_file')[0].files[0];
if (!file) {
e.preventDefault();
alert('请选择要上传的文件');
}
});
});
</script>
{% endblock %}
templates\data_processing.html
{% extends "base.html" %}
{% block title %}数据处理 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">数据处理 - {{ filename }}</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
请选择目标变量、分类特征和数值特征,以便系统进行适当的数据处理。
</div>
<div class="row mb-4">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-light">
<h5 class="mb-0">数据信息</h5>
</div>
<div class="card-body">
<p><strong>数据形状:</strong> {{ data_info.shape[0] }} 行 × {{ data_info.shape[1] }} 列</p>
<p><strong>数据类型:</strong></p>
<ul class="list-group">
{% for col, dtype in data_info.dtypes.items() %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ col }}
<span class="badge bg-primary rounded-pill">{{ dtype }}</span>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-light">
<h5 class="mb-0">缺失值信息</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>列名</th>
<th>缺失值数量</th>
<th>缺失比例</th>
</tr>
</thead>
<tbody>
{% for col, missing in data_info.missing_values.items() %}
<tr>
<td>{{ col }}</td>
<td>{{ missing }}</td>
<td>{{ (missing / data_info.shape[0] * 100) | round(2) }}%</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">数据预览</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
{% for col in preview_data[0].keys() %}
<th>{{ col }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in preview_data %}
<tr>
{% for col, val in row.items() %}
<td>{{ val }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
<p class="text-muted mt-2">显示前10行数据</p>
</div>
</div>
<form action="{{ url_for('data_processing', filename=filename) }}" method="post">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">特征选择</h5>
</div>
<div class="card-body">
<div class="mb-3">
<label for="target_column" class="form-label"><strong>目标变量</strong> (要预测的成绩列)</label>
<select class="form-select" id="target_column" name="target_column" required>
<option value="" selected disabled>请选择目标变量</option>
{% for col in columns %}
<option value="{{ col }}">{{ col }}</option>
{% endfor %}
</select>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label"><strong>分类特征</strong> (非数值型特征)</label>
<div class="border p-3 rounded" style="max-height: 200px; overflow-y: auto;">
{% for col in columns %}
<div class="form-check">
<input class="form-check-input feature-checkbox" type="checkbox" name="categorical_columns" value="{{ col }}" id="cat_{{ col }}">
<label class="form-check-label" for="cat_{{ col }}">
{{ col }}
</label>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label"><strong>数值特征</strong> (数值型特征)</label>
<div class="border p-3 rounded" style="max-height: 200px; overflow-y: auto;">
{% for col in columns %}
<div class="form-check">
<input class="form-check-input feature-checkbox" type="checkbox" name="numerical_columns" value="{{ col }}" id="num_{{ col }}">
<label class="form-check-label" for="num_{{ col }}">
{{ col }}
</label>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="mb-3">
<label class="form-label"><strong>要删除的列</strong> (不参与模型训练的列)</label>
<div class="border p-3 rounded" style="max-height: 200px; overflow-y: auto;">
{% for col in columns %}
<div class="form-check">
<input class="form-check-input feature-checkbox" type="checkbox" name="drop_columns" value="{{ col }}" id="drop_{{ col }}">
<label class="form-check-label" for="drop_{{ col }}">
{{ col }}
</label>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-cogs me-2"></i>处理数据
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// 目标变量选择后,自动从其他选项中排除
$('#target_column').change(function() {
const targetCol = $(this).val();
// 禁用目标变量在其他选项中的选择
$('.feature-checkbox').each(function() {
if ($(this).val() === targetCol) {
$(this).prop('checked', false);
$(this).prop('disabled', true);
} else {
$(this).prop('disabled', false);
}
});
});
// 根据数据类型自动选择分类和数值特征
const dtypes = JSON.parse('{{ data_info.dtypes | tojson }}');
Object.entries(dtypes).forEach(([col, dtype]) => {
// 如果是数值类型
if (dtype.includes('int') || dtype.includes('float')) {
$(`#num_${col}`).prop('checked', true);
}
// 如果是分类类型
else if (dtype.includes('object') || dtype.includes('category')) {
$(`#cat_${col}`).prop('checked', true);
}
});
// 当选择删除列时,自动取消其他选项
$('input[name="drop_columns"]').change(function() {
const col = $(this).val();
const isChecked = $(this).prop('checked');
if (isChecked) {
// 如果选择删除,取消其他选项
$(`#cat_${col}`).prop('checked', false);
$(`#num_${col}`).prop('checked', false);
// 如果是目标变量,取消选择
if ($('#target_column').val() === col) {
$('#target_column').val('');
}
}
});
// 当选择其他选项时,自动取消删除选项
$('input[name="categorical_columns"], input[name="numerical_columns"]').change(function() {
const col = $(this).val();
const isChecked = $(this).prop('checked');
if (isChecked) {
// 如果选择为特征,取消删除选项
$(`#drop_${col}`).prop('checked', false);
}
});
});
</script>
{% endblock %}
templates\feature_engineering.html
{% extends "base.html" %}
{% block title %}特征工程 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">特征工程</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
特征工程是提高模型性能的关键步骤。在这里,您可以创建新特征、选择重要特征,以增强模型的预测能力。
</div>
<form action="{{ url_for('feature_engineering') }}" method="post">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">交互特征</h5>
</div>
<div class="card-body">
<p>交互特征是两个特征的乘积,可以捕捉特征之间的相互作用。</p>
<div class="row mb-3">
<div class="col-md-10">
<select class="form-select mb-2" id="interaction_feature1">
<option value="" selected disabled>选择第一个特征</option>
{% for col in feature_columns %}
<option value="{{ col }}">{{ col }}</option>
{% endfor %}
</select>
<select class="form-select" id="interaction_feature2">
<option value="" selected disabled>选择第二个特征</option>
{% for col in feature_columns %}
<option value="{{ col }}">{{ col }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<button type="button" class="btn btn-success w-100 h-100" id="add_interaction">
<i class="fas fa-plus"></i> 添加
</button>
</div>
</div>
<div class="border p-3 rounded mb-3" id="interaction_features_container">
<p class="text-muted" id="no_interaction_text">尚未添加交互特征</p>
<ul class="list-group" id="interaction_features_list"></ul>
</div>
<div id="interaction_features_hidden"></div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">多项式特征</h5>
</div>
<div class="card-body">
<p>多项式特征是原始特征的幂,可以捕捉非线性关系。</p>
<div class="border p-3 rounded" style="max-height: 200px; overflow-y: auto;">
{% for col in feature_columns %}
<div class="form-check">
<input class="form-check-input" type="checkbox" name="polynomial_features" value="{{ col }}" id="poly_{{ col }}">
<label class="form-check-label" for="poly_{{ col }}">
{{ col }}
</label>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">分箱特征</h5>
</div>
<div class="card-body">
<p>分箱特征将连续值分成几个区间,可以捕捉非线性关系并减少异常值的影响。</p>
<div class="border p-3 rounded" style="max-height: 200px; overflow-y: auto;">
{% for col in feature_columns %}
<div class="form-check">
<input class="form-check-input" type="checkbox" name="binning_features" value="{{ col }}" id="bin_{{ col }}">
<label class="form-check-label" for="bin_{{ col }}">
{{ col }}
</label>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">特征选择</h5>
</div>
<div class="card-body">
<p>特征选择可以减少模型复杂性,提高性能并防止过拟合。</p>
<div class="mb-3">
<label for="feature_selection" class="form-label">选择方法</label>
<select class="form-select" id="feature_selection" name="feature_selection">
<option value="none">不进行特征选择</option>
<option value="correlation">基于相关性</option>
<option value="mutual_info">基于互信息</option>
</select>
</div>
<div class="mb-3" id="n_features_container" style="display: none;">
<label for="n_features" class="form-label">保留特征数量</label>
<input type="number" class="form-control" id="n_features" name="n_features" min="1" value="10">
</div>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-tools me-2"></i>应用特征工程
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// 特征选择方法改变时显示/隐藏特征数量输入框
$('#feature_selection').change(function() {
if ($(this).val() === 'none') {
$('#n_features_container').hide();
} else {
$('#n_features_container').show();
}
});
// 添加交互特征
$('#add_interaction').click(function() {
const feature1 = $('#interaction_feature1').val();
const feature2 = $('#interaction_feature2').val();
if (feature1 && feature2 && feature1 !== feature2) {
const featurePair = `${feature1} × ${feature2}`;
const featureId = `interaction_${feature1}_${feature2}`;
// 检查是否已存在
if ($(`#${featureId}`).length === 0) {
// 添加到列表
$('#no_interaction_text').hide();
$('#interaction_features_list').append(`
<li class="list-group-item d-flex justify-content-between align-items-center" id="${featureId}">
${featurePair}
<button type="button" class="btn btn-sm btn-danger remove-interaction" data-id="${featureId}">
<i class="fas fa-times"></i>
</button>
</li>
`);
// 添加隐藏输入
$('#interaction_features_hidden').append(`
<input type="hidden" name="interaction_features" value="${feature1}">
<input type="hidden" name="interaction_features" value="${feature2}">
`);
// 清空选择
$('#interaction_feature1').val('');
$('#interaction_feature2').val('');
} else {
alert('该交互特征已添加');
}
} else if (feature1 === feature2) {
alert('请选择两个不同的特征');
} else {
alert('请选择两个特征');
}
});
// 删除交互特征
$(document).on('click', '.remove-interaction', function() {
const id = $(this).data('id');
const parts = id.split('_');
const feature1 = parts[1];
const feature2 = parts[2];
// 移除列表项
$(`#${id}`).remove();
// 移除隐藏输入
$('#interaction_features_hidden input[value="' + feature1 + '"]').remove();
$('#interaction_features_hidden input[value="' + feature2 + '"]').remove();
// 如果没有交互特征,显示提示文本
if ($('#interaction_features_list li').length === 0) {
$('#no_interaction_text').show();
}
});
});
</script>
{% endblock %}
templates\index.html
{% extends "base.html" %}
{% block title %}首页 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h2 class="text-center mb-0">欢迎使用学生成绩预测系统</h2>
</div>
<div class="card-body">
<div class="text-center mb-4">
<i class="fas fa-graduation-cap fa-4x text-primary"></i>
</div>
<p class="lead text-center">
本系统利用机器学习技术,通过分析学生的历史数据来预测学生的学习成绩,
帮助教育工作者提前识别可能需要额外支持的学生,并为个性化教学提供数据支持。
</p>
<div class="row mt-5">
<div class="col-md-4">
<div class="card h-100">
<div class="card-body text-center">
<i class="fas fa-database fa-3x text-primary mb-3"></i>
<h4>数据处理</h4>
<p>上传学生数据,进行清洗、标准化和特征工程,为模型训练做准备。</p>
<a href="{{ url_for('upload_file') }}" class="btn btn-outline-primary mt-3">开始上传</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body text-center">
<i class="fas fa-brain fa-3x text-primary mb-3"></i>
<h4>模型训练</h4>
<p>使用多种机器学习算法构建预测模型,并评估其性能。</p>
<a href="{{ url_for('model_training') }}" class="btn btn-outline-primary mt-3">训练模型</a>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-body text-center">
<i class="fas fa-chart-line fa-3x text-primary mb-3"></i>
<h4>成绩预测</h4>
<p>使用训练好的模型进行单个或批量学生成绩预测。</p>
<a href="{{ url_for('prediction') }}" class="btn btn-outline-primary mt-3">开始预测</a>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12 text-center">
<a href="{{ url_for('upload_file') }}" class="btn btn-primary btn-lg mt-4">
<i class="fas fa-play-circle me-2"></i>开始使用
</a>
</div>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header bg-light">
<h4 class="mb-0">系统功能</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<ul class="list-group">
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>数据收集与预处理
</li>
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>特征工程与选择
</li>
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>多种机器学习模型训练
</li>
</ul>
</div>
<div class="col-md-6">
<ul class="list-group">
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>模型评估与比较
</li>
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>单个和批量成绩预测
</li>
<li class="list-group-item">
<i class="fas fa-check-circle text-success me-2"></i>数据可视化与分析
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
templates\model_training.html
{% extends "base.html" %}
{% block title %}模型训练 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">模型训练</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
选择要训练的机器学习模型和相关参数,系统将自动训练并评估模型性能。
</div>
<form action="{{ url_for('model_training') }}" method="post">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">选择模型</h5>
</div>
<div class="card-body">
<p>选择一个或多个模型进行训练和评估:</p>
<div class="row">
{% for model in available_models %}
<div class="col-md-4 mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="models" value="{{ model }}" id="model_{{ model }}" {% if model == 'LinearRegression' %}checked{% endif %}>
<label class="form-check-label" for="model_{{ model }}">
{{ model }}
</label>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">训练参数</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label for="test_size" class="form-label">测试集比例</label>
<div class="input-group">
<input type="number" class="form-control" id="test_size" name="test_size" min="0.1" max="0.5" step="0.05" value="0.2">
<span class="input-group-text">%</span>
</div>
<div class="form-text">用于测试的数据比例(0.1-0.5)</div>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="random_state" class="form-label">随机种子</label>
<input type="number" class="form-control" id="random_state" name="random_state" min="0" value="42">
<div class="form-text">确保结果可重现的随机数种子</div>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="cv_folds" class="form-label">交叉验证折数</label>
<input type="number" class="form-control" id="cv_folds" name="cv_folds" min="2" max="10" value="5">
<div class="form-text">交叉验证的折数(2-10)</div>
</div>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">高级选项</h5>
</div>
<div class="card-body">
<div class="form-check form-switch mb-3">
<input class="form-check-input" type="checkbox" id="hyperparameter_tuning" checked>
<label class="form-check-label" for="hyperparameter_tuning">启用超参数调优</label>
</div>
<div class="form-text">启用超参数调优可以提高模型性能,但会增加训练时间。</div>
<div id="tuning_options" class="mt-3">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="tuning_method" class="form-label">调优方法</label>
<select class="form-select" id="tuning_method">
<option value="grid">网格搜索</option>
<option value="random" selected>随机搜索</option>
</select>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="tuning_cv" class="form-label">调优交叉验证折数</label>
<input type="number" class="form-control" id="tuning_cv" min="2" max="5" value="3">
</div>
</div>
</div>
</div>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-brain me-2"></i>开始训练模型
</button>
</div>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-header bg-light">
<h4 class="mb-0">模型说明</h4>
</div>
<div class="card-body">
<div class="accordion" id="modelAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingLinear">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLinear" aria-expanded="false" aria-controls="collapseLinear">
线性回归 (Linear Regression)
</button>
</h2>
<div id="collapseLinear" class="accordion-collapse collapse" aria-labelledby="headingLinear" data-bs-parent="#modelAccordion">
<div class="accordion-body">
<p>线性回归是最基本的回归模型,通过建立目标变量与特征之间的线性关系进行预测。</p>
<p><strong>优点:</strong> 简单、易于解释、计算效率高</p>
<p><strong>缺点:</strong> 只能捕捉线性关系,对异常值敏感</p>
<p><strong>适用场景:</strong> 特征与目标变量存在线性关系的简单预测任务</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingRidge">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseRidge" aria-expanded="false" aria-controls="collapseRidge">
岭回归 (Ridge Regression)
</button>
</h2>
<div id="collapseRidge" class="accordion-collapse collapse" aria-labelledby="headingRidge" data-bs-parent="#modelAccordion">
<div class="accordion-body">
<p>岭回归是线性回归的正则化版本,通过L2正则化减少过拟合风险。</p>
<p><strong>优点:</strong> 处理多重共线性问题,减少过拟合</p>
<p><strong>缺点:</strong> 仍然只能捕捉线性关系</p>
<p><strong>适用场景:</strong> 特征之间存在高度相关性的预测任务</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingLasso">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseLasso" aria-expanded="false" aria-controls="collapseLasso">
Lasso回归 (Lasso Regression)
</button>
</h2>
<div id="collapseLasso" class="accordion-collapse collapse" aria-labelledby="headingLasso" data-bs-parent="#modelAccordion">
<div class="accordion-body">
<p>Lasso回归使用L1正则化,可以将不重要特征的系数压缩为零,实现特征选择。</p>
<p><strong>优点:</strong> 自动特征选择,减少过拟合</p>
<p><strong>缺点:</strong> 在特征高度相关时可能表现不稳定</p>
<p><strong>适用场景:</strong> 需要特征选择的高维数据预测任务</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingRF">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseRF" aria-expanded="false" aria-controls="collapseRF">
随机森林 (Random Forest)
</button>
</h2>
<div id="collapseRF" class="accordion-collapse collapse" aria-labelledby="headingRF" data-bs-parent="#modelAccordion">
<div class="accordion-body">
<p>随机森林是一种集成学习方法,通过构建多个决策树并取平均值进行预测。</p>
<p><strong>优点:</strong> 可捕捉非线性关系,不易过拟合,提供特征重要性</p>
<p><strong>缺点:</strong> 计算复杂度高,模型解释性较差</p>
<p><strong>适用场景:</strong> 复杂的非线性预测任务,需要稳定性和准确性</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingGBR">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseGBR" aria-expanded="false" aria-controls="collapseGBR">
梯度提升回归 (Gradient Boosting Regressor)
</button>
</h2>
<div id="collapseGBR" class="accordion-collapse collapse" aria-labelledby="headingGBR" data-bs-parent="#modelAccordion">
<div class="accordion-body">
<p>梯度提升回归是一种集成学习方法,通过顺序构建弱学习器来逐步减少误差。</p>
<p><strong>优点:</strong> 高预测准确性,可捕捉复杂的非线性关系</p>
<p><strong>缺点:</strong> 容易过拟合,对异常值敏感,训练时间长</p>
<p><strong>适用场景:</strong> 需要高预测准确性的复杂预测任务</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingSVR">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseSVR" aria-expanded="false" aria-controls="collapseSVR">
支持向量回归 (Support Vector Regressor)
</button>
</h2>
<div id="collapseSVR" class="accordion-collapse collapse" aria-labelledby="headingSVR" data-bs-parent="#modelAccordion">
<div class="accordion-body">
<p>支持向量回归使用支持向量机的原理进行回归预测,通过核函数可以处理非线性关系。</p>
<p><strong>优点:</strong> 对高维数据有效,可处理非线性关系</p>
<p><strong>缺点:</strong> 对参数敏感,计算复杂度高,不适合大数据集</p>
<p><strong>适用场景:</strong> 中小型复杂数据集的预测任务</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// 超参数调优开关
$('#hyperparameter_tuning').change(function() {
if ($(this).is(':checked')) {
$('#tuning_options').show();
} else {
$('#tuning_options').hide();
}
});
// 至少选择一个模型
$('form').submit(function(e) {
if (!$('input[name="models"]:checked').length) {
e.preventDefault();
alert('请至少选择一个模型进行训练');
}
});
// 测试集比例验证
$('#test_size').change(function() {
const value = parseFloat($(this).val());
if (value < 0.1) {
$(this).val(0.1);
} else if (value > 0.5) {
$(this).val(0.5);
}
});
});
</script>
{% endblock %}
templates\prediction.html
{% extends "base.html" %}
{% block title %}成绩预测 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">学生成绩预测</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
请输入学生的各项特征数据,系统将使用训练好的模型预测学生的成绩。
</div>
<form action="{{ url_for('prediction') }}" method="post">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">学生信息</h5>
</div>
<div class="card-body">
<div class="row">
{% for column in feature_columns %}
<div class="col-md-4 mb-3">
<label for="{{ column }}" class="form-label">{{ column }}</label>
<input type="text" class="form-control" id="{{ column }}" name="{{ column }}" placeholder="输入{{ column }}">
</div>
{% endfor %}
</div>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-chart-line me-2"></i>预测成绩
</button>
</div>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-header bg-light">
<h4 class="mb-0">使用说明</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h5><i class="fas fa-info-circle me-2 text-primary"></i>数据输入指南</h5>
<ul>
<li>数值型特征(如出勤率)应输入具体数值,例如:0.95</li>
<li>分类型特征(如性别)应输入具体类别,例如:M 或 F</li>
<li>所有必填字段都需要填写,否则可能影响预测准确性</li>
<li>输入数据应与训练数据的格式保持一致</li>
</ul>
</div>
<div class="col-md-6">
<h5><i class="fas fa-lightbulb me-2 text-primary"></i>预测结果解读</h5>
<ul>
<li>预测结果是基于历史数据和当前输入的估计值</li>
<li>预测结果仅供参考,实际成绩可能受多种因素影响</li>
<li>系统会显示预测的具体分数</li>
<li>您可以根据预测结果调整教学策略或提供针对性辅导</li>
</ul>
</div>
</div>
<div class="alert alert-warning mt-3">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>注意:</strong> 预测结果的准确性取决于模型训练的质量和输入数据的完整性。请确保输入准确的数据以获得最佳预测效果。
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header bg-light">
<h4 class="mb-0">批量预测</h4>
</div>
<div class="card-body text-center">
<p>需要为多名学生预测成绩?使用批量预测功能。</p>
<a href="{{ url_for('batch_prediction') }}" class="btn btn-outline-primary">
<i class="fas fa-tasks me-2"></i>前往批量预测
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// 表单验证
$('form').submit(function(e) {
let isValid = true;
let emptyFields = [];
// 检查必填字段
$('.form-control').each(function() {
if ($(this).val() === '') {
isValid = false;
emptyFields.push($(this).attr('id'));
$(this).addClass('is-invalid');
} else {
$(this).removeClass('is-invalid');
}
});
if (!isValid) {
e.preventDefault();
alert('请填写所有必填字段: ' + emptyFields.join(', '));
}
});
// 输入框焦点事件
$('.form-control').focus(function() {
$(this).removeClass('is-invalid');
});
// 数值型输入验证
$('.form-control').blur(function() {
const id = $(this).attr('id');
const value = $(this).val();
// 检查是否为数值型字段
if (id.includes('rate') || id.includes('time') || id.includes('score') || id.includes('grade')) {
if (value !== '' && isNaN(value)) {
$(this).addClass('is-invalid');
alert(`${id} 应为数值`);
}
}
});
});
</script>
{% endblock %}
templates\prediction_result.html
{% extends "base.html" %}
{% block title %}预测结果 - 学生成绩预测系统{% endblock %}
{% block content %}
<style>
.custom-progress {
height: 30px;
border-radius: 5px;
margin-bottom: 10px;
}
.custom-progress-bar {
height: 100%;
text-align: center;
line-height: 30px;
color: white;
font-weight: bold;
}
.custom-progress-bar.bg-danger {
background-color: #dc3545;
}
.custom-progress-bar.bg-warning {
background-color: #ffc107;
}
.custom-progress-bar.bg-success {
background-color: #28a745;
}
</style>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">预测结果</h3>
</div>
<div class="card-body">
<div class="text-center mb-4">
<div class="display-4 mb-3">
<i class="fas fa-chart-line text-primary me-2"></i>
预测成绩: <span class="fw-bold">{{ prediction | round(2) }}</span>
</div>
<p class="lead">
使用模型: <span class="badge bg-info">{{ model_name }}</span>
</p>
</div>
<div class="row mb-4">
<div class="col-md-6 mx-auto">
{% set score_percent = (prediction / 100) * 100 %}
{% set score_color = 'bg-danger' if score_percent < 60 else ('bg-warning' if score_percent < 80 else 'bg-success') %}
<!-- Progress bar using custom CSS classes -->
<div class="custom-progress">
<div class="custom-progress-bar {{ score_color }} w-{{ score_percent|int }}" role="progressbar" aria-valuenow="{{ score_percent }}" aria-valuemin="0" aria-valuemax="100">
{{ prediction | round(2) }}
</div>
</div>
<!-- Scale labels -->
<div class="d-flex justify-content-between mt-2">
<small>0</small>
<small>不及格</small>
<small>及格</small>
<small>良好</small>
<small>优秀</small>
<small>100</small>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">输入数据</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>特征</th>
<th>值</th>
</tr>
</thead>
<tbody>
{% for feature, value in input_data.items() %}
<tr>
<td>{{ feature }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">结果解读</h5>
</div>
<div class="card-body">
{% set score_level = '不及格' if prediction < 60 else ('及格' if prediction < 70 else ('良好' if prediction < 85 else '优秀')) %}
<p>根据输入的学生数据,预测该学生的成绩为 <strong>{{ prediction | round(2) }}</strong>,属于<strong>{{ score_level }}</strong>水平。</p>
{% if score_level == '不及格' %}
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle me-2"></i>
<strong>注意:</strong> 该学生可能需要额外的学习支持和辅导。建议关注以下方面:
<ul>
<li>增加学习时间和提高学习效率</li>
<li>提高课堂出勤率和参与度</li>
<li>加强作业完成情况的监督</li>
<li>提供个性化的辅导和支持</li>
</ul>
</div>
{% elif score_level == '及格' %}
<div class="alert alert-warning">
<i class="fas fa-info-circle me-2"></i>
<strong>建议:</strong> 该学生成绩达到及格水平,但仍有提升空间。建议:
<ul>
<li>巩固基础知识,查漏补缺</li>
<li>适当增加学习时间</li>
<li>提高课堂参与度</li>
<li>加强薄弱环节的练习</li>
</ul>
</div>
{% elif score_level == '良好' %}
<div class="alert alert-info">
<i class="fas fa-thumbs-up me-2"></i>
<strong>良好:</strong> 该学生成绩良好,具有较好的学习能力。建议:
<ul>
<li>保持良好的学习习惯</li>
<li>挑战更高难度的学习内容</li>
<li>培养独立思考和创新能力</li>
<li>参与更多拓展性学习活动</li>
</ul>
</div>
{% else %}
<div class="alert alert-success">
<i class="fas fa-award me-2"></i>
<strong>优秀:</strong> 该学生成绩优秀,具有很强的学习能力。建议:
<ul>
<li>保持优秀的学习状态</li>
<li>拓展学习广度和深度</li>
<li>参与竞赛和高级课程</li>
<li>发展领导力和协作能力</li>
</ul>
</div>
{% endif %}
</div>
</div>
<div class="text-center">
<a href="{{ url_for('prediction') }}" class="btn btn-primary me-2">
<i class="fas fa-redo me-2"></i>重新预测
</a>
<a href="{{ url_for('visualization') }}" class="btn btn-outline-primary">
<i class="fas fa-chart-bar me-2"></i>查看可视化分析
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
templates\single_prediction.html
{% extends "base.html" %}
{% block title %}单条预测 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">单条成绩预测</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
输入单个学生的数据,系统将立即生成预测结果。
</div>
<form id="prediction-form" action="{{ url_for('prediction.predict_single') }}" method="post">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">学生信息</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="student_id" class="form-label">学生ID</label>
<input type="text" class="form-control" id="student_id" name="student_id" placeholder="可选">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="student_name" class="form-label">学生姓名</label>
<input type="text" class="form-control" id="student_name" name="student_name" placeholder="可选">
</div>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">学习数据</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="attendance_rate" class="form-label">出勤率 (0-1)</label>
<input type="number" step="0.01" min="0" max="1" class="form-control" id="attendance_rate" name="attendance_rate" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="homework_completion" class="form-label">作业完成率 (0-1)</label>
<input type="number" step="0.01" min="0" max="1" class="form-control" id="homework_completion" name="homework_completion" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="class_participation" class="form-label">课堂参与度 (0-1)</label>
<input type="number" step="0.01" min="0" max="1" class="form-control" id="class_participation" name="class_participation" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="study_hours_per_week" class="form-label">每周学习时间 (小时)</label>
<input type="number" step="0.5" min="0" max="100" class="form-control" id="study_hours_per_week" name="study_hours_per_week" required>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="previous_exam_score" class="form-label">上次考试成绩 (0-100)</label>
<input type="number" step="0.1" min="0" max="100" class="form-control" id="previous_exam_score" name="previous_exam_score" required>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="difficulty_level" class="form-label">课程难度 (1-5)</label>
<select class="form-select" id="difficulty_level" name="difficulty_level">
<option value="1">1 - 非常简单</option>
<option value="2">2 - 简单</option>
<option value="3" selected>3 - 中等</option>
<option value="4">4 - 困难</option>
<option value="5">5 - 非常困难</option>
</select>
</div>
</div>
</div>
</div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-calculator me-2"></i>预测成绩
</button>
</div>
</form>
</div>
</div>
<!-- 预测结果卡片,初始隐藏 -->
<div id="prediction-result" class="card mt-4" style="display: none;">
<div class="card-header bg-success text-white">
<h4 class="mb-0">预测结果</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="text-center mb-4">
<h2 class="display-4 fw-bold" id="predicted-score">--</h2>
<p class="text-muted">预测分数</p>
</div>
</div>
<div class="col-md-6">
<div class="text-center mb-4">
<h5 class="mb-2">置信区间</h5>
<p class="lead" id="confidence-interval">-- 至 --</p>
</div>
</div>
</div>
<div class="alert" id="prediction-explanation">
<i class="fas fa-lightbulb me-2"></i>
<span id="explanation-text">根据预测结果,该学生需要...</span>
</div>
<div class="mt-3">
<h5>关键影响因素</h5>
<ul id="key-factors" class="list-group">
<!-- 动态填充 -->
</ul>
</div>
<div class="text-center mt-4">
<button type="button" class="btn btn-outline-primary" onclick="resetForm()">
<i class="fas fa-redo me-2"></i>重新预测
</button>
<button type="button" class="btn btn-outline-success" onclick="printResult()">
<i class="fas fa-print me-2"></i>打印结果
</button>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header bg-light">
<h4 class="mb-0">单条预测说明</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<h5><i class="fas fa-info-circle me-2 text-primary"></i>如何使用</h5>
<ul>
<li>填写学生的各项学习数据</li>
<li>点击"预测成绩"按钮获取预测结果</li>
<li>查看预测分数、置信区间和解释</li>
<li>可以打印结果或重新预测</li>
</ul>
</div>
<div class="col-md-6">
<h5><i class="fas fa-lightbulb me-2 text-primary"></i>数据说明</h5>
<ul>
<li><strong>出勤率</strong>:学生的课堂出勤比例,范围0-1</li>
<li><strong>作业完成率</strong>:学生完成作业的比例,范围0-1</li>
<li><strong>课堂参与度</strong>:学生在课堂上的参与程度,范围0-1</li>
<li><strong>每周学习时间</strong>:学生每周用于学习的小时数</li>
<li><strong>上次考试成绩</strong>:学生在上次考试中的得分,范围0-100</li>
</ul>
</div>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-lightbulb me-2"></i>
<strong>提示:</strong> 单条预测功能适用于快速评估单个学生的预期表现,可以帮助教师与学生进行一对一交流,讨论学习进展和改进方向。
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// 表单提交处理
$('#prediction-form').submit(function(e) {
e.preventDefault();
// 获取表单数据
const formData = {};
$(this).serializeArray().forEach(function(item) {
formData[item.name] = item.value;
});
// 发送AJAX请求
$.ajax({
url: "{{ url_for('prediction.predict') }}",
type: "POST",
contentType: "application/json",
data: JSON.stringify(formData),
success: function(response) {
// 显示预测结果
$('#predicted-score').text(parseFloat(response.prediction).toFixed(2));
// 计算置信区间(使用模型的RMSE)
const rmse = 5.0; // 默认值,实际应从模型元数据获取
const lowerBound = Math.max(0, parseFloat(response.prediction) - 1.96 * rmse).toFixed(2);
const upperBound = Math.min(100, parseFloat(response.prediction) + 1.96 * rmse).toFixed(2);
$('#confidence-interval').text(`${lowerBound} 至 ${upperBound}`);
// 设置解释文本和样式
const score = parseFloat(response.prediction);
let explanation = "";
let alertClass = "";
if (score < 60) {
explanation = "根据预测结果,该学生需要额外辅导和关注,建议制定个性化学习计划。";
alertClass = "alert-danger";
} else if (score < 70) {
explanation = "该学生需要巩固基础知识,建议增加练习和复习时间。";
alertClass = "alert-warning";
} else if (score < 85) {
explanation = "该学生表现良好,可以适当提高学习难度和深度。";
alertClass = "alert-info";
} else {
explanation = "该学生表现优秀,建议提供更具挑战性的学习内容和项目。";
alertClass = "alert-success";
}
$('#prediction-explanation').removeClass("alert-danger alert-warning alert-info alert-success").addClass(alertClass);
$('#explanation-text').text(explanation);
// 生成关键影响因素
generateKeyFactors(formData, score);
// 显示结果卡片
$('#prediction-result').fadeIn();
// 滚动到结果区域
$('html, body').animate({
scrollTop: $("#prediction-result").offset().top - 20
}, 500);
},
error: function(error) {
alert("预测失败:" + (error.responseJSON ? error.responseJSON.error : "未知错误"));
}
});
});
});
// 生成关键影响因素
function generateKeyFactors(formData, predictedScore) {
const keyFactors = $('#key-factors');
keyFactors.empty();
// 分析输入数据,找出关键因素
const factors = [
{
name: "出勤率",
value: parseFloat(formData.attendance_rate),
threshold: 0.8,
message: formData.attendance_rate < 0.8 ?
"出勤率较低,建议提高课堂出勤" :
"出勤率良好,继续保持"
},
{
name: "作业完成率",
value: parseFloat(formData.homework_completion),
threshold: 0.85,
message: formData.homework_completion < 0.85 ?
"作业完成率不足,需要更加重视课后作业" :
"作业完成情况良好,继续保持"
},
{
name: "课堂参与度",
value: parseFloat(formData.class_participation),
threshold: 0.7,
message: formData.class_participation < 0.7 ?
"课堂参与度不足,建议积极回答问题和参与讨论" :
"课堂参与度良好,继续保持"
},
{
name: "每周学习时间",
value: parseFloat(formData.study_hours_per_week),
threshold: 10,
message: formData.study_hours_per_week < 10 ?
"学习时间不足,建议增加每周学习时间" :
"学习时间充足,注意提高学习效率"
},
{
name: "上次考试成绩",
value: parseFloat(formData.previous_exam_score),
threshold: 70,
message: formData.previous_exam_score < 70 ?
"上次考试成绩不理想,需要查漏补缺" :
"上次考试表现良好,继续保持"
}
];
// 排序因素(低于阈值的排在前面)
factors.sort((a, b) => {
const aBelow = a.value < a.threshold;
const bBelow = b.value < b.threshold;
if (aBelow && !bBelow) return -1;
if (!aBelow && bBelow) return 1;
// 如果都低于或都高于阈值,按值排序
return a.value - b.value;
});
// 添加到列表
factors.forEach(factor => {
const listItem = $('<li class="list-group-item d-flex justify-content-between align-items-center"></li>');
// 设置样式
if (factor.value < factor.threshold) {
listItem.addClass('list-group-item-warning');
}
// 添加内容
listItem.html(`
<div>
<strong>${factor.name}:</strong> ${factor.value}
<div class="small text-muted">${factor.message}</div>
</div>
<span class="badge ${factor.value < factor.threshold ? 'bg-warning' : 'bg-success'} rounded-pill">
${factor.value < factor.threshold ? '需改进' : '良好'}
</span>
`);
keyFactors.append(listItem);
});
}
// 重置表单
function resetForm() {
$('#prediction-form')[0].reset();
$('#prediction-result').hide();
}
// 打印结果
function printResult() {
const printContent = `
<html>
<head>
<title>学生成绩预测结果</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.header { text-align: center; margin-bottom: 30px; }
.result { font-size: 24px; text-align: center; margin: 20px 0; }
.info { margin-bottom: 20px; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { padding: 10px; border: 1px solid #ddd; text-align: left; }
th { background-color: #f2f2f2; }
.footer { margin-top: 50px; text-align: center; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="header">
<h1>学生成绩预测结果</h1>
<p>生成时间: ${new Date().toLocaleString()}</p>
</div>
<div class="info">
<p><strong>学生ID:</strong> ${$('#student_id').val() || '未提供'}</p>
<p><strong>学生姓名:</strong> ${$('#student_name').val() || '未提供'}</p>
</div>
<div class="result">
<p>预测分数: <strong>${$('#predicted-score').text()}</strong></p>
<p>置信区间: ${$('#confidence-interval').text()}</p>
</div>
<p><strong>预测解释:</strong> ${$('#explanation-text').text()}</p>
<h3>学生数据</h3>
<table>
<tr>
<th>指标</th>
<th>值</th>
</tr>
<tr>
<td>出勤率</td>
<td>${$('#attendance_rate').val()}</td>
</tr>
<tr>
<td>作业完成率</td>
<td>${$('#homework_completion').val()}</td>
</tr>
<tr>
<td>课堂参与度</td>
<td>${$('#class_participation').val()}</td>
</tr>
<tr>
<td>每周学习时间</td>
<td>${$('#study_hours_per_week').val()} 小时</td>
</tr>
<tr>
<td>上次考试成绩</td>
<td>${$('#previous_exam_score').val()}</td>
</tr>
<tr>
<td>课程难度</td>
<td>${$('#difficulty_level option:selected').text()}</td>
</tr>
</table>
<h3>改进建议</h3>
<ul>
${$('#key-factors li').map(function() {
return '<li>' + $(this).find('div').text().trim() + '</li>';
}).get().join('')}
</ul>
<div class="footer">
<p>此预测结果由学生成绩预测系统生成,仅供参考。</p>
</div>
</body>
</html>
`;
const printWindow = window.open('', '_blank');
printWindow.document.write(printContent);
printWindow.document.close();
printWindow.focus();
// 延迟打印,确保内容加载完成
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 500);
}
</script>
{% endblock %}
templates\upload.html
{% extends "base.html" %}
{% block title %}上传数据 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">上传学生数据</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
请上传包含学生信息和成绩数据的CSV或Excel文件。文件应包含学生的各项特征(如出勤率、作业完成情况等)和目标成绩列。
</div>
<form action="{{ url_for('upload_file') }}" method="post" enctype="multipart/form-data" class="mt-4">
<div class="mb-4 text-center">
<div class="upload-area p-5 border rounded" id="drop-area">
<i class="fas fa-cloud-upload-alt fa-4x text-primary mb-3"></i>
<h4>拖放文件到这里或点击选择文件</h4>
<p class="text-muted">支持的文件格式: .csv, .xlsx, .xls</p>
<input type="file" name="file" id="file-input" class="d-none" accept=".csv,.xlsx,.xls">
<button type="button" class="btn btn-outline-primary mt-3" id="browse-btn">
<i class="fas fa-folder-open me-2"></i>浏览文件
</button>
</div>
<div id="file-name" class="mt-3 fw-bold"></div>
</div>
<div class="text-center">
<button type="submit" class="btn btn-primary btn-lg" id="upload-btn" disabled>
<i class="fas fa-upload me-2"></i>上传文件
</button>
</div>
</form>
</div>
</div>
<div class="card mt-4">
<div class="card-header bg-light">
<h4 class="mb-0">数据要求</h4>
</div>
<div class="card-body">
<p>为了获得最佳预测效果,上传的数据应包含以下信息:</p>
<div class="row">
<div class="col-md-6">
<h5><i class="fas fa-user me-2 text-primary"></i>学生基本信息</h5>
<ul>
<li>学生ID</li>
<li>性别</li>
<li>年龄</li>
<li>年级/班级</li>
<li>家庭背景信息(可选)</li>
</ul>
</div>
<div class="col-md-6">
<h5><i class="fas fa-book me-2 text-primary"></i>学习相关指标</h5>
<ul>
<li>出勤率</li>
<li>作业完成情况</li>
<li>课堂参与度</li>
<li>以往考试成绩</li>
<li>学习时间</li>
<li>目标成绩(用于训练)</li>
</ul>
</div>
</div>
<div class="alert alert-warning mt-3">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>注意:</strong> 请确保数据中不包含敏感的个人身份信息,以保护学生隐私。
</div>
<div class="mt-3">
<h5><i class="fas fa-file-csv me-2 text-primary"></i>示例数据格式</h5>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead class="table-light">
<tr>
<th>student_id</th>
<th>gender</th>
<th>age</th>
<th>attendance_rate</th>
<th>study_time</th>
<th>assignment_completion_rate</th>
<th>previous_grade</th>
<th>final_grade</th>
</tr>
</thead>
<tbody>
<tr>
<td>1001</td>
<td>M</td>
<td>15</td>
<td>0.95</td>
<td>3.5</td>
<td>0.92</td>
<td>85</td>
<td>88</td>
</tr>
<tr>
<td>1002</td>
<td>F</td>
<td>16</td>
<td>0.88</td>
<td>2.8</td>
<td>0.85</td>
<td>78</td>
<td>80</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
$(document).ready(function() {
// 点击浏览按钮触发文件选择
$('#browse-btn').click(function() {
$('#file-input').click();
});
// 文件选择后显示文件名
$('#file-input').change(function() {
const file = this.files[0];
if (file) {
$('#file-name').text('已选择: ' + file.name);
$('#upload-btn').prop('disabled', false);
} else {
$('#file-name').text('');
$('#upload-btn').prop('disabled', true);
}
});
// 拖放文件功能
const dropArea = document.getElementById('drop-area');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropArea.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropArea.classList.add('border-primary');
}
function unhighlight() {
dropArea.classList.remove('border-primary');
}
dropArea.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
const file = files[0];
const fileInput = document.getElementById('file-input');
// 检查文件类型
const validExtensions = ['.csv', '.xlsx', '.xls'];
const fileName = file.name;
const fileExtension = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();
if (validExtensions.includes(fileExtension)) {
// 将拖放的文件分配给文件输入
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
fileInput.files = dataTransfer.files;
// 触发change事件
const event = new Event('change');
fileInput.dispatchEvent(event);
} else {
alert('不支持的文件类型。请上传CSV或Excel文件。');
}
}
}
});
</script>
{% endblock %}
templates\visualization.html
{% extends "base.html" %}
{% block title %}可视化分析 - 学生成绩预测系统{% endblock %}
{% block content %}
<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header bg-primary text-white">
<h3 class="mb-0">数据可视化分析</h3>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
以下是基于学生数据和模型训练结果的可视化分析,帮助您更好地理解学生成绩分布和影响因素。
</div>
<ul class="nav nav-tabs mb-4" id="visualizationTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="overview-tab" data-bs-toggle="tab" data-bs-target="#overview" type="button" role="tab" aria-controls="overview" aria-selected="true">
<i class="fas fa-chart-pie me-2"></i>总览
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="distribution-tab" data-bs-toggle="tab" data-bs-target="#distribution" type="button" role="tab" aria-controls="distribution" aria-selected="false">
<i class="fas fa-chart-area me-2"></i>成绩分布
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="features-tab" data-bs-toggle="tab" data-bs-target="#features" type="button" role="tab" aria-controls="features" aria-selected="false">
<i class="fas fa-chart-bar me-2"></i>特征分析
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="model-tab" data-bs-toggle="tab" data-bs-target="#model" type="button" role="tab" aria-controls="model" aria-selected="false">
<i class="fas fa-brain me-2"></i>模型评估
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="correlation-tab" data-bs-toggle="tab" data-bs-target="#correlation" type="button" role="tab" aria-controls="correlation" aria-selected="false">
<i class="fas fa-project-diagram me-2"></i>相关性分析
</button>
</li>
</ul>
<div class="tab-content" id="visualizationTabContent">
<!-- 总览 -->
<div class="tab-pane fade show active" id="overview" role="tabpanel" aria-labelledby="overview-tab">
<div class="row">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-light">
<h5 class="mb-0">成绩分布概览</h5>
</div>
<div class="card-body text-center">
<img src="{{ image_paths.grade_distribution }}" alt="成绩分布图" class="img-fluid">
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header bg-light">
<h5 class="mb-0">特征重要性</h5>
</div>
<div class="card-body text-center">
<img src="{{ image_paths.feature_importance }}" alt="特征重要性图" class="img-fluid">
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">模型评估概览</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead class="table-primary">
<tr>
<th>模型</th>
<th>RMSE</th>
<th>MAE</th>
<th>R²</th>
</tr>
</thead>
<tbody>
{% for model_name, metrics in evaluation_results.items() %}
<tr>
<td>{{ model_name }}</td>
<td>{{ metrics.rmse | round(3) }}</td>
<td>{{ metrics.mae | round(3) }}</td>
<td>{{ metrics.r2 | round(3) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- 成绩分布 -->
<div class="tab-pane fade" id="distribution" role="tabpanel" aria-labelledby="distribution-tab">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">学生成绩分布</h5>
</div>
<div class="card-body text-center">
<img src="{{ image_paths.grade_distribution }}" alt="成绩分布图" class="img-fluid">
<div class="mt-3">
<p>该图展示了学生成绩的分布情况,包括平均分、中位数和标准差等统计信息。通过分析成绩分布,可以了解学生整体学习情况和成绩差异。</p>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">预测值与实际值对比</h5>
</div>
<div class="card-body text-center">
<img src="{{ image_paths.prediction_vs_actual }}" alt="预测值与实际值对比图" class="img-fluid">
<div class="mt-3">
<p>该图对比了模型预测的成绩与学生实际成绩,红色虚线表示完美预测线。点越接近该线,表示预测越准确。通过该图可以直观评估模型的预测准确性。</p>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">残差分析</h5>
</div>
<div class="card-body text-center">
<img src="{{ image_paths.residuals }}" alt="残差图" class="img-fluid">
<div class="mt-3">
<p>残差是实际值与预测值之间的差异。上图显示了残差与预测值的关系,下图显示了残差的分布情况。理想情况下,残差应该随机分布在零附近,且呈正态分布。</p>
</div>
</div>
</div>
</div>
<!-- 特征分析 -->
<div class="tab-pane fade" id="features" role="tabpanel" aria-labelledby="features-tab">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">特征重要性分析</h5>
</div>
<div class="card-body text-center">
<img src="{{ image_paths.feature_importance }}" alt="特征重要性图" class="img-fluid">
<div class="mt-3">
<p>该图展示了各个特征对预测结果的重要性排名。特征重要性越高,表示该特征对预测学生成绩的影响越大。通过分析特征重要性,可以了解哪些因素对学生成绩影响最大。</p>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">特征与目标变量的相关性</h5>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-lightbulb me-2"></i>
<strong>解读:</strong> 根据特征重要性分析,以下因素对学生成绩影响较大:
<ul>
<li>以往考试成绩:过去的学习表现是预测未来成绩的重要指标</li>
<li>学习时间:投入更多学习时间通常与更好的成绩相关</li>
<li>出勤率:定期参加课堂对学习效果有积极影响</li>
<li>作业完成情况:按时完成作业有助于巩固知识</li>
</ul>
教师可以根据这些关键因素,有针对性地调整教学策略和提供学生支持。
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 模型评估 -->
<div class="tab-pane fade" id="model" role="tabpanel" aria-labelledby="model-tab">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">模型性能对比</h5>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead class="table-primary">
<tr>
<th>模型</th>
<th>RMSE</th>
<th>MAE</th>
<th>R²</th>
<th>训练时间(秒)</th>
</tr>
</thead>
<tbody>
{% for model_name, metrics in evaluation_results.items() %}
<tr>
<td>{{ model_name }}</td>
<td>{{ metrics.rmse | round(3) }}</td>
<td>{{ metrics.mae | round(3) }}</td>
<td>{{ metrics.r2 | round(3) }}</td>
<td>{{ metrics.train_time | default(0) | round(3) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="mt-3">
<p><strong>评估指标说明:</strong></p>
<ul>
<li><strong>RMSE (均方根误差):</strong> 预测值与实际值差异的平方根,越小越好</li>
<li><strong>MAE (平均绝对误差):</strong> 预测值与实际值绝对差异的平均值,越小越好</li>
<li><strong>R² (决定系数):</strong> 模型解释的方差比例,越接近1越好</li>
</ul>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">预测值与实际值对比</h5>
</div>
<div class="card-body text-center">
<img src="{{ image_paths.prediction_vs_actual }}" alt="预测值与实际值对比图" class="img-fluid">
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">模型分析结论</h5>
</div>
<div class="card-body">
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
<strong>最佳模型:</strong>
{% set best_model = None %}
{% set best_r2 = -1 %}
{% for model_name, metrics in evaluation_results.items() %}
{% if metrics.r2 > best_r2 %}
{% set best_model = model_name %}
{% set best_r2 = metrics.r2 %}
{% endif %}
{% endfor %}
{{ best_model }} (R² = {{ best_r2 | round(3) }})
</div>
<p>根据模型评估结果,我们可以得出以下结论:</p>
<ul>
<li>模型能够较好地预测学生成绩,R²值表明模型能解释大部分成绩变异</li>
<li>预测误差在可接受范围内,可以为教学决策提供有价值的参考</li>
<li>模型性能可能通过进一步的特征工程和参数调优得到提升</li>
</ul>
<p><strong>应用建议:</strong></p>
<ul>
<li>利用模型预测识别可能需要额外支持的学生</li>
<li>根据特征重要性,有针对性地调整教学策略</li>
<li>定期更新模型,以适应新的学生数据和教学环境</li>
</ul>
</div>
</div>
</div>
<!-- 相关性分析 -->
<div class="tab-pane fade" id="correlation" role="tabpanel" aria-labelledby="correlation-tab">
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">特征相关性矩阵</h5>
</div>
<div class="card-body text-center">
<img src="{{ image_paths.correlation_matrix }}" alt="相关性矩阵热图" class="img-fluid">
<div class="mt-3">
<p>相关性矩阵热图展示了各特征之间的相关关系。颜色越深表示相关性越强,红色表示正相关,蓝色表示负相关。通过分析特征间的相关性,可以了解哪些特征之间存在关联,有助于特征选择和模型解释。</p>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header bg-light">
<h5 class="mb-0">关键发现</h5>
</div>
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-lightbulb me-2"></i>
<strong>相关性分析发现:</strong>
<ul>
<li>学习时间与成绩呈正相关,表明投入更多学习时间通常会获得更好的成绩</li>
<li>出勤率与成绩呈正相关,说明定期参加课堂对学习效果有积极影响</li>
<li>以往考试成绩与最终成绩高度相关,是预测未来表现的重要指标</li>
<li>作业完成情况与成绩呈正相关,表明按时完成作业有助于巩固知识</li>
</ul>
</div>
<p><strong>教学建议:</strong></p>
<ol>
<li>鼓励学生增加有效学习时间,提供时间管理指导</li>
<li>强调课堂出勤的重要性,提高课堂教学质量和吸引力</li>
<li>关注学生的阶段性考试表现,对表现下滑的学生及时干预</li>
<li>设计有意义的作业,并确保学生按时完成</li>
<li>为学习困难的学生提供个性化支持和辅导</li>
</ol>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}