在实际的数据科学与人工智能课程中,期末实践报告往往是学生将理论知识转化为具体项目能力的第一次系统性尝试。对于会计、金融等非纯技术背景的班级而言,这份报告的核心挑战在于如何将数据科学(Data Science)与人工智能(Artificial Intelligence)的方法论,与一个具体的、可理解的业务领域(如会计、财务分析)相结合,而不是仅仅停留在算法调用的表面。一个成功的实践报告,应该清晰地展示从问题定义、数据获取与处理、模型选择与训练,到结果分析与业务解读的完整闭环,并体现出对技术原理的初步理解和工程化落地的思考。
本文将以“会计数据分析”为背景,构建一个完整的期末实践项目框架。我们将围绕“企业财务风险预警”这一经典场景,使用Python生态中的常见工具库,完成一个从数据到模型再到可视化报告的最小可行项目。通过这个项目,你将掌握如何搭建一个标准的数据科学工作流,理解机器学习模型在财务领域的应用逻辑,并学会撰写一份结构清晰、内容扎实的技术实践报告。无论你是会计专业的学生初次接触编程,还是已有一定基础希望深化理解,本文提供的步骤、代码和思路都将为你提供一个可直接复现的蓝本。
1. 理解项目目标:构建一个财务风险预警分析系统
在开始写代码之前,必须明确我们究竟要解决什么问题,以及如何用数据科学的方法来解决它。一个模糊的目标会导致后续所有步骤的混乱。
1.1 业务问题定义:从会计视角看风险预警
对于会计或财务分析而言,风险预警的核心是提前识别出可能陷入财务困境(如破产、债务违约、持续亏损)的企业。传统的财务分析依赖于财务比率(如流动比率、资产负债率)和专家经验判断。数据科学的方法则试图从历史数据中自动学习出这些风险模式。
我们的项目目标可以定义为:利用上市公司公开的财务数据,构建一个分类模型,能够根据企业过去一年的关键财务指标,预测其下一年是否会被标记为“ST”(特别处理,通常意味着财务异常)。ST状态在中国A股市场是一个明确的财务风险信号,数据相对容易获取且标签清晰,非常适合作为学习项目。
1.2 技术路径设计:标准CRISP-DM工作流
为了实现上述目标,我们将遵循跨行业数据挖掘标准流程(CRISP-DM),这是数据科学项目最常用的方法论框架。我们的技术路径将分为六个阶段:
- 业务理解:明确预测目标(ST状态)和可用数据(财务指标)。
- 数据理解:获取数据,探索数据的基本情况、分布和缺失值。
- 数据准备:清洗数据、处理缺失值、特征工程、划分训练集和测试集。
- 建模:选择并训练一个或多个机器学习模型。
- 评估:使用测试集评估模型性能,选择最佳模型。
- 部署:在项目中体现为生成分析报告和可视化结果。
这个流程确保了项目的系统性和可重复性。
1.3 环境与工具准备:搭建Python数据科学工作台
工欲善其事,必先利其器。一个稳定、统一的环境是项目成功的基础。我们将使用Python作为主要编程语言,并依赖以下几个核心库:
- 数据处理:
pandas,numpy - 数据可视化:
matplotlib,seaborn - 机器学习:
scikit-learn - 统计分析:
scipy(可选) - 开发环境:推荐使用Jupyter Notebook或VS Code,便于交互和展示。
首先,我们通过命令行创建并配置项目环境。如果你使用conda,可以按以下步骤操作:
# 1. 创建一个新的conda环境,指定Python版本 conda create -n finance_risk_py39 python=3.9 # 2. 激活环境 conda activate finance_risk_py39 # 3. 安装核心依赖库 pip install pandas numpy matplotlib seaborn scikit-learn jupyter如果你使用纯pip和venv:
# 1. 创建项目文件夹并进入 mkdir finance_risk_project && cd finance_risk_project # 2. 创建虚拟环境 python -m venv venv # 3. 激活虚拟环境 # 在Windows上: venv\Scripts\activate # 在Mac/Linux上: source venv/bin/activate # 4. 安装核心依赖库 pip install pandas numpy matplotlib seaborn scikit-learn jupyter环境配置完成后,可以在项目根目录下启动Jupyter Notebook,并创建一个新的Notebook文件,例如financial_risk_analysis.ipynb。后续的代码都将在这个Notebook中分步骤执行和讲解。
2. 数据获取、理解与准备
数据是模型的燃料,其质量直接决定模型性能的上限。这一阶段耗时最长,也最容易出错。
2.1 数据来源与获取
对于上市公司财务数据,有多个开源或付费的数据库。为了学习目的,我们可以使用akshare这个免费的金融数据接口库,或者使用预设的示例数据集。这里为了流程的稳定性和可复现性,我们使用scikit-learn内置的一个模拟分类数据集作为演示,并模拟财务数据的特征命名。在实际报告中,你应该尽可能使用真实数据(如从CSMAR、Wind或akshare获取)。
首先,我们加载必要的库并创建一个模拟的财务数据集。
# 导入必要的库 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split # 设置中文显示和图形样式(可选) plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 sns.set_style("whitegrid") # 生成模拟财务数据 # n_samples: 样本数(公司数) # n_features: 特征数(财务指标数),这里设为10个关键指标 # n_informative: 实际对分类有用的特征数 # n_redundant: 冗余特征(由信息特征线性组合而成) # n_clusters_per_class: 每个类别有几个簇 # random_state: 随机种子,确保结果可复现 X, y = make_classification(n_samples=1000, n_features=10, n_informative=6, n_redundant=2, n_clusters_per_class=1, random_state=42) # 将数据转换为DataFrame,便于理解和操作 # 为特征命名,模拟常见的财务指标 feature_names = [ '流动比率', '速动比率', '资产负债率', '总资产周转率', '应收账款周转率', '存货周转率', '营业收入增长率', '净利润增长率', '净资产收益率', '每股收益' ] df = pd.DataFrame(X, columns=feature_names) # 添加目标列:是否ST (1表示ST,0表示非ST) df['is_ST'] = y # 查看数据前5行和基本信息 print("数据形状(样本数, 特征数+目标):", df.shape) print("\n数据前5行:") print(df.head()) print("\n数据基本信息:") print(df.info()) print("\n目标变量分布:") print(df['is_ST'].value_counts()) print("ST比例:", df['is_ST'].mean())2.2 数据探索与可视化
在建模前,我们必须了解数据的“长相”。这包括检查缺失值、异常值、特征分布以及特征与目标的关系。
# 1. 检查缺失值 print("缺失值统计:") print(df.isnull().sum()) # 2. 查看基本统计描述 print("\n数值特征描述性统计:") print(df.describe()) # 3. 目标变量分布可视化 plt.figure(figsize=(6, 4)) sns.countplot(x='is_ST', data=df) plt.title('ST与非ST公司数量分布') plt.xlabel('是否ST (0:否, 1:是)') plt.ylabel('公司数量') plt.show() # 4. 特征分布直方图(以两个特征为例) fig, axes = plt.subplots(2, 3, figsize=(15, 8)) axes = axes.ravel() # 将二维坐标轴数组展平 for idx, col in enumerate(feature_names[:6]): # 只看前6个特征 axes[idx].hist(df[col], bins=30, edgecolor='black', alpha=0.7) axes[idx].set_title(f'{col}分布') axes[idx].set_xlabel(col) axes[idx].set_ylabel('频数') plt.tight_layout() plt.show() # 5. 特征与目标的关系(箱线图) plt.figure(figsize=(10, 6)) # 选取‘资产负债率’和‘净资产收益率’两个关键指标进行对比 df_melt = pd.melt(df, id_vars=['is_ST'], value_vars=['资产负债率', '净资产收益率'], var_name='财务指标', value_name='值') sns.boxplot(x='财务指标', y='值', hue='is_ST', data=df_melt) plt.title('ST与非ST公司在关键财务指标上的差异') plt.show() # 6. 特征间相关性热力图 plt.figure(figsize=(10, 8)) correlation_matrix = df[feature_names].corr() sns.heatmap(correlation_matrix, annot=True, fmt='.2f', cmap='coolwarm', square=True, cbar_kws={"shrink": .8}) plt.title('财务指标相关性热力图') plt.show()通过以上可视化,我们可以初步判断:数据是否平衡(ST公司通常远少于非ST公司,即样本不均衡);哪些特征分布有异常;特征之间是否存在高度相关性(如流动比率和速动比率可能相关);以及哪些特征在ST和非ST组间表现出明显差异。
2.3 数据预处理与特征工程
原始数据很少能直接用于建模。我们需要进行一系列清洗和转换。
from sklearn.preprocessing import StandardScaler from sklearn.feature_selection import SelectKBest, f_classif # 1. 处理缺失值(本例模拟数据无缺失,此处展示通用方法) # 对于数值特征,常用均值、中位数或众数填充 # df.fillna(df.mean(), inplace=True) # 2. 处理异常值(可选,根据业务逻辑) # 例如,对于‘资产负债率’,理论上应在0-1之间,但可能有极端值 # 可以使用分位数进行截断 # for col in ['资产负债率']: # lower = df[col].quantile(0.01) # upper = df[col].quantile(0.99) # df[col] = df[col].clip(lower, upper) # 3. 特征缩放 # 很多模型(如SVM、KNN、神经网络)对特征尺度敏感,需要进行标准化 scaler = StandardScaler() X_scaled = scaler.fit_transform(df[feature_names]) # 将缩放后的数据存回DataFrame,方便查看 df_scaled = pd.DataFrame(X_scaled, columns=[f'{col}_scaled' for col in feature_names]) df = pd.concat([df, df_scaled], axis=1) # 4. 特征选择 # 使用方差分析(ANOVA)选择与目标最相关的k个特征 selector = SelectKBest(score_func=f_classif, k=6) # 选择6个最佳特征 X_selected = selector.fit_transform(df[[f'{col}_scaled' for col in feature_names]], df['is_ST']) # 获取被选中的特征名 selected_mask = selector.get_support() selected_features = [feature_names[i] for i in range(len(feature_names)) if selected_mask[i]] print(f"选择出的{len(selected_features)}个重要特征:{selected_features}") # 5. 划分训练集和测试集 # 这是关键一步,确保模型评估的公正性。stratify参数保证训练集和测试集中ST比例一致。 X_train, X_test, y_train, y_test = train_test_split( X_selected, df['is_ST'], test_size=0.2, random_state=42, stratify=df['is_ST'] ) print(f"训练集大小:{X_train.shape}, 测试集大小:{X_test.shape}") print(f"训练集ST比例:{y_train.mean():.3f}, 测试集ST比例:{y_test.mean():.3f}")3. 模型选择、训练与评估
有了干净的数据,我们就可以开始尝试不同的机器学习模型,并科学地评估它们的性能。
3.1 模型选择与训练
我们尝试三种经典且解释性不同的分类模型:逻辑回归(基础)、随机森林(集成树模型)、支持向量机(SVM)。通过对比,理解不同模型的特性。
from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.svm import SVC from sklearn.metrics import classification_report, confusion_matrix, accuracy_score, roc_auc_score, roc_curve # 初始化模型 models = { '逻辑回归': LogisticRegression(random_state=42, max_iter=1000), '随机森林': RandomForestClassifier(n_estimators=100, random_state=42), '支持向量机': SVC(probability=True, random_state=42) # 启用概率估计,方便计算AUC } # 用于存储每个模型在测试集上的预测结果和性能 results = {} for name, model in models.items(): print(f"\n=== 训练 {name} ===") # 训练模型 model.fit(X_train, y_train) # 在测试集上预测 y_pred = model.predict(X_test) y_pred_proba = model.predict_proba(X_test)[:, 1] if hasattr(model, "predict_proba") else None # 计算评估指标 accuracy = accuracy_score(y_test, y_pred) report = classification_report(y_test, y_pred, output_dict=True) cm = confusion_matrix(y_test, y_pred) # 存储结果 results[name] = { 'model': model, 'y_pred': y_pred, 'y_pred_proba': y_pred_proba, 'accuracy': accuracy, 'classification_report': report, 'confusion_matrix': cm } # 打印关键指标 print(f"准确率 (Accuracy): {accuracy:.4f}") print(f"精确率 (Precision - ST类): {report['1']['precision']:.4f}") print(f"召回率 (Recall - ST类): {report['1']['recall']:.4f}") print(f"F1分数 (F1-Score - ST类): {report['1']['f1-score']:.4f}") print("混淆矩阵:") print(cm)3.2 模型性能评估与可视化
对于分类问题,尤其是样本不均衡的风险预警问题,不能只看准确率。我们需要综合多个指标,并借助可视化工具。
# 1. 绘制ROC曲线比较模型 plt.figure(figsize=(8, 6)) for name, res in results.items(): if res['y_pred_proba'] is not None: fpr, tpr, _ = roc_curve(y_test, res['y_pred_proba']) auc = roc_auc_score(y_test, res['y_pred_proba']) plt.plot(fpr, tpr, label=f'{name} (AUC = {auc:.3f})') plt.plot([0, 1], [0, 1], 'k--', label='随机猜测 (AUC = 0.5)') plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel('假正率 (False Positive Rate)') plt.ylabel('真正率 (True Positive Rate)') plt.title('不同模型的ROC曲线对比') plt.legend(loc="lower right") plt.grid(True) plt.show() # 2. 绘制性能指标对比柱状图 metrics_df = pd.DataFrame({ '模型': list(results.keys()), '准确率': [res['accuracy'] for res in results.values()], 'ST类精确率': [res['classification_report']['1']['precision'] for res in results.values()], 'ST类召回率': [res['classification_report']['1']['recall'] for res in results.values()], 'ST类F1': [res['classification_report']['1']['f1-score'] for res in results.values()], }) fig, axes = plt.subplots(2, 2, figsize=(12, 8)) axes = axes.ravel() metric_cols = ['准确率', 'ST类精确率', 'ST类召回率', 'ST类F1'] for idx, col in enumerate(metric_cols): axes[idx].bar(metrics_df['模型'], metrics_df[col]) axes[idx].set_title(col) axes[idx].set_ylabel('分数') # 在柱子上方显示数值 for i, v in enumerate(metrics_df[col]): axes[idx].text(i, v + 0.01, f'{v:.3f}', ha='center') plt.tight_layout() plt.show() # 3. 分析最佳模型的特征重要性(以随机森林为例) if '随机森林' in results: best_model = results['随机森林']['model'] importances = best_model.feature_importances_ indices = np.argsort(importances)[::-1] # 按重要性降序排列 plt.figure(figsize=(10, 6)) plt.title("随机森林模型 - 特征重要性") plt.bar(range(len(selected_features)), importances[indices], align="center") plt.xticks(range(len(selected_features)), [selected_features[i] for i in indices], rotation=45) plt.xlabel("特征") plt.ylabel("重要性得分") plt.tight_layout() plt.show() # 打印重要性排序 print("特征重要性排序:") for i in indices: print(f" {selected_features[i]}: {importances[i]:.4f}")3.3 模型选择与解释
根据以上评估,我们可以得出一些结论:
- 准确率:可能很高,但因为ST公司样本少,模型如果全部预测为“非ST”也能获得高准确率,因此这个指标参考价值有限。
- 精确率:预测为ST的公司中,真正是ST的比例。高精确率意味着预警信号的可信度高,可以减少误报。
- 召回率:真正的ST公司中,被模型成功预测出来的比例。高召回率意味着漏报少。
- F1分数:精确率和召回率的调和平均数,是衡量模型对少数类(ST)识别能力的综合指标。
- AUC:ROC曲线下的面积,衡量模型整体排序能力,越接近1越好,对样本不均衡不敏感。
通常,在财务风险预警中,我们更关心召回率(尽量不漏掉风险公司)和精确率(预警信号要靠谱),需要在两者之间根据业务成本进行权衡。从特征重要性图中,我们可以解读出哪些财务指标对预测ST状态贡献最大,这本身就是一份有价值的分析结论。
4. 项目总结、报告撰写与进阶思考
完成模型构建与评估后,需要将整个工作整理成一份结构化的实践报告,并思考项目的局限性与改进方向。
4.1 实践报告的核心结构
一份合格的数据科学与人工智能实践报告应包含以下部分,你可以将前面步骤的代码、输出和图表整合进去:
- 摘要:用200-300字概括项目目标、方法、主要发现和结论。
- 引言/背景:阐述财务风险预警的意义,以及数据科学方法在该领域的应用价值。
- 问题定义与数据:明确预测目标(二分类:ST/非ST),说明数据来源、字段含义、样本规模及基本统计特征。
- 方法论:
- 数据预处理流程(缺失值、异常值、标准化)。
- 特征工程方法(特征选择、构造)。
- 使用的模型及其原理简介(逻辑回归、随机森林、SVM)。
- 模型评估指标(准确率、精确率、召回率、F1、AUC、混淆矩阵)。
- 实验过程与结果:
- 展示数据探索的可视化结果(分布、相关性、类别差异)。
- 呈现模型训练后的性能对比表格和图表(如ROC曲线、指标柱状图)。
- 展示最佳模型的特征重要性分析。
- 讨论与分析:
- 对比不同模型的优劣,解释为什么某个模型在本任务上表现更好。
- 结合特征重要性,讨论哪些财务指标对风险预警最关键,这与传统财务分析理论是否一致?
- 分析模型的误报(将健康公司判为ST)和漏报(将ST公司判为健康)案例,讨论其业务影响。
- 结论与展望:
- 总结项目是否达到预期目标。
- 指出本项目的局限性(如数据量小、特征有限、未考虑时序性等)。
- 提出未来改进方向(如引入更多非财务数据、使用深度学习模型、考虑动态预测等)。
- 参考文献:列出引用的数据来源、算法库和关键理论文献。
- 附录:可以包含核心代码片段或完整代码的GitHub仓库链接。
4.2 项目常见问题与排查
在完成项目的过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 检查与解决思路 |
|---|---|---|
| 模型准确率很高(>95%),但召回率极低(接近0) | 样本严重不均衡。模型倾向于预测多数类。 | 1. 检查目标变量分布 (df[‘is_ST’].value_counts())。2. 使用过采样(如SMOTE)、欠采样或调整类别权重(如 class_weight=‘balanced’)。3. 改用AUC、F1等对不均衡更鲁棒的指标评估。 |
| 所有模型性能都很差,AUC接近0.5 | 特征与目标无关或数据噪声太大。模型无法学习到有效模式。 | 1. 重新进行数据探索,可视化特征与目标的关系,检查是否真的存在区分度。 2. 检查数据预处理是否正确,例如标准化是否误操作。 3. 尝试更复杂的特征工程,或检查数据来源和质量。 |
| 训练集表现很好,测试集表现很差 | 过拟合。模型过度记忆了训练集的噪声。 | 1. 增加训练数据量。 2. 简化模型复杂度(如减少树深度、增加正则化参数)。 3. 使用交叉验证选择超参数,而不是在训练集上优化。 |
ValueError: Unknown label type: ‘continuous’ | 目标变量y是浮点数,但分类模型期望整数标签。 | 使用y = y.astype(int)将目标变量转换为整型。 |
运行akshare等库获取数据时报网络错误或接口变更 | 网络问题或第三方库API更新。 | 1. 检查网络连接。 2. 查看库的官方文档或GitHub Issues,确认接口用法是否已更新。 3. 作为备选,使用本地缓存的CSV文件或模拟数据确保项目主体可进行。 |
4.3 从课程实践到生产环境的思考
课程项目与真实生产系统之间存在巨大鸿沟。完成基础实践后,可以从以下角度进行扩展思考,这能让你的报告更具深度:
数据层面:
- 实时性:财务报告是季报/年报,如何构建更及时的预警指标?(如结合股价、新闻舆情等另类数据)。
- 时序性:本项目使用了截面数据。真实风险是演化的,如何利用多年财务数据构建时序模型(如LSTM)?
- 数据质量:真实财务数据存在缺失、错误、操纵(财务造假)问题,如何清洗和验证?
模型层面:
- 可解释性:在金融风控领域,模型的可解释性至关重要。如何用SHAP、LIME等工具解释单个预测?
- 模型稳定性:模型性能是否会随时间推移而衰减?如何设计模型监控和定期重训练机制?
- 集成与优化:是否可以用模型融合(Stacking)提升性能?如何系统地进行超参数调优?
系统层面:
- 工程化:如何将Jupyter Notebook中的分析代码,重构为模块化、可配置、可调度的生产代码(Python脚本)?
- API服务:如何将训练好的模型封装成RESTful API,供其他系统调用?
- 自动化报告:如何利用Python(如
Jinja2+WeasyPrint)或BI工具(如Tableau, Superset)自动生成可视化分析报告?
这份期末实践报告的核心价值,不仅在于你跑通了一个机器学习流程,更在于你通过这个过程,理解了数据科学项目从业务问题出发,经过数据、模型、评估,最终回归业务决策的完整逻辑。尝试用你的代码和文字,将这个故事清晰地讲述出来。