引言:金融预测与线性回归
金融大数据时代,预测能力是核心竞争力。线性回归作为最基础、最强大的预测模型之一,是理解更复杂算法的基石。
本章学习目标:理解金融预测的基石
- 理解核心应用: 线性回归是预测股价、盈利和回报率等连续变量的基石。
本章学习目标:掌握模型结构 \(y = w_0 + w_1x\)
- 掌握模型结构: 我们将深入剖析 \(y = w_0 + w_1x\) 的每一个组成部分。
本章学习目标:揭秘模型训练过程
- 揭秘训练过程: 理解机器如何通过“梯度下降”方法自动“学习”最佳参数。
本章学习目标:实现代码实战
- 实现代码实战: 我们将使用 Python 的专业库
statsmodels
和 scikit-learn
解决真实的金融预测问题。
核心问题:我们能否预测一家公司的盈利能力?
在金融市场,预期是影响价格的关键因素。公司季报或年报发布前,分析师会发布盈利预测。如果实际盈利不及预期,股价常会应声下跌。
传统预测方法及其局限
- 传统方法: 分析师通过大量行业调研、公司访谈来做出预测,成本高昂。
我们的目标:构建自动化预测系统
- 我们的目标: 利用机器学习,构建一个数据驱动的、自动化的盈利预测系统,以历史数据预测未来每股收益 (Earnings Per Share, EPS)。
数据是模型的燃料:目标变量 y
我们将使用公开的财务数据来构建模型。我们的目标是预测 每股收益 (y)。
目标 (y) |
每股收益 (EPS) |
公司季报 |
季报公告后 |
数据是模型的燃料:特征变量 x
特征 (x) |
股价 (Price) |
交易所 |
季报公告前 |
特征 (x) |
净市率 (Book-to-Market) |
前期财报 |
季报公告前 |
特征 (x) |
资产收益率 (ROA) |
前期财报 |
季报公告前 |
数据是模型的燃料:关键逻辑
关键逻辑: 我们必须使用在预测时刻点 已经可以获得 的信息 (特征 x) 来预测 未来未知 的信息 (目标 y)。
第一步:从最简单的模型开始——一元线性回归
现实世界是复杂的,但科学研究总是从最简单的模型开始。我们首先假设,只用一个特征来预测目标。
例如:我们假设公司的 股价 (x) 与其未来的 每股收益 (y) 之间存在线性关系。
模型表达式与参数解读
我们的模型可以表示为一个简单的直线方程:
\[ \large{ \text{预测的每股收益} = f(x) = w_0 + w_1 \times \text{股价} } \]
- \(x\): 特征变量 (股价)
- \(w_0\): 截距项 (Intercept),在机器学习中也称为 偏置项 (Bias)
- \(w_1\): 斜率 (Slope),代表股价每变动1单位,预测的EPS会变动多少。在机器学习中称为 权重 (Weight)
不同参数代表了对数据关系的不同“假设”
w₀
和 w₁
的不同取值,就构成了不同的直线,代表了我们对股价和EPS关系的不同理解。
左图 (\(w_1 > 0\)) 假设股价越高,未来盈利越强。右图 (\(w_1 < 0\)) 则相反。
import numpy as np
import matplotlib.pyplot as plt
# Set a professional and distinct color palette
primary_blue = '#007bff'
accent_red = '#dc3545'
text_color = '#212529'
grid_color = '#e0e0e0'
background_color = '#f8f9fa'
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), sharey=True)
fig.set_facecolor(background_color)
# Model 1: Positive slope (w1 > 0)
x1 = np.linspace(0, 10, 100)
y1 = 1.5 + 0.6 * x1
ax1.plot(x1, y1, color=accent_red, linewidth=3, label='$f(x) = 1.5 + 0.6x$')
ax1.set_title('$w_1 > 0$ (正相关)', fontsize=16, color=text_color, pad=10)
ax1.set_xlabel('特征 (x)', color=text_color, fontsize=12)
ax1.set_ylabel('预测值 (y)', color=text_color, fontsize=12)
ax1.set_xlim(0, 10)
ax1.set_ylim(0, 10)
ax1.grid(True, linestyle='--', alpha=0.7, color=grid_color)
ax1.tick_params(colors=text_color)
ax1.set_facecolor('white')
ax1.text(5, 9, '$f(x) = 1.5 + 0.6x$', ha='center', va='center', fontsize=14, color=accent_red)
# Model 2: Negative slope (w1 < 0)
x2 = np.linspace(0, 10, 100)
y2 = 8 - 0.5 * x2
ax2.plot(x2, y2, color=primary_blue, linewidth=3, label='$f(x) = 8.0 - 0.5x$')
ax2.set_title('$w_1 < 0$ (负相关)', fontsize=16, color=text_color, pad=10)
ax2.set_xlabel('特征 (x)', color=text_color, fontsize=12)
ax2.set_xlim(0, 10)
ax2.set_ylim(0, 10)
ax2.grid(True, linestyle='--', alpha=0.7, color=grid_color)
ax2.tick_params(colors=text_color)
ax2.set_facecolor('white')
ax2.text(5, 2, '$f(x) = 8.0 - 0.5x$', ha='center', va='center', fontsize=14, color=primary_blue)
plt.tight_layout(pad=2.0)
plt.show()
我们的核心任务是:从无数条可能的直线中,找到“最”拟合数据的那一条。 这就是 模型训练 (Model Training)。
如何定义“最佳”拟合?——最小化预测误差
假设我们有了一些真实数据点(蓝色x),我们尝试用两条不同的直线去拟合。哪一条更好?
直观上,左边的模型更好,因为它产生的 预测误差 (灰色虚线) 整体更小。
import numpy as np
import matplotlib.pyplot as plt
# Generate sample data
np.random.seed(42)
X = 2 * np.random.rand(20, 1) # Feature data
y = 4 + 3 * X + np.random.randn(20, 1) # Target data with noise
x_range = np.array([[np.min(X)-0.1], [np.max(X)+0.1]])
# Two example models
w1_good, w0_good = 3.1, 4.2 # Parameters for a good fit
w1_bad, w0_bad = -0.5, 8.0 # Parameters for a bad fit
y_good_pred_line = w0_good + w1_good * x_range
y_bad_pred_line = w0_bad + w1_bad * x_range
# Set a professional color palette
data_point_color = '#007bff'
good_fit_line_color = '#28a745'
bad_fit_line_color = '#dc3545'
error_line_color = '#6c757d'
text_color = '#212529'
grid_color = '#e0e0e0'
background_color = '#f8f9fa'
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5), sharey=True)
fig.set_facecolor(background_color)
# Good Fit Model
ax1.scatter(X, y, marker='x', color=data_point_color, s=80, label='真实数据点', zorder=5)
ax1.plot(x_range, y_good_pred_line, "-", color=good_fit_line_color, linewidth=3, label='模型预测')
for i in range(len(X)):
ax1.plot([X[i], X[i]], [y[i], w0_good + w1_good * X[i]], '--', color=error_line_color, alpha=0.7)
ax1.set_title(f'模型 A (较好拟合)', fontsize=16, color=text_color, pad=10)
ax1.set_xlabel('x', color=text_color, fontsize=12)
ax1.set_ylabel('y', color=text_color, fontsize=12)
ax1.legend(fontsize=12, loc='upper left')
ax1.grid(True, linestyle='--', alpha=0.7, color=grid_color)
ax1.tick_params(colors=text_color)
ax1.set_facecolor('white')
# Bad Fit Model
ax2.scatter(X, y, marker='x', color=data_point_color, s=80, label='真实数据点', zorder=5)
ax2.plot(x_range, y_bad_pred_line, "-", color=bad_fit_line_color, linewidth=3, label='模型预测')
for i in range(len(X)):
ax2.plot([X[i], X[i]], [y[i], w0_bad + w1_bad * X[i]], '--', color=error_line_color, alpha=0.7)
ax2.set_title(f'模型 B (较差拟合)', fontsize=16, color=text_color, pad=10)
ax2.set_xlabel('x', color=text_color, fontsize=12)
ax2.legend(fontsize=12, loc='upper left')
ax2.grid(True, linestyle='--', alpha=0.7, color=grid_color)
ax2.tick_params(colors=text_color)
ax2.set_facecolor('white')
plt.tight_layout(pad=2.0)
plt.show()
我们需要一个数学工具来 量化 这个“整体误差”。这个工具就是 代价函数 (Cost Function)。
用代价函数 \(J(w_0, w_1)\) 量化模型的总误差
代价函数 (Cost Function),也叫 损失函数 (Loss Function),衡量的是模型在所有数据点上总的预测误差。
对于线性回归,最常用的代价函数是 均方误差 (Mean Squared Error, MSE):
\[ \large{ J(w_0, w_1) = \frac{1}{2n} \sum_{i=1}^{n} \left( f(x^{(i)}) - y^{(i)} \right)^2 } \]
- \(n\): 数据点的总数。
- \(x^{(i)}, y^{(i)}\): 第 \(i\) 个数据点的特征和真实值。
- \(f(x^{(i)})\):模型对第 \(i\) 个数据点的预测值,即 \(w_0 + w_1x^{(i)}\)。
理解残差与平方误差
\[ \large{ J(w_0, w_1) = \frac{1}{2n} \sum_{i=1}^{n} \left( \color{#dc3545}{f(x^{(i)}) - y^{(i)}} \right)^2 } \]
- \(\color{#dc3545}{(f(x^{(i)}) - y^{(i)})}\):这是第 \(i\) 个点的 残差 (residual) 或 误差。
- \(\color{#007bff}{(\dots)^2}\):对误差求平方,使得正负误差不会相互抵消,并且对大误差的惩罚更重。
求和与平均化:完成代价函数
\[ \large{ J(w_0, w_1) = \color{#007bff}{\frac{1}{2n} \sum_{i=1}^{n}} \left( f(x^{(i)}) - y^{(i)} \right)^2 } \]
- \(\color{#007bff}{\sum_{i=1}^{n}}\):将所有数据点的平方误差加起来。
- \(\color{#007bff}{\frac{1}{2n}}\):求平均值,使代价函数的大小与数据量无关。分母中的
2
是为了后续求导计算方便。
我们的目标: 找到一组参数 \((w_0, w_1)\),使得代价函数 \(J(w_0, w_1)\) 的值 最小。
代价函数的可视化:一个二次型的碗
为了简化,我们暂时固定 \(w_0=0\),只看代价函数 \(J(w_1)\) 如何随 \(w_1\) 变化。它是一个开口向上的抛物线。
- 左图: 不同的 \(w_1\) 值对应不同的拟合直线。
- 右图: 每一条直线都对应代价函数 \(J(w_1)\) 上的一个点。
- 最佳拟合: 左图中最贴合数据的直线,对应右图抛物线的 最低点。
import numpy as np
import matplotlib.pyplot as plt
# Set a professional color palette
data_point_color = '#007bff'
fit_line_color = '#dc3545'
cost_plot_color = '#28a745'
current_point_color = '#ffc107'
text_color = '#212529'
grid_color = '#e0e0e0'
background_color = '#f8f9fa'
# Generate sample data with intercept close to 0
np.random.seed(0)
X = 2 * (np.random.rand(20, 1) - 0.5)
y = 3 * X + np.random.randn(20, 1) * 0.5
# Calculate Cost Function J for a range of w1
w1_vals = np.linspace(-1, 7, 100)
J_vals = [np.mean((w1 * X - y)**2) / 2 for w1 in w1_vals]
min_w1_idx = np.argmin(J_vals)
best_w1 = w1_vals[min_w1_idx]
# Create plots
fig = plt.figure(figsize=(12, 10))
fig.set_facecolor(background_color)
scenarios = [
{'w1': 0.5, 'title': '较差拟合 (w₁ 斜率过小)'},
{'w1': best_w1, 'title': f'最佳拟合 (w₁ ≈ {best_w1:.2f})'},
{'w1': 5.5, 'title': '较差拟合 (w₁ 斜率过大)'}
]
for i, scen in enumerate(scenarios):
w1 = scen['w1']
# Left plot: Data and fit line
ax_left = fig.add_subplot(3, 2, 2*i + 1)
ax_left.scatter(X, y, marker='x', alpha=0.8, color=data_point_color, s=80)
ax_left.plot([-1, 1], [w1 * -1, w1 * 1], '-', color=fit_line_color, linewidth=3)
ax_left.set_title(scen['title'], fontsize=14, color=text_color, pad=10)
ax_left.set_xlabel('x', color=text_color, fontsize=12)
ax_left.set_ylabel('y', color=text_color, fontsize=12)
ax_left.set_xlim(-1.2, 1.2)
ax_left.set_ylim(-4, 4)
ax_left.grid(True, linestyle='--', alpha=0.7, color=grid_color)
ax_left.tick_params(colors=text_color)
ax_left.set_facecolor('white')
# Right plot: Cost function
ax_right = fig.add_subplot(3, 2, 2*i + 2)
ax_right.plot(w1_vals, J_vals, color=cost_plot_color, linewidth=2.5)
cost_val = np.mean(((w1 * X) - y)**2) / 2
ax_right.plot([w1], [cost_val], color=current_point_color, marker='o', markersize=12, zorder=5, markeredgecolor='black', markeredgewidth=1.5)
ax_right.set_title(f'代价函数 J(w₁)', fontsize=14, color=text_color, pad=10)
ax_right.set_xlabel('w₁', color=text_color, fontsize=12)
ax_right.set_ylabel('J(w₁)', color=text_color, fontsize=12)
ax_right.grid(True, linestyle='--', alpha=0.7, color=grid_color)
ax_right.tick_params(colors=text_color)
ax_right.set_facecolor('white')
plt.tight_layout(pad=2.0, h_pad=3.0)
plt.show()
核心问题转化为: 如何系统性地、自动地找到这个“碗”的最低点?
寻找最低点的方法:梯度下降 (Gradient Descent)
梯度下降是一种强大的优化算法,用于寻找函数的最小值。 它的核心目标是:
\[ \large{ \text{目标: } \min_{w_0, w_1} J(w_0, w_1) } \]
梯度下降:直观的山谷类比
核心思想: 想象你身处一个碗状的山谷中,想要走到谷底。最快的方法是什么?
- 环顾四周,找到 最陡峭的下坡方向。
- 朝着这个方向 迈出一步。
- 重复以上过程,直到你到达谷底(即无法再往下走)。
在数学上,“最陡峭的下坡方向”就是 负梯度 的方向。
梯度下降的数学表达:迭代更新规则
梯度下降是一个迭代过程。我们从一个随机的 \((w_0, w_1)\) 开始,然后不断地更新它们,使 \(J(w_0, w_1)\) 越来越小。
算法步骤:
- 初始化: 随机选择一个起始点 \((w_0, w_1)\)。
- 迭代更新: 重复以下步骤,直到收敛:
\[ \large{ w_0 := w_0 - \alpha \frac{\partial}{\partial w_0} J(w_0, w_1) } \] \[ \large{ w_1 := w_1 - \alpha \frac{\partial}{\partial w_1} J(w_0, w_1) } \]
更新规则的组成部分
\[ \large{ w_j := w_j - \alpha \frac{\partial}{\partial w_j} J(w_0, w_1) } \]
- \(:=\) 是赋值操作,表示用右边的值更新左边的变量。
- \(\alpha\) 是 学习率 (Learning Rate),它控制着我们每一步“走”多远。
- \(\frac{\partial}{\partial w_j} J(w_0, w_1)\) 是代价函数对参数 \(w_j\) 的 偏导数 (即梯度)。它告诉我们在当前点,代价函数在 \(w_j\) 方向上的“坡度”。
学习率 \(\alpha\) 是决定成败的关键超参数
学习率 \(\alpha\) (Alpha) 是一个 超参数 (Hyperparameter),需要我们手动设定。它的选择至关重要:
- 如果 \(\alpha\) 太小: 每次更新的步子太小,收敛速度会非常慢。
- 如果 \(\alpha\) 太大: 每次更新的步子太大,可能会“跨过”最低点,导致在谷底两侧来回震荡,甚至无法收敛。
import numpy as np
import matplotlib.pyplot as plt
def J(w): return (w - 5)**2 + 3
def dJ(w): return 2 * (w - 5)
def gradient_descent(w_start, alpha, n_iterations):
w_path = [w_start]
w = w_start
for _ in range(n_iterations):
w = w - alpha * dJ(w)
w_path.append(w)
return np.array(w_path)
cost_plot_color = '#007bff'
path_color_good = '#28a745'
path_color_bad = '#dc3545'
text_color = '#212529'
grid_color = '#e0e0e0'
background_color = '#f8f9fa'
w_range = np.linspace(0, 10, 100)
j_range = J(w_range)
path_small_alpha = gradient_descent(w_start=0.5, alpha=0.1, n_iterations=15)
path_large_alpha = gradient_descent(w_start=0.5, alpha=0.98, n_iterations=15)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
fig.set_facecolor(background_color)
# Plot for suitable alpha
ax1.plot(w_range, j_range, color=cost_plot_color, linewidth=2.5, zorder=1)
ax1.plot(path_small_alpha, J(path_small_alpha), '-o', color=path_color_good, markersize=8, linewidth=2, label=r'学习路径', zorder=2)
ax1.set_title(r'合适的 α: 平稳收敛', fontsize=16, color=text_color, pad=10)
ax1.set_xlabel('$w_1$', color=text_color, fontsize=12)
ax1.set_ylabel('$J(w_1)$', color=text_color, fontsize=12)
ax1.legend(fontsize=12)
ax1.grid(True, linestyle='--', alpha=0.7, color=grid_color)
ax1.tick_params(colors=text_color)
ax1.set_facecolor('white')
# Plot for too large alpha
ax2.plot(w_range, j_range, color=cost_plot_color, linewidth=2.5, zorder=1)
ax2.plot(path_large_alpha, J(path_large_alpha), '-o', color=path_color_bad, markersize=8, linewidth=2, label=r'学习路径', zorder=2)
ax2.set_title(r'过大的 α: 震荡或发散', fontsize=16, color=text_color, pad=10)
ax2.set_xlabel('$w_1$', color=text_color, fontsize=12)
ax2.legend(fontsize=12)
ax2.grid(True, linestyle='--', alpha=0.7, color=grid_color)
ax2.tick_params(colors=text_color)
ax2.set_facecolor('white')
plt.tight_layout(pad=2.0)
plt.show()
线性回归的梯度下降更新规则
我们将代价函数 \(J(w_0, w_1)\) 的公式代入梯度下降的更新规则中,通过求偏导数可以得到线性回归的具体更新步骤:
重复执行直到收敛:
\[ \large{ w_0 := w_0 - \alpha \frac{1}{n} \sum_{i=1}^{n} (f(x^{(i)}) - y^{(i)}) } \] \[ \large{ w_1 := w_1 - \alpha \frac{1}{n} \sum_{i=1}^{n} (f(x^{(i)}) - y^{(i)}) \cdot x^{(i)} } \]
理解线性回归的梯度更新
\[ \large{ w_0 := w_0 - \alpha \frac{1}{n} \sum_{i=1}^{n} \color{#dc3545}{(f(x^{(i)}) - y^{(i)})} } \] \[ \large{ w_1 := w_1 - \alpha \frac{1}{n} \sum_{i=1}^{n} \color{#dc3545}{(f(x^{(i)}) - y^{(i)})} \cdot x^{(i)} } \]
- 更新 \(w_0\): 与所有数据点的 平均误差 成正比。
- 更新 \(w_1\): 与所有数据点的 平均误差 和 特征值 \(x^{(i)}\) 的乘积成正比。
重要说明: 这是一个 批量梯度下降 (Batch Gradient Descent) 算法,因为每次更新都使用了 全部 的训练数据。
扩展到多维:多元线性回归
现实中,只用一个特征(如股价)来预测盈利通常是不够的。我们需要使用多个特征来构建一个更强大的模型。
例如,我们可以同时使用 股价 (\(x_1\))、净市率 (\(x_2\)) 和 资产收益率 (\(x_3\)) 来预测 每股收益 (y)。
模型就从一条直线扩展为一个高维平面:
\[ \large{ f(x_1, x_2, x_3) = w_0 + w_1 x_1 + w_2 x_2 + w_3 x_3 } \]
这个模型被称为 多元线性回归 (Multiple Linear Regression)。
多元回归:模型几何意义
当有两个特征时,线性回归模型 \(y = w_0 + w_1 x_1 + w_2 x_2\) 不再是一条直线,而是一个三维空间中的 平面。 当特征数量更多时,它是一个抽象的 超平面。
使用线性代数简化多元回归的表达
当特征数量增多时,写出一长串的公式会很繁琐。我们可以用线性代数中的向量和矩阵来优雅地表示它。
我们定义 特征向量 x 和 参数向量 w: \[ \large{ \mathbf{x} = \begin{bmatrix} x_0 \\ x_1 \\ x_2 \\ \vdots \\ x_m \end{bmatrix} \quad , \quad \mathbf{w} = \begin{bmatrix} w_0 \\ w_1 \\ w_2 \\ \vdots \\ w_m \end{bmatrix} } \] 其中 \(x_0\) 是一个常数项,我们通常设为 1,这样 \(w_0\) 就可以被包含进向量运算中。
向量点积表示模型函数
模型函数就可以简洁地表示为向量的 点积: \[ \large{ f(\mathbf{x}) = w_0x_0 + w_1x_1 + \dots + w_mx_m = \mathbf{w}^T \mathbf{x} } \]
梯度下降的更新规则也同样适用于向量 w,我们对 w 中的每一个元素 \(w_j\) 进行同步更新。
多元回归的注意事项 (1): 特征缩放问题
当不同特征的取值范围相差巨大时(例如,股价可能在10-100之间,而市净率在0.5-2.0之间),梯度下降的收敛过程会变得非常缓慢和曲折。
代价函数的等高线图会呈现为又高又窄的椭圆形,导致梯度下降在寻找最低点时走“Z字形”路线。
解决方案:特征标准化 (Standardization)
解决方案: 特征缩放 (Feature Scaling)。在训练模型前,将所有特征预处理到相近的范围内。
最常用的方法是 标准化 (Standardization): \[ \large{ x_j' = \frac{x_j - \mu_j}{\sigma_j} } \] * \(\mu_j\): 特征 \(j\) 的均值。 * \(\sigma_j\): 特征 \(j\) 的标准差。
处理后,每个特征的均值变为0,标准差变为1。这使得梯度下降可以更直接、更快速地找到最优解。
多元回归的注意事项 (2): 多重共线性
多重共线性 (Multicollinearity) 指的是两个或多个特征之间存在高度相关关系。
- 例如: 同时使用“公司总资产”和“公司净资产”作为特征。这两个变量很可能是高度相关的。
多重共线性:问题与解决方案
问题: 1. 使得模型参数的估计变得 不稳定。微小的数据变动可能导致系数估计值发生巨大变化。 2. 难以解释单个特征的 独立贡献。我们分不清预测效果是来自特征A还是特征B。 * 解决方案: 1. 计算特征之间的 相关系数矩阵。 2. 如果发现某两个特征相关性很高 (例如,相关系数的绝对值 > 0.8),应考虑 移除其中一个。
实践:用 Python 预测公司盈利 - statsmodels
理论讲完了,现在我们进入实战环节。我们将使用真实的A股财务数据来预测每股收益 (EPS)。
我们将介绍两个在Python中实现线性回归最核心的库:
statsmodels
:
- 源于统计学和计量经济学。
- 强项在于推断 (Inference): 提供详细的统计报告,如系数的t检验、p值、R²等,帮助我们理解变量之间的关系。
实践:用 Python 预测公司盈利 - scikit-learn
scikit-learn
:
- 机器学习领域的标准库。
- 强项在于预测 (Prediction): 提供统一、简洁的API,专注于模型的训练、预测和评估,是构建机器学习工作流的首选。
实战1:使用 statsmodels
进行回归分析
statsmodels
的流程更接近传统的统计软件,重在分析和解释。
任务: 使用 pps
(每股股价), bm
(净市率), roa
(资产收益率) 预测 eps_basic
(每股收益)。
解读 statsmodels
的回归结果报告:coef
与 std err
这张表信息量巨大,是进行统计推断的核心。
coef |
系数 |
特征每增加一个单位,目标变量的预期变化量。 |
pps 的系数约为 0.02,表示股价每增加1元,预期EPS增加0.02元。 |
std err |
标准误 |
对系数估计值不确定性的度量,值越小越好。 |
系数0.02的标准误很小(0.001),说明估计比较精确。 |
解读 statsmodels
的回归结果报告:t
与 P>|t|
t |
t统计量 |
衡量系数是否显著不为零。t = coef / std err 。 |
t值的绝对值(18.5)远大于2,表明系数极有可能不为0。 |
P>|t| |
p值 |
系数实际为零的概率。这是最重要的指标。 |
p值 (0.000) 远小于0.05,我们有充分理由拒绝“pps与EPS无关”的原假设。 |
结论: 所有三个特征 (pps
, bm
, roa
) 的p值都接近于0,表明它们都是预测EPS的 统计显著 的指标。
解读 statsmodels
的回归结果报告:R-squared
与置信区间
R-squared |
R² |
模型可以解释目标变量变异的百分比。 |
R²为0.301,表示我们的模型可以解释EPS变化的30.1%。 |
[0.025 0.975] |
置信区间 |
我们有95%的信心认为,真实的系数值落在这个区间内。 |
我们95%确信,pps的真实系数在 [0.018, 0.022] 之间。 |
实战2:使用 scikit-learn
进行回归分析
scikit-learn
的流程是为构建机器学习工作流设计的:预处理 -> 训练 -> 预测
。
sklearn
结果解读:截距与标准化系数
sklearn
直接给出了截距和系数,但没有统计检验报告。
sklearn
结果解读:Beta系数的意义
由于我们对数据进行了标准化,正确的解释是:“在其他特征不变的情况下,pps 每增加一个标准差,我们预测EPS将增加 0.2311 元。”
这种标准化的系数有时被称为 Beta Coefficients,它可以用来比较不同特征的 相对重要性。在这里,pps
的标准化系数 (0.2311) 最大,说明在我们的模型中,它是对EPS影响最大的预测因子。
总结:statsmodels
vs. scikit-learn
- 统计推断
核心焦点 |
统计推断 |
机器学习预测 (次要) |
主要产出 |
详细的统计摘要 |
训练好的模型对象 (次要) |
显著性检验 |
提供 t-统计量, p-值 |
不直接提供 |
数据预处理 |
需要手动添加常数项 |
强调特征缩放 (可选,但推荐) |
适用场景 |
学术研究, 经济分析, 解释变量关系 |
(次要) |
结论: statsmodels
更适用于需要深入理解变量之间关系、进行假设检验和学术报告的场景。
总结:statsmodels
vs. scikit-learn
- 机器学习预测
核心焦点 |
统计推断 (次要) |
机器学习预测 |
主要产出 |
详细的统计摘要 (次要) |
训练好的模型对象 |
显著性检验 |
(次要) |
不直接提供 |
数据预处理 |
需要手动添加常数项 (次要) |
强调特征缩放 |
适用场景 |
(次要) |
构建预测系统, 机器学习流水线 |
结论: scikit-learn
更适用于构建自动化预测系统、集成到复杂机器学习流程中,或侧重于模型预测性能而非统计解释的场景。作为经济学专业的学生,你们需要同时掌握这两种工具。
练习1:手动计算梯度下降 - 问题
给定以下数据点和初始模型参数,请手动执行一步梯度下降。
数据: | x | y | |:—:|:—:| | 2.3 | -1.2 | | 4.5 | 0.0 | | 6.7 | 3.4 |
模型: \(f(x) = w_0 + w_1x\) 初始参数: \(w_0 = 0, w_1 = 0.5\) 学习率: \(\alpha = 0.01\)
问题: 1. 计算当前参数下的代价函数 \(J(w_0, w_1)\) 的值。 2. 计算当前参数下 \(J\) 对 \(w_0\) 和 \(w_1\) 的梯度。 3. 执行一步梯度下降,计算更新后的新参数值 \(w_0'\) 和 \(w_1'\)。
练习1:解题步骤 1 - 计算预测值和误差
1. 计算每个点的预测值和误差
2.3 |
-1.2 |
\(0 + 0.5 \times 2.3 = 1.15\) |
\(1.15 - (-1.2) = 2.35\) |
4.5 |
0.0 |
\(0 + 0.5 \times 4.5 = 2.25\) |
\(2.25 - 0.0 = 2.25\) |
6.7 |
3.4 |
\(0 + 0.5 \times 6.7 = 3.35\) |
\(3.35 - 3.4 = -0.05\) |
练习1:解题步骤 2 - 计算代价函数 J
2. 计算代价函数 J (使用 1/n 而不是 1/2n 作为分母,两者仅为常数差异) \[ \large{ J = \frac{1}{3} \sum (f(x) - y)^2 = \frac{1}{3} (2.35^2 + 2.25^2 + (-0.05)^2) } \] \[ \large{ = \frac{1}{3} (5.5225 + 5.0625 + 0.0025) = \frac{10.5875}{3} \approx 3.529 } \]
练习1:解题步骤 3 - 计算梯度
3. 计算梯度 \[ \large{ \frac{\partial J}{\partial w_0} = \frac{1}{n} \sum (f(x) - y) = \frac{1}{3}(2.35 + 2.25 - 0.05) = \frac{4.55}{3} \approx 1.517 } \] \[ \large{ \frac{\partial J}{\partial w_1} = \frac{1}{n} \sum (f(x) - y) \cdot x = \frac{1}{3}(2.35 \cdot 2.3 + 2.25 \cdot 4.5 - 0.05 \cdot 6.7) } \] \[ \large{ = \frac{1}{3}(5.405 + 10.125 - 0.335) = \frac{15.195}{3} \approx 5.065 } \]
练习1:解题步骤 4 - 更新参数
4. 更新参数 (学习率 \(\alpha = 0.01\)) \[ \large{ w_0' = w_0 - \alpha \frac{\partial J}{\partial w_0} = 0 - 0.01 \cdot 1.517 = -0.01517 } \] \[ \large{ w_1' = w_1 - \alpha \frac{\partial J}{\partial w_1} = 0.5 - 0.01 \cdot 5.065 = 0.44935 } \]
更新后的参数为: \(w_0 \approx -0.015\), \(w_1 \approx 0.449\)
练习2:编程实践与模型改进 - 任务 1
请使用我们今天创建的 ols_training.csv
数据集完成以下任务:
- 处理非线性关系:
- 对目标变量
eps_basic
和特征 pps
同时进行对数变换 (np.log
)。
- 使用变换后的数据作为新的目标变量和特征,重新训练一个 OLS 模型 (
statsmodels
)。
- 比较新模型的 R² 和均方误差(MSE),与原始模型相比是否有改善?
提示: MSE 是反向指标,越低越好。R² 是正向指标,越高越好。
练习2:编程实践与模型改进 - 任务 2
- 添加多项式特征:
- 在原始模型的基础上,额外加入 \(pps^2\), \(pps^3\), \(pps^4\) 作为新的特征。
- 训练新模型。这些新的高次项特征是否统计显著 (检查p值)?
- 加入新特征后,模型的拟合度 (R²) 是否得到了提高?
开放式讨论:我们还能找到哪些变量来改进模型?
我们已经使用了股价、净市率和资产收益率。如果我们想进一步提高盈利预测的准确率,还可以考虑加入哪些新的特征变量?
请从以下几个角度思考:
- 宏观经济: 利率、GDP增长率、通货膨胀率…
- 行业层面: 行业景气指数、行业平均市盈率…
开放式讨论:公司自身与另类数据
- 公司自身:
- 历史财务数据: 过去几期的盈利增长率、研发投入…
- 另类数据: 管理层变动新闻、网络舆情、供应链数据…
什么样的变量可以帮助我们更好地进行预测?这是一个没有标准答案的问题,也是数据科学和金融学完美结合的地方。