1. 模型评估
在机器学习领域,模型评估至关重要,只有选择和问题相匹配的评估方法,才能更快更好的完成训练。
将模型评估之前,需要有几个定义牢记心中。
- TP — 将正样本分类为正的数;
- FN — 将正样本分类为负的数;
- FP — 将负样本分类为正的数;
- TN — 将负样本分类为负的数。
Accuracy
准确率,分类正确的样本占总样本的比例。
当不同类别的样本比例非常不均衡时,占比大的类别往往成为影响准确率的主要因素。即准确率虽然简单直观,但会收到样本不均衡问题的干扰,导致不能很好的反应模型性能。
可以使用平均准确率(每个类别下样本准确了的算数平均)。
Recall
召回率,实际为正且预测为正的样本量与实际为正的样本量的比值。
Precision
精确率,实际为正且预测为正的样本量与预测为正的样本量的比值。
F1-score
前边介绍了Recall和Precision,他们是既矛盾又统一的两个指标。为了提高Precision,模型要尽可能把更有把握的样本预测为正,这就回导致大量实际为正的样本预测为负,致使Recall变低。F1-score是个很好的指标能融合Recall和Precision两个指标。
P-R曲线
P-R曲线横轴是Recall,纵轴是Precision。P-R曲线上的每个点,代表在在某一阈值下,将大于该阈值的样本分类为正,小于该阈值的样本分类为负时,Recall和Precision的值。P-R曲线是将阈值从大到小排列生成的。
ROC曲线
ROC曲线,受试者工作特征曲线。最初用于军事和医学领域。横轴是假阳性率(FPR),纵轴是真阳性率(TPR)。
ROC曲线的画法:
二分类任务中,模型输出一般为预测样本为正例的概率。按照预测概率从高到低排序,并封别将其作为阈值,大于该阈值的为正例。每个阈值对应一个FPR和TPR,连接所有点就成了ROC曲线。
AUC
AUC为ROC曲线下面积。一般取值在0.5~1之间,越大越好。
P-R VS ROC
至此我们分别介绍了P-R曲线和ROC曲线。在非常偏态的数据集上,P-R曲线能更全面衡量模型表现。当正负样本发生变化时,ROC曲线形状基本不变,而P-R曲线会发生剧烈变化。
2. 基础模型
GBDT(梯度提升树)
梯度提升树,是机器学习中非常优秀的模型。非常好的体现了“从错误中学习”的理念,基于决策树训练的残差进行学习,其中残差用损失函数的负梯度来拟合。需要注意的是,在每一轮迭代中,首先计算当前模型在所有样本上的负梯度,并以此为新的目标训练一个新的弱分类器并计算该弱分类器的权重,最终实现模型的更新。
如上图所示,表示一个基函数,即CART树。参数表示树的划分变量、划分位置等信息,通过优化算法得出。为当前第m棵树的权重,通过优化算法得出。
优点
- 预测阶段很快,树之间可以并行计算
- 在分布稠密的数据集上,泛化能力和表达能力都很好
- 使用弱分类器,具有更好的鲁棒性
缺点
- 在高维稀疏数据集上,表现不如SVM或者神经网络
- 训练过程比较慢,需要串行
XGBoost/GBDT及联系和区别
XGBoost是陈天奇等人开源的机器学习框架。是GBDT的一种高效实现方式,并在工程上做了一些改进。原始的GBDT由经验损失函数的负梯度构建新的决策树,在决策树构建完成后进行剪枝。而XGBoost在构建决策树阶段就加入了正则项
其中树结构的正则化项:
T为叶子结点的个数,为叶子结点预测值。
对损失函数在处进行二阶泰勒展开:
其中,,表示所有叶子结点j的样本的索引的集合。
如果树的结构已知,那么可以通过对损失函数求导得出叶子结点的预测值:
但是我们很难从所有结构中找到最好的树结构,是个NP-hard问题。因此只能用贪心算法找到一个次优的树。XGBoost有自己的方式选取最优分裂,采用的是CART算法。
将预测值倒入到损失函数中,可以求得损失函数的极小值:
然后计算分裂前后损失的差值:
XGBoost通过遍历所有可能的取值,找到最大化Gain的值进行分裂。
现总结如下:
- GBDT是算法,XGBoost是其中一种工程实现
- 在CART作为基分类器时,XGBoost引入了正则化项来控制模型的复杂度,这有利于防止模型过拟合。
- GBDT在训练时,只用了损失函数的一阶导数,XGBoost对损失函数进行泰勒展开,同时用了一阶导数和二阶导数。
- 传统GBDT采用CART树作为基分类起,XGBoost支持多种类型的分类器。
- 传统GBDT在每轮迭代是使用了全部数据,XGBoost采用了类似随机森林的策略,支持对数据进行采样。
- 传统GBDT没有对缺失值进行处理,XGBoost采用稀疏感知算法,能自动学习出缺失值的处理策略。稀疏感知算法,分别枚举特征缺省的样本归为左右分支后的增益,选择增益最大的枚举项即为最优缺省方向。
LightGBM和XGBoost的区别
从名字可以看出,LightGBM是轻量级的GBM,相比XGBoost有训练速度快,占用内存底等特点。
单边梯度抽样算法(GOSS)
GBDT的梯度大小可以反应样本的权重,梯度越小说明模型拟合越好。单边梯度抽样算法利用这一点,减少了梯度小的样本,计算过程中重点关注梯度大的样本,极大减少了计算量。同时为了不改变样本分布,在计算增益时,对梯度小的样本引入一个常数进行平衡。
直方图算法
直方图法,是将连续特征离散化成k特离散特征,从而不再需要遍历所有取值,只需要遍历k个离散特征寻找最佳分裂点。虽然该方法可能会对模型精度有一定影响,在一定程度上起到了正则化的效果。
互斥特征捆绑算法
特征之间有可能是互相排斥的,将互相排斥的特征捆绑,可以降低特征数量。
基于最大深度的 Leaf-wise 的垂直生长算法
- Level-wise: 基于层进行生长,直到达到停止条件;
- Leaf-wise: 每次分裂增益最大的叶子节点,直到达到停止条件。
XGBoost 采用 Level-wise 的增长策略,方便并行计算每一层的分裂节点,提高了训练速度,但同时也因为节点增益过小增加了很多不必要的分裂,降低了计算量; LightGBM 采用 Leaf-wise 的增长策略减少了计算量,配合最大深度的限制防止过拟合,由于每次都需要计算增益最大的节点,所以无法并行分裂。
类别特征最优分割
LightGBM 原生支持类别特征,采用 many-vs-many 的切分方式将类别特征分为两个子集,实现类别特征的最优切分。 假设有某维特征有 k 个类别,则有 2^{(k-1)} - 1 中可能,时间复杂度为 O(2^k) ,LightGBM 基于 Fisher 大佬的 《On Grouping For Maximum Homogeneity》实现了 O(klog_2k) 的时间复杂度。
特征并行和数据并行
缓存优化
LightGBM怎么调参
以下是参数结束,复制自官网。
- boosting_type (str, optional (default='gbdt')) – ‘gbdt’, traditional Gradient Boosting Decision Tree. ‘dart’, Dropouts meet Multiple Additive Regression Trees. ‘goss’, Gradient-based One-Side Sampling. ‘rf’, Random Forest.
- num_leaves (int, optional (default=31)) – Maximum tree leaves for base learners.
- max_depth (int, optional (default=-1)) – Maximum tree depth for base learners, <=0 means no limit.
- learning_rate (float, optional (default=0.1)) – Boosting learning rate. You can use
callbacks
parameter offit
method to shrink/adapt learning rate in training usingreset_parameter
callback. Note, that this will ignore thelearning_rate
argument in training.- n_estimators (int, optional (default=100)) – Number of boosted trees to fit.
- subsample_for_bin (int, optional (default=200000)) – Number of samples for constructing bins.
- objective (str, callable or None, optional (default=None)) – Specify the learning task and the corresponding learning objective or a custom objective function to be used (see note below). Default: ‘regression’ for LGBMRegressor, ‘binary’ or ‘multiclass’ for LGBMClassifier, ‘lambdarank’ for LGBMRanker.
- class_weight (dict, 'balanced' or None, optional (default=None)) – Weights associated with classes in the form
{class_label: weight}
. Use this parameter only for multi-class classification task; for binary classification task you may useis_unbalance
orscale_pos_weight
parameters. Note, that the usage of all these parameters will result in poor estimates of the individual class probabilities. You may want to consider performing probability calibration (https://scikit-learn.org/stable/modules/calibration.html) of your model. The ‘balanced’ mode uses the values of y to automatically adjust weights inversely proportional to class frequencies in the input data asn_samples / (n_classes * np.bincount(y))
. If None, all classes are supposed to have weight one. Note, that these weights will be multiplied withsample_weight
(passed through thefit
method) ifsample_weight
is specified.- min_split_gain (float, optional (default=0.)) – Minimum loss reduction required to make a further partition on a leaf node of the tree.
- min_child_weight (float, optional (default=1e-3)) – Minimum sum of instance weight (hessian) needed in a child (leaf).
- min_child_samples (int, optional (default=20)) – Minimum number of data needed in a child (leaf).
- subsample (float, optional (default=1.)) – Subsample ratio of the training instance.
- subsample_freq (int, optional (default=0)) – Frequency of subsample, <=0 means no enable.
- colsample_bytree (float, optional (default=1.)) – Subsample ratio of columns when constructing each tree.
- reg_alpha (float, optional (default=0.)) – L1 regularization term on weights.
- reg_lambda (float, optional (default=0.)) – L2 regularization term on weights.
- random_state (int, RandomState object or None, optional (default=None)) – Random number seed. If int, this number is used to seed the C++ code. If RandomState object (numpy), a random integer is picked based on its state to seed the C++ code. If None, default seeds in C++ code are used.
- n_jobs (int, optional (default=-1)) – Number of parallel threads to use for training (can be changed at prediction time).
- importance_type (str, optional (default='split')) – The type of feature importance to be filled into
feature_importances_
. If ‘split’, result contains numbers of times the feature is used in a model. If ‘gain’, result contains total gains of splits which use the feature.
针对更好的准确率
- Use large
max_bin
(may be slower) - Use small
learning_rate
with largenum_iterations
- Use large
num_leaves
(may cause over-fitting) - Use bigger training data
- Try
dart
- 使用较大的
max_bin
(学习速度可能变慢) - 使用较小的
learning_rate
和较大的num_iterations
- 使用较大的
num_leaves
(可能导致过拟合) - 使用更大的训练数据
- 尝试
dart
处理过拟合
- 使用early_stopping
- Use small
max_bin
- Use small
num_leaves
- Use
min_data_in_leaf
andmin_sum_hessian_in_leaf
- Use bagging by set
bagging_fraction
andbagging_freq
- Use feature sub-sampling by set
feature_fraction
- Use bigger training data
- Try
lambda_l1
,lambda_l2
andmin_gain_to_split
for regularization - Try
max_depth
to avoid growing deep tree - 使用较小的
max_bin
- 使用较小的
num_leaves
- 使用
min_data_in_leaf
和min_sum_hessian_in_leaf
- 通过设置
bagging_fraction
和bagging_freq
来使用 bagging - 通过设置
feature_fraction
来使用特征子抽样 - 使用更大的训练数据
- 使用
lambda_l1
,lambda_l2
和min_gain_to_split
来使用正则 - 尝试
max_depth
来避免生成过深的树
逻辑回归损失函数推导
逻辑回归
其中。也叫对数几率回归:
其中为几率。
已知逻辑回归模型:
逻辑回归作为二分类问题,损失函数也分为两部分
- 当真实样本y=1时,估计出来的概率p越小,损失函数值越大
- 当真实样本y=0时,估计出来的概率p越大,损失函数值越大
可以使用如下函数
上边两个式子可以合并:
对于整个集合的损失函数,可以取其平均值:
其中。
即:
SoftMax和CrossEntropy求梯度
我们考虑如下结构的softmax
输入分别为1、2、3,经过全连接后得到4、5、6,在对4、5、6做softmax,得到a4、a5、a6。
a4、a5、a6机会模型输出的概率分布,损失函数为 -Y log A。现在考虑对w41求导:
其中, ,比较麻烦的是。
当i = j时:
当i != j时:
3. 神经网络
神经网络有哪些初始化方法
神经网络初始化的选择很关键,不合理的初始化甚至会导致梯度消失、梯度爆炸等问题,对模型训练产生负面影响。
1. 全零初始化或等值初始化
并不推荐,由于每个神经元学到的东西完全相同,会导致对称性问题。
2. 正态初始化
0均值,标准差设置成一个小值。
这样做的好处是,权重有相同的偏差,有正有负,比较合理。
3. 均值初始化
均值初始化的区间为表示输入神经元的数量。
4. Xavier初始化
根据sigmoid函数的特点,可以推想出:
如果初始化值很小,随着层数的传递,方差就会趋近于0,从而导致失去非线性只有线性。
如果初始化值很大,随着层数的传递,方差会迅速变大,sigmoid的输入很大时,会导致梯度小时问题。Xavier初始化同时考虑了网络的大小(输入、输出神经元数量),有两种方法:
- 均值为0,方差为
- 均匀分布,
这种方法对于激活函数是sigmoid或tanh的神经网络比较好。
5. He 初始化
也有两种方式:
- 均值为0,方差为
- 均匀分布,
适用于ReLU等为激活函数的神经网络。
6. Pre-trained
即最近流行的迁移学习,使用与训练的权重初始化,起点更好,收敛速度更快。
BatchNorm
训练和测试
Batch-Normalization是一种让神经网络训练更快、更稳定的方法。计算每个mini-batch的均值方差,并将输入拉回到均值为0方差为1的表征正态分布,最后还要学习对其进行伸缩变换,最终得到Batch-Normalization层的输出。
需要注意的是,在测试阶段,由于不一定存在mini-batch,因此需要使用训练阶段的均值、方差。
affine表示是否进行仿射。
track_running_stats表示是否使用全局均值、方差。
记录一个滑动平均的均值、方差:
mean = momentum * mean + (1-momentum) * x_mean
var = momentum * var + (1-momentum) * x_var
研究表明,BatchNorm放到激活函数之后通常能带来更好的效果。
BatchNorm作用
- 随着网络的深入,每个隐藏层的参数变化导致后一层神经层的输入发生变化,不同batch的数据分布也可能发生细微变化。这些变化会迫使神经网络要拟合到不同分布的数据,增加了训练难度和过拟合风险。
- BN效果好是因为BN的存在会引入mini-batch内其他样本的信息,就会导致预测一个独立样本时,其他样本信息相当于正则项,使得loss曲面变得更加平滑,更容易找到最优解。
Dropout
在机器学习中,如果模型参数太多,而训练样本不足,很容易导致模型过拟合。Dropout可以在一定程度上缓解过拟合的风险,起到正则化的效果。
Dropout就是在前向传播的过程中,让神经元以一定的概率p停止工作,这样可以使他不过多依赖特定特征组合,使模型泛化能力更强。
Dropout流程
首先以一定概率删掉该层的神经元,得到新的神经网络。再把输入x输入到新的神经网络中,得到损失函数,反向传播,更新这个新的神经网络的参数。最后恢复原始的神经网络,在另一个batch的训练中重复前边两个步骤,直到训练完全结束。
在测试阶段,由于不再以概率p将神经元删除,需要对权重W乘以概率参数p。保证该层输出比训练阶段不出现太大变化。
由于我们训练的时候会随机的丢弃一些神经元,但是预测的时候就没办法随机丢弃了。如果丢弃一些神经元,这会带来结果不稳定的问题,也就是给定一个测试数据,有时候输出a有时候输出b,结果不稳定,这是实际系统不能接受的,用户可能认为模型预测不准。那么一种”补偿“的方案就是每个神经元的权重都乘以一个p,这样在“总体上”使得测试数据和训练数据是大致一样的。比如一个神经元的输出是x,那么在训练的时候它有p的概率参与训练,(1-p)的概率丢弃,那么它输出的期望是px+(1-p)0=px。因此测试的时候把这个神经元的权重乘以p可以得到同样的期望。
Dropout位置
Dropout一般放到全连接层之后,激活函数之前,防止过拟合。一般不会放到卷基层后,由于卷基层参数较少,一般使用BatchNorm即可。
Dropout防止过拟合的原因
- 取平均。Dropout掉不同的神经元,就像在训练不同的神经网络。不同的网络会产生不同的过拟合,Dropout相当于去平均,防止过拟合。
- 减少神经元之间复杂的共适关系。Dropout可使两个神经元不一定每次都在同一个Dropout网络中出现,可以防止某些特征仅仅在其他特定特征出现时才发挥作用。使神经网络不会对特定的特征片段过于敏感,防止某些特征丢失导致的性能下降。
源码中的Dropout
def dropout(x, level):
if level < 0. or level >= 1: #level是概率值,必须在0~1之间
raise ValueError('Dropout level must be in interval [0, 1[.')
retain_prob = 1. - level
# 我们通过binomial函数,生成与x一样的维数向量。binomial函数就像抛硬币一样,我们可以把每个神经元当做抛硬币一样
# 硬币 正面的概率为p,n表示每个神经元试验的次数
# 因为我们每个神经元只需要抛一次就可以了所以n=1,size参数是我们有多少个硬币。
random_tensor = np.random.binomial(n=1, p=retain_prob, size=x.shape) #即将生成一个0、1分布的向量,0表示这个神经元被屏蔽,不工作了,也就是dropout了
print(random_tensor)
x *= random_tensor
print(x)
x /= retain_prob
return x
计算CNN输出,参数量
W为输入的长度。
F为卷积核长度。
P为padding长度。
S为stride。
当padding = “VALID”时,
这里表示的是向下取整再加1。
当padding='SAME'时,
向上取整。
textRNN网络结构
textRNN比较简单,先是讲单词word embedding,然后输入到一个BiLSTM结构中,结下来有两种处理方式:1是讲每个LSTM的最后一个隐藏状态concat到一起;2是将每个时间步的两个LSTM隐藏状态concat到一起,再将所有时间步对其取平均值。最终连接个全连接+softmax。
textCNN网络结构
将文本当作单通道图片处理,即输入为(Batch_size, sequence_length, embedding_dim, 1),然后选取卷积: (2, embedding_dim), (3, embedding_dim), (4, embedding_dim),每个卷积选两个卷积核。再将卷机后的结果做1维的maxpooling,再concat到一起,输入的softmax中,得到最终分类。
self-attention中为什么除以根号dk
self-attention公示:
是词向量/隐藏层的维度。
- 一个原因是除一个数,方式输入到softmax的值过大,导致偏导数趋近0.
- 使QK的结果满足期望为0,方差为1的分布,类似于归一化。
Word2Vec
Skip Gram
Skip-gram的思想是利用目标词预测背景词。其输入是一个单词,输出是这个单词的背景词。
CBOW
全程是continuous bag of words。其本质是通过context word预测targetword,通过背景词预测目标词。
如上图所示,context word包含C个单词,分别是target word的临近的C个单词,将C个单词输出相加再除以C,得到target word的隐藏层,在对隐藏层进行输出。
Hierarchical Softmax
在NLP任务重,Vocabrary往往会很大,因此在寻找概率最大的输出时,需要对一个很大的向量进行Softmax,Hierarchical Softmax可以解决计算量过大的问题。
Huffman Tree
哈夫曼是一种带权路径长度最短的二叉树,也称为最优二叉树。
首先根据词频构建哈夫曼树,这样做的优点是高频词距离跟节点较近,低频次距离跟节点较远。
根据哈夫曼树,给出单词w的条件概率公式:
这里边有几个重要的概念需要解释,其中为从跟节点到单词w所在的叶子结点的节点个数,表示单词在该节点的编码(0, 1),注意这是从2开始的,因为根节点没有编码。
对于逻辑回归,我们可以用指数的方式将两项合并成一项:
将上边工时带入到条件概率公式得到:
Negative Sampling
对于Vocabrary过大的问题,还有一种解决方案就是Negative Sampling。一般情况下,在训练过程中,每个样本会影响网络的所有参数,Negative Sampling的思想,就是每个样本只更新部分参数,而非全部。假设输入样本为('A', 'B'),当'A'经过one-hot编码,隐藏层,输出为长度为vocab_size大小的向量,而只有其中'B'对应的单词是我们希望的输出,其他都是负样本。Negative Sampling的思想是随机选择一小部分负样本来更新权重。
那么如何选择Negative Samples呢,根据一定的概率选出,而这个概率是和单词的词频相关的。
ELMo
word2vec十分好用,但有一个缺陷就是,身为静态词向量,无法解决一词多义的问题。而ELMo是一种动态词向量,能根据语义生成词向量,因此可以很好的应对一词多义问题。
如上图所示,ELMo的原理是将文本输入到两个BiLSTM中,并将输入向量、中间层向量和输出向量分别加权求和:
其中为可变参数,可以使用固定值,也可以在训练过程中学习。再将得到的ELMo结果输入到下游任务重。