最近在辅导金融专业同学完成《数据科学与人工智能导论》期末实践报告时,发现很多同学对如何将理论知识转化为一个结构完整、有深度的实践项目感到迷茫。报告往往停留在概念罗列,缺乏从数据获取、处理、建模到业务解读的完整闭环,更难以体现金融领域的专业洞察。
本文将以“2025秋季金融班”的期末实践为背景,完整拆解一份高质量实践报告的构建过程。我们将围绕一个具体的金融分析场景——上市公司财务风险预警,从项目设计、技术实现到报告撰写,提供一套可复用的“实操模板”。无论你是零基础的金融学生,还是希望将AI应用于金融领域的开发者,都能通过本文掌握从0到1构建数据科学项目的核心能力。
1. 项目背景与核心目标设计
一份优秀的实践报告,始于一个清晰、具体且可落地的项目目标。避免空泛地谈论“人工智能在金融中的应用”,而应聚焦于解决一个具体的业务问题。
1.1 为什么选择“上市公司财务风险预警”?
对于金融专业的学生而言,这个选题具有多重优势:
- 业务关联性强:直接契合公司金融、风险管理等核心课程知识。
- 数据公开可得:上市公司财务数据(如资产负债表、利润表、现金流量表)可通过公开渠道(如Tushare、AKShare、Wind等)稳定获取,避免了数据爬取的合规风险。
- 问题定义清晰:风险预警是一个典型的二分类问题(是否ST/*ST),非常适合初学者理解机器学习任务。
- 价值可解释:模型结果可以直接关联到具体的财务指标(如资产负债率、净利润增长率),便于进行业务解读。
1.2 项目核心目标拆解
我们的项目目标不应仅仅是“构建一个预测模型”,而应是一个完整的分析流程:
- 主要目标:利用过去三年的上市公司财务数据,构建一个分类模型,预测上市公司在未来一年内陷入财务困境(被标记为ST)的可能性。
- 衍生分析目标:
- 特征工程:探究哪些财务指标对财务风险最为敏感。
- 模型对比:比较逻辑回归、决策树、随机森林等经典模型在该任务上的性能。
- 业务解读:将模型结果转化为业务语言,例如“高流动负债占比和连续净利润下滑是主要风险信号”。
1.3 技术栈选型说明
基于项目目标和学生群体的技术基础,我们选择以下稳定、易上手的技术栈:
- 编程语言:Python。因其在数据科学领域的绝对主导地位,拥有丰富的库和社区支持。
- 核心工具库:
pandas,numpy: 进行数据清洗、转换和计算。matplotlib,seaborn: 进行数据可视化,生成统计图表。scikit-learn: 提供完整的机器学习算法实现、评估工具和预处理模块。
- 开发环境:Jupyter Notebook 或 VS Code。Jupyter适合分步演示和分析,VS Code适合工程化开发,报告撰写推荐使用Jupyter以便将代码、输出和文字叙述结合。
2. 环境准备与数据获取
“工欲善其事,必先利其器”。一个可复现的环境是项目成功的基石。
2.1 创建独立的Python环境
强烈建议使用conda或venv创建独立环境,避免包版本冲突。
# 使用 conda 创建环境 conda create -n finance_risk python=3.9 conda activate finance_risk # 安装核心依赖 pip install pandas numpy matplotlib seaborn scikit-learn jupyter2.2 获取上市公司财务与ST数据
我们使用akshare作为数据源,它是一个免费、开源的金融数据接口库。
# 文件:data_acquisition.py import akshare as ak import pandas as pd # 1. 获取上市公司基本信息 stock_info = ak.stock_info_a_code_name() print(f"获取到A股上市公司数量:{len(stock_info)}") # 2. 获取特定财务指标,这里以资产负债表为例 # 以“贵州茅台”为例,获取其最新年报的资产负债表 # 实际项目中,需要循环获取所有公司的数据 balance_sheet = ak.stock_financial_report_sina(stock="sh600519", symbol="资产负债表") print(balance_sheet.head()) # 3. 获取ST/*ST股票列表(关键!) # 注意:实际ST名单是动态的,这里获取当前状态。历史ST数据需要更复杂的处理或专用数据库。 st_list = ak.stock_info_a_code_name() # 此接口不直接提供ST标记,需要从其他维度获取或模拟 # 模拟:我们通常需要自己构建标签。一种方法是利用“是否ST”作为字段,或从定期报告中判断。 # 本例中,我们将从本地CSV文件加载一个模拟的数据集,以便后续演示。重要提示:完整获取所有A股历史财务数据和准确的ST标记是一个复杂的工程问题,涉及数据清洗、时间对齐和标签定义(财务困境发生前一年的数据作为特征,发生当年标记为1)。为了教学演示,我们准备了一个简化版的模拟数据集。
2.3 加载与查看模拟数据集
我们将使用一个预先准备好的、包含特征和标签的company_finance_risk.csv文件。
# 文件:load_data.py import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # 加载数据 df = pd.read_csv('./data/company_finance_risk.csv') # 假设数据文件在此路径 print("数据集形状:", df.shape) print("\n前5行数据:") print(df.head()) print("\n数据基本信息:") print(df.info()) print("\n标签分布 (1:ST, 0:非ST):") print(df['is_st'].value_counts()) print(df['is_st'].value_counts(normalize=True)) # 查看比例3. 数据探索性分析与预处理
数据决定了模型的上限,而预处理和特征工程决定了模型能逼近这个上限的程度。
3.1 探索性数据分析
在建模前,必须理解数据。
# 文件:eda.py # 1. 描述性统计 print(df.describe()) # 2. 检查缺失值 print("\n缺失值统计:") print(df.isnull().sum()) # 3. 可视化特征分布 # 绘制部分关键特征的分布直方图 fig, axes = plt.subplots(2, 3, figsize=(15, 10)) features_to_plot = ['debt_to_assets', 'current_ratio', 'roe', 'net_profit_margin', 'operating_cash_flow', 'inventory_turnover'] for idx, feature in enumerate(features_to_plot): ax = axes[idx//3, idx%3] df[feature].hist(bins=30, ax=ax) ax.set_title(f'{feature} 分布') ax.set_xlabel(feature) ax.set_ylabel('频数') plt.tight_layout() plt.show() # 4. 查看特征与标签的相关性 correlation_matrix = df.corr() plt.figure(figsize=(12, 8)) sns.heatmap(correlation_matrix, annot=True, fmt='.2f', cmap='coolwarm', center=0) plt.title('特征相关性热力图') plt.show() # 重点关注与‘is_st’相关性高的特征 print("与‘is_st’标签相关性最高的前10个特征:") print(correlation_matrix['is_st'].abs().sort_values(ascending=False).head(10))3.2 数据预处理
清洗数据,为模型输入做准备。
# 文件:data_preprocessing.py from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler import numpy as np # 1. 处理缺失值(假设我们的模拟数据已清洗,此处演示通用方法) # 对于数值型特征,常用中位数或均值填充 # df.fillna(df.median(), inplace=True) # 2. 分离特征 (X) 和标签 (y) X = df.drop('is_st', axis=1) # 特征 y = df['is_st'] # 标签 # 3. 处理类别不平衡(ST公司通常远少于非ST公司) # 查看不平衡比例 print(f"非ST样本数: {sum(y==0)}, ST样本数: {sum(y==1)}") print(f"ST样本占比: {sum(y==1)/len(y):.2%}") # 使用过采样(如SMOTE)或欠采样来缓解不平衡。这里使用随机欠采样演示(仅适用于演示,实际需谨慎选择)。 from sklearn.utils import resample # 将多数类和少数类分开 df_majority = df[df['is_st']==0] df_minority = df[df['is_st']==1] # 对多数类进行下采样,使其数量等于少数类 df_majority_downsampled = resample(df_majority, replace=False, # 不放回抽样 n_samples=len(df_minority), random_state=42) # 合并下采样后的数据 df_balanced = pd.concat([df_majority_downsampled, df_minority]) print("平衡后数据集形状:", df_balanced.shape) print("平衡后标签分布:") print(df_balanced['is_st'].value_counts()) # 重新定义平衡后的X和y X_balanced = df_balanced.drop('is_st', axis=1) y_balanced = df_balanced['is_st'] # 4. 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X_balanced, y_balanced, test_size=0.2, random_state=42, stratify=y_balanced) print(f"训练集大小: {X_train.shape}, 测试集大小: {X_test.shape}") # 5. 特征标准化(对基于距离的模型如逻辑回归很重要) scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) # 拟合训练集,并转换 X_test_scaled = scaler.transform(X_test) # 使用训练集的参数转换测试集4. 机器学习模型构建与评估
这是项目的核心环节,我们将构建并对比多个模型。
4.1 模型选择与训练
我们选择三个具有代表性的模型:逻辑回归(线性模型)、决策树(非线性、可解释性强)、随机森林(集成模型,通常表现较好)。
# 文件:model_training.py from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, confusion_matrix, classification_report # 初始化模型 models = { 'Logistic Regression': LogisticRegression(random_state=42, max_iter=1000), 'Decision Tree': DecisionTreeClassifier(random_state=42, max_depth=5), # 限制深度防止过拟合 'Random Forest': RandomForestClassifier(random_state=42, n_estimators=100) } # 存储评估结果 results = {} for name, model in models.items(): print(f"\n=== 训练 {name} ===") # 训练模型 model.fit(X_train_scaled, y_train) # 在测试集上预测 y_pred = model.predict(X_test_scaled) y_pred_proba = model.predict_proba(X_test_scaled)[:, 1] if hasattr(model, "predict_proba") else None # 计算评估指标 accuracy = accuracy_score(y_test, y_pred) precision = precision_score(y_test, y_pred) recall = recall_score(y_test, y_pred) f1 = f1_score(y_test, y_pred) auc = roc_auc_score(y_test, y_pred_proba) if y_pred_proba is not None else None # 存储结果 results[name] = { 'model': model, 'accuracy': accuracy, 'precision': precision, 'recall': recall, 'f1': f1, 'auc': auc, 'y_pred': y_pred, 'y_pred_proba': y_pred_proba } # 打印分类报告和混淆矩阵 print(f"准确率: {accuracy:.4f}") print(f"精确率: {precision:.4f}") print(f"召回率: {recall:.4f}") print(f"F1分数: {f1:.4f}") if auc: print(f"AUC: {auc:.4f}") print("\n分类报告:") print(classification_report(y_test, y_pred)) print("混淆矩阵:") print(confusion_matrix(y_test, y_pred))4.2 模型性能对比与可视化
直观地对比模型表现。
# 文件:model_evaluation.py import matplotlib.pyplot as plt import pandas as pd # 将结果整理成DataFrame metrics_df = pd.DataFrame(index=models.keys()) for name in models.keys(): metrics_df.loc[name, 'Accuracy'] = results[name]['accuracy'] metrics_df.loc[name, 'Precision'] = results[name]['precision'] metrics_df.loc[name, 'Recall'] = results[name]['recall'] metrics_df.loc[name, 'F1-Score'] = results[name]['f1'] if results[name]['auc']: metrics_df.loc[name, 'AUC'] = results[name]['auc'] print("模型性能对比:") print(metrics_df.round(4)) # 绘制性能对比柱状图 fig, ax = plt.subplots(figsize=(10, 6)) metrics_df[['Accuracy', 'Precision', 'Recall', 'F1-Score']].plot(kind='bar', ax=ax) ax.set_title('模型性能指标对比') ax.set_ylabel('分数') ax.set_xlabel('模型') plt.xticks(rotation=45) plt.legend(loc='lower right') plt.tight_layout() plt.show() # 绘制ROC曲线(对于有概率输出的模型) from sklearn.metrics import roc_curve plt.figure(figsize=(8, 6)) for name in ['Logistic Regression', 'Random Forest']: # 决策树概率输出可能不稳定,此处略过 if results[name]['y_pred_proba'] is not None: fpr, tpr, _ = roc_curve(y_test, results[name]['y_pred_proba']) auc = results[name]['auc'] plt.plot(fpr, tpr, label=f'{name} (AUC = {auc:.3f})') plt.plot([0, 1], [0, 1], 'k--', label='随机猜测') plt.xlabel('假正率 (FPR)') plt.ylabel('真正率 (TPR)') plt.title('ROC曲线对比') plt.legend(loc='lower right') plt.grid(True) plt.show()4.3 特征重要性分析(以随机森林为例)
理解模型决策的依据,这是金融风控模型可解释性的关键。
# 文件:feature_importance.py # 获取随机森林模型的特征重要性 rf_model = results['Random Forest']['model'] feature_names = X.columns importances = rf_model.feature_importances_ # 创建特征重要性DataFrame并排序 feat_imp_df = pd.DataFrame({ 'feature': feature_names, 'importance': importances }).sort_values('importance', ascending=False) print("随机森林特征重要性排名 (Top 10):") print(feat_imp_df.head(10)) # 可视化Top N个重要特征 top_n = 15 plt.figure(figsize=(10, 6)) plt.barh(range(top_n), feat_imp_df['importance'].head(top_n)[::-1]) plt.yticks(range(top_n), feat_imp_df['feature'].head(top_n)[::-1]) plt.xlabel('特征重要性') plt.title('Top 15 特征重要性 (随机森林)') plt.tight_layout() plt.show()5. 项目总结与业务解读
模型评估完成并非终点,将技术结果转化为业务洞察才是报告的价值所在。
5.1 技术总结
- 模型选择:在本案例的平衡数据集上,随机森林模型在F1-Score和AUC上表现最佳,体现了集成学习的优势。逻辑回归表现稳定,且模型系数具有可解释性。决策树容易过拟合,需严格控制深度。
- 关键特征:特征重要性分析显示,“资产负债率(
debt_to_assets)”、“流动比率(current_ratio)”、“净资产收益率(roe)”和“经营现金流(operating_cash_flow)”是预测财务风险最重要的指标。这与公司金融理论高度吻合。 - 评估指标意义:在财务风险预警中,召回率(
Recall)至关重要,它衡量了模型找出所有真实“ST”公司的能力。漏报(将ST预测为非ST)的成本远高于误报(将非ST预测为ST)。因此,在模型调优时,应倾向于选择召回率更高的模型或阈值。
5.2 业务建议与报告撰写要点
在实践报告的“结论与建议”部分,应包含:
- 研究发现:陈述通过模型识别出的关键风险财务指标,并引用具体的数值和图表支持。
- 模型局限性:
- 数据局限性:模拟数据、历史ST标记的准确性、财务数据造假可能。
- 模型局限性:机器学习模型是“黑箱”(尤其是复杂模型),其预测基于历史规律,无法预知全新的系统性风险。
- 时间滞后性:财务报告具有滞后性,模型无法反映最新季度的突发状况。
- 实践建议:
- 对投资者的建议:在分析公司时,应重点关注模型指出的高权重指标,将其作为风险筛查的“红色警报”。
- 对监管者的建议:可探索将此类模型作为辅助监控工具,对评分极高的公司进行重点审查。
- 后续研究建议:引入更多非财务数据(如舆情、供应链、管理层变动)、使用更复杂的时序模型(如LSTM)、进行更精细的行业细分建模。
6. 完整项目结构参考
一个清晰的项目结构是专业性的体现。你的实践报告附件或代码仓库应包含:
financial_risk_early_warning/ ├── README.md # 项目说明 ├── data/ # 数据目录 │ ├── raw/ # 原始数据(如有) │ └── processed/ # 处理后的数据 │ └── company_finance_risk.csv ├── notebooks/ # Jupyter Notebook 分析报告 │ └── Financial_Risk_Analysis.ipynb # 主分析文件,包含从EDA到建模的全流程 ├── src/ # 源代码 │ ├── data_acquisition.py # 数据获取 │ ├── data_preprocessing.py # 数据预处理 │ ├── eda.py # 探索性分析 │ ├── model_training.py # 模型训练 │ ├── model_evaluation.py # 模型评估 │ └── feature_importance.py # 特征分析 ├── models/ # 保存训练好的模型(.pkl文件) ├── reports/ # 生成的图表、报告 │ ├── figures/ # 图片 │ └── final_report.pdf # 最终书面报告 └── requirements.txt # 项目依赖7. 常见问题与排查思路
在实践过程中,你可能会遇到以下问题:
| 问题现象 | 可能原因 | 解决思路 |
|---|---|---|
akshare获取数据失败或很慢 | 网络问题、接口变更、目标网站反爬 | 1. 检查网络连接。 2. 查看 akshare官方文档和Issue,确认接口是否更新。3. 添加请求头( headers)模拟浏览器,或使用代理(需合规)。4. 考虑使用付费金融数据API(如Tushare Pro、Baostock)获取更稳定数据。 |
| 数据集类别极端不平衡(如ST仅占1%) | 现实世界中“坏样本”本就稀少 | 1.不要只用准确率评价模型(一个全预测为“好”的模型准确率就有99%)。 2. 使用精确率、召回率、F1、AUC、PR曲线等更合适的指标。 3. 采用过采样(如SMOTE)、欠采样或结合两者的方法处理数据。 4. 使用代价敏感学习,给少数类错误分类更高的惩罚。 |
| 模型在训练集上表现完美,在测试集上很差 | 过拟合 | 1. 检查是否使用了测试集数据进行了特征缩放拟合(fit),必须仅用训练集fit。2. 增加训练数据量(如果可能)。 3. 简化模型(如减小决策树深度、增加随机森林的 min_samples_leaf)。4. 使用正则化(如逻辑回归的 C参数,岭回归)。5. 使用交叉验证选择超参数。 |
| 特征重要性最高的指标与金融常识不符 | 数据质量问题、特征间多重共线性、模型局限性 | 1. 检查数据清洗是否彻底,是否存在异常值扭曲分布。 2. 计算特征间的方差膨胀因子(VIF),排查多重共线性。 3. 尝试使用L1正则化的逻辑回归(LASSO)进行特征选择。 4. 结合业务知识,对特征重要性结果进行批判性审视。 |
Jupyter中图表不显示或代码单元格无输出 | 未正确导入库或未使用显示命令 | 1. 确保已正确执行import matplotlib.pyplot as plt。2. 在Jupyter中,需要添加魔术命令 %matplotlib inline以使图表内嵌显示。3. 对于 seaborn,有时需要先执行sns.set()。 |
8. 最佳实践与进阶方向
8.1 项目最佳实践
- 版本控制:使用Git管理代码,每次实验(不同的特征组合、模型参数)对应一个提交,便于回溯。
- 模块化编程:将数据获取、清洗、特征工程、训练、评估等步骤写成独立函数或类,提高代码可读性和复用性。
- 参数配置化:将模型参数、文件路径等写入配置文件(如
config.yaml或config.py),避免硬编码。 - 实验记录:使用
MLflow或Weights & Biases等工具记录每次实验的超参数、指标和模型,方便比较。 - 交叉验证:始终使用交叉验证(如
cross_val_score)来评估模型的泛化能力,而不仅仅是单次训练测试分割。
8.2 金融风控项目进阶方向
- 更复杂的标签定义:财务困境不止ST,可以定义为“股价暴跌”、“债券违约”等。
- 引入时序特征:使用过去3-5年的财务数据序列,构建时序特征(如增长率、波动率),或使用LSTM等时序模型。
- 融合另类数据:结合新闻舆情(情感分析)、供应链数据、宏观经济指标等,构建多维度风险视图。
- 模型可解释性深化:使用SHAP、LIME等工具对单个预测进行解释,回答“为什么这家公司被预测为高风险?”。
- 在线学习与监控:设计模型定期更新的流水线,并监控模型在生产环境中的预测性能衰减(概念漂移)。
通过以上八个部分的系统实践,你不仅完成了一份内容扎实的《数据科学与人工智能导论》期末报告,更完整地体验了一个金融数据分析项目的标准生命周期。从明确业务问题开始,到数据获取、探索、预处理,再到模型构建、评估、解释,最后进行业务总结和展望,这个过程本身就是数据科学的核心方法论。记住,技术是手段,解决业务问题、创造价值才是目的。希望这份详尽的指南能帮助你交出一份出色的答卷,并为未来的金融科技学习与研究打下坚实基础。