Mxnet (6):过拟合和欠拟合

强颜欢笑 提交于 2020-10-01 16:05:33

1.过拟合

究其根本我们训练模型,目的是预测也好,分类也好,希望的是无论输入的数据是否训练过,都要维持在高的准确率,就是说普适性。打个比方,我们学习数学的过程,这个过程其实就很像在训练我们大脑中的模型,我们做的练习题就时训练过程,参加考试就是测试模型,对于一个考试,A同学逻辑思维很好,通过平日的练习训练出了解题的逻辑,在考试中拿到了高分;B同学记忆力特别好,做过的每一道题都能记住,恰好这次考试考的题目他都做过,分数比A还高;又一次考试,A还是那些分,但是这次考的题目B都没做过,拿了低分。B同学的训练的模型只适用于他做过的题,而A同学的模型使用于所有题;那么B同学就属于过拟合了。

使训练数据拟合得比拟合基础分布更紧密的现象称为过拟合,而用来对抗过度拟合的技术称为正则化。在前面的部分中,您在尝试使用Fashion-MNIST数据集时可能已经观察到这种效果。如果您在实验期间更改了模型结构或超参数,您可能已经注意到,如果神经元,层数和训练时期足够,即使测试数据的准确性下降,模型最终仍可以在训练集上达到理想的准确性。

同样我们平时思考,处理问题的时候也要知其然知其所以然,不要死记硬背、人云亦云,不然的话大脑也会过拟合,降低判断的准确性

2.训练误差和泛化误差

为了更清晰的理解上面的问题,我们需要区分训练误差(training error)和泛化误差(generalization error)。

  • 训练误差通常指数据集有问题导致的误差,可以理解为训练集准确率
  • 泛化误差指模型在任意一个测试数据样本上表现出的误差的期望,并常常通过测试数据集上的误差来近似,可以理解为测试集准确率

由训练数据集学到的模型参数会使模型在训练数据集上的表现优于或等于在测试数据集上的表现。由于无法从训练误差估计泛化误差,一味地降低训练误差并不意味着泛化误差一定会降低。

举个例子:我们写一个模型用来区分,苹果,香蕉,西瓜。其中1000个苹果样本,1000个香蕉样本,1个西瓜样本。这样的样本训练过程不会出现问题,但是如果测试集都是西瓜,那没就会有问题,因为西瓜并没有训练的很好,模型对西瓜样本的泛化能力就很差,当然这里的泛化能力差是因为训练数据集的问题导致的,并非过拟合导致的

3.导致过拟合因素

在众多模型中,我们将重点关注一些倾向于影响模型类的可概括性的因素:

  • 可调参数的数量。当可调参数的数量(有时称为自由度)很大时,模型往往更容易过拟合。
  • 参数所取的值。当权重可以取较宽的值范围时,模型可能更容易过拟合。
  • 培训实例数。即使模型很简单,对只包含一个或两个示例的数据集进行过拟合也很容易。但是,要用数百万个示例对数据集进行过度拟合,则需要一个非常灵活的模型。

4.选型

在机器学习中,我们通常在评估几个候选模型后选择最终模型。此过程称为模型选择。有时,要进行比较的模型本质上本质上是不同的(例如,决策树与线性模型)。在其他时候,我们将比较使用不同超参数设置训练的同一类模型的成员,就是炼丹最费时的调参。

4.1 验证数据集

  • 为了合理验证数据集,不要讲测试集用于训练模型
  • 有时需要通过设定训练、测试和验证数据集,有时可以省略测试数据集的使用,这样可以保证验证的准确
  • 当数据集稀缺的时候,将数据集分为训练集合验证集会浪费,可以通过K折交叉验证的方法进行验证。数据集分为k个不重叠的子集,然后执行模型训练和验证K次,每次迭代选取一个子集为测试集,其他的子集为训练集,最后将验证集准确率进行平均。

5. 欠拟合

除了过拟合之外,还有另一种在训练过程中出现的问题,欠拟合:模型无法得到较低的训练误差,我们将这一现象称作欠拟合(underfitting)
虽然有很多因素可能导致这两种拟合问题,在这里我们重点讨论两个因素:模型复杂度和训练数据集大小。

5.1 模型复杂度

因为高阶多项式函数模型参数更多,模型函数的选择空间更大,所以高阶多项式函数比低阶多项式函数的复杂度更高。因此,高阶多项式函数比低阶多项式函数更容易在相同的训练数据集上得到更低的训练误差。

5.2 训练集大小

  • 训练数据集中样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生
  • 在计算资源允许的范围之内,我们通常希望训练数据集大一些,特别是在模型复杂度较高时,如层数较多的深度学习模型。

6.多项式函数拟合实验

通过对一个多项式进行拟合到数据来更加具象的了探索这些概念
多项式如下:
y = 5 + 1.2 x − 3.4 x 2 2 ! + 5.6 x 3 3 ! y = 5 + 1.2x - 3.4\frac{x^2}{2!} + 5.6 \frac{x^3}{3!} y=5+1.2x3.42!x2+5.63!x3

from d2l import mxnet as d2l
from mxnet import gluon, np, npx, autograd, init
from mxnet.gluon import nn
from plotly.offline import plot, init_notebook_mode
import plotly.graph_objs as go
import math
init_notebook_mode(connected=True)
npx.set_np()
ctx = npx.gpu() if (npx.num_gpus()) > 0 else mx.cpu()

6.1 模拟数据

通过多项式生成labels,并且添加服从正态分布,平均值为0,标准差为0.1的白噪声。

max_degree = 20  # 设置多项式最大项
n_train, n_test = 100, 100  # 模拟数据集大小
true_w = np.zeros(max_degree, ctx=ctx)  
true_w[0:4] = np.array([5, 1.2, -3.4, 5.6])

多项式写成下面这种形式更好理解:
y = 5 x 0 1 ! + 1.2 x 1 1 ! − 3.4 x 2 2 ! + 5.6 x 3 3 ! y = 5\frac{x^0}{1!} + 1.2\frac{x^1}{1!} - 3.4\frac{x^2}{2!} + 5.6 \frac{x^3}{3!} y=51!x0+1.21!x13.42!x2+5.63!x3

  • 解释这里一下np.power用法,第一个array中每一行对第二个array中每一个元素进行取n次方处理

在这里插入图片描述

npx.random.seed(2020)
features = np.random.normal(size=(n_train + n_test, 1))
np.random.shuffle(features)

poly_features = np.power(features, np.arange(max_degree)).as_in_context(ctx)

for i in range(max_degree):
    poly_features[:, i] /= math.gamma(i + 1)  # `gamma(n)` = (n-1)!; gamma(1)= 1 gamma(2)=1

labels = np.dot(poly_features, true_w)
labels += np.random.normal(scale=0.1, size=labels.shape, ctx=ctx)

6.2 完成模型

之前使用的模型略微修改,因为拟合不能获取准确率,损失函数也换位线性回归用的L2

def train_epoch(net, train_iter, loss, updater):
    
    l_sum, total = 0, 0
    
    if isinstance(updater, gluon.Trainer):
        updater = updater.step
        
    for X,y in train_iter:
        X = X.copyto(ctx)
        y = y.copyto(ctx)
        with autograd.record():
            pre_y = net(X)
            l = loss(pre_y, y)
        l.backward()
        updater(y.size)
        l_sum += float(l.sum())
        total += y.size
    return l_sum/total

def evaluate_loss(net, data_iter, loss):  

    total_num, loss_sum = 0, 0
    for _, (X, y) in enumerate(data_iter):
        X = X.as_in_context(ctx)
        y = y.as_in_context(ctx)
        l = loss(net(X), y)
        loss_sum += d2l.reduce_sum(l)
        total_num += y.size
    return loss_sum/total_num

def train(net, train_iter, test_iter, loss, epochs, updater):
    l_lst, test_loss_lst = [], []
    for epoch in range(epochs):
        l = train_epoch(net, train_iter, loss, updater)
        test_l = evaluate_loss(net, test_iter, loss)
        if epoch == 0 or (epoch + 1) % 20 == 0:
            l_lst.append(l)
            test_loss_lst.append(test_l)
    print('weight:', net[0].weight.data().asnumpy())
    return [l_lst, test_loss_lst]
 
def train_prepare(train_features, test_features, train_labels, test_labels, num_epochs=400):
    loss = gluon.loss.L2Loss()
    net = nn.Sequential()
    net.add(nn.Dense(1, use_bias=False))
    net.initialize(ctx = ctx)
    batch_size = min(10, train_labels.shape[0])
    train_iter = d2l.load_array((train_features, train_labels), batch_size)
    test_iter = d2l.load_array((test_features, test_labels), batch_size, is_train=False)
    trainer = gluon.Trainer(net.collect_params(), 'sgd', {
   
   'learning_rate': 0.01})
    return train(net, train_iter, test_iter, loss, num_epochs, trainer)

6.3 可视化

  • 为了更清楚的看清loss的变化,将y轴更改为通过log形式展示
def draw_graph(result, yaxis_range=[-3,2], names = ['train loss', 'test loss'], yaxis_type='log'):
    data = []
    colors = ['aquamarine', 'hotpink']
    for i, info in enumerate(result):
        trace = go.Scatter(
            x = list(range(1, len(info)+1)),
            y = info,
            mode = 'lines+markers',
            name = names[i],
            marker = {
   
   
                'color':colors[i],
            },
        )
        data.append(trace)
        layout = go.Layout(yaxis = {
   
   
            'type':yaxis_type,
            'range':yaxis_range,
            'title_text': 'loss'
        })
    fig = go.Figure(data = data, layout=layout)
    fig.update_xaxes(title_text = "epoch")
    fig.show()

6.4 标准拟合,三项多项式拟合

我们将使用三阶多项式函数,该函数与数据生成函数的阶数相同。结果表明,该模型的训练损失和测试损失均可以有效降低。学习到的模型参数也接近真实值 w=[5,1.2,−3.4,5.6]

result1 = train_prepare(poly_features[:n_train, :4], poly_features[n_train:, :4], labels[:n_train], labels[n_train:])

# weight: [[ 4.997967   1.2569637 -3.4112906  5.5044274]]

在这里插入图片描述

6.5 欠拟合,模拟线性拟合

在早期时期减少之后,变得难以进一步减少该模型的训练损失。在最后一个时期迭代完成之后,训练损失仍然很高。当用于拟合非线性模式(如此处的三阶多项式函数)时,线性模型容易欠拟合。

result2 = train_prepare(poly_features[:n_train, :2], poly_features[n_train:, :2], labels[:n_train], labels[n_train:])

# weight: [[3.433732  3.9014688]]

在这里插入图片描述

6.6 过拟合,高阶多项式函数拟合

尝试使用过高的多项式来训练模型。在这里,没有足够的数据来学习高次系数应该具有接近零的值。结果,我们的过于复杂的模型非常容易受到训练数据中噪声的影响。尽管可以有效地减少训练损失,但是测试损失仍然更高。它表明复杂模型适合数据。

result3 = train_prepare(poly_features[:n_train, :], poly_features[n_train:, :], labels[:n_train], labels[n_train:], 2500)

#weight: [[ 4.981164    1.2779253  -3.325646    5.0806932  -0.05322443  1.4853427
#  0.1137785   0.15010506 -0.00709852 -0.01766696 -0.02341763  0.02885529
#  0.01728348 -0.03277307  0.03010063  0.03926953  0.02006698 -0.04912236
#  0.0061793  -0.02380637]]

在这里插入图片描述

7.参考

https://d2l.ai/chapter_multilayer-perceptrons/underfit-overfit.html

https://plotly.com/python/axes/

https://community.plotly.com/t/set-linear-or-log-axes-from-button-or-drop-down-menu-python/34927

8.代码

github

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!