理解损失函数(代码篇)机器学习你会遇到的“坑”

读芯术

2018-09-18 21:27鲲鹏计划获奖作者
关注

全文共1950字,预计学习时长4分钟

在上一节,我们主要讲解了替代损失(Surrogate loss)由来和性质,明白了机器学习中损失函数定义的本质,我们先对回归任务总结一下常用的损失函数:

均方误差(MSE):

import numpy as np

defMSE(true, pred):return (true - pred)**2

绝对误差(MAE):

import numpy as np

defMAE(true, pred):return np.abs(true - pred)

HuberLoss:

import numpy as npdef Huber(true, pred,e): a=np.abs(true-pred)return np.array([0.5*i**2 if i<e else e*i-0.5*e**2 for i in a ])

其中,MSE和MAE都是比较熟悉且简单的损失函数,MSE对预测值和真实值的差取了平方,当出现异常值的时候,就会放大相应的Loss,而MAE只是取到预测值和真实值的绝对值。换而言之,异常值会在MSE中占到更大的比例,这样并不合理。我们可以画出简单的图像:

......

a=np.linspace(-100,100)sns.set(style='darkgrid')plt.plot(a,MSE(0,a),'r-')plt.title('MSE')plt.show()......

......

plt.plot(a,MAE(0,a),'r-')......

从图中可以看出,同样是数值为100的点,MSE的Loss会更大,当我们把这些全部加起来得到总体的Loss,数值与真实值偏离越大的比重也会越大。

......

a=np.linspace(0,100)sns.set(style='darkgrid')plt.plot(a,MSE(0,a)/np.sum(MSE(0,a)),'r-')plt.plot(a,MAE(0,a)/np.sum(MAE(0,a)),'b-')plt.title('MAE ratio VS MSEretio')plt.show()......

可以直观的看到,随着偏离真实值的程度增大,异常点所产生的loss的比重也会增大。

从这样看来,MAE似乎是更好的损失函数,使得最后的结果不会偏向于异常值,但是MAE的导数是个常数,使得每次的梯度更新时,都下降同样的长度,这不能保证我们得到满意的结果。Huber Loss虽然看起来很复杂,但实际上只是MSE和MAE的折中,因为它定义了一个参数,这个参数的主要作用是判断一个数据点是否是异常值,当我们的预测值距离真实值较远时,我们选用MSE,反之,就利用MAE,我们尝试调节参数看其在target所张成空间中的形状:

......

fori in [10,30,50,90]: plt.plot(a,Huber(0,a,i))plt.title('Huber Loss')plt.show()

如图,我们可以看到随着的增加,huber loss越来越像MSE,这是因为参数的变大,意味着异常点越来越少。

同时我们也可以预料到,huber loss中偏离越大的点占据的比重应该介于两者之间:

plt.plot(a,Huber(0,a,30)/np.sum(Huber(0,a,30)),'g-',label='Huber')

既然huber loss可以适应异常点,那么我们构建一个含有异常点的数据,用简单的线性回归作为模型,采用不同的loss function,观察它找出的线性回归模型的差异在哪里。首先,我们构建包含异常点的数据:

import numpy as np

from sklearn.datasets import make_regressionrng = np.random.RandomState(0)X, y =make_regression(n_samples=50, n_features=1, random_state=0, noise=4.0,bias=100.0)sns.set(style='darkgrid')X_outliers = rng.normal(0, 0.5, size=(4, 1))y_outliers = rng.normal(0, 2.0, size=4)X_outliers[:2, :] += X.max() + X.mean() /4.X_outliers[2:, :] += X.min() - X.mean() /4.y_outliers[:2] += y.min() - y.mean() /4.y_outliers[2:] += y.max() + y.mean() /4.X = np.vstack((X,X_outliers))y = np.concatenate((y,y_outliers))plt.plot(X, y, 'k.')plt.show()

如图,我们可以很清晰的看到中间的样本点是近乎完美的线性,但在左上方和右下放却出现了一些异常点。

我们分别构建普通最小二乘的loss(MSE)、ridge regression的loss(MSE+),以及huber loss来观察在样本空间会形成怎样的结果:

from sklearn.linear_model import HuberRegressor,Ridge

from sklearn.linear_model import LinearRegressionx = np.linspace(X.min(), X.max(), 100)huber =HuberRegressor(fit_intercept=True, alpha=0.0, max_iter=100,epsilon=1.5)huber.fit(X, y)coef_ = huber.coef_ * x + huber.intercept_plt.plot(x,coef_,'r-',label="huber loss, %s"%1.5)ridge = Ridge(fit_intercept=True, alpha=3, random_state=0, normalize=True)ridge.fit(X, y)coef_ridge = ridge.coef_coef_ = ridge.coef_ * x + ridge.intercept_plt.plot(x,coef_, 'g-', label="ridge regression")ols =LinearRegression(fit_intercept=True, normalize=True)ols.fit(X, y)coef_ols = ols.coef_coef_ =ols.coef_ * x + ols.intercept_plt.plot(x,coef_, 'b-', label="linear regression")plt.title("Comparison ofHuberRegressor vs Ridge")plt.xlabel("X")plt.ylabel("y")plt.legend()plt.show()

如图,OLS和Ridge regression 都不同程度上受到了异常点的影响,而huber loss却没有受任何影响。

如果我们只是看这幅图,也不会觉得huber loss有多么厉害,因为OLS和Ridge regression的偏差也不是很大。这里面的很大一部分原因是异常点的偏离还不是很大,或者正常的点占的比重还是比较大,我们接下来减小正常点的个数:

X, y = make_regression(n_samples=15, n_features=1, random_state=0,noise=4.0,

bias=100.0)

如图,正常点所占的比重越小,普通的Loss就无法适应,它所决定出的回归方程几乎没有什么预测能力。

面对分类问题,我们是不是也可以选择不同的loss function使得效果更好呢?分类任务中,我们常用:

负对数损失:

defcross_entropy(predictions, targets, epsilon=1e-10):

predictions = np.clip(predictions,epsilon, 1.- epsilon) N = predictions.shape[0] ce_loss =-np.sum(np.sum(targets * np.log(predictions +1e-5)))/Nreturn ce_loss

hingeloss:

defhinge(y,f):return max(1-y*f)

我们分别选用这两种Loss,其实也对应着linear SVM和logistic regression,然后在IRIS数据利用随机梯度下降的算法观察决策边界的变化:

import numpy as np

import matplotlib.pyplot as pltfrom sklearn import datasetsfrom sklearn.linear_model import SGDClassifieriris = datasets.load_iris()X = iris.data[:, :2]y = iris.targetcolors ="bry"idx = np.arange(X.shape[0])np.random.seed(13)np.random.shuffle(idx)X = X[idx]y = y[idx]mean = X.mean(axis=0)std = X.std(axis=0)X = (X - mean) / stdh =.02clf = SGDClassifier(loss='log',alpha=0.01, max_iter=100).fit(X, y)x_min, x_max = X[:, 0].min() -1, X[:, 0].max() +1y_min, y_max = X[:, 1].min() -1, X[:, 1].max() +1xx, yy =np.meshgrid(np.arange(x_min, x_max, h),np.arange(y_min, y_max,h))Z =clf.predict(np.c_[xx.ravel(), yy.ravel()])Z = Z.reshape(xx.shape)cs = plt.contourf(xx, yy,Z, cmap=plt.cm.RdBu,alpha=0.6)plt.axis('tight')for i, color in zip(clf.classes_, colors): idx = np.where(y == i) plt.scatter(X[idx, 0], X[idx, 1], c=color, label=iris.target_names[i], cmap=plt.cm.RdBu, edgecolor='black', s=20,edgecolors='k')plt.title("Decision surfaceof Log Loss")plt.axis('tight')plt.show()

同样,我们可以画出hinge loss:

clf = SGDClassifier(loss='hinge',alpha=0.01, max_iter=100).fit(X, y)

从图中我们无法说明哪一个loss是更好的,一方面对于这样简单的数据,可能loss并不起主要作用,另一方面,我们需要观察测试集的泛化误差来确定,这里面只是两个特征所构成的空间。

注意到我们在这里添加了L2正则化,除此之外,我们还可以自由的添加L1正则化和弹性网(结合两类正则化),作为我们的结构风险项。总之,在替代损失结构之中,我们只需要让其具备连续性和一致性(凸函数不是必要的),就是合理的,所以我们完全可以根据自己的数据和任务去设计更灵活的loss function。

读芯君开扒

课堂TIPS

除了MSE,MAE,huber loss,在回归任务中,我们还会使用log-cosh loss,它可以保证二阶导数的存在,有些优化算法会用到二阶导数,在xgboost中我们同样需要利用二阶导数;同时,我们还会用到分位数损失,希望能给不确定的度量。

除了log和hinge,在分类任务中,我们还有对比损失(contrastive loss)、softmax cross-entropy loss、中心损失(center loss)等损失函数,它们一般用在神经网络中。

lossfunction多样性的背后实际上是靠着一类叫做随机梯度下降(SGD)的优化算法作为支撑,随机梯度下降的优越性绝不是为了减小时间效率,而是机器学习伟大的创新之一,我们将在下一节介绍以SGD为代表的优化算法。

均方误差(MSE):是回归问题中最常被使用的损失函数。

留言 点赞 发个朋友圈

我们一起探讨AI落地的最后一公里

作者:唐僧不用海飞丝

如需转载,请后台留言,遵守转载规范

举报/反馈