> For the complete documentation index, see [llms.txt](https://ruyuanzhang.gitbook.io/compmodcogpsy/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ruyuanzhang.gitbook.io/compmodcogpsy/di-er-zhang-ji-suan-mo-xing-ji-chu/2.4-mo-xing-zhun-que-du.md).

# 2.4 模型准确度

在我们选择了一个模型，并且尝试去优化估计这个模型参数的时候，我们如何来确定估计出来的模型是否准确呢？

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

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

## R方的概念与计算

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

<figure><img src="/files/PgoMfD4orBFwFysgxG0x" alt=""><figcaption><p>图2.4.1 数据方差和模型误差之间的关系</p></figcaption></figure>

$$
R^2 = 100%×(1-\dfrac{unexplained\quad variance}{total\quad variance})  \qquad（2.4.1）
$$

我们将$$R^2$$的数学表达式写出来：

$$
R^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}}) \qquad (2.4.2）
$$

约去n-1后，

$$
R^2=100%×(1-\dfrac{\sum\_{i=1}^n(d\_i-m\_i)^2}{\sum\_{i=1}^n(d\_i-\bar{d})^2}) \qquad (2.4.3）
$$

可以看出，$$R^2$$不受到数据点数量的影响。从定义上看，$$R^2$$的值越高，模型曲线越接近数据点，但这不代表$$R^2$$越高越好。这就涉及到一个关键问题，模型准确性和模型复杂度的互相制约。

## 模型准确性、模型复杂度和过拟合

我们当然可以使用十分复杂的模型（例如，更多增加模型的参数）来拟合数据，让数据点无限接近模型曲线，此时我们似乎得到了一个看似近乎“完美”的模型，它的模型准确性非常高。但是，这样看似“完美”的模型实际上还拟合了与数据点无关的噪音，这样会导致过拟合（over-fitting）。

在图2.4.2中，在左侧的图表中红点是总体数据（Population，从前述的人口-房价关系来看，可以看做是中国所有城市的数据点），蓝点为样本数据（Sample，即我们从中国所有城市采样了一些城市），黑色曲线为真模型（True model，即中国所有城市满足的真实曲线），浅蓝色曲线是通过样本数据拟合出来的模型（Fitted model，即从样本中估计拟合出来的曲线）。右侧的表格列出模型在数据上的$$R^2$$， 可以看出，根据样本数据拟合的模型（浅蓝色）对于样本的拟合准确性非常高，达到85%，但当把模型推广至总体时，准确性却只有69%，远低于真模型的80%，这就出现了过拟合的问题。

<figure><img src="/files/BhbcVKbhhtObnkEAdDyp" alt=""><figcaption><p>图2.4.2 </p></figcaption></figure>

接下来，尝试通过代码来计算$$R^2$$：

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

<pre class="language-python"><code class="lang-python"><strong># Here We create data
</strong>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) ##加上高斯噪音
</code></pre>

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

```python
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')
```

我们会得到如下散点图：

<figure><img src="/files/mHLmkSZ86DoubMT28g8z" alt=""><figcaption><p>图2.4.3 模拟数据结果</p></figcaption></figure>

下一步，计算$$R^2$$的各项参数：

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

```python
def lossfun(params):
    a,b,c = params
    predict_data = a*x**2+b*x + c
    return ((predict_data-y)**2).sum() ## 返回最小二乘误差的值
```

然后我们来优化这个函数，即找到$$a$$, $$b$$, $$c$$让这个函数最小

```python
from scipy.optimize import minimize
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]。

```
[ 0.49314323 -1.45505584 12.9220037 ]
0.5 -2 5
```

{% hint style="info" %}
注意这里真实模型的参数和估计的参数产生了误差，这是由于所采用的样本区域不能很好的区分这两组参数所导致。下面画出拟合模型和真实模型和会发现在数据点的范围内，两条线几乎重合，说明同样的两条曲线，都能很好的解释这部分数据。
{% endhint %}

画出拟合模型和真实模型

```python
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')
```

<figure><img src="/files/N2JF4vpYQ5G7avLPAmaI" alt=""><figcaption><p>图2.4.4 模拟数据结果</p></figcaption></figure>

最后，$$R^2$$的定义，先计算predict\_data，再计算$$R^2$$

```python
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所示，一个过拟合的模型在其拟合的sample数据上会有较好的拟合结果，但在泛化至整个population的数据上可能表现不佳。交叉验证（cross-validation）则可以体现模型的泛化能力，减少过拟合。常用的方法有留一交叉验证（leave-one-out cross-validation）、k折交叉验证（k-fold cross-validation）等。

其具体操作步骤如下：

1. 在数据集中挑出部分数据(一般是少部分)，作为测试集；
2. 剩余数据作为训练集，拟合模型；
3. 最后采用测试集的数据评价用训练集拟合出来的模型准确性。

交叉验证的关键是，测试集和训练集的数据应该是两份独立的数据，不能重合。

下面我们来展示一下，如果做一个5折交叉验证的例子。

```python
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) ## 把每一折的r2的值都存下来
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())
```

得到结果

```
Cross validation R2 is  [0.8750066  0.88558795 0.88000305 0.85245239 0.85173928]

 The average R2 is 0.8689578544743828
```

以上为交叉验证的基本操作流程，但不同形式的验证方法还会有差异。一个数据集可以平均分为6份（以白色方块表示），每次验证指保留一个数据点作为测试集（以灰色方块表示，只有1个），其余没选中的则为训练集（以红色方块表示，共5个）。根据红色方块训练模型，再用灰色方块验证模型，得到模型准确性指标。在完成6次的交叉验证后，一共得到6个模型准确性的值，将这一组数据求和或者求平均，即可综合评价模型准确性。

<figure><img src="/files/SvkMCykixgwAcstKa3RK" alt=""><figcaption><p>图2.4.5 留-交叉验证的过程</p></figcaption></figure>

一般在小样本数据中，常使用留一交叉验证方法。当数据集较大时，K折交叉验证更为通用(如上面的例子就是6折交叉验证)。交叉验证其实体现了一个重要的思想，即评价一个模型好不好，并非看它在训练集上的表现，而是看它在测试集上的表现。

## 选择合适的模型

前面的例子可以看出，一个复杂的模型(通常认为参数较多)，拥有较强大的拟合能力，对于不同数据更加灵活，能够解释更多数据点。其中，能够决定模型复杂度（model complexity）的因素之一就是函数的形式，比如二阶非线性模型比一阶线性模型更复杂，但这不代表二阶非线性模型的拟合情况一定比线性模型好。

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

右半部分中红色折线代表在拟合模型函数阶数不同（即模型复杂度不同）的情况下，拟合样本数据的均方误差；黑色折线则代表拟合模型在总体数据(population)上测试的均方误差。

<figure><img src="/files/6I8znuote92uKEvuJVbl" alt=""><figcaption><p>图2.4.6 过拟合</p></figcaption></figure>

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

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

图2.4.7中红点为数据点，黑色曲线为真模型，浅蓝色曲线为拟合模型。可以看出，当数据采样噪音较高(数据点和真模型黑色线距离较大)的情况下，为了避免过拟合，应选择简单模型。而当数据采样噪音较低情况下，为了更好地解释数据特征（例如，样本方差），选择复杂模型往往更好。

<figure><img src="/files/kVzwC4ecp51cgrKYD6dk" alt=""><figcaption><p>图2.4.7 不同噪音程度下，模型复杂度对拟合准确性的影响</p></figcaption></figure>
