Python 机器学习算法交易实用指南(二)(1)https://developer.aliyun.com/article/1523316
对于第二阶段,我们对横截面投资组合的期间收益进行 96 次回归,以估计因子载荷:
lambdas = [] for period in ff_portfolio_data.index: step2 = OLS(endog=ff_portfolio_data.loc[period, betas.index], exog=betas).fit() lambdas.append(step2.params) lambdas = pd.DataFrame(lambdas, index=ff_portfolio_data.index, columns=betas.columns.tolist()) lambdas.info() PeriodIndex: 96 entries, 2010-01 to 2017-12 Freq: M Data columns (total 5 columns): Mkt-RF 96 non-null float64 SMB 96 non-null float64 HML 96 non-null float64 RMW 96 non-null float64 CMA 96 non-null float64
最后,我们计算 96 个周期的平均值,以获取我们的因子风险溢价估计值:
lambdas.mean() Mkt-RF 1.201304 SMB 0.190127 HML -1.306792 RMW -0.570817 CMA -0.522821
linear_models
库通过各种面板数据模型扩展了statsmodels
,并且还实现了两阶段法马—麦克贝斯程序:
model = LinearFactorModel(portfolios=ff_portfolio_data, factors=ff_factor_data) res = model.fit()
这为我们提供了相同的结果:
线性因子模型估计摘要
随附的笔记本通过在估计较大的个别股票风险溢价时使用行业虚拟变量来说明了分类变量的使用。
缩小方法:线性回归的正则化
当高斯—马尔可夫假设得到满足时,最小二乘法用于训练线性回归模型将会产生最佳、线性和无偏的系数估计。即使 OLS 关于误差协方差矩阵的假设被违反,类似 GLS 的变种也会表现得相当好。然而,有一些估计器会产生偏斜的系数,以减少方差以达到更低的总体泛化误差。
当线性回归模型包含许多相关变量时,它们的系数将被较差地确定,因为大正系数对 RSS 的影响可能会被相关变量上的同样大的负系数抵消。因此,由于系数的这种摆动空间,模型将具有高方差的倾向,增加模型过度拟合样本的风险。
如何对抗过度拟合
控制过拟合的一种流行技术是正则化,它涉及将惩罚项添加到误差函数中,以阻止系数达到较大的值。换句话说,对系数的大小施加约束可以缓解结果对样本外预测的潜在负面影响。由于过拟合是如此普遍的问题,我们将在所有模型中遇到正则化方法。
在本节中,我们将介绍缩小方法,以解决改进迄今为止讨论的线性模型方法的两个动机:
- 预测准确性:最小二乘估计的低偏差但高方差表明,通过收缩或将某些系数设置为零,可以减少泛化误差,从而在略微增加偏差的同时减少模型的方差。
- 解释性:大量的预测变量可能会使结果的解释或传达变得复杂化。牺牲一些细节以限制模型只包括具有最强效果的参数子集可能更可取。
收缩模型通过对其大小施加惩罚来限制回归系数。这些模型通过向目标函数添加一个项来实现此目标,使得收缩模型的系数最小化 RSS 加上一个与系数(绝对值)大小正相关的惩罚项。添加的惩罚将线性回归系数的查找转变为一个约束最小化问题,通常采用以下拉格朗日形式:
正则化参数 λ 决定了惩罚效果的大小,即正则化的强度。一旦 λ 为正,系数将与无约束的最小二乘参数不同,这意味着一个有偏的估计。超参数 λ 应该通过交叉验证来自适应地选择,以最小化预测误差的估计。
收缩模型的区别在于它们如何计算惩罚,即 S 的函数形式。最常见的版本是岭回归,它使用了系数平方的和,而套索模型则基于系数绝对值的和来设置惩罚。
岭回归的工作原理
岭回归通过将惩罚添加到目标函数中来缩小回归系数,该惩罚等于系数的平方和,进而对应于系数向量的 L² 范数:
因此,岭回归系数定义如下:
截距已从惩罚中排除,以使程序独立于为输出变量选择的原点——否则,将常数添加到所有输出值将改变所有斜率参数而不是平行移位。
重要的是通过从每个输入中减去相应的均值并将结果除以输入的标准差来标准化输入,因为岭解对输入的尺度敏感。岭估计器也有一个类似 OLS 情况的闭式解:
在求逆之前,解决方案将缩放后的单位矩阵λI添加到XTX*中,这保证了问题是非奇异的,即使*XT**X没有满秩。这是最初引入该估计量时的动机之一。
岭惩罚导致所有参数的成比例收缩。在正交输入的情况下,岭估计只是最小二乘估计的缩放版本,即:
使用输入矩阵X的奇异值分解(SVD),我们可以深入了解在更常见的情况下,收缩如何影响非正交输入。中心矩阵的 SVD 表示矩阵的主成分(参见第十一章,梯度提升机,有关无监督学习的内容),按方差降序捕获数据的列空间中的不相关方向。
岭回归会收缩与数据中方差较小的方向相关联的输入变量的系数,而不是收缩与展现更多方差的方向相关联的输入变量的系数。因此,岭回归的隐含假设是,在数据中变化最大的方向在预测输出时最有影响力或最可靠。
套索回归的工作原理
套索,在信号处理中称为基 Pursuit,通过向残差的平方和添加惩罚来缩小系数,但套索惩罚具有稍微不同的效果。套索惩罚是系数向量的绝对值的总和,对应于其 L¹范数。因此,套索估计由以下方式定义:
与岭回归类似,输入需要被标准化。套索惩罚使解决方案非线性,并且与岭回归不同,系数没有闭式表达式。相反,套索解是一个二次规划问题,有可用的高效算法可以计算出对于不同λ值产生的系数整个路径,其计算成本与岭回归相同。
Lasso 惩罚的效果是随着正则化的增加逐渐将一些系数减少到零。因此,Lasso 可用于连续选择一组特征。
如何使用线性回归预测回报
笔记本 linear_regression.ipynb
包含使用 statsmodels
和 sklearn
进行 OLS 的股价预测的示例,以及岭回归和 Lasso 模型。它旨在在 Quantopian 研究平台上作为笔记本运行,并依赖于 第四章 中介绍的 factor_library
,Alpha Factors Research。
准备数据
我们需要选择一组股票和一个时间范围,构建和转换我们将用作特征的 alpha 因子,计算我们希望预测的前瞻回报,并可能清理我们的数据。
宇宙创建和时间范围
我们将使用 2014 年和 2015 年的股票数据,来自使用内置过滤器、因子和分类器选择最后 200 个交易日的平均美元成交量最高的 100 支股票的自定义 Q100US
宇宙,根据额外的默认标准进行筛选(请参阅 GitHub 上链接的 Quantopian 文档以获取详细信息)。该宇宙会根据筛选标准动态更新,因此,在任何给定点上可能有 100 支股票,但样本中可能有超过 100 个不同的股票:
def Q100US(): return filters.make_us_equity_universe( target_size=100, rankby=factors.AverageDollarVolume(window_length=200), mask=filters.default_us_equity_universe_mask(), groupby=classifiers.fundamentals.Sector(), max_group_weight=0.3, smoothing_func=lambda f: f.downsample('month_start'), )
目标回报计算
我们将测试不同 lookahead
期间的预测,以确定产生最佳可预测性的最佳持有期,该期间由信息系数衡量。更具体地说,我们使用内置的 Returns
函数计算 1、5、10 和 20 天的收益,结果是在两年内(每年大约有 252 个交易日)对 100 支股票的宇宙产生超过 50,000 个观察值:
lookahead = [1, 5, 10, 20] returns = run_pipeline(Pipeline({'Returns{}D'.format(i): Returns(inputs=[USEquityPricing.close], window_length=i+1, mask=UNIVERSE) for i in lookahead}, screen=UNIVERSE), start_date=START, end_date=END) return_cols = ['Returns{}D'.format(i) for i in lookahead] returns.info() MultiIndex: 50362 entries, (2014-01-02 00:00:00+00:00, Equity(24 [AAPL])) to (2015-12-31 00:00:00+00:00, Equity(47208 [GPRO])) Data columns (total 4 columns): Returns10D 50362 non-null float64 Returns1D 50362 non-null float64 Returns20D 50360 non-null float64 Returns5D 50362 non-null float64
Alpha 因子选择和转换
我们将使用超过 50 个涵盖市场、基本和替代数据的各种因素的特征。笔记本还包括自定义转换,将通常以季度报告频率提供的基本数据转换为滚动年度总额或平均值,以避免过度季节波动。
一旦通过 第四章 中概述的各种流水线计算出因子,Alpha 因子研究,我们使用 pd.concat()
将它们组合起来,分配索引名称,并创建一个用于识别每个数据点的资产的分类变量:
data = pd.concat([returns, value_factors, momentum_factors, quality_factors, payout_factors, growth_factors, efficiency_factors, risk_factors], axis=1).sortlevel() data.index.names = ['date', 'asset'] data['stock'] = data.index.get_level_values('asset').map(lambda x: x.asset_name)
数据清洗 - 缺失数据
接下来,我们删除缺少超过 20% 观察值的行和列,导致损失 6% 的观察值和 3 列:
rows_before, cols_before = data.shape data = (data .dropna(axis=1, thresh=int(len(data) * .8)) .dropna(thresh=int(len(data.columns) * .8))) data = data.fillna(data.median()) rows_after, cols_after = data.shape print('{:,d} rows and {:,d} columns dropped'.format(rows_before - rows_after, cols_before - cols_after)) 2,985 rows and 3 columns dropped
此时,我们有 51 个特征和股票的分类标识符:
data.sort_index(1).info() MultiIndex: 47377 entries, (2014-01-02, Equity(24 [AAPL])) to (2015-12- 31, Equity(47208 [GPRO])) Data columns (total 52 columns): AssetToEquityRatio 47377 non-null float64 AssetTurnover 47377 non-null float64 CFO To Assets 47377 non-null float64 ... WorkingCapitalToAssets 47377 non-null float64 WorkingCapitalToSales 47377 non-null float64 stock 47377 non-null object dtypes: float64(51), object(1)
数据探索
对于线性回归模型,重要的是探索特征之间的相关性,以识别多重共线性问题,并检查特征与目标之间的相关性。笔记本包含一个 seaborn clustermap,显示特征相关矩阵的层次结构。它识别出少量高度相关的集群。
对分类变量进行虚拟编码
我们需要将分类变量stock
转换为数字格式,以便线性回归可以处理它。为此,我们使用创建每个类别级别的单独列并使用1
标记此级别在原始分类列中存在,否则标记为0
的虚拟编码。 pandas 函数get_dummies()
自动执行虚拟编码。它检测并正确转换类型为对象的列,如下所示。如果您需要对包含整数的列获取虚拟变量,例如,您可以使用columns
关键字标识它们:
df = pd.DataFrame({'categories': ['A','B', 'C']}) categories 0 A 1 B 2 C pd.get_dummies(df) categories_A categories_B categories_C 0 1 0 0 1 0 1 0 2 0 0 1
当将所有类别转换为虚拟变量并估计模型时,使用截距(通常情况下)会无意中创建多重共线性:矩阵现在包含冗余信息,不再具有完整的秩,即,变得奇异。通过删除新指标列中的一个来简单避免这种情况。缺失类别级别上的系数现在将由截距(当其他每个类别虚拟为0
时始终为1
)捕获。使用drop_first
关键字相应地更正虚拟变量:
pd.get_dummies(df, drop_first=True) categories_B categories_C 0 0 0 1 1 0 2 0 1
应用于我们的综合特征和回报,我们获得了 181 列,因为有超过 100 只股票作为宇宙定义,它会自动更新股票选择:
X = pd.get_dummies(data.drop(return_cols, axis=1), drop_first=True) X.info() MultiIndex: 47377 entries, (2014-01-02 00:00:00+00:00, Equity(24 [AAPL])) to (2015-12-31 00:00:00+00:00, Equity(47208 [GPRO])) Columns: 181 entries, DividendYield to stock_YELP INC dtypes: float64(182) memory usage: 66.1+ MB
创建前瞻回报
目标是预测给定持有期内的回报。因此,我们需要将特征与回报值与相应的未来 1、5、10 或 20 天的回报数据点对齐,对于每个股票。我们通过将 pandas 的.groupby()
方法与.shift()
方法结合使用来实现这一点:
y = data.loc[:, return_cols] shifted_y = [] for col in y.columns: t = int(re.search(r'\d+', col).group(0)) shifted_y.append(y.groupby(level='asset')['Returns{}D'.format(t)].shift(-t).to_frame(col)) y = pd.concat(shifted_y, axis=1) y.info() MultiIndex: 47377 entries, (2014-01-02, Equity(24 [AAPL])) to (2015-12-31, Equity(47208 [GPRO])) Data columns (total 4 columns): Returns1D 47242 non-null float64 Returns5D 46706 non-null float64 Returns10D 46036 non-null float64 Returns20D 44696 non-null float64 dtypes: float64(4)
现在每个回报系列的观察次数不同,因为前向移位在每个股票的尾部创建了缺失值。
使用 statsmodels 进行线性 OLS 回归
我们可以像之前演示的那样使用statsmodels
估计线性回归模型的 OLS。我们选择一个前瞻回报,例如一个 10 天的持有期,删除低于 2.5%和高于 97.5%百分位数的异常值,然后相应地拟合模型:
target = 'Returns10D' model_data = pd.concat([y[[target]], X], axis=1).dropna() model_data = model_data[model_data[target].between(model_data[target].quantile(.025), model_data[target].quantile(.975))] model = OLS(endog=model_data[target], exog=model_data.drop(target, axis=1)) trained_model = model.fit() trained_model.summary()
诊断统计
摘要可在笔记本中保存一些空间,因为变量数量很多。诊断统计显示,鉴于 Jarque—Bera 统计量的高 p 值,不能拒绝残差服从正态分布的假设。
然而,杜宾-沃森统计量为 1.5,因此我们可以在 5%的水平上舒适地拒绝无自相关的零假设。因此,标准误差可能呈正相关。如果我们的目标是了解哪些因素与前向收益显著相关,我们需要使用稳健标准误差重新运行回归(在statsmodels .fit()
方法中的一个参数),或者完全使用不同的方法,如允许更复杂误差协方差的面板模型。
使用 sklearn 进行线性 OLS 回归
由于 sklearn 专门用于预测,我们将根据其预测性能使用交叉验证评估线性回归模型。
自定义时间序列交叉验证
我们的数据包括分组的时间序列数据,需要一个自定义的交叉验证函数来提供训练和测试索引,以确保测试数据立即跟随每个股票的训练数据,我们不会无意中产生前瞻性偏差或泄漏。
我们可以使用以下函数实现这一点,该函数返回一个generator
,产生训练和测试日期的对。确保训练期的最小长度的训练日期集。对数nfolds
取决于参数。不同的测试期不重叠,位于数据中可用的周期末。在使用测试期后,它将成为相应增长长度的训练数据的一部分:
def time_series_split(d=model_data, nfolds=5, min_train=21): """Generate train/test dates for nfolds with at least min_train train obs """ train_dates = d[:min_train].tolist() n = int(len(dates)/(nfolds + 1)) + 1 test_folds = [d[i:i + n] for i in range(min_train, len(d), n)] for test_dates in test_folds: if len(train_dates) > min_train: yield train_dates, test_dates train_dates.extend(test_dates)
选择特征和目标
我们需要选择适当的回报系列(我们将再次使用 10 天的持有期),并去除异常值。我们还将将回报转换为对数回报,如下所示:
target = 'Returns10D' outliers = .01 model_data = pd.concat([y[[target]], X], axis=1).dropna().reset_index('asset', drop=True) model_data = model_data[model_data[target].between(*model_data[target].quantile([outliers, 1-outliers]).values)] model_data[target] = np.log1p(model_data[target]) features = model_data.drop(target, axis=1).columns dates = model_data.index.unique() DatetimeIndex: 45114 entries, 2014-01-02 to 2015-12-16 Columns: 183 entries, Returns10D to stock_YELP INC dtypes: float64(183)
对模型进行交叉验证
我们将使用 250 个折叠来通常预测历史训练数据后大约 2 天的前向收益。每次迭代从我们的自定义交叉验证函数中获得适当的训练和测试日期,选择相应的特征和目标,然后进行训练和预测。我们捕获根均方误差以及实际值和预测值之间的 Spearman 等级相关性:
nfolds = 250 lr = LinearRegression() test_results, result_idx, preds = [], [], pd.DataFrame() for train_dates, test_dates in time_series_split(dates, nfolds=nfolds): X_train = model_data.loc[idx[train_dates], features] y_train = model_data.loc[idx[train_dates], target] lr.fit(X=X_train, y=y_train) X_test = model_data.loc[idx[test_dates], features] y_test = model_data.loc[idx[test_dates], target] y_pred = lr.predict(X_test) rmse = np.sqrt(mean_squared_error(y_pred=y_pred, y_true=y_test)) ic, pval = spearmanr(y_pred, y_test) test_results.append([rmse, ic, pval]) preds = preds.append(y_test.to_frame('actuals').assign(predicted=y_pred)) result_idx.append(train_dates[-1])
测试结果-信息系数和 RMSE
我们已经从 250 个折叠中捕获了测试预测,并可以计算整体和 21 天滚动平均值:
fig, axes = plt.subplots(nrows=2) rolling_result = test_result.rolling(21).mean() rolling_result[['ic', 'pval']].plot(ax=axes[0], title='Information Coefficient') axes[0].axhline(test_result.ic.mean(), lw=1, ls='--', color='k') rolling_result[['rmse']].plot(ax=axes[1], title='Root Mean Squared Error') axes[1].axhline(test_result.rmse.mean(), lw=1, ls='--', color='k')
我们得到以下图表,突显了 IC 和 RMSE 的负相关及其各自的值:
突显 IC 和 RMSE 的负相关的图表
对于整个时期,我们看到信息系数通过实际值和预测值的等级相关性来测量,呈弱正相关且具有统计学显著性:
使用 sklearn 进行岭回归
对于岭回归,我们需要使用关键字alpha
来调整正则化参数,该参数对应于我们之前使用的λ。 我们将尝试从 10^(-5)到 10⁵的 21 个值以对数步长进行尝试。
岭惩罚的尺度敏感性要求我们使用StandardScaler
对输入进行标准化。 请注意,我们始终从训练集中学习均值和标准差,然后使用.fit_transform()
方法将这些学习参数应用于测试集,然后使用.transform()
方法。
使用交叉验证调整正则化参数
然后,我们继续使用250
个折叠交叉验证超参数值:
nfolds = 250 alphas = np.logspace(-5, 5, 21) scaler = StandardScaler() ridge_result, ridge_coeffs = pd.DataFrame(), pd.DataFrame() for i, alpha in enumerate(alphas): coeffs, test_results = [], [] lr_ridge = Ridge(alpha=alpha) for train_dates, test_dates in time_series_split(dates, nfolds=nfolds): X_train = model_data.loc[idx[train_dates], features] y_train = model_data.loc[idx[train_dates], target] lr_ridge.fit(X=scaler.fit_transform(X_train), y=y_train) coeffs.append(lr_ridge.coef_) X_test = model_data.loc[idx[test_dates], features] y_test = model_data.loc[idx[test_dates], target] y_pred = lr_ridge.predict(scaler.transform(X_test)) rmse = np.sqrt(mean_squared_error(y_pred=y_pred, y_true=y_test)) ic, pval = spearmanr(y_pred, y_test) test_results.append([train_dates[-1], rmse, ic, pval, alpha]) test_results = pd.DataFrame(test_results, columns=['date', 'rmse', 'ic', 'pval', 'alpha']) ridge_result = ridge_result.append(test_results) ridge_coeffs[alpha] = np.mean(coeffs, axis=0)
交叉验证结果和岭回归系数路径
我们现在可以绘制每个超参数值获得的信息系数,并可视化随着正则化增加而系数值如何变化。 结果显示,我们在λ=10\时获得了最高的 IC 值。 对于这个正则化水平,右侧面板显示,与(几乎)不受约束的模型相比,系数已经显着收缩,λ=10^(-5):
交叉验证结果和岭回归系数路径
前 10 个系数
系数的标准化允许我们通过比较它们的绝对值来得出关于它们相对重要性的结论。 最相关的 10 个系数是:
前 10 个系数
使用 sklearn 的套索回归
套索实现看起来与我们刚刚运行的岭回归模型非常相似。 主要区别在于,套索需要使用迭代坐标下降来找到解决方案,而岭回归可以依赖于闭合形式的解决方案:
nfolds = 250 alphas = np.logspace(-8, -2, 13) scaler = StandardScaler() lasso_results, lasso_coeffs = pd.DataFrame(), pd.DataFrame() for i, alpha in enumerate(alphas): coeffs, test_results = [], [] lr_lasso = Lasso(alpha=alpha) for i, (train_dates, test_dates) in enumerate(time_series_split(dates, nfolds=nfolds)): X_train = model_data.loc[idx[train_dates], features] y_train = model_data.loc[idx[train_dates], target] lr_lasso.fit(X=scaler.fit_transform(X_train), y=y_train) X_test = model_data.loc[idx[test_dates], features] y_test = model_data.loc[idx[test_dates], target] y_pred = lr_lasso.predict(scaler.transform(X_test)) rmse = np.sqrt(mean_squared_error(y_pred=y_pred, y_true=y_test)) ic, pval = spearmanr(y_pred, y_test) coeffs.append(lr_lasso.coef_) test_results.append([train_dates[-1], rmse, ic, pval, alpha]) test_results = pd.DataFrame(test_results, columns=['date', 'rmse', 'ic', 'pval', 'alpha']) lasso_results = lasso_results.append(test_results) lasso_coeffs[alpha] = np.mean(coeffs, axis=0)
交叉验证信息系数和套索路径
与以前一样,我们可以绘制在交叉验证期间使用的所有测试集的平均信息系数。 我们再次看到,正则化可以提高 IC 超出不受约束模型,以λ=10^(-5)为水平获得最佳的样本外结果。 最优的正则化值与岭回归不同,因为惩罚是相对较小的系数值的绝对值之和,而不是平方值。 我们还可以看到,对于这个正则化水平,系数已经类似地收缩,就像岭回归案例中一样:
交叉验证信息系数和套索路径
总的来说,岭回归和套索将产生类似的结果。 岭回归通常计算速度更快,但套索也通过逐渐将系数减小到零来产生连续的特征子集选择,从而消除了特征。
线性分类
到目前为止讨论的线性回归模型假设定量响应变量。 在本节中,我们将重点讨论对推断和预测建模定性输出变量的方法,这个过程被称为分类,在实践中甚至比回归更频繁地发生。
针对数据点预测定性响应被称为分类该观察,因为它涉及将观察分配到一个类别或类别。 在实践中,分类方法通常为定性变量的每个类别预测概率,然后使用此概率来决定适当的分类。
我们可以忽略输出变量取离散值的事实,并应用线性回归模型尝试使用多个输入变量来预测分类输出。然而,很容易构造出这种方法表现非常糟糕的例子。 此外,当我们知道*y ∈ [0, 1]*时,模型产生大于 1 或小于 0 的值并不直观。
有许多不同的分类技术或分类器可用于预测定性响应。 在本节中,我们将介绍广泛使用的逻辑回归,它与线性回归密切相关。 在接下来的章节中,我们将介绍更复杂的方法,包括决策树和随机森林的广义可加模型,以及梯度提升机和神经网络。
逻辑回归模型
逻辑回归模型源于希望对输出类别的概率建模,给定一个在x中线性的函数,就像线性回归模型一样,同时确保它们总和为一,并保持在[0, 1],正如我们从概率期望的那样。
在本节中,我们介绍逻辑回归模型的目标和函数形式,并描述培训方法。 然后,我们说明如何使用 statsmodels 对宏观数据进行统计推断,并使用 sklearn 实现的正则化逻辑回归来预测价格走势。
目标函数
为了说明,我们将使用输出变量 y,如果在给定时间跨度 d 内股票收益为正,则取值为 1,否则为 0:
我们可以轻松地将 y 扩展到三个类别,其中 0 和 2 反映超过某个阈值的负面和正面价格变动,否则为 1。 然而,与其对y建模,逻辑回归模型对y属于给定一向量α因子或特征的类别的概率进行建模。 换句话说,逻辑回归模型了解包含在模型中的变量值时,股票价格上涨的概率:
逻辑函数
为了防止模型生成超出 [0, 1] 区间的值,我们必须使用一个只在整个 x 的定义域上产生 0 和 1 之间输出的函数来对 p(x) 建模。逻辑函数满足这一要求,并且始终产生一个 S 形曲线(见笔记本示例),因此,无论 X 的值如何,我们都将得到一个合理的预测:
在这里,向量 x 包括一个由 第一个分量捕获的截距,。我们可以转换此表达式以隔离类似线性回归的部分,得到:
量 p(x)/[1−p(x)] 称为 几率,是表达概率的另一种方式,可能与赌博中熟悉的方式相似,可以取 0 到 ∞ 之间的任意值,其中较低的值也意味着较低的概率,而较高的值意味着较高的概率。
对数几率也称为对数几率(因为它是几率的对数)。因此,逻辑回归表示的对数线性回归 x 看起来与前述的线性回归非常相似。
最大似然估计
系数向量 必须使用可用的训练数据进行估计。虽然我们可以使用(非线性)最小二乘法来拟合 logistic 回归模型,但更一般的最大似然方法更受欢迎,因为它具有更好的统计特性。正如我们刚刚讨论的,使用最大似然拟合 logistic 回归模型的基本直觉是寻找估计值 ,使得预测的概率 尽可能接近实际结果。换句话说,我们试图找到 这样这些估计值在股价上涨的所有情况下都接近 1,否则接近 0 的情况。更正式地说,我们正在寻求最大化似然函数:
使用总和比乘积更容易,因此让我们两边都取对数,得到对数似然函数和 logistic 回归系数的相应定义:
通过将 对 的导数设为零来通过最大化此方程获得 p+1 个所谓的得分方程,在参数中是非线性的,可以使用迭代数值方法来解决凹的对数似然函数。
如何使用 statsmodels 进行推理
我们将演示如何使用 statsmodels
进行逻辑回归,基于一个包含从 1959 年至 2009 年的季度美国宏观数据的简单内置数据集(详见笔记本 logistic_regression_macro_data.ipynb
)。
变量及其转换列于以下表格中:
变量 | 描述 | 转换 |
realgdp |
实际国内生产总值 | 年增长率 |
realcons |
实际个人消费支出 | 年增长率 |
realinv |
实际私人国内投资总额 | 年增长率 |
realgovt |
实际联邦支出和总投资 | 年增长率 |
realdpi |
实际私人可支配收入 | 年增长率 |
m1 |
M1 名义货币存量 | 年增长率 |
tbilrate |
月度 3 个国库券利率 | 水平 |
unemp |
季节性调整后的失业率(%) | 水平 |
infl |
通货膨胀率 | 水平 |
realint |
实际利率 | 水平 |
为了得到一个二元目标变量,我们计算季度实际 GDP 年增长率的 20 季度滚动平均值。然后,如果当前增长超过移动平均值,则分配 1,否则分配 0。最后,我们将指标变量移位,以使下一季度的结果与当前季度对齐。
我们使用一个截距,并将季度值转换为虚拟变量,并按以下方式训练逻辑回归模型:
import statsmodels.api as sm data = pd.get_dummies(data.drop(drop_cols, axis=1), columns=['quarter'], drop_first=True).dropna() model = sm.Logit(data.target, sm.add_constant(data.drop('target', axis=1))) result = model.fit() result.summary()
这产生了我们的模型摘要,其中包含截距的 198 个观测值和 13 个变量:
逻辑回归结果
摘要显示,该模型已使用最大似然进行训练,并在 -67.9 处提供了对数似然函数的最大化值。
LL-Null 值为 -136.42 是只包括截距时对数似然函数的最大化结果。它形成了伪 R² 统计量和 Log-似然比(LLR)测试的基础。
伪 R^(2 ) 统计量是最小二乘法下可用的熟悉 R² 的替代品。它是基于空模型 m[0] 和完整模型 m[1] 的最大化对数似然函数的比率计算的,如下所示:
值从 0 变化(当模型不改善似然时)到 1(模型完美拟合时)且对数似然在 0 处最大化。因此,较高的值表明拟合效果更好。
LLR 测试通常比较一个更受限制的模型,并计算如下:
原假设是受限制模型的性能更好,但低的 p 值表明我们可以拒绝这一假设,并更喜欢完整模型而不是空模型。这类似于线性回归的 F 检验(当我们使用 MLE 估计模型时,也可以使用 LLR 测试)。
Z 统计量在线性回归输出中起着与 t 统计量相同的作用,并且与系数估计和其标准误差的比率一样计算。p 值还指示了在假设 H[0]:β = 0(总体系数为零)的情况下观察到测试统计量的概率。我们可以拒绝这个假设,对于截距
、realcons
、realinv
、realgovt
、realdpi
和unemp
。
如何使用逻辑回归进行预测
套索 L[1] 惩罚和岭 L[2] 惩罚都可以与逻辑回归一起使用。它们具有我们刚刚讨论的相同收缩效应,而套索可以再次用于任何线性回归模型的变量选择。
与线性回归一样,对输入变量进行标准化非常重要,因为正则化模型对比例敏感。正则化超参数还需要使用交叉验证进行调整,就像线性回归的情况一样。
如何使用 sklearn 预测价格变动
我们继续价格预测示例,但现在我们将结果变量二值化,以便在 10 天回报为正时取值为 1,否则为 0;请参阅子目录 stock_price_prediction
中的笔记本 logistic_regression.ipynb
:
target = 'Returns10D' label = (y[target] > 0).astype(int).to_frame(target)
有了这个新的分类结果变量,我们现在可以使用默认的 L[2]正则化训练逻辑回归。对于逻辑回归,正则化与线性回归相反:λ的值越高,正则化越少,反之亦然。我们使用交叉验证评估 11 个参数值如下:
nfolds = 250 Cs = np.logspace(-5, 5, 11) scaler = StandardScaler() logistic_results, logistic_coeffs = pd.DataFrame(), pd.DataFrame() for C in Cs: coeffs = [] log_reg = LogisticRegression(C=C) for i, (train_dates, test_dates) in enumerate(time_series_split(dates, nfolds=nfolds)): X_train = model_data.loc[idx[train_dates], features] y_train = model_data.loc[idx[train_dates], target] log_reg.fit(X=scaler.fit_transform(X_train), y=y_train) X_test = model_data.loc[idx[test_dates], features] y_test = model_data.loc[idx[test_dates], target] y_pred = log_reg.predict_proba(scaler.transform(X_test))[:, 1] coeffs.append(log_reg.coef_.squeeze()) logistic_results = (logistic_results .append(y_test .to_frame('actuals') .assign(predicted=y_pred, C=C))) logistic_coeffs[C] = np.mean(coeffs, axis=0)
然后我们使用前一章讨论的 roc_auc_score
来比较各种正则化参数的预测准确度:
auc_by_C = logistic_results.groupby('C').apply(lambda x: roc_auc_score(y_true=x.actuals.astype(int), y_score=x.predicted))
我们可以再次绘制 AUC 结果,以显示超参数值范围以及系数路径,该路径显示系数在最佳正则化值 10² 处略微收缩时预测精度的提高:
AUC 和 Logistic Ridge 路径
摘要
在本章中,我们介绍了使用线性模型进行回归和分类的重要基线案例的第一个机器学习模型。我们探讨了两个任务的目标函数的制定,学习了各种训练方法,并学习了如何将模型用于推理和预测。
我们将这些新的机器学习技术应用于估计线性因子模型,这些模型对于管理风险、评估新的阿尔法因子和归因绩效非常有用。我们还应用线性回归和分类来完成第一个预测任务,即在绝对和方向性方面预测股票收益。
在下一章中,我们将讨论重要的线性时间序列模型主题,这些模型旨在捕捉单变量和多变量情况下的串行相关模式。我们还将学习关于新的交易策略,因为我们将探讨基于协整概念的配对交易,该概念捕捉了两个股价序列之间的动态相关性。