政安晨:【机器学习基础】(二)—— 评估机器学习模型改进

根据前面我的文章看来,咱们只能控制可以观察到的东西。因为您的目标是开发出能够成功泛化到新数据的模型,所以能够可靠地衡量模型泛化能力是至关重要的,咱们这篇文章将正式介绍评估机器学习模型的各种方法。

政安晨的个人主页政安晨

欢迎 👍点赞✍评论⭐收藏

收录专栏政安晨的机器学习笔记

希望政安晨的博客能够对您有所裨益,如有不足之处,欢迎在评论区提出指正!

本系列的前一篇文章为:

政安晨:【机器学习基础】(一)—— 泛化:机器学习的目标icon-default.png?t=N7T8https://blog.csdn.net/snowdenkeke/article/details/136275013


训练集、验证集和测试集

评估模型的重点是将可用数据划分为三部分:训练集、验证集和测试集

在训练数据上训练模型,在验证数据上评估模型。模型准备上线之前,在测试数据上最后测试一次,测试数据应与生产数据尽可能相似。做完这些工作之后,就可以在生产环境中部署该模型。

你可能会问,为什么不将数据划分为两部分,即训练集和测试集?在训练数据上进行训练,在测试数据上进行评估。这样做简单多了。

原因在于开发模型时总是需要调节模型配置,比如确定层数或每层大小[这些叫作模型的超参数(hyperparameter),以便与参数(权重)区分开]。这个调节过程需要使用模型在验证数据上的表现作为反馈信号。

该过程本质上是一种学习过程:在某个参数空间中寻找良好的模型配置。因此,基于模型在验证集上的表现来调节模型配置,很快会导致模型在验证集上过拟合,即使你并没有在验证集上直接训练模型。造成这一现象的核心原因是信息泄露(information leak)

每次基于模型在验证集上的表现来调节模型超参数,都会将验证数据的一些信息泄露到模型中。如果对每个参数只调节一次,那么泄露的信息很少,验证集仍然可以可靠地评估模型。但如果多次重复这一过程(运行一次实验,在验证集上评估,然后据此修改模型),那么会有越来越多的验证集信息泄露到模型中。

最后得到的模型在验证数据上的表现非常好——这是人为造成的,因为这正是你优化模型的目的。你关心的是模型在全新数据上的表现,而不是在验证数据上的表现,因此你需要一个完全不同、前所未见的数据集来评估模型,这就是测试集。

你的模型一定不能读取与测试集有关的任何信息,间接读取也不行,如果基于测试集表现对模型做了任何调节,那么对泛化能力的衡量将是不准确的。

将数据划分为训练集、验证集和测试集,看起来可能很简单,但如果可用数据很少,那么有几种高级方法可以派上用场。

我们将介绍三种经典的评估方法:简单的留出验证、K折交叉验证,以及带有打乱数据的重复K折交叉验证。我们还会介绍使用基于常识的基准,以判断模型训练是否有效。

简单的留出验证

留出一定比例的数据作为测试集。在剩余的数据上训练模型,然后在测试集上评估模型。如前所述,为防止信息泄露,你不能基于测试集来调节模型,所以还应该保留一个验证集

下图展示了留出验证的原理,并且往下代码给出了其简单实现:

留出验证(下段代码省略了标签,不可执行)

num_validation_samples = 10000#  通常需要打乱数据
np.random.shuffle(data)#  定义验证集
validation_data = data[:num_validation_samples]#  定义训练集
training_data = data[num_validation_samples:]# (本行及以下2行)在训练数据上训练模型,然后在验证数据上评估模型
model = get_model()
model.fit(training_data, ...)
validation_score = model.evaluate(validation_data, ...)# 现在可以对模型进行调节、重新训练、评估,然后再次调节
...# (本行及以下3行)调节好模型的超参数之后,通常的做法是在所有非测试数据上从头开始训练最终模型
model = get_model()model.fit(np.concatenate([training_data,validation_data]), ...)test_score = model.evaluate(test_data, ...)

这是最简单的评估方法,但它有一个缺点:如果可用的数据很少,那么可能验证集包含的样本就很少,无法在统计学上代表数据。

这个问题很容易发现:在划分数据前进行不同的随机打乱,如果最终得到的模型性能差别很大,那么就存在这个问题。接下来会介绍解决这一问题的两种方法:K折交叉验证和重复K折交叉验证。

K折交叉验证

K折交叉验证是指将数据划分为K个大小相等的分区。

对于每个分区i,在剩余的K-1个分区上训练模型,然后在分区i上评估模型。最终分数等于K个分数的平均值。对于不同的训练集−测试集划分,如果模型的性能变化很大,那么这种方法很有用。与留出验证一样,这种方法也需要独立的验证集来校准模型。

下图展示了K折交叉验证的原理,下面代码给出了其简单实现:

K折交叉验证(省略了标签)

k = 3
num_validation_samples = len(data) // k
np.random.shuffle(data)
validation_scores = []
for fold in range(k):# (本行及以下1行)选择验证数据分区validation_data = data[num_validation_samples * fold:num_validation_samples * (fold + 1)]# (本行及以下2行)使用剩余数据作为训练数据。注意,+运算符表示列表拼接,不是加法training_data = np.concatenate(data[:num_validation_samples * fold],data[num_validation_samples * (fold + 1):])# 创建一个全新的模型实例(未训练)model = get_model()model.fit(training_data, ...)validation_score = model.evaluate(validation_data, ...)validation_scores.append(validation_score)# 最终验证分数:K折交叉验证分数的平均值
validation_score = np.average(validation_scores)# (本行及以下2行)在所有非测试数据上训练最终模型
model = get_model()
model.fit(data, ...)
test_score = model.evaluate(test_data, ...)

带有打乱数据的重复K折交叉验证

如果可用的数据相对较少,而你又需要尽可能精确地评估模型,那么可以使用带有打乱数据的重复K折交叉验证。

您会发现这种方法在Kaggle竞赛中特别有用,具体做法是多次使用K折交叉验证,每次将数据划分为K个分区之前都将数据打乱。最终分数是每次K折交叉验证分数的平均值。注意,这种方法一共要训练和评估P * K个模型(P是重复次数),计算代价很大。

超越基于常识的基准

除了不同的评估方法,你还应该了解的是利用基于常识的基准。

训练深度学习模型就好比在平行世界里按下发射火箭的按钮,你听不到也看不到。你无法观察流形学习过程,它发生在数千维空间中,即使投影到三维空间中,你也无法解释它。唯一的反馈信号就是验证指标,就像隐形火箭的高度计。

特别重要的是,我们需要知道火箭是否离开了地面。发射地点的海拔高度是多少?模型似乎有15%的精度——这算是很好吗?在开始处理一个数据集之前,你总是应该选择一个简单的基准,并努力去超越它。如果跨过了这道门槛,你就知道你的方向对了——模型正在使用输入数据中的信息做出具有泛化能力的预测,你可以继续做下去。这个基准既可以是随机分类器的性能,也可以是你能想到的最简单的非机器学习方法的性能。

比如对于MNIST数字分类示例,一个简单的基准是验证精度大于0.1(随机分类器);对于IMDB示例,基准可以是验证精度大于0.5。对于路透社示例,由于类别不均衡,因此基准约为0.18~0.19。对于一个二分类问题,如果90%的样本属于类别A,10%的样本属于类别B,那么一个总是预测类别A的分类器就已经达到了0.9的验证精度,你需要做得比这更好。

在面对一个全新的问题时,你需要设定一个可以参考的基于常识的基准,这很重要。如果无法超越简单的解决方案,那么你的模型毫无价值——也许你用错了模型,也许你的问题根本不能用机器学习方法来解决。这时应该重新思考解决问题的思路。

模型评估的注意事项

选择模型评估方法时,需要注意以下几点:

数据代表性(data representativeness)。训练集和测试集应该都能够代表当前数据。假设你要对数字图像进行分类,而初始样本是按类别排序的,如果你将前80%作为训练集,剩余20%作为测试集,那么会导致训练集中只包含类别0~7,而测试集中只包含类别8和9。这个错误看起来很可笑,但非常常见。因此,将数据划分为训练集和测试集之前,通常应该随机打乱数据。

时间箭头(the arrow of time)。如果想根据过去预测未来(比如明日天气、股票走势等),那么在划分数据前不应该随机打乱数据,因为这么做会造成时间泄露(temporal leak):模型将在未来数据上得到有效训练。对于这种情况,应该始终确保测试集中所有数据的时间都晚于训练数据。

数据冗余(redundancy in your data)。如果某些数据点出现了两次(这对于现实世界的数据来说十分常见),那么打乱数据并划分成训练集和验证集,将导致训练集和验证集之间出现冗余。从效果上看,你将在部分训练数据上评估模型,这是极其糟糕的。一定要确保训练集和验证集之间没有交集。

有了评估模型性能的可靠方法,你就可以监控机器学习的核心矛盾——优化与泛化之间的矛盾,以及欠拟合与过拟合之间的矛盾。

改进模型拟合

为了实现完美的拟合,你必须首先实现过拟合。由于事先并不知道界线在哪里,因此你必须穿过界线才能找到它。在开始处理一个问题时,你的初始目标是构建一个具有一定泛化能力并且能够过拟合的模型。得到这样一个模型之后,你的重点将是通过降低过拟合来提高泛化能力。

在这一阶段,你会遇到以下3种常见问题:

训练不开始:训练损失不随着时间的推移而减小。

训练开始得很好,但模型没有真正泛化:模型无法超越基于常识的基准。

训练损失和验证损失都随着时间的推移而减小,模型可以超越基准,但似乎无法过拟合,这表示模型仍然处于欠拟合状态。

我们来看一下如何解决这些问题,从而抵达机器学习项目的第一个重要里程碑

得到一个具有一定泛化能力(可以超越简单的基准)并且能够过拟合的模型。

调节关键的梯度下降参数

有时训练不开始,或者过早停止。损失保持不变。这个问题总是可以解决的——请记住,对随机数据也可以拟合一个模型。即使你的问题毫无意义,也应该可以训练出一个模型,不过模型可能只是记住了训练数据。

出现这种情况时,问题总是出在梯度下降过程的配置优化器、模型权重初始值的分布、学习率或批量大小。所有这些参数都是相互依赖的,因此,保持其他参数不变,调节学习率和批量大小通常就足够了。

咱们来看一个具体的例子:

训练MNIST模型,但选取一个过大的学习率(取值为1),代码如下所示:

使用过大的学习率训练MNIST模型

(train_images, train_labels), _ = mnist.load_data()
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255model = keras.Sequential([layers.Dense(512, activation="relu"),layers.Dense(10, activation="softmax")
])
model.compile(optimizer=keras.optimizers.RMSprop(1.),loss="sparse_categorical_crossentropy",metrics=["accuracy"])
model.fit(train_images, train_labels,epochs=10,batch_size=128,validation_split=0.2)

这个模型的训练精度和验证精度很快就达到了30%~40%,但无法超出这个范围。下面我们试着把学习率降低到一个更合理的值1e-2,代码如下所示:

使用更合理的学习率训练同一个模型

model = keras.Sequential([layers.Dense(512, activation="relu"),layers.Dense(10, activation="softmax")
])
model.compile(optimizer=keras.optimizers.RMSprop(1e-2),loss="sparse_categorical_crossentropy",metrics=["accuracy"])
model.fit(train_images, train_labels,epochs=10,batch_size=128,validation_split=0.2)

注:要使用上述代码进行训练,您还需先准备如下工作,再执行上述代码:

import tensorflow
from tensorflow import keras
from tensorflow.keras import layersfrom tensorflow.keras.datasets import mnist(train_images, train_labels), _ = mnist.load_data()
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255

演绎如下:

现在模型可以正常训练了。

如果你自己的模型出现类似的问题,那么可以尝试以下做法:

降低或提高学习率。学习率过大,可能会导致权重更新大大超出正常拟合的范围,就像前面的例子一样。学习率过小,则可能导致训练过于缓慢,以至于几乎停止。增加批量大小。如果批量包含更多样本,那么梯度将包含更多信息且噪声更少(方差更小)。最终,你会找到一个能够开始训练的配置。

利用更好的架构预设

你有了一个能够拟合的模型,但由于某些原因,验证指标根本没有提高。这些指标一直与随机分类器相同,也就是说,模型虽然能够训练,但并没有泛化能力。这是怎么回事?

这也许是你在机器学习中可能遇到的最糟糕的情况。这表示你的方法从根本上就是错误的,而且可能很难判断问题出在哪里。下面给出一些提示:

首先,你使用的输入数据可能没有包含足够的信息来预测目标。也就是说,这个问题是无法解决的。前面我们试图拟合一个标签被打乱的MNIST模型,它就属于这种情况:模型可以训练得很好,但验证精度停留在10%,因为这样的数据集显然是不可能泛化的。

其次,你使用的模型类型可能不适合解决当前问题。对于一个时间序列预测问题的示例,密集连接架构的性能无法超越简单的基准,而更加合适的循环架构则能够很好地泛化。模型能够对问题做出正确的假设,这是实现泛化的关键,你应该利用正确的架构预设。

未来年还会学到针对各种数据模式的最佳架构,这些数据模式包括图像、文本、时间序列等。总体来说,你应该充分调研待解决任务的架构最佳实践,因为你可能不是第一个尝试解决这个任务的人。

提高模型容量

如果你成功得到了一个能够拟合的模型,验证指标正在下降,而且模型似乎具有一定的泛化能力,那么您就快要成功了。接下来,你需要让模型过拟合。

考虑下面这个小模型,如下代码所示:它是在MNIST上训练的一个简单的logistic回归模型。

model = keras.Sequential([layers.Dense(10, activation="softmax")])
model.compile(optimizer="rmsprop",loss="sparse_categorical_crossentropy",metrics=["accuracy"])
history_small_model = model.fit(train_images, train_labels,epochs=20,batch_size=128,validation_split=0.2)

演绎如下:

模型得到的损失曲线如下所示:

import matplotlib.pyplot as plt
val_loss = history_small_model.history["val_loss"]
epochs = range(1, 21)
plt.plot(epochs, val_loss, "b--",label="Validation loss")
plt.title("Effect of insufficient model capacity on validation loss")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend()

演绎如下:

上图为模型容量不足对损失曲线的影响。

验证指标似乎保持不变,或者改进得非常缓慢,而不是达到峰值后扭转方向。

验证损失达到了0.26,然后就保持不变。

你可以拟合模型,但无法实现过拟合,即使在训练数据上多次迭代之后也无法实现。

在你的职业生涯中,你可能会经常遇到类似的曲线。

请记住,任何情况下应该都可以实现过拟合。与训练损失不下降的问题一样,这个问题也总是可以解决的。如果无法实现过拟合,可能是因为模型的表示能力(representational power)存在问题:你需要一个容量(capacity)更大的模型,也就是一个能够存储更多信息的模型。若要提高模型的表示能力,你可以添加更多的层、使用更大的层(拥有更多参数的层),或者使用更适合当前问题的层类型(也就是更好的架构预设)。

我们尝试训练一个更大的模型,它有两个中间层,每层有96个单元:

model = keras.Sequential([layers.Dense(96, activation="relu"),layers.Dense(96, activation="relu"),layers.Dense(10, activation="softmax"),
])
model.compile(optimizer="rmsprop",loss="sparse_categorical_crossentropy",metrics=["accuracy"])
history_large_model = model.fit(train_images, train_labels,epochs=20,batch_size=128,validation_split=0.2)

演绎如下:

现在验证曲线看起来正是它应有的样子:模型很快拟合,并在8轮之后开始过拟合,如下图所示

代码如下

import matplotlib.pyplot as plt
val_loss = history_large_model.history["val_loss"]
epochs = range(1, 21)
plt.plot(epochs, val_loss, "b--",label="Validation loss")
plt.title("Effect of insufficient model capacity on validation loss")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend()

演绎和图如下

至此,咱们这篇文章的讲解目标达到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/702947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Hikvision SPON IP网络对讲广播系统命令执行漏洞

声明 本文仅用于技术交流,请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 1.漏洞描述 Hikvision Intercom Broadcasting System是中国海康威视&a…

低功耗设计——门控时钟

1. 前言 芯片功耗组成中,有高达40%甚至更多是由时钟树消耗掉的。这个结果的原因也很直观,因为这些时钟树在系统中具有最高的切换频率,而且有很多时钟buffer,而且为了最小化时钟延时,它们通常具有很高的驱动强度。此外&…

leetcode hot100 买卖股票最佳时机3

本题中,依旧可以采用动态规划来进行解决,之前的两个题我们都是用二维数组dp[i][2]来表示的,其中i表示第i天,2表示长度为2,其中0表示不持有,1表示持有。 本题中,说至多完成两笔交易,也…

力扣--动态规划1027.最长等差数列

思路分析: 使用动态规划的思想,定义二维数组dp,其中dp[i][j]表示以nums[i]为结尾,公差为(j-1000)的等差数列长度。为了适应负数的情况,将公差的范围设为[-1000, 1000],并且加上1000作为数组索引。 初始化r…

11:日志分析系统ELK|Elasticsearch|kibana

日志分析系统ELK|Elasticsearch|kibana 日志分析系统ELKELK概述Elasticsearch安装Elasticsearch部署Elasticsearch集群Elasticsearch插件 熟悉Elasticsearch的API调用_cat API创建 tedu 索引使用 PUT 方式增加数据查询数据修改数据删除数据 KibanaKibana…

锂电池SOC估计 | PyTorch实现基于Basisformer模型的锂电池SOC估计

目录 预测效果基本介绍程序设计参考资料 预测效果 基本介绍 PyTorch实现基于Basisformer模型的锂电池SOC估计 锂电池SOC估计,全新【Basisformer】时间序列预测 1.采用自适应监督自监督对比学习方法学习时序特征; 2.通过双向交叉注意力机制计算历史序列和…

BL、万科、中海地产、碧桂园、华润置地、佳兆业、金地商置、龙湖、绿城、融创、时代中国、旭辉、中国建筑校招笔试题

为了帮助应聘者更好地备战地产公司的招聘考试,我将介绍以下13套校招试题资料,涵盖了24 BL、24万科、24中海地产、碧桂园、华润置地、佳兆业、金地商置、龙湖、绿城、融创、时代中国、旭辉和中国建筑等知名房地产企业,为您提供全方位的备考资源…

提高移动应用的安全性:策略与实践

提高移动应用的安全性:策略与实践 随着移动应用的普及,安全性问题变得日益重要。用户数据保护、应用逻辑安全、以及防止恶意攻击都是开发者必须关注的重点。本文将探讨如何通过一系列策略和实践来提高移动应用的安全性。 1. 数据加密与保护 敏感数据加…

2024年环境安全科学、材料工程与制造国际学术会议(ESSMEM2024)

【EI检索】2024年环境安全科学、材料工程与制造国际学术会议(ESSMEM2024) 会议简介 我们很高兴邀请您参加将在三亚举行的2024年环境安全科学、材料工程和制造国际学术会议(ESSMEM 2024)。 ESSMEM2024将汇集世界各国和地区的研究人员&…

初识Lombok

前言 最近读一些公司的业务代码,发现近几年的java项目工程中都使用了lombok,lombok是一个可以自动生成get,set、toString等模板类方法的工具框架,程序再引入lombok后,添加一个注解便可以不写get\set\toString等方法。 Lombok示例…

计算机组成原理(15)----输入/输出系统

目录 一.I/O系统的基本概念 (1)I/O硬件 (2)I/O控制方式 1.程序查询方式 2.程序中断方式 3.DMA(Direct Memory Access)控制方式 4.通道控制方式 (3)I/O软件 1.I/O指令 2.通…

全志H713/H618方案:调焦电机(相励磁法步进电机)的驱动原理、适配方法

一、篇头 全志H713平台,作为FHD投影的低成本入门方案,其公板上也配齐了许多投影使用的模组,本文即介绍投影仪调焦所用的步进电机,此模组的驱动原理、配制方法、调试方法。因为条件限制,本文采用的是H618香橙派Z3平台&…

Linux-进程-007

1进程 1.1进程基本概念 程序:存放在外存中的一段数据组成的文件 进程:是一个程序动态执行的过程,包括进程的创建,进程的调度,进程的消亡1.2进程相关命令 1.2.1打开任务管理器 【top】:动态查看当前系统…

国企招聘考试------笔试资料-校园招聘

国企招聘考试一直备受广大求职者的关注,因为国企招聘通常具有稳定的岗位、优厚的福利待遇和广阔的发展空间,因此备战国企招聘考试成为许多求职者的重要任务。为了帮助求职者更好地准备国企招聘笔试,我将为你介绍这套校招试题资料。 国企招聘…

idea集成git(实用篇)

0.Git常用命令 Git常用命令-CSDN博客 1.下载git Git - Downloads 一路傻瓜式安装即可(NEXT) 2.软件测试 在Windows桌面空白处,点击鼠标右键,弹出右键菜单 Git软件安装后,会在右键菜单中增加两个菜单 Git GUI He…

Go编译到linux运行出现 cannot execute binary file

1.初学Go就在windows上写了个"Hello,World!",在windown上编译了一下,生成了可执行文件。运行无问题 go build text.go .\text.exe Hello,World!2.但是按照网上的教程进行生成linux的可执行文件时出现报错 set CGO_ENABLED0 set GOOSlinux set GOARCHam…

程序媛的mac修炼手册-- 2024如何彻底卸载Python

啊,前段时间因为想尝试chatgpt的API,需要先创建一个python虚拟环境来安装OpenAI Python library. 结果,不出意外的出意外了,安装好OpenAI Python library后,因为身份认证问题,根本就没有获取API key的权限…

Git命令操作

什么是Git? Git是⼀个免费的,开源的分布式版本控制软件系统 git区域 存储区域:Git软件⽤于存储资源得区域。⼀般指得就是.git⽂件夹 ⼯作区域:Git软件对外提供资源得区域,此区域可⼈⼯对资源进⾏处理。 暂存区&am…

Escalate_Linux(3)--通过读取密码文件shadow来破解root用户的口令实现提权

通过读取密码文件shadow来破解root用户的口令实现提权 通过读取密码文件/etc/shadow来破解root用户的口令 ls -l /etc/shadow 普通用户无查看shadow权限 echo "cat /etc/shadow" >/tmp/ls ​ chmod 755 /tmp/ls ​ export PATH/tmp:$PATH ​ /home/user5/script…

019 Spring Boot+Vue 电影院会员管理系统(源代码+数据库+文档)

部分代码地址: https://github.com/XinChennn/xc019-cinema 一、系统介绍 cinema项目是一套电影院会员管理系统,使用前后端分离架构开发包含管理员、会员管理、会员卡管理、电影票、消费记录、数据统计等模块 二、所用技术 后端技术栈: …