一、评估机器学习模型
评估模型的重点是将数据划分为三个集合:训练集、验证集、测试集。
在训练数据上训练模型,在验证数据上评估模型,一旦找到了最佳参数,就在测试数据上最后测试一次。
将数据划分为训练集、验证集、测试集看起来简单,但如果可用数据很少,就有以下三种经典的评估方法:简单的留出验证、K折验证、带有打乱数据的重复K折验证。
1、简单的留出验证
代码
num_validation_samples=1000
#通常需要打乱数据
np.random.shuffle(data)
#定义验证集
validation_data = data[:num_validation_samples]
data=data[num_validation_samples:]
#定义训练集
training_data=data[:]
#在训练数据上训练模型,并在验证数据上评估模型
model=get_model()
model.train(training_data)
validation_score=model.evaluate(validation_data)
#现在可以调节模型、重新训练、评估,然后再次调节
model=get_model()
model.train(np.concatenate([training_data,validation_data]))
test_score=model.evaluate(test_data)
2、K折验证
将数据划分为大小相同的K个分区。对于每个分区i,在剩余的K-1个分区上训练模型,然后再分区i上评估模型。
k=4
num_validation_samples=len(data)//k
#打乱数据
np.random.shuffle(data)
validation_scores=[]
for flod in range(k):
#选择验证数据分区
validation_data=data[num_validation_samples * fold:num_validation_samples *(fold+1)]
#使用剩余数据作为训练数据。+运算符是列表合并,不是求和
training_data=data[:num_validation_samples * fold + num_validation_samples * (fold + 1):]
#创建一个全新的模型(未训练)
model = get_model()
model.train(training_data)
validation_score=model.evaluate(validation_data)
validation_scores.append(validation_score)
#最终验证分数:K折验证分数的平均值
validation_score=np.average(validation_scores)
#在所有非测试数据上训练最终模型
model=get_model()
model.train(data)
test_score=model.evaluate(test_data)
3、带有打乱数据的重复K折验证
可用数据相对较少,又需要尽可能精确的评估模型时使用。
二、数据预处理
神经网络的数据预处理
数据预处理的目的是使原始数据更适用于神经网络处理,包括向量化、标准化、处理缺失值和特征提取。
1、向量化
神经网络的所有输入和目标都必须是浮点数张量(在特定情况下可以是整数张量)。无论处理什么数据(声音、图像、文本等),都必须首先将其转换为张量,这一步叫做数据向量化。
例如:将两个文本分类,开始时文本都表示为整数列表,然后可用one-hot编码将其转换为float 32格式的张量。
2、值标准化
在手写数字分类的例子中,开始时图像数据被编码为0~255范围内的整数,表示灰度值。将这些数据输入网络之前,需要将其转化为float 32格式并除以255,这样就能得到0~1范围内的浮点数。
一般来说,将取值相对较大的数据(比如多位整数,比网络权重的初始值大很多)或异质数据(比如数据的一个特征在0~1范围内,另一个特征在100~200范围内)输入到神经网络中是不安全的。可能会导致较大的梯度更新,进而导致网络无法收敛。因此输入数据应具有以下特征:
取值较小:大部分值都应该在0~1范围内
同质性:所有特征的取值也应该在大致相同的范围内
还有一种更严格的标准化方法:
将每个特征分别标准化,使其平均值为0
将每个特征分别标准化,使其标准差为1
#假设x是一个形状为(samples,features)的二维矩阵
x -= x.mean(axis=0)
x /= x.std(axis=0)
3、处理缺失值
一般来说,对于神经网络,将缺失值设置为0是安全的,只要0不是一个有意义的值。网络能够从数据中学到0意味着缺失数据,并且会忽略这个值。
特征工程
特征工程是指将数据输入模型之前,利用自己关于数据和机器学习算法(神经网络)的只是对数据进行硬编码的转换(不是模型学到的),以改善模型的效果。
三、过拟合与欠拟合
机器学习的根本问题是优化和泛化之间的对立。优化是指调节模型以在训练数据上得到最佳性能(即 机器学习中的学习),而泛化指训练好的模型在前所未见的数据上的性能好坏。
训练开始时,优化和泛化是相关的:训练数据上的损失越小,测试数据上的损失也越小。这时模型是欠拟合的,仍有改进空间,网络还没有对训练数据中所有相关模式建模。
在训练数据上迭代一定此时之后,泛化不再提高,验证指标先是不变,然后开始变差,即模型开始过拟合。
降低过拟合的方法交正则化,几种最常见的正则化方法:
1、减小网络大小
防止过拟合最简单的方法就是减小模型大小,即减少模型中可学习参数的个数(这由层数和每层的单元个数决定)。
在深度学习中,模型中可学习参数的个数通常被称为模型的容量。直观上看,参数更多的模型拥有更大的记忆容量,因此能够在训练样本和目标之间轻松的学会完美的字典式映射,这种映射没有任何泛化能力。
与此相反,如果网络的记忆资源有限,则无法轻松学会这种映射。因此,为了让损失最小化,网络必须学会对目标具有很强预测能力的压缩表示。同时,你是用的模型应该具有足够多的参数,以防欠拟合,即模型应避免记忆资源不足。在容量过大与容量不足之间找一个折中。
原始模型:
from keras import models
from keras import layers
model = models.Sequential()
model.add(layers.Dense(16,activation='relu',input_shape=(10000,)))
model.add(layers.Dense(16,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))
容量更小的模型
model = models.Sequential()
model.add(layers.Dense(4,activation='relu',input_shape=(10000,)))
model.add(layers.Dense(4,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))
容量更大的模型
model = models.Sequential()
model.add(layers.Dense(512,activation='relu',input_shape=(10000,)))
model.add(layers.Dense(512,activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))
2、添加权重正则化
一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,使得权重值的分布更加规则。这种方法叫权重正则化,实现方法是 向网络损失函数中添加与较大权重值相关的成本。成本有两种形式:
L1正则化:添加的成本与权重系数的绝对值成正比
L2正则化:添加的成本与权重系数的平方成正比
向模型添加L2权重正则化:
from keras import regularizers
model = models.Sequential()
model.add(layers.Dense(16,kernel_regularizer=regularizers.12(0.001),
activation='relu',input_shape=(10000,)))
model.add(layers.Dense(16,kernel_regularizer=regularizers.12(0.001),
activation='relu'))
model.add(layers.Dense(1,activation='sigmoid'))
Keras中不同的权重正则化项:
from keras import regularizers
#L1正则化
regularizers.l1(0.001)
#同时做L1、L2正则化
regularizers.l1_l2(l1=0.001,l2=0.001)
3、添加dropout正则化
dropout是神经网络最有效也最常用的正则化方法之一。对某一层使用dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为0)。dropout比率 是被设置为0的特征所占的比例,通常在0.2~0.5范围内。
假设有一个包含某层输出的Numpy矩阵layer_output,其形状为(batch_size,features)。
训练时,我们随机将矩阵中一部分值设为0:
#训练时,舍弃50%的输出单元
layer_output *= np.random.randint(0,high=2,size=layer_output.shape)
测试时,将输出按dropout比率缩小,乘0.5(因为前面舍弃了一半的单元)
#测试时
layer_output *=0.5
在Keras中,可以通过Dropout层向网络中引入dropout,dropout将被应用于前面一层的输出:
model.add(layers.Dropout(0.5))
向IMDB网络中添加dropout:
model=models.Sequential()
nodel.add(layers.Dense(16,activation='relu',input_shape=(10000,)))
nodel.add(layers.Dropout(0.5))
nodel.add(layers.Dense(16,activation='relu'))
nodel.add(layers.Dropout(0.5))
nodel.add(layers.Dense(1,activation='sigmoid'))
四、 机器学习的通用工作流程
1、定义问题,收集数据集
你的输入数据是什么?你要预测什么?只有拥有可用的训练数据,才能学习预测某件事情。
面对的是什么类型的问题?二分类、多分类、标量回归、向量回归问题,还是多分类、多标签问题?或者其他问题,比如聚类、生成或强化学习?确定问题类型有助于选择模型架构、损失函数。
2、选择衡量成功的指标
要去的成功就必须给出成功的定义:精度、准确率、召回率?衡量成功的指标可以引导选择损失函数,即模型要优化什么。直接与目标保持一致。
3、确定评估方法
一旦确定目标,就必须确定如何衡量当前的进展,有三种常见的评估方法:
留出验证集:数据量很大时可以采用这种方法
K折交叉验证:若留出的验证的样本量太少,无法保证可靠性,采用这种方法
重复的K折验证:若可用的数据很少,同时模型评估又需要非常准确,使用该方法
4、准备数据
准备好输入数据和目标数据的张量。
将数据格式化为张量;
这些张量的取值通常应该缩放为较小的值,比如在[-1,1]或[0,1];
若不同的特征具有不同的取值范围,应该做数据标准化;
可能需要做特征工程,尤其是对于小数据问题
5、开发比基准更好的模型
这一阶段的目标是获得统计功效(statistical power),即开发一个小型模型,它能够打败纯随机的基准(dumb baseline)。在 MNIST数字分类的例子中,任何精度大于0.1的模型都可以说具有统计功效;在IMDB 的例子中,任何精度大于0.5 的模型都可以说具有统计功效。
注意,不一定总是能获得统计功效。如果你尝试了多种合理架构之后仍然无法打败随机基准那么原因可能是问题的答案并不在输人数据中。
要记住你所做的两个假设:
假设输出是可以根据输人进行预测的。
假设可用的数据包含足够多的信息,足以学习输入和输出之间的关系。
这些假设很可能是错误的,这样的话你需要从头重新开始。
如果一切顺利,你还需要选择三个关键参数来构建第一个工作模型。
最后一层的激活。它对网络输出进行有效的限制。例如,IMDB 分类的例子在最后一层使用了 sigmoid,回归的例子在最后一层没有使用激活,等等。
损失函数。它应该匹配你要解决的问题的类型。例如,IMDB 的例子使用 binary.crossentropy、回归的例子使用 mse,等等。
优化配置。你要使用哪种优化器?学习率是多少?大多数情况下,使用rmsprop 及其默认的学习率是稳妥的。
问题类型 | 最后一层激活 | 损失函数 |
二分类问题 | sigmoid | binary_crossrntropy |
多分类、单标签问题 | softmax | categorical_crossrntropy |
多分类、多标签问题 | sigmoid | binary_crossrntropy |
回归到任意值 | 无 | mse |
回归到0~1范围内的值 | sigmoid | mse或binary_crossrntropy |
6、扩大模型规模:开发过拟合的模型
一旦得到了具有统计功效的模型,问题就变成了: 模型是否足够强大?它是否具有足够多的层和参数来对问题进行建模?例如,只有单个隐藏层且只有两个单元的网络,在 MNIST 问题上具有统计功效,但并不足以很好地解决问题。请记住,机器学习中无处不在的对立是优化和泛化的对立,理想的模型是刚好在欠拟合和过拟合的界线上,在容量不足和容量过大的界线上为了找到这条界线,你必须穿过它。
要搞清楚你需要多大的模型,就必须开发一个过拟合的模型,这很简单。
(1) 添加更多的层。
(2) 让每一层变得更大
(3) 训练更多的轮次。
要始终监控训练损失和验证损失,以及你所关心的指标的训练值和验证值。如果你发现模型在验证数据上的性能开始下降,那么就出现了过拟合。
下一阶段将开始正则化和调节模型,以便尽可能地接近理想模型,既不过拟合也不欠拟合。
7、模型正则化与调节超参数
这一步是最费时间的:你将不断地调节模型、训练、在验证数据上评估(这里不是测试数据)再次调节模型,然后重复这一过程,直到模型达到最佳性能。你应该尝试以下几项:
添加 dropout。
尝试不同的架构:增加或减少层数
添加 L1 和/或 L2 正则化。
尝试不同的超参数(比如每层的单元个数或优化器的学习率 ),以找到最佳配置
(可选)反复做特征工程:添加新特征或删除没有信息量的特征。
注意:每次使用验证过程的反馈来调节模型,都会将有关验证过程的信息泄露到模型中如果只重复几次,那么无关紧要;但如果系统性地迭代许多次,最终会导致模型对验证过程过拟合(即使模型并没有直接在验证数据上训练)。这会降低验证过程的可靠性。
一旦开发出满意的模型配置,就可以在所有可用数据(训练数据 +验证数据 ) 上训练最终的生产模型,然后在测试集上最后评估一次。如果测试集上的性能比验证集上差很多,就意味着你的验证流程不可靠,或者在调节模型参数时在验证数据上出现了过拟合。在这种情况下,可能需要换用更加可靠的评估方法,比如重复的K折验证。