type
status
date
slug
summary
tags
category
icon
password
设计模式是一系列针对软件开发中常见的问题场景的药方,使用得当则可以拿来快速解决当前问题。本文是O’Reilly的《机器学习设计模式》一书前半部分的整理笔记,涉及到数据表示、问题表示和模型训练的模式。这本书前半段偏向于建模,后半段则偏向于MLOps。
本文假设读者有一定的机器学习模型训练经验,因为很多模式都是在实践中司空见惯的行规了,所以对其中提到的专业概念不会做任何解释。因为目的是把书读薄,所以做了一些概念简化,省略了书中详细的代码实现,并加入了一些自己的想法,给尚未读过这本书的人探探路。
数据表示设计模式
模式1:哈希特征
使用独热编码时,如果要编码的内容是机场ID,飞机场又有上百个非常多,会导致矩阵稀疏,维度爆炸导致存储和部署开销,新增机场会导致难扩展模型。
解决方案是扩展一列存储指纹哈希码,把几百个机场哈希到少量桶中,经验上推荐让每个桶获得5个条目,或者作为一个要调优的超参数看待。
带来的权衡问题是这是个有损操作,我们无法给哈希做法在业务上的可解释性(牺牲信息换算力),会导致模型准确率降低,也有可能导致哈希结果发生严重偏斜(比如某个特定机场客流量明显高)。
模式2:嵌入
依旧是独热编码,为了解决维度爆炸问题,可以在所有独热编码特征的后面加一层特征嵌入层,使用降维的方法生成列数较少的“学习编码”,用学习编码进行模型训练。
这样做的坏处是损失了无损的原始信息,但好处是带来了隐藏的特征关联信息。事实上NLP的词向量嵌入做的就是这件事。
至于降低到多少维,经验数据建议在原始维度4方根到平方根的1.6倍之间,作为超参数训练。
模式3:特征交叉
对于线性模型,可以将两个特征组合成一个新的特征做训练,这种事情在树模型和神经网络中天然能做,但是线性模型只能人工拼凑特征。
这样做的好处是引入了特征之间的关联性,比如周几和几点组合起来可以更好地预测交通状况,缺点是维度爆炸和稀疏矩阵,可以使用嵌入的方法,或者L1正则化削减维度。
模式4:多模态输入
多模态输入通常指将不同类型的数据(表格、文本、图像)经过各自适合的编码器转化为稠密向量后,在模型内部进行融合。表格数据常见做法是归一化或嵌入后输入全连接层;文本通常通过token嵌入,再经过RNN/LSTM/Transformer编码;图像则常用CNN或ViT,将池化或cls token表示作为输出。融合时既可以直接拼接向量,也可以采用注意力等交互机制。多模态的挑战之一是,这些高维稠密向量表示可解释性较差,尽管可借助注意力可视化、特征归因方法进行部分解释。
问题表示设计模式
模式5:重构
可以把回归问题的数值区间分桶,转化为多个区间上的多分类问题,预测落在每个区间的概率,而不是输出单一数值,获取解释性更好的预测。比如处理偏态分布和双峰分布等情况(预测降水量),以及避免钟形分布下误差宽度的问题(预测婴儿体重)。反过来说,也可以将分类问题转化为回归问题,比如预测病毒将要在哪个城市爆发,不如预测在哪个经纬度爆发。
需要权衡的是,如何给线性数据分桶是个需要结合实际考量的问题。此外,如果概率密度函数足够集中且是单峰分布,回归问题往往比多分类问题更准确。最终评价模型需要使用均方根误差还是准确率,二者没有可比性,还是要结合场景来决定。另一个问题是,从业界经验上说,对于分类任务,对于每个分类标签至少需要10倍于特征数量的数据,对于回归任务,需要特征数量50倍的数据,能否恰当重构问题需要结合数据量加以考量。
与其试图在回归或分类任务上挣扎,不如两个都做,无非是共享参数的同时换掉神经网络的最后一层而已。
模式6:多标签
首先,sigmoid会把每一种标签(结果)的概率都输出出来,而softmax则归一化让所有结果之和为1,这样看,后者更适合于互斥判断的多选1。但是有一种情况,就是有多个标签都是正确的,比如图片识别问题里图中有猫也有狗,比如问题的候选答案可能是tensorflow也可能是pytorch。在这种情况下,就需要给同一条记录打多个概率标记,这时应该选择sigmoid,并使用二元交叉熵损失,输出每一个标记的概率。
需要注意的是,要权衡如何定义判断的阈值,如何在N个标签里选择前M个,以及如何处理标签概念嵌套(如猫也是动物)和近义词标签的问题。此外训练数据集也需要在每个标签上足够均衡。
对于不能用sigmoid输出多个结果的场景(比如SVM),可以对每两个标签训练一个二分类器,在集成时投票,但是这大大增加了复杂度,不是主流解法。
模式7:集成
ML模型总是有误差,误差分为由数据集本身导致的不可约误差,和我们可以解决的由算法和模型选择导致的偏差和方差。偏差指没有充分学习导致的欠拟合,方差指泛化能力差导致的过拟合,二者很难兼得(尤其是在中小规模数据上),那么如何权衡这两者就成为了一个问题。把多个具有不同偏差的模型的输出归纳起来的方法叫做集成,手法分为bagging、boosting、stacking。
bagging是一种减少方差过高过拟合的方法,会把原始数据(甚至针对特征)下采样多份训练多个只能看见局部数据的模型,最终集成每个学习器的结果进行输出,一个典型的算法是集成了多个短决策树的随机森林。它有效在每个子模型犯的错误都是随机且具备一定独立性的,当被集成在一起时,这种随机错误被抵消。(一个和bagging效果接近的手法是dropout)
boosting是一种减少偏差过高欠拟合的方法,它最终构建的是一个比原始模型更大的模型,其思想是残差学习,每次迭代都构建一个专注于学习上一次迭代中错误样本(更难被专注到的部分)的后续模型,并通过加权平均整合这些学习器的结果,对错误样本的反复学习降低了偏差,典型算法是xgboost。
stacking旨在训练多个模型,然乎把每个模型的输出作为一个特征,再训练一个模型进行预测,当然,也可以使用简单的加权平均。
集成学习的问题在于它增加了模型的训练时间,降低了模型的可解释性,在实际使用时需要结合实际情况以及需要削弱的是偏差还是方差来选择具体手法。
模式8:级联
这是一个拆解问题的方式,训练多个模型,将一个模型的输出作为后续不同模型的输入,最终整合多个模型的预测结果。比如,在电商环境,用户退货的模式和经销商退货的模式不同,且两种用户的数据量相差悬殊,所以要先预测是哪种退货的情况,然后分别训练一个模型进行预测退货。再比如在CV领域,可以用一个分类器筛掉大部分不需要处理的情况,再用下一个分类器做精密判断。
为了实现这种解决方案,需要使用管道等基础设施将多个模型训练和下一步需要的数据集生成串起来,这将会拖慢整体性能,但是换来的是对少量样本情况的重视,风险在,如果前面的模型预测不准,则会带偏后面的模型。
级联未必是一个最佳实践,它暴露了原始数据集中缺少某些维度,导致没能把所有数据在同一个模型中训练,当然,如果某些关键维度上的样本极度不均衡,确实可以考虑级联。
模式9:中立类
在一些任务中,类别标签本身存在模糊或多解。例如,药物适用性可能对大多数人都没有唯一答案(A或B都可);在商品评价中,人类也会给出“中立”分数。此时强行做二分类会让模型在灰色区域反复误判。
一个常见方法是将二分类改为三分类,引入“中立/不确定”类别。当模型对样本信号不足时,可以拒绝给出确定预测。这不会让模型在所有样本上的accuracy必然提高,但能提升高置信预测的可靠性,并降低错误率。在评估时,应结合覆盖率(多少样本被confident地分类)和准确率(confident样本的正确率)共同考量,而不仅仅依赖单一准确率。
模式10:再平衡
典型场景是异常数据占比极少(比如0.1%)的情况下,模型准确率很高,但召回率不够,无法正常预测异常值。
对于这种问题,在训练层面,准确度和AUC都不太关注模型在具体类别上的表现,更好的方法是用F值衡量模型性能,或把召回率作为优化目标(SageMaker很容易实现),或设置类别权值让模型更重视异常数据。对于数据集,如果异常值本身有几千个,足够多,可以下采样,下采样的做法是,根据这0.1%的体量,选出3倍于其数据量的多数类数据,训练一个小模型,用这种方式训练多个模型做集成。相反,合成少样过采样技术(SMOTE)是指分析少数类别的特征空间,用k邻近方法找出周边样本,再在周边样本周围生成相似的数据强化原始数据集。
另一种解决方案是任务重构,把分类转化为回归,把回归的结果按照数字范围分桶后,就能从每个桶里挑出和异常桶中数量相等的数据,构成分别平衡的数据集进行训练。问题还可以转化为聚类问题,找出异常数值属于哪个聚类。但这两种做法各有风险,未必是最佳的。
模型训练设计模式
模式11:有用的过拟合
当机器学习模型几乎完全拟合原始数据时,就类似于对原始数据进行了数值分析意义上的”插值”,当原始数据足量且质量足够好的时候,使用过拟合的机器学习模型来逼近插值算法的结果是个更高容量的选择,但模型的泛化行为取决于原始数据中的噪声。另一个例子是,可以通过一个较小的模型逼近巨大的模型的结果,这个过程叫做知识蒸馏。
模式12:检查点
checkpoint的好处有,一是防止硬件故障(如EC2 Spot实例关机)时训练了一半的模型信息和优化器信息不会丢失,二是能随时按需获取当前模型镜像选择何时早停,三是能用最终的检查点接入新的数据集做训练进行迁移学习或模型微调。
模式13:迁移学习
通过删去神经网络的最后的输出层(或更多被认为是瓶颈的层),并冻结前面的层的权值(也可以按需逐渐解冻前面的层),再在其后面接上新的神经网络,用其他领域的有限量数据(图片等)以比较低的学习率继续训练的过程,叫做迁移学习,这种方式可以有效地利用既有模型的知识,减少训练时间、降低对数据量的依赖。
模式14:分布式策略
面对海量数据时,可以把数据或模型分散到多块显卡上进行训练,又可以按照同步或异步分类训练方式。
按照数据分布式的同步训练,是把模型部署在所有机器上,每次把不同的数据切片喂给每个机器,每台机器将梯度返回给中央控制器,控制器再将梯度做平均处理后传播给每台机器,如此迭代。按照数据分布似乎的异步训练,是把模型部署在中央控制器上,它每次从每一台GPU获取梯度更新信息后直接就地更新,而不去通知所有机器集体更新,这将带来更大的吞吐量。由于权值更新不同步,一台机器可能会基于旧的权值进行训练,但是在极多的轮次面前,这种误差通常在权衡下可以容忍,异步分布式适合极大规模或需要容错的场景。按照数据进行分布式训练,可以通过提升机器数量的方式来提升速度,很符合云计算的精神。
模型并行则是指当神经网络过于庞大时,要将它拆分到多个设备,将神经网络的各部分计算拆分到多个核上,每个设备都在相同的小批量数据上运行,但是仅执模型中各自有关的组件。当每个神经元计算量很大时,比如全连接层很多的宽模型上,模型并行的效率会更好,但像卷积层那样每个权值计算量都很大时,数据并行则更有效。
模式14:超参数优化
这一章主要是在对比手动索索、网格搜索、随机搜索和贝叶斯优化,并提出了可以用遗传算法进行搜索。没有提到的是每一家云厂都有自己贴牌的高效优化器,主要也是基于贝叶斯优化+早停+分布式。