支持向量机解决的是“如何画出最稳健的边界”
本章围绕一个核心问题展开:
线性可分时,哪条分界线最值得信任?
数据重叠时,如何允许有限错误却保持泛化能力?
决策边界明显弯曲时,如何仍然使用“线性分类”的思想?
这些理论在中国股票与财务困境识别中是否有效?
先抓住三个判断标准
只要模型开始讨论‘距离边界多远’,它就已经不只是在追求分类正确,而是在追求稳健性。
只要模型开始允许少量错误,它就已经进入偏差与方差的权衡,而不是机械追求训练集满分。
只要模型开始谈核函数,它就已经把‘在原空间画曲线’改写成‘在高维空间画直线’。
这一讲不是把 SVM 当作一个黑箱分类器来背诵,而是把它拆成三步:硬间隔、软间隔、核方法。学生听完之后,应该能够明确知道什么时候用线性边界,什么时候必须引入核函数,以及为什么支持向量才是模型真正关心的观测。
SVM 从最大间隔扩展到软间隔与核方法
最大间隔分类器
线性可分时谁最稳健
间隔、支持向量、硬间隔
支持向量分类器
不可分时怎样有限犯错
松弛变量、\(C\) 、软间隔
支持向量机
非线性边界如何处理
对偶、核函数、RBF
中国数据实证
方法在现实里表现如何
海康威视、长三角企业、ROC
这一章真正要连起来的是三步扩展:
最大间隔回答的是‘线性可分时,哪条边界最稳健’。
软间隔回答的是‘样本重叠时,如何有纪律地允许错误’。
核方法回答的是‘边界弯曲时,如何继续坚持线性分类思想’。
理解 SVM 必须抓住间隔、容错与核映射三个机制
先理解‘间隔’为什么是稳健性的几何表达,再看最大间隔优化。
先把硬间隔与软间隔区分清楚,再理解参数 \(C\) 到底在约束什么。
先接受‘核函数是在替换内积’,再比较线性核、多项式核与 RBF 核。
真正需要记住的是三组对应关系 :
间隔对应稳健边界。
容错对应现实数据中的重叠与异常。
核映射对应非线性边界下继续使用线性分类思想。
超平面本质上是一条线性打分规则
在 \(p\) 维空间里,超平面由式 Equation 1 定义:
\[ \large{\beta_0 + \beta_1 X_1 + \beta_2 X_2 + \cdots + \beta_p X_p = 0} \tag{1}\]
等式左边是一个线性打分函数 。
分数为正,观测落在超平面一侧;分数为负,落在另一侧。
因而超平面并不只是几何对象,它同时是一条分类规则 。
读懂这条式子的最简方法
\(\beta_0\) 可以理解为整体平移项,它决定边界在空间中的初始位置。
\(\beta_j\) 衡量第 \(j\) 个特征把样本往哪一侧推、推得有多强。
所有特征的加权和本质上是在做一次‘总分排序’,分类只是看总分的正负号。
超平面把特征空间切成两块区域
本页图 Figure 1 直观展示了“分数符号”如何决定类别归属。
分类决策来自符号而置信度来自距离
SVM 对新样本 \(x^*\) 的判别规则是式 Equation 2 :
\[ \large{f(x^*) = \beta_0 + \beta_1 x_1^* + \cdots + \beta_p x_p^*} \tag{2}\]
若 \(f(x^*) > 0\) ,判为 \(+1\) 类。
若 \(f(x^*) < 0\) ,判为 \(-1\) 类。
若 \(|f(x^*)|\) 很大,样本离边界更远,分类更有把握。
若 \(|f(x^*)|\) 接近 \(0\) ,样本贴近边界,更容易翻转。
这里最容易混淆的两点
SVM 的默认输出首先是‘打分’而不是‘概率’,所以它更像边界判断器,而不是概率模型。
距离边界远不等于经济后果一定更大,它只说明模型在当前特征空间里更确信这次分类。
为什么 SVM 默认先给“分数”而不是“概率”
SVM 直接优化的是间隔 ,不是像逻辑回归那样直接优化概率似然。
因此它天然先输出的是:
样本位于边界哪一侧
离边界大约有多远
模型对这次分类有多’坚定’
而不是:
逻辑回归
概率
‘发生的可能性有多大’
SVM
间隔分数
‘离边界有多远’
如果业务必须要概率,通常需要在 SVM 训练之后再做概率校准 ,例如 Platt scaling。
标准化不是预处理装饰 而是在保护距离度量
SVM 之所以几乎总要先标准化,不是因为软件包习惯这样写,而是因为它依赖内积 与距离 。
\[ \large{z_{ij} = \frac{x_{ij} - \bar{x}_j}{s_j}} \tag{3}\]
如果一个变量量纲特别大,它会主导 \(\beta^\top x\) ,让边界被量纲而非结构决定。
线性核依赖内积,RBF 核依赖距离;二者都会被尺度差异直接扭曲。
所以标准化不是让数据‘更好看’,而是让‘谁离边界近’这件事重新变得可信。
先看一个反例
若资产负债率大多在 \(0\) 到 \(2\) 之间,而营业收入以亿元计量,未标准化时,收入变量会在数值上压过杠杆变量。
这时模型看起来像是在学分类,实际上却是在被单位差异牵着走。
因而先标准化,再谈 \(C\) 、\(\gamma\) ,才是 SVM 的正确顺序。
忽略标准化时 模型会把量纲误当成结构
假设资产负债率主要在 \(0\) 到 \(2\) 之间,而营业收入以亿元计量,二者数量级天生不同。
如果不先标准化,收入变量会在数值上压过杠杆变量,边界就可能被单位差异主导。
这样学出来的并不是企业风险结构,而更像是“哪个变量的数字写得更大”。
真正的课堂判断是
只要模型依赖距离与内积,就不能把标准化当成可有可无的装饰。
先让量纲可比,再讨论 \(C\) 、\(\gamma\) 与核函数,模型调参才有真实含义。
最大间隔意味着最稳健的线性边界
最大间隔分类器并不满足于“分开就行”,而要让最近样本到边界的最小距离尽可能大。
硬间隔优化把“最大间隔”写成凸优化问题
硬间隔 SVM 的核心目标见式 Equation 4 :
\[ \large{\max_{\beta_0,\beta,M}\; M} \tag{4}\]
同时满足:
\[ \large{\sum_{j=1}^{p} \beta_j^2 = 1} \tag{5}\]
\[ \large{y_i (\beta_0 + \beta^\top x_i) \ge M,\quad i = 1,\ldots,n} \tag{6}\]
约束式 Equation 5 固定法向量长度,否则同一超平面可被任意缩放。
约束式 Equation 6 要求所有样本不仅分对,还至少离边界 \(M\) 那么远。
因而最优解就是最近样本距离最大 的那条边界。
为什么这一步值得单独学
它把‘边界要稳’从一句直觉,改写成了一个可求解的优化目标。
它说明 SVM 不是先分对再微调,而是一开始就把最危险样本放到目标函数中心。
它也为后面的对偶形式和核技巧做好铺垫,因为后续所有扩展都建立在这个优化框架上。
读这个优化问题的顺序
先看约束式 Equation 5 :它先把尺度固定住,避免同一条边界只靠数值放大假装间隔更大。
再看约束式 Equation 6 :每个样本都必须被分到正确一侧,而且至少离边界 \(M\) 那么远。
最后再看目标:真正被最大化的不是平均安全感,而是最近样本的最小安全边际。
线性不可分时 硬间隔问题会直接无解
现实数据常有噪声、异常值和类别重叠。
一旦存在哪怕少量混叠样本,式 Equation 6 就不可能同时成立。
这说明“绝不犯错”的分类规则在现实里往往过于理想化。
直观理解
真实金融数据不是教科书里的整齐点云。公司财务指标、收益率和波动率都会同时受行业冲击、政策扰动和测量误差影响,因此完全分开的两团样本几乎从来不存在。
课堂判断
如果你已经观察到少量离群点把边界拖得非常别扭,就不要再执着于硬间隔。
如果任务本身噪声很高,例如短期收益方向预测,硬间隔通常不是合理起点。
软间隔把“绝不犯错”改成“有限预算犯错”
支持向量分类器的核心妥协是:
允许少数样本进入间隔带。
甚至允许极少数样本被错分。
但这些违规必须受到总预算约束。
预算越紧,模型越努力贴合训练集;预算越松,模型越强调稳健边界。
把这页翻译成管理语言
硬间隔像‘零容错制度’,任何例外都不允许。
软间隔像‘有限容错制度’,允许例外,但每个例外都要记账。
真正高质量的模型不是从不犯错,而是把错误控制在最值得容忍的位置上。
软间隔优化问题把松弛变量与参数 \(C\) 结合起来
软间隔 SVM 的优化问题见式 Equation 7 到式 Equation 9 :
\[ \large{\max_{\beta_0,\beta,\epsilon_1,\ldots,\epsilon_n,M}\; M} \tag{7}\]
\[ \large{y_i(\beta_0 + \beta^\top x_i) \ge M(1-\epsilon_i)} \tag{8}\]
\[ \large{\epsilon_i \ge 0,\quad \sum_{i=1}^{n} \epsilon_i \le C} \tag{9}\]
\(\epsilon_i\)
第 \(i\) 个样本的违规程度
单个样本对边界提出的“例外要求”
\(C\)
总违规预算
模型能承受多少训练误差
大 \(C\)
更愿意容忍违规
高偏差,低方差
小 \(C\)
更少容忍违规
低偏差,高方差
本章这套记号里要特别记住 :\(C\) 是违规总量的上界,所以 \(C\) 越大,预算越宽松;不要和另一类把 \(C\) 写成惩罚系数的教材机械混用。
读这一页时只抓三层 :
\(\epsilon_i\) 是逐样本违规账单。
\(C\) 是全局总预算。
真正该调的不是训练集零错误,而是样本外边界是否更稳。
松弛变量把违规程度分成三种状态
当 \(\epsilon_i = 0\) 时,样本不但分对了,而且位于安全间隔之外。
当 \(0 < \epsilon_i \le 1\) 时,样本虽然还没被分错,但已经挤进了危险带。
当 \(\epsilon_i > 1\) 时,样本已经跑到错误一侧,属于真正错分。
理解这一页时只记一句话
\(C\) 从来不是孤立理解的,它决定的是‘你愿意为了减少训练误差付出多大复杂度代价’。
支持向量之所以重要 是因为只有它们的乘子非零
在线性 SVM 的对偶表示中,决策函数可写作式 Equation 10 :
\[ \large{f(x) = \beta_0 + \sum_{i \in S} \alpha_i \langle x, x_i \rangle} \tag{10}\]
集合 \(S\) 是支持向量索引集合。
非支持向量对应 \(\alpha_i = 0\) ,不会影响最终边界。
因而 SVM 天然具有稀疏决策骨架 。
这也是它对远离边界样本更鲁棒的根源。
和普通样本相比 支持向量特殊在哪里
它们离边界最近,所以最能改变最优超平面的位置。
它们通常位于间隔上、间隔内,或已经被错分。
删除少量非支持向量,边界往往变化不大;删除关键支持向量,边界可能明显改写。
补充说明:拉格朗日对偶的价值
对偶形式把原问题改写成“只依赖样本内积”的优化。这样一来,我们就不必显式构造高维特征,只需要替换内积即可进入核方法。
对偶问题把参数优化改写成样本关系优化
原问题直接求的是参数 \(\beta_0\) 与 \(\beta\) ;对偶问题则把注意力转到每个样本的权重 \(\alpha_i\) 上。
\[ \large{\max_{\alpha}\; \sum_{i=1}^{n} \alpha_i - \frac{1}{2} \sum_{i=1}^{n} \sum_{k=1}^{n} \alpha_i \alpha_k y_i y_k \langle x_i, x_k \rangle} \tag{11}\]
原问题问的是‘边界参数怎么选’。
对偶问题问的是‘哪些样本真正有资格影响边界’。
一旦写成对偶形式,模型就只看样本之间的内积,核函数才能自然接进来。
把对偶问题翻译成直观的话
原问题像在直接画线。
对偶问题像先给每个样本投票权,再由高票的边缘样本共同决定那条线。
因而 \(\alpha_i \ne 0\) 的样本才会留下,非支持向量自动退出决策骨架。
核技巧把非线性问题转化为高维线性问题
图 Figure 3 用两栏对比展示核技巧的核心思想。
常见核函数决定了边界的弯曲方式
线性核
\(K(x_i,x_j)=x_i^\top x_j\)
高维稀疏、近似线性
\(C\)
多项式核
\(K(x_i,x_j)=(1+x_i^\top x_j)^d\)
明显交互项
\(C,d\)
RBF 核
\(K(x_i,x_j)=\exp(-\gamma\lVert x_i-x_j \rVert^2)\)
边界弯曲、局部结构强
\(C,\gamma\)
\(\gamma\) 大,单个样本影响更局部,边界更蜿蜒。
\(\gamma\) 小,影响范围更广,边界更平滑。
在实践中,\(C\) 与 \(\gamma\) 必须联动调优。
一个实用选择顺序
先用线性核做基线,确认是否真的需要非线性边界。
若线性核明显欠拟合,再尝试 RBF 核,而不是一开始就追求复杂核。
若样本不多但局部结构明显,RBF 核通常比高阶多项式核更稳健。
如果 RBF 只带来很小提升,却显著增加调参和解释成本,优先保留更简单的线性边界。
RBF 核里的 \(\gamma\) 可以理解成“单个样本的影响半径”
RBF 核的形式是:
\[ \large{K(x_i, x_j)=\exp\left(-\gamma \lVert x_i - x_j \rVert^2\right)} \]
其中 \(\gamma\) 最容易用’影响半径’来理解:
\(\gamma\) 大 :每个样本只影响自己附近很小一圈区域,边界更容易弯来弯去
\(\gamma\) 小 :每个样本会影响更大范围,边界更平滑
\(\gamma\) 太大
很碎、很贴样本
过拟合
\(\gamma\) 太小
很平、很钝
欠拟合
最实用的记忆法 :
\(C\) 主要在管’允许犯多少错’
\(\gamma\) 主要在管’边界弯得有多局部’
Mercer 条件保证“核函数 = 合法内积”
核函数不是任意相似度函数都可以,必须保证核矩阵半正定:
\[ \large{\mathbf{v}^\top \mathbf{K}\mathbf{v} = \sum_{i=1}^{n}\sum_{j=1}^{n} v_i v_j K(x_i,x_j) \ge 0} \tag{12}\]
若条件成立,就存在某个映射 \(\phi(x)\) 使得 \(K(x,z)=\langle \phi(x), \phi(z) \rangle\) 。
这就是“不显式进入高维空间,却在高维空间里做线性分类 ”的数学基础。
RBF 核尤其重要,因为它对应的是无限维特征空间。
多类 SVM 依赖“拆解问题”而不是直接改写超平面
一对一
每两个类别训练一个分类器
每个子问题更简单
需要训练 \(\binom{K}{2}\) 个模型
一对其余
每个类别对其余类别训练一个分类器
结构清晰
类别不平衡更明显
预测时,一对一通常用投票决定类别。
一对其余通常比较各分类器给出的分数大小。
SVM 与逻辑回归共享“损失函数 + 正则化”框架
支持向量分类器可以写成式 Equation 13 :
\[ \large{\min_{\beta_0,\beta}\; \sum_{i=1}^{n}\max\{0,1-y_i f(x_i)\} + \lambda \sum_{j=1}^{p}\beta_j^2} \tag{13}\]
前半部分是铰链损失 ,只惩罚贴边或越界样本。
后半部分是 \(L_2\) 正则项,限制模型复杂度。
与逻辑回归相比,SVM 更强调“安全间隔”,逻辑回归更强调概率拟合。
铰链损失只关心“没有安全过线”的样本
图 Figure 4 直接比较铰链损失与逻辑回归损失。
Code
import numpy as np # 导入 NumPy 以生成自变量网格。
import matplotlib.pyplot as plt # 导入 Matplotlib 以绘制损失函数图形。
plt.rcParams['font.family' ] = ['Source Han Serif SC' , 'Noto Serif CJK SC' , 'SimSun' ] # 指定中文图表优先使用课程统一的中文衬线字体与后备字体。
plt.rcParams['axes.unicode_minus' ] = False # 关闭负号替换以避免坐标轴负号显示异常。
margin_grid = np.linspace(- 4 , 4 , 400 ) # 构造从严重错分到安全正确分类的间隔取值网格。
hinge_loss = np.maximum(0 , 1 - margin_grid) # 根据定义计算 SVM 的铰链损失。
logistic_loss = np.log1p(np.exp(- margin_grid)) # 计算逻辑回归对应的对数损失曲线。
figure, axis = plt.subplots(figsize= (10 , 6 )) # 创建单图画布用于比较两条损失曲线。
axis.plot(margin_grid, hinge_loss, color= '#2563EB' , linewidth= 3 , label= '铰链损失' ) # 绘制铰链损失曲线并突出其折线结构。
axis.plot(margin_grid, logistic_loss, color= '#DC2626' , linewidth= 3 , label= '逻辑回归损失' ) # 绘制逻辑回归损失曲线并展示其平滑衰减。
axis.axvline(1 , color= '#6B7280' , linestyle= '--' , linewidth= 1.5 ) # 用虚线标出安全间隔阈值 1。
axis.text(1.08 , 0.55 , '安全间隔阈值' , color= '#6B7280' , fontsize= 11 ) # 直接在图上标出阈值的经济含义。
axis.set_xlabel('$y_i f(x_i)$' , fontsize= 12 ) # 设置横轴为带标签的间隔量。
axis.set_ylabel('损失值' , fontsize= 12 ) # 设置纵轴为损失函数取值。
axis.set_title('SVM 只持续惩罚未安全过线的样本' , fontsize= 14 ) # 总结图形主结论。
axis.grid(alpha= 0.25 ) # 添加浅色网格便于比较曲线差异。
axis.legend(fontsize= 11 ) # 显示图例以区分两种损失。
plt.tight_layout() # 自动压缩边距避免标题与文字被裁切。
plt.show() # 输出最终图形。
实证一:海康威视的次日涨跌方向几乎不可预测
我们用长三角企业海康威视的真实股价数据构造一个二分类任务:
样本:海康威视后复权日线行情。
特征:滞后收益率、20 日波动率、相对均线偏离度。
任务:预测下一交易日是上涨还是下跌。
评估:时间序列切分 + 交叉验证选取 \(C\) 与 \(\gamma\) 。
海康威视案例的真实结果接近随机猜测
表 Table 1 给出时间序列检验后的结果汇总。
Code
import os # 导入操作系统库以兼容 Windows 与 Linux 路径。
import pandas as pd # 导入 Pandas 以处理海康威视价格序列。
from sklearn.model_selection import GridSearchCV, TimeSeriesSplit # 导入时间序列交叉验证与网格搜索工具。
from sklearn.preprocessing import StandardScaler # 导入标准化器以统一特征量纲。
from sklearn.svm import SVC # 导入支持向量分类器。
data_dir = 'C:/qiufei/data' if os.name == 'nt' else '/home/ubuntu/r2_data_mount/data' # 根据系统选择本地数据目录。
price_data = pd.read_hdf(os.path.join(data_dir, 'stock/stock_price_post_adjusted.h5' ), where= "order_book_id='002415.XSHE'" ).reset_index() # 只读取海康威视的后复权行情。
price_data = price_data.sort_values('date' ).copy() # 按日期升序排列以保持时间顺序。
price_data['ret' ] = price_data['close' ].pct_change() # 用收盘价计算日收益率。
for lag_period in [1 , 2 , 3 , 5 ]: # 遍历常见短期动量滞后期。
price_data[f'lag_ { lag_period} ' ] = price_data['ret' ].shift(lag_period) # 生成滞后收益率特征。
price_data['vol_20' ] = price_data['ret' ].rolling(20 ).std().shift(1 ) # 计算滞后一日的 20 日滚动波动率。
price_data['ma_20' ] = price_data['close' ].rolling(20 ).mean().shift(1 ) # 计算滞后一日的 20 日均线水平。
price_data['dist_ma20' ] = price_data['close' ].shift(1 ) / price_data['ma_20' ] - 1 # 构造价格相对均线的偏离度。
price_data['target' ] = 1 # 先默认把目标变量设为上涨类别。
price_data.loc[price_data['ret' ] <= 0 , 'target' ] = - 1 # 将当日非正收益样本标记为下跌类别。
analysis_sample = price_data.dropna().iloc[- 1500 :].copy() # 删除缺失值并保留最近 1500 个交易日样本。
feature_columns = [column for column in analysis_sample.columns if column.startswith('lag_' )] + ['vol_20' , 'dist_ma20' ] # 选出全部技术指标特征列。
split_index = int (len (analysis_sample) * 0.8 ) # 按时间顺序计算训练集与测试集分界点。
train_x = analysis_sample[feature_columns].iloc[:split_index] # 取前 80% 样本作为训练特征。
test_x = analysis_sample[feature_columns].iloc[split_index:] # 取后 20% 样本作为测试特征。
train_y = analysis_sample['target' ].iloc[:split_index] # 取前 80% 样本标签作为训练标签。
test_y = analysis_sample['target' ].iloc[split_index:] # 取后 20% 样本标签作为测试标签。
scaler = StandardScaler() # 创建标准化器以防止尺度大的变量主导内积。
train_x_scaled = scaler.fit_transform(train_x) # 在训练集上拟合均值与标准差并完成标准化。
test_x_scaled = scaler.transform(test_x) # 用训练集参数变换测试集避免信息泄露。
svm_grid = GridSearchCV(SVC(kernel= 'rbf' ), {'C' : [0.5 , 2 , 10 ], 'gamma' : [0.01 , 0.1 , 1 ]}, cv= TimeSeriesSplit(n_splits= 3 ), scoring= 'accuracy' , n_jobs= 1 ) # 为 RBF 核配置轻量但有效的参数网格。
svm_grid.fit(train_x_scaled, train_y) # 在时间序列交叉验证框架下搜索最优超参数。
GridSearchCV(cv=TimeSeriesSplit(gap=0, max_train_size=None, n_splits=3, test_size=None),
estimator=SVC(), n_jobs=1,
param_grid={'C': [0.5, 2, 10], 'gamma': [0.01, 0.1, 1]},
scoring='accuracy') In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
Code
from sklearn.metrics import accuracy_score, f1_score # 导入准确率与 F1 指标用于结果汇总。
test_predictions = svm_grid.best_estimator_.predict(test_x_scaled) # 用最优模型对测试集做方向预测。
pd.DataFrame({ # 构造便于课堂阅读的结果表。
'指标' : ['最佳 C' , '最佳 gamma' , '交叉验证准确率' , '测试集准确率' , '测试集 F1' ], # 设置输出行名称。
'结果' : [svm_grid.best_params_['C' ], svm_grid.best_params_['gamma' ], round (svm_grid.best_score_, 4 ), round (accuracy_score(test_y, test_predictions), 4 ), round (f1_score(test_y, test_predictions, pos_label= 1 ), 4 )] # 汇总最关键的评估结果。
})
结论
测试集准确率大约只有 \(0.49\) ,几乎等同于随机猜测。这说明仅依赖公开技术指标,哪怕使用 RBF 核 SVM,也难以稳定预测下一日涨跌方向。
这页真正要学到的不是‘结果低’,而是‘为什么低’
短期方向本来就高度噪声化,很多日度波动并不携带稳定结构。
交叉验证略优但样本外失败,通常说明模型学到的是历史偶然模式。
一个诚实的负结果本身就是重要发现,它提醒我们问题可能比模型更难。
实证二:长三角企业财务困境识别更适合 SVM 发挥
第二个案例改用更稳定的结构性问题:识别长三角上市公司的财务困境。
地域:上海、江苏、浙江、安徽。
特征:流动比率、资产负债率、销售净利率。
标签:净利润为负记为财务困境。
对比:LDA、线性 SVC、RBF SVC。
长三角财务困境的 ROC 曲线显示 SVM 表现突出
图 Figure 5 对比了长三角真实财务数据上的 ROC 曲线。
Code
import os # 导入操作系统库以兼容跨平台数据路径。
import pandas as pd # 导入 Pandas 以拼接财务表与公司基本信息表。
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis # 导入 LDA 作为线性基准方法。
from sklearn.model_selection import train_test_split # 导入训练测试集划分工具。
from sklearn.preprocessing import StandardScaler # 导入标准化器以服务 SVM 训练。
from sklearn.svm import SVC # 导入支持向量分类器。
data_dir = 'C:/qiufei/data' if os.name == 'nt' else '/home/ubuntu/r2_data_mount/data' # 根据系统定位本地金融数据路径。
financial = pd.read_hdf(os.path.join(data_dir, 'stock/financial_statement.h5' ), columns= ['order_book_id' , 'quarter' , 'current_assets' , 'current_liabilities' , 'total_assets' , 'total_liabilities' , 'net_profit' , 'operating_revenue' ]) # 选择性读取财务困境识别所需字段。
stock_basic = pd.read_hdf(os.path.join(data_dir, 'stock/stock_basic_data.h5' ))[['order_book_id' , 'symbol' , 'province' ]] # 读取公司基本信息中的简称与省份字段。
yrd_panel = financial.merge(stock_basic[stock_basic['province' ].isin(['上海市' , '江苏省' , '浙江省' , '安徽省' ])], on= 'order_book_id' , how= 'inner' ) # 将样本限制在长三角上市公司范围内。
yrd_panel = yrd_panel[yrd_panel['quarter' ] >= '2020q1' ].copy() # 仅保留近年财务样本以提升现实相关性。
yrd_panel['current_ratio' ] = yrd_panel['current_assets' ] / (yrd_panel['current_liabilities' ] + 1 ) # 计算流动比率衡量短期偿债能力。
yrd_panel['debt_ratio' ] = yrd_panel['total_liabilities' ] / (yrd_panel['total_assets' ] + 1 ) # 计算资产负债率衡量杠杆压力。
yrd_panel['profit_margin' ] = yrd_panel['net_profit' ] / (yrd_panel['operating_revenue' ] + 1 ) # 计算销售净利率衡量盈利质量。
yrd_panel['distress' ] = (yrd_panel['net_profit' ] < 0 ).astype(int ) # 将亏损定义为财务困境标签。
yrd_panel = yrd_panel[['current_ratio' , 'debt_ratio' , 'profit_margin' , 'distress' ]].replace([float ('inf' ), float ('-inf' )], pd.NA).dropna() # 清除无穷值与缺失值。
yrd_panel = yrd_panel[(yrd_panel['current_ratio' ].between(0 , 15 )) & (yrd_panel['debt_ratio' ].between(0 , 2 )) & (yrd_panel['profit_margin' ].between(- 1 , 1 ))] # 保留合理财务区间内的样本。
distressed = yrd_panel[yrd_panel['distress' ] == 1 ] # 取出亏损企业样本。
healthy = yrd_panel[yrd_panel['distress' ] == 0 ] # 取出健康企业样本。
sample_size = min (len (distressed), len (healthy), 300 ) # 设定平衡抽样规模以控制运行时间并避免类别失衡。
balanced_panel = pd.concat([distressed.sample(sample_size, random_state= 42 , replace= len (distressed) < sample_size), healthy.sample(sample_size, random_state= 42 )], ignore_index= True ) # 生成平衡训练样本。
train_x, test_x, train_y, test_y = train_test_split(balanced_panel[['current_ratio' , 'debt_ratio' , 'profit_margin' ]], balanced_panel['distress' ], test_size= 0.3 , random_state= 42 , stratify= balanced_panel['distress' ]) # 按分层抽样划分训练集与测试集。
scaler = StandardScaler() # 创建标准化器以统一财务指标量纲。
train_x_scaled = scaler.fit_transform(train_x) # 在训练集上拟合并转换特征。
test_x_scaled = scaler.transform(test_x) # 用训练集参数变换测试集特征。
classification_models = {'LDA' : LinearDiscriminantAnalysis(), '线性 SVC' : SVC(kernel= 'linear' , C= 1 , probability= True , random_state= 42 ), 'RBF SVC' : SVC(kernel= 'rbf' , C= 1 , gamma= 0.5 , probability= True , random_state= 42 )} # 同时配置三类候选模型供 ROC 比较。
for model in classification_models.values(): # 遍历模型集合完成拟合。
model.fit(train_x_scaled, train_y) # 在标准化训练集上拟合各自分类器。
Code
import matplotlib.pyplot as plt # 导入 Matplotlib 以绘制 ROC 曲线。
from sklearn.metrics import auc, roc_curve # 导入 ROC 曲线与 AUC 计算函数。
plt.rcParams['font.family' ] = ['Source Han Serif SC' , 'Noto Serif CJK SC' , 'SimSun' ] # 指定中文图表优先使用课程统一的中文衬线字体与后备字体。
plt.rcParams['axes.unicode_minus' ] = False # 关闭负号替换以避免坐标轴负号显示异常。
figure, axis = plt.subplots(figsize= (10 , 6 )) # 创建宽幅画布以便展示多模型 ROC 曲线。
for model_name, fitted_model in classification_models.items(): # 遍历每个已拟合模型并生成 ROC 曲线。
positive_probability = fitted_model.predict_proba(test_x_scaled)[:, 1 ] # 提取测试集中属于困境类的预测概率。
false_positive_rate, true_positive_rate, _ = roc_curve(test_y, positive_probability) # 计算 ROC 曲线所需的坐标点。
roc_auc_value = auc(false_positive_rate, true_positive_rate) # 计算当前模型的 AUC 值。
axis.plot(false_positive_rate, true_positive_rate, linewidth= 2.5 , label= f' { model_name} (AUC= { roc_auc_value:.3f} )' ) # 将各模型 ROC 曲线绘制到同一坐标系中。
axis.plot([0 , 1 ], [0 , 1 ], linestyle= '--' , color= '#6B7280' , linewidth= 1.5 , label= '随机猜测' ) # 添加随机猜测基线供比较。
axis.set_xlabel('假正率' , fontsize= 12 ) # 设置横轴为假正率。
axis.set_ylabel('真正率' , fontsize= 12 ) # 设置纵轴为真正率。
axis.set_title('长三角财务困境识别:SVM 与 LDA 的 ROC 对比' , fontsize= 14 ) # 概括图形的实证主题。
axis.grid(alpha= 0.25 ) # 添加浅色网格帮助读取 ROC 曲线差异。
axis.legend(loc= 'lower right' , fontsize= 10 ) # 在右下角显示图例与 AUC 数值。
plt.tight_layout() # 自动压缩边距避免标签重叠。
plt.show() # 输出 ROC 比较图。
课堂结论
在这类低维但可能存在非线性分界的财务问题上,SVM 的优势比在短期股价方向预测中更容易显现,因为财务困境往往具有更稳定的结构特征。
这张图先留下一个总判断
在这类结构性风险任务里,SVM 的优势更容易通过整条 ROC 曲线而不是单一准确率显现出来。
真正进入业务决策时,还需要进一步结合阈值偏好来解读曲线不同区段的差异。
ROC 图不是只看 AUC 还要看阈值取舍
ROC 图的横轴是假正率,纵轴是真正率,它展示的是:当阈值不断移动时,误报与漏报怎样一起变化 。
曲线是否压住对角线
是否整体高于随机猜测
模型是否真的学到结构
左上角附近是否更饱满
低误报下能否保留较高真正率
风控预警是否实用
AUC 是否显著拉开
模型领先幅度大不大
是否值得付出更多复杂度
读图时先回答三个问题
哪条曲线整体更靠近左上角?这说明它在更多阈值下都更稳。
AUC 差异是轻微领先还是明显领先?这决定我们是否值得付出更多模型复杂度。
任务更怕漏报还是误报?这决定我们最后不能只看 AUC,还要看业务阈值选择。
容易误判的地方
AUC 更高,不等于任何阈值下都更适合业务。
如果银行更怕漏掉高风险企业,就要优先看高真正率区域,而不是只看整条曲线面积。
如果误报成本很高,就要重点看低假正率区间谁更稳。
实务上 \(C\) 与 \(\gamma\) 决定了模型是“过硬”还是“过弯”
训练集几乎完美、测试集变差
\(C\) 太大或 \(\gamma\) 太大
降低 \(C\) 或降低 \(\gamma\)
边界过于平滑、欠拟合严重
\(C\) 太小或 \(\gamma\) 太小
提高 \(C\) 或提高 \(\gamma\)
少数尺度大的变量主导结果
特征未标准化
先标准化再调参
多类任务训练很慢
模型数太多或核过重
先试线性核或降维
推荐的最小调参流程
第一步先标准化并跑线性核,判断线性边界是否已经足够。
第二步再切到 RBF 核,同时做 \(C\) 与 \(\gamma\) 的网格搜索。
第三步只看时间切分或交叉验证结果,不用训练集准确率自我安慰。
如果线性核已经接近 RBF 核表现,就优先保留更简单的线性边界。
何时优先考虑 SVM 而不是其他分类器
优先用 SVM :样本不算特别大、边界可能非线性、需要稳健间隔。
优先用逻辑回归 :需要概率解释、系数解释和更简单的基线。
优先用树模型 :特征有复杂非单调关系,且可解释性依赖规则分裂。
优先用深度学习 :样本极大、特征表达需要自动学习。
不要期待过高的场景 :极低信噪比的短期方向预测,即使使用复杂核函数,也往往很难稳定优于随机猜测。
本章小结:SVM 是“最大间隔 + 有限违规 + 核技巧”的统一框架
最大间隔分类器解决线性可分问题。
支持向量分类器通过软间隔处理不可分样本。
核技巧把非线性边界转化为高维线性边界。
只有支持向量真正决定模型。
在中国金融数据上,SVM 更适合结构性分类,而不是短期方向预测。
本章三句工作口令 :
SVM 首先是边界模型,它最关心的是离边界最近的样本。
SVM 不是拒绝犯错,而是把错误纳入可控预算。
SVM 的非线性能力来自核函数替换内积,而不是直接在原空间里硬画复杂曲线。
读任何 SVM 结果前先问三件事 :数据是否标准化、评估是否样本外、当前任务是否真的存在可学习边界。
最小路线图 :先用线性核判断有没有基本边界,再用 RBF 核检查是否存在稳定非线性,最后才讨论是否值得为那一点精度增益付出更高复杂度。
理论前沿说明 SVM 仍未过时
小样本高维问题中,SVM 往往比深度学习更稳定。
Nyström 近似与随机特征让核方法可扩展到更大样本。
在文本、金融风控、医学筛查等任务中,最大间隔思想仍然十分重要。
前沿提醒
今天即便不直接使用经典 SVM,很多现代方法仍在借用它的思想,例如大间隔训练、核近似和对偶优化。