20. 正则化技术:L1/L2正则化防止过拟合,确保曲面平滑
做波动率曲面平滑,最怕什么?
怕过拟合。
你辛辛苦苦拟合出一个曲面,样本点上误差几乎为零,可一到样本外,预测值就飞得离谱。这种「训练集上猛如虎,测试集上二百五」的情况,我见过太多次了。
正则化,就是专门治这个毛病的。
20.1 为什么需要正则化?
先想一个问题:我们为什么要平滑波动率曲面?
因为市场数据本身是有噪声的。同一个到期日、同一个执行价,不同做市商报的隐含波动率可能差0.5个vol。如果你用高阶多项式硬拟合,模型会拼命去「记住」这些噪声点。
结果呢?
曲面变得坑坑洼洼,delta对冲算出来的希腊字母忽大忽小。我有个朋友(真的不是我)曾经用了一个过拟合的曲面做期权定价,结果vega算出来是负的——你说这合理吗?
正则化的核心思想很简单:
- 拟合误差要小——这是基本要求
- 模型参数不能太大——这是约束条件
说白了,就是在「拟合得好」和「参数别太离谱」之间找个平衡。
20.2 L2正则化:岭回归
L2正则化,也叫岭回归(Ridge Regression)。
它的损失函数长这样:
Loss = MSE + λ * Σ(θᵢ²)
其中MSE是均方误差,θᵢ是模型参数,λ是正则化强度。
λ越大,惩罚越重,参数就越往0缩。但注意,L2不会把参数精确缩到0,只会让它变得很小。
我在实际项目中,通常把λ设成0.01到0.1之间。太小了没效果,太大了曲面会变成一条直线——那就矫枉过正了。
举个栗子。假设我们用三次样条拟合波动率曲面:
import numpy as np
from scipy.interpolate import UnivariateSpline
# 生成带噪声的波动率数据
x = np.linspace(0.1, 2.0, 20)
y_true = 0.2 + 0.1 * np.exp(-x) # 真实的波动率曲面
y_noise = y_true + np.random.normal(0, 0.02, len(x))
# 无正则化:s=0表示完全拟合数据点
spline_no_reg = UnivariateSpline(x, y_noise, s=0)
# 有正则化:s=0.1表示允许一定平滑
spline_reg = UnivariateSpline(x, y_noise, s=0.1)
# 对比一下在x=1.5处的预测值
print(f"无正则化预测: {spline_no_reg(1.5):.4f}")
print(f"有正则化预测: {spline_reg(1.5):.4f}")
print(f"真实值: {np.interp(1.5, x, y_true):.4f}")
跑一下你会发现,加了正则化的预测值更接近真实值。为什么?因为它没去拟合那些随机噪声。
20.3 L1正则化:Lasso回归
L1正则化,也叫Lasso回归。
损失函数:
Loss = MSE + λ * Σ|θᵢ|
看起来和L2就差一个平方,但效果天差地别。
L1会把不重要的参数直接变成0。这在特征选择上特别好用——你有一堆基函数,L1帮你挑出哪些是真正有用的。
但L1有个问题:如果特征之间高度相关,它会随机选一个保留,其他的干掉。这会导致结果不稳定。你换一组数据,保留的特征可能就变了。
20.4 L1 vs L2:怎么选?
| 特性 | L2(岭回归) | L1(Lasso) |
|---|---|---|
| 参数收缩 | 均匀缩小 | 部分归零 |
| 特征选择 | 不选,全保留 | 自动选择 |
| 稳定性 | 高 | 低(相关特征时) |
| 适用场景 | 所有特征都有用 | 特征冗余,需要降维 |
| 计算复杂度 | 低(有闭式解) | 较高(需迭代求解) |
我个人习惯:做波动率曲面平滑,优先用L2。因为波动率曲面的基函数(比如样条基、径向基)之间往往有相关性,L1会随机丢弃,导致曲面不连续。L2虽然不删特征,但能把参数压得很小,效果更稳定。
20.5 弹性网络:两全其美
如果你既想要L1的特征选择能力,又想要L2的稳定性,那就用弹性网络(Elastic Net)。
Loss = MSE + λ₁ * Σ|θᵢ| + λ₂ * Σ(θᵢ²)
它把L1和L2的惩罚项加在一起。两个λ可以分别调,但实际中我一般固定λ₁:λ₂ = 1:1,然后调总惩罚强度。
20.6 正则化强度怎么选?
λ选多大?这是个好问题。
太小了,正则化没效果;太大了,曲面变成一条直线,连基本的市场结构都丢了。
我常用的方法是交叉验证:
- 把数据分成K份(通常K=5或10)
- 对每个候选λ,用K-1份训练,1份验证
- 选验证集误差最小的λ
代码实现也不复杂:
from sklearn.linear_model import RidgeCV
# 候选λ值
alphas = np.logspace(-3, 1, 20)
# 自动交叉验证选λ
model = RidgeCV(alphas=alphas, cv=5)
model.fit(X_train, y_train)
print(f"最优λ: {model.alpha_:.4f}")
嗯,这里要注意:交叉验证选出来的λ,在样本外不一定最优。我一般会在选出来的λ附近再手动试几个值,看看曲面形状是否合理。
20.7 实战:正则化在波动率曲面中的应用
最后,我们看一个完整的例子。假设我们有10个期权报价,要用三次样条拟合波动率曲面:
import numpy as np
from scipy.interpolate import UnivariateSpline
import matplotlib.pyplot as plt
# 模拟数据:10个期权报价
strike = np.array([0.8, 0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.3, 1.4])
vol = np.array([0.25, 0.22, 0.20, 0.19, 0.18, 0.18, 0.19, 0.20, 0.23, 0.27])
# 加一点噪声
vol_noise = vol + np.random.normal(0, 0.01, len(vol))
# 无正则化
spline_no_reg = UnivariateSpline(strike, vol_noise, s=0)
# 有正则化(s控制平滑程度)
spline_reg = UnivariateSpline(strike, vol_noise, s=0.05)
# 预测
x_new = np.linspace(0.7, 1.5, 100)
y_no_reg = spline_no_reg(x_new)
y_reg = spline_reg(x_new)
# 对比
print("无正则化 - 参数个数:", len(spline_no_reg.get_coeffs()))
print("有正则化 - 参数个数:", len(spline_reg.get_coeffs()))
你会发现,无正则化的样条参数更多,曲线在数据点附近剧烈波动。加了正则化后,参数变少,曲线也更平滑。
我曾经在一个实盘项目中,因为没加正则化,导致曲面在远月合约上出现了「波浪形」——做市商看了直接说这曲面没法用。后来加了L2正则化,问题立刻解决。从那以后,正则化就成了我代码里的标配。