NYC's Blog - 机器学习 http://niyanchun.com/tag/machine-learning/ 集成学习介绍(4)——GBDT&amp;XGBoost http://niyanchun.com/gbdt-and-xgboost.html 2022-03-26T10:20:00+08:00 GBDT概述GBDT全称Gradient Boosting Decision Trees,即梯度提升回归树。GBDT可以拆分2部分:GB+DT。GB是一种通用思想或者算法,GBDT只是众多GBM(Gradient Boosting Machine)里面的一种。所以先来看GB。Boosting前面已经介绍过了,就是用多个弱学习器顺序迭代生成一个强学习器,GB里面的重点在于每次迭代的时候是拟合残差。所谓残差(residual)就是真实值和预测值的差值: $residual_i = y_i-f(x_i)$,它表示了数据模型中所不可能刻画的部分。所以GB的思想大致可以描述如下:初始:先有一个初始的弱学习器,然后用这个学习器去训练样本上面进行预测,得到每个样本的残差。第一轮:用训练样本作为属性,第一轮得到的残差作为target(而不是原始样本的target),训练得到第2个弱学习器。然后用这个弱学习器对样本进行预测得到预测值,并计算与真实值的残差,作为一下轮样本的target。第N轮:重复前面的步骤。每次变化的是使用上一轮计算的残差作为本轮样本的target。N次迭代后得到最终的强学习器。所以可以看到GB其实就是不断的迭代拟合残差。但至此并没有看到任何关于Gradient的影子。接着往下看:假设现在有样本集 $(x_1,y_1),(x_2,y_2),...(x_n,y_n)$ ,然后用一个模型$f(x)$去拟合这些数据。如果是回归模型的话,我们一般使用均方误差作为损失函数,即:$L(\theta) = \frac{1}{2}\sum_{i=0}^n(y_i-f(x_i))^2$然后最优化算法求解模型的参数$\theta$。常用的最优化求解方法之一就是梯度下降(gradient descent)。对损失函数求梯度得到:$\nabla L(\theta) = \sum_{i=0}^n{(f(x_i)-y_i)}$至此,可以看到我们前面定义的残差就是这里的负梯度:$residual_i = y_i-f(x_i) = -(f(xi)-y_i)$。也就是说前面对残差的拟合,其实就是对负梯度的拟合,而根据残差来更新集成后的模型实际就是根据负梯度来更新。这样来看,梯度提升方法就变成了广义上的梯度下降。这就是GB中Gradient的部分。需要注意的是:尽管这里残差和负梯度的值完全一样,但二者代表的含义却是不一样的:负梯度指向的是单个模型参数更新的方向,残差(即梯度提升)则表示了集成模型下一个模型的拟合目标。梯度的不断下降可以让模型的参数逐渐收敛到最优参数上,而残差的不断拟合则让集成之后的模型越来越解决真实数据的生成机制。换言之,梯度下降是参数空间的优化:针对单个模型的参数优化,找到最优的一组参数;梯度提升是函数空间的优化:用多个模型的组合来逼近问题的解,每个模型都是一个函数。有了从残差转换到梯度的思路以后,又可以再继续改进,不再局限于残差,而是可以从损失函数的负梯度角度去构造更丰富的提升方法,即构造更多的损失函数。因为基于残差的损失函数有一个明显的缺点就是对异常值比较敏感。看下面的例子:上面的例子中,5*是一个异常点。很明显,按照Boosting的思路,后续模型会对这个值关注过多,这不是一个好现象。所以一般回归类的损失函数会使用绝对损失(absolute loss)或者huber损失函数(huber loss)来代替平方损失函数:如果GB中的弱学习器使用决策树,就是GBDT了。GBDT中一般使用CART决策树。这便是GBDT算法,整体还是Boosting的思路,但具体到细节又和AdaBoost有明显的差别:AdaBoost中每个弱学习器的短板通过权重的加强得以凸显,而梯度提升中则将权重换成了梯度。在解决分类问题时,GBDT 中的负梯度可以表示成样本归属于每个类别的真实概率和上一轮预测概率的差值,这个差值会被回归树拟合,拟合值与上一轮预测概率求和就是这一轮的输出。算法下图来自Wikipedia:https://en.wikipedia.org/wiki/Gradient_boosting初始化$F_0(x)$,表示第0棵树的预测值。那怎么初始化呢?取决于所选择的损失函数:均方误差(mean square error,MSE)时:$F_0(x)=\bar y,\overline y$ 是样本真实值的平均值平均绝对误差(mean absolute error,MAE):$F_0(x)=median(y)$,即真实值的中位数Logit时(分类问题): $F_0(x)=\frac{1}{2}*log\frac{\sum{y_i}}{\sum(1-y_i)}, y_i\in{0, 1}$假如进行M轮迭代(即M个弱分类器),则对于第m轮($m=1,2,...,M$):计算上一轮模型的残差(即负梯度)$r_{im}$,即用真实值减去上一轮模型的预测值将计算出来的残差作为本轮的target,得到本轮的训练数据:$(x_1, r_{1m}),(x_2, r_{2m}),...,(x_n, r_{nm})$。然后训练得到本轮的弱分类器是$h_m(x)$。然后就是求解本轮弱分类器的权重$\gamma_m$了。迭代到本轮为止得到的强分类器为:$F_m(x)=F_{m-1}(x)+\gamma_mh_m(x)$,所以求解$\gamma_m$的方式就是:$\gamma_m = \mathop{\arg\min}\limits_{\theta}\sum_{i=1}^n{L(y_i,F_m(x_i))} = \mathop{\arg\min}\limits_{\theta}\sum_{i=1}^n{L(y_i,F_{m-1}(x_i)+\gamma h_m(x_i))}$得到$\gamma_m$之后,本轮迭代之后得到模型就是:$F_m(x)=F_{m-1}(x)+\gamma_mh_m(x)$。得到最终的模型:$F_M(x)$。再总结一下关键点:初始的时候先找一个常量模型$F_0$,计算初始的残差。之所以说是常量模型,是因为一般这个是直接计算的所有样本观测值(target)的“均值”(如果是分类问题的话,一般使用logit),然后样本观测值减去计算出来的“均值”就得到了初始的残差。然后就开始Boosting的迭代,每轮迭代的样本属性就是原始训练样本的属性,但样本观测值不是原始的观测值,而是计算出来的残差。第一轮使用初始算出的残差;第二轮使用第一轮模型预测的到的残差,...,第m轮使用第m-1轮模型预测得到的残差,以此类推。因为Boosting模型得到的最终强学习器形式为:$F_{strong\ learner}(x) = \sum_{i=1}^M{\gamma_i*h_i(x)} +const_{F_0}$其中的$const_{F_0}$就是初始计算的常量“均值”,所以每轮除了使用样本$(x_1, r_{1m}),(x_2, r_{2m}),...,(x_n, r_{nm})$ 训练得到本轮的弱分类器以外,还需要计算本轮弱分类器在最终强分类器中所占的权重$\gamma$,计算方式就是上面计算$\gamma_m$的方法。需要注意的是每轮训练时的target使用的是残差,而不是原始的target,所以该弱分类器模型后面预测出来的值也是残差,而不是原始样本的target。也即所以上面公式中的$\sum_{i=1}^M{\gamma_i*h_i(x)}$累加起来其实是M个弱分类器预测的“残差和”,需要加上最初始$F_0$算出来的“平均值”才是最终的预测值。另外还需要说明一个概念:学习率(Learning Rate),背景是这样的:测试表明每次沿正确的方向前进一小步可以获得更好的预测性能,所以设计了一个超参learning rate,用来控制每轮模型的影响。一般会设置的比较小(比如0.1),以此来让模型使用更多的弱分类器。英文版:It’s been shown through experimentation that taking small incremental steps towards the solution achieves a comparable bias with a lower overall vatiance (a lower variance leads to better accuracy on samples outside of the training data). Thus, to prevent overfitting, we introduce a hyperparameter called learning rate. When we make a prediction, each residual is multiplied by the learning rate. This forces us to use more decision trees, each taking a small step towards the final solution.上面的算法流程里面没有给出来,但实际迭代的时候还会引入一个学习率$\nu$,完整的迭代公式是这样的:$F_m(x)=F_{m-1}(x)+\nu*\gamma_mH_m(x)$一幅图表示就是(来自:How XGBoost Works):整个介绍起来比较抽象,可参考下面例子中的第一个:Gradient Boosting Decision Tree Algorithm Explained来感受一下整个流程。例子Gradient Boosting Decision Tree Algorithm Explained: 该例是一个使用GBDT解决回归的问题。见:gbdt-regression-demo.ipynb(参考自: Gradient Boosting Decision Tree Algorithm Explained)。GBDT算法原理以及实例理解Gradient Boosting In Classification: Not a Black Box Anymore!,是一个二分类例子。XGBoost概述XGBoost(eXtreme Gradient Boosting)是对GBDT的优化和工程化的实现。优化可分为算法优化和工程实现方面的优化:Algorithmic Enhancements:Regularization: It penalizes more complex models through both LASSO (L1) and Ridge (L2) regularization to prevent overfitting.Sparsity Awareness: XGBoost naturally admits sparse features for inputs by automatically ‘learning’ best missing value depending on training loss and handles different types of sparsity patterns in the data more efficiently.Weighted Quantile Sketch: XGBoost employs the distributed weighted Quantile Sketch algorithm to effectively find the optimal split points among weighted datasets.Cross-validation: The algorithm comes with built-in cross-validation method at each iteration, taking away the need to explicitly program this search and to specify the exact number of boosting iterations required in a single run.System Optimization:Parallelization: XGBoost approaches the process of sequential tree building using parallelized implementation. This is possible due to the interchangeable nature of loops used for building base learners; the outer loop that enumerates the leaf nodes of a tree, and the second inner loop that calculates the features. This nesting of loops limits parallelization because without completing the inner loop (more computationally demanding of the two), the outer loop cannot be started. Therefore, to improve run time, the order of loops is interchanged using initialization through a global scan of all instances and sorting using parallel threads. This switch improves algorithmic performance by offsetting any parallelization overheads in computation.Tree Pruning: The stopping criterion for tree splitting within GBM framework is greedy in nature and depends on the negative loss criterion at the point of split. XGBoost uses ‘max_depth’ parameter as specified instead of criterion first, and starts pruning trees backward. This ‘depth-first’ approach improves computational performance significantly.Hardware Optimization: This algorithm has been designed to make efficient use of hardware resources. This is accomplished by cache awareness by allocating internal buffers in each thread to store gradient statistics. Further enhancements such as ‘out-of-core’ computing optimize available disk space while handling big data-frames that do not fit into memory.以上引用自:XGBoost Algorithm: Long May She Reign!算法从算法角度来说,XGBoost主要对GBDT的目标函数进行了优化。GBDT的目标函数:$Obj_{gbdt} = \sum_{i=1}^N{L(f_m(x_i), y_i)}=\sum_{i=1}^N{L(f_{m-1}(x_i) + h_m(x_i), y_i)}$而XGBoost对目标函数的优化有两方面:加了一个正则项;使用泰勒公式展开。先看正则项:$Obj_{xgboost} = \sum_{i=1}^N{L(f_m(x_i), y_i)} + \sum_{j=1}^m{\Omega f(x_j)}=\sum_{i=1}^N{L(f_{m-1}(x_i) + h_m(x_i), y_i) + \sum_{j=1}^m{\Omega f(x_j)}}$加正则项就是Shrinkage的思想,可以防止过拟合、降低方差,获取更好的泛化效果。这个正则项的具体公式为:$\Omega f(x_i) = \gamma T + \frac{1}{2}\lambda||\omega||^2$其中$T$为树$f$的叶节点个数,$\omega$为所有叶节点输出回归值构成的向量,$\gamma,\lambda$为超参数。再看泰勒公式:更细的部分可参考下面2篇文章:机器学习|XGBoost详解(CSDN)XGBooost详解(知乎)总结梯度提升算法(Gradient Boosting Machine,GBM)其实是一类算法,GBDT只是其中一种,而XGBoost则是GBDT的一个优化版本(或者说是GBDT算法的一个具体的实现),这个优化不仅体现在算法层面,也包括很多工程实现方面的优化,比如并行、内存使用量少等。同类的还有微软开源的LightGBM,号称速度更快,内存使用更低,网上有很多对比评测文章,有兴趣的可以看看。 集成学习介绍(3)——Random Forest http://niyanchun.com/random-forest.html 2022-03-20T21:16:00+08:00 随机森林是一个非常直观,理解起来也比较容易的Bagging算法。前面我们介绍过决策树,其最大的一个缺点就是容易过拟合。随机森林则是由若干决策树组成的模型,其思想就是“三个臭皮匠顶个诸葛亮”。比如下图,就是由9个决策树组成的一个随机森林,其中6个决策树预测值为1,三个预测为0 ,所以最终预测值取多数方:1。如果是回归问题,一般取所有决策树预测结果的均值。理解随机森林的关键点在于理解“相关度低甚至不相关的多个决策树组合在一起的效果好于其中任何一个决策树”。这里拿一个例子做论证(注:此例来自第一个参考文章),做一个游戏:使用一个均匀分布的随机数产生器产生一个数字,如果这个数字大于等于40,则算你赢,可以获得一些钱;如果小于40,则算你输,你需要给对方同样数额的钱。现在有三种玩法供选择:玩法1(Game1):玩100次,每次的筹码是1元。玩法2(Game2):玩10次,每次筹码是10元。玩法3(Game3):玩1次,筹码是100.你会怎么选哪一个?我们计算一下赢钱的期望值:$E_1 = (0.6*1+0.4*(-1))*100=20$$E_2=(0.6*10+0.4*(-10))*100=20$$E_3=(0.6*100+0.4*(-100))=20$三种选择赢钱的期望值是一样的,那到底该如何选?我们做一个模拟:每种情况都模拟10000次,代码如下:import numpy as np import matplotlib.pyplot as plt import seaborn as sns sns.set_theme() # Game 1 simulations = 10000 # number of Monte Carlo Simulations games = 100 # number of times the game is played threshold = 40 # threshold where if greater than or equal to you win bet = 1 # dollar bet for the game # outer loop is Monte Carlo sims and inner loop is games played sim_results_1 = [] for sim in range(simulations): result = [] for g in range(games): number = int(np.random.uniform()*100) # get a random number to see who wins if number >= threshold: result.append(bet) else: result.append(-bet) sim_results_1.append(sum(result)) # sim_results_1 stores results for Game 1 print('Game 1 Mean: ', round(np.mean(sim_results_1), 2)) print('Game 1 Prob Positive: ', round(sum([1 for i in sim_results_1 if i>0])/simulations, 2)) print('\n') # Game 2 (structure of code is same as above) simulations = 10000 games = 10 threshold = 40 bet = 10 sim_results_2 = [] for sim in range(simulations): result = [] for g in range(games): number = int(np.random.uniform()*100) if number >= threshold: result.append(bet) else: result.append(-bet) sim_results_2.append(sum(result)) print('Game 2 Mean: ', round(np.mean(sim_results_2), 2)) print('Game 2 Prob Positive: ', round(sum([1 for i in sim_results_2 if i>0])/simulations, 2)) print('\n') # Game 3 (structure of code is same as above) simulations = 10000 games = 1 threshold = 40 bet = 100 sim_results_3 = [] for sim in range(simulations): result = [] for g in range(games): number = int(np.random.uniform()*100) if number >= threshold: result.append(bet) else: result.append(-bet) sim_results_3.append(sum(result)) print('Game 3 Mean: ', round(np.mean(sim_results_3), 2)) print('Game 3 Prob Positive: ', round(sum([1 for i in sim_results_3 if i>0])/simulations, 2)) # Histogram that shows the distribution of the Monte Carlo Results for 2 spending levels fig, ax = plt.subplots(figsize=(8,6)) sns.distplot(sim_results_1, kde=False, bins=60, label='Play 100 Times') sns.distplot(sim_results_2, kde=False, bins=60, label='Play 10 Times', color='orange') sns.distplot(sim_results_3, kde=False, bins=60, label='Play 1 Time', color='pink') ax.set_xlabel('Money Won by You', fontsize=16) ax.set_ylabel('Frequency',fontsize=16) plt.legend() plt.tight_layout() plt.savefig(fname='game_hist', dpi=150) plt.show()模拟的输出以分布图如下:# 代码输出 Game 1 Mean: 20.01 Game 1 Prob Positive: 0.97 Game 2 Mean: 20.2 Game 2 Prob Positive: 0.63 Game 3 Mean: 21.08 Game 3 Prob Positive: 0.61那可以看到赢钱的均值和即我们先前计算的期望值是一致的,三种玩法都接近20(Game x Mean),但赢钱的概率却相差很大,玩法1是97%,玩法2是63%,玩法3是61%。当然模拟次数再多一些,还有有一些变化。随机森林的思想和这个是一样的,里面包含的决策树的个数就是这里玩的次数。另外,随机森林还有一个非常关键的限定条件:各个决策树之间不相关或者关联度很低。类比到上面的游戏中,我们的假设是产生随机数的算法是遵从均匀分布的,也就是产生1~100之间的数字的概率是完全相等的。如果不是,那上面第一种玩法1最优的结论就不一定成立了。而随机森林实现各个决策树之间不相关或者关联度很低是通过“两个随机”实现的:数据随机:即每次只选取原始数据集的一个子集作为一个决策树的训练集,一般就是自助采样法(Bootstrap)。这是最核心的随机,主要是去除或者削弱各独立模型之间的相关性。属性随机:属性随机化的好处在于让每个单独的基学习器不会过分关注在训练集中具有高度预测性或者描述性的特征。通过两个随机,可以降低最终生成的模型的方差。而且通过这种方式生成的树一般也无需剪枝。Wikipedia上面的描述是这样的:Each decision tree in the forest considers a random subset of features when forming questions and only has access to a random set of the training data points. This increases diversity in the forest leading to more robust overall predictions and the name ‘random forest.’随机森林还有一个变种:Extra Trees。也称Extremely Randomized Trees,一般翻译为极限树,是随机森林的一个变种,进行了更彻底的随机,主要有两点:训练各个决策树的时候使用全部数据集,而非一个采样的数据子集;在选择决策树的拆分点的时候,均匀随机选择一个特征,而不是像随机森林那样根据信息增益或者基尼不纯度进行选择。极限树的两个改动点在大部分情况下会进一步的降低方差,但可能会稍微增大一些偏差。总结一下,随机森林的思路就是“群众的眼睛是雪亮的”,通过使用多个决策树组成一个“委员会”来进行预测或者回归,这些“群众”就是集成算法中的“若分类器”,他们相互之间没有关联度或者关联度低,而且一般在某一个点表现还可以。这点是通过“两个随机”去实现的,也是随机森林最核心的地方。References:Understanding Random ForestWikipedia:Random Forest 集成学习介绍(2)——AdaBoost http://niyanchun.com/adaboost.html 2022-03-12T11:17:00+08:00 概述AdaBoost是Adaptive Boosting的缩写,即自适应提升法,是最成功的Boosting算法。具体算法如下:Step1: Initialise the dataset and assign equal weight to each of the data point. Step2: Provide this as input to the model and identify the wrongly classified data points. Step3: Increase the weight of the wrongly classified data points. Step4: if (got required results) Goto step 5 else Goto step 2 Step5: End即初始的时候,赋予每个训练样本相同的权重;然后每次迭代后,增加分类错误样本的权重(数据集还是原来的数据集,只不过各个样本的权重变了),使得下一轮迭代时更加关注这些样本。AdaBoost一般选用决策树桩(decision stump)作为弱学习器。所谓stump是指由一个决策节点和两个叶子节点组成的二叉树:下图是一个二分类问题的学习过程示例,包含了三轮迭代:算法AdaBoost的算法描述如下:下面进行解释。“输入”(Given)部分:输入训练数据集包含m个样本,其中$x_i$是属性,取值范围属于集合$X$(实数集的子集),$y_i$是标签,取值是-1或+1.“初始化”(Initialize)部分:$D$表示带权重的样本(有时也用$w$表示),$D_i$表示第$i$个权重的样本。刚开始的时候,所有样本权重相同为$D_i = 1/m$.然后开始循环:使用权值分布为$D_t$的数据集,训练得到一个弱分类器$h_t$计算$h_t$在训练数据上面的误差$\varepsilon_t$根据训练误差$\varepsilon_t$计算出权重调整系数$\alpha_t$更新所有训练样本的权重,用于下一次迭代当训练误差比较低(比如错误率为0)或者弱分类器数目达到预先的设置值(超参控制),就停止循环最终的分类器就是各个各个弱分类器的加权求和,如果是分类问题,就再加上符号函数$sign$(输入数据大于0,输出1;等于0,输出0;小于0,输出-1).如何计算训练误差$\varepsilon_t$?公式如下:$$ \varepsilon_t = \frac {\sum_{i=1}^N\omega_iI(y_i \neq h_t(x_i))}{\sum_{i=1}^N\omega_i} $$其中$I(y_i \neq h_j(x_i))$的含义是如果$y_i \neq h_j(x_i)$ 成立,则返回1,否则返回0。下面看个具体的计算例子:有4个样本的权重分别为:0.5,0.2,0.1,0.2,弱分类器预测的值分别为:1,1,-1,-1实际的真实值为:-1,1,-1,1则$I$的值分别为:1,0,0,1于是有$$ \varepsilon_t = \frac{0.5*1+0.2*0+0.1*0+0.2*1}{0.5+0.2+0.1+0.2} = 0.7 $$有时也称$\varepsilon_t$为“total error”表示的是“所有错误分类的样本权重之和”,其实和上面的公式是一致的,因为权重一般是做过归一化的,所以分母里面的所有权重之和其实为1;而且分子中分类正确的样本的$I$值为0,也就是上面的公式可以简化为:$$ \varepsilon_t = \frac {\sum_{i=1}^N\omega_iI(y_i \neq h_t(x_i))}{\sum_{i=1}^N\omega_i} \\ = \sum_{i=1}^N\omega_iI(y_i \neq h_t(x_i)) = 错误分类样本的权重之和 = total\ error $$ 那权重调整系数$\alpha_t$的作用是什么呢?看个具体的例子。假设现在有3个$\varepsilon$值:0.3、0.5、0.7。则对应的$\alpha$的值如下:$$ \alpha(\varepsilon=0.3) = \frac{1}{2}*ln\frac{1-0.3}{0.3} = 0.42365 \\ \alpha(\varepsilon=0.5) = \frac{1}{2}*ln\frac{1-0.5}{0.5} = 0 \\ \alpha(\varepsilon=0.7) = \frac{1}{2}*ln\frac{1-0.7}{0.7} = -0.42365 \\ $$可以看到,当弱分类器的准确率为0.5时,其权重为0;准确率大于0.5(即错误率小于0.5)时,权重大于0;准确率小于0.5(即错误率大于0.5)时,权重小于0。所以,错误率$\varepsilon$越小,$\alpha$越大,即当前模型的表现越好,在最终的生成器中占的权重就越大。所以$\alpha$也称为“amount of say”、“Performance”、“Importance”,都指的是当前分类器在最终分类器中的权重。另外注意计算的时候有时使用自然对数($ln=log_e$),有时使用常用对数($log_{10}$)。再来看最重要的部分:样本权重更新。在Boosting的迭代中,我们每次要找的是错误率比较低的弱分类器。为了方便我们沿用上面$\alpha(\varepsilon=0.3)$的计算结果,看下分类错误和正确时计算出来的权重值:当在样本$i$上预测正确(即$y_i = h_t(i)$),训练误差为0.3时,计算的新的权重如下:当在样本$i$上预测错误(即$y_i \neq h_t(i)$),训练误差为0.3时,计算的新的权重如下:可以看到当弱分类器在某个样本上分类正确的时候,该样本的权重会降低;否则就会提升,符合理论预期。最后提一下$Z_t$,这是一个规范化系数(normalization factor ),是为了让新计算出来的权重代表一个真正的分布,一般就是归一化。比如上面的例子所有样本的权重更新后加起来后做一下归一化。上面的公式可以简化一下:对于分类正确的样本:$D_{t+1}(i)=\frac{D_t*e^{-\alpha_t}}{Z_t}$对于分类错误的样本:$D_{t+1}(i)=\frac{D_t*e^{\alpha_t}}{Z_t}$最后一个问题,增加了预测错误的样本的权重之后,如何在下一轮迭代中更关注他们呢?实质是通过权重影响每一轮迭代数据集的选择来实现:比如我们有一个包含N个样本的数据集,每个样本都有一个权重(第一轮时权重相同,均为1/N),然后每一轮选择本轮使用的$m(m \le N)$个样本时,是根据权重随机采样的。也就是有些样本可能会被选择多次,有的可能一次也不会被选中。特别是当样本的权重被更新后,权重大的样本就更容易被选中,甚至选中多次了。这样这些样本自然会对后面的弱分类器产生比较多的影响。比如Pandas的sample方法就可以实现在一个数据集上面按照权重采样。另外,还有一种“Bucket”的方式,思路如下:原来有N个样本,每次选N个样本进行弱学习器的训练。第一轮大家权重一样,第二轮的时候,根据权重先将原数据集划分“Bucket”,权重高的样本会占据多个bucket。而每个bucket被选中的几率是一样的,所以权重高的样本就可能被多选几次。也就是第二轮的时候虽然还是N个样本,但某些权重低的可能没有被选,某些权重高的样本可能被选了多次。比如下面的例子(N=5,ref:AdaBoost Algorithm – A Complete Guide for Beginners):第一轮,所有样本权重一样,可以理解为每个样本就是一个bucket:第一轮结束的时候,各个样本的权重已经发生了变化(假设第4条数据分错了,它的权重提升了):如上图,按照权重重新划分了数据集的bucket,分错的第2个样本占了比较多的bucket。所以在为第二轮迭代选择样本的时候,第4条被选中的概率就会比较大。比如算法随机的5个bucket是:0.38,0.26,0.98,0.40,0.55。那选出的样本就是下图:可以看到,样本4在第二轮出现了3次,这样后面的分类器必然会对该样本倾斜。上面的第一种情况其实是后面bucket这种方式的一种特殊情况。所以,需要注意的是,每轮迭代改变了样本的权重后,对下一轮的影响是体现在挑选数据集的时候。当数据集选好后,这些数据集又会被赋予相同的权重,开始新一轮的迭代。后续不断按此方式迭代,直到错误率降到某个阈值或者达到预设的迭代次数。例子下面看两个例子,第一个偏理论,第二个偏实现,都有助于理解整个算法的流程和内在逻辑。A Mathematical Explanation of AdaBoost in 5 Minutes该例引用自:A Mathematical Explanation of AdaBoost in 5 Minutes。如下图:有一个包含6个样本的数据集,共3个属性:x1,x2,x3,输出为Y。其中T代表True,F代表False。Step1:初始化,给每个样本赋予相同的权重,即1/6:Step2:使用上面的数据生成弱学习器stump:分别计算各个属性的基尼不纯度(gini impurity)。$$ Gini\ Impurity = 1- Pr_{true}^2-Pr_{false}^2 $$这里以$x_2$属性为例进行计算:$$ Total \ Impurity(x_2) = 0.375*(\frac{1+3}{6})+0*(\frac{2+0}{6}) = 0.25 $$同理可以计算出$x_1$和$x_3$的不纯度为:$$ Total\ Impurity(x_1)=(1-(\frac{2}{2+2})^2-(\frac{2}{2+2})^2)*(\frac{2+2}{6}) \\ + (1-(\frac{1}{1+0})^2-(\frac{0}{1+0})^2)*(\frac{1+0}{6}) \\ = 0.33 \\ \\ Total\ Impurity(x_3)=(1-(\frac{0}{0+1})^2-(\frac{1}{0+1})^2)*(\frac{0+1}{6}) \\ + (1-(\frac{3}{3+2})^2-(\frac{2}{3+2})^2)*(\frac{3+2}{6}) \\ = 0.4 $$可以看到$x_2$的基尼不纯度最低,所以使用它生成第一个stump。Step3:计算“amount of say”。使用x2属性生成的stump会将第1个样本分错,其它都正确。这样的话total error就是1/6。所以:$$ Amount\ of\ say = \frac{1}{2}*log{\frac{1-\frac{1}{6}}{\frac{1}{6}}} = 0.35 $$Step4:计算下一个弱分类器(stump)的样本权重。分类错误的样本新权重 $w_{incorrect} = \frac{1}{6}* e^{0.35} = 0.24$分类正确的样本新权重 $w_{correct} = \frac{1}{6}* e^{-0.35} = 0.12$然后归一化得到下一个stump(即下一轮迭代)的样本及权重,如下图:这样第一轮就结束了。第二轮迭代开始之前,要先根据样本权重选取训练数据,比如选择的样本可能如下(可以看到第一轮分类错误的样本这次被选了3次):然后,将选中的样本权重初始化为相等的值,继续重复前面的过程:直到训练误差足够小,或者弱分类器个数达到限制,则迭代终止。对所有弱分类器加权求和得到最终的强分类器。Implementing the AdaBoost Algorithm From Scratch上面的例子比较偏理论性,这个例子则是具体到代码实现层面的,限于篇幅和格式,就不在文章里面贴了。具体可以参见我的Github: implement-adaboost-from-scratch.ipynb。里面会根据上面的算法实现一个AdaBoost,并且最终和scikit-learn的AdaBoostClassifier做对比。​该例参考自:Implementing the AdaBoost Algorithm From Scratch总结AdaBoost算法的思想还是比较简单的,算法也容易理解,主要需要理解下面几个关键点:如何计算训练误差$\varepsilon_t$理解权重调整系数$\alpha_t$的作用样本权重是如何更新的,又是如何影响到下一轮迭代的掌握了这些,在做模型的调优以及一些超参设置上也就可以游刃有余了。 决策树介绍 http://niyanchun.com/decision-tree.html 2022-03-05T12:05:00+08:00 在接下来介绍的具体的集成算法里面,大都是以决策树作为最底层的算法,所以本篇先介绍一下决策树。本文整理自2017年的学习笔记。决策树是一个非常简单的算法,至少其思想是非常简单的。生活中我们经常会使用,看几个例子。场景1,母亲给女儿介绍男朋友,下面是二人的对话:女儿:多大年纪了?母亲:26。女儿:长的帅不帅?母亲:挺帅的。女儿:收入高不?母亲:不算很高,中等情况。女儿:是公务员不?母亲:是,在税务局上班呢。女儿:那好,我去见见。女儿通过年龄、长相、收入、是否是公务员将男人分为两个类别:见和不见。女孩选择见或不见的过程就是决策树决策的过程。假设女孩对男朋友的要求是:30岁以下、长相中等以上、高收入者或者中等收入以上的公务员。我们可以构造如下一个决策树:其中,绿色节点表示判断条件,蓝色节点(叶子节点)表示决策结果,左右箭头称作分支。过去的专家系统往往就使用决策树。再看一个例子,平面上有一些点,我们需要找到一个函数(曲线)把它们分开。如果是线性可分的情况,直觉上我们会画一条直线来切分平面,它的方程与x,y两个属性均有关,可以表示为:而对于决策树,通常每次决策(平面划分)只与一个特征相关(x或y)。也就是说,我们只能画水平或竖直的线:决策树同样适用于线性不可分的情况(并非最优划分):接下来,我们使用下面的数据集看下构造决策树的一些关键点。该数据集有2个特征:f1和f2,然后label是是否属于鱼类,共5条样本: 能否在水中生存(f1)是否有脚蹼(f2)是否属于鱼类1是是是2是是是3是否否4否是否5否是否如何构造根据f1、f2两个特征判断是否属于鱼类的决策树?下面是2种可能的决策树:哪个更好?为什么?构造决策树时,需要确定在哪个特征/属性上面划分数据集,我们称该属性为分裂属性。如何确定分裂属性?大原则:划分后,让无序数据变得更加有序。那如何评估数据的有序程度呢?有两种:信息增益(Information Gain)和基尼不纯度(Gini Impurity)。我们平时说“xxx事情包含的信息量很大”,直观感受就是这个事情的不确定性很大。其实有专门一门学科是专门研究信息的:信息论(这个课还是我大学时的专业课,当时觉得太理论,没什么意思,现在...唉)。这个学科的创始人香农(Claude Elwood Shannon)提出了量化一个系统包含信息量多少的概念——熵(Entropy),单位是比特(bit),它衡量的是随机变量的不确定性。其定义如下:如果有一个系统S内存在多个事件$S = {E_1,...,E_n}$,每个事件的概率分布$P = {p_1, ..., p_n}$,则每个事件本身的信息为(单位是bit):$I_e=-log_2p_i$。​熵是信息的期望值,即整个系统的平均信息量:$H_s=\sum_{i=1}^n{p_iI_e}=-\sum_{i=1}^np_i{log_2p_i}$举个例子,比如英语有26个字母,如果每个字母在文章中出现的次数均等的话,则在这篇文章中每个字母的信息量为:$I_e=-log_2\frac1{26}=4.7$。整个文章的熵为:$H_s=\sum_{i=1}^{26}\frac{1}{26}*4.7=\frac{1}{26}*4.7*26=4.7$。因为这里假设每个事件发生概率一样,所以单个事件信息量就等于整个系统的平均信息量。所以,熵描述的其实是随机变量的不确定性。对于确定的系统,熵为0。那什么是信息增益?这就涉及到条件熵的概念:条件熵——在一个条件下,随机变量的不确定性。而信息增益就是“熵 - 条件熵”。表示在一个条件下,信息不确定性减少的程度。放到决策树这里,就是当选用某个特征划分数据集,系统前后信息发生的变化。计算公式为:$Gain_{split}=H(p)-\sum_{i=1}^k\frac{n_i}{n}H(i)$。即使用某个特征(split)划分数据集以后,得到的信息增益为划分前数据集的熵减去划分后的数据集的熵。下面以前面的鱼类为例,看具体如何计算:划分前整个数据集为:{是,是,否,否,否} ,对应的熵为:$H=-\frac{2}{5}log_2\frac{2}{5}-\frac{3}{5}log_2\frac{3}{5}=0.97$如果使用特征f1划分数据集,得到两个数据子集:f1=是,S1:{是,是,否},对应的熵为:$H_{f1=是}=-\frac{2}{3}log_2\frac{2}{3}-\frac{1}{3}log_2\frac{1}{3}=0.92$f1=否:S2:{否,否},对应的熵为:$H_{f1=否}=-1log_21=0$​​所以,按f1划分后获得的信息增益为:$$ Gain_{f1}=H-(\frac{3}{5}H_{f1=是}+\frac{2}{5}H_{f1=否})=0.97-\frac{3}{5}*0.92-\frac{2}{5}*0=0.42 $$同理,可以计算按照f2划分数据集以后得到的信息增益为:$$ Gain_{f2}=H-(\frac{4}{5}H_{f1=是}+\frac{1}{5}H_{f1=否})=0.97-\frac{4}{5}*1-\frac{1}{5}*0=0.17 $$通过对比,使用f1划分数据集,获得的信息增益大于使用f2划分,也就是使用f1划分使得系统的不确定性下降的更多,所以使用f1优于f2.除了信息增益,还有一种常用的评价标准——基尼不纯度(Gini Impurity):将来自集合中的某种结果随机应用于集合中某一数据项的预期误差率。英文定义是这样的:Gini impurity (named after Italian mathematician Corrado Gini) is a measure of how often a randomly chosen element from the set would be incorrectly labeled if it was randomly labeled according to the distribution of labels in the subset.英文的定义其实更好理解一些,就是你随机从一个集合里面选择一个元素,然后根据这个集合的分布情况随机给这个数据选择一个类别,选择错误可能性的一个描述。维基百科给的计算公式如下:其中$p_i$是选中第$i$个样本的概率。根据公式可以看到,基尼不纯度的取值范围是[0, 1)。当一个集合完全去定,即里面只有一种元素,则基尼不纯度为0,因为你随机选一个样本,再随机猜一个类别,肯定是不会错的,因为集合里面就只有一种样本;如果一个集合里面全是不同的元素(即混乱程度比较高),则基尼不纯度趋于1,也好理解,你随机选一个样本,随便猜一个种类,因为每个样本都不一样,当n趋于无穷大的时候,猜对的概率几乎为0。所以,不管是信息熵,还是基尼不纯度,衡量的都是一个集合的混乱程度。值越大,越混乱,包含的信息量也越大。对于决策树,使用基尼不纯度和熵的差别非常小:熵对于混乱集合的惩罚略重于基尼不纯度;熵的计算量略大于基尼不纯度。以二分决策为例,此时p1+p2=1,因此:对应的曲线图如下:有了树的划分标准以后,就是根据特征进行递归划分数据集,满足下面任一条件,递归结束:遍历完所有划分数据集的属性每个分支下的所有实例都具有相同的分类如果遍历完所有属性,类标签仍不唯一,一般采用多数表决的方法决定该叶子节点的分类。决策树的一个缺点在于很容易过拟合,一般通过剪枝操作来解决改问题,根据剪枝的时机分为两种:预剪枝(prepruning):在这种方法中,树增长算法在产生完全拟合整个训练数据集的完全增长的决策树之前就停止决策树的生长。为了做到这一点,需要采用更具有限制性的结束条件,例如,当观察到的不纯性度量的增益(或估计的泛化误差的改进)低于某个确定的阈值时就停止扩展叶节点。这种方法的优点在于避免产生过分拟合训练数据的过于复杂的子树。然而,很难为提前终止选取正确的阈值。阈值太高将导致拟合不足的模型,而阈值太低就不能充分的解决过拟合的问题。此外,即便使用已有的属性得不到显著的增益,接下来的划分也可能产生较好的子树。后剪枝(postpruning):在该方法中,初始决策树按照最大规模生长,然后进行剪枝的步骤,按照自底向上的方式修剪完全增长的决策树。修剪有两种方法:用新的叶节点替换子树,该叶节点的类标号由子树下记录中的多数类确定。用子树中最常使用的分支代替子树。当模型不能再改进时终止剪枝步骤。两种方法各有优劣:与先剪枝相比,后剪枝倾向于产生更好的结果,因为不像先剪枝,后剪枝是根据完全增长的决策树做出的剪枝决策,先剪枝则可能过早终止了决策树的生长。然而,对于后剪枝,当子树被剪掉后,生长完全决策树的额外计算就被浪费了。目前常见的决策树算法有:ID3 (Iterative Dichotomiser 3) :该算法只能处理标称型数据集。我们之前构造决策树中使用的方法就是ID3算法,该算法使用信息增益作为分裂特征选取的标准。ID3算法可以归纳为以下几点:使用所有没有使用的属性并计算与之相关的样本熵值选取其中熵值最小的属性生成包含该属性的节点C4.5:ID3的优化版本,主要有两个优化点:不仅可以处理标称型数据,还可以处理数值型数据。使用信息增益率而不是信息增益,改善了分裂特征偏向于具有大量值属性的问题。C5.0:C4.5的优化版本,更高效且内存占用更小,但注册了专利,所以使用的比较少。CART(Classification and Regression Trees):CART算法采用一种二分递归分割的技术,算法总是将当前样本集分割为两个子样本集,使得生成的决策树的每个非叶结点都只有两个分枝。因此CART算法生成的决策树是结构简洁的二叉树。CART算法适用于样本特征的取值为是或非的场景,对于连续特征的处理则与C4.5算法相似。scikit-learn里面使用的就是优化过的CART算法。其中除了CART使用基尼不纯度外,前面集中都是用的是信息增益作为选择分类属性的标准。下面看scikit-learn中决策树的一个例子:from sklearn.datasets import load_iris from sklearn import tree import graphviz iris = load_iris() clf = tree.DecisionTreeClassifier() clf = clf.fit(iris.data, iris.target) dot_data = tree.export_graphviz(clf, out_file=None, feature_names=iris.feature_names, class_names=iris.target_names, filled=True, rounded=True, special_characters=True) graph = graphviz.Source(dot_data) graph.render("iris")上面的代码生成的决策树如下:目前已经很少有单独使用决策树作为最终算法模型的场景了,一般都会选取基于决策树的更好的集成算法。下面是决策树大致发展过程的一个概括:后面的文章会介绍这些集成算法。refs:Wikipedia: Decision tree learninghttp://www.cnblogs.com/leoo2sk/archive/2010/09/19/decision-tree.htmlhttp://www.jiarui-blog.com/2017/08/10/决策树-1/http://www.jiarui-blog.com/2017/08/28/decision-tree-2/https://github.com/MangoLiu/mangoliu.github.io/blob/master/机器学习_决策树.md 集成学习介绍(1)——Boosting&amp;Bagging http://niyanchun.com/boosting-and-bagging.html 2022-02-26T11:31:00+08:00 最近准备整理一下之前关于集成学习的学习笔记,写一个关于集成学习的系列文章,毕竟目前用的比较多的机器学习算法基本都属于集成学习,整理一下,也算温习一下。有些笔记时间比较久了,里面的一些引用来源找不到了,所以有些引用可能附不全,敬请谅解。目前确定的几篇包括:集成学习介绍(1)——Boosting && Bagging集成学习介绍(2)——AdaBoost集成学习介绍(3)——Random Forests集成学习介绍(4)——GBDT集成学习介绍(5)——XGBoost后续待定...后面可能会根据时间补充一下其它一些现在也比较流行的算法,比如LightGBM。本文是第一篇。集成学习(ensemble learning)是将多个基学习器(base learners)进行集成,得到比每个单独基学习器更优的强学习器(strong learner)的方法。每个用于集成的基学习器都是弱学习器(weak learner),即性能只比随机猜测好一点点或只在某些方面表现好一点的学习器(classifiers that produce prediction that is slightly better than random guessing)。​那如何保证多个弱学习器集成在一起会变的更好,而不是更差呢?即如何实现“1+1>2”的效果呢?这对弱学习器提出了一些要求:弱学习器的性能要有一定的保证,至少在某个方面要表现比较好。弱学习器的性能要有一定的差异,即擅长点要有所区分,这样组合后才能集百家之所长。根据训练数据使用方法的不同,集成学习可以分为三种:提升法(Boosting):各弱学习器之间存在强依赖关系而必须串行生成的序列化方法,各个弱学习器在最终的强学习器中权重不同;装袋法(Bagging):各弱学习器之间不存在强依赖关系因而可以同时生成的并行化方法,各个弱学习器在最终的强学习器中权重相同;堆叠法(Stacking):融合提升法和装袋法的一种集成。注意:集成学习属于机器学习,但它只是一种训练思路,而不是某种具体的算法。一般把他划归到元学习(meta learning):关于学习的学习。Boosting/Bagging的所使用的弱学习器一般是同类型的。Boosting提升法的是通过改变训练数据的权重(或概率分布)来训练不同的弱分类器,然后组合为强分类器。下面是两个示意图:Boosting的重点在于取新模型之长补旧模型之短来降低偏差(bias),尽可能获得无偏估计。BaggingBagging是Bootstrap Aggregation的缩写。Bootstrap也称为自助法,是一种有放回抽样方法。Bagging的的基本思想是对训练数据进行有放回抽样,每次抽样数据就训练一个模型,最终在这些模型上面取平均。具体算法如下:Step 1: Multiple subsets are created from the original data set with equal tuples, selecting observations with replacement.Step 2: A base model is created on each of these subsets.Step 3: Each model is learned in parallel from each training set and independent of each other.Step 4: The final predictions are determined by combining the predictions from all the models.下面是一个示意图:Bagging可以降低模型算法的方差,但并没有降低偏差的效果,所以也就没法提升预测的准确性,所以在选择弱分类器时要尽量选择偏差小的。​为什么Bagging能降低模型的方差?因为“如果对N个相互独立且方差相同的高斯分布取平均值,新分布的方差就会变成原始方差的 $1/N$”。Bagging采用独立有放回抽样得到N份数据,并训练得到N个模型,预测的时候最终会取这N个结果的平均(分类的话是取多数),这样就可以降低方差。对比下面对Boosting和Bagging做了一些对比: BoostingBagging样本选择每一轮训练集不变,但权重根据上一轮进行调整从原始数据集中通过独立、有放回抽样获得样例权重根据错误率不断调整,错误率越大的样本权重越大使用均匀抽样,各个样本权重相同模型权重每个弱分类器都有相应的权重,分类误差越小的分类器权重越高各个模型(弱分类器)权重相同并行计算各个分类器串行生成,因为后面的分类器需要前一轮的结果各个分类器并行生成目标主要为了减小偏差主要为了减小方差,并且解决过拟合问题适用场景分类器比较稳定(即方差比较小)和简单(即偏差比较大)分类器不稳定(即方差比较大),且偏差比较小的代表算法AdaBoost、GBDT、XGBoost随机森林Stacking除了提升法和装袋法之外,另一种知名度较低的集成方法是堆叠法。堆叠法(stacking)也叫堆叠泛化(stacked generalization),是层次化的集成方法,其思想和神经网络类似,只不过神经网络堆叠的对象是神经元和隐藏层,而集成方法堆叠的是同构或者异构的基学习器。​堆叠法先要用自助采样生成不同的数据子集,用数据子集训练第一层中不同的基学习器。第一层基学习器的输出再被送到第二层的元分类器(meta classifier)中作为输入,用来训练元分类器的参数。​堆叠法的思想和前两种方法有所不同。无论是提升法还是装袋法,其重点都落在单个模型的生成方式上,也就是如何训练出合适的基学习器,基学习器的形式一般是统一的。而堆叠法的重点在于如何将不同的基学习器的结果组合起来,研究的对象是让所有基学习器共同发挥出最大效果的组合策略。某种意义上说,堆叠法的训练数据不是原始的训练数据集,而是不同基学习器在训练数据集上的结果,起到的是模型平均(model averaging)的作用,提升法和装袋法都可以看成它的特例。正因如此,堆叠法除了被视为集成方法外,还可以看成是模型选择的一个手段。以上这段摘自极客时间《机器学习40讲专栏》​关于偏差和方差最后补充介绍一下偏差和方差这两个重要的概念。一个机器学习模型的误差可分为两类:经验误差(empirical error):又称训练误差(training error),指模型在训练集上面的误差;泛化误差(generalization error):模型在测试集上面的误差。其中泛化误差又可以分为三部分:偏差(bias):表示算法预测值与真实值之间的偏离程度,刻画的是模型的欠拟合(under-fitting)特性。偏差大,说明模型欠拟合。方差(variance):表示数据扰动对预测性能的影响,刻画的是模型过拟合(over-fitting)特性。方差大,说明模型过拟合。噪声(noise):表示在当前学习任务上所能达到的最小泛化误差,刻画的是任务本身的难度。用公式可表示为:$$ generalization\_error = bias^2 + variance + noise $$一般很难做到同时将偏差和方差都降到很低,只能在二者之间做权衡。下图中,靶心的红色是是预测正确的值,越往外,预测结果越差。四个图分别描述了误差和方差的高低情况:上图来自:Understanding the Bias-Variance Tradeoff一文,关于偏差和方差的更多细节,也可以参考这篇文章。 Logistic Regression算法 http://niyanchun.com/logistic-regression-introduction.html 2018-10-10T23:25:00+08:00 在之前的《常见线性回归模型》一文中,介绍了机器学习中比较简单但又非常常用的线性回归模型,今天来介绍另外一个模型:Logistic Regression,这又是机器学习中用的非常多的一个模型。虽然Logistic Regression(后简称LR)里面带了回归字样(Regression),但它实际是一个分类模型(关于回归和分类的区别见《机器学习介绍》),更准确的说是一个二分类模型(0、1或者true、false之类,当然通过一些手段也可以实现多分类),比如预测一份邮件是不是垃圾垃圾邮件、一个人是否患了某种病等。它的基本思想是:假设一份邮件为垃圾邮件的概率为$p$,我们根据一些特征计算出这个$p$,如果$p$大于0.5,那么就认为是垃圾邮件,否则就不是垃圾邮件。下面我们就看看是如何计算这个概率p的。先回顾一下线性回归的公式:$$ { \hat y=\omega_0+\omega_1x_1+...+\omega_px_p =\omega_0+X\omega } $$线性回归预测出来的结果y属于整个实数域,而概率只能是0~1之间的数。有没有什么方法可以将整个实数域映射到0~1之间呢?当然有,Logit函数就可以,我们看下Logit函数:$$ { logit(x) = \frac{1}{1+e^{-x}} } $$我们看下函数图像:从函数公式和图像可以看出来自变量x的定义域为整个实数域,而因变量的值则为0~1之间的实数,正好符合我们上面的要求。其实除了logit函数,还有一些其它函数也可以实现该功能,因为这些函数的图形都呈S型,所以也称之为Sigmoid(英文含义为“S型函数”)函数,我们看看除了logit函数之外的其他sigmoid函数:当然最常用的sigmoid函数还是logit函数,所以很多地方提到sigmoid函数指的就是logit函数。言归正传,现在我们已经解决了从实数域映射到0~1之间的问题了。然后我们将线性回归公式和logit函数合并一下:$$ p = y(w,x) = \frac{1}{1+e^{-(\omega_0+X\omega)}} $$$X$依旧是特征,所以现在和线性回归就一样了,我们只要根据已有的数据计算出$\omega_i$的值,也就是构建出了预测模型了。当有一条新的数据之后,我们带到上面的公式里面,计算出$y$的值,如果大于0.5就认为是某个类别,否则就属于另外一个类别。可以看到,LR模型是通过将线性模型与Sigmoid函数组合而实现的,这也是为什么里面带了回归的字样。现在的问题就成了如何计算$\omega_i$了,也就是参数估计。常用的两种参数估计方法为:(普通)最小二乘法(Ordinary Least Squares, OLS)和最大似然估计(Maximum Likehood Estimation, MLE)。前者基于距离,后者基于概率,当误差满足正态分布的时候,虽然两种估计原理和方式不一样,但结果是一样的。而之前线性回归模型中我们使用的就是最小二乘法,而LR是基于概率的,所以一般使用最大似然估计来计算参数$\omega_i$的值。如果你还不知道什么是最大似然估计,可以看下知乎上面马同学的回答:如何通俗地理解概率论中的极大似然估计法?网上关于LR算法参数估计详细的数学推导很多,这里就简单推导一下。使用MLE做参数估计的核心就是计算似然函数。现在假设有n个样本,观测值分别为$y_1,y_2,…,y_n$($y_i$的取值为0或1)。设$p_i=p(y=1|x_i)$为给定$x_i$ (第i条样本或第i个观测值)的条件下得到$y_i=1$的条件概率;这样,在同样条件下得到结果$y_i=0$的条件概率为$p(y=0|x_i)=1-p_i$。从而,我们得到一个观测值的概率为:$$ { p(y_i) = p_i^{y_i}(1-p_i)^{1-y_i} } $$因为各项观测值相互独立,所以他们的联合分布就是各边缘分布的乘积:$$ { L(\theta)=\prod_{i=1}^n p_i^{y_i}(1 - p_i)^{1-y_i} } $$这就是我们要求的n个观测值的似然函数(前面加一个负号就是Logistic Regression的损失函数)。MLE就是要求出能够使这一似然函数值最大的参数估计。为了简化,一般将其转换为对数形式(这里不打公式了,直接截图之前在word里面打的公式了):接下来的问题就是求上面对数似然函数的极大值了。根据高数的理论,讨论就是求倒数,然后另倒数等于0,计算出来的点就是极值点了。我们分别对$\omega_0$和$\omega_i(i=1,...,n)$求偏导:上面两个式子称为似然方程(likehood equations)。如果模型中有$n$个自变量,那么就有$n+1$个联立方程来估计$\omega_0$和$\omega_i(i=1,...,n)$的值。在线性回归中,似然方程是通过把偏差平方和分别对$\omega_0$和$\omega_i(i=1,...,n)$求偏导所得到的(即之前说的OLS),它对于未知参数都是线性的,因此很容易求解。但对于LR,上面两个似然方程都是$\omega_0$和$\omega_i(i=1,...,n)$的非线性函数,所以求解十分困难。对于这种非线性函数的极值问题,一般是通过迭代的方式求解的,最常用的就是梯度上升(求极大值)和梯度下降(求极小值)算法了。这里我们是要求最大值。而梯度上升的基本思想就是:要找某函数的最大值,最好的方法是沿着该函数的梯度方向探查,即沿着梯度的方向移动,每移动一次称之为一次迭代。梯度记为$\nabla$,$f(\omega,\omega_0)$的梯度为:沿梯度方向每次移动的大小称之为步长,记为$\alpha$。这样梯度上升算法的迭代公式为:$$ { \omega := \omega + \alpha \nabla_\omega f(\omega) } $$我们不停的迭代该公式,直到达到某个停止条件位置迭代次数到达某个指定值。此时的$\omega$和$\omega_0$就是我们要求的回归系数和截距。原生的Logistic Regression模型是处理二分类问题的,但通过一些技术手段也可以处理多分类,目前LR一般有三种类型:Binary Logistic Regression:即原生的二分类模型。Multinomial Logistic Regression:多分类模型,即要预测的目标变量多于两个。Ordinal Logistic Regression:序数多分类模型,目标变量多于两个,且有序,比如1到5.另外简单总结下LR模型的优缺点:优点:高效、直观易懂,容易实现、结果容易解释,不需要耗费非常高的计算能力;不需要对特征做归一化(一把基于概率的模型不需要做归一化,而基于距离的模型需要做归一化);给出了观测值的概率,这一点在实际应用中非常重要,像很多评分模型的分数就是基于概率计算的。缺点:不太能处理特征特别多的场景,容易过拟合;如果特征和目标变量相关性很小的话,模型性能也不好;如果特征之间存在相关性的话,模型性能表现不好。以上便是Logistic Regression的理论知识,实际中我们基本上不会自己实现LR模型,都是使用各种库,所以那些优(fan)美(ren)的数学公式的计算对我们都是不可见的。最后我们使用scikit-learn这个Python的机器学习库中提供的LR模型做一个简单的二分类预测:有一个糖尿病的数据,数据中前8列为特征变量,最后一列为目标变量。我们按照通用的套路构建模型:读入数据集,划分训练集和测试集;划分训练集和测试集;创建并训练模型;在测试集上面进行预测;探索模型性能需要注意的是,第5步探索模型性能的时候用到了机器学习里面的一些专业东西,比如准确率、精确度、召回率等,如果你还不了解这些的含义,可以阅读我之前的文章《confusion matrix,precision,recall,F1-score,ROC,AUC,p-value概念总结》。下面是完整的代码:import pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn import metrics import matplotlib.pyplot as plt # 读入数据 df = pd.read_csv("https://raw.githubusercontent.com/niyanchun/AI_Learning/master/SampleData/diabetes.csv") X = df.values[:, :-1] y = df.values[:, -1] # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25,random_state=1024) # 创建模型并训练 lr = LogisticRegression() lr.fit(X_train, y_train) # 在测试集上面预测 predictions = lr.predict(X_test) # 求混淆矩阵、准确率、精确率、召回率 print("Confusion Matrix: ", metrics.confusion_matrix(y_test, predictions)) print("Accuracy: ", metrics.accuracy_score(y_test, predictions)) print("Precision: ", metrics.precision_score(y_test,predictions)) print("Recall: ", metrics.recall_score(y_test, predictions)) # 画ROC图 pred_proba = lr.predict_proba(X_test)[::,1] fpr, tpr, _ = metrics.roc_curve(y_test, pred_proba) auc = metrics.roc_auc_score(y_test, pred_proba) plt.plot(fpr,tpr,label="data 1, auc="+str(auc)) plt.legend(loc=4) plt.show()ROC图:这是总结的之前自己做的一些笔记,当时查阅的资料已经不记得有哪些已经出处了,本文就不附References了。 朴素贝叶斯分类器 http://niyanchun.com/naive-bayes-classifier.html 2018-07-28T23:31:00+08:00 贝叶斯定理是概率论中非常有名的一个定理,而朴素贝叶斯(Naive Bayes)则是贝叶斯理论下非常有名的一个算法,在ML和NLP领域里面应用非常多。之前做过一些学习笔记,今天把原来的笔记再梳理了一下,发到博客上面来。如有不对之处,欢迎指正。另外,因为过的时间比较久了,当时整理时参考的一些出处已经记不得了,后面就不附出处了,开始正题。贝叶斯公式先看一下贝叶斯涉及到的一些概率论的概念:$$ P(A|B) = \frac{P(AB)}{P(B)} $$上面的公式是条件概率(conditional probability)的通用定义,介绍以下三个概念:边缘概率(marginal probability):仅和单个随机变量有关的概率,比如上面的$P(A)$和$P(B)$。联合概率(joint probability):包含多个条件且所有条件同时成立的概率。条件概率:在某些条件确定的情况下另外一些条件发生的概率,比如上面公式是$P(A|B)$就是A的条件概率。下面看著名的贝叶斯公式:$$ P(X|Y) = \frac{P(Y|X)P(X)}{P(Y)} $$公式及其推导非常简单,因为$P(XY)=P(Y|X)P(X)$,用该式替换掉条件概率公式的分母即可得到贝叶斯公式。这里我们再介绍几个术语,在上面公式中:$P(X)$称之为(X的)先验概率(prior probability),因为$P(X)$的概率是已知的了。$P(Y|X)$称之为(Y的)条件概率,和之前概念一样。$P(X|Y)$称之为(X的)后验概率(posterior probability)。贝叶斯公式形式很简单,那有什么重大意义呢?上面我在写贝叶斯公式的时候用X、Y代替了原来的A、B,是因为我们一般用X表示自变量,Y表示应变量。而贝叶斯公式就是来描述这种因果关系的,这样写更习惯一些。放到现实中,X就是事件发生的原因,Y对应于事件的结果。贝叶斯公式的重大意义在于给出了一个解决逆问题的方法,所谓逆问题是指从结果Y反推原因X,因为有些情况下事情发生的原因往往是无法被直接观察和测量的。举一些例子:通信:根据含有噪声的接收信号Y推测发送信号X语音识别:根据麦克风识别的音频波形数据Y推测语音信息X文字识别:根据扫描仪读取的图像数据Y推测用户书写的文字X垃圾邮件过滤:根据收到的邮件文本Y推测邮件的类型X(是否是广告等)这里再提一下先验概率和后验概率(因为我当时对于这二者的含义疑惑了比较久),这二者主要用于表示事件是发生于结果Y取得之前还是之后,比如$P(X)$是Y还没有得到,所以为先验概率;而$P(X|Y)$是结果Y已经得到了,所以为后验概率。从上面的描述可以看出,X和Y之间存在一定的因果关系,这样我们是不是可以建立一个$X=f(Y)$的公式来由结果Y计算原因X呢?这的确是一种解决方案,但实际效果往往会很差,因为现实中存在噪声和误差,即使即使X相同,Y也可能不同,而贝叶斯公式给出了一个借助概率来处理这些噪声与误差进而解决逆问题的方法,这便是它的伟大之处。那么问题来了,贝叶斯公式中的$P(Y)$如何求解呢?这时候就用到全概率公式了:假设{$B_n:n=1,2,3...$}是一个概率空间的有限或者可数无限的分割(即$B_n$为一个完备事件组),且每个集合$B_n$是一个可测集合,则对任意事件$A$有全概率公式:$$ P(A) = \sum_n P(AB_n) $$再结合条件概率公式,全概率公式又可写作:$$ P(A) = \sum_n P(A|B_n)P(B_n) $$全概率公式的意义在于将一复杂事件A的概率求解问题转化为了在不同情况或不同原因$B_n$下发生的简单事件的概率的求和问题。所以由全概率公式可知:$$ P(Y) = \sum_j P(Y|X_j)P(X_j) $$从而有:$$ P(X_i|Y) = \frac{P(Y|X_i)P(X_i)}{\sum_j P(Y|X_j)P(X_j)} $$上面是比较一般化的情况,$X_j$是事件集合里面的部分集合,所有的$X_j$构成一个完备事件组。如果我们将原因划分为$X$和$\lnot X$(非$X$,即$X$的补集),这样上述公式可以简写为:$$ P(X|Y) = \frac{P(Y|X)P(X)}{P(Y|X)P(X) + P(Y|\lnot X)P(\lnot X)} $$这也是贝叶斯公式的扩展形式。朴素贝叶斯公式推导朴素贝叶斯(下文简称NB)是监督学习里面的一种分类算法,使用场景为:给定训练集$(X, Y)$,其中每个样本$x$都包含$n$维特征,即$x=(x_1,x_2,...,x_n)$,标签集合含有$k$种类别,即$y=(y_1,y_2,...,y_k)$。如果现在来了一个新的样本$x$,我们如何判断它属于哪个类别?NB的解决思路是:从概率的角度看,这个问题就是给定x,它属于哪个类别的概率最大。那么问题就转化为求解$P(y_1|x),P(y_2|x),...,P(y_k|x)$中最大的那个,即求后验概率最大的那个:$$ \mathop{argmax}_{y} P(y|x) $$所以朴素贝叶斯分类器就是求上式的最大值,根据贝叶斯公式将上面式子展开后如下:$$ P(y|x_1,...,x_n) = \frac{P(y)P(x_1,...,x_n|y)}{P(x_1,...,x_n)} $$可以看到,上面的式子中:$P(y)$是先验概率,可以直接根据数据集计算出来。$P(x_1,...,x_n)$如何计算?$P(x_1,...,x_n|y)$如何计算?首先来看$P(x_1,...,x_n|y)$的计算。这里需要用到条件联合概率分解公式:$P(XY|Z) = P(X|YZ)P(Y|Z)$$P(UVWX|YZ) = P(U|VWXYZ)P(V|WXYZ)P(W|XYZ)P(X|YZ)P(Y|Z)$利用条件联合概率分解公式:$$ P(x_1,...,x_n|y) = P(x_1|x_2,...,x_n,y)P(x_2|x_3,...,x_n,y)...P(x_n|y) $$上面式子很难计算,所以NB理论对条件概率分布做了独立性假设,就是在我们的场景下各个维度特征$x_1,...,x_n$相互独立(这就是朴素贝叶斯中“朴素”二字的含义),即:$$ P(x_i|y,x_1,...,x_{i-1},x_{i+1},...,x_n) = P(x_i|y) $$有了上面的假设,之前那个难计算的公式就好计算了:$$ \begin{align} P(x_1,...,x_n|y) &= P(x_1|x_2,...,x_n,y)P(x_2|x_3,...,x_n,y)...P(x_n|y)\\ &=P(x_1|y)P(x_2|y)...P(x_n|y)\\ &= \frac{P(y)\prod_{i=1}^n P(x_i|y)}{P(x_1,...,x_n)} \end{align} $$这样,最初的式子就变为:$$ P(x_1,...,x_n|y) = \frac{P(y)\prod_{i=1}^n P(x_i|y)}{P(x_1,...,x_n)} $$对于确定的输入,不论$y$取哪个值,${P(x_1,...,x_n)}$都是一样的,所以$$ P(x_1,...,x_n|y) \propto P(y)\prod_{i=1}^n P(x_i|y) $$所以新样例$x=(x_1,...,x_n)$所属的类别$y$的计算公式为:$$ \hat y= \mathop{argmax}_{y} P(y|x)\prod_{i=1}^n P(x_i|y) $$所以,NB分类器最后就是求先验概率$P(y)$和$x_i$的条件概率$P(x_i|y)$,这两个概率都是可以计算的。NB共有三个模型:多项式模型高斯模型伯努利模型多项式和伯努利模型是处理离散型数据的,高斯模型是处理连续型数据的。多项式模型当特征是离散的时候,使用多项式模型。多项式模型在计算先验概率$P(y)$和条件概率$P(x_i|y)$的时候会做一些平滑处理:$$ P(y) = \frac{N_y+\alpha}{N+k\alpha} $$其中,$N$是样本总数,$k$是类别总数,$N_y$是类别为y的样本个数,$\alpha$是平滑值。$$ P(x_i|y) = \frac{N_{y,x_i}+\alpha}{N_y+n\alpha} $$其中,$N_{y,x_i}$是类别为$y$的样本中,第$i$维特征的值是$x_i$的个数,$n$是第$i$维特征的个数,其它同上。当$\alpha=1$时,称作Laplace平滑(常用);$0<\alpha<1$时,称作Lidstone平滑;$\alpha=0$时不做平滑。如果不做平滑,当某一维特征的值$x_i$没在训练样本中出现过时,会导致$P(x_i|y)=0$,从而导致后验概率为0,加上平滑可以克服这个问题。一般比较常用的是Laplace平滑。下面看一个例子:有如下训练数据,15个样本,2维特征$X^{(1)}和X^{(2)}$,2种类别-1和1.给定测试样本$x=(2,S)^T$,运用多项式模型(取$\alpha=1$),判断其类别。序号$X^{(1)}$$X^{(1)}$$Y$11S-121M-131M141S151S-162S-172M-182M192L1102L1113L1123M1133M1143L1153L-1根据公式,我们需要计算先验概率和各种条件概率(注意做了Laplace平滑):先验概率:$P(Y=1)=\frac{10}{17}, P(Y=-1)=\frac{7}{17}$各种条件概率:$P(X^{(1)}=1|Y=1)=\frac{3}{12}, P(X^{(1)}=2|Y=1)=\frac{4}{12}, P(X^{(1)}=3|Y=1)=\frac{5}{12}$$P(X^{(2)}=S|Y=1)=\frac{2}{12}, P(X^{(2)}=M|Y=1)=\frac{5}{12}, P(X^{(2)}=L|Y=1)=\frac{5}{12}$$P(X^{(1)}=1|Y=-1)=\frac{4}{9}, P(X^{(1)}=2|Y=-1)=\frac{3}{9}, P(X^{(1)}=3|Y=-1)=\frac{2}{9}$$P(X^{(2)}=S|Y=-1)=\frac{4}{9}, P(X^{(2)}=M|Y=-1)=\frac{3}{9}, P(X^{(2)}=L|Y=-1)=\frac{2}{9}$对于给定的$x=(2,S)^T$,根据NB公式计算:属于1类别的概率:$P(Y=1)P(X^{(1)}=2|Y=1)P(X^{(2)}=S|Y=1)=\frac{10}{17}\frac{4}{12}\frac{2}{12}=\frac{5}{153}\approx0.0327$属于-1类别的概率:$P(Y=-1)P(X^{(1)}=2|Y=-1)P(X^{(2)}=S|Y=-1)=\frac{7}{17}\frac{3}{9}\frac{4}{9}=\frac{28}{459}\approx0.0610$所以,显然对于特征$x=(2,S)^T$,应该属于-1类别。高斯模型当特征是连续变量的时候,运用多项式模型就会导致很多$P(x_i|y_k)=0$(不做平滑的情况下),此时即使做平滑,所得到的条件概率也难以描述真实情况。所以处理连续型特征变量,一般采用高斯模型:$$ P(x_i|y) = \frac{1}{\sqrt {2\pi\sigma^2_y}}exp(-\frac{(x_i-\mu_y)^2}{2\sigma^2_y}) $$其中,$\mu_y$和$\sigma^2_y$分别标识类别为$y$的样本中,第$i$维特征的均值和方差。看一个例子:已知如下一些样本数据:性别身高(英尺)体重(磅)脚掌(英寸)男618012男5.9219011男5.5817012男5.9216510女51006女5.51508女5.421307女5.751509现在问题来了:已知某人身高6英尺,体重130磅,脚掌8英寸,请问该人是男是女?这里$P(男)=P(女)=0.5$,依据NB分类器,我们还需计算:$$ P(身高|性别)P(体重|性别)P(脚掌|性别)P(性别) $$但可以看到,由于身高、体重、脚掌都是连续变量,不能采用离散变量的方法计算概率。而且由于样本太少,也无法分成区间计算。怎么办?可以假设男性和女性的身高、体重、脚掌都服从正太分布,通过样本计算出均值和方差,也就是的到正态分布的概率密度函数。有了概率密度函数,就可以把值带入,算出某一点密度函数的值。比如男性的身高是均值为5.855,方差为0.035的正太分布。所以,男性的身高为6英尺的概率的相对值等于1.5789(大于1并没有关系,因为这里是密度函数的值,只用来反映各个值的相对可能性)。$$ P(身高|性别)=\frac{1}{\sqrt {2\pi\sigma^2_y}}exp(-\frac{(6-\mu_y)^2}{2\sigma^2_y}) \approx 1.5789 $$对于脚掌和体重同样可以计算均值和方差(计算过程省略)。有了这些数据以后,就可以计算性别的分类了:是男性的概率:$P(身高=6|男)P(体重=130|男)P(脚掌=8|男)P(男) \approx 6.1984 \times 10^{-9}$是女性的概率:$P(身高=6|女)P(体重=130|女)P(脚掌=8|女)P(女) \approx 5.3778 \times 10^{-4}$可以看到,女性的概率比男性要高出将近10000倍,所以判断该人为女性。伯努利模型与多项式模型一样,伯努利模型适用于离散特征的情况,所不同的是,伯努利模型中每个特征的取值只能是1或0(以文本分类为例,某个单词在文档中出现过,则其特征值为,否则为0)。伯努利模型中,条件概率$P(x_i|y)$的计算方式时:当特征值$x_i=1$时,$P(x_i|y)=P(x_i=1|y)$当特征值$x_i=0$时,$P(x_i|y)=1-P(x_i=1|y)$总结总结一下重点:贝叶斯公式的重大意义在于提供了一种使用概率解决逆问题的方法。朴素贝叶斯的朴素指的是假定各个特征之间是相互独立的,从而简化了计算过程。PS:打公式好累,真是痛并快乐着... 常见线性回归模型 http://niyanchun.com/linear-regression.html 2018-05-27T17:26:00+08:00 线性回归公式线性回归公式:$$ { \hat y=\omega_0+\omega_1x_1+...+\omega_px_p } $$说明:数学上,我们把$\omega=(\omega_1,...,\omega_p)$称为系数(coefficient),$\omega_0$称为截距(intercept)。在机器学习里面,$y$是我们要预测的目标变量,$x_i$代表每个特征变量。$y$上面的小标记(hat)表示式子右边是对左边的最佳估计。上面的式子也可以表示为向量形式:$\hat y=X\omega$。这里的线性回归方程是超平面的线性回归方程。所以线性回归的模型很简单,就是一个超平面方程,接下来需要做的就是根据已知的数据(即已知$y_i$和$x_i$)来求系数$\omega_i$和截距$\omega_0$。根据求解方式(称为目标函数或损失函数)的不同,产生了很多线性模型。普通最小二乘法最常见的线性模型就是普通最小二乘法(Ordinary Least Squares, OLS)模型,即最小化预测值和真实值之间的差,为了避免正负值相互抵消,使用差的平方和。也就是说OLS的目标函数如下:$$ { \underset{{\omega}}min||X\omega-y||_2^2 } $$对$\omega$求导并令其等于0得到:$$ { \hat \omega=(X^TX)^{-1}X^Ty } $$这样我们便得出了$\omega$的一种计算方法。不过从式子中可以看到需要对矩阵求逆,所以OLS只适用于矩阵存在逆矩阵的情况。也就是说OLS要求模型的各个特征之间要是相互独立的(且必须是方阵,因为只有方阵可能有逆矩阵,即样本点一定不能小于特征数),否则矩阵将会是奇异的(singular:一个$n*n$矩阵若不存在乘法逆元,则称为奇异的)。岭回归简单来说,岭回归(Ridge regression)就是在矩阵$X^TX$上加了一个$\alpha{I}$从而使得矩阵非奇异,进而能对$X^TX+\alpha{I}$求逆,这样就解决了OLS所存在的问题。因为引入的单位矩阵$I$对角线上面值为1,其它值为0,像一道“岭”一样,因此得名。岭回归的目标函数为:$$ { \underset{\omega}min(||X\omega-y||_2^2+\alpha{||\omega||_2}^2) } $$通过引入惩罚项(penalty)来减少不重要的参数,这种技术在统计学里面称为缩减(shrinkage)。维基百科里面的描述如下:In regression analysis, a fitted relationship appears to perform less well on a new data set than on the data set used for fitting.[1] In particular the value of the coefficient of determination 'shrinks'. This idea is complementary to overfitting and, separately, to the standard adjustment made in the coefficient of determination to compensate for the subjunctive effects of further sampling, like controlling for the potential of new explanatory terms improving the model by chance: that is, the adjustment formula itself provides "shrinkage." But the adjustment formula yields an artificial shrinkage, in contrast to the first definition.岭回归里面就是通过引入$\alpha$(≥0)来限制所有$\omega$的和,$\alpha$值非常小时,系数与普通回归一样;$\alpha$非常大时,所有回归系数缩减为0(缩减的量达到最大),一般我们需要在中间某处找到使得预测结果最好的$\alpha$的值。代码演示如下(代码来自here):import numpy as np import matplotlib.pyplot as plt from sklearn import linear_model # X is the 10x10 Hilbert matrix X = 1. / (np.arange(1, 11) + np.arange(0, 10)[:, np.newaxis]) y = np.ones(10) # ############################################################################# # Compute paths n_alphas = 200 alphas = np.logspace(-10, -2, n_alphas) coefs = [] for a in alphas: ridge = linear_model.Ridge(alpha=a, fit_intercept=False) ridge.fit(X, y) coefs.append(ridge.coef_) # ############################################################################# # Display results ax = plt.gca() ax.plot(alphas, coefs) ax.set_xscale('log') ax.set_xlim(ax.get_xlim()[::-1]) # reverse axis plt.xlabel('alpha') plt.ylabel('weights') plt.title('Ridge coefficients as a function of the regularization') plt.axis('tight') plt.show()运行结果:Lasso回归Lasso全称为The Least Absolute Shrinkage and Selection Operator。其原理和岭回归类似,不过其惩罚项采用L1范数(L1-norm):$$ { \underset{{\omega}}min(\frac{1}{2n_{sample}}||X\omega-y||_2^2+\alpha{||\omega||_1}) } $$和岭回归相比,Lasso虽然只是将L2范数换为了L1范数,但其产生的影响正好相反,我们将岭回归中的代码简单修改如下:import numpy as np import matplotlib.pyplot as plt from sklearn import linear_model # X is the 10x10 Hilbert matrix X = 1. / (np.arange(1, 11) + np.arange(0, 10)[:, np.newaxis]) y = np.ones(10) # ############################################################################# # Compute paths n_alphas = 200 alphas = np.logspace(-10, -2, n_alphas) coefs = [] for a in alphas: lasso = linear_model.Lasso(alpha=a, fit_intercept=False) lasso.fit(X, y) coefs.append(lasso.coef_) # ############################################################################# # Display results ax = plt.gca() ax.plot(alphas, coefs) ax.set_xscale('log') ax.set_xlim(ax.get_xlim()[::-1]) # reverse axis plt.xlabel('alpha') plt.ylabel('weights') plt.title('Lasso coefficients as a function of the regularization') plt.axis('tight') plt.show()运行结果如下:可见与岭回归正好相反,Lasso中当$\alpha$足够小时,系数会被缩减到0.所以Lasso模型一般偏向于产生比较少的非零系数。Elastic Net回归Elastic Net回归可以说是Ridge和Lasso的并集,其目标函数为:$$ { \underset{{\omega}}min(\frac{1}{2n_{sample}}||X\omega-y||_2^2+\alpha\rho{||\omega||_1}+\frac{\alpha(1-\rho)}{2}||\omega||_2^2) } $$可见Elastic Net同时采用了L1和L2范数作为正则化,这样使得Elastic Net集成了Ridge和Lasso的功能。可以看到,当$\rho=1$的时候,Elastic Net就是Lasso。有一点不同的是,当有多个特征相关时,Lasso会从相关的特征里面随机选一个,而Elastic Net则会都选用。References:scikit-learn 使用Pandas探索数据 http://niyanchun.com/explore-data-with-pandas.html 2018-05-17T01:01:00+08:00 做数据分析时,对数据越了解,越有助于我们去分析,本文介绍如何使用Pandas探索数据。1. Pandas简介Pandas是Python中一个高性能库,提供了非常易用的数据结构以及丰富的函数工具用来做数据分析,所以该库在数据分析领域用的非常广泛。使用Pandas的关键点在于理解Pandas的两个核心数据结构:Series和DataFrame。1.1 SeriesSeries表示带有标签的一维数组(one-dimensional labeled array):数组内的元素可以是任意类型;label称为index,index可以不唯一,但必须是可哈希的类型(hashable type)。构造Series结构最常用的方式就是使用pandas.Series类,参数及说明如下:pandas.Series(data=None, index=None, dtype=None, name=None, copy=False, fastpath=False)data:数组中的数据,参数可以是数组、字典或标量值index:标签或索引,可以重复,但必须是可哈希类型。参数可以是数组或一维的Index类型,长度必须和数据的长度相同;如果数据是字典类型,又显式的传了index,会覆盖字典的key;默认的index是(0, 1, 2, ..., len(data)-1)dtype:numpy.dtype或者None,如果传None的话,会自动检测类型。copy:布尔值,是否拷贝输入的值,默认是False。下面列举几个创建Series的例子。In [1]: import pandas as pd In [2]: import numpy as np # 使用数组创建Series,并指定index In [3]: arr = np.array([1,2,3,4,5]) In [4]: s = pd.Series(arr, index=['a','b','c','d','a']) In [5]: s Out[5]: a 1 b 2 c 3 d 4 a 5 dtype: int64 In [6]: s.index Out[6]: Index(['a', 'b', 'c', 'd', 'a'], dtype='object') # 使用数组创建Series,且使用默认的index In [7]: pd.Series(np.random.randn(5)) Out[7]: 0 -1.316523 1 -1.211854 2 -0.532214 3 -0.143331 4 0.335070 dtype: float64 # 通过字典创建Series In [8]: d = {'b':1, 'a':0, 'c':2} In [9]: pd.Series(d) Out[9]: a 0 b 1 c 2 dtype: int64 # 使用标量创建Series,要注意此时标量重复len(index)次 In [10]: pd.Series(5., index=['a','b','c','d','e']) Out[10]: a 5.0 b 5.0 c 5.0 d 5.0 e 5.0 dtype: float64在时间序列预测中Series数据结构将非常有用。1.2 DataFrameDataFrame和Series类似,但DataFrame是二维的,非常类似于关系型数据库里面的表。我们一般把列标签依旧称为index,而把行标签称为Column,对应的也有着两个参数去指定行列标签。DataFrame对象的构造方法和Series极其类似,参数情况如下:pandas.DataFrame(data=None, index=None, columns=None, dtype=None, copy=False)这里不再赘述了,看以下一些例子:In [15]: d = { 'one': pd.Series([1., 2., 3.], index=['a', 'b', 'c']), ...: 'two': pd.Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])} ...: In [16]: d Out[16]: {'one': a 1.0 b 2.0 c 3.0 dtype: float64, 'two': a 1.0 b 2.0 c 3.0 d 4.0 dtype: float64} In [17]: df = pd.DataFrame(d) In [18]: df Out[18]: one two a 1.0 1.0 b 2.0 2.0 c 3.0 3.0 d NaN 4.0 In [19]: pd.DataFrame(d, index=['d', 'b', 'a']) Out[19]: one two d NaN 4.0 b 2.0 2.0 a 1.0 1.0 In [20]: pd.DataFrame(d, index=['d', 'b', 'a'], columns=['two', 'three']) Out[20]: two three d 4.0 NaN b 2.0 NaN a 1.0 NaN In [21]: df.index Out[21]: Index(['a', 'b', 'c', 'd'], dtype='object') In [22]: df.columns Out[22]: Index(['one', 'two'], dtype='object')注:在Pandas中,缺失值一般用NaN表示。会创建Series和Pandas对象只是学习Pandas的第一步,其实Pandas更加强大的地方在于它在这两个核心数据结构之上提供了非常丰富的函数和方法,让我们可以更专注于数据分析本身。本文的重点不是介绍Pandas,所以Pandas的介绍到此为止,有兴趣的可以看起官方文档:http://pandas.pydata.org/pandas-docs/stable/index.html。里面有一个10分钟的入门教程,对于只想快速了解一下的同学是个非常不错的选择。下面我们言归正传,看一下如何利用Pandas提供的各种工具来探索我们的数据。2. 探索数据数据说明:本次示例所用的数据来自于https://archive.ics.uci.edu/ml/datasets/bank+marketing,是葡萄牙银行做的一次电话调查,旨在判断用户对于行方定期存储产品的认购情况。数据包含了很多列,有些列的值是标量型的,有些是数值型的。本文的重点在于如何利用Pandas工具去探索数据(分布)情况,所以列的含义不是那么重要,这里就不解释了,有兴趣的可以去上面给出的链接处查看,有详细的说明。导入相关的包并读入数据:In [1]: import numpy as np In [2]: import pandas as pd In [3]: import matplotlib.pyplot as plt In [4]: df = pd.read_csv('SampleData/banking.csv')2.1 统计信息1,查看数据基本信息。# head默认显示5条数据,可以指定显示几条 In [5]: df.head() Out[5]: age job marital education default housing loan \ 0 44 blue-collar married basic.4y unknown yes no 1 53 technician married unknown no no no 2 28 management single university.degree no yes no 3 39 services married high.school no no no 4 55 retired married basic.4y no yes no contact month day_of_week ... campaign pdays previous poutcome \ 0 cellular aug thu ... 1 999 0 nonexistent 1 cellular nov fri ... 1 999 0 nonexistent 2 cellular jun thu ... 3 6 2 success 3 cellular apr fri ... 2 999 0 nonexistent 4 cellular aug fri ... 1 3 1 success emp_var_rate cons_price_idx cons_conf_idx euribor3m nr_employed y 0 1.4 93.444 -36.1 4.963 5228.1 0 1 -0.1 93.200 -42.0 4.021 5195.8 0 2 -1.7 94.055 -39.8 0.729 4991.6 1 3 -1.8 93.075 -47.1 1.405 5099.1 0 4 -2.9 92.201 -31.4 0.869 5076.2 1 [5 rows x 21 columns] # 查看数据维度 In [6]: df.shape Out[6]: (41188, 21) # 查看数据类型 In [7]: df.dtypes Out[7]: age int64 job object marital object education object default object housing object loan object contact object month object day_of_week object duration int64 campaign int64 pdays int64 previous int64 poutcome object emp_var_rate float64 cons_price_idx float64 cons_conf_idx float64 euribor3m float64 nr_employed float64 y int64 dtype: object通过查看基本信息,我们可以查看到一些示例数据,以及数据总条数、维度数,各个维度的类型等,这些都是我们探索数据的第一步。2,通过Pandas的describe方法查看一些汇总信息,探索各个维度值的分布情况:In [11]: df.describe() Out[11]: age duration campaign pdays previous \ count 41188.00000 41188.000000 41188.000000 41188.000000 41188.000000 mean 40.02406 258.285010 2.567593 962.475454 0.172963 std 10.42125 259.279249 2.770014 186.910907 0.494901 min 17.00000 0.000000 1.000000 0.000000 0.000000 25% 32.00000 102.000000 1.000000 999.000000 0.000000 50% 38.00000 180.000000 2.000000 999.000000 0.000000 75% 47.00000 319.000000 3.000000 999.000000 0.000000 max 98.00000 4918.000000 56.000000 999.000000 7.000000 emp_var_rate cons_price_idx cons_conf_idx euribor3m \ count 41188.000000 41188.000000 41188.000000 41188.000000 mean 0.081886 93.575664 -40.502600 3.621291 std 1.570960 0.578840 4.628198 1.734447 min -3.400000 92.201000 -50.800000 0.634000 25% -1.800000 93.075000 -42.700000 1.344000 50% 1.100000 93.749000 -41.800000 4.857000 75% 1.400000 93.994000 -36.400000 4.961000 max 1.400000 94.767000 -26.900000 5.045000 nr_employed y count 41188.000000 41188.000000 mean 5167.035911 0.112654 std 72.251528 0.316173 min 4963.600000 0.000000 25% 5099.100000 0.000000 50% 5191.000000 0.000000 75% 5228.100000 0.000000 max 5228.100000 1.000000可以看到该方法默认只计算了数值型维度的5个方面的信息:count:总数mean:均值std:标准差min:最小值25%:第一四分位数或较小四分位数(Q1):等于该样本中所有数值由小到大排列后第25%的数字。50%:第二四分位数或中位数(Q2):等于该样本中所有数值由小到大排列后第50%的数字。75%:第三四分位数或较大四分位数(Q3):等于该样本中所有数值由小到大排列后第75%的数字。max:最大值。当然,对于数值型数据describe方法会统计上面5个方面的信息,对于标称型数据则统计另外4个方面的信息:In [13]: df.describe(exclude=[np.number]) Out[13]: job marital education default housing loan contact \ count 41188 41188 41188 41188 41188 41188 41188 unique 12 4 8 3 3 3 2 top admin. married university.degree no yes no cellular freq 10422 24928 12168 32588 21576 33950 26144 month day_of_week poutcome count 41188 41188 41188 unique 10 5 3 top may thu nonexistent freq 13769 8623 35563这4方面的信息包括:count:总数unique:去重后的个数top:出现频率最高的值freq:出现最高频率的值的频率对于Timestamp类型还会有first和last两个信息。3,通过groupby查看各个标称型维度值的分布情况。describe方法对于探索数值型数据的分布非常有用,但对于标称型数据虽然也有统计,但还不够。往往我们更多的使用groupby方法,该方法与SQL中的groupby类似,这里举几个例子:In [14]: df.groupby('education').size() Out[14]: education basic.4y 4176 basic.6y 2292 basic.9y 6045 high.school 9515 illiterate 18 professional.course 5243 university.degree 12168 unknown 1731 dtype: int64 In [15]: df.groupby('y').size() Out[15]: y 0 36548 1 4640 dtype: int644,有很多算法要求所选取的维度之间要没有相关性,所以探索维度之间的相关性也非常重要。Pandas提供了corr方法可以计算相关系数(默认计算皮尔逊相关系数):In [16]: df.corr() Out[16]: age duration campaign pdays previous \ age 1.000000 -0.000866 0.004594 -0.034369 0.024365 duration -0.000866 1.000000 -0.071699 -0.047577 0.020640 campaign 0.004594 -0.071699 1.000000 0.052584 -0.079141 pdays -0.034369 -0.047577 0.052584 1.000000 -0.587514 previous 0.024365 0.020640 -0.079141 -0.587514 1.000000 emp_var_rate -0.000371 -0.027968 0.150754 0.271004 -0.420489 cons_price_idx 0.000857 0.005312 0.127836 0.078889 -0.203130 cons_conf_idx 0.129372 -0.008173 -0.013733 -0.091342 -0.050936 euribor3m 0.010767 -0.032897 0.135133 0.296899 -0.454494 nr_employed -0.017725 -0.044703 0.144095 0.372605 -0.501333 y 0.030399 0.405274 -0.066357 -0.324914 0.230181 emp_var_rate cons_price_idx cons_conf_idx euribor3m \ age -0.000371 0.000857 0.129372 0.010767 duration -0.027968 0.005312 -0.008173 -0.032897 campaign 0.150754 0.127836 -0.013733 0.135133 pdays 0.271004 0.078889 -0.091342 0.296899 previous -0.420489 -0.203130 -0.050936 -0.454494 emp_var_rate 1.000000 0.775334 0.196041 0.972245 cons_price_idx 0.775334 1.000000 0.058986 0.688230 cons_conf_idx 0.196041 0.058986 1.000000 0.277686 euribor3m 0.972245 0.688230 0.277686 1.000000 nr_employed 0.906970 0.522034 0.100513 0.945154 y -0.298334 -0.136211 0.054878 -0.307771 nr_employed y age -0.017725 0.030399 duration -0.044703 0.405274 campaign 0.144095 -0.066357 pdays 0.372605 -0.324914 previous -0.501333 0.230181 emp_var_rate 0.906970 -0.298334 cons_price_idx 0.522034 -0.136211 cons_conf_idx 0.100513 0.054878 euribor3m 0.945154 -0.307771 nr_employed 1.000000 -0.354678 y -0.354678 1.000000皮尔逊相关系数取值范围为[-1, 1],-1表示(线性)负相关,0表示(线性)无关,1表示(线性)正相关。通过这个(对角)矩阵,我们可以很容易的看出各个属性间的相关性。5,pandas还提供了skew来计算偏度(skew):衡量实数随机变量概率分布的不对称性。偏度的值可以为正,可以为负或者甚至是无法定义。在数量上,偏度为负(左偏态或负偏态,Negative Skew)就意味着在概率密度函数左侧的尾部比右侧的长,绝大多数的值(包括中位数在内)位于平均值的右侧。偏度为正(右偏态或正偏态,Positive Skew)就意味着在概率密度函数右侧的尾部比左侧的长,绝大多数的值(但不一定包括中位数)位于平均值的左侧。如图:偏度为零就表示数值相对均匀地分布在平均值的两侧,但不一定意味着其为对称分布。如果分布对称,那么平均值=中位数,偏度为零(此外,如果分布为单峰分布,那么平均值=中位数=众数)。In [17]: df.skew() Out[17]: age 0.784697 duration 3.263141 campaign 4.762507 pdays -4.922190 previous 3.832042 emp_var_rate -0.724096 cons_price_idx -0.230888 cons_conf_idx 0.303180 euribor3m -0.709188 nr_employed -1.044262 y 2.450330 dtype: float642.2 可视化统计信息虽然非常精确,但看上去并不是非常直观,我们可以通过一些可视化的手段来非常直观的探索数据分布。1,直方图(histogram):In [20]: %matplotlib Using matplotlib backend: MacOSX In [21]: df.hist(figsize=(12, 8))2,密度图(density plot):In [22]: df.plot(kind='density', subplots=True, figsize=(12,8), layout=(3,4), sharex=False)3,箱型图(boxplots):In [23]: df.plot(kind='box', subplots=True, figsize=(12,8), layout=(3,4), sharex=False, sharey=False)箱型图的含义参见:箱型图介绍。4,相关系数矩阵图:corr = df.corr() figure = plt.figure(figsize=(12,8)) ax = figure.add_subplot(111) cax = ax.matshow(corr, vmin=-1, vmax=1) figure.colorbar(cax) plt.show() # 0~10,依次对应age~y5,通过散点矩阵图(scatter matrix)查看各维度相关性:from pandas.plotting import scatter_matrix scatter_matrix(df, figsize=(18, 12))关于scatter matrix需要解释一下,和上面的相关系数矩阵图类似,该图也描述了各维度间的相关性。而对角线上是各个维度自己与自己的相关性,显然都是完全相关的,也就是一条直线(皮尔逊相关系数为1),这没什么用。所以Pandas就把对角线上面的改为展示维度的密度图了。这里再附上皮尔逊相关系数的值与其图形的关系(图片来自皮尔逊相关系数维基百科):以及一个比较泛(不是非常的严格和通用)的判断相关性强弱的标准:相关性负正无-0.09~0.00.0~0.09弱-0.3~-0.10.1~0.3中-0.5~-0.30.3~0.5强-1.0~-0.50.5~1.0可视化可以帮助我们“定性的”了解数据,统计信息则帮助我们“定量的”了解数据,二者结合可以使我们更加了解自己的数据,从而选择下一步的数据处理流程。除了本文已经介绍的之外,Pandas还有非常非常多的工具帮助我们分析数据,可以查看其文档慢慢积累。本文源码见这里。References:维基百科(具体各个链接见博客内容)。Pandas官方文档.Machine Learning Mastery with Python. confusion matrix,precision,recall,F1-score,ROC,AUC,p-value概念总结 http://niyanchun.com/evaluation-metrics-for-a-classification-model.html 2018-01-17T21:55:00+08:00 机器学习中二元分类模型是非常常见的,也有很多的算法模型,今天我们来简单汇总一下评估二分类模型性能的一些方法,当然有些不局限于二分类模型。这里的简单有两层含义:只汇总了一部分,并不全面。很多评估方式都来在统计学理论,而且有一些还是存在争论的(比如p-value),所以这里不是详细讲解每种评估方法,而是做简单说明,让你知道每个值表示的含义,从而知道自己的模型性能如何。或者说当你使用一些机器学习框架输出了模型的一些性能相关的描述时,你知道每一项代表什么含义。为了保证一致性,术语类的优先采用原始英文,不做翻译。Positive & Negative所谓二分类,就是指预测的目标值为1或0,用来代表“是/否”、“成功/失败”等。对应的,我们可以把1称为Positive(P, 阳性),0称为Negative(N, 阴性)。当然也可以反过来,由你具体的研究决定。比如你的分类器是判断一个人是不是男性,那此时男性就是Positive,非男性就是Negative;如果你的分类器是判别一个人是不是女性,那此时女性就是Positive,非女性就是Negative。这里我们用一个例子来具体介绍,假设现在有27个动物:8只猫,6只狗,13只兔子。我们现在有一个分类模型,该模型可以区分出一个动物是否为猫。此时:Positive:猫,猫的数量等。Negative:非猫,非猫的数量等。TP(true positive, 真阳性):本来是猫,分类器预测出来也是猫。此时我们说命中了(hit).TN(true negative, 真阴性):本来不是猫,分类器预测出来也不是猫。此时我们称正确拒绝(correct rejection)。FP(false positive, 伪阳性):本来不是猫,分类器预测出来是猫。这种我们称为错误的肯定,或假警报(false alarm),也称为第一类型错误(Type I error)。FN(false negative, 伪阴性):本来是猫,分类器预测出来不是猫。这种我们称为错误的否定,或未命中(miss),也称为第二类型错误(Type II error)。好,这里已经涉及了一些术语了,有些后面会再介绍,我们接着看。TPR(true positive rate, 真阳性率),又称命中率(hit rate)、敏感度(sensitivity)、召回率(recall),计算公式为:$$TPR=\frac{TP}{P}=\frac{TP}{TP+FN}$$TNR(true negative rate, 真阴性率),又称特异度(specificity, SPC),计算公式为:$$TNR=\frac{TN}{N}=\frac{TN}{FP+TN}$$PPV(positive predictive value, 阳性预测值),又称精度(precision),计算公式为:$$PPV=\frac{TP}{TP+FP}$$NPV(negative predictive value, 阴性预测值),计算公式为:$$NPV=\frac{TN}{TN+FN}$$FNR(false negative rate, 伪阴性率),又称未命中率(miss rate),计算公式为:$$FNR=\frac{FN}{P}=\frac{FN}{FN+TP}=1-TPR$$FPR(false positive rate, 伪阳性率),又称错误命中率,误检率(fall-out rate),假警报率(false alarm rate),其计算公式为:$$FPR=\frac{FP}{N}=\frac{FP}{FP+TN}=1-TNR$$FDR(false discovery rate, 假发现率),计算公式为:$$FDR=\frac{FP}{FP+TP}=1-PPV$$FOR(false ommission rate, 假错过率),计算公式为:$$FOR=\frac{FN}{FN+TN}=1-NPV$$ACC(accuracy, 准确率),计算公式为:$$ACC=\frac{TP+TN}{P+N}=\frac{TP+TN}{TP+TN+FP+FN}$$$F_1$ score($F_1$-measure),是precision和recall的调和平均数,计算公式为:$$F_1=\frac{2}{\frac{1}{precision}+\frac{1}{recall}} =2*\frac{PPV*TPR}{PPV+TPR}=\frac{2TP}{2TP+FP+FN}$$缩略词和公式有点多,当然实际中我们在同一个模型里面往往不会关注这么多,会根据应用场景关注几个比较在意的维度。比如ACC应该是我们经常比较关注的,但是也不能只关注ACC,因为ACC高往往并不一定能说明我们的分类器性能就好,这和数据是否balance有关系。比如我们有100个测试样本,里面有99只猫,1只狗。那现在有个分类器,不论任何输入,输出都是猫,那在这个测试集上面,ACC高达99%,但显然这种分类器不是我们想要的。上面的这种情况我们称数据集是unbalanced,即数据集中各个观测值的个数变化很大。confusion matrixconfusion matrix即混淆矩阵,又称error matrix(错误矩阵),主要用于以可视化的方式衡量监督学习中算法模型的性能(在非监督学习中对应的概念是matching matrix)。混淆矩阵中行代表每个类的预测值,列代表实际值,当然也可以反过来。以上节中猫的预测为例,其可能的一个混淆矩阵如下(图片来自维基百科,链接见最后面):如上图,可以看出来对于混淆矩阵而言,对角线上的预测都是正确的,其他都是错的的,非常直观。实际中,如果是二值的,也就是说只有两类,比如上面的例子,我们也可以划归为猫和非猫两类,这样我们就得到了confusion table(有时也称为混淆矩阵,图片来自维基百科,链接见最后面):可以看到,混淆表只有两行两列,且分别代表了TP、FP、FN、TN的个数。也就是说我们能从多个维度去看分类器的性能,而不只是准确率ACC。precision & recall & F1-score前面我们已经看了precision(PPV)和recall(TPR)的公式,那他们的实际含义是什么呢?这两个概念在模式识别、信息检索、二分类里面经常使用,用于衡量相关性(Relevance)。这里举维基百科上面的两个例子,第一个例子主要说明如何计算precision和recall,第二个例子主要说明他们代表的实际意义。例子一:现在有一幅画,上面画了12个狗和若干只猫。有一个识别狗的程序识别出来该画上面有8个狗,而识别出来的8只里面实际只有5条是狗,其它3个实际是猫,那此时precision就是5/8,而recall是5/12。这个根据我们前面介绍的公式也很容易算出来。例子二:现在搜索引擎根据我们搜索的关键字返回了30个相关的页面,但这30个页面里面实际只有20个页面是和我们的关键字相关的,另外10个是不相关的。但其实真正相关的页面除了刚才的20个以外,还有40个,只是搜索引擎没有检索到。那此时,precision就是20/30=2/3,recall是20/60=1/3。那在这个场景中,precision表征了返回结果的有用程度是多少,或者说和我们的关键字的相关程度有多高;recall则表征了返回的结果的完整程度,即所有相关的东西实际只返回了多少。再来一张维基百科上面的图:左半边的矩形区域内都是P(实心点),右半边矩形区域都是N(空心点);这里P表示相关,N表示不相关。FN表示预测不相关,但实际相关;TP表示预测相关,实际也相关;TN表示预测不相关,实际也不相关;FP表示预测相关,实际不相关;也即图中的四个区域。图中圆内的东西表示选择的元素,也即上面例子二中搜索引擎返回的页面,此时precision用来评价返回的页面里面有多少是真正相关的,recall评估返回的页面数是否完整/完全,或者说返回了多少个相关的页面。也就是说:precision是对精确程度或者质量(quality)的评价,recall是对完整程度或者数量(quantity)的评价。我们再看下前面的例子一,里面有多少个第一类错误和第二类错误呢?第一类错误FP指本来不是,但预测是,例子中FP=8-5=3,因为预测的8个里面有3个不是狗,但预测成狗了。第二类错误FN指本来是,但预测不是,例子中FN=12-5=7,因为总共12个狗,只有5个预测成狗了,7个miss掉了。我们再看下precision和recall的公式就可以得到以下结论:第一类错误决定了precision所能取到的最大值。如果第一类错误个数FP为0,那precision将达到最大值1,即上面例子中找出来的8个都是狗。第二类错误决定了recall所能取到的最大值。如果第二类错误个数为FN=0,那recall将达到最大值1,即上面例子中找到了全部12条狗。那这个结论什么意义呢?实际中几乎没有完美的模型,一个模型要么会“过度识别”,比如把不是狗的也识别成狗了;要么就会“欠识别”,本来是狗,却没识别出来,这两种情况也就对应上面的第一类错误和第二类错误,那我们总要有所取舍。一般情况下,我们会保证第一类错误不超过某个阈值的情况下,尽可能的减小第二类错误。因为往往过度识别经过再次确认可以发现问题,比如心脏病检查,不是心脏病的误识别为心脏病(第一类错误)最多就是再做一次复诊确认一下而已,但如果漏识别了(第二类错误),那就是人命关天的大事了。当然这也不是绝对的,也需要根据我们的实际场景,看我们能够容忍哪种情况来决定。最后,我们看一下$F_1 score$,$F_1 score$又称$F_1 measure$,是precision和recall的加权平均,当然$F_1$是$F_\beta$的特殊形式,表示precision和recall的权值相等。$F_1 score$也是对模型性能评估的一种方式,最佳值为1(precision和recall都是最佳状态,即值为1),最差情况为0.ROC & AUCROC和AUC在维基百科的中文版里面已经讲的比较清晰明了了,这里就不再赘述了,传送门:https://zh.wikipedia.org/wiki/ROC%E6%9B%B2%E7%BA%BF。个人觉得,主要明白以下两点就行:在一个ROC曲线中,曲线越靠近左上角,分类效果越理想。左上角(0.0,1.0)是理想最佳的位置。此时,我们的分类器既不会错分(横轴FPR为0),也不会漏分(纵轴TPR为1)。如果是垃圾邮件分类器的话,那就是过滤掉了所有的垃圾邮件,也没有将正常的邮件划分为垃圾邮件。在不同的ROC曲线中(取不同的阈值得到了多个ROC曲线),AUC值越大,该分类器正确率越高,相比于其他的ROC曲线更优。p-value要了解p-value,首先得知道什么是假设检验(Hypothesis Testing),什么又是显著性假设检验?一般而言,我不推荐在百度百科上面看专业知识,但关于假设检验的概念不得不说,百度百科上面的讲解还算不错,这里就不重复造轮子了,不了解假设检验的,先去这里:假设检验百度百科传送门。OK,如果你之前不懂,看了没看懂,那我就简单做一个不是那么准确的解释:简单说就是现在有一个问题,我们不知道真假,那怎么办?那我就先做一个假设,这个假设专业术语叫null hypothesis(一般翻译为零假设,记做$H_0$),然后零假设的对立面就叫alternative hypothesis(一般翻译为备择假设,记做$H_1$)。现在我们的任务就是要通过实验来确定我们是要接受这个$H_0$假设还是拒绝它(拒绝$H_0$假设就标识接受了$H_1$假设)。那如何确定是接受还是拒绝呢?其原理就是(1)反证法(2)小概率事件在一次实验中不会发生。举一个例子:我们现在要确定一枚硬币是否均匀,一般正常的硬币都是均匀的,所以我们先做出一个$H_0$假设:硬币是均匀的(即抛一次正面出现的概率为0.5)。然后我们开始做实验,结果我们抛了10次硬币(抛硬币遵从二项分布),有8次是正面,2次是反面。那如果硬币是均匀的,出现这个结果的概率为$$p=\binom{10}{8}0.5^8(1-0.5)^2=0.044$$注:可使用python scipy里面提供的二项分布函数计算:from scipy.stats import binom binom.pmf(n=10,k=8,p=0.5) # 0.043945312499999993那现在问题来了,如果原假设是正确的,即硬币是均匀的,那现在这个结果出现的概率应该是比较小的(0.04),可是它的确在一次实验中就出现了。我们是选择相信这是一次偶然事件呢?还是说我们刚开始的$H_0$假设(硬币是均匀的)是错误的呢?这时,就有一个显著性水平(significance level, 记为$\alpha$),的概念了。我们一般会选取一个显著性水平值,一般这个值取0.05或0.01(因为一般定义发生的概率小于0.05或0.01的事件为小概率事件)。如果上面计算出来的那个0.044的概率(也就是p-value)大于这个$\alpha$,我们就认为这次事件是一次偶然事件,我们依旧接受之前做的假设(即认为假设是对的);如果p-value小于$\alpha$,我们就认为小概率事件在一次实验中发生了,但这是不可能的,所以我们就认为出现这种结果的原因是因为我们刚开始做的假设是不对的,所以拒绝之前的零假设,选择接受备择假设。这也就是我们经常会看到p-value和0.05做比较的原因。这里我们需要注意一下,一般我们做假设的时候都选择对立面,比如我们想要判断两个随机变量是否相关,那我们的零假设就是它们不相关,然后根据上面的过程,如果拒绝了零假设,那就说明他们有很大概率是相关的,但是这个结论是通过反证法得到的,而不是我们直接证明了他们是相关的。再比如我们要证明两个随机变量是独立的,那我们就先做出假设他们不是相互独立的,然后最后如果通过实验拒绝了这个假设,那就说明它们是相互独立的了。也就是说显著性检验的原理就是我们虽然无法证明一个命题是真的,但是我们可以证明它的对立面是假的,证明的手段就是反证法+小概率事件在一次实验中不可能发生。最后我们再说一下前面提到第一类错误和第二类错误。在显著性假设检验中,如果$H_0$(原假设、零假设)为真,但实验结果却告诉我们不可能,导致我们拒绝了零假设,那这种错误就是之前说的第一类错误,一般把第一类错误发生的概率记为$\alpha$。如果$H_0$(原假设、零假设)为假,而实验结果却告诉我们原来的假设是正确的,导致我们接受了零假设,这种错误就是我们之前说的第二类错误,一般把第二类错误发生的概率记为$\beta$。在显著性假设中,我们一般会限定一个发生第一类错误的最大值,而这个值就是我们之前说的显著性水平(现在知道为什么那里记为$\alpha$了吧)。如果你还想更加深入的了解,推荐知乎上面的一个问题:统计学假设检验中 p 值的含义具体是什么?好吧,扯了那么多。其实对很多人来说,只要知道你的模型结果中的p-value表示什么含义就行。举个例子,很多分类算法就是根据样本求出一个线性方程的各个系数(比如Logistic Regression分类算法)。那最终的输出结果里面除了有系数外,可能每个系数还会对应一个p值。那其实是算法做了一个零假设:该系数(实质反映的是该系数对应的那个特征)和最终要预测的目标分类是没有关系的(即系数为0)。那如果对应的p-value小于0.05,那我们就可以决绝原假设,认为该系数和目标变量是有关系的。还是有点抽象哈,再举个例子说明下上面这个:比如现在我们选取了一些人的特征:年龄(x1)、性别(x2)、职业(x3)、身高(x4),现在我们要预测这个人是否有车,即目标变量是否有车。那如果使用Logistic Regression算法的话,我们就是要得到一个$$y=\omega_0+\omega_1x_1+\omega_2x_2+\omega_3x_3+\omega_4x_4$$中的系数($\omega_1$~$\omega_4$)以及截距$\omega_0$。那通过一些库提供的API计算出来系数以后,我们会发现年龄和职业两个特征对应的系数的p-value应该是小于0.05的,也就是说假设这两个特征对目标变量(是否有车)是没有影响的是不可以接受的,也就是说他们对目标变量有影响。而性别和身高这两个特征对应的系数的p-value很可能是大于0.05的,所以我们接受他们和目标变量没有关系的假设(原假设),实际中性别和身高的确和一个人有没有车关系不大。也就是说我们在做模型的时候最好就不要选这种特征了。不知道这样举例是不是稍微清楚了点。当然,评测一个分类模型性能还有很多其他维度,后续再更新。loss和val_loss注:该部分增加于2020.2.21日在深度学习中,经常还会看到用loss、val_loss(与之对应的可能还会有acc和val_acc)来评价模型(当然一般是在训练过程中输出)的优劣,那它们分别代表什么含义呢?这里的val是validation的缩写,我们训练模型的时候一般会将数据分为训练集和测试集,这里的validation就指的是测试集。所以loss和acc都指的是模型在训练数据上面的表现,val_loss和val_acc则是模型在训练集上的表现,loss和val_loss都指的是损失函数(cost function或loss function)的值。如果一个模型的loss逐渐下降、收敛,但val_loss却升高、不收敛,则说明很可能过拟合了,只有当loss和val_loss同时下降、收敛,才能说明模型的泛化能力比较好。References:https://en.wikipedia.org/wiki/Confusion_matrixhttps://en.wikipedia.org/wiki/Precision_and_recall