04 分类方法 (Classification)

核心问题:如何预测定性的类别标签?

在金融实务中,许多关键决策本质上是分类问题

  • 一家上市公司明年会不会被 ST 风险警示
  • 一笔贷款申请是否会 违约
  • 明天的股价会 还是

这些问题的响应变量 \(Y\)定性的(qualitative),而非连续的数值。

本章将系统介绍从逻辑回归到判别分析、朴素贝叶斯等主流分类方法。

分类 vs. 回归:响应变量的本质区别

特征 回归 (Regression) 分类 (Classification)
响应变量 \(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
Table 1: 逻辑回归系数估计(浦发银行日线涨跌预测)
import pandas as pd  # 导入pandas用于数据处理
import numpy as np  # 导入numpy用于数值计算
import os  # 导入os用于路径操作
import statsmodels.api as sm  # 导入statsmodels用于逻辑回归建模

# 根据操作系统自动选择数据路径
DATA_ROOT = 'C:/qiufei/data' if os.name == 'nt' else '/home/ubuntu/r2_data_mount/qiufei/data'  # 设置数据根目录
PRICE_PATH = os.path.join(DATA_ROOT, 'stock/stock_price_pre_adjusted.h5')  # 构建前复权股价文件路径
stock_price_df = pd.read_hdf(PRICE_PATH).reset_index()  # 读取前复权日线行情数据并将MultiIndex转为普通列

浦发银行逻辑回归实证(续)

# 筛选浦发银行的交易数据
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]  # 展示系数估计表
Coef. Std.Err. z P>|z| [0.025 0.975]
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 } \]

对比 LDA QDA
协方差假设 \(\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位小数展示结果表
Table 2: 八种分类方法在 ST 预警任务上的性能对比
测试准确率 CV均值 CV标准差
逻辑回归 0.9654 0.9615 0.0026
LDA 0.9642 0.9636 0.0023
QDA 0.9623 0.9604 0.0038
朴素贝叶斯 0.9642 0.9606 0.0022
KNN(K=5) 0.9604 0.9628 0.0020
KNN(K=20) 0.9629 0.9642 0.0007
SVM线性 0.9642 0.9639 0.0005
SVM-RBF 0.9642 0.9636 0.0009

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  # 柱子宽度
Figure 1: 不同分类方法的性能比较(ST 财务困境预测)
# 绘制分组柱状图
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>

本章知识体系总结

分类方法知识体系 展示判别式与生成式两大建模范式及其具体方法的层次关系图 分类方法知识体系 (Classification Methods) 分类问题 P(Y|X) 判别式 (Discriminative) 直接建模 P(Y|X) 逻辑回归 (Logistic) SVM KNN 生成式 (Generative) 建模 P(X|Y)·P(Y) LDA (线性) QDA (二次) 朴素贝叶斯 (NB) 核心要点 • 逻辑回归:可解释性强,风控首选 • LDA/QDA:基于正态假设,小样本稳定 • 朴素贝叶斯:高维高效,AUC 排序性能好 • 方法选择取决于:样本量、维度、可解释性 • 不平衡数据下准确率可能误导 • 实际落地需关注概率校准与阈值选择
Figure 2: 分类方法知识体系:判别式与生成式两大范式及其代表方法