2.4 模型准确度

型准确性

我们常常通过比较模型预测的结果和实际的数据点两者的差距来描述模型的准确性(model accuracy)。在模型拟合(见2.3)一节中, 我们通过在数学上最小化平方误差(squared error)来求解模型的最优参数,也就是说,模型优化的最终目的是尽可能找到准确的模型。当我们需要描述总体数据的特征和关系时,通常的做法是在总体中抽样,根据样本特征(拟合模型)估计总体特征(真模型)。

但我们从平方误差的公式可以看出,平方误差受到数据点数量的影响。逻辑上,对于同一个模型、同一个数据集,数据点数量不同,误差也不同。因此,我们一般不用平方误差来衡量模型准确性。

R^2的概念与计算

R2R^2 是一种常用的描述模型准确性的指标,描述了数据方差和模型误差之间的关系(见图2.4.1)。R2R^2 在数学上表示为模型多大程度上解释了数据的总方差,或用100%减去没有解释的方差和数据总方差之间的占比(公式 2.4.1)。R2R^2取值范围是0~100%。

图2.4.1 数据方差和模型误差之间的关系

R2=100%×(1unexplainedvariancetotalvariance)(公式2.4.1R^2 = 100\%×(1-\dfrac{unexplained\quad variance}{total\quad variance}) (公式2.4.1)

我们将R2R^2的数学表达式写出来:

R2=100%×(1i=1n(dimi)2n1i=1n(didˉ)2n1)(公式2.4.2R^2=100\%×(1-\dfrac{\frac{\sum_{i=1}^n(d_i-m_i)^2}{n-1}}{\frac{\sum_{i=1}^n(d_i-\bar{d})^2}{n-1}})(公式2.4.2)

约去n-1后,

R2=100%×(1i=1n(dimi)2i=1n(didˉ)2)(公式2.4.3R^2=100\%×(1-\dfrac{\sum_{i=1}^n(d_i-m_i)^2}{\sum_{i=1}^n(d_i-\bar{d})^2})(公式2.4.3)

可以看出,R2R^2 不受到数据点数量的影响。从定义上看,R2R^2的值越高,模型曲线越接近数据点,但这不代表R2R^2越高越好。我们当然可以使用十分复杂的模型(例如,无限增加模型的参数)来拟合数据,让数据点无限接近模型曲线,此时我们似乎得到了一个近乎“完美”的模型,它的模型准确性非常高。但是,这样“完美”的模型实际上还拟合了与数据点无关的噪音,这样会导致过拟合(over-fitting)。2.4.2想说明的就是这个问题。

在左侧的图表中红点是总体数据(Population),蓝点为样本数据(Sample),黑色曲线为真模型(True model),浅蓝色曲线是通过样本数据拟合出来的模型(Fitted model)。右侧的表格列出模型在数据上的R2R^2, 可以看出,根据样本数据拟合的模型(浅蓝色)对于样本的拟合准确性非常高,达到85%,但当把模型推广至总体时,准确性却只有69%,远低于真模型的80%,这就出现了过拟合的问题。

图2.4.2

接下来,尝试通过代码来计算R2R^2

考虑真实的模型 y=ax2+bx+cy=ax^2+bx+c, 其中a=0.5a=0.5, b=2b=-2, c=5c=5, 生成模拟数据:

# Here We create data
a = 0.5  # coefficient1
b = -2   # coefficient1
c = 5    # intercept
noiseVar = 500 # 高斯noise的标准差
x = np.arange(-50, 100, 0.1)
# generate data
y =  a * x**2 + b*x + c + noiseVar* np.random.randn(x.size)

我们现在通过代码将数据点绘制出来:

plt.plot(x, y, 'o', color='r', label='Data') # plot data
plt.plot(x, a * x**2 + b*x + c, '-', color='k', label='True model') # plot tru model
plt.xlabel('Population (1000 ppl)')
plt.ylabel('House price (k yuan)')
plt.legend(fontsize=14)
plt.title('Linearized model')

我们会得到如下散点图:

图2.4.3 模拟数据结果

下一步,计算R2R^2的各项参数:

我们来拟合一条 y=ax2+bx+cy=ax^2+bx+c的曲线, 首先我们定义loss function:

from scipy.optimize import minimize
def lossfun(params):
    a,b,c = params
    predict_data = a*x**2+b*x + c
    return ((predict_data-y)**2).sum()

然后我们来优化这个函数,即找到aa, bb, cc让这个函数最小

res = minimize(fun=lossfun, x0=(0.5, -3, 4)) # 用minimize去优化求解, 这里x0函数搜索的初始值
print(res.x)
fitParams = res.x # fitted parameter
print(a, b ,c)

可以得到结果:

[ 0.49314323 -1.45505584 12.9220037 ]
0.5 -2 5

画出拟合模型和真实模型

plt.plot(x, y, 'o', color='r', label='Data') # plot data
plt.plot(x, a * x**2 + b*x + c, '-', color='k', label='True model') # plot tru model
plt.plot(x, fitParams[0]*x**2 + fitParams[1]*x + fitParams[2], '-', color='b', label='Fitted model') # plot tru model
plt.xlabel('Population (1000 ppl)')
plt.ylabel('House price (k yuan)')
plt.legend(fontsize=14)
plt.title('Linearized model')

可以得到结果:

图2.4.4 模拟数据结果

最后,R2R^2的定义,先计算predict_data,再计算R2R^2

predict_data = fitParams[0]*x**2+fitParams[1]*x + fitParams[2] # mi

R2 = 1-((y-predict_data)**2).sum()/((y-y.mean())**2).sum()
print('R2 is', R2)

得到结果

R2 is 0.8719394366677774

模型拟合的目标是接近真实模型(true model),但由于数据中含有噪音,我们永远无法知道真实模型到底是怎样的。在模型拟合的过程中,如何避免无意义的噪音,是一件十分困难的事情,这实际上也是所有计算模型面临的一个核心问题。许多方法试图解决这一问题,其中交叉验证就是一种典型方法。

交叉验证

如图2.4.2所示,一个过拟合的模型在其拟合的数据上会有较好的拟合结果,但在泛化至新数据上可能表现不佳。交叉验证(cross-validation)则可以体现模型的泛化能力,减少过拟合。常用的方法有留一交叉验证(leave-one-out cross-validation)、k折交叉验证(k-fold cross-validation)等。

其具体操作步骤如下:

  1. 在数据集中挑出一些数据,作为测试集;

  2. 剩余数据作为训练集,拟合模型;

  3. 最后采用测试集的数据评价用训练集拟合出来的模型准确性。

注:测试集和训练集的数据应该是两份独立的数据,不能重合。

代码示例(交叉验证)

import numpy as np
from scipy.optimize import minimize

idx = np.mod(np.arange(x.size),5) # 把所有的数据分成5份
np.random.shuffle(idx) # 随机打乱顺序

# 现在来做Cross-validation

r2 = np.zeros(5)
for i in range(5):
     y_train = y[idx!=i].copy()
     y_test = y[idx==i].copy()
     x_train = x[idx!=i].copy()
     x_test = x[idx==i].copy()

    # define loss function
    lossfun=lambda params: ((params[0]*x_train**2+params[1]*x_train+params[2]-y_train)**2).sum()

    # optimize loss function
    res = minimize(fun=lossfun, x0=(0.5, -3, 4)) # 用minimize去优化求解
    fitParams = res.x

    # calculate R2
    y_pred = fitParams[0]*x_test**2+fitParams[1]*x_test+fitParams[2]
    r2[i] = 1-((y_test-y_pred)**2).sum()/((y_test-y_test.mean())**2).sum()

print('Cross validation R2 is ', r2)
print('\n The average R2 is', r2.mean())

以上为交叉验证的基本操作流程,但不同形式的验证方法还会有差异,比如图2.4.5显示的就是留-交叉验证(leave-one-out cross-validation)的过程——数据集中共6个数据点(以白色方块表示),每次验证指保留一个数据点作为测试集(以灰色方块表示,只有1个),其余没选中的则为训练集(以红色方块表示,共5个)。根据红色方块训练模型,再用灰色方块验证模型,得到模型准确性指标。在完成6次的交叉验证后,一共得到6个模型准确性的值,将这一组数据求和或者求平均,即可综合评价模型准确性。

图2.4.5 留-交叉验证的过程

得到结果

Cross validation R2 is  [0.8750066  0.88558795 0.88000305 0.85245239 0.85173928]

 The average R2 is 0.8689578544743828

一般在小样本数据中,常使用留一交叉验证方法。当数据集较大时,K折交叉验证更为通用。K折交叉验证方法将数据随机分为k组,每组分别做一次测试集,剩下的k-1组做训练集。如此循环后,最终会得到k个模型,并有k个模型准确性评价结果(例如,R2R^2),可以将这k个结果求和或求平均,综合评价模型准确性。

总的来说,评价一个模型好不好,并非看它在训练集上的表现,而是看它在测试集上的表现。交叉验证是一种思想,并没有严格规定的方法,其根本目的是为了估计模型的准确性,确保模型的泛化能力。

选择合适的模型

一个复杂的模型,拥有较强大的拟合能力,对于不同数据更加灵活,能够解释更多数据点。其中,能够决定模型复杂度(model complexity)的因素之一就是函数的形式,比如二阶非线性模型比一阶线性模型更复杂,但这不代表二阶非线性模型的拟合情况一定比线性模型好。如图

以图2.4.6为例,左侧图像绘制了真模型与一个7阶拟合模型的相似情况,红色的空心点代表样本数据点,因为有噪音的存在,所以真模型与数据点不完全重合。蓝色曲线表示的是根据红色空心点进行拟合的模型,可以看到其与真模型是不完全相似的。使用7阶函数虽然可以最大限度地拟合红色空心点,但由于拟合了不必要的噪音,导致其过拟合。

右半部分中红色折线代表在拟合模型函数阶数不同(即模型复杂度不同)的情况下,拟合样本数据的均方误差;黑色折线则代表拟合模型在拟合总体数据时的均方误差。

图2.4.6 过拟合

对于总体而言,在阶数小于3时,模型的拟合准确性随复杂度增加而提高;但当阶数大于3时,模型复杂度越高,对总体的拟合准确性反而越低(均方误差越高),也就是出现了过拟合(over-fitting)的情况,表明模型将噪音也拟合了进去,不能很好地反映总体情况。阶数小于3时出现了欠拟合(under-fitting)的情况,模型的拟合程度不够,或模型不能很好地捕捉到数据特征,此时模型的效度较低。从图中我们可以判断出,对于该样本数据,拟合最优点(optimal)是当模型为3阶函数时。

过拟合和欠拟合的核心逻辑就是简单模型和复杂模型的平衡问题。根据奥卡姆剃刀原则,在评估解释数据时,应尽量选择简单的模型。而更复杂的模型在拟合能力上似乎总是更强大。尤其在心理学测量中,数据受到噪音的干扰会更明显,一方面我们希望一个足够灵活的模型来描述、解释数据,并做到准确的预测;另一方面,我们需要一个简单的模型,避免有害噪音的影响。再这样的情况下,我们该如何权衡简单模型和复杂模型呢?

图2.4.7中红点为数据点,黑色曲线为真模型,浅蓝色曲线为拟合模型。可以看出,High noise的情况下,为了避免过拟合,应选择简单模型。而当处于Low noise情况下时,为了更好地解释数据特征(例如,样本方差),应选择复杂模型。

图2.4.7 不同噪音程度下,模型复杂度对拟合准确性的影响

最后更新于