给定一组样本(通常由人工标注),它可以学会将输入数据映射到已知目标[也叫标注( annotation)]。
虽然监督学习主要包括分类和回归,但还有更多的奇特变体,主要包括如下几种:
无监督学习是指在没有目标的情况下寻找输入数据的有趣变换,其目的在于数据可视化、数据压缩、数据去噪或更好地理解数据中的相关性。
无监督学习是数据分析的必备技能,在解决监督学习问题之前,为了更好地了解数据集,它通常是一个必要步骤。
降维( dimensionality reduction)和聚类( clustering)都是众所周知的无监督学习方法。
自监督学习是没有人工标注的标签的监督学习,你可以将它看作没有人类参与的监督学习。
标签仍然存在(因为总要有什么东西来监督学习过程),但它们是从输入数据中生成的,通常是使用启发式算法生成的。
在强化学习中, 智能体( agent)接收有关其环境的信息,并学会选择使某种奖励最大化的行动。
机器学习的目的是得到可以泛化( generalize)的模型,即在前所未见的数据上表现很好的模型,而过拟合则是核心难点。
评估模型的重点是将数据划分为三个集合:训练集、验证集和测试集。在训练数据上训练模型,在验证数据上评估模型。一旦找到了最佳参数,就在测试数据上最后测试一次。
你可能会问,为什么不是两个集合:一个训练集和一个测试集?在训练集上训练模型,然后在测试集上评估模型。这样简单得多!
原因在于开发模型时总是需要调节模型配置,比如选择层数或每层大小[这叫作模型的超参数( hyperparameter)]。这个调节过程需要使用模型在验证数据上的性能作为反馈信号。这个调节过程本质上就是一种学习:在某个参数空间中寻找良好的模型配置。因此,如果基于模型在验证集上的性能来调节模型配置,会很快导致模型在验证集上过拟合,即使你并没有在验证集上直接训练模型也会如此。
造成这一现象的关键在于信息泄露( information leak)。
最后,你得到的模型在验证集上的性能非常好(人为造成的),因为这正是你优化的目的。你关心的是模型在全新数据上的性能,而不是在验证数据上的性能,因此你需要使用一个完全不同的、前所未见的数据集来评估模型,它就是测试集。你的模型一定不能读取与测试集有关的任何信息,既使间接读取也不行。
留出一定比例的数据作为测试集。在剩余的数据上训练模型,然后在测试集上评估模型。如前所述,为了防止信息泄露,你不能基于测试集来调节模型,所以还应该保留一个验证集。
num_validation_samples = 10000
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)
这是最简单的评估方法,但有一个缺点:如果可用的数据很少,那么可能验证集和测试集包含的样本就太少,从而无法在统计学上代表数据。这个问题很容易发现:如果在划分数据前进行不同的随机打乱,最终得到的模型性能差别很大,那么就存在这个问题。接下来会介绍 K 折验证与重复的 K 折验证,它们是解决这一问题的两种方法。
K 折验证(K-fold validation)将数据划分为大小相同的 K 个分区。 对于每个分区 i,在剩余的 K-1 个分区上训练模型,然后在分区 i 上评估模型。最终分数等于 K 个分数的平均值。对于不同的训练集 - 测试集划分,如果模型性能的变化很大,那么这种方法很有用。与留出验证一样,这种方法也需要独立的验证集进行模型校正。
k = 4
num_validation_samples = len(data) // k
np.random.shuffle(data)
validation_scores = []
for fold in range(k):
validation_data = data[num_validation_samples * fold:
num_validation_samples * (fold + 1)] #选择验证数据分区
training_data = data[:num_validation_samples * fold] +
data[num_validation_samples * (fold + 1):] #使用剩余数据作为训练数据。注意,+ 运算符是列表合并,不是求和
model = get_model() #创建一个全新的模型实例(未训练)
model.train(training_data)
validation_score = model.evaluate(validation_data)
validation_scores.append(validation_score)
validation_score = np.average(validation_scores) #最终验证分数: K 折验证分数的平均值
# 在所有非测试数据上训练最终模型
model = get_model()
model.train(data)
test_score = model.evaluate(test_data)
如果可用的数据相对较少,而你又需要尽可能精确地评估模型,那么可以选择带有打乱数据的重复 K 折验证( iterated K-fold validation with shuffling)。具体做法是多次使用 K 折验证,在每次将数据划分为 K 个分区之前都先将数据打乱。最终分数是每次 K 折验证分数的平均值。注意,这种方法一共要训练和评估 P× K 个模型(P 是重复次数),计算代价很大。
将数据输入神经网络之前,如何准备输入数据和目标?
数据预处理的目的是使原始数据更适于用神经网络处理,包括向量化、标准化、处理缺失值和特征提取。
向量化
神经网络的所有输入和目标都必须是浮点数张量(在特定情况下可以是整数张量)。无论处理什么数据(声音、图像还是文本),都必须首先将其转换为张量,这一步叫作数据向量化(data vectorization)。
值标准化
一般来说,将取值相对较大的数据(比如多位整数,比网络权重的初始值大很多)或异质数据( heterogeneous data,比如数据的一个特征在 0~1 范围内,另一个特征在 100~200 范围内)输入到神经网络中是不安全的。这么做可能导致较大的梯度更新,进而导致网络无法收敛。
为了让网络的学习变得更容易,输入数据应该具有以下特征:
- 取值较小:大部分值都应该在 0~1 范围内;
- 同质性( homogenous):所有特征的取值都应该在大致相同的范围内。
此外,下面这种更严格的标准化方法也很常见,而且很有用,虽然不一定总是必需的:
处理缺失值
一般来说,对于神经网络,将缺失值设置为 0 是安全的,只要 0 不是一个有意义的值。网络能够从数据中学到 0 意味着缺失数据,并且会忽略这个值。
注意,如果测试数据中可能有缺失值,而网络是在没有缺失值的数据上训练的,那么网络不可能学会忽略缺失值。在这种情况下,你应该人为生成一些有缺失项的训练样本:多次复制一些训练样本,然后删除测试数据中可能缺失的某些特征。
特征工程( feature engineering)是指将数据输入模型之前,利用你自己关于数据和机器学习算法(这里指神经网络)的知识对数据进行硬编码的变换(不是模型学到的),以改善模型的效果。多数情况下,一个机器学习模型无法从完全任意的数据中进行学习。呈现给模型的数据应该便于模型进行学习。
特征工程的本质:用更简单的方式表述问题,从而使问题变得更容易。它通常需要深入理解问题。
机器学习的根本问题是优化和泛化之间的对立。 优化( optimization)是指调节模型以在训练数据上得到最佳性能(即机器学习中的学习),而**泛化( generalization)是指训练好的模型在前所未见的数据上的性能好坏**。机器学习的目的当然是得到良好的泛化,但你无法控制泛化,只能基于训练数据调节模型。
训练开始时,优化和泛化是相关的:训练数据上的损失越小,测试数据上的损失也越小。这时的模型是欠拟合( underfit)的,即仍有改进的空间,网络还没有对训练数据中所有相关模式建模。但在训练数据上迭代一定次数之后,泛化不再提高,验证指标先是不变,然后开始变差,即模型开始过拟合。这时模型开始学习仅和训练数据有关的模式,但这种模式对新数据来说是错误的或无关紧要的。
为了防止模型从训练数据中学到错误或无关紧要的模式, 最优解决方法是获取更多的训练数据。模型的训练数据越多,泛化能力自然也越好。如果无法获取更多数据,次优解决方法是调节模型允许存储的信息量,或对模型允许存储的信息加以约束。如果一个网络只能记住几个模式,那么优化过程会迫使模型集中学习最重要的模式,这样更可能得到良好的泛化。
这种降低过拟合的方法叫作正则化( regularization)。
防止过拟合的最简单方法就是减小模型大小,即减少模型中可学习参数的个数(这由层数和每层的单元个数决定)。
在深度学习中,模型中可学习参数的个数通常被称为模型的容量( capacity)。直观上来看,参数更多的模型拥有更大的记忆容量( memorization capacity),因此能够在训练样本和目标之间轻松地学会完美的字典式映射,这种映射没有任何泛化能力。
始终牢记:深度学习模型通常都很擅长拟合训练数据,但真正的挑战在于泛化,而不是拟合。
与此相反,如果网络的记忆资源有限,则无法轻松学会这种映射。
因此,为了让损失最小化,网络必须学会对目标具有很强预测能力的压缩表示,这也正是我们感兴趣的数据表示。
使用的模型应该具有足够多的参数,以防欠拟合,即模型应避免记忆资源不足。
在容量过大与容量不足之间要找到一个折中。
你必须评估一系列不同的网络架构(当然是在验证集上评估,而不是在测试集上),以便为数据找到最佳的模型大小。
要找到合适的模型大小,一般的工作流程是开始时选择相对较少的层和参数,然后逐渐增加层的大小或增加新层,直到这种增加对验证损失的影响变得很小。
更小的验证损失对应更好的模型,更小的网络开始过拟合的时间要晚于参考网络,而且开始过拟合之后,它的性能变差的速度也更慢。
简单模型比复杂模型更不容易过拟合;
这里的简单模型(simple model)是指参数值分布的熵更小的模型,或参数更少的模型;
一种常见的降低过拟合的方法就是强制让模型权重只能取较小的值,从而限制模型的复杂度,这使得权重值的分布更加规则(regular)。
这种方法叫作权重正则化(weight regularization),其实现方法是向网络损失函数中添加与较大权重值相关的成本( cost)。这个成本有两种形式:
在 Keras 中,添加权重正则化的方法是向层传递权重正则化项实例( weight regularizer instance)作为关键字参数。
from keras import regularizers
model = models.Sequential()
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation='relu', input_shape=(10000,)))
model.add(layers.Dense(16, kernel_regularizer=regularizers.l2(0.001),
activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
dropout 是神经网络最有效也最常用的正则化方法之一;
对某一层使用 dropout,就是在训练过程中随机将该层的一些输出特征舍弃(设置为 0)。
dropout 比率( dropout rate)是被设为 0 的特征所占的比例,通常在 0.2~0.5 范围内。
在 Keras 中,你可以通过 Dropout 层向网络中引入 dropout, dropout 将被应用于前面一层的输出。
model4 = models.Sequential()
model4.add(layers.Dense(16, activation='relu', input_shape=(10000,)))
model4.add(layers.Dropout(0.5))
model4.add(layers.Dense(16, activation='relu'))
model4.add(layers.Dropout(0.5))
model4.add(layers.Dense(1, activation='sigmoid'))
总结一下,防止神经网络过拟合的常用方法包括:
你必须定义所面对的问题:
只有明确了输入、输出以及所使用的数据,你才能进入下一阶段。注意你在这一阶段所做的假设:
机器学习只能用来记忆训练数据中存在的模式。
要取得成功,就必须给出成功的定义:精度?准确率( precision)和召回率( recall)?客户保留率?衡量成功的指标将指引你选择损失函数,即模型要优化什么。
- 对于平衡分类问题(每个类别的可能性相同),精度和接收者操作特征曲线下面积( area under the receiver operating characteristic curve, ROC AUC)是常用的指标;
- 对于类别不平衡的问题,你可以使用准确率和召回率;
- 对于排序问题或多标签分类,你可以使用平均准确率均值( mean average precision)。
一旦明确了目标,你必须确定如何衡量当前的进展。前面介绍了三种常见的评估方法:
- 留出验证集。数据量很大时可以采用这种方法。
- K 折交叉验证。如果留出验证的样本量太少,无法保证可靠性,那么应该选择这种方法。
- 重复的 K 折验证。如果可用的数据很少,同时模型评估又需要非常准确,那么应该使用这种方法。
将数据格式化,使其可以输入到机器学习模型中:
- 应该将数据格式化为张量;
- 这些张量的取值通常应该缩放为较小的值,比如在 [-1, 1] 区间或 [0, 1] 区间;
- 如果不同的特征具有不同的取值范围(异质数据),那么应该做数据标准化;
- 你可能需要做特征工程,尤其是对于小数据问题。
这一阶段的目标是获得统计功效( statistical power),即开发一个小型模型,它能够打败纯随机的基准( dumb baseline)。
如果一切顺利,你还需要选择三个关键参数来构建第一个工作模型:
- 最后一层的激活。它对网络输出进行有效的限制;
- 损失函数。它应该匹配你要解决的问题的类型;
- 优化配置。你要使用哪种优化器?学习率是多少?大多数情况下,使用 rmsprop 及其默认的学习率是稳妥的。
问题类型 | 最后一层激活 | 损失函数 |
---|---|---|
二分类问题 | sigmoid | binary_crossentropy |
多分类、单标签问题 | softmax | categorical_crossentropy |
多分类、多标签问题 | sigmoid | binary_crossentropy |
回归到任意值 | 无 | mse |
回归到 0~1 范围内的值 | sigmoid | mse 或 binary_crossentropy |
一旦得到了具有统计功效的模型,问题就变成了:模型是否足够强大?它是否具有足够多的层和参数来对问题进行建模?
机器学习中无处不在的对立是优化和泛化的对立,理想的模型是刚好在欠拟合和过拟合的界线上,在容量不足和容量过大的界线上。为了找到这条界线,你必须穿过它。
要搞清楚你需要多大的模型,就必须开发一个过拟合的模型,这很简单:
- 添加更多的层;
- 让每一层变得更大;
- 训练更多的轮次。
要始终监控训练损失和验证损失,以及你所关心的指标的训练值和验证值。如果你发现模型在验证数据上的性能开始下降,那么就出现了过拟合。
下一阶段将开始正则化和调节模型,以便尽可能地接近理想模型,既不过拟合也不欠拟合。
这一步是最费时间的:你将不断地调节模型、训练、在验证数据上评估(这里不是测试数据)、再次调节模型,然后重复这一过程,直到模型达到最佳性能。你应该尝试以下几项:
- 添加 dropout;
- 尝试不同的架构:增加或减少层数;
- 添加 L1 和 / 或 L2 正则化;
- 尝试不同的超参数(比如每层的单元个数或优化器的学习率),以找到最佳配置;
- (可选)反复做特征工程:添加新特征或删除没有信息量的特征。
请注意:每次使用验证过程的反馈来调节模型,都会将有关验证过程的信息泄露到模型中。
一旦开发出令人满意的模型配置,你就可以在所有可用数据(训练数据 + 验证数据)上训练最终的生产模型,然后在测试集上最后评估一次。
如果测试集上的性能比验证集上差很多,那么这可能意味着你的验证流程不可靠,或者你在调节模型参数时在验证数据上出现了过拟合。
在这种情况下,你可能需要换用更加可靠的评估方法,比如重复的 K 折验证。