原文:
towardsdatascience.com/linear-regression-kernel-trick-and-linear-kernel-39b6be3a3bf5
·发表于 Towards Data Science ·8 分钟阅读·2023 年 11 月 5 日
--
除非另有说明,所有图片均由作者提供
在这篇文章中,我想展示一个起初对我来说并不明显的有趣结果,即:
线性回归和没有正则化的线性核岭回归是等价的。
实际上涉及了很多概念和技术,因此我们将逐一回顾每一个,最后综合使用它们来解释这一说法。
首先,我们将回顾经典的线性回归。然后我将解释核技巧和线性核是什么,最后我们将展示上述陈述的数学证明。
经典的——普通最小二乘法(OLS)线性回归是以下问题:
其中:
-
Y 是一个长度为 n 的向量,包含线性模型的目标值
-
beta 是一个长度为 m 的向量:这是模型需要“学习”的未知量
-
X 是一个数据矩阵,具有 n 行和 m 列。我们通常说我们在 m 特征空间中记录了 n 个向量
所以目标是找到使平方误差最小化的 beta 值:
这个问题实际上有一个封闭形式的解,被称为普通最小二乘问题。解为:
一旦知道了解决方案,我们可以使用拟合模型计算给定新 X 值时的新 Y 值,使用:
让我们用 scikit-learn 验证我们的数学:这里有一个 Python 代码示例,展示了如何使用 sklearn 的线性回归器以及基于 numpy 的回归
%matplotlib qt
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
np.random.seed(0)
n = 100
X_ = np.random.uniform(3, 10, n).reshape(-1, 1)
beta_0 = 2
beta_1 = 2
true_y = beta_1 * X_ + beta_0
noise = np.random.randn(n, 1) * 0.5 # change the scale to reduce/increase noise
y = true_y + noise
fig, axes = plt.subplots(1, 2, squeeze=False, sharex=True, sharey=True, figsize=(18, 8))
axes[0, 0].plot(X_, y, "o", label="Input data")
axes[0, 0].plot(X_, true_y, '--', label='True linear relation')
axes[0, 0].set_xlim(0, 11)
axes[0, 0].set_ylim(0, 30)
axes[0, 0].legend()
# f_0 is a column of 1s
# f_1 is the column of x1
X = np.c_[np.ones((n, 1)), X_]
beta_OLS_scratch = np.linalg.inv(X.T @ X) @ X.T @ y
lr = LinearRegression(
fit_intercept=False, # do not fit intercept independantly, since we added the 1 column for this purpose
).fit(X, y)
new_X = np.linspace(0, 15, 50).reshape(-1, 1)
new_X = np.c_[np.ones((50, 1)), new_X]
new_y_OLS_scratch = new_X @ beta_OLS_scratch
new_y_lr = lr.predict(new_X)
axes[0, 1].plot(X_, y, 'o', label='Input data')
axes[0, 1].plot(new_X[:, 1], new_y_OLS_scratch, '-o', alpha=0.5, label=r"OLS scratch solution")
axes[0, 1].plot(new_X[:, 1], new_y_lr, '-*', alpha=0.5, label=r"sklearn.lr OLS solution")
axes[0, 1].legend()
fig.tight_layout()
print(beta_OLS_scratch)
print(lr.coef_)
[[2.12458946]
[1.99549536]]
[[2.12458946 1.99549536]]
如你所见,这两种方法给出了相同的结果(显然!):
现在让我们回顾一种常见的技术,称为核技巧。
思路如下:
我们原始的问题(可以是分类或回归等)存在于输入数据矩阵 X 的空间中,形状为 m 特征空间中的 n 向量。有时,向量在这个低维空间中无法分离或分类,因此我们希望将输入数据转换到高维空间。我们可以手动创建新的特征来实现这一点。随着特征数量的增加,数值计算也会增加:想象一下计算两个长度为 10 亿的向量的点积,并对矩阵中的所有向量重复这一过程。
核技巧的核心在于使用精心设计的变换函数——通常记作 T 或 phi——它将长度为 m 的向量 x 转换为长度为 m'的新向量 x',使得我们的新数据确实具有高维,但计算负担保持在最低限度。
为了实现这一点,函数 phi 必须满足一些属性,以便新高维特征空间中的点积可以写成一个函数——即核函数——关于相应的输入向量:
这意味着高维空间中的内积可以表示为输入向量的函数。换句话说,我们可以仅使用低维向量来计算高维空间中的内积。这就是核技巧:我们可以利用高维空间的多样性,而无需实际在该空间中进行计算。
唯一的条件是我们只需要高维空间中的点积。
实际上,有一些强大的数学定理描述了创建这种变换 phi 和/或核函数的条件。
创建 m² 维空间的第一个核函数示例如下:
另一个例子:在核函数中添加常数会增加维度,生成新的特征,这些特征是缩放的输入特征:
另一个我们下面将使用的核是线性核:
因此,单位变换等同于使用一个在原始空间中计算内积的核函数。
实际上,还有很多其他有用的核函数,比如径向基(RBF)核或更通用的多项式核,它们创建高维且非线性的特征空间。为了完整性,这里有一个使用 RBF 核在线性回归背景下计算非线性回归的例子:
import numpy as np
from sklearn.kernel_ridge import KernelRidge
import matplotlib.pyplot as plt
np.random.seed(0)
X = np.sort(5 * np.random.rand(80, 1), axis=0)
y = np.sin(X).ravel()
y[::5] += 3 * (0.5 - np.random.rand(16))
# Create a test dataset
X_test = np.arange(0, 5, 0.01)[:, np.newaxis]
# Fit the KernelRidge model with an RBF kernel
kr = KernelRidge(
kernel='rbf', # use RBF kernel
alpha=1, # regularization
gamma=1, # scale for rbf
)
kr.fit(X, y)
y_rbf = kr.predict(X_test)
# Plot the results
fig, ax = plt.subplots()
ax.scatter(X, y, color='darkorange', label='Data')
ax.plot(X_test, y_rbf, color='navy', lw=2, label='RBF Kernel Ridge Regression')
ax.set_title('Kernel Ridge Regression with RBF Kernel')
ax.legend()
如果变换 phi 将 x 转换为 phi(x),那么我们可以写出一个新的线性回归问题:
注意维度的变化:线性回归问题的输入矩阵从 [nxm] 变为 [nxm’],因此系数向量 beta 从长度 m 变为 m’。
这就是核技巧出现的地方:在计算解 beta’ 时,注意到 X’ 与其转置的乘积出现,这实际上是所有点积的矩阵,称为核矩阵,原因显而易见:
最后,让我们看看原始陈述的证明:使用线性核在线性回归中是无用的,因为它等同于标准线性回归。
线性核通常在支持向量机的背景下使用,但我想知道它在线性回归中会表现如何。
为了证明这两种方法是等价的,我们必须证明:
使用 beta 的第一种方法是原始线性回归,而使用 beta’ 的第二种方法是使用线性核化方法。
我们实际上可以使用上面看到的矩阵属性和关系来证明这一点:
我们可以再次使用 Python 和 scikit-learn 来验证这一点:
%matplotlib qt
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
np.random.seed(0)
n = 100
X_ = np.random.uniform(3, 10, n).reshape(-1, 1)
beta_0 = 2
beta_1 = 2
true_y = beta_1 * X_ + beta_0
noise = np.random.randn(n, 1) * 0.5 # change the scale to reduce/increase noise
y = true_y + noise
fig, axes = plt.subplots(1, 2, squeeze=False, sharex=True, sharey=True, figsize=(18, 8))
axes[0, 0].plot(X_, y, "o", label="Input data")
axes[0, 0].plot(X_, true_y, '--', label='True linear relation')
axes[0, 0].set_xlim(0, 11)
axes[0, 0].set_ylim(0, 30)
axes[0, 0].legend()
# f_0 is a column of 1s
# f_1 is the column of x1
X = np.c_[np.ones((n, 1)), X_]
beta_OLS_scratch = np.linalg.inv(X.T @ X) @ X.T @ y
lr = LinearRegression(
fit_intercept=False, # do not fit intercept independantly, since we added the 1 column for this purpose
).fit(X, y)
new_X = np.linspace(0, 15, 50).reshape(-1, 1)
new_X = np.c_[np.ones((50, 1)), new_X]
new_y_OLS_scratch = new_X @ beta_OLS_scratch
new_y_lr = lr.predict(new_X)
axes[0, 1].plot(X_, y, 'o', label='Input data')
axes[0, 1].plot(new_X[:, 1], new_y_OLS_scratch, '-o', alpha=0.5, label=r"OLS scratch solution")
axes[0, 1].plot(new_X[:, 1], new_y_lr, '-*', alpha=0.5, label=r"sklearn.lr OLS solution")
axes[0, 1].legend()
fig.tight_layout()
print(beta_OLS_scratch)
print(lr.coef_)
在这篇文章中,我们回顾了简单线性回归,包括问题及其解的矩阵公式。
然后我们看到核技巧是什么,以及它如何使我们能够从非常高维的空间中受益,而无需实际将我们的低维数据移动到这个计算密集型空间中。
最后,我展示了在线性回归的背景下线性核实际上是无用的,它相当于简单线性回归。
如果你考虑加入 Medium,请使用此链接快速订阅并成为我的推荐会员:
[## 使用我的推荐链接加入 Medium - Yoann Mocquin
并订阅以在我发布新帖子时收到通知:
最后,你可以查看我其他一些帖子,涉及傅里叶变换、pandas 数据类型或数据科学中的线性代数技术: