本文最初发表于 Towards Data Science 博客,经原作者 Rashida Nasrin Sucky 授权,InfoQ 中文站翻译并分享。
我们花了那么多时间去开发一个机器学习算法。但在部署完成后,如果该算法性能较差,就会变得令人沮丧。问题是,如果算法没有达到预期的效果,那下一步该怎么办呢?是什么地方出了问题?训练数据的数量是否足够?使用的特征是否正确?是否应该继续收集更多的数据?我们可以这样做,但这样一来,既费时又费钱。我们应该增加更多的特征吗?那也可能很昂贵。
往哪个方向走?
如果你的机器学习算法没有达到预期的效果,那下一步该怎么办?下面有几个选择:
获取更多的训练数据,这是非常耗时的。甚至可能需要数月才能获得更多的研究数据。
获取更多的训练特征。这可能也会非常耗时。但如果添加一些多项式特征可行的话,那就太酷了。
选择一组较小的训练特征集。
增加正则化项。
减少正则化项。
那么,接下来你应该尝试哪种选择呢?不要随便开始尝试任何选择,这可不是一个好主意。因为你最终可能会在一些没有帮助的事情上耗费太多时间。你需要做的是,先发现问题,然后采取相应的行动。学习曲线有助于轻松地发现问题,这可以节省很多时间。
学习曲线对于确定如何提高算法的性能非常有用。判断算法是否存在偏差或欠拟合,方差或过拟合,或者两者兼而有之,学习曲线在这方面是很有用的。
学习曲线的工作原理学习曲线是成本函数的曲线图。训练数据的成本函数和交叉验证数据的成本函数在同一个图中给出了关于算法的重要见解。作为提醒,下面是成本函数的公式:
换句话说,学习曲线是预测输出减去原始输出的平方除以训练数据数量的两倍。为了绘制学习曲线,我们需要将这些成本函数绘制成训练数据(m)的函数。我们将只使用较小的训练数据子集来训练数据,而不是使用所有的训练数据。
请看下面的图片:
这是一个概念。
如果训练的数据数量太少,算法将对训练数据完全拟合,并且成本函数将返回 0。
上图清楚地表明,当我们只用一个、两个或三个数据算法训练数据时,可以很好地学习很少的数据,并且训练成本为零或接近于零。但是这种算法在其他数据上并不能得到很好的性能。
当你尝试将交叉验证数据拟合到该算法上时,它在交叉验证数据上表现不佳的可能性很高。因此,交叉验证数据的成本函数将返回非常高的值。
另一方面,当我们使用越来越多的数据来训练算法时,它将不能很好地拟合训练数据。这样,训练成本就会越来越高。
同时,由于该算法是在大量数据上进行训练的,所以,在交叉验证数据上的表现会更好,交叉验证数据的成本函数会返回一个较低的值。下面是如何制定学习曲线的方法。
开发一种学习方法我将逐步演示如何绘制学习曲线。为了绘制学习曲线,我们首先需要一个机器学习算法。为简单起见,我将使用线性回归算法。让我们先开发一个线性回归算法。
首先,导入包和数据集。我在这里使用的数据集取自 Andrew Ng(吴恩达)的 Coursera 机器学习课程。在这个数据集中,X 值和 y值分别组织在 Excel 文档中的不同工作表中。
提醒一下,X 是我们将用于开发和训练机器学习算法的特征。y 是我们需要预测的输出特征。
交叉验证数据的 X 值和 y值也组织在同一个 Excel 文档的另外两个工作表中。我在文末提供了这个数据集的下载链接。请随时下载这个数据集并亲自练习。
%matplotlib inline
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
file = pd.ExcelFile('dataset.xlsx')
df = pd.read_excel(file, 'Xval', header=None)
df.head()
以同样的方式,导入训练集的 y 值:
y = pd.read_excel(file, 'yval', header=None)
y.head()
让我们快速开发线性回归算法。
定义假设
线性回归使用非常基本的线性方程来预测,我们在学校都学过。公式如下:
Y = C + BX
对于机器学习,我们使用不同的术语。
这里,h 是假设或预测值,θ0 和 θ1 是系数,X 是输入特征。
我们已经有了 X。我们需要计算出 h,它应该与 y的值匹配,因为我们的目标是能够预测 y 的值。
θ0 和 θ1 在开始时随机初始化。我们将通过迭代不断优化θ0 和 θ1 的值。
在每次迭代中,我们将使用成本函数和梯度公式来计算成本,以更新 θ 值。
成本函数和梯度下降
成本函数为我们提供了预测值与原始输出特征有多大差异的概念。在这里,我们的输出特征是 y,预测的输出将是“h”。所以,成本函数会告诉我们,“h”与“y”的偏差有多大。我们希望成本函数值越低越好。
成本函数公式如下:
我们将继续运行算法,直到成本函数最小。在每次迭代中,我们使用梯度下降来更新 θ 值。
为了更新 θ 值,我们将从之前的 θ 值中扣除梯度下降。当我们将其编码时,它会更加清晰。
其中,m 是训练数据的数量,而 α 是学习率。
开发线性回归算法
利用上述公式开发假设和成本函数。
m = len(df)
def hypothesis(theta, X):
return theta[0] + theta[1]*X
def cost_calc(theta, X, y):
return (1/2*m) * np.sum((hypothesis(theta, X) - y)**2)
现在,我们将定义梯度下降来优化参数 θ0 和 θ1。在每次迭代中,我们将更新 θ值,并跟踪成本函数和 θ 值。
最后,它将返回每次迭代的成本列表 \thetra 值。代码很简单,请参阅如下代码段:
def gradient_descent(theta, X, y, epoch, alpha):
cost = []
theta_hist = []
i = 0
while i < epoch:
hx = hypothesis(theta, X)
theta[0] -= alpha*(sum(hx-y)/m)
theta[1] -= (alpha * np.sum((hx - y) * X))/m
cost.append(cost_calc(theta, X, y))
i += 1
return theta, cost
这就完成了线性回归算法。我们需要一种方法来预测输出。在预测方法中,我们将使用来自梯度下降函数和假设函数的最终 θ 进行预测。
def predict(theta, X, y, epoch, alpha):
theta, cost = gradient_descent(theta, X, y, epoch, alpha)
return hypothesis(theta, X), cost, theta
现在,将参数初始化为零,并使用predict
函数来预测输出变量。
theta = [0,0]
y_predict, cost, theta = predict(theta, df[0], y[0], 1400, 0.001)
更新后的 θ 值为:[10.724868115832654, 0.3294833798797125]
现在,将预测 output(h) 和原始 output(y) 与 df 或 X 绘制在同一图中。
plt.figure()
plt.scatter(df, y)
plt.scatter(df, y_predict)
看起来算法运行得很好。预测的输出线是从中性位置开始的。
是时候绘制学习曲线了!
绘制学习曲线现在,我们可以绘制学习曲线了。首先,让我们导入交叉验证数据及的 x 和 y值。正如我之前提到的,我们将它们组织在单独的 Excel 表格中。
file = pd.ExcelFile('dataset.xlsx')
cross_val = pd.read_excel(file, 'X', header=None)
cross_val.head()
cross_y = pd.read_excel(file, 'y', header=None)
cross_y.head()
出于这个目的,我想稍微修改一下gradient_descent
函数。
在之前的gradient_descent
函数中,我们计算了每次迭代的成本。我这么做是因为在传统的机器学习算法开发中,这是一个很好的实践做法。
但对于学习曲线,我们并不需要每次迭代的成本。因此,为了节省运行时间,我将不计算每个轮数的成本函数。我们将只返回更新的参数。
def grad_descent(theta, X, y, epoch, alpha):
i = 0
while i < epoch:
hx = hypothesis(theta, X)
theta[0] -= alpha*(sum(hx-y)/m)
theta[1] -= (alpha * np.sum((hx - y) * X))/m
i += 1
return theta
正如我之前所讨论的,要制定一个学习曲线,我们需要用不同的训练数据子集来训练学习算法。
在我们的训练数据集中,我们有 21 个数据。我将只使用 1 个数据训练算法,然后使用 2 个数据,再然后使用 3 个数据,以此类推,一直到 21 个数据。
因此,我们将在 21 个训练数据子集上对算法进行 21 次训练。我们还将跟中每个训练数据子集的成本函数。请仔细阅读一下代码,这样理解才会更清楚。
j_tr = []
theta_list = []
for i in range(0, len(df)):
theta = [0,0]
theta_list.append(grad_descent(theta, df[0][:i], y[0][:i], 1400, 0.001))
j_tr.append(cost_calc(theta, df[0][:i], y[0][:i]))
theta_list
下面是每个训练数据子集的训练参数:
以下是每个训练子集的成本:
看看每个子集的成本。当训练数据仅为 1 或 2 时,成本为零或几乎为零。随着我们不断增加训练数据,成本也随之上升,这正是我们所预期的。
现在,对训练数据的所有子集使用上述参数来计算交叉验证数据的成本:
j_val = []
for i in theta_list:
j_val.append(cost_calc(i, cross_val[0], cross_y[0]))
j_val
刚开始的时候,由于训练参数来自太少的训练数据,所以成本确实很高。但随着训练数据的增多,参数的改进,交叉验证误差将会不断下降。
让我们将训练误差和交叉验证误差绘制在同一图中:
%matplotlib inline
import matplotlib.pyplot as plt
plt.figure()
plt.scatter(range(0, 21), j_tr)
plt.scatter(range(0, 21), j_val)
这就是我们的学习曲线。
从学习曲线中得出决策上面的学习曲线看起来不错。它正以我们预期的那样流动着。一开始,训练误差太小,验证误差太大。
慢慢地,它们彼此完全重叠在一起了。所以,这是完美的!但在现实生活中,这种情况并不经常发生。
大多数机器学习算法并非第一次就能完美地工作。几乎每时每刻,它都会有一些我们需要解决的问题。下面我将讨论一些问题。
我们可能会发现学习曲线如下所示:
如果在训练误差和验证误差之间存在显著的差距,则表明存在高方差问题。这也可以称为过拟合问题。
获取更多训练数据或选择更小的特征集,或者两者做法都可以解决这个问题。
如果学习曲线看起来是这样的,这就意味着在开始时训练误差太小,验证误差太高。慢慢地,训练误差越来越高,而验证误差越来越低。但在某一时刻上,它们将变得平行。从图中可以看出,经过某一点后,即使有了更多的训练数据,交叉验证误差也不会再减少了。
在这种情况下,获取更多的训练数据并不能改善机器学习算法。
这表明该学习算法存在高偏差问题。在这种情况下,获取更多的训练特征可能会有所帮助。
修正学习算法假设我们正在实现线性回归算法,但算法并没有如预期的那样工作。
那该怎么办?
首先,绘制一条学习曲线,就像我在下面演示的那样:
如果检测到高方差问题,请根据特征的重要性选择较小的特征集。如果有帮助的话,那将会节省一些时间。如果没有,请尝试获取更多的训练数据。
如果你从学习曲线中检测到高偏差问题,那么你已经知道获取额外的特征是一个可能的解决方案。你甚至可以尝试添加一些多项式特征。大量的时间对你很有帮助,也节省了很多时间。
如果你正在实现一个带有正则化项 λ 的算法,如果该算法存在高偏差,请尝试减少 λ ;如果该算法存在高方差问题,请尝试增大 λ 。这里有一篇文章《如何改进机器学习算法:偏差、方差和正则化》(How to Improve a Machine Learning Algorithm: Bias, Variance and, Regularization),详细阐述了正则项与偏差和方差的关系:
在使用神经网络的情况下,我们也可能会遇到这种偏差或方差问题。
对于高偏差或欠拟合的问题,我们需要增加神经元的数量或隐藏层的数量。为了解决高方差或过拟合的问题,我们应该减少神经元的数量或隐藏层的数量。我们甚至可以用不同数量的神经元绘制学习曲线。
作者介绍:Rashida Nasrin Sucky,波士顿大学学生。
原文链接:
https://towardsdatascience.com/your-ml-algorithm-is-not-performing-well-613dd2b07fc
你也「在看」吗?👇