核心问题:如何预测定性的类别标签?
在金融实务中,许多关键决策本质上是分类问题:
- 一家上市公司明年会不会被 ST 风险警示?
- 一笔贷款申请是否会 违约?
- 明天的股价会 涨 还是 跌?
这些问题的响应变量 \(Y\) 是定性的(qualitative),而非连续的数值。
本章将系统介绍从逻辑回归到判别分析、朴素贝叶斯等主流分类方法。
分类 vs. 回归:响应变量的本质区别
| 响应变量 \(Y\) |
定量(连续) |
定性(离散类别) |
| 输出 |
预测值 \(\hat{Y}\) |
类别标签 \(\hat{Y}\) 或概率 \(\hat{P}(Y=k\mid X)\) |
| 常用方法 |
OLS、Ridge、Lasso |
逻辑回归、LDA、QDA、朴素贝叶斯 |
| 损失函数 |
MSE |
交叉熵、0-1 损失 |
| 金融应用 |
股票收益率预测 |
违约判别、ST 预警、涨跌方向 |
分类方法通常先估计条件概率 \(P(Y = k \mid X = x)\),再依据概率做出分类决策。
为什么不能直接用线性回归做分类?
线性回归应用于分类会产生三个根本问题:
问题一:编码方式影响结果
若响应变量有三类(如”低风险=1、中风险=2、高风险=3”),线性回归隐含假设了”高风险与中风险的距离 = 中风险与低风险的距离”,但换成 1/2/3 → 1/3/5 编码会得到完全不同的结果。
问题二:预测值超出合理范围
线性回归的预测值 \(\hat{Y}\) 可以是任意实数,可能输出 \(-0.3\) 或 \(1.5\)——这些值无法被解释为概率。
问题三:多分类困难
多于两类时,线性回归无法自然地建模多类之间的关系。
逻辑回归:用 Sigmoid 函数约束概率
逻辑回归(Logistic Regression)使用 Sigmoid 函数 将线性预测值映射到 \((0,1)\):
\[ \large{ p(X) = \frac{e^{\beta_0 + \beta_1 X_1 + \cdots + \beta_p X_p}}{1 + e^{\beta_0 + \beta_1 X_1 + \cdots + \beta_p X_p}} } \]
关键性质:
- 当 \(\beta_0 + \beta^\top X \to +\infty\) 时,\(p(X) \to 1\)
- 当 \(\beta_0 + \beta^\top X \to -\infty\) 时,\(p(X) \to 0\)
- 输出值始终在 \((0, 1)\) 范围内,天然可解释为概率
从概率到对数几率:Log-Odds 的线性结构
对 \(p(X)\) 做变换,可以得到对数几率(log-odds / logit):
\[ \large{ \log\frac{p(X)}{1-p(X)} = \beta_0 + \beta_1 X_1 + \cdots + \beta_p X_p } \]
- 左边称为 logit 变换,将 \((0,1)\) 映射到 \((-\infty, +\infty)\)
- 右边是标准的线性结构,系数 \(\beta_j\) 的含义:\(X_j\) 增加一单位,对数几率增加 \(\beta_j\)
- \(\exp(\beta_j)\) 称为几率比(odds ratio),是金融风控中的核心可解释性指标
最大似然估计:逻辑回归的参数学习
逻辑回归通过最大似然估计(MLE)求解参数,而非最小化残差平方和。
似然函数为:
\[ \large{ L(\beta) = \prod_{i=1}^{n} p(x_i)^{y_i} [1 - p(x_i)]^{1-y_i} } \]
取对数并求梯度:
\[ \large{ \nabla \ell(\beta) = \sum_{i=1}^{n} (y_i - p_i) x_i } \]
- 当 \(y_i = 1\) 且 \(p_i\) 偏小时,梯度为正 → 增大 \(\beta\) 以提高 \(p_i\)
- 当 \(y_i = 0\) 且 \(p_i\) 偏大时,梯度为负 → 减小 \(\beta\) 以降低 \(p_i\)
- 这一梯度形式与 OLS 的 \((y_i - \hat{y}_i)x_i\) 在结构上完全对应
A 股实证:逻辑回归预测股价涨跌方向
使用浦发银行(600000.XSHG)的日线行情数据,构造滞后收益率特征预测次日涨跌:
- 特征:前三日收益率 \(\text{Lag}_1, \text{Lag}_2, \text{Lag}_3\)
- 响应变量:次日涨跌方向(\(Y=1\) 表示上涨)
- 模型:
statsmodels.Logit
浦发银行逻辑回归实证(续)
# 筛选浦发银行的交易数据
pudong_bank_df = stock_price_df[stock_price_df['order_book_id'] == '600000.XSHG'].copy() # 提取浦发银行数据
pudong_bank_df = pudong_bank_df.sort_values('date') # 按日期排序确保时序正确
pudong_bank_df['daily_return'] = pudong_bank_df['close'].pct_change() # 计算日收益率
# 构造滞后特征:前1/2/3日收益率
pudong_bank_df['lag_1'] = pudong_bank_df['daily_return'].shift(1) # 前1日收益率
pudong_bank_df['lag_2'] = pudong_bank_df['daily_return'].shift(2) # 前2日收益率
pudong_bank_df['lag_3'] = pudong_bank_df['daily_return'].shift(3) # 前3日收益率
# 构造二分类响应变量:涨=1,跌=0
pudong_bank_df['is_up'] = (pudong_bank_df['daily_return'] > 0).astype(int) # 当日收益率>0标记为上涨
pudong_bank_df = pudong_bank_df.dropna(subset=['lag_1', 'lag_2', 'lag_3', 'is_up']) # 删除缺失值
# 构建特征矩阵并添加截距项
feature_matrix = sm.add_constant(pudong_bank_df[['lag_1', 'lag_2', 'lag_3']]) # 添加常数列作为截距
response_vector = pudong_bank_df['is_up'] # 提取响应变量
# 拟合逻辑回归模型
logit_model = sm.Logit(response_vector, feature_matrix).fit(disp=0) # 使用MLE拟合逻辑回归
logit_model.summary2().tables[1] # 展示系数估计表
| const |
-0.091713 |
0.028113 |
-3.262326 |
0.001105 |
-0.146813 |
-0.036613 |
| lag_1 |
-2.545384 |
1.345245 |
-1.892134 |
0.058473 |
-5.182016 |
0.091247 |
| lag_2 |
-0.763111 |
1.341220 |
-0.568967 |
0.569378 |
-3.391854 |
1.865633 |
| lag_3 |
0.553342 |
1.340937 |
0.412653 |
0.679861 |
-2.074847 |
3.181531 |
贝叶斯定理:分类的概率论基础
贝叶斯定理是生成式分类方法的理论基石:
\[ \large{ P(Y = k \mid X = x) = \frac{P(X = x \mid Y = k) \cdot P(Y = k)}{P(X = x)} } \]
| 后验概率 |
\(P(Y=k\mid X)\) |
观测到特征后的类别概率 |
| 似然 |
\(P(X\mid Y=k)\) |
类别 \(k\) 下特征的分布 |
| 先验概率 |
\(P(Y=k)\) |
各类别出现的频率 |
| 边际概率 |
\(P(X)\) |
特征的整体分布(归一化常数) |
判别式 vs. 生成式:逻辑回归直接建模后验 \(P(Y\mid X)\);而 LDA/QDA 通过建模似然 \(P(X\mid Y)\) 与先验 \(P(Y)\),再用贝叶斯定理间接得到后验。
LDA:线性判别分析
线性判别分析(LDA)假设每个类别的特征服从多元正态分布且协方差矩阵相同:
\[ \large{ X \mid Y = k \sim N(\mu_k, \Sigma) } \]
在此假设下,判别函数为线性形式:
\[ \large{ \delta_k(x) = x^\top \Sigma^{-1} \mu_k - \frac{1}{2} \mu_k^\top \Sigma^{-1} \mu_k + \log \pi_k } \]
- \(\mu_k\):类别 \(k\) 的均值向量
- \(\Sigma\):共享的协方差矩阵
- \(\pi_k = P(Y=k)\):先验概率
- 决策规则:将 \(x\) 分配到使 \(\delta_k(x)\) 最大的类别 \(k\)
QDA:允许不同协方差的二次判别
二次判别分析(QDA)放松了 LDA 的等协方差假设,允许每个类别有自己的协方差矩阵:
\[ \large{ X \mid Y = k \sim N(\mu_k, \Sigma_k) } \]
判别函数变为二次形式:
\[ \large{ \delta_k(x) = -\frac{1}{2} x^\top \Sigma_k^{-1} x + x^\top \Sigma_k^{-1} \mu_k + C_k } \]
| 协方差假设 |
\(\Sigma_1 = \Sigma_2 = \Sigma\) |
\(\Sigma_1 \neq \Sigma_2\) |
| 决策边界 |
线性 |
二次曲线 |
| 参数数量 |
较少 |
较多 |
| 适用场景 |
小样本、类分布相似 |
大样本、类分布差异大 |
朴素贝叶斯:高维分类的实用利器
朴素贝叶斯(Naive Bayes)做了一个大胆假设:给定类别后,特征之间相互独立。
\[ \large{ P(X_1, \ldots, X_p \mid Y = k) = \prod_{j=1}^{p} P(X_j \mid Y = k) } \]
优势:
- 参数量从 \(O(p^2)\) 降到 \(O(p)\),大幅降低过拟合风险
- 计算速度极快,适合高维数据(如文本分类)
- 在许多实际场景中,分类效果出奇地好
局限:
- 独立性假设通常不严格成立
- 概率估计的准确性较差(但排序性能好,AUC 可能不受影响)
六种分类方法全景对比
| 逻辑回归 |
线性 |
好 |
强 |
中 |
需正则化 |
| LDA |
线性 |
中 |
中 |
好 |
中 |
| QDA |
二次 |
中 |
弱 |
差 |
差 |
| 朴素贝叶斯 |
非线性 |
差 |
中 |
好 |
好 |
| KNN |
非线性 |
差 |
弱 |
中 |
差 |
| SVM |
非线性 |
差 |
弱 |
中 |
中 |
金融实务指导:
- 风控报审 → 逻辑回归(监管要求可解释)
- 小样本早期预警 → LDA
- 高维文本情感 → 朴素贝叶斯
A 股 ST 预警实证:八种模型性能比较
基于本地 A 股财务数据构建 ST(特别处理)预测模型:
- 特征:ROA(资产收益率)、资产负债率、对数总资产
- 标签:公司是否被 ST(0=正常,1=ST)
- 方法:逻辑回归、LDA、QDA、朴素贝叶斯、KNN(K=5)、KNN(K=20)、SVM线性核、SVM-RBF核
import pandas as pd # 导入pandas用于数据处理
import numpy as np # 导入numpy用于数值计算
import os # 导入os用于路径操作
import matplotlib.pyplot as plt # 导入matplotlib用于绑图
# 根据操作系统自动选择数据路径
DATA_ROOT = 'C:/qiufei/data' if os.name == 'nt' else '/home/ubuntu/r2_data_mount/qiufei/data' # 数据根目录
FINANCE_PATH = os.path.join(DATA_ROOT, 'stock/financial_statement.h5') # 财务报表数据路径
BASIC_PATH = os.path.join(DATA_ROOT, 'stock/stock_basic_data.h5') # 股票基本信息路径
financial_data_df = pd.read_hdf(FINANCE_PATH) # 读取财务报表数据
basic_info_df = pd.read_hdf(BASIC_PATH) # 读取股票基本信息
ST 预警实证:数据准备与特征工程
# 筛选2023年年报数据用于分析
financial_data_df = financial_data_df[financial_data_df['quarter'] == '2023q4'].copy() # 选取2023年年报
# 合并财务数据与ST标记
join_key = 'order_book_id' # 以股票代码作为合并键
merged_financial_data = pd.merge( # 内连接合并两张表
financial_data_df,
basic_info_df[[join_key, 'special_type']],
on=join_key, how='inner'
)
# 构造三个核心财务特征
merged_financial_data['roa'] = merged_financial_data['net_profit'] / merged_financial_data['total_assets'] # ROA:衡量资产创利能力
merged_financial_data['debt_to_assets'] = merged_financial_data['total_liabilities'] / merged_financial_data['total_assets'] # 资产负债率:衡量杠杆水平
merged_financial_data['log_size'] = np.log10(merged_financial_data['total_assets'] + 1) # 对数总资产:控制规模效应
# 数据清洗:去除异常值和缺失值
merged_financial_data = merged_financial_data.replace([np.inf, -np.inf], np.nan).dropna(subset=['roa', 'debt_to_assets', 'log_size']) # 替换无穷值并删除缺失
merged_financial_data = merged_financial_data[(merged_financial_data['debt_to_assets'] >= 0) & (merged_financial_data['debt_to_assets'] <= 2)] # 资产负债率限制在合理范围
merged_financial_data = merged_financial_data[merged_financial_data['total_assets'] > 1e6] # 排除总资产过小的异常记录
ST 预警实证:模型训练与交叉验证
from sklearn.model_selection import train_test_split, cross_val_score # 导入数据切分与交叉验证工具
from sklearn.linear_model import LogisticRegression # 导入逻辑回归
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis # 导入LDA和QDA
from sklearn.naive_bayes import GaussianNB # 导入朴素贝叶斯
from sklearn.neighbors import KNeighborsClassifier # 导入K近邻
from sklearn.svm import SVC # 导入支持向量机
from sklearn.preprocessing import StandardScaler # 导入标准化工具
# 构造ST标签:special_type含'ST'标记为1,否则为0
merged_financial_data['is_st'] = merged_financial_data['special_type'].fillna('Normal').str.contains('ST').astype(int) # 根据special_type列判断是否为ST
features_matrix = merged_financial_data[['roa', 'debt_to_assets', 'log_size']].values # 提取特征矩阵
st_target_labels = merged_financial_data['is_st'].values # 提取目标标签
# 分层抽样切分训练/测试集(70%/30%)
features_train, features_test, target_train, target_test = train_test_split( # 按ST比例分层抽样
features_matrix, st_target_labels, test_size=0.3, random_state=42, stratify=st_target_labels
)
# 特征标准化(对KNN和SVM至关重要)
scaler = StandardScaler() # 初始化标准化器
features_train_scaled = scaler.fit_transform(features_train) # 在训练集上拟合并转换
features_test_scaled = scaler.transform(features_test) # 用训练集参数转换测试集(防止数据泄露)
ST 预警实证:八种模型评估结果
# 定义8种分类模型
models = { # 构建模型字典
'逻辑回归': LogisticRegression(max_iter=1000), # 逻辑回归,增大迭代次数防止不收敛
'LDA': LinearDiscriminantAnalysis(), # 线性判别分析
'QDA': QuadraticDiscriminantAnalysis(), # 二次判别分析
'朴素贝叶斯': GaussianNB(), # 高斯朴素贝叶斯
'KNN(K=5)': KNeighborsClassifier(n_neighbors=5), # 5近邻
'KNN(K=20)': KNeighborsClassifier(n_neighbors=20), # 20近邻
'SVM线性': SVC(kernel='linear', probability=True), # 线性核SVM
'SVM-RBF': SVC(kernel='rbf', probability=True), # RBF核SVM
}
# 训练每个模型并记录结果
results = {} # 初始化结果字典
for name, model in models.items(): # 遍历所有模型
model.fit(features_train_scaled, target_train) # 拟合模型
test_score = model.score(features_test_scaled, target_test) # 测试集准确率
cv_scores = cross_val_score(model, features_train_scaled, target_train, cv=5) # 5折交叉验证
results[name] = {'测试准确率': test_score, 'CV均值': cv_scores.mean(), 'CV标准差': cv_scores.std()} # 存储结果
pd.DataFrame(results).T.round(4) # 转置并保留4位小数展示结果表
ST 预警可视化:测试集 vs. 交叉验证准确率
Code
# 配置中文字体
plt.rcParams['font.sans-serif'] = ['Source Han Serif SC', 'SimHei', 'Arial Unicode MS'] # 设置中文字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
fig, ax = plt.subplots(figsize=(14, 7)) # 创建画布
methods = list(results.keys()) # 提取方法名列表
test_scores = [results[m]['测试准确率'] for m in methods] # 各方法测试集准确率
cv_means = [results[m]['CV均值'] for m in methods] # 各方法CV均值
cv_stds = [results[m]['CV标准差'] for m in methods] # 各方法CV标准差
x = np.arange(len(methods)) # 分组柱状图的x轴位置
width = 0.35 # 柱子宽度
# 绘制分组柱状图
ax.bar(x - width/2, test_scores, width, label='测试集准确率', color='steelblue', alpha=0.8, edgecolor='black') # 测试集准确率柱
ax.bar(x + width/2, cv_means, width, yerr=cv_stds, label='CV准确率(±1SD)', color='coral', alpha=0.8, edgecolor='black', capsize=5) # CV准确率柱(含误差线)
ax.set_xlabel('分类方法', fontsize=13, fontweight='bold') # x轴标签
ax.set_ylabel('准确率', fontsize=13, fontweight='bold') # y轴标签
ax.set_title('分类方法性能比较(ST 财务困境预测)', fontsize=14, fontweight='bold') # 图标题
ax.set_xticks(x) # 设置x轴刻度位置
ax.set_xticklabels(methods, rotation=45, ha='right') # 设置刻度标签并旋转
ax.legend(fontsize=11, loc='lower right') # 添加图例
ax.set_ylim([0.7, 1.0]) # 聚焦有效区间
ax.grid(True, alpha=0.3, axis='y') # 添加水平网格线
plt.tight_layout() # 自动调整布局
plt.show() # 显示图表
<Figure size 960x480 with 0 Axes>
本章知识体系总结