在存在异常值的情况下,使用不同阶数的LK范数训练机器学习模型对结果的影响解读

7
( RMSE 和 MAE 都是衡量两个向量之间距离的方法:预测向量和目标值向量。有各种距离度量或范数可供选择。通常情况下,计算向量的大小或长度通常直接需要或作为更广泛的向量或向量矩阵运算的一部分。

尽管在回归任务中RMSE通常是首选的性能度量,但在某些情况下,您可能更喜欢使用另一个函数。例如,如果数据集中有许多异常值实例,则可以考虑使用平均绝对误差(MAE)。

更正式地说,范数指数越高,它就越关注大的值而忽略小的值。这就是为什么RMSE比MAE更敏感于异常值的原因。) 来源:《Python机器学习基础教程》

因此,在任何数据集中理想情况下,如果我们有很多异常值,则表示“表示预测和真实标签之间的绝对差异的向量的范数;类似于下面代码中的y_diff”应增长,如果我们增加范数...换句话说,RMSE应该大于MAE。--> 如果有误,请纠正 <--

根据这个定义,我生成了一个随机数据集,并在其中添加了许多异常值,如下面的代码所示。我计算了残差或y_difflk_norm,对于许多k值(从1到5),但我发现当K值增加时,lk_norm会降低;然而,我期望RMSE,即norm=2,大于MAE,即norm=1。

我很想了解LK范数如何在增加K(即顺序)时降低,这与上面的定义相反。

感谢您提前提供的任何帮助!

代码:

import numpy as np
import plotly.offline as pyo
import plotly.graph_objs as go
from plotly import tools

num_points = 1000
num_outliers = 50

x = np.linspace(0, 10, num_points)

# places where to add outliers:
outlier_locs = np.random.choice(len(x), size=num_outliers, replace=False)
outlier_vals = np.random.normal(loc=1, scale=5, size=num_outliers)

y_true = 2 * x
y_pred = 2 * x + np.random.normal(size=num_points)
y_pred[outlier_locs] += outlier_vals

y_diff = y_true - y_pred

losses_given_lk = []
norms = np.linspace(1, 5, 50)

for k in norms:
    losses_given_lk.append(np.linalg.norm(y_diff, k))

trace_1 = go.Scatter(x=norms, 
                     y=losses_given_lk, 
                     mode="markers+lines", 
                     name="lk_norm")

trace_2 = go.Scatter(x=x, 
                     y=y_true, 
                     mode="lines", 
                     name="y_true")

trace_3 = go.Scatter(x=x, 
                     y=y_pred, 
                     mode="markers", 
                     name="y_true + noise")

fig = tools.make_subplots(rows=1, cols=3, subplot_titles=("lk_norms", "y_true", "y_true + noise"))
fig.append_trace(trace_1, 1, 1)
fig.append_trace(trace_2, 1, 2)
fig.append_trace(trace_3, 1, 3)

pyo.plot(fig, filename="lk_norms.html")

输出:

输入图像描述

最后,我想知道什么情况下使用L3或L4范数等等...

2个回答

6
为了回答您的问题,我们首先需要再次查看Lp范数,了解其定义以及在极限情况下的含义。
Lp范数的定义如下:

Definition Lp-norm

你正确地指出当p=1时,求绝对值之和,当p=2时,求平方和。还有两种特殊情况:p=0p=∞。对于p=0,Lp范数本质上是计算向量x中非零元素的数量,而p=∞返回向量x的最大绝对值。

这在拟合模型(例如简单的线性回归)方面意味着什么?正如你所正确陈述的那样,p越高,越强调异常值。L-infinity损失将尝试将回归模型的最大绝对误差最小化。这意味着即使一个异常值也会完全影响拟合线的参数。相反,L0损失将尝试将非零元素的数量最小化,这意味着最优模型将尽可能多地使点直接落在拟合线上(即误差为零)。以下是一个例子,展示了在存在单个异常值时采用不同Lp范数作为损失函数时对拟合线的影响:

Outlier example

现在针对您的建议,即更大的p会导致误差向量y_diff的范数增加。只有当您在范数定义中省略了第p个根时才会出现这种情况。我调整了您的代码并绘制了py_diff的Lp-范数以及Lp-范数的p次方。

y_diff for different Lp norms.

你可以看到,如果在范数中省略 pth-root,你的直觉是正确的。但是如果包含它,它将会收敛到 L-无穷范数(即最大绝对误差),对于较大的 p。由于 L-无穷范数将忽略除最大误差之外的所有误差,所以它是递减的。
代码:
import numpy as np
np.random.seed(42)

num_points = 1000
num_outliers = 50

x = np.linspace(0, 10, num_points)

# places where to add outliers:
outlier_locs = np.random.choice(len(x), size=num_outliers, replace=False)
outlier_vals = np.random.normal(loc=1, scale=5, size=num_outliers)

y_true = 2 * x
y_pred = 2 * x + np.random.normal(size=num_points)
y_pred[outlier_locs] += outlier_vals

y_diff = y_true - y_pred

losses_given_lk, losses = [], []
norms = np.linspace(1, 5, 50)

for k in norms:
    losses_given_lk.append(np.linalg.norm(y_diff, k))
    losses.append(np.linalg.norm(y_diff,k)**k)
    
plt.figure(dpi=100)
plt.semilogy(norms, losses_given_lk, label=r"$||y_{diff}||_p$")
plt.semilogy(norms, losses,label=r"$||y_{diff}||_p^p$")
plt.grid('on')
plt.legend()
plt.xlabel('p')

from scipy.optimize import minimize

points = np.array([[0,1.1],[0.75,1.8],[1,2.2],[1.75,2.8],[2,3.1],[1.5,1.25]])
plt.figure(dpi=100)
plt.scatter(points[:-1,0],points[:-1,1],label="inliers",marker='x')
plt.scatter(points[-1,0],points[-1,1],label="outlier",marker='x')

def lp_loss(p):
    return lambda x: np.linalg.norm((x[0] + x[1]*points[:,0]) - points[:,1],p)

x = np.arange(0,3)
for p in [0, 1, 2, np.inf]:
    x0 = (1.1,1.1)
    res = minimize(lp_loss(p), x0, method="bfgs")
    print(p, res.x)
    y_pred = res.x[0] + res.x[1]*x
    print((res.x[0] + points[:,0]*res.x[1]) - points[:,1])
    plt.plot(x,y_pred,label=f"p={p}")

plt.title(r"True model: $y=x+1 + \epsilon$")
plt.legend()
plt.xlabel("x")
plt.ylabel("y")

谢谢@Tinu在这里回答,然而,我不认为答案是正确的,主要是因为你修改了Lp范数的定义。我们为什么要将lp_norm提高到p次方?文献中从未提到过。请问你能分享一下参考资料吗?谢谢。 - I. A
1
我没有改变Lp范数的定义,在我的答案中有一个维基链接。随着p的增加,误差的p次幂(绝对误差、平方误差、立方误差,等等)将会增加,根据你的直觉。然而,在范数的定义中,你要取误差之和的第p个根,并且这就是为什么误差的范数随着p的增大而减小。我将Lp范数提高到p的幂以说明如果我们不取第p个根,那么你的直觉是正确的。 - Tinu

2

另一个用于np.linalg的Python实现是:

def my_norm(array, k):
    return np.sum(np.abs(array) ** k)**(1/k)

为了测试我们的函数,请运行以下内容:
array = np.random.randn(10)
print(np.linalg.norm(array, 1), np.linalg.norm(array, 2), np.linalg.norm(array, 3), np.linalg.norm(array, 10))
# And
print(my_norm(array, 1), my_norm(array, 2), my_norm(array, 3), my_norm(array, 10))

输出:

(9.561258110585216, 3.4545982749318846, 2.5946495606046547, 2.027258231324604)
(9.561258110585216, 3.454598274931884, 2.5946495606046547, 2.027258231324604)

因此,我们可以看到数字正在减少,与我们在上面问题中发布的图表输出类似。
然而,在Python中正确实现RMSE的方法是:np.mean(np.abs(array) ** k)**(1/k),其中k等于2。因此,我已经用mean替换了sum
因此,如果我添加以下函数:
def my_norm_v2(array, k):
    return np.mean(np.abs(array) ** k)**(1/k)

执行以下命令:

print(my_norm_v2(array, 1), my_norm_v2(array, 2), my_norm_v2(array, 3), my_norm_v2(array, 10))

输出:

(0.9561258110585216, 1.092439894967332, 1.2043296427640868, 1.610308452218342)

因此,数字正在增加。
在下面的代码中,我重新运行了上面问题中发布的相同代码,使用了修改后的实现方式,结果如下:
import numpy as np
import plotly.offline as pyo
import plotly.graph_objs as go
from plotly import tools

num_points = 1000
num_outliers = 50

x = np.linspace(0, 10, num_points)

# places where to add outliers:
outlier_locs = np.random.choice(len(x), size=num_outliers, replace=False)
outlier_vals = np.random.normal(loc=1, scale=5, size=num_outliers)

y_true = 2 * x
y_pred = 2 * x + np.random.normal(size=num_points)
y_pred[outlier_locs] += outlier_vals

y_diff = y_true - y_pred

losses_given_lk = []
losses = []
norms = np.linspace(1, 5, 50)

for k in norms:
    losses_given_lk.append(np.linalg.norm(y_diff, k))
    losses.append(my_norm(y_diff, k))

trace_1 = go.Scatter(x=norms, 
                     y=losses_given_lk, 
                     mode="markers+lines", 
                     name="lk_norm")

trace_2 = go.Scatter(x=norms, 
                     y=losses, 
                     mode="markers+lines", 
                     name="my_lk_norm")

trace_3 = go.Scatter(x=x, 
                     y=y_true, 
                     mode="lines", 
                     name="y_true")

trace_4 = go.Scatter(x=x, 
                     y=y_pred, 
                     mode="markers", 
                     name="y_true + noise")

fig = tools.make_subplots(rows=1, cols=4, subplot_titles=("lk_norms", "my_lk_norms", "y_true", "y_true + noise"))
fig.append_trace(trace_1, 1, 1)
fig.append_trace(trace_2, 1, 2)
fig.append_trace(trace_3, 1, 3)
fig.append_trace(trace_4, 1, 4)

pyo.plot(fig, filename="lk_norms.html")

输出:

在此输入图片描述

这就解释了为什么我们增加k时损失会增加。


2
我实在找不到你所谓的“广义RMSE”的理由,除了当k=2时可以恢复RMSE。RMSE作为残差方差估计量是有其合理性的,但它不能推广到其他幂次。 - One Full Time Equivalent
1
你的定义在不同k值之间比较上也有缺陷:1/n因子位于第k次根号下,所以你只是用任意权重(1/n)^(1/k)来比较L_k范数,该权重随k增加而增加。 - One Full Time Equivalent

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接