0%

第二章 机器学习基础

​ 机器学习起源于上世纪50年代,1959年在IBM工作的Arthur Samuel设计了一个下棋程序,这个程序具有学习的能力,它可以在不断的对弈中提高自己。由此提出了“机器学习”这个概念,它是一个结合了多个学科如概率论,优化理论,统计等,最终在计算机上实现自我获取新知识,学习改善自己的这样一个研究领域。机器学习是人工智能的一个子集,目前已经发展出许多有用的方法,比如支持向量机,回归,决策树,随机森林,强化方法,集成学习,深度学习等等,一定程度上可以帮助人们完成一些数据预测,自动化,自动决策,最优化等初步替代脑力的任务。本章我们主要介绍下机器学习的基本概念、监督学习、分类算法、逻辑回归、代价函数、损失函数、LDA、PCA、决策树、支持向量机、EM算法、聚类和降维以及模型评估有哪些方法、指标等等。

2.1 基本概念

2.1.1 大话理解机器学习本质

​ 机器学习(Machine Learning, ML),顾名思义,让机器去学习。这里,机器指的是计算机,是算法运行的物理载体,你也可以把各种算法本身当做一个有输入和输出的机器。那么到底让计算机去学习什么呢?对于一个任务及其表现的度量方法,设计一种算法,让算法能够提取中数据所蕴含的规律,这就叫机器学习。如果输入机器的数据是带有标签的,就称作有监督学习。如果数据是无标签的,就是无监督学习。

2.1.2 什么是神经网络

​ 神经网络就是按照一定规则将多个神经元连接起来的网络。不同的神经网络,具有不同的连接规则。例如全连接(Full Connected, FC)神经网络,它的规则包括:

(1)有三种层:输入层,输出层,隐藏层。

(2)同一层的神经元之间没有连接。

(3)fully connected的含义:第 N 层的每个神经元和第 N-1 层的所有神经元相连,第 N-1 层神经元的输出就是第 N 层神经元的输入。

(4)每个连接都有一个权值。

神经网络架构
​ 图2-1就是一个神经网络系统,它由很多层组成。输入层负责接收信息,比如一只猫的图片。输出层是计算机对这个输入信息的判断结果,它是不是猫。隐藏层就是对输入信息的传递和加工处理。
图2-2 神经网络系统

​ 图2-1 神经网络系统

2.1.3 各种常见算法图示

​ 日常使用机器学习的任务中,我们经常会遇见各种算法,图2-2是各种常见算法的图示。

回归算法 聚类算法 正则化方法
决策树学习 贝叶斯方法 基于核的算法
聚类算法 关联规则学习 人工神经网络
深度学习 降低维度算法 集成算法

​ 图2-2 各种常见算法图示

2.1.4 计算图的导数计算

​ 计算图导数计算是反向传播,利用链式法则和隐式函数求导。

​ 假设 $z = f(u,v)$ 在点 $(u,v)$ 处偏导连续,$(u,v)$是关于 $t$ 的函数,在 $t$ 点可导,求 $z$ 在 $t$ 点的导数。

根据链式法则有

​ 链式法则用文字描述:“由两个函数凑起来的复合函数,其导数等于里边函数代入外边函数的值之导数,乘以里边函数的导数。
​ 为了便于理解,下面举例说明:

​ 则:

2.1.5 理解局部最优与全局最优

​ 笑谈局部最优和全局最优

​ 柏拉图有一天问老师苏格拉底什么是爱情?苏格拉底叫他到麦田走一次,摘一颗最大的麦穗回来,不许回头,只可摘一次。柏拉图空着手出来了,他的理由是,看见不错的,却不知道是不是最好的,一次次侥幸,走到尽头时,才发现还不如前面的,于是放弃。苏格拉底告诉他:“这就是爱情。”这故事让我们明白了一个道理,因为生命的一些不确定性,所以全局最优解是很难寻找到的,或者说根本就不存在,我们应该设置一些限定条件,然后在这个范围内寻找最优解,也就是局部最优解——有所斩获总比空手而归强,哪怕这种斩获只是一次有趣的经历。
​ 柏拉图有一天又问什么是婚姻?苏格拉底叫他到树林走一次,选一棵最好的树做圣诞树,也是不许回头,只许选一次。这次他一身疲惫地拖了一棵看起来直挺、翠绿,却有点稀疏的杉树回来,他的理由是,有了上回的教训,好不容易看见一棵看似不错的,又发现时间、体力已经快不够用了,也不管是不是最好的,就拿回来了。苏格拉底告诉他:“这就是婚姻。”

​ 优化问题一般分为局部最优和全局最优。其中,

(1)局部最优,就是在函数值空间的一个有限区域内寻找最小值;而全局最优,是在函数值空间整个区域寻找最小值问题。

(2)函数局部最小点是它的函数值小于或等于附近点的点,但是有可能大于较远距离的点。

(3)全局最小点是那种它的函数值小于或等于所有的可行点。

2.1.5 大数据与深度学习之间的关系

首先来看大数据、机器学习及数据挖掘三者简单的定义:

大数据通常被定义为“超出常用软件工具捕获,管理和处理能力”的数据集。
机器学习关心的问题是如何构建计算机程序使用经验自动改进。
数据挖掘是从数据中提取模式的特定算法的应用,在数据挖掘中,重点在于算法的应用,而不是算法本身。

机器学习和数据挖掘之间的关系如下:
数据挖掘是一个过程,在此过程中机器学习算法被用作提取数据集中的潜在有价值模式的工具。
大数据与深度学习关系总结如下:

(1)深度学习是一种模拟大脑的行为。可以从所学习对象的机制以及行为等等很多相关联的方面进行学习,模仿类型行为以及思维。

(2)深度学习对于大数据的发展有帮助。深度学习对于大数据技术开发的每一个阶段均有帮助,不管是数据的分析还是挖掘还是建模,只有深度学习,这些工作才会有可能一一得到实现。

(3)深度学习转变了解决问题的思维。很多时候发现问题到解决问题,走一步看一步不是一个主要的解决问题的方式了,在深度学习的基础上,要求我们从开始到最后都要基于一个目标,为了需要优化的那个最终目标去进行处理数据以及将数据放入到数据应用平台上去,这就是端到端(End to End)。

(4)大数据的深度学习需要一个框架。在大数据方面的深度学习都是从基础的角度出发的,深度学习需要一个框架或者一个系统。总而言之,将你的大数据通过深度分析变为现实,这就是深度学习和大数据的最直接关系。

2.2 机器学习学习方式

​ 根据数据类型的不同,对一个问题的建模有不同的方式。依据不同的学习方式和输入数据,机器学习主要分为以下四种学习方式。

2.2.1 监督学习

​ 特点:监督学习是使用已知正确答案的示例来训练网络。已知数据和其一一对应的标签,训练一个预测模型,将输入数据映射到标签的过程。

​ 常见应用场景:监督式学习的常见应用场景如分类问题和回归问题。

​ 算法举例:常见的有监督机器学习算法包括支持向量机(Support Vector Machine, SVM),朴素贝叶斯(Naive Bayes),逻辑回归(Logistic Regression),K近邻(K-Nearest Neighborhood, KNN),决策树(Decision Tree),随机森林(Random Forest),AdaBoost以及线性判别分析(Linear Discriminant Analysis, LDA)等。深度学习(Deep Learning)也是大多数以监督学习的方式呈现。

2.2.2 非监督式学习

​ 定义:在非监督式学习中,数据并不被特别标识,适用于你具有数据集但无标签的情况。学习模型是为了推断出数据的一些内在结构。

​ 常见应用场景:常见的应用场景包括关联规则的学习以及聚类等。

​ 算法举例:常见算法包括Apriori算法以及k-Means算法。

2.2.3 半监督式学习

​ 特点:在此学习方式下,输入数据部分被标记,部分没有被标记,这种学习模型可以用来进行预测。

​ 常见应用场景:应用场景包括分类和回归,算法包括一些对常用监督式学习算法的延伸,通过对已标记数据建模,在此基础上,对未标记数据进行预测。

​ 算法举例:常见算法如图论推理算法(Graph Inference)或者拉普拉斯支持向量机(Laplacian SVM)等。

2.2.4 弱监督学习

​ 特点:弱监督学习可以看做是有多个标记的数据集合,次集合可以是空集,单个元素,或包含多种情况(没有标记,有一个标记,和有多个标记)的多个元素。 数据集的标签是不可靠的,这里的不可靠可以是标记不正确,多种标记,标记不充分,局部标记等。已知数据和其一一对应的弱标签,训练一个智能算法,将输入数据映射到一组更强的标签的过程。标签的强弱指的是标签蕴含的信息量的多少,比如相对于分割的标签来说,分类的标签就是弱标签。

​ 算法举例:举例,给出一张包含气球的图片,需要得出气球在图片中的位置及气球和背景的分割线,这就是已知弱标签学习强标签的问题。

​ 在企业数据应用的场景下, 人们最常用的可能就是监督式学习和非监督式学习的模型。 在图像识别等领域,由于存在大量的非标识的数据和少量的可标识数据, 目前半监督式学习是一个很热的话题。

2.2.5 监督学习有哪些步骤

​ 监督学习是使用已知正确答案的示例来训练网络,每组训练数据有一个明确的标识或结果。想象一下,我们可以训练一个网络,让其从照片库中(其中包含气球的照片)识别出气球的照片。以下就是我们在这个假设场景中所要采取的步骤。

步骤1:数据集的创建和分类
​ 首先,浏览你的照片(数据集),确定所有包含气球的照片,并对其进行标注。然后,将所有照片分为训练集和验证集。目标就是在深度网络中找一函数,这个函数输入是任意一张照片,当照片中包含气球时,输出1,否则输出0。

步骤2:数据增强(Data Augmentation)
​ 当原始数据搜集和标注完毕,一般搜集的数据并不一定包含目标在各种扰动下的信息。数据的好坏对于机器学习模型的预测能力至关重要,因此一般会进行数据增强。对于图像数据来说,数据增强一般包括,图像旋转,平移,颜色变换,裁剪,仿射变换等。

步骤3:特征工程(Feature Engineering)
​ 一般来讲,特征工程包含特征提取和特征选择。常见的手工特征(Hand-Crafted Feature)有尺度不变特征变换(Scale-Invariant Feature Transform, SIFT),方向梯度直方图(Histogram of Oriented Gradient, HOG)等。由于手工特征是启发式的,其算法设计背后的出发点不同,将这些特征组合在一起的时候有可能会产生冲突,如何将组合特征的效能发挥出来,使原始数据在特征空间中的判别性最大化,就需要用到特征选择的方法。在深度学习方法大获成功之后,人们很大一部分不再关注特征工程本身。因为,最常用到的卷积神经网络(Convolutional Neural Networks, CNNs)本身就是一种特征提取和选择的引擎。研究者提出的不同的网络结构、正则化、归一化方法实际上就是深度学习背景下的特征工程。

步骤4:构建预测模型和损失
​ 将原始数据映射到特征空间之后,也就意味着我们得到了比较合理的输入。下一步就是构建合适的预测模型得到对应输入的输出。而如何保证模型的输出和输入标签的一致性,就需要构建模型预测和标签之间的损失函数,常见的损失函数(Loss Function)有交叉熵、均方差等。通过优化方法不断迭代,使模型从最初的初始化状态一步步变化为有预测能力的模型的过程,实际上就是学习的过程。

步骤5:训练
​ 选择合适的模型和超参数进行初始化,其中超参数比如支持向量机中核函数、误差项惩罚权重等。当模型初始化参数设定好后,将制作好的特征数据输入到模型,通过合适的优化方法不断缩小输出与标签之间的差距,当迭代过程到了截止条件,就可以得到训练好的模型。优化方法最常见的就是梯度下降法及其变种,使用梯度下降法的前提是优化目标函数对于模型是可导的。

步骤6:验证和模型选择
​ 训练完训练集图片后,需要进行模型测试。利用验证集来验证模型是否可以准确地挑选出含有气球在内的照片。
​ 在此过程中,通常会通过调整和模型相关的各种事物(超参数)来重复步骤2和3,诸如里面有多少个节点,有多少层,使用怎样的激活函数和损失函数,如何在反向传播阶段积极有效地训练权值等等。

步骤7:测试及应用
​ 当有了一个准确的模型,就可以将该模型部署到你的应用程序中。你可以将预测功能发布为API(Application Programming Interface, 应用程序编程接口)调用,并且你可以从软件中调用该API,从而进行推理并给出相应的结果。

2.8 分类算法

​ 分类算法和回归算法是对真实世界不同建模的方法。分类模型是认为模型的输出是离散的,例如大自然的生物被划分为不同的种类,是离散的。回归模型的输出是连续的,例如人的身高变化过程是一个连续过程,而不是离散的。

​ 因此,在实际建模过程时,采用分类模型还是回归模型,取决于你对任务(真实世界)的分析和理解。

2.8.1 常用分类算法的优缺点?

​ 接下来我们介绍常用分类算法的优缺点,如表2-1所示。

​ 表2-1 常用分类算法的优缺点

算法 优点 缺点
Bayes 贝叶斯分类法 1)所需估计的参数少,对于缺失数据不敏感。
2)有着坚实的数学基础,以及稳定的分类效率。
1)需要假设属性之间相互独立,这往往并不成立。(喜欢吃番茄、鸡蛋,却不喜欢吃番茄炒蛋)。
2)需要知道先验概率。
3)分类决策存在错误率。
Decision Tree决策树 1)不需要任何领域知识或参数假设。
2)适合高维数据。
3)简单易于理解。
4)短时间内处理大量数据,得到可行且效果较好的结果。
5)能够同时处理数据型和常规性属性。
1)对于各类别样本数量不一致数据,信息增益偏向于那些具有更多数值的特征。
2)易于过拟合。
3)忽略属性之间的相关性。
4)不支持在线学习。
SVM支持向量机 1)可以解决小样本下机器学习的问题。
2)提高泛化性能。
3)可以解决高维、非线性问题。超高维文本分类仍受欢迎。
4)避免神经网络结构选择和局部极小的问题。
1)对缺失数据敏感。
2)内存消耗大,难以解释。
3)运行和调参略烦人。
KNN K近邻 1)思想简单,理论成熟,既可以用来做分类也可以用来做回归;
2)可用于非线性分类;
3)训练时间复杂度为O(n);
4)准确度高,对数据没有假设,对outlier不敏感;
1)计算量太大。
2)对于样本分类不均衡的问题,会产生误判。
3)需要大量的内存。
4)输出的可解释性不强。
Logistic Regression逻辑回归 1)速度快。
2)简单易于理解,直接看到各个特征的权重。
3)能容易地更新模型吸收新的数据。
4)如果想要一个概率框架,动态调整分类阀值。
特征处理复杂。需要归一化和较多的特征工程。
Neural Network 神经网络 1)分类准确率高。
2)并行处理能力强。
3)分布式存储和学习能力强。
4)鲁棒性较强,不易受噪声影响。
1)需要大量参数(网络拓扑、阀值、阈值)。
2)结果难以解释。
3)训练时间过长。
Adaboosting 1)adaboost是一种有很高精度的分类器。
2)可以使用各种方法构建子分类器,Adaboost算法提供的是框架。
3)当使用简单分类器时,计算出的结果是可以理解的。而且弱分类器构造极其简单。
4)简单,不用做特征筛选。
5)不用担心overfitting。
对outlier比较敏感

2.8.2 分类算法的评估方法

​ 分类评估方法主要功能是用来评估分类算法的好坏,而评估一个分类器算法的好坏又包括许多项指标。了解各种评估方法,在实际应用中选择正确的评估方法是十分重要的。

  • 几个常用术语
    ​ 这里首先介绍几个常见的模型评价术语,现在假设我们的分类目标只有两类,计为正例(positive)和负例(negative)分别是:
    1) True positives(TP): 被正确地划分为正例的个数,即实际为正例且被分类器划分为正例的实例数;
    2) False positives(FP): 被错误地划分为正例的个数,即实际为负例但被分类器划分为正例的实例数;
    3) False negatives(FN):被错误地划分为负例的个数,即实际为正例但被分类器划分为负例的实例数;
    4) True negatives(TN): 被正确地划分为负例的个数,即实际为负例且被分类器划分为负例的实例数。 

    ​ 表2-2 四个术语的混淆矩阵

图2-3 术语的混淆矩阵

表2-2是这四个术语的混淆矩阵,做以下说明:
1)P=TP+FN表示实际为正例的样本个数。
2)True、False描述的是分类器是否判断正确。
3)Positive、Negative是分类器的分类结果,如果正例计为1、负例计为-1,即positive=1、negative=-1。用1表示True,-1表示False,那么实际的类标=TF*PN,TF为true或false,PN为positive或negative。
4)例如True positives(TP)的实际类标=1*1=1为正例,False positives(FP)的实际类标=(-1)*1=-1为负例,False negatives(FN)的实际类标=(-1)*(-1)=1为正例,True negatives(TN)的实际类标=1*(-1)=-1为负例。

  • 评价指标
    1) 正确率(accuracy)

    正确率是我们最常见的评价指标,accuracy = (TP+TN)/(P+N),正确率是被分对的样本数在所有样本数中的占比,通常来说,正确率越高,分类器越好。
    

    2) 错误率(error rate)

    错误率则与正确率相反,描述被分类器错分的比例,error rate = (FP+FN)/(P+N),对某一个实例来说,分对与分错是互斥事件,所以accuracy =1 -  error rate。
    

    3) 灵敏度(sensitivity)

    sensitivity = TP/P,表示的是所有正例中被分对的比例,衡量了分类器对正例的识别能力。
    

    4) 特异性(specificity)

    specificity = TN/N,表示的是所有负例中被分对的比例,衡量了分类器对负例的识别能力。
    

    5) 精度(precision)

    precision=TP/(TP+FP),精度是精确性的度量,表示被分为正例的示例中实际为正例的比例。
    

    6) 召回率(recall)

    召回率是覆盖面的度量,度量有多个正例被分为正例,recall=TP/(TP+FN)=TP/P=sensitivity,可以看到召回率与灵敏度是一样的。
    

    7) 其他评价指标

    计算速度:分类器训练和预测需要的时间;
    鲁棒性:处理缺失值和异常值的能力;
    可扩展性:处理大数据集的能力;
    可解释性:分类器的预测标准的可理解性,像决策树产生的规则就是很容易理解的,而神经网络的一堆参数就不好理解,我们只好把它看成一个黑盒子。
    

    8) 精度和召回率反映了分类器分类性能的两个方面。如果综合考虑查准率与查全率,可以得到新的评价指标F1-score,也称为综合分类率:$F1=\frac{2 \times precision \times recall}{precision + recall}​$。

      为了综合多个类别的分类情况,评测系统整体性能,经常采用的还有微平均F1(micro-averaging)和宏平均F1(macro-averaging )两种指标。
    
      (1)宏平均F1与微平均F1是以两种不同的平均方式求的全局F1指标。
    
      (2)宏平均F1的计算方法先对每个类别单独计算F1值,再取这些F1值的算术平均值作为全局指标。
    
      (3)微平均F1的计算方法是先累加计算各个类别的a、b、c、d的值,再由这些值求出F1值。
    
      (4)由两种平均F1的计算方式不难看出,宏平均F1平等对待每一个类别,所以它的值主要受到稀有类别的影响,而微平均F1平等考虑文档集中的每一个文档,所以它的值受到常见类别的影响比较大。
    
  • ROC曲线和PR曲线

      如图2-3,ROC曲线是(Receiver Operating Characteristic Curve,受试者工作特征曲线)的简称,是以灵敏度(真阳性率)为纵坐标,以1减去特异性(假阳性率)为横坐标绘制的性能评价曲线。可以将不同模型对同一数据集的ROC曲线绘制在同一笛卡尔坐标系中,ROC曲线越靠近左上角,说明其对应模型越可靠。也可以通过ROC曲线下面的面积(Area Under Curve, AUC)来评价模型,AUC越大,模型越可靠。
    

​ 图2-3 ROC曲线

​ PR曲线是Precision Recall Curve的简称,描述的是precision和recall之间的关系,以recall为横坐标,precision为纵坐标绘制的曲线。该曲线的所对应的面积AUC实际上是目标检测中常用的评价指标平均精度(Average Precision, AP)。AP越高,说明模型性能越好。

2.8.3 正确率能很好的评估分类算法吗

​ 不同算法有不同特点,在不同数据集上有不同的表现效果,根据特定的任务选择不同的算法。如何评价分类算法的好坏,要做具体任务具体分析。对于决策树,主要用正确率去评估,但是其他算法,只用正确率能很好的评估吗?
​ 答案是否定的。
​ 正确率确实是一个很直观很好的评价指标,但是有时候正确率高并不能完全代表一个算法就好。比如对某个地区进行地震预测,地震分类属性分为0:不发生地震、1发生地震。我们都知道,不发生的概率是极大的,对于分类器而言,如果分类器不加思考,对每一个测试样例的类别都划分为0,达到99%的正确率,但是,问题来了,如果真的发生地震时,这个分类器毫无察觉,那带来的后果将是巨大的。很显然,99%正确率的分类器并不是我们想要的。出现这种现象的原因主要是数据分布不均衡,类别为1的数据太少,错分了类别1但达到了很高的正确率缺忽视了研究者本身最为关注的情况。

2.8.4 什么样的分类器是最好的

​ 对某一个任务,某个具体的分类器不可能同时满足或提高所有上面介绍的指标。
​ 如果一个分类器能正确分对所有的实例,那么各项指标都已经达到最优,但这样的分类器往往不存在。比如之前说的地震预测,既然不能百分百预测地震的发生,但实际情况中能容忍一定程度的误报。假设在1000次预测中,共有5次预测发生了地震,真实情况中有一次发生了地震,其他4次则为误报。正确率由原来的999/1000=99.9下降为996/1000=99.6。召回率由0/1=0%上升为1/1=100%。对此解释为,虽然预测失误了4次,但真的地震发生前,分类器能预测对,没有错过,这样的分类器实际意义更为重大,正是我们想要的。在这种情况下,在一定正确率前提下,要求分类器的召回率尽量高。

2.9 逻辑回归

2.9.1 回归划分

广义线性模型家族里,依据因变量不同,可以有如下划分:

(1)如果是连续的,就是多重线性回归。

(2)如果是二项分布,就是逻辑回归。

(3)如果是泊松(Poisson)分布,就是泊松回归。

(4)如果是负二项分布,就是负二项回归。

(5)逻辑回归的因变量可以是二分类的,也可以是多分类的,但是二分类的更为常用,也更加容易解释。所以实际中最常用的就是二分类的逻辑回归。

2.9.2 逻辑回归适用性

逻辑回归可用于以下几个方面:

(1)用于概率预测。用于可能性预测时,得到的结果有可比性。比如根据模型进而预测在不同的自变量情况下,发生某病或某种情况的概率有多大。

(2)用于分类。实际上跟预测有些类似,也是根据模型,判断某人属于某病或属于某种情况的概率有多大,也就是看一下这个人有多大的可能性是属于某病。进行分类时,仅需要设定一个阈值即可,可能性高于阈值是一类,低于阈值是另一类。

(3)寻找危险因素。寻找某一疾病的危险因素等。

(4)仅能用于线性问题。只有当目标和特征是线性关系时,才能用逻辑回归。在应用逻辑回归时注意两点:一是当知道模型是非线性时,不适用逻辑回归;二是当使用逻辑回归时,应注意选择和目标为线性关系的特征。

(5)各特征之间不需要满足条件独立假设,但各个特征的贡献独立计算。

2.9.3 逻辑回归与朴素贝叶斯有什么区别

逻辑回归与朴素贝叶斯区别有以下几个方面:

(1)逻辑回归是判别模型, 朴素贝叶斯是生成模型,所以生成和判别的所有区别它们都有。

(2)朴素贝叶斯属于贝叶斯,逻辑回归是最大似然,两种概率哲学间的区别。

(3)朴素贝叶斯需要条件独立假设。

(4)逻辑回归需要求特征参数间是线性的。

2.9.4 线性回归与逻辑回归的区别

线性回归与逻辑回归的区别如下描述:

(1)线性回归的样本的输出,都是连续值,$ y\in (-\infty ,+\infty )$,而逻辑回归中$y\in (0,1)$,只能取0和1。

(2)对于拟合函数也有本质上的差别:

​ 线性回归:$f(x)=\theta ^{T}x=\theta {1}x {1}+\theta {2}x {2}+…+\theta {n}x {n}$

​ 逻辑回归:$f(x)=P(y=1|x;\theta )=g(\theta ^{T}x)$,其中,$g(z)=\frac{1}{1+e^{-z}}$

​ 可以看出,线性回归的拟合函数,是对f(x)的输出变量y的拟合,而逻辑回归的拟合函数是对为1类样本的概率的拟合。

​ 那么,为什么要以1类样本的概率进行拟合呢,为什么可以这样拟合呢?

​ $\theta ^{T}x=0$就相当于是1类和0类的决策边界:

​ 当$\theta ^{T}x>0$,则y>0.5;若$\theta ^{T}x\rightarrow +\infty $,则$y \rightarrow 1 $,即y为1类;

​ 当$\theta ^{T}x<0$,则y<0.5;若$\theta ^{T}x\rightarrow -\infty $,则$y \rightarrow 0 $,即y为0类;

这个时候就能看出区别,在线性回归中$\theta ^{T}x$为预测值的拟合函数;而在逻辑回归中$\theta ^{T}x$为决策边界。下表2-3为线性回归和逻辑回归的区别。

​ 表2-3 线性回归和逻辑回归的区别

线性回归 逻辑回归
目的 预测 分类
$y^{(i)}$ 未知 (0,1)
函数 拟合函数 预测函数
参数计算方式 最小二乘法 极大似然估计

下面具体解释一下:

  1. 拟合函数和预测函数什么关系呢?简单来说就是将拟合函数做了一个逻辑函数的转换,转换后使得$y^{(i)} \in (0,1)$;
  2. 最小二乘和最大似然估计可以相互替代吗?回答当然是不行了。我们来看看两者依仗的原理:最大似然估计是计算使得数据出现的可能性最大的参数,依仗的自然是Probability。而最小二乘是计算误差损失。

2.10 代价函数

2.10.1 为什么需要代价函数

  1. 为了得到训练逻辑回归模型的参数,需要一个代价函数,通过训练代价函数来得到参数。
  2. 用于找到最优解的目的函数。

2.10.2 代价函数作用原理

​ 在回归问题中,通过代价函数来求解最优解,常用的是平方误差代价函数。假设函数图像如图2-4所示,当参数发生变化时,假设函数状态也会随着变化。

​ 图2-4 $h(x) = A + Bx$函数示意图

​ 想要拟合图中的离散点,我们需要尽可能找到最优的$A$和$B$来使这条直线更能代表所有数据。如何找到最优解呢,这就需要使用代价函数来求解,以平方误差代价函数为例,假设函数为$h(x)=\theta_0x$。
平方误差代价函数的主要思想就是将实际数据给出的值与拟合出的线的对应值做差,求出拟合出的直线与实际的差距。在实际应用中,为了避免因个别极端数据产生的影响,采用类似方差再取二分之一的方式来减小个别数据的影响。因此,引出代价函数:

最优解即为代价函数的最小值$\min J(\theta_0, \theta_1)$。如果是1个参数,代价函数一般通过二维曲线便可直观看出。如果是2个参数,代价函数通过三维图像可看出效果,参数越多,越复杂。
当参数为2个时,代价函数是三维图像,如下图2-5所示。

​ 图2-5 代价函数三维图像

2.10.3 为什么代价函数要非负

​ 目标函数存在一个下界,在优化过程当中,如果优化算法能够使目标函数不断减小,根据单调有界准则,这个优化算法就能证明是收敛有效的。
​ 只要设计的目标函数有下界,基本上都可以,代价函数非负更为方便。

2.10.4 常见代价函数

(1)二次代价函数(quadratic cost)

​ 其中,$J$表示代价函数,$x$表示样本,$y$表示实际值,$a$表示输出值,$n$表示样本的总数。使用一个样本为例简单说明,此时二次代价函数为:

​ 假如使用梯度下降法(Gradient descent)来调整权值参数的大小,权值$w$和偏置$b$的梯度推导如下:

其中,$z​$表示神经元的输入,$\sigma​$表示激活函数。权值$w​$和偏置$b​$的梯度跟激活函数的梯度成正比,激活函数的梯度越大,权值$w​$和偏置$b​$的大小调整得越快,训练收敛得就越快。

:神经网络常用的激活函数为sigmoid函数,该函数的曲线如下图2-6所示:

​ 图2-6 sigmoid函数曲线

如上图所示,对0.88和0.98两个点进行比较:
​ 假设目标是收敛到1.0。0.88离目标1.0比较远,梯度比较大,权值调整比较大。0.98离目标1.0比较近,梯度比较小,权值调整比较小。调整方案合理。
​ 假如目标是收敛到0。0.88离目标0比较近,梯度比较大,权值调整比较大。0.98离目标0比较远,梯度比较小,权值调整比较小。调整方案不合理。
​ 原因:在使用sigmoid函数的情况下, 初始的代价(误差)越大,导致训练越慢。

(2)交叉熵代价函数(cross-entropy)

其中,$J$表示代价函数,$x$表示样本,$y$表示实际值,$a$表示输出值,$n$表示样本的总数。
权值$w$和偏置$b​$的梯度推导如下:

当误差越大时,梯度就越大,权值$w$和偏置$b$调整就越快,训练的速度也就越快。
二次代价函数适合输出神经元是线性的情况,交叉熵代价函数适合输出神经元是S型函数的情况。

(3)对数似然代价函数(log-likelihood cost)
对数似然函数常用来作为softmax回归的代价函数。深度学习中普遍的做法是将softmax作为最后一层,此时常用的代价函数是对数似然代价函数。
对数似然代价函数与softmax的组合和交叉熵与sigmoid函数的组合非常相似。对数似然代价函数在二分类时可以化简为交叉熵代价函数的形式。
在tensorflow中:
与sigmoid搭配使用的交叉熵函数:tf.nn.sigmoid_cross_entropy_with_logits()
与softmax搭配使用的交叉熵函数:tf.nn.softmax_cross_entropy_with_logits()
在pytorch中:
与sigmoid搭配使用的交叉熵函数:torch.nn.BCEWithLogitsLoss()
与softmax搭配使用的交叉熵函数:torch.nn.CrossEntropyLoss()

2.10.5 为什么用交叉熵代替二次代价函数

(1)为什么不用二次方代价函数
由上一节可知,权值$w$和偏置$b$的偏导数为$\frac{\partial J}{\partial w}=(a-y)\sigma’(z)x$,$\frac{\partial J}{\partial b}=(a-y)\sigma’(z)$, 偏导数受激活函数的导数影响,sigmoid函数导数在输出接近0和1时非常小,会导致一些实例在刚开始训练时学习得非常慢。

(2)为什么要用交叉熵
交叉熵函数权值$w$和偏置$b$的梯度推导为:

由以上公式可知,权重学习的速度受到$\sigma{(z)}-y$影响,更大的误差,就有更快的学习速度,避免了二次代价函数方程中因$\sigma’{(z)}$导致的学习缓慢的情况。

2.11 损失函数

2.11.1 什么是损失函数

​ 损失函数(Loss Function)又叫做误差函数,用来衡量算法的运行情况,估量模型的预测值与真实值的不一致程度,是一个非负实值函数,通常使用$
L(Y, f(x))​$来表示。损失函数越小,模型的鲁棒性就越好。损失函数是经验风险函数的核心部分,也是结构风险函数重要组成部分。

2.11.2 常见的损失函数

​ 机器学习通过对算法中的目标函数进行不断求解优化,得到最终想要的结果。分类和回归问题中,通常使用损失函数或代价函数作为目标函数。
​ 损失函数用来评价预测值和真实值不一样的程度。通常损失函数越好,模型的性能也越好。
​ 损失函数可分为经验风险损失函数和结构风险损失函数。经验风险损失函数指预测结果和实际结果的差别,结构风险损失函数是在经验风险损失函数上加上正则项。
​ 下面介绍常用的损失函数:

(1)0-1损失函数
如果预测值和目标值相等,值为0,如果不相等,值为1。

一般的在实际使用中,相等的条件过于严格,可适当放宽条件:

(2)绝对值损失函数
和0-1损失函数相似,绝对值损失函数表示为:

(3)平方损失函数

这点可从最小二乘法和欧几里得距离角度理解。最小二乘法的原理是,最优拟合曲线应该使所有点到回归直线的距离和最小。

(4)对数损失函数

​ 常见的逻辑回归使用的就是对数损失函数,有很多人认为逻辑回归的损失函数是平方损失,其实不然。逻辑回归它假设样本服从伯努利分布(0-1分布),进而求得满足该分布的似然函数,接着取对数求极值等。逻辑回归推导出的经验风险函数是最小化负的似然函数,从损失函数的角度看,就是对数损失函数。

(6)指数损失函数
指数损失函数的标准形式为:

例如AdaBoost就是以指数损失函数为损失函数。

(7)Hinge损失函数
Hinge损失函数的标准形式如下:

统一的形式:

其中y是预测值,范围为(-1,1),t为目标值,其为-1或1。

在线性支持向量机中,最优化问题可等价于

上式相似于下式

其中$l(wx_i+by_i)$是Hinge损失函数,$\Vert w\Vert ^2$可看做为正则化项。

2.11.3 逻辑回归为什么使用对数损失函数

假设逻辑回归模型

假设逻辑回归模型的概率分布是伯努利分布,其概率质量函数为:

其似然函数为:

对数似然函数为:

对数函数在单个数据点上的定义为:

则全局样本损失函数为:

由此可看出,对数损失函数与极大似然估计的对数似然函数本质上是相同的。所以逻辑回归直接采用对数损失函数。

2.11.4 对数损失函数是如何度量损失的

​ 例如,在高斯分布中,我们需要确定均值和标准差。
​ 如何确定这两个参数?最大似然估计是比较常用的方法。最大似然的目标是找到一些参数值,这些参数值对应的分布可以最大化观测到数据的概率。
​ 因为需要计算观测到所有数据的全概率,即所有观测到的数据点的联合概率。现考虑如下简化情况:

(1)假设观测到每个数据点的概率和其他数据点的概率是独立的。

(2)取自然对数。
假设观测到单个数据点$x_i(i=1,2,…n)$的概率为:

(3)其联合概率为:

​ 对上式取自然对数,可得:

根据对数定律,上式可以化简为:

然后求导为:

​ 上式左半部分为对数损失函数。损失函数越小越好,因此我们令等式左半的对数损失函数为0,可得:

同理,可计算$\sigma ​$。

2.12 梯度下降

2.12.1 机器学习中为什么需要梯度下降

梯度下降是机器学习中常见优化算法之一,梯度下降法有以下几个作用:

(1)梯度下降是迭代法的一种,可以用于求解最小二乘问题。

(2)在求解机器学习算法的模型参数,即无约束优化问题时,主要有梯度下降法(Gradient Descent)和最小二乘法。

(3)在求解损失函数的最小值时,可以通过梯度下降法来一步步的迭代求解,得到最小化的损失函数和模型参数值。

(4)如果我们需要求解损失函数的最大值,可通过梯度上升法来迭代。梯度下降法和梯度上升法可相互转换。

(5)在机器学习中,梯度下降法主要有随机梯度下降法和批量梯度下降法。

2.12.2 梯度下降法缺点

梯度下降法缺点有以下几点:

(1)靠近极小值时收敛速度减慢。

(2)直线搜索时可能会产生一些问题。

(3)可能会“之字形”地下降。

梯度概念也有需注意的地方:

(1)梯度是一个向量,即有方向有大小。

(2)梯度的方向是最大方向导数的方向。

(3)梯度的值是最大方向导数的值。

2.12.3 梯度下降法直观理解

梯度下降法经典图示如下图2.7所示:

​ 图2.7 梯度下降法经典图示

​ 形象化举例,由上图2.7所示,假如最开始,我们在一座大山上的某处位置,因为到处都是陌生的,不知道下山的路,所以只能摸索着根据直觉,走一步算一步,在此过程中,每走到一个位置的时候,都会求解当前位置的梯度,沿着梯度的负方向,也就是当前最陡峭的位置向下走一步,然后继续求解当前位置梯度,向这一步所在位置沿着最陡峭最易下山的位置走一步。不断循环求梯度,就这样一步步地走下去,一直走到我们觉得已经到了山脚。当然这样走下去,有可能我们不能走到山脚,而是到了某一个局部的山势低处。
​ 由此,从上面的解释可以看出,梯度下降不一定能够找到全局的最优解,有可能是一个局部的最优解。当然,如果损失函数是凸函数,梯度下降法得到的解就一定是全局最优解。

核心思想归纳

(1)初始化参数,随机选取取值范围内的任意数;

(2)迭代操作:
a)计算当前梯度;
b)修改新的变量;
c)计算朝最陡的下坡方向走一步;
d)判断是否需要终止,如否,返回a);

(3)得到全局最优解或者接近全局最优解。

2.12.4 梯度下降法算法描述

梯度下降法算法步骤如下:

(1)确定优化模型的假设函数及损失函数。
​ 举例,对于线性回归,假设函数为:

其中,$\theta_i,x_i(i=0,1,2,…,n)$分别为模型参数、每个样本的特征值。
对于假设函数,损失函数为:

(2)相关参数初始化。
​ 主要初始化${\theta}_i$、算法迭代步长${\alpha} $、终止距离${\zeta} $。初始化时可以根据经验初始化,即${\theta} $初始化为0,步长${\alpha} $初始化为1。当前步长记为${\varphi}_i $。当然,也可随机初始化。

(3)迭代计算。

​ 1)计算当前位置时损失函数的梯度,对${\theta}_i $,其梯度表示为:

​ 2)计算当前位置下降的距离。

​ 3)判断是否终止。
​ 确定是否所有${\theta}_i$梯度下降的距离${\varphi}_i$都小于终止距离${\zeta}$,如果都小于${\zeta}$,则算法终止,当然的值即为最终结果,否则进入下一步。
​ 4)更新所有的${\theta}_i$,更新后的表达式为:

​ 5)令上式$x^{(j)}_0=1$,更新完毕后转入1)。
​ 由此,可看出,当前位置的梯度方向由所有样本决定,上式中 $\frac{1}{m}​$、$\alpha \frac{1}{m}​$ 的目的是为了便于理解。

2.12.5 如何对梯度下降法进行调优

实际使用梯度下降法时,各项参数指标不能一步就达到理想状态,对梯度下降法调优主要体现在以下几个方面:

(1)算法迭代步长$\alpha$选择。
在算法参数初始化时,有时根据经验将步长初始化为1。实际取值取决于数据样本。可以从大到小,多取一些值,分别运行算法看迭代效果,如果损失函数在变小,则取值有效。如果取值无效,说明要增大步长。但步长太大,有时会导致迭代速度过快,错过最优解。步长太小,迭代速度慢,算法运行时间长。

(2)参数的初始值选择。
初始值不同,获得的最小值也有可能不同,梯度下降有可能得到的是局部最小值。如果损失函数是凸函数,则一定是最优解。由于有局部最优解的风险,需要多次用不同初始值运行算法,关键损失函数的最小值,选择损失函数最小化的初值。

(3)标准化处理。
由于样本不同,特征取值范围也不同,导致迭代速度慢。为了减少特征取值的影响,可对特征数据标准化,使新期望为0,新方差为1,可节省算法运行时间。

2.12.6 随机梯度和批量梯度区别

​ 随机梯度下降(SDG)和批量梯度下降(BDG)是两种主要梯度下降法,其目的是增加某些限制来加速运算求解。
下面通过介绍两种梯度下降法的求解思路,对其进行比较。
假设函数为:

损失函数为:

其中,$m​$为样本个数,$j​$为参数个数。

1、 批量梯度下降的求解思路如下:
a) 得到每个$ \theta ​$对应的梯度:

b) 由于是求最小化风险函数,所以按每个参数 $ \theta ​$ 的梯度负方向更新 $ \theta_i ​$ :

c) 从上式可以注意到,它得到的虽然是一个全局最优解,但每迭代一步,都要用到训练集所有的数据,如果样本数据很大,这种方法迭代速度就很慢。
相比而言,随机梯度下降可避免这种问题。

2、随机梯度下降的求解思路如下:
a) 相比批量梯度下降对应所有的训练样本,随机梯度下降法中损失函数对应的是训练集中每个样本的粒度。
损失函数可以写成如下这种形式,

b)对每个参数 $ \theta​$ 按梯度方向更新 $ \theta​$:

c) 随机梯度下降是通过每个样本来迭代更新一次。
随机梯度下降伴随的一个问题是噪音较批量梯度下降要多,使得随机梯度下降并不是每次迭代都向着整体最优化方向。

小结:
随机梯度下降法、批量梯度下降法相对来说都比较极端,简单对比如下:

方法 特点
批量梯度下降 a)采用所有数据来梯度下降。
b)批量梯度下降法在样本量很大的时候,训练速度慢。
随机梯度下降 a)随机梯度下降用一个样本来梯度下降。
b)训练速度很快。
c)随机梯度下降法仅仅用一个样本决定梯度方向,导致解有可能不是全局最优。
d)收敛速度来说,随机梯度下降法一次迭代一个样本,导致迭代方向变化很大,不能很快的收敛到局部最优解。

下面介绍能结合两种方法优点的小批量梯度下降法。

3、 小批量(Mini-Batch)梯度下降的求解思路如下
对于总数为$m$个样本的数据,根据样本的数据,选取其中的$n(1< n< m)$个子样本来迭代。其参数$\theta$按梯度方向更新$\theta_i$公式如下:

2.12.7 各种梯度下降法性能比较

​ 下表简单对比随机梯度下降(SGD)、批量梯度下降(BGD)、小批量梯度下降(Mini-batch GD)、和Online GD的区别:

BGD SGD Mini-batch GD Online GD
训练集 固定 固定 固定 实时更新
单次迭代样本数 整个训练集 单个样本 训练集的子集 根据具体算法定
算法复杂度 一般
时效性 一般 一般
收敛性 稳定 不稳定 较稳定 不稳定

BGD、SGD、Mini-batch GD,前面均已讨论过,这里介绍一下Online GD。

​ Online GD于Mini-batch GD/SGD的区别在于,所有训练数据只用一次,然后丢弃。这样做的优点在于可预测最终模型的变化趋势。

​ Online GD在互联网领域用的较多,比如搜索广告的点击率(CTR)预估模型,网民的点击行为会随着时间改变。用普通的BGD算法(每天更新一次)一方面耗时较长(需要对所有历史数据重新训练);另一方面,无法及时反馈用户的点击行为迁移。而Online GD算法可以实时的依据网民的点击行为进行迁移。

2.14 线性判别分析(LDA)

2.14.1 LDA思想总结

​ 线性判别分析(Linear Discriminant Analysis,LDA)是一种经典的降维方法。和主成分分析PCA不考虑样本类别输出的无监督降维技术不同,LDA是一种监督学习的降维技术,数据集的每个样本有类别输出。

LDA分类思想简单总结如下:

  1. 多维空间中,数据处理分类问题较为复杂,LDA算法将多维空间中的数据投影到一条直线上,将d维数据转化成1维数据进行处理。
  2. 对于训练数据,设法将多维数据投影到一条直线上,同类数据的投影点尽可能接近,异类数据点尽可能远离。
  3. 对数据进行分类时,将其投影到同样的这条直线上,再根据投影点的位置来确定样本的类别。

如果用一句话概括LDA思想,即“投影后类内方差最小,类间方差最大”。

2.14.2 图解LDA核心思想

​ 假设有红、蓝两类数据,这些数据特征均为二维,如下图所示。我们的目标是将这些数据投影到一维,让每一类相近的数据的投影点尽可能接近,不同类别数据尽可能远,即图中红色和蓝色数据中心之间的距离尽可能大。

左图和右图是两种不同的投影方式。

​ 左图思路:让不同类别的平均点距离最远的投影方式。

​ 右图思路:让同类别的数据挨得最近的投影方式。

​ 从上图直观看出,右图红色数据和蓝色数据在各自的区域来说相对集中,根据数据分布直方图也可看出,所以右图的投影效果好于左图,左图中间直方图部分有明显交集。

​ 以上例子是基于数据是二维的,分类后的投影是一条直线。如果原始数据是多维的,则投影后的分类面是一低维的超平面。

2.14.3 二类LDA算法原理

​ 输入:数据集 $D={(\boldsymbol x_1,\boldsymbol y_1),(\boldsymbol x_2,\boldsymbol y_2),…,(\boldsymbol x_m,\boldsymbol y_m)}​$,其中样本 $\boldsymbol x_i ​$ 是n维向量,$\boldsymbol y_i \epsilon {0, 1}​$,降维后的目标维度 $d​$。定义

​ $N_j(j=0,1)$ 为第 $j$ 类样本个数;

​ $X_j(j=0,1)$ 为第 $j$ 类样本的集合;

​ $u_j(j=0,1)​$ 为第 $j​$ 类样本的均值向量;

​ $\sum_j(j=0,1)$ 为第 $j$ 类样本的协方差矩阵。

​ 其中

​ 假设投影直线是向量 $\boldsymbol w$,对任意样本 $\boldsymbol x_i$,它在直线 $w$上的投影为 $\boldsymbol w^Tx_i$,两个类别的中心点 $u_0$, $u_1 $在直线 $w$ 的投影分别为 $\boldsymbol w^Tu_0$ 、$\boldsymbol w^Tu_1$。

​ LDA的目标是让两类别的数据中心间的距离 $| \boldsymbol w^Tu_0 - \boldsymbol w^Tu_1 |^2_2$ 尽量大,与此同时,希望同类样本投影点的协方差$\boldsymbol w^T \sum_0 \boldsymbol w$、$\boldsymbol w^T \sum_1 \boldsymbol w$ 尽量小,最小化 $\boldsymbol w^T \sum_0 \boldsymbol w + \boldsymbol w^T \sum_1 \boldsymbol w​$ 。
​ 定义
​ 类内散度矩阵

​ 类间散度矩阵 $S_b = (u_0 - u_1)(u_0 - u_1)^T$

​ 据上分析,优化目标为

​ 根据广义瑞利商的性质,矩阵 $S^{-1}{w} S_b$ 的最大特征值为 $J(\boldsymbol w)$ 的最大值,矩阵 $S^{-1}{w} S_b$ 的最大特征值对应的特征向量即为 $\boldsymbol w$。

2.14.4 LDA算法流程总结

LDA算法降维流程如下:

​ 输入:数据集 $D = { (x_1,y_1),(x_2,y_2), … ,(x_m,y_m) }$,其中样本 $x_i $ 是n维向量,$y_i \epsilon {C_1, C_2, …, C_k}$,降维后的目标维度 $d$ 。

​ 输出:降维后的数据集 $\overline{D} $ 。

步骤:

  1. 计算类内散度矩阵 $S_w$。
  2. 计算类间散度矩阵 $S_b​$ 。
  3. 计算矩阵 $S^{-1}_wS_b​$ 。
  4. 计算矩阵 $S^{-1}_wS_b$ 的最大的 d 个特征值。
  5. 计算 d 个特征值对应的 d 个特征向量,记投影矩阵为 W 。
  6. 转化样本集的每个样本,得到新样本 $P_i = W^Tx_i​$ 。
  7. 输出新样本集 $\overline{D} = { (p_1,y_1),(p_2,y_2),…,(p_m,y_m) }​$

2.14.5 LDA和PCA区别

异同点 LDA PCA
相同点 1. 两者均可以对数据进行降维;
2. 两者在降维时均使用了矩阵特征分解的思想;
3. 两者都假设数据符合高斯分布;
不同点 有监督的降维方法; 无监督的降维方法;
降维最多降到k-1维; 降维多少没有限制;
可以用于降维,还可以用于分类; 只用于降维;
选择分类性能最好的投影方向; 选择样本点投影具有最大方差的方向;
更明确,更能反映样本间差异; 目的较为模糊;

2.14.6 LDA优缺点

优缺点 简要说明
优点 1. 可以使用类别的先验知识;
2. 以标签、类别衡量差异性的有监督降维方式,相对于PCA的模糊性,其目的更明确,更能反映样本间的差异;
缺点 1. LDA不适合对非高斯分布样本进行降维;
2. LDA降维最多降到分类数k-1维;
3. LDA在样本分类信息依赖方差而不是均值时,降维效果不好;
4. LDA可能过度拟合数据。

2.15 主成分分析(PCA)

2.15.1 主成分分析(PCA)思想总结

  1. PCA就是将高维的数据通过线性变换投影到低维空间上去。
  2. 投影思想:找出最能够代表原始数据的投影方法。被PCA降掉的那些维度只能是那些噪声或是冗余的数据。
  3. 去冗余:去除可以被其他向量代表的线性相关向量,这部分信息量是多余的。
  4. 去噪声,去除较小特征值对应的特征向量,特征值的大小反映了变换后在特征向量方向上变换的幅度,幅度越大,说明这个方向上的元素差异也越大,要保留。
  5. 对角化矩阵,寻找极大线性无关组,保留较大的特征值,去除较小特征值,组成一个投影矩阵,对原始样本矩阵进行投影,得到降维后的新样本矩阵。
  6. 完成PCA的关键是——协方差矩阵。协方差矩阵,能同时表现不同维度间的相关性以及各个维度上的方差。协方差矩阵度量的是维度与维度之间的关系,而非样本与样本之间。
  7. 之所以对角化,因为对角化之后非对角上的元素都是0,达到去噪声的目的。对角化后的协方差矩阵,对角线上较小的新方差对应的就是那些该去掉的维度。所以我们只取那些含有较大能量(特征值)的维度,其余的就舍掉,即去冗余。

2.15.2 图解PCA核心思想

​ PCA可解决训练数据中存在数据特征过多或特征累赘的问题。核心思想是将m维特征映射到n维(n < m),这n维形成主元,是重构出来最能代表原始数据的正交特征。

​ 假设数据集是m个n维,$(\boldsymbol x^{(1)}, \boldsymbol x^{(2)}, \cdots, \boldsymbol x^{(m)})$。如果$n=2$,需要降维到$n’=1$,现在想找到某一维度方向代表这两个维度的数据。下图有$u_1, u_2$两个向量方向,但是哪个向量才是我们所想要的,可以更好代表原始数据集的呢?

从图可看出,$u_1$比$u_2$好,为什么呢?有以下两个主要评价指标:

  1. 样本点到这个直线的距离足够近。
  2. 样本点在这个直线上的投影能尽可能的分开。

如果我们需要降维的目标维数是其他任意维,则:

  1. 样本点到这个超平面的距离足够近。
  2. 样本点在这个超平面上的投影能尽可能的分开。

2.15.3 PCA算法推理

下面以基于最小投影距离为评价指标推理:

​ 假设数据集是m个n维,$(x^{(1)}, x^{(2)},…,x^{(m)})$,且数据进行了中心化。经过投影变换得到新坐标为 ${w_1,w_2,…,w_n}$,其中 $w$ 是标准正交基,即 $| w |_2 = 1$,$w^T_iw_j = 0$。

​ 经过降维后,新坐标为 ${ w1,w_2,…,w_n }$,其中 $n’$ 是降维后的目标维数。样本点 $x^{(i)}$ 在新坐标系下的投影为 $z^{(i)} = \left(z^{(i)}_1, z^{(i)}_2, …, z^{(i)}{n’} \right)$,其中 $z^{(i)}_j = w^T_j x^{(i)}$ 是 $x^{(i)} ​$ 在低维坐标系里第 j 维的坐标。

​ 如果用 $z^{(i)} $ 去恢复 $x^{(i)} $ ,则得到的恢复数据为 $\widehat{x}^{(i)} = \sum^{n’}_{j=1} x^{(i)}_j w_j = Wz^{(i)}$,其中 $W$为标准正交基组成的矩阵。

​ 考虑到整个样本集,样本点到这个超平面的距离足够近,目标变为最小化 $\sum^m_{i=1} | \hat{x}^{(i)} - x^{(i)} |^2_2$ 。对此式进行推理,可得:

​ 在推导过程中,分别用到了 $\overline{x}^{(i)} = Wz^{(i)}$ ,矩阵转置公式 $(AB)^T = B^TA^T$,$W^TW = I$,$z^{(i)} = W^Tx^{(i)}$ 以及矩阵的迹,最后两步是将代数和转为矩阵形式。
​ 由于 $W$ 的每一个向量 $wj$ 是标准正交基,$\sum^m{i=1} x^{(i)} \left( x^{(i)} \right)^T$ 是数据集的协方差矩阵,$\sum^m{i=1} \left( x^{(i)} \right)^T x^{(i)} $ 是一个常量。最小化 $\sum^m{i=1} | \hat{x}^{(i)} - x^{(i)} |^2_2$ 又可等价于

利用拉格朗日函数可得到

​ 对 $W$ 求导,可得 $-XX^TW + \lambda W = 0 $ ,也即 $ XX^TW = \lambda W $ 。 $ XX^T $ 是 $ n’ $ 个特征向量组成的矩阵,$\lambda$ 为$ XX^T $ 的特征值。$W$ 即为我们想要的矩阵。
​ 对于原始数据,只需要 $z^{(i)} = W^TX^{(i)}$ ,就可把原始数据集降维到最小投影距离的 $n’$ 维数据集。

​ 基于最大投影方差的推导,这里就不再赘述,有兴趣的同仁可自行查阅资料。

2.15.4 PCA算法流程总结

输入:$n​$ 维样本集 $D = \left( x^{(1)},x^{(2)},…,x^{(m)} \right)​$ ,目标降维的维数 $n’​$ 。

输出:降维后的新样本集 $D’ = \left( z^{(1)},z^{(2)},…,z^{(m)} \right)$ 。

主要步骤如下:

  1. 对所有的样本进行中心化,$ x^{(i)} = x^{(i)} - \frac{1}{m} \sum^m_{j=1} x^{(j)} $ 。
  2. 计算样本的协方差矩阵 $XX^T​$ 。
  3. 对协方差矩阵 $XX^T$ 进行特征值分解。
  4. 取出最大的 $n’ $ 个特征值对应的特征向量 ${ w1,w_2,…,w{n’} }$ 。
  5. 标准化特征向量,得到特征向量矩阵 $W$ 。
  6. 转化样本集中的每个样本 $z^{(i)} = W^T x^{(i)}$ 。
  7. 得到输出矩阵 $D’ = \left( z^{(1)},z^{(2)},…,z^{(n)} \right)​$ 。
    :在降维时,有时不明确目标维数,而是指定降维到的主成分比重阈值 $k(k \epsilon(0,1])​$ 。假设 $n​$ 个特征值为 $\lambda1 \geqslant \lambda_2 \geqslant … \geqslant \lambda_n​$ ,则 $n’​$ 可从 $\sum^{n’}{i=1} \lambdai \geqslant k \times \sum^n{i=1} \lambda_i ​$ 得到。

2.15.5 PCA算法主要优缺点

优缺点 简要说明
优点 1. 仅仅需要以方差衡量信息量,不受数据集以外的因素影响。 2.各主成分之间正交,可消除原始数据成分间的相互影响的因素。3. 计算方法简单,主要运算是特征值分解,易于实现。
缺点 1.主成分各个特征维度的含义具有一定的模糊性,不如原始样本特征的解释性强。2. 方差小的非主成分也可能含有对样本差异的重要信息,因降维丢弃可能对后续数据处理有影响。

2.15.6 降维的必要性及目的

降维的必要性

  1. 多重共线性和预测变量之间相互关联。多重共线性会导致解空间的不稳定,从而可能导致结果的不连贯。
  2. 高维空间本身具有稀疏性。一维正态分布有68%的值落于正负标准差之间,而在十维空间上只有2%。
  3. 过多的变量,对查找规律造成冗余麻烦。
  4. 仅在变量层面上分析可能会忽略变量之间的潜在联系。例如几个预测变量可能落入仅反映数据某一方面特征的一个组内。

降维的目的

  1. 减少预测变量的个数。
  2. 确保这些变量是相互独立的。
  3. 提供一个框架来解释结果。相关特征,特别是重要特征更能在数据中明确的显示出来;如果只有两维或者三维的话,更便于可视化展示。
  4. 数据在低维下更容易处理、更容易使用。
  5. 去除数据噪声。
  6. 降低算法运算开销。

2.15.7 KPCA与PCA的区别

​ 应用PCA算法前提是假设存在一个线性超平面,进而投影。那如果数据不是线性的呢?该怎么办?这时候就需要KPCA,数据集从 $n$ 维映射到线性可分的高维 $N >n$,然后再从 $N$ 维降维到一个低维度 $n’(n’<n<N)$ 。

​ KPCA用到了核函数思想,使用了核函数的主成分分析一般称为核主成分分析(Kernelized PCA, 简称KPCA)。

假设高维空间数据由 $n​$ 维空间的数据通过映射 $\phi​$ 产生。

​ $n$ 维空间的特征分解为:

​ 其映射为

​ 通过在高维空间进行协方差矩阵的特征值分解,然后用和PCA一样的方法进行降维。由于KPCA需要核函数的运算,因此它的计算量要比PCA大很多。

2.16 模型评估

2.16.1 模型评估常用方法?

​ 一般情况来说,单一评分标准无法完全评估一个机器学习模型。只用good和bad偏离真实场景去评估某个模型,都是一种欠妥的评估方式。下面介绍常用的分类模型和回归模型评估方法。

分类模型常用评估方法:

指标 描述
Accuracy 准确率
Precision 精准度/查准率
Recall 召回率/查全率
P-R曲线 查准率为纵轴,查全率为横轴,作图
F1 F1值
Confusion Matrix 混淆矩阵
ROC ROC曲线
AUC ROC曲线下的面积

回归模型常用评估方法:

指标 描述
Mean Square Error (MSE, RMSE) 平均方差
Absolute Error (MAE, RAE) 绝对误差
R-Squared R平方值

2.16.2 误差、偏差和方差有什么区别和联系

在机器学习中,Bias(偏差),Error(误差),和Variance(方差)存在以下区别和联系:

对于Error

  • 误差(error):一般地,我们把学习器的实际预测输出与样本的真是输出之间的差异称为“误差”。

  • Error = Bias + Variance + Noise,Error反映的是整个模型的准确度。

对于Noise:

噪声:描述了在当前任务上任何学习算法所能达到的期望泛化误差的下界,即刻画了学习问题本身的难度。

对于Bias:

  • Bias衡量模型拟合训练数据的能力(训练数据不一定是整个 training dataset,而是只用于训练它的那一部分数据,例如:mini-batch),Bias反映的是模型在样本上的输出与真实值之间的误差,即模型本身的精准度。
  • Bias 越小,拟合能力越高(可能产生overfitting);反之,拟合能力越低(可能产生underfitting)。
  • 偏差越大,越偏离真实数据,如下图第二行所示。

对于Variance:

  • 方差公式:$S{N}^{2}=\frac{1}{N}\sum{i=1}^{N}(x_{i}-\bar{x})^{2}$

  • Variance描述的是预测值的变化范围,离散程度,也就是离其期望值的距离。方差越大,数据的分布越分散,模型的稳定程度越差。

  • Variance反映的是模型每一次输出结果与模型输出期望之间的误差,即模型的稳定性。
  • Variance越小,模型的泛化的能力越高;反之,模型的泛化的能力越低。
  • 如果模型在训练集上拟合效果比较优秀,但是在测试集上拟合效果比较差劣,则方差较大,说明模型的稳定程度较差,出现这种现象可能是由于模型对训练集过拟合造成的。 如下图右列所示。

2.16.3 经验误差与泛化误差

经验误差(empirical error):也叫训练误差(training error),模型在训练集上的误差。

泛化误差(generalization error):模型在新样本集(测试集)上的误差称为“泛化误差”。

2.16.4 图解欠拟合、过拟合

根据不同的坐标方式,欠拟合与过拟合图解不同。

  1. 横轴为训练样本数量,纵轴为误差

如上图所示,我们可以直观看出欠拟合和过拟合的区别:

​ 模型欠拟合:在训练集以及测试集上同时具有较高的误差,此时模型的偏差较大;

​ 模型过拟合:在训练集上具有较低的误差,在测试集上具有较高的误差,此时模型的方差较大。

​ 模型正常:在训练集以及测试集上,同时具有相对较低的偏差以及方差。

  1. 横轴为模型复杂程度,纵轴为误差

​ 红线为测试集上的Error,蓝线为训练集上的Error

​ 模型欠拟合:模型在点A处,在训练集以及测试集上同时具有较高的误差,此时模型的偏差较大。

​ 模型过拟合:模型在点C处,在训练集上具有较低的误差,在测试集上具有较高的误差,此时模型的方差较大。

​ 模型正常:模型复杂程度控制在点B处为最优。

  1. 横轴为正则项系数,纵轴为误差

​ 红线为测试集上的Error,蓝线为训练集上的Error

​ 模型欠拟合:模型在点C处,在训练集以及测试集上同时具有较高的误差,此时模型的偏差较大。

​ 模型过拟合:模型在点A处,在训练集上具有较低的误差,在测试集上具有较高的误差,此时模型的方差较大。 它通常发生在模型过于复杂的情况下,如参数过多等,会使得模型的预测性能变弱,并且增加数据的波动性。虽然模型在训练时的效果可以表现的很完美,基本上记住了数据的全部特点,但这种模型在未知数据的表现能力会大减折扣,因为简单的模型泛化能力通常都是很弱的。

​ 模型正常:模型复杂程度控制在点B处为最优。

2.16.5 如何解决过拟合与欠拟合

如何解决欠拟合:

  1. 添加其他特征项。组合、泛化、相关性、上下文特征、平台特征等特征是特征添加的重要手段,有时候特征项不够会导致模型欠拟合。
  2. 添加多项式特征。例如将线性模型添加二次项或三次项使模型泛化能力更强。例如,FM(Factorization Machine)模型、FFM(Field-aware Factorization Machine)模型,其实就是线性模型,增加了二阶多项式,保证了模型一定的拟合程度。
  3. 可以增加模型的复杂程度。
  4. 减小正则化系数。正则化的目的是用来防止过拟合的,但是现在模型出现了欠拟合,则需要减少正则化参数。

如何解决过拟合:

  1. 重新清洗数据,数据不纯会导致过拟合,此类情况需要重新清洗数据。
  2. 增加训练样本数量。
  3. 降低模型复杂程度。
  4. 增大正则项系数。
  5. 采用dropout方法,dropout方法,通俗的讲就是在训练的时候让神经元以一定的概率不工作。
  6. early stopping。
  7. 减少迭代次数。
  8. 增大学习率。
  9. 添加噪声数据。
  10. 树结构中,可以对树进行剪枝。
  11. 减少特征项。

欠拟合和过拟合这些方法,需要根据实际问题,实际模型,进行选择。

2.16.6 交叉验证的主要作用

​ 为了得到更为稳健可靠的模型,对模型的泛化误差进行评估,得到模型泛化误差的近似值。当有多个模型可以选择时,我们通常选择“泛化误差”最小的模型。

​ 交叉验证的方法有许多种,但是最常用的是:留一交叉验证、k折交叉验证。

2.16.7 理解k折交叉验证

  1. 将含有N个样本的数据集,分成K份,每份含有N/K个样本。选择其中1份作为测试集,另外K-1份作为训练集,测试集就有K种情况。
  2. 在每种情况中,用训练集训练模型,用测试集测试模型,计算模型的泛化误差。
  3. 交叉验证重复K次,每份验证一次,平均K次的结果或者使用其它结合方式,最终得到一个单一估测,得到模型最终的泛化误差。
  4. 将K种情况下,模型的泛化误差取均值,得到模型最终的泛化误差。
  5. 一般$2\leqslant K \leqslant10$。 k折交叉验证的优势在于,同时重复运用随机产生的子样本进行训练和验证,每次的结果验证一次,10折交叉验证是最常用的。
  6. 训练集中样本数量要足够多,一般至少大于总样本数的50%。
  7. 训练集和测试集必须从完整的数据集中均匀取样。均匀取样的目的是希望减少训练集、测试集与原数据集之间的偏差。当样本数量足够多时,通过随机取样,便可以实现均匀取样的效果。

2.16.8 混淆矩阵

第一种混淆矩阵:

真实情况T or F 预测为正例1,P 预测为负例0,N
本来label标记为1,预测结果真为T、假为F TP(预测为1,实际为1) FN(预测为0,实际为1)
本来label标记为0,预测结果真为T、假为F FP(预测为1,实际为0) TN(预测为0,实际也为0)

第二种混淆矩阵:

预测情况P or N 实际label为1,预测对了为T 实际label为0,预测对了为T
预测为正例1,P TP(预测为1,实际为1) FP(预测为1,实际为0)
预测为负例0,N FN(预测为0,实际为1) TN(预测为0,实际也为0)

2.16.9 错误率及精度

  1. 错误率(Error Rate):分类错误的样本数占样本总数的比例。
  2. 精度(accuracy):分类正确的样本数占样本总数的比例。

2.16.10 查准率与查全率

将算法预测的结果分成四种情况:

  1. 正确肯定(True Positive,TP):预测为真,实际为真
  2. 正确否定(True Negative,TN):预测为假,实际为假
  3. 错误肯定(False Positive,FP):预测为真,实际为假
  4. 错误否定(False Negative,FN):预测为假,实际为真

则:

查准率(Precision)=TP/(TP+FP)

理解:预测出为阳性的样本中,正确的有多少。区别准确率(正确预测出的样本,包括正确预测为阳性、阴性,占总样本比例)。
例,在所有我们预测有恶性肿瘤的病人中,实际上有恶性肿瘤的病人的百分比,越高越好。

查全率(Recall)=TP/(TP+FN)

理解:正确预测为阳性的数量占总样本中阳性数量的比例。
例,在所有实际上有恶性肿瘤的病人中,成功预测有恶性肿瘤的病人的百分比,越高越好。

2.16.11 ROC与AUC

​ ROC全称是“受试者工作特征”(Receiver Operating Characteristic)。

​ ROC曲线的面积就是AUC(Area Under Curve)。

​ AUC用于衡量“二分类问题”机器学习算法性能(泛化能力)。

​ ROC曲线,通过将连续变量设定出多个不同的临界值,从而计算出一系列真正率和假正率,再以假正率为横坐标、真正率为纵坐标绘制成曲线,曲线下面积越大,推断准确性越高。在ROC曲线上,最靠近坐标图左上方的点为假正率和真正率均较高的临界值。

​ 对于分类器,或者说分类算法,评价指标主要有Precision,Recall,F-score。下图是一个ROC曲线的示例。

ROC曲线的横坐标为False Positive Rate(FPR),纵坐标为True Positive Rate(TPR)。其中

​ 下面着重介绍ROC曲线图中的四个点和一条线。
​ 第一个点(0,1),即FPR=0, TPR=1,这意味着FN(False Negative)=0,并且FP(False Positive)=0。意味着这是一个完美的分类器,它将所有的样本都正确分类。
​ 第二个点(1,0),即FPR=1,TPR=0,意味着这是一个最糟糕的分类器,因为它成功避开了所有的正确答案。
​ 第三个点(0,0),即FPR=TPR=0,即FP(False Positive)=TP(True Positive)=0,可以发现该分类器预测所有的样本都为负样本(Negative)。
​ 第四个点(1,1),即FPR=TPR=1,分类器实际上预测所有的样本都为正样本。
​ 经过以上分析,ROC曲线越接近左上角,该分类器的性能越好。

​ ROC曲线所覆盖的面积称为AUC(Area Under Curve),可以更直观的判断学习器的性能,AUC越大则性能越好。

2.16.12 如何画ROC曲线

​ 下图是一个示例,图中共有20个测试样本,“Class”一栏表示每个测试样本真正的标签(p表示正样本,n表示负样本),“Score”表示每个测试样本属于正样本的概率。

步骤:
1、假设已经得出一系列样本被划分为正类的概率,按照大小排序。
2、从高到低,依次将“Score”值作为阈值threshold,当测试样本属于正样本的概率大于或等于这个threshold时,我们认为它为正样本,否则为负样本。举例来说,对于图中的第4个样本,其“Score”值为0.6,那么样本1,2,3,4都被认为是正样本,因为它们的“Score”值都大于等于0.6,而其他样本则都认为是负样本。
3、每次选取一个不同的threshold,得到一组FPR和TPR,即ROC曲线上的一点。以此共得到20组FPR和TPR的值。
4、根据3、中的每个坐标点,画图。

2.16.13 如何计算TPR,FPR

1、分析数据
y_true = [0, 0, 1, 1];scores = [0.1, 0.4, 0.35, 0.8];
2、列表

样本 预测属于P的概率(score) 真实类别
y[0] 0.1 N
y[1] 0.4 N
y[2] 0.35 P
y[3] 0.8 P

3、将截断点依次取为score值,计算TPR和FPR。
当截断点为0.1时:
说明只要score>=0.1,它的预测类别就是正例。 因为4个样本的score都大于等于0.1,所以,所有样本的预测类别都为P。
scores = [0.1, 0.4, 0.35, 0.8];y_true = [0, 0, 1, 1];y_pred = [1, 1, 1, 1];
正例与反例信息如下:

正例 反例
正例 TP=2 FN=0
反例 FP=2 TN=0

由此可得:
TPR = TP/(TP+FN) = 1; FPR = FP/(TN+FP) = 1;

当截断点为0.35时:
scores = [0.1, 0.4, 0.35, 0.8];y_true = [0, 0, 1, 1];y_pred = [0, 1, 1, 1];
正例与反例信息如下:

正例 反例
正例 TP=2 FN=0
反例 FP=1 TN=1

由此可得:
TPR = TP/(TP+FN) = 1; FPR = FP/(TN+FP) = 0.5;

当截断点为0.4时:
scores = [0.1, 0.4, 0.35, 0.8];y_true = [0, 0, 1, 1];y_pred = [0, 1, 0, 1];
正例与反例信息如下:

正例 反例
正例 TP=1 FN=1
反例 FP=1 TN=1

由此可得:
TPR = TP/(TP+FN) = 0.5; FPR = FP/(TN+FP) = 0.5;

当截断点为0.8时:
scores = [0.1, 0.4, 0.35, 0.8];y_true = [0, 0, 1, 1];y_pred = [0, 0, 0, 1];

正例与反例信息如下:

正例 反例
正例 TP=1 FN=1
反例 FP=0 TN=2

由此可得:
TPR = TP/(TP+FN) = 0.5; FPR = FP/(TN+FP) = 0;

4、根据TPR、FPR值,以FPR为横轴,TPR为纵轴画图。

2.16.14 如何计算AUC

  • 将坐标点按照横坐标FPR排序 。
  • 计算第$i$个坐标点和第$i+1$个坐标点的间距$dx$ 。
  • 获取第$i$或者$i+1$个坐标点的纵坐标y。
  • 计算面积微元$ds=ydx$。
  • 对面积微元进行累加,得到AUC。

2.16.15 为什么使用Roc和Auc评价分类器

​ 模型有很多评估方法,为什么还要使用ROC和AUC呢?
​ 因为ROC曲线有个很好的特性:当测试集中的正负样本的分布变换的时候,ROC曲线能够保持不变。在实际的数据集中经常会出现样本类不平衡,即正负样本比例差距较大,而且测试数据中的正负样本也可能随着时间变化。

2.16.16 直观理解AUC

​ 下图展现了三种AUC的值:

​ AUC是衡量二分类模型优劣的一种评价指标,表示正例排在负例前面的概率。其他评价指标有精确度、准确率、召回率,而AUC比这三者更为常用。
​ 一般在分类模型中,预测结果都是以概率的形式表现,如果要计算准确率,通常都会手动设置一个阈值来将对应的概率转化成类别,这个阈值也就很大程度上影响了模型准确率的计算。
​ 举例:
​ 现在假设有一个训练好的二分类器对10个正负样本(正例5个,负例5个)预测,得分按高到低排序得到的最好预测结果为[1, 1, 1, 1, 1, 0, 0, 0, 0, 0],即5个正例均排在5个负例前面,正例排在负例前面的概率为100%。然后绘制其ROC曲线,由于是10个样本,除去原点我们需要描10个点,如下:

​ 描点方式按照样本预测结果的得分高低从左至右开始遍历。从原点开始,每遇到1便向y轴正方向移动y轴最小步长1个单位,这里是1/5=0.2;每遇到0则向x轴正方向移动x轴最小步长1个单位,这里也是0.2。不难看出,上图的AUC等于1,印证了正例排在负例前面的概率的确为100%。

​ 假设预测结果序列为[1, 1, 1, 1, 0, 1, 0, 0, 0, 0]。

​ 计算上图的AUC为0.96与计算正例与排在负例前面的概率0.8 × 1 + 0.2 × 0.8 = 0.96相等,而左上角阴影部分的面积则是负例排在正例前面的概率0.2 × 0.2 = 0.04。

​ 假设预测结果序列为[1, 1, 1, 0, 1, 0, 1, 0, 0, 0]。

​ 计算上图的AUC为0.88与计算正例与排在负例前面的概率0.6 × 1 + 0.2 × 0.8 + 0.2 × 0.6 = 0.88相等,左上角阴影部分的面积是负例排在正例前面的概率0.2 × 0.2 × 3 = 0.12。

2.16.17 代价敏感错误率与代价曲线

不同的错误会产生不同代价。以二分法为例,设置代价矩阵如下:

当判断正确的时候,值为0,不正确的时候,分别为$Cost{01}​$和$Cost{10}​$ 。

$Cost_{10}$:表示实际为反例但预测成正例的代价。

$Cost_{01}$:表示实际为正例但是预测为反例的代价。

代价敏感错误率=样本中由模型得到的错误值与代价乘积之和 / 总样本。
其数学表达式为:

$D^{+}、D^{-}​$分别代表样例集的正例子集和反例子集,x是预测值,y是真实值。

代价曲线
在均等代价时,ROC曲线不能直接反应出模型的期望总体代价,而代价曲线可以。
代价曲线横轴为[0,1]的正例函数代价:

其中p是样本为正例的概率。

代价曲线纵轴维[0,1]的归一化代价:

其中FPR为假阳率,FNR=1-TPR为假阴率。

注:ROC每个点,对应代价平面上一条线。

例如,ROC上(TPR,FPR),计算出FNR=1-TPR,在代价平面上绘制一条从(0,FPR)到(1,FNR)的线段,面积则为该条件下期望的总体代价。所有线段下界面积,所有条件下学习器的期望总体代价。

2.16.18 模型有哪些比较检验方法

正确性分析:模型稳定性分析,稳健性分析,收敛性分析,变化趋势分析,极值分析等。
有效性分析:误差分析,参数敏感性分析,模型对比检验等。
有用性分析:关键数据求解,极值点,拐点,变化趋势分析,用数据验证动态模拟等。
高效性分析:时空复杂度分析与现有进行比较等。

2.16.19 为什么使用标准差

方差公式为:$S^2{N}=\frac{1}{N}\sum{i=1}^{N}(x_{i}-\bar{x})^{2}​$

标准差公式为:$S{N}=\sqrt{\frac{1}{N}\sum{i=1}^{N}(x_{i}-\bar{x})^{2}}​$

样本标准差公式为:$S{N}=\sqrt{\frac{1}{N-1}\sum{i=1}^{N}(x_{i}-\bar{x})^{2}}​$

与方差相比,使用标准差来表示数据点的离散程度有3个好处:
1、表示离散程度的数字与样本数据点的数量级一致,更适合对数据样本形成感性认知。

2、表示离散程度的数字单位与样本数据的单位一致,更方便做后续的分析运算。

3、在样本数据大致符合正态分布的情况下,标准差具有方便估算的特性:68%的数据点落在平均值前后1个标准差的范围内、95%的数据点落在平均值前后2个标准差的范围内,而99%的数据点将会落在平均值前后3个标准差的范围内。

2.16.20 类别不平衡产生原因

​ 类别不平衡(class-imbalance)是指分类任务中不同类别的训练样例数目差别很大的情况。

产生原因:

​ 分类学习算法通常都会假设不同类别的训练样例数目基本相同。如果不同类别的训练样例数目差别很大,则会影响学习结果,测试结果变差。例如二分类问题中有998个反例,正例有2个,那学习方法只需返回一个永远将新样本预测为反例的分类器,就能达到99.8%的精度;然而这样的分类器没有价值。

2.16.21 常见的类别不平衡问题解决方法

  防止类别不平衡对学习造成的影响,在构建分类模型之前,需要对分类不平衡性问题进行处理。主要解决方法有:

1、扩大数据集

​ 增加包含小类样本数据的数据,更多的数据能得到更多的分布信息。

2、对大类数据欠采样

​ 减少大类数据样本个数,使与小样本个数接近。
​ 缺点:欠采样操作时若随机丢弃大类样本,可能会丢失重要信息。
​ 代表算法:EasyEnsemble。其思想是利用集成学习机制,将大类划分为若干个集合供不同的学习器使用。相当于对每个学习器都进行欠采样,但对于全局则不会丢失重要信息。

3、对小类数据过采样

​ 过采样:对小类的数据样本进行采样来增加小类的数据样本个数。

​ 代表算法:SMOTE和ADASYN。

​ SMOTE:通过对训练集中的小类数据进行插值来产生额外的小类样本数据。

​ 新的少数类样本产生的策略:对每个少数类样本a,在a的最近邻中随机选一个样本b,然后在a、b之间的连线上随机选一点作为新合成的少数类样本。
​ ADASYN:根据学习难度的不同,对不同的少数类别的样本使用加权分布,对于难以学习的少数类的样本,产生更多的综合数据。 通过减少类不平衡引入的偏差和将分类决策边界自适应地转移到困难的样本两种手段,改善了数据分布。

4、使用新评价指标

​ 如果当前评价指标不适用,则应寻找其他具有说服力的评价指标。比如准确度这个评价指标在类别不均衡的分类任务中并不适用,甚至进行误导。因此在类别不均衡分类任务中,需要使用更有说服力的评价指标来对分类器进行评价。

5、选择新算法

​ 不同的算法适用于不同的任务与数据,应该使用不同的算法进行比较。

6、数据代价加权

​ 例如当分类任务是识别小类,那么可以对分类器的小类样本数据增加权值,降低大类样本的权值,从而使得分类器将重点集中在小类样本身上。

7、转化问题思考角度

​ 例如在分类问题时,把小类的样本作为异常点,将问题转化为异常点检测或变化趋势检测问题。 异常点检测即是对那些罕见事件进行识别。变化趋势检测区别于异常点检测在于其通过检测不寻常的变化趋势来识别。

8、将问题细化分析

​ 对问题进行分析与挖掘,将问题划分成多个更小的问题,看这些小问题是否更容易解决。

2.17 决策树

2.17.1 决策树的基本原理

​ 决策树(Decision Tree)是一种分而治之的决策过程。一个困难的预测问题,通过树的分支节点,被划分成两个或多个较为简单的子集,从结构上划分为不同的子问题。将依规则分割数据集的过程不断递归下去(Recursive Partitioning)。随着树的深度不断增加,分支节点的子集越来越小,所需要提的问题数也逐渐简化。当分支节点的深度或者问题的简单程度满足一定的停止规则(Stopping Rule)时, 该分支节点会停止分裂,此为自上而下的停止阈值(Cutoff Threshold)法;有些决策树也使用自下而上的剪枝(Pruning)法。

2.17.2 决策树的三要素?

​ 一棵决策树的生成过程主要分为下3个部分:

​ 1、特征选择:从训练数据中众多的特征中选择一个特征作为当前节点的分裂标准,如何选择特征有着很多不同量化评估标准,从而衍生出不同的决策树算法。

​ 2、决策树生成:根据选择的特征评估标准,从上至下递归地生成子节点,直到数据集不可分则决策树停止生长。树结构来说,递归结构是最容易理解的方式。

​ 3、剪枝:决策树容易过拟合,一般来需要剪枝,缩小树结构规模、缓解过拟合。剪枝技术有预剪枝和后剪枝两种。

2.17.3 决策树学习基本算法

2.17.4 决策树算法优缺点

决策树算法的优点

1、决策树算法易理解,机理解释起来简单。

2、决策树算法可以用于小数据集。

3、决策树算法的时间复杂度较小,为用于训练决策树的数据点的对数。

4、相比于其他算法智能分析一种类型变量,决策树算法可处理数字和数据的类别。

5、能够处理多输出的问题。

6、对缺失值不敏感。

7、可以处理不相关特征数据。

8、效率高,决策树只需要一次构建,反复使用,每一次预测的最大计算次数不超过决策树的深度。

决策树算法的缺点

1、对连续性的字段比较难预测。

2、容易出现过拟合。

3、当类别太多时,错误可能就会增加的比较快。

4、在处理特征关联性比较强的数据时表现得不是太好。

5、对于各类别样本数量不一致的数据,在决策树当中,信息增益的结果偏向于那些具有更多数值的特征。

2.17.5 熵的概念以及理解

​ 熵:度量随机变量的不确定性。
​ 定义:假设随机变量X的可能取值有$x{1},x{2},…,x{n}$,对于每一个可能的取值$x{i}$,其概率为$P(X=x{i})=p{i},i=1,2…,n$。随机变量的熵为:

​ 对于样本集合,假设样本有k个类别,每个类别的概率为$\frac{|C{k}|}{|D|}$,其中 ${|C{k}|}{|D|}$为类别为k的样本个数,$|D|​$为样本总数。样本集合D的熵为:

2.17.6 信息增益的理解

​ 定义:以某特征划分数据集前后的熵的差值。
​ 熵可以表示样本集合的不确定性,熵越大,样本的不确定性就越大。因此可以使用划分前后集合熵的差值来衡量使用当前特征对于样本集合D划分效果的好坏。 ​ 假设划分前样本集合D的熵为H(D)。使用某个特征A划分数据集D,计算划分后的数据子集的熵为H(D|A)。
​ 则信息增益为:

注:在决策树构建的过程中我们总是希望集合往最快到达纯度更高的子集合方向发展,因此我们总是选择使得信息增益最大的特征来划分当前数据集D。
​ 思想:计算所有特征划分数据集D,得到多个特征划分数据集D的信息增益,从这些信息增益中选择最大的,因而当前结点的划分特征便是使信息增益最大的划分所使用的特征。
​ 另外这里提一下信息增益比相关知识:
​ $信息增益比=惩罚参数\times信息增益$
​ 信息增益比本质:在信息增益的基础之上乘上一个惩罚参数。特征个数较多时,惩罚参数较小;特征个数较少时,惩罚参数较大。
​ 惩罚参数:数据集D以特征A作为随机变量的熵的倒数。

2.17.7 剪枝处理的作用及策略

​ 剪枝处理是决策树学习算法用来解决过拟合问题的一种办法。

​ 在决策树算法中,为了尽可能正确分类训练样本, 节点划分过程不断重复, 有时候会造成决策树分支过多,以至于将训练样本集自身特点当作泛化特点, 而导致过拟合。 因此可以采用剪枝处理来去掉一些分支来降低过拟合的风险。

​ 剪枝的基本策略有预剪枝(pre-pruning)和后剪枝(post-pruning)。

​ 预剪枝:在决策树生成过程中,在每个节点划分前先估计其划分后的泛化性能, 如果不能提升,则停止划分,将当前节点标记为叶结点。

​ 后剪枝:生成决策树以后,再自下而上对非叶结点进行考察, 若将此节点标记为叶结点可以带来泛化性能提升,则修改之。

2.18 支持向量机

2.18.1 什么是支持向量机

​ 支持向量:在求解的过程中,会发现只根据部分数据就可以确定分类器,这些数据称为支持向量。

​ 支持向量机(Support Vector Machine,SVM):其含义是通过支持向量运算的分类器。

​ 在一个二维环境中,其中点R,S,G点和其它靠近中间黑线的点可以看作为支持向量,它们可以决定分类器,即黑线的具体参数。

​ 支持向量机是一种二分类模型,它的目的是寻找一个超平面来对样本进行分割,分割的原则是边界最大化,最终转化为一个凸二次规划问题来求解。由简至繁的模型包括:

​ 当训练样本线性可分时,通过硬边界(hard margin)最大化,学习一个线性可分支持向量机;

​ 当训练样本近似线性可分时,通过软边界(soft margin)最大化,学习一个线性支持向量机;

​ 当训练样本线性不可分时,通过核技巧和软边界最大化,学习一个非线性支持向量机;

2.18.2 支持向量机能解决哪些问题

线性分类

​ 在训练数据中,每个数据都有n个的属性和一个二分类类别标志,我们可以认为这些数据在一个n维空间里。我们的目标是找到一个n-1维的超平面,这个超平面可以将数据分成两部分,每部分数据都属于同一个类别。

​ 这样的超平面有很多,假如我们要找到一个最佳的超平面。此时,增加一个约束条件:要求这个超平面到每边最近数据点的距离是最大的,成为最大边距超平面。这个分类器即为最大边距分类器。

非线性分类

​ SVM的一个优势是支持非线性分类。它结合使用拉格朗日乘子法(Lagrange Multiplier)和KKT(Karush Kuhn Tucker)条件,以及核函数可以生成非线性分类器。

2.18.3 核函数特点及其作用

​ 引入核函数目的:把原坐标系里线性不可分的数据用核函数Kernel投影到另一个空间,尽量使得数据在新的空间里线性可分。
​ 核函数方法的广泛应用,与其特点是分不开的:

1)核函数的引入避免了“维数灾难”,大大减小了计算量。而输入空间的维数n对核函数矩阵无影响。因此,核函数方法可以有效处理高维输入。

2)无需知道非线性变换函数Φ的形式和参数。

3)核函数的形式和参数的变化会隐式地改变从输入空间到特征空间的映射,进而对特征空间的性质产生影响,最终改变各种核函数方法的性能。

4)核函数方法可以和不同的算法相结合,形成多种不同的基于核函数技术的方法,且这两部分的设计可以单独进行,并可以为不同的应用选择不同的核函数和算法。

2.18.4 SVM为什么引入对偶问题

1,对偶问题将原始问题中的约束转为了对偶问题中的等式约束,对偶问题往往更加容易求解。

2,可以很自然的引用核函数(拉格朗日表达式里面有内积,而核函数也是通过内积进行映射的)。

3,在优化理论中,目标函数 f(x) 会有多种形式:如果目标函数和约束条件都为变量 x 的线性函数,称该问题为线性规划;如果目标函数为二次函数,约束条件为线性函数,称该最优化问题为二次规划;如果目标函数或者约束条件均为非线性函数,称该最优化问题为非线性规划。每个线性规划问题都有一个与之对应的对偶问题,对偶问题有非常良好的性质,以下列举几个:

​ a, 对偶问题的对偶是原问题;

​ b, 无论原始问题是否是凸的,对偶问题都是凸优化问题;

​ c, 对偶问题可以给出原始问题一个下界;

​ d, 当满足一定条件时,原始问题与对偶问题的解是完全等价的。

2.18.5 如何理解SVM中的对偶问题

在硬边界支持向量机中,问题的求解可以转化为凸二次规划问题。

​ 假设优化目标为

step 1. 转化问题:

上式等价于原问题,因为若满足(1)中不等式约束,则(2)式求max时,$\alpha_i(1 - y_i(\boldsymbol w^T\boldsymbol x_i+b))$必须取0,与(1)等价;若不满足(1)中不等式约束,(2)中求max会得到无穷大。 交换min和max获得其对偶问题:

交换之后的对偶问题和原问题并不相等,上式的解小于等于原问题的解。

step 2.现在的问题是如何找到问题(1) 的最优值的一个最好的下界?

若方程组(3)无解, 则v是问题(1)的一个下界。若(3)有解, 则

由逆否命题得:若

则(3)无解。

那么v是问题

(1)的一个下界。
要求得一个好的下界,取最大值即可

step 3. 令

$p^$为原问题的最小值,对应的$w,b$分别为$w^,b^*$,则对于任意的$a>0$:

则 $\min_{\boldsymbol w, b} L(\boldsymbol w, b,\boldsymbol a)$是问题(1)的一个下界。

此时,取最大值即可求得好的下界,即

2.18.7 常见的核函数有哪些

核函数 表达式 备注
Linear Kernel线性核 $k(x,y)=x^{t}y+c$
Polynomial Kernel多项式核 $k(x,y)=(ax^{t}y+c)^{d}$ $d\geqslant1$为多项式的次数
Exponential Kernel指数核 $k(x,y)=exp(-\frac{\left \ x-y \right \ }{2\sigma ^{2}})$ $\sigma>0$
Gaussian Kernel高斯核 $k(x,y)=exp(-\frac{\left \ x-y \right \ ^{2}}{2\sigma ^{2}})$ $\sigma$为高斯核的带宽,$\sigma>0$,
Laplacian Kernel拉普拉斯核 $k(x,y)=exp(-\frac{\left \ x-y \right \ }{\sigma})$ $\sigma>0$
ANOVA Kernel $k(x,y)=exp(-\sigma(x^{k}-y^{k})^{2})^{d}$
Sigmoid Kernel $k(x,y)=tanh(ax^{t}y+c)$ $tanh$为双曲正切函数,$a>0,c<0$

2.18.9 SVM主要特点

特点:

(1) SVM方法的理论基础是非线性映射,SVM利用内积核函数代替向高维空间的非线性映射。
(2) SVM的目标是对特征空间划分得到最优超平面,SVM方法核心是最大化分类边界。
(3) 支持向量是SVM的训练结果,在SVM分类决策中起决定作用的是支持向量。
(4) SVM是一种有坚实理论基础的新颖的适用小样本学习方法。它基本上不涉及概率测度及大数定律等,也简化了通常的分类和回归等问题。
(5) SVM的最终决策函数只由少数的支持向量所确定,计算的复杂性取决于支持向量的数目,而不是样本空间的维数,这在某种意义上避免了“维数灾难”。
(6) 少数支持向量决定了最终结果,这不但可以帮助我们抓住关键样本、“剔除”大量冗余样本,而且注定了该方法不但算法简单,而且具有较好的“鲁棒性”。这种鲁棒性主要体现在:
​ ①增、删非支持向量样本对模型没有影响;
​ ②支持向量样本集具有一定的鲁棒性;
​ ③有些成功的应用中,SVM方法对核的选取不敏感
(7) SVM学习问题可以表示为凸优化问题,因此可以利用已知的有效算法发现目标函数的全局最小值。而其他分类方法(如基于规则的分类器和人工神经网络)都采用一种基于贪心学习的策略来搜索假设空间,这种方法一般只能获得局部最优解。
(8) SVM通过最大化决策边界的边缘来控制模型的能力。尽管如此,用户必须提供其他参数,如使用核函数类型和引入松弛变量等。
(9) SVM在小样本训练集上能够得到比其它算法好很多的结果。SVM优化目标是结构化风险最小,而不是经验风险最小,避免了过拟合问题,通过margin的概念,得到对数据分布的结构化描述,减低了对数据规模和数据分布的要求,有优秀的泛化能力。
(10) 它是一个凸优化问题,因此局部最优解一定是全局最优解的优点。

2.18.10 SVM主要缺点

(1) SVM算法对大规模训练样本难以实施
​ SVM的空间消耗主要是存储训练样本和核矩阵,由于SVM是借助二次规划来求解支持向量,而求解二次规划将涉及m阶矩阵的计算(m为样本的个数),当m数目很大时该矩阵的存储和计算将耗费大量的机器内存和运算时间。
​ 如果数据量很大,SVM的训练时间就会比较长,如垃圾邮件的分类检测,没有使用SVM分类器,而是使用简单的朴素贝叶斯分类器,或者是使用逻辑回归模型分类。

(2) 用SVM解决多分类问题存在困难

​ 经典的支持向量机算法只给出了二类分类的算法,而在实际应用中,一般要解决多类的分类问题。可以通过多个二类支持向量机的组合来解决。主要有一对多组合模式、一对一组合模式和SVM决策树;再就是通过构造多个分类器的组合来解决。主要原理是克服SVM固有的缺点,结合其他算法的优势,解决多类问题的分类精度。如:与粗糙集理论结合,形成一种优势互补的多类问题的组合分类器。

(3) 对缺失数据敏感,对参数和核函数的选择敏感

​ 支持向量机性能的优劣主要取决于核函数的选取,所以对于一个实际问题而言,如何根据实际的数据模型选择合适的核函数从而构造SVM算法。目前比较成熟的核函数及其参数的选择都是人为的,根据经验来选取的,带有一定的随意性。在不同的问题领域,核函数应当具有不同的形式和参数,所以在选取时候应该将领域知识引入进来,但是目前还没有好的方法来解决核函数的选取问题。

2.18.11 逻辑回归与SVM的异同

相同点:

  • LR和SVM都是分类算法。
  • LR和SVM都是监督学习算法。
  • LR和SVM都是判别模型
  • 如果不考虑核函数,LR和SVM都是线性分类算法,也就是说他们的分类决策面都是线性的。
    说明:LR也是可以用核函数的.但LR通常不采用核函数的方法。(计算量太大

不同点:

1、LR采用log损失,SVM采用合页(hinge)损失。
逻辑回归的损失函数:

支持向量机的目标函数:

​ 逻辑回归方法基于概率理论,假设样本为1的概率可以用sigmoid函数来表示,然后通过极大似然估计的方法估计出参数的值。
​ 支持向量机基于几何边界最大化原理,认为存在最大几何边界的分类面为最优分类面。

2、LR对异常值敏感,SVM对异常值不敏感

​ 支持向量机只考虑局部的边界线附近的点,而逻辑回归考虑全局。LR模型找到的那个超平面,是尽量让所有点都远离他,而SVM寻找的那个超平面,是只让最靠近中间分割线的那些点尽量远离,即只用到那些支持向量的样本。
​ 支持向量机改变非支持向量样本并不会引起决策面的变化。
​ 逻辑回归中改变任何样本都会引起决策面的变化。

3、计算复杂度不同。对于海量数据,SVM的效率较低,LR效率比较高

​ 当样本较少,特征维数较低时,SVM和LR的运行时间均比较短,SVM较短一些。准确率的话,LR明显比SVM要高。当样本稍微增加些时,SVM运行时间开始增长,但是准确率赶超了LR。SVM时间虽长,但在可接受范围内。当数据量增长到20000时,特征维数增长到200时,SVM的运行时间剧烈增加,远远超过了LR的运行时间。但是准确率却和LR相差无几。(这其中主要原因是大量非支持向量参与计算,造成SVM的二次规划问题)

4、对非线性问题的处理方式不同

​ LR主要靠特征构造,必须组合交叉特征,特征离散化。SVM也可以这样,还可以通过核函数kernel(因为只有支持向量参与核计算,计算复杂度不高)。由于可以利用核函数,SVM则可以通过对偶求解高效处理。LR则在特征空间维度很高时,表现较差。

5、SVM的损失函数就自带正则
​ 损失函数中的1/2||w||^2项,这就是为什么SVM是结构风险最小化算法的原因!!!而LR必须另外在损失函数上添加正则项!!!**

6、SVM自带结构风险最小化,LR则是经验风险最小化

7、SVM会用核函数而LR一般不用核函数。

2.19 贝叶斯分类器

2.19.1 图解极大似然估计

极大似然估计的原理,用一张图片来说明,如下图所示:

​ 例:有两个外形完全相同的箱子,1号箱有99只白球,1只黑球;2号箱有1只白球,99只黑球。在一次实验中,取出的是黑球,请问是从哪个箱子中取出的?

​ 一般的根据经验想法,会猜测这只黑球最像是从2号箱取出,此时描述的“最像”就有“最大似然”的意思,这种想法常称为“最大似然原理”。

2.19.2 极大似然估计原理

​ 总结起来,最大似然估计的目的就是:利用已知的样本结果,反推最有可能(最大概率)导致这样结果的参数值。

​ 极大似然估计是建立在极大似然原理的基础上的一个统计方法。极大似然估计提供了一种给定观察数据来评估模型参数的方法,即:“模型已定,参数未知”。通过若干次试验,观察其结果,利用试验结果得到某个参数值能够使样本出现的概率为最大,则称为极大似然估计。

​ 由于样本集中的样本都是独立同分布,可以只考虑一类样本集$D$,来估计参数向量$\vec\theta$。记已知的样本集为:

似然函数(likelihood function):联合概率密度函数$p(D|\vec\theta )$称为相对于$\vec x{1},\vec x{2},…,\vec x_{n}$的$\vec\theta$的似然函数。

如果$\hat{\vec\theta}$是参数空间中能使似然函数$l(\vec\theta)$最大的$\vec\theta$值,则$\hat{\vec\theta}$应该是“最可能”的参数值,那么$\hat{\vec\theta}​$就是$\theta$的极大似然估计量。它是样本集的函数,记作:

$\hat{\vec\theta}(\vec x{1},\vec x{2},…,\vec x_{n})$称为极大似然函数估计值。

2.19.3 贝叶斯分类器基本原理

​ 贝叶斯决策论通过相关概率已知的情况下利用误判损失来选择最优的类别分类。
假设有$N$种可能的分类标记,记为$Y={c_1,c_2,…,c_N}$,那对于样本$\boldsymbol{x}$,它属于哪一类呢?

计算步骤如下:

step 1. 算出样本$\boldsymbol{x}$属于第i个类的概率,即$P(c_i|x)​$;

step 2. 通过比较所有的$P(c_i|\boldsymbol{x})$,得到样本$\boldsymbol{x}$所属的最佳类别。

step 3. 将类别$c_i$和样本$\boldsymbol{x}$代入到贝叶斯公式中,得到:

​ 一般来说,$P(c_i)$为先验概率,$P(\boldsymbol{x}|c_i)$为条件概率,$P(\boldsymbol{x})$是用于归一化的证据因子。对于$P(c_i)$可以通过训练样本中类别为$c_i$的样本所占的比例进行估计;此外,由于只需要找出最大的$P(\boldsymbol{x}|c_i)$,因此我们并不需要计算$P(\boldsymbol{x})$。
​ 为了求解条件概率,基于不同假设提出了不同的方法,以下将介绍朴素贝叶斯分类器和半朴素贝叶斯分类器。

2.19.4 朴素贝叶斯分类器

​ 假设样本$\boldsymbol{x}$包含$d$个属性,即$\boldsymbol{x}={ x_1,x_2,…,x_d}$。于是有:

这个联合概率难以从有限的训练样本中直接估计得到。于是,朴素贝叶斯(Naive Bayesian,简称NB)采用了“属性条件独立性假设”:对已知类别,假设所有属性相互独立。于是有:

这样的话,我们就可以很容易地推出相应的判定准则了:

条件概率$P(x_j|c_i)​$的求解

如果$x_j$是标签属性,那么我们可以通过计数的方法估计$P(x_j|c_i)$

其中,$#(xj,c_i)$表示在训练样本中$x_j$与$c{i}$共同出现的次数。

如果$xj​$是数值属性,通常我们假设类别中$c{i}​$的所有样本第$j​$个属性的值服从正态分布。我们首先估计这个分布的均值$μ​$和方差$σ​$,然后计算$x_j​$在这个分布中的概率密度$P(x_j|c_i)​$。

2.19.5 举例理解朴素贝叶斯分类器

使用经典的西瓜训练集如下:

编号 色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜
1 青绿 蜷缩 浊响 清晰 凹陷 硬滑 0.697 0.460
2 乌黑 蜷缩 沉闷 清晰 凹陷 硬滑 0.774 0.376
3 乌黑 蜷缩 浊响 清晰 凹陷 硬滑 0.634 0.264
4 青绿 蜷缩 沉闷 清晰 凹陷 硬滑 0.608 0.318
5 浅白 蜷缩 浊响 清晰 凹陷 硬滑 0.556 0.215
6 青绿 稍蜷 浊响 清晰 稍凹 软粘 0.403 0.237
7 乌黑 稍蜷 浊响 稍糊 稍凹 软粘 0.481 0.149
8 乌黑 稍蜷 浊响 清晰 稍凹 硬滑 0.437 0.211
9 乌黑 稍蜷 沉闷 稍糊 稍凹 硬滑 0.666 0.091
10 青绿 硬挺 清脆 清晰 平坦 软粘 0.243 0.267
11 浅白 硬挺 清脆 模糊 平坦 硬滑 0.245 0.057
12 浅白 蜷缩 浊响 模糊 平坦 软粘 0.343 0.099
13 青绿 稍蜷 浊响 稍糊 凹陷 硬滑 0.639 0.161
14 浅白 稍蜷 沉闷 稍糊 凹陷 硬滑 0.657 0.198
15 乌黑 稍蜷 浊响 清晰 稍凹 软粘 0.360 0.370
16 浅白 蜷缩 浊响 模糊 平坦 硬滑 0.593 0.042
17 青绿 蜷缩 沉闷 稍糊 稍凹 硬滑 0.719 0.103

对下面的测试例“测1”进行 分类:

编号 色泽 根蒂 敲声 纹理 脐部 触感 密度 含糖率 好瓜
测1 青绿 蜷缩 浊响 清晰 凹陷 硬滑 0.697 0.460

首先,估计类先验概率$P(c_j)$,有

然后,为每个属性估计条件概率(这里,对于连续属性,假定它们服从正态分布)

于是有

由于$0.063>6.80\times 10^{-5}$,因此,朴素贝叶斯分类器将测试样本“测1”判别为“好瓜”。

2.19.6 半朴素贝叶斯分类器

​ 朴素贝叶斯采用了“属性条件独立性假设”,半朴素贝叶斯分类器的基本想法是适当考虑一部分属性间的相互依赖信息。独依赖估计(One-Dependence Estimator,简称ODE)是半朴素贝叶斯分类器最常用的一种策略。顾名思义,独依赖是假设每个属性在类别之外最多依赖一个其他属性,即:

其中$pa_j$为属性$x_i$所依赖的属性,成为$x_i$的父属性。假设父属性$pa_j$已知,那么可以使用下面的公式估计$P(x_j|c_i,{\rm pa}_j)$

2.20 EM算法

2.20.1 EM算法基本思想

​ 最大期望算法(Expectation-Maximization algorithm, EM),是一类通过迭代进行极大似然估计的优化算法,通常作为牛顿迭代法的替代,用于对包含隐变量或缺失数据的概率模型进行参数估计。

​ 最大期望算法基本思想是经过两个步骤交替进行计算:

​ 第一步是计算期望(E),利用对隐藏变量的现有估计值,计算其最大似然估计值

​ 第二步是最大化(M),最大化在E步上求得的最大似然值来计算参数的值。

​ M步上找到的参数估计值被用于下一个E步计算中,这个过程不断交替进行。

2.20.2 EM算法推导

​ 对于$m$个样本观察数据$x=(x^{1},x^{2},…,x^{m})$,现在想找出样本的模型参数$\theta$,其极大化模型分布的对数似然函数为:

如果得到的观察数据有未观察到的隐含数据$z=(z^{(1)},z^{(2)},…z^{(m)})$,极大化模型分布的对数似然函数则为:

由于上式不能直接求出$\theta$,采用缩放技巧:

上式用到了Jensen不等式:

并且引入了一个未知的新分布$Q_i(z^{(i)})$。

此时,如果需要满足Jensen不等式中的等号,所以有:

由于$Q_i(z^{(i)})$是一个分布,所以满足

综上,可得:

如果$Q_i(z^{(i)}) = P( z^{(i)}|x^{(i)};\theta)$ ,则第(1)式是我们的包含隐藏数据的对数似然的一个下界。如果我们能极大化这个下界,则也在尝试极大化我们的对数似然。即我们需要最大化下式:

简化得:

以上即为EM算法的M步,$\sum\limits_{z^{(i)}}Q_i(z^{(i)})log{P(x^{(i)}, z^{(i)};\theta)}​$可理解为$logP(x^{(i)}, z^{(i)};\theta) $基于条件概率分布$Q_i(z^{(i)}) $的期望。以上即为EM算法中E步和M步的具体数学含义。

2.20.3 图解EM算法

​ 考虑上一节中的(a)式,表达式中存在隐变量,直接找到参数估计比较困难,通过EM算法迭代求解下界的最大值到收敛为止。

​ 图片中的紫色部分是我们的目标模型$p(x|\theta)$,该模型复杂,难以求解析解,为了消除隐变量$z^{(i)}$的影响,我们可以选择一个不包含$z^{(i)}$的模型$r(x|\theta)$,使其满足条件$r(x|\theta) \leqslant p(x|\theta) $。

求解步骤如下:

(1)选取$\theta_1$,使得$r(x|\theta_1) = p(x|\theta_1)$,然后对此时的$r$求取最大值,得到极值点$\theta_2$,实现参数的更新。

(2)重复以上过程到收敛为止,在更新过程中始终满足$r \leqslant p $.

2.20.4 EM算法流程

输入:观察数据$x=(x^{(1)},x^{(2)},…x^{(m)})$,联合分布$p(x,z ;\theta)$,条件分布$p(z|x; \theta)$,最大迭代次数$J$

1)随机初始化模型参数$\theta$的初值$\theta^0$。

2)$for \ j \ from \ 1 \ to \ j$:

​ a) E步。计算联合分布的条件概率期望:

​ b) M步。极大化$L(\theta, \theta^{j})$,得到$\theta^{j+1}$:

​ c) 如果$\theta^{j+1}$收敛,则算法结束。否则继续回到步骤a)进行E步迭代。

输出:模型参数$\theta​$。

2.21 降维和聚类

2.21.1 图解为什么会产生维数灾难

​ 假如数据集包含10张照片,照片中包含三角形和圆两种形状。现在来设计一个分类器进行训练,让这个分类器对其他的照片进行正确分类(假设三角形和圆的总数是无限大),简单的,我们用一个特征进行分类:

​ 图2.21.1.a

​ 从上图可看到,如果仅仅只有一个特征进行分类,三角形和圆几乎是均匀分布在这条线段上,很难将10张照片线性分类。那么,增加一个特征后的情况会怎么样:

​ 图2.21.1.b

增加一个特征后,我们发现仍然无法找到一条直线将猫和狗分开。所以,考虑需要再增加一个特征:

​ 图2.21.1.c

​ 图2.21.1.d

​ 此时,可以找到一个平面将三角形和圆分开。

​ 现在计算一下不同特征数是样本的密度:

​ (1)一个特征时,假设特征空间时长度为5的线段,则样本密度为$10 \div 5 = 2$。

​ (2)两个特征时,特征空间大小为$ 5\times5 = 25$,样本密度为$10 \div 25 = 0.4$。

​ (3)三个特征时,特征空间大小是$ 5\times5\times5 = 125$,样本密度为$10 \div 125 = 0.08$。

​ 以此类推,如果继续增加特征数量,样本密度会越来越稀疏,此时,更容易找到一个超平面将训练样本分开。当特征数量增长至无限大时,样本密度就变得非常稀疏。

​ 下面看一下将高维空间的分类结果映射到低维空间时,会出现什么情况?

​ 图2.21.1.e

​ 上图是将三维特征空间映射到二维特征空间后的结果。尽管在高维特征空间时训练样本线性可分,但是映射到低维空间后,结果正好相反。事实上,增加特征数量使得高维空间线性可分,相当于在低维空间内训练一个复杂的非线性分类器。不过,这个非线性分类器太过“聪明”,仅仅学到了一些特例。如果将其用来辨别那些未曾出现在训练样本中的测试样本时,通常结果不太理想,会造成过拟合问题。

​ 图2.21.1.f

​ 上图所示的只采用2个特征的线性分类器分错了一些训练样本,准确率似乎没有图2.21.1.e的高,但是,采用2个特征的线性分类器的泛化能力比采用3个特征的线性分类器要强。因为,采用2个特征的线性分类器学习到的不只是特例,而是一个整体趋势,对于那些未曾出现过的样本也可以比较好地辨别开来。换句话说,通过减少特征数量,可以避免出现过拟合问题,从而避免“维数灾难”。

​ 上图从另一个角度诠释了“维数灾难”。假设只有一个特征时,特征的值域是0到1,每一个三角形和圆的特征值都是唯一的。如果我们希望训练样本覆盖特征值值域的20%,那么就需要三角形和圆总数的20%。我们增加一个特征后,为了继续覆盖特征值值域的20%就需要三角形和圆总数的45%($0.452^2\approx0.2$)。继续增加一个特征后,需要三角形和圆总数的58%($0.583^3\approx0.2$)。随着特征数量的增加,为了覆盖特征值值域的20%,就需要更多的训练样本。如果没有足够的训练样本,就可能会出现过拟合问题。

​ 通过上述例子,我们可以看到特征数量越多,训练样本就会越稀疏,分类器的参数估计就会越不准确,更加容易出现过拟合问题。“维数灾难”的另一个影响是训练样本的稀疏性并不是均匀分布的。处于中心位置的训练样本比四周的训练样本更加稀疏。

​ 假设有一个二维特征空间,如上图所示的矩形,在矩形内部有一个内切的圆形。由于越接近圆心的样本越稀疏,因此,相比于圆形内的样本,那些位于矩形四角的样本更加难以分类。当维数变大时,特征超空间的容量不变,但单位圆的容量会趋于0,在高维空间中,大多数训练数据驻留在特征超空间的角落。散落在角落的数据要比处于中心的数据难于分类。

2.21.2 怎样避免维数灾难

有待完善!!!

解决维度灾难问题:

主成分分析法PCA,线性判别法LDA

奇异值分解简化数据、拉普拉斯特征映射

Lassio缩减系数法、小波分析法、

2.21.3 聚类和降维有什么区别与联系

​ 聚类用于找寻数据内在的分布结构,既可以作为一个单独的过程,比如异常检测等等。也可作为分类等其他学习任务的前驱过程。聚类是标准的无监督学习。

​ 1)在一些推荐系统中需确定新用户的类型,但定义“用户类型”却可能不太容易,此时往往可先对原有的用户数据进行聚类,根据聚类结果将每个簇定义为一个类,然后再基于这些类训练分类模型,用于判别新用户的类型。

​ 2)而降维则是为了缓解维数灾难的一个重要方法,就是通过某种数学变换将原始高维属性空间转变为一个低维“子空间”。其基于的假设就是,虽然人们平时观测到的数据样本虽然是高维的,但是实际上真正与学习任务相关的是个低维度的分布。从而通过最主要的几个特征维度就可以实现对数据的描述,对于后续的分类很有帮助。比如对于Kaggle(数据分析竞赛平台之一)上的泰坦尼克号生还问题。通过给定一个乘客的许多特征如年龄、姓名、性别、票价等,来判断其是否能在海难中生还。这就需要首先进行特征筛选,从而能够找出主要的特征,让学习到的模型有更好的泛化性。

​ 聚类和降维都可以作为分类等问题的预处理步骤。

​ 但是他们虽然都能实现对数据的约减。但是二者适用的对象不同,聚类针对的是数据点,而降维则是对于数据的特征。另外它们有着很多种实现方法。聚类中常用的有K-means、层次聚类、基于密度的聚类等;降维中常用的则PCA、Isomap、LLE等。

2.21.4 有哪些聚类算法优劣衡量标准

不同聚类算法有不同的优劣和不同的适用条件。可从以下方面进行衡量判断:
1、算法的处理能力:处理大的数据集的能力,即算法复杂度;处理数据噪声的能力;处理任意形状,包括有间隙的嵌套的数据的能力;
2、算法是否需要预设条件:是否需要预先知道聚类个数,是否需要用户给出领域知识;

​ 3、算法的数据输入属性:算法处理的结果与数据输入的顺序是否相关,也就是说算法是否独立于数据输入顺序;算法处理有很多属性数据的能力,也就是对数据维数是否敏感,对数据的类型有无要求。

2.21.5 聚类和分类有什么区别

聚类(Clustering)
聚类,简单地说就是把相似的东西分到一组,聚类的时候,我们并不关心某一类是什么,我们需要实现的目标只是把相似的东西聚到一起。一个聚类算法通常只需要知道如何计算相似度就可以开始工作了,因此聚类通常并不需要使用训练数据进行学习,在机器学习中属于无监督学习。

分类(Classification)

​ 分类,对于一个分类器,通常需要你告诉它“这个东西被分为某某类”。一般情况下,一个分类器会从它得到的训练集中进行学习,从而具备对未知数据进行分类的能力,在机器学习中属于监督学习。

2.21.6 不同聚类算法特点性能比较

算法名称 可伸缩性 适合的数据类型 高维性 异常数据抗干扰性 聚类形状 算法效率
WAVECLUSTER 很高 数值型 很高 较高 任意形状 很高
ROCK 很高 混合型 很高 很高 任意形状 一般
BIRCH 较高 数值型 较低 较低 球形 很高
CURE 较高 数值型 一般 很高 任意形状 较高
K-PROTOTYPES 一般 混合型 较低 较低 任意形状 一般
DENCLUE 较低 数值型 较高 一般 任意形状 较高
OPTIGRID 一般 数值型 较高 一般 任意形状 一般
CLIQUE 较高 数值型 较高 较高 任意形状 较低
DBSCAN 一般 数值型 较低 较高 任意形状 一般
CLARANS 较低 数值型 较低 较高 球形 较低

2.21.7 四种常用聚类方法之比较

​ 聚类就是按照某个特定标准把一个数据集分割成不同的类或簇,使得同一个簇内的数据对象的相似性尽可能大,同时不在同一个簇中的数据对象的差异性也尽可能地大。即聚类后同一类的数据尽可能聚集到一起,不同类数据尽量分离。
​ 主要的聚类算法可以划分为如下几类:划分方法、层次方法、基于密度的方法、基于网格的方法以及基于模型的方法。下面主要对k-means聚类算法、凝聚型层次聚类算法、神经网络聚类算法之SOM,以及模糊聚类的FCM算法通过通用测试数据集进行聚类效果的比较和分析。

2.21.8 k-means聚类算法

k-means是划分方法中较经典的聚类算法之一。由于该算法的效率高,所以在对大规模数据进行聚类时被广泛应用。目前,许多算法均围绕着该算法进行扩展和改进。
k-means算法以k为参数,把n个对象分成k个簇,使簇内具有较高的相似度,而簇间的相似度较低。k-means算法的处理过程如下:首先,随机地 选择k个对象,每个对象初始地代表了一个簇的平均值或中心;对剩余的每个对象,根据其与各簇中心的距离,将它赋给最近的簇;然后重新计算每个簇的平均值。 这个过程不断重复,直到准则函数收敛。通常,采用平方误差准则,其定义如下:

 这里E是数据中所有对象的平方误差的总和,p是空间中的点,$m_i$是簇$C_i$的平均值[9]。该目标函数使生成的簇尽可能紧凑独立,使用的距离度量是欧几里得距离,当然也可以用其他距离度量。

算法流程
​ 输入:包含n个对象的数据和簇的数目k;
​ 输出:n个对象到k个簇,使平方误差准则最小。
​ 步骤:
  (1) 任意选择k个对象作为初始的簇中心;
  (2) 根据簇中对象的平均值,将每个对象(重新)赋予最类似的簇;
  (3) 更新簇的平均值,即计算每个簇中对象的平均值;
  (4) 重复步骤(2)、(3)直到簇中心不再变化;

2.21.9 层次聚类算法

​ 根据层次分解的顺序是自底向上的还是自上向下的,层次聚类算法分为凝聚的层次聚类算法和分裂的层次聚类算法。
 凝聚型层次聚类的策略是先将每个对象作为一个簇,然后合并这些原子簇为越来越大的簇,直到所有对象都在一个簇中,或者某个终结条件被满足。绝大多数层次聚类属于凝聚型层次聚类,它们只是在簇间相似度的定义上有所不同。

算法流程

注:以采用最小距离的凝聚层次聚类算法为例:

 (1) 将每个对象看作一类,计算两两之间的最小距离;
 (2) 将距离最小的两个类合并成一个新类;
 (3) 重新计算新类与所有类之间的距离;
 (4) 重复(2)、(3),直到所有类最后合并成一类。

2.21.10 SOM聚类算法

​ SOM神经网络[11]是由芬兰神经网络专家Kohonen教授提出的,该算法假设在输入对象中存在一些拓扑结构或顺序,可以实现从输入空间(n维)到输出平面(2维)的降维映射,其映射具有拓扑特征保持性质,与实际的大脑处理有很强的理论联系。

​ SOM网络包含输入层和输出层。输入层对应一个高维的输入向量,输出层由一系列组织在2维网格上的有序节点构成,输入节点与输出节点通过权重向量连接。 学习过程中,找到与之距离最短的输出层单元,即获胜单元,对其更新。同时,将邻近区域的权值更新,使输出节点保持输入向量的拓扑特征。

算法流程

​ (1) 网络初始化,对输出层每个节点权重赋初值;
​ (2) 从输入样本中随机选取输入向量并且归一化,找到与输入向量距离最小的权重向量;
​ (3) 定义获胜单元,在获胜单元的邻近区域调整权重使其向输入向量靠拢;
​ (4) 提供新样本、进行训练;
​ (5) 收缩邻域半径、减小学习率、重复,直到小于允许值,输出聚类结果。

2.21.11 FCM聚类算法

​ 1965年美国加州大学柏克莱分校的扎德教授第一次提出了‘集合’的概念。经过十多年的发展,模糊集合理论渐渐被应用到各个实际应用方面。为克服非此即彼的分类缺点,出现了以模糊集合论为数学基础的聚类分析。用模糊数学的方法进行聚类分析,就是模糊聚类分析[12]。
​ FCM算法是一种以隶属度来确定每个数据点属于某个聚类程度的算法。该聚类算法是传统硬聚类算法的一种改进。
​ 设数据集$X={x1,x_2,…,x_n}$,它的模糊$c$划分可用模糊矩阵$U=[u{ij}]$表示,矩阵$U$的元素$u{ij}$表示第$j(j=1,2,…,n)$个数据点属于第$i(i=1,2,…,c)$类的隶属度,$u{ij}$满足如下条件:

目前被广泛使用的聚类准则是取类内加权误差平方和的极小值。即:

其中$V$为聚类中心,$m$为加权指数,$d_{ij}(x_j,v_i)=||v_i-x_j||$。

算法流程

 (1) 标准化数据矩阵;
 (2) 建立模糊相似矩阵,初始化隶属矩阵;
 (3) 算法开始迭代,直到目标函数收敛到极小值;
 (4) 根据迭代结果,由最后的隶属矩阵确定数据所属的类,显示最后的聚类结果。

2.21.12 四种聚类算法试验

​ 选取专门用于测试分类、聚类算法的国际通用的UCI数据库中的IRIS数据集,IRIS数据集包含150个样本数据,分别取自三种不同 的莺尾属植物setosa、versicolor和virginica的花朵样本,每个数据含有4个属性,即萼片长度、萼片宽度、花瓣长度、花瓣宽度,单位为cm。 在数据集上执行不同的聚类算法,可以得到不同精度的聚类结果。基于前面描述的各算法原理及流程,可初步得如下聚类结果。

聚类方法 聚错样本数 运行时间/s 平均准确率/(%)
K-means 17 0.146001 89
层次聚类 51 0.128744 66
SOM 22 5.267283 86
FCM 12 0.470417 92

(1) 聚错样本数:总的聚错的样本数,即各类中聚错的样本数的和;
(2) 运行时间:即聚类整个过程所耗费的时间,单位为s;
(3) 平均准确度:设原数据集有k个类,用$ci$表示第i类,$n_i$为$c_i$中样本的个数,$m_i$为聚类正确的个数,则$m_i/n_i$为 第i类中的精度,则平均精度为:$avg=\frac{1}{k}\sum{i=1}^{k}\frac{m{i}}{n{i}}$。

参考文献

[1] Goodfellow I, Bengio Y, Courville A. Deep learning[M]. MIT press, 2016.
[2] 周志华. 机器学习[M].清华大学出版社, 2016.
[3] Michael A. Nielsen. “Neural Networks and Deep Learning”, Determination Press, 2015.
[4] Suryansh S. Gradient Descent: All You Need to Know, 2018.
[5] 刘建平. 梯度下降小结,EM算法的推导, 2018
[6] 杨小兵.聚类分析中若干关键技术的研究[D]. 杭州:浙江大学, 2005.
[7] XU Rui, Donald Wunsch 1 1. survey of clustering algorithm[J].IEEE.Transactions on Neural Networks, 2005, 16(3):645-67 8.
[8] YI Hong, SAM K. Learning assignment order of instances for the constrained k-means clustering algorithm[J].IEEE Transactions on Systems, Man, and Cybernetics, Part B:Cybernetics,2009,39 (2):568-574.
[9] 贺玲, 吴玲达, 蔡益朝.数据挖掘中的聚类算法综述[J].计算机应用研究, 2007, 24(1):10-13.
[10] 孙吉贵, 刘杰, 赵连宇.聚类算法研究[J].软件学报, 2008, 19(1):48-61.
[11] 孔英会, 苑津莎, 张铁峰等.基于数据流管理技术的配变负荷分类方法研究.中国国际供电会议, CICED2006.
[12] 马晓艳, 唐雁.层次聚类算法研究[J].计算机科学, 2008, 34(7):34-36.
[13] FISHER R A. Iris Plants Database https://www.ics.uci.edu/vmlearn/MLRepository.html, Authorized license.
[14] Quinlan J R. Induction of decision trees[J]. Machine learning, 1986, 1(1): 81-106.
[15] Breiman L. Random forests[J]. Machine learning, 2001, 45(1): 5-32.

数组

寻找数组中第k大的元素

思路1:堆。创建一个大根堆,将所有数组中的元素加入堆中,并保持堆的大小小于等于k。这样,堆中就保留了前k个最大的元素。

1
2
3
4
5
6
def findKthLargest(self, nums: List[int], k: int) -> int:
import heapq
if k>len(nums):
return 0
res = heapq.nlargest(k, nums)
return res[-1]

思路2:快排。使用划分算法将基准值放在数组中的合适位置 pos。将小于基准值的元素移到左边,大于等于基准值的元素移到右边。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def findKthLargest(self, nums: List[int], k:int) -> int:
def partition(nums, left, right):
pivot = nums[left]
j = left
for i in range(left+1, right+1):
if nums[i] < pivot:
j += 1
nums[i], nums[j] = nums[j], nums[i]
nums[left], nums[j] = nums[j], nums[left]
return j
size = len(nums)
target = size - k
left, right = 0, size - 1
while True:
minIndex = partition(nums, left, right)
if minIndex == target:
return nums[minIndex]
elif minIndex < target:
left = minIndex + 1
else:
right = minIndex - 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
public:
void swap(vector<int>& nums, int left, int right){
int temp = nums[right];
nums[right] = nums[left];
nums[left] = temp;
}

int partition(vector<int>& nums, int left, int right){

int pivot = nums[left];
int j = left;
for(int i=left+1; i<=right; i++){
if (nums[i] <pivot){
j++;
swap(nums, i, j);
}
}
swap(nums, left, j);
return j;
}
int findKthLargest(vector<int>& nums, int k) {
int size = nums.size();
int target = size - k;
int left = 0;
int right = size-1;
while(true){
int midIndex = partition(nums, left, right);
if(midIndex == target){
return nums[midIndex];
}
else{
if(midIndex < target){
left = midIndex + 1;
}
else{
right = midIndex - 1;
}
}
}
return 0;
}
};

寻找两个有序数组的中位数

思路1:合并两个有序数组,遍历第len/2+1个元素即中位数。由于两队列之和是偶数时,中位数为数组中间左右两个数之和的平均,故需要一个变量保存前一次遍历时的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def findMedianSortedArrays(nums1, nums2):
l1 = len(nums1)
l2 = len(nums2)
l = l1 + l2
pre, cur = -1, -1
p1, p2 = 0, 0
for i in range(l // 2 + 1):
pre = cur
if p1 < l1 and (p2 >= l2 or nums1[p1] < nums2[p2]):
cur = nums1[p1]
p1 += 1
else:
cur = nums2[p2]
p2 += 1
if l % 2 == 0:
return (pre + cur) / 2.0
else:
return cur

时间复杂度是O(l1+l2),如果要求时间复杂度为O(log(l1+l2)),则优化算法如下。
假设我们有两个数组:num1: [a1,a2,a3,…an],nums2: [b1,b2,b3,…bn]。则合并后的数组为:[nums1[:left1],nums2[:left2] | nums1[left1:], nums2[left2:]]。只要保证左右两边 个数 相同,中位数就在 | 这个边界旁边产生。
如何找边界值,我们可以用二分法,我们先确定 num1 取 m1 个数的左半边,那么 num2 取 m2 = (m+n+1)/2 - m1 的左半边,找到合适的 m1,就用二分法找。
youyu左半部分最大的值小于等于右半部分最小的值 max ( nums1[left1 - 1], nums2[left2 - 1]))<= min(nums1[left1], nums2[left2]))
因为nums1数组和nums2数组是有序的,所以nums1[left1-1]<=nums1[left],nums2[left2-1]<=left[left2],故只需保证nums2[left2-1]<=nums1[left1]和nums1[left1-1]<=nums2[left2]。
若nums2[left2-1]>nums1[left],并且为了不越界,我们需要增加left1。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def findMedianSortedArraysII(nums1, nums2):
if len(nums1)>len(nums2):
nums1, nums2 = nums2, nums1
l1, l2 = len(nums1), len(nums2)
mid = (l1+l2+1)//2
left, right = 0, l1
while left<right:
mid1 = left+(right-left)//2
mid2 = mid - mid1
if nums1[mid1]<nums2[mid2-1]:
left = mid1+1
else:
right = mid1
mid1 = left
mid2 = mid - mid1
if mid1 == 0:
max_out_left = nums2[mid2-1]
elif mid2 == 0:
max_out_left = nums1[mid1-1]
else:
max_out_left = max(nums1[mid1-1], nums2[mid2-1])
if (l1+l2)%2 == 1:
return max_out_left
if mid1 == l1:
min_out_right = nums2[mid2]
elif mid2 == l2:
min_out_right = nums1[mid1]
else:
min_out_right = min(nums1[mid1], nums2[mid2])
return (max_out_left+min_out_right)/2.0

连续子数组的最大和

思路:数组有正有负的情况,这时我们遍历数组,累计求和total, 如果total小于0,则直接舍弃前面的和,重新从0开始计算,确保子数组和最大。

1
2
3
4
5
6
7
8
9
10
11
12
def maxSubArray(self, nums: List[int]) -> int:
if not nums:
return 0
nsum = 0
ans = float("-inf")
for num in nums:
if nsum>=0:
nsum += num
else:
nsum = num
ans = max(ans, nsum)
return ans

最长连续递增序列

给定一个未经排序的整数数组,找到最长且连续的递增序列。
输入:[1,3,5,4,7],最长连续递增序列是 [1,3,5], 长度为3。
尽管 [1,3,5,7] 也是升序的子序列, 但它不是连续的,因为5和7在原数组里被4隔开。
思路:从 0 位置开始遍历,遍历时根据前后元素状态判断是否递增,递增则 count++,递减则 count=1。如果 count>ans,则更新 ans,直到循环结束。

1
2
3
4
5
6
7
8
9
10
11
def findLengthOfLCIS(self, nums: List[int]) -> int:
if len(nums)<=1:
return len(nums)
ans, count = 1, 1
for i in range(len(nums)-1):
if nums[i+1]>nums[i]:
count += 1
else:
count = 1
ans = max(ans, count)
return ans

最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。
输入:[10,9,2,5,3,7,101,18],最长的上升子序列是 [2,3,7,101],它的长度是 4。
思路一:动态规划
dp[i] 的值代表 nums 前i个数字的最长子序列长度。当 nums[i] > nums[j] 时: nums[i] 可以接在 nums[j] 之后(此题要求严格递增),此情况下最长上升子序列长度为 dp[j] + 1 ;实现方式为遍历 j 时,每轮执行 dp[i] = max(dp[i], dp[j] + 1)。

1
2
3
4
5
6
7
8
9
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
dp = [1]*len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[j]<nums[i]:
dp[i] = max(dp[i], dp[j]+1)
return max(dp)

思路二:动态规划+二分查找
动态规划中,通过线性遍历来计算 dpdp 的复杂度无法降低;
每轮计算中,需要通过线性遍历 [0,k) 区间元素来得到 dp[k] 。我们考虑:是否可以通过重新设计状态定义,使整个 dp 为一个排序列表;这样在计算每个 dp[k] 时,就可以通过二分法遍历 [0,k) 区间元素,将此部分复杂度由 O(N) 降至 O(logN)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
if n<2:
return n
res = [nums[0]]
for num in nums[1:]:
if num>res[-1]:
res.append(num)
continue
left, right = 0, len(res)-1
while left<right:
mid = left+(right-left)//2
if res[mid]<num:
left = mid+1
else:
right = mid
res[left] = num
return len(res)

最长连续序列

给定一个未排序的整数数组,找出最长连续序列的长度。
输入: [100, 4, 200, 1, 3, 2],最长连续序列是 [1, 2, 3, 4],它的长度为 4。
思路:对每个元素进行判断,是不是序列的开始。即nums[i]-1是否在哈希表中(O(1)的时间)。
如果nums[i]-1在set中,说明不是序列的开始,则跳过。
如果nums[i]-1不在,说明nums[i]是序列的开始。判断nums[i]+1是否在哈希集合中,直到没有出现在集合中,判断此时的长度cur_len。和最长序列长度进行比较,max_len = max(curr_len,max_len)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def longestConsecutive(self, nums: List[int]) -> int:
if not nums:
return 0
s = set(nums)
maxlen = 0
for num in s:
curlen = 1
if num-1 in s:
continue
while num+1 in s:
num += 1
curlen += 1
maxlen = max(maxlen, curlen)
return maxlen

滑动窗口

什么是滑动窗口?其实就是一个队列,比如题目无重复字符的最长子串中的abcabcbb,进入这个队列(窗口)为abc满足题目,当再进入a,队列就变成了abca,这时候不满足要求。所以,我们就要移动这个队列。我们只要把队列的左边元素移出就行,直到满足题目要求。
例题1:leetcode3.无重复字符的最长子串(滑窗模板)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def lengthOfLongestSubstring(s):
from collections import defaultdict
if not s:
return 0
if len(s)==1:
return 1
left, right, counter= 0, 0, 0
maxLen = 0
windows = defaultdict(int)
while right<len(s):
if windows[s[right]]>0:
counter += 1
windows[s[right]] += 1
right += 1
while counter>0: # 窗口条件决定左边元素是否移出
if windows[s[left]]>1:
counter -= 1
windows[s[left]] -= 1
left += 1
maxLen = max(maxLen, right-left)
return maxLen

例题:
76.最小覆盖子串
209.长度最小的子数组
567.字符串的排列

二叉树

前序

递归

1
2
3
4
5
6
def preOrder(node):
if not node:
return None
print(node.val)
preOrder(node.left)
preOrder(node.right)

非递归
1
2
3
4
5
6
7
8
9
def preOrder(node):
stack = [node]
while stack:
print(node.val)
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
node = stack.pop()

中序遍历

递归

1
2
3
4
5
6
def inorder(node):
if not node:
return None
inorder(node.left)
print(node.val)
inorder(node.right)

非递归
1
2
3
4
5
6
7
8
9
def inorder(node):
stack = []
while node or len(stack)>0:
while node:
stack.append(node)
node = node.left
node = stack.pop()
print(node.val)
node = node.right

后序遍历

递归

1
2
3
4
5
6
def postOrder(node):
if not node:
return None
postOrder(node.left)
postOrder(node.right)
print(node.val)

非递归:使用两个栈结构,第一个栈进栈顺序:左节点->右节点->根节点,第一个栈弹出顺序:根节点->右节点->左节点,第二个栈存储为第一个栈的每个弹出依次入栈,最后第二个栈依次出栈。
1
2
3
4
5
6
7
8
9
10
11
12
def postOrder(node):
stack = [node]
stack2 = []
while len(stack)>0:
node = stack.pop()
stack2.append(node)
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
while len(stack2)>0:
print(stack2.pop().val)

层次遍历

1
2
3
4
5
6
7
8
9
10
11
if not node:
return None
queue = []
queue.append(node)
while len(queue)>0:
node = queue.pop(0)
print(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)

验证二叉搜索树

买卖股票

1)给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),求获得的最大利润。
思路1:只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice。

1
2
3
4
5
6
7
def maxProfit(self, prices: List[int]) -> int:
minprice = float("inf")
maxprofit = 0
for price in prices:
maxprofit = max(price - minprice, maxprofit)
minprice = min(price, minprice)
return maxprofit

思路2:dp[i][j]表示第i天用户持股为j所获最大利润。
j只有两个值:0表示不持股,1表示持股。
则dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i]),因为题目只允许一次交易,因此不能加上dp[i-1][0]
初始值:第0天不持股,dp[0][0]=0;第0天持股,dp[0][1]=-prices[0]
1
2
3
4
5
6
7
8
9
10
11
def maxProfit(prices):
if len(prices)<2:
return 0
n = len(prices)
dp = [[0]*n for _ in range(2)]
dp[0][0] = 0
dp[0][1] = -prices[0]
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])
return dp[-1][0]

考虑状态压缩,dp[i]仅仅依赖于dp[i-1]
1
2
3
4
5
6
7
8
9
10
11
def maxProfit2(prices):
n = len(prices)
if n<2:
return 0
dp = [0]*2
dp[0] = 0
dp[1] = -prices[0]
for i in range(1, n):
dp[0] = max(dp[0], dp[1]+prices[i])
dp[1] = max(dp[1], -prices[i])
return dp[0]

2)可以尽可能地完成更多的交易(多次买卖一支股票),但不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
思路:dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i])
dp[0][0] = 0, dp[0][1] = -prices[0]
1
2
3
4
5
6
7
8
9
10
11
def maxProfitII(prices):
n = len(prices)
if n<2:
return 0
dp = [[0]*2 for _ in range(n)]
dp[0][0] = 0
dp[0][1] = -prices[0]
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i])
return dp[-1][0]

考虑状态压缩 dp[i]也仅仅依赖dp[i-1]

3)最多可以完成两笔交易。
注:必须在再次购买前出售掉之前的股票
思路:重新定义状态方程
dp[i][0]表示未交易
dp[i][1]表示第一次买入一支股票
dp[i][2]表示第一次卖出一支股票
dp[i][3]表示第二次买入一支股票
dp[i][4]表示第二次卖出一支股票
状态转移方程见代码
初始化:第0天初始化为前两个状态,而状态3(第二次持股)只能赋值为一个不可能的数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def maxProfitIII(prices):
n = len(prices)
if n<2:
return 0
dp = [[0]*5 for _ in range(n)]
dp[0][1] = -prices[0]
for i in range(n):
dp[i][3] = float("-inf")
for i in range(1, n):
dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i])
dp[i][2] = max(dp[i-1][2], dp[i-1][1]+prices[i])
dp[i][3] = max(dp[i-1][3], dp[i-1][2]-prices[i])
dp[i][4] = max(dp[i-1][4], dp[i-1][3]+prices[i])
# 最大值只发生在不持股的时候
return max(0, dp[-1][2], dp[-1][4])

状态压缩,dp[i]仅仅依赖于dp[i-1]

4)最多可以完成 k 笔交易
注:必须在再次购买前出售掉之前的股票

5)在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
a) 必须在再次购买前出售掉之前的股票;
b) 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)
思路:增加一个状态,j取三个值:
0表示不持股,1表示持股,2表示冷冻期
状态转移方程:
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][2]-prices[i])
dp[i][2] = dp[i-1][0]

1
2
3
4
5
6
7
8
9
10
11
def maxProfitV(prices):
n = len(prices)
if n<2:
return 0
dp = [[0]*3 for _ in range(n)]
dp[0][1] = -prices[0]
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][2]-prices[i])
dp[i][2] = dp[i-1][0]
return max(dp[-1][0], dp[-1][2])

考虑状态压缩,dp[i]仅仅只依赖于dp[i-1]
6)你可以无限次地完成交易,但是你每次交易都需要付手续费fee。
如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。
思路:规定手续费在买入股票时扣除
状态转移方程:
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]-fee)
1
2
3
4
5
6
7
8
9
10
def maxProfitVI(prices, fee):
n = len(prices)
if n<2:
return 0
dp = [[0]*2 for _ in range(n)]
dp[0][1] = -prices[0]-fee
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i]-fee)
return dp[-1][0]

动态规划

最长回文子串

给定一个字符串s,找到s中最长的回文子串。
例:输入:”babad”,输出为:”bab”。
思路:设dp[i][j]表示子串s[i,j]是否为回文子串,则状态转移方程为:dp[i][j] = (s[i]==s[j]) and dp[i+1][j-1]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def longestPalindrome(self, s: str) -> str:
if not s or len(s)==1:
return s
n = len(s)
dp = [[0]*n for _ in range(n)]
left, right = 0, 0
maxLen = 0
for j in range(n):
dp[j][j] = 1
for i in range(j):
if s[i]==s[j] and (j-i<2 or dp[i+1][j-1]==1):
dp[i][j] = 1
else:
dp[i][j] = 0
if dp[i][j]==1 and maxLen<j-i+1:
maxLen = j-i+1
left, right = i, j
return s[left:right+1]

最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列。
实例:输入text1 = “abcde”, text2 = “ace” ,输出3。最长公共子序列是 “ace”,它的长度为 3。
思路:设dp[i][j]是text1[:i]和text2[:j]的最长公共子序列长度,则状态转移方程为:dp[i][j] = dp[i-1][j-1]+1 if text1[i]==text2[j] else max(dp[i-1][j], dp[i][j-1])。

1
2
3
4
5
6
7
8
9
10
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
m, n = len(text1), len(text2)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1]+1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[-1][-1]

最长公共子串

给出两个字符串str1,str2返回该两个字符串的最长公共子串。
实例:输入str1 = “1AB2345CD”,str2 = “12345EF”,输出“2345”。
思路:dp[i][j]的含义为以str1[i]和str2[j]结尾的最长公共子串,则状态转移方程为dp[i][j]=dp[i-1][j-1]+1 if str1[i]==str2[j] else 0.

1
2
3
4
5
6
7
8
9
10
def longestCommonSubstring(self, str1: str, str2: str) -> int:
m, n = len(str1), len(str2)
dp = [[0]*(n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if str1[i-1] == str2[j-1]:
dp[i][j] = dp[i-1][j-1]+1
else:
dp[i][j] = 0
return dp[-1][-1]

编辑距离

给你两个单词word1和word2,请你计算出将word1转换为word2所使用的最少操作数。
你可以对一个单词进行如下三种操作:
1)插入一个字符;2)删除一个字符;3)替换一个字符。
示例:输入word1 = “horse”, word2 = “ros”,输出为3。
思路:dp[i][j] 代表 word1 到 i 位置转换成 word2 到 j 位置需要最少步数。所以,当 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];当 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1。其中,dp[i-1][j-1] 表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def minDistance(self, word1: str, word2: str) -> int:
n1, n2 = len(word1), len(word2)
dp = [[0]*(n2+1) for _ in range(n1+1)]
for j in range(1, n2+1):
dp[0][j] = dp[0][j-1]+1
for i in range(1, n1+1):
dp[i][0] = dp[i-1][0]+1
for i in range(1, n1+1):
for j in range(1, n2+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])+1
return dp[-1][-1]

让字符串成为回文串的最少插入次数

给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。请你返回让 s 成为回文串的 最少操作次数 。
示例:输入s = “mbadm”,输出为2。字符串可变为 “mbdadbm” 或者 “mdbabdm” 。
思路:是最长公共子序列的变形。当前字符串要变成回文,那只要把不一样的找出来就好了。即:求出反过来的字符串和当前字符串的最长公共子序列,然后用字符串长度减去最长公共子序列长度即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
def minInsertions(self, s: str) -> int:
n = len(s)
t = s[::-1]
if s==t:
return 0
dp = [[0]*(n+1) for _ in range(n+1)]
for i in range(1, n+1):
for j in range(1, n+1):
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1]+1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return n-dp[-1][-1]

零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。
示例:输入coins = [1, 2, 5], amount = 11,输出3。11可拆分成5+5+1。
思路:

回溯:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    def coinChange(self, coins: List[int], amount: int) -> int:
if not coins:
return -1
res = [float("inf")]
def dfs(coins, amount, count, res):
if amount == 0:
res[0] = min(res[0], count)
for coin in coins:
diff = amount-coin
if diff<0:
break
dfs(coins, diff, count+1, res)
dfs(coins, amount, 0, res)
return res[0] if res[0]!=float("inf") else -1
```
在回溯法中,如果含有很多的重复的计算的时候,就可以使用记忆化的搜索,将可能出现的重复计算大状态使用一个数组来保存其值,在进行重复的计算的时候,就可以直接的调用数组中的值,较少了不必要的递归。使用了记忆化搜索后,一般还可以进行优化,在记忆化搜索的基础上,变成 自底而上 的动态规划。
若包含当前的 coins[i],那么剩余钱就是 i-coins[i],这种操作要兑换的硬币数是 memo[i-coins[j]] + 1
```Python
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [float("inf")]*(amount+1)
dp[0] = 0
for i in range(1,amount+1):
for coin in coins:
if i >= coin:
dp[i] = min(dp[i], dp[i-coin]+1)
return dp[-1] if (dp[-1]!=float("inf")) else -1

不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?
思路:令 dp[i][j] 是到达 i, j 最多路径,则动态转移方程为:dp[i][j] = dp[i-1][j] + dp[i][j-1]。
对于第一行 dp[0][j],或者第一列 dp[i][0],由于都是在边界,所以只能为 1。

1
2
3
4
5
6
7
8
9
10
def uniquePaths(self, m: int, n: int) -> int:
dp = [[0]*n for _ in range(m)]
for i in range(1, m):
dp[i][0] = 1
for i in range(1, n):
dp[0][i] = 1
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j]+dp[i][j-1]
return dp[-1][-1]

空间复杂度为O(mn),由于当前位置只和该位置左边和上方的变量有关,故空间复杂度可优化为O(n)
1
2
3
4
5
6
def uniquePaths(self, m: int, n: int) -> int:
dp = [1] * n
for i in range(1, m):
for j in range(1, n):
dp[j] = dp[j]+dp[j-1]
return dp[-1]

礼物的最大价值

在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
思路:令dp[i][j]为位置[i,j]的礼物价值,则动态转移方程为:dp[i][j] = max(dp[i-1][j], dp[i][j-1])+gift[i][j]。

1
2
3
4
5
6
7
8
9
10
11
12
def maxValue(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
dp = [[0]*n for _ in range(m)]
dp[0][0] = grid[0][0]
for i in range(1, m):
dp[i][0] = dp[i-1][0]+grid[i][0]
for j in range(1, n):
dp[0][j] = dp[0][j-1]+grid[0][j]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = max(dp[i-1][j], dp[i][j-1])+grid[i][j]
return dp[-1][-1]

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
思路:和拿糖果问题类似。设dp[i]为抢劫第i间房间的最大金额,np[i]为不抢劫第i间房间的最大金额。则动态转移方程为:dp[i]=np[i]+nums[i],np[i] = max(np[i-1], dp[i-1])

1
2
3
4
5
6
7
8
9
10
11
12
13
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
if len(nums)==1:
return nums[0]
n = len(nums)
dp = [0]*n
np = [0]*n
dp[0] = nums[0]
for i in range(1, n):
dp[i] = np[i-1]+nums[i]
np[i] = max(np[i-1], dp[i-1])
return max(dp)

优化,在当前位置 n 房屋可盗窃的最大值,要么就是 n-1 房屋可盗窃的最大值,要么就是 n-2 房屋可盗窃的最大值加上当前房屋的值。
1
2
3
4
5
6
7
8
9
10
11
def rob(self, nums: List[int]) -> int:
if not nums:
return 0
if len(nums)==1:
return nums[0]
n = len(nums)
dp = [0]*(n+1)
dp[1] = nums[0]
for i in range(2, n+1):
dp[i] = max(dp[i-1], dp[i-2]+nums[i-1])
return dp[-1]

打家劫舍II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
思路:此题中的房间是环状排列的(即首尾相接),环状排列意味着第一个房子和最后一个房子中只能选择一个偷窃,因此可以把此环状排列房间问题约化为两个单排排列房间子问题:
1)在不偷窃第一个房子的情况下(即 nums[1:]),最大金额是p1;
2)在不偷窃最后一个房子的情况下(即 nums[:n-1]),最大金额是 p2。
设dp[i] 代表前 i 个房子在满足条件下的能偷窃到的最高金额,则dp[i] = max(dp[i-1], dp[i-2]+nums[i-1])。我们发现 dp[n] 只与 dp[n-1] 和 dp[n-2] 有关系,因此我们可以设两个变量 cur和 pre 交替记录,将空间复杂度降到 O(1)。

1
2
3
4
5
6
7
8
9
def rob(self, nums: List[int]) -> int:
def robCore(nums):
cur, pre = 0, 0
for num in nums:
cur, pre = max(cur, pre+num), cur
return cur
if len(nums)==1:
return nums[0]
return max(robCore(nums[:-1]), robCore(nums[1:]))

打家劫舍III

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
思路:假设func(root)为问题的解,即能从以root为根节点的树中盗窃到的最大金额。那么以root房子开始分析,可以选择偷窃root房子,也可以选择不偷窃。如果偷窃root房子,那么func(root) = root->val + func(root->left)->unrob + func(root->left)->unrob。
如果不偷窃root房子,那么func(root) = func(root->left)->robed + func(root->right)->robed。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def rob(self, root: TreeNode) -> int:
def robCore(root):
if not root:
return [0, 0]
left = robCore(root.left)
right = robCore(root.right)
robed = root.val+left[1]+right[1]
unrob = left[0]+right[0]
return [max(robed, unrob), unrob]
if not root:
return 0
# res[0]偷窃该节点的金额,res[1]不偷窃该节点的金额
res = robCore(root)
return res[0]

递归

全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。
思路:回溯法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def permute(self, nums: List[int]) -> List[List[int]]:
if len(nums)<=1:
return [nums]
res, stack = [], []
def backtrack(nums, stack):
if not nums:
res.append(stack[:])
return
for i in range(len(nums)):
stack.append(nums[i])
backtrack(nums[:i]+nums[i+1:], stack)
stack.pop()
backtrack(nums, stack)
return res

全排列II

给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:输入[1,1,2],输出[[1,1,2],[1,2,1],[2,1,1]]
思路:先排序,然后回溯+剪枝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
def backtrack(nums, tmp):
if not nums:
res.append(tmp[:])
return
for i in range(len(nums)):
if i>0 and nums[i]==nums[i-1]:
continue
tmp.append(nums[i])
backtrack(nums[:i]+nums[i+1:], tmp)
tmp.pop()
backtrack(nums, [])
return res

组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。解集不能包含重复的组合。
示例:输入candidates=[2,3,6,7],target=7,输出[[7],[2,2,3]]
思路:回溯+剪枝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
def dfs(nums, target, start, tmp, res):
if target==0:
res.append(tmp[:])
return
for i in range(start, len(nums)):
diff = target-nums[i]
if diff<0:
break
tmp.append(nums[i])
dfs(nums, diff, i, tmp, res)
tmp.pop()
res = []
dfs(candidates, target, 0, [], res)
return res

组合总和II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。解集不能包含重复的组合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
def dfs(nums, target, start, tmp, res):
if target==0:
res.append(tmp[:])
return
for i in range(start, len(nums)):
diff = target-nums[i]
if diff<0:
break
if i>start and nums[i]==nums[i-1]:
continue
tmp.append(nums[i])
dfs(nums, diff, i+1, tmp, res)
tmp.pop()
res = []
candidates.sort()
dfs(candidates, target, 0, [], res)
return res

组合总和III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
示例:输入k=3,n=7,输出[[1,2,4]]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
def dfs(target, k, start, path, res):
if len(path)==k and target==0:
res.append(path[:])
return
for i in range(start, 10):
diff = target-i
if diff<0:
continue
path.append(i)
dfs(diff, k, i+1, path, res)
path.pop()

if k<=0 or n<k:
return []
res = []
dfs(n, k, 1, [], res)
return res

组合总和IV

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
示例:输入nums=[1,2,3],target=4,所有的可能组合为:(1, 1, 1, 1),(1, 1, 2),(1, 2, 1),(1, 3),(2, 1, 1),(2, 2),(3, 1)。
因此输出为7。
思路:背包问题,动态规划
状态转移方程:dp[i]= dp[i - nums[0]] + dp[i - nums[1]] + dp[i - nums[2]] + …,值得注意的是:dp[0] = 1,表示,如果那个硬币的面值刚刚好等于需要凑出的价值,这个就成为 1 种组合方案。

1
2
3
4
5
6
7
8
9
10
def combinationSum4(self, nums: List[int], target: int) -> int:
if not nums or target<=0:
return 0
dp = [0 for _ in range(target+1)]
dp[0] = 1
for i in range(1, target+1):
for num in nums:
if i >= num:
dp[i] += dp[i-num]
return dp[-1]

子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。
示例:输入nums = [1,2,3],输出[[3],[1],[2],[1,2,3],[1,3],[2,3],[1,2],[]]

1
2
3
4
5
6
7
8
9
10
def subsets(self, nums: List[int]) -> List[List[int]]:
def dfs(nums, start, tmp, res):
res.append(tmp[:])
for i in range(start, len(nums)):
tmp.append(nums[i])
dfs(nums, i+1, tmp, res)
tmp.pop()
res = []
dfs(nums, 0, [], res)
return res

子集II

定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。
示例:输入[1,2,2],输出[[2],[1],[1,2,2],[2,2],[1,2],[]]

1
2
3
4
5
6
7
8
9
10
11
12
13
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
def dfs(nums, start, tmp, res):
res.append(tmp[:])
for i in range(start, len(nums)):
if i>start and nums[i]==nums[i-1]:
continue
tmp.append(nums[i])
dfs(nums, i+1, tmp, res)
tmp.pop()
res = []
nums.sort()
dfs(nums, 0, [], res)
return res

N皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

给定一个整数 n,返回 n 皇后不同的解决方案的数量。
示例:输入4,输出2,即存在两种不同的解法
思路:回溯法,存在约束条件:一行只可能有一个皇后且一列也只可能有一个皇后。对于所有的主对角线有 行号 + 列号 = 常数,对于所有的次对角线有 行号 - 列号 = 常数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def totalNQueens(self, n: int) -> int:
def dfs(n, row, col, master, slave, path, res):
if row == n:
res[0] += 1
return
for i in range(n):
if i not in col and row+i not in slave and row-i not in master:
path.append(i)
col.add(i)
master.add(row-i)
slave.add(row+i)
dfs(n, row+1, col, master, slave, path, res)
slave.remove(row+i)
master.remove(row-i)
col.remove(i)
path.pop()
if n == 0:
return 0
res = [0]
col, master, slave = set(), set(), set()
dfs(n, 0, col, master, slave, [], res)
return res[0]

岛屿数量

给定一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
输入:
11110
11010
11000
00000
输出:1
思路:深度优先遍历,将遍历过的相邻岛屿修改为0,计算矩阵中不相邻的1的个数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, row, col, rows, cols):
if not 0<=row<rows or not 0<=col<cols or grid[row][col]=='0':
return
grid[row][col] = '0'
dfs(grid, row+1, col, rows, cols)
dfs(grid, row-1, col, rows, cols)
dfs(grid, row, col+1, rows, cols)
dfs(grid, row, col-1, rows, cols)
if not grid:
return 0
ans = 0
m, n = len(grid), len(grid[0])
for i in range(m):
for j in range(n):
if grid[i][j] == '1':
ans += 1
dfs(grid, i, j, m, n)
return ans

岛屿的最大面积

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
思路:深度优先遍历。在一个土地上,以 4 个方向探索与之相连的每一个土地(以及与这些土地相连的土地),那么探索过的土地总数将是该连通形状的面积。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
def dfs(grid, seen, row, col, rows, cols):
if 0<=row<rows and 0<=col<cols and grid[row][col]==1 and seen[row][col]==False:
seen[row][col] = True
up = dfs(grid, seen, row-1, col, rows, cols)
down = dfs(grid, seen, row+1, col, rows, cols)
left = dfs(grid, seen, row, col-1, rows, cols)
right = dfs(grid, seen, row, col+1, rows, cols)
return up+down+left+right+1
else:
return 0
if not grid:
return 0
m, n = len(grid), len(grid[0])
seen = [[False]*n for _ in range(m)]
maxArea = 0
for i in range(m):
for j in range(n):
if grid[i][j]==1:
curArea = dfs(grid, seen, i, j, m, n)
maxArea = max(maxArea, curArea)
return maxArea

最大正方形面积

给定01矩阵
0 1 0 1 1 1
1 0 1 1 1 0
1 1 0 1 1 1
输出最大正方形面积,上面的例子面积是4。

1
2
3
4
5
6
7
8
9
10
11
12
13
def findMaxSquare(nums):
if not nums or not nums[0]:
return 0
rows = len(nums)
cols = len(nums[0])
l = 0
dp = [[0]*(cols+1) for _ in range(rows+1)]
for i in range(1, rows+1):
for j in range(1, cols+1):
if nums[i-1][j-1] == 1:
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])+1
l = max(dp[i][j], l)
return l*l

Python

常用的Python标准库有哪些?

常用的标准库:os操作系统,time时间,random随机,pymysql连接数据库,math数学,threading线程,multiprocessing进程,queue队列
第三方库:django和flask,requests,virtualenv,hashlib,md5
常用的科学计算库:Numpy,Scipy,Pandas等。

Python有哪些特点和优点?

作为一门编程入门语言,Python主要有以下特点和优点:
1)可解释
2)具有动态特性
3)面向对象
4)简明简单
5)开源
6)具有强大的社区支持

深拷贝和浅拷贝之间的区别是什么?

深拷贝就是将一个对象拷贝到另一个对象中,这意味着如果你对一个对象的拷贝做出改变时,不会影响原对象。在Python中,我们使用函数deepcopy()执行深拷贝。

1
2
import copy
b = copy.deepcopy(a)

浅拷贝则是将一个对象的引用拷贝到另一个对象上,所以如果我们在拷贝中改动,会影响到原对象。我们使用函数function()执行浅拷贝。
1
b = copy.copy(a)

列表和元组之间的区别是?

二者的主要区别是列表是可变的,而元组是不可变的。

在Python中如何实现多线程?

Python中的GIL(全局解释器锁)确保一次执行单个线程。一个线程保存GIL并在将其传递给下个线程之前执行一些操作,这会让我们产生并行运行的错觉。但实际上,只是线程在CPU上轮流运行。

在Python中是如何管理内存的?

Python有一个私有堆空间来保存所有的对象和数据结构。作为开发者,我们无法访问它,是解释器在管理它。但是有了核心API后,我们可以访问一些工具。Python内存管理器控制内存分配。另外,内置垃圾回收器会回收使用所有的未使用内存,所以使其适用于堆空间。

当退出Python时,是否释放全部内存?

不会。循环引用其它对象或引用自全局命名空间的对象的模块,在Python退出时并非完全释放。另外,也不会释放C库保留的内存部分。

Python中的命名空间是什么?

命名空间是一个命名系统,用于确保名称是唯一性,以避免命名冲突。

什么是PYTHONPATH?

它是导入模块时使用的环境变量。每当导入模块时,也会查找PYTHONPATH以检查各个目录中是否存在导入的模块。解释器使用它来确定要加载的模块。

什么是init?

init是Python中的方法或者结构。在创建类的新对象/实例时,将自动调用此方法来分配内存。所有类都有init方法。

Python中的self是什么?

self是类的实例或对象。在Python中,self包含在第一个参数中。但是,Java中的情况并非如此,它是可选的。它有助于区分具有局部变量的类的方法和属性。init方法中的self变量引用新创建的对象,而在其他方法中,它引用其方法被调用的对象。

什么是python迭代器?

迭代器是可以遍历或迭代的对象。

什么是python中的装饰器?

装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码到装饰器中并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

os和sys的区别

os模块负责程序与操作系统的交互,提供了访问操作系统底层的接口;sys模块负责程序与python解释器的交互,提供了一系列的函数和变量,用于操控python的运行时环境。

什么是负指数,功能是什么?

Python中的序列是索引的,它由正数和负数组成。积极的数字使用’0’作为第一个索引,’1’作为第二个索引,进程继续使用。
负数的索引从’-1’开始,表示序列中的最后一个索引,’ - 2’作为倒数第二个索引,序列像正数一样前进。

什么是猴子补丁?

在运行期间动态修改一个类或模块。

请解释使用args和*kwargs的含义

当我们不知道向函数传递多少参数时,比如我们向传递一个列表或元组,我们就使用args。
在我们不知道该传递多少关键字参数时,使用*
kwargs来收集关键字参数。

解释一下Python中的身份运算符

通过身份运算符‘is’和‘is not’,我们可以确认两个值是否相同。

为何不建议以下划线作为标识符的开头

因为Python并没有私有变量的概念,所以约定速成以下划线为开头来声明一个变量为私有。所以如果你不想让变量私有,就不要使用下划线开头。

range&xrange有什么区别?

在大多数情况下,xrange和range在功能方面完全相同。它们都提供了一种生成整数列表的方法,唯一的区别是range返回一个Python列表对象,x range返回一个xrange对象。这就表示xrange实际上在运行时并不是生成静态列表。它使用称为yielding的特殊技术根据需要创建值。该技术与一种称为生成器的对象一起使用。因此如果你有一个非常巨大的列表,那么就要考虑xrange。

Pytorch、Tensorflow

tensorflow

Linux系统

linux常用指令

Git

git常见问题

Java

Java常见问题

2020年快手Y-tech面经

一面:

1、PCA原理,应用
PCA原理是在原有n维特征的基础上重新构造出k维全新的正交特征,PCA的工作就是从原始空间顺序中到一组互相正交的坐标轴,第一个坐标轴选择就是原始数据中方差最大的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使方差最大的,第三个轴是与第1、2个轴正交的平面中方差最大的。依次类推可以得到n个这样的坐标轴。通过这种方式获得的新坐标轴,我们发现大部分方差都包含在前面k个坐标轴中,后面的坐标轴所含的方差几乎为0。于是我们可以忽略余下的坐标轴,只保留前面k个含有大部分方差的坐标轴。
PCA的应用范围有:1)数据压缩。数据压缩或者数据降维首先能够减少内存的使用,其次,数据降维能够加快机器学习的速度。2)数据可视化。在很多情况下,可能我们需要查看样本特征,但是高维度的特征根本无法观察,这个时候我们可以将样本的特征维数降到2个特征或3个特征,这样就可以采用可视化观察数据。3)去除数据噪声。
如何得到这些包含最大差异性的主成分方向呢?通过计算数据矩阵的协方差矩阵,然后得到协方差矩阵的特征值特征向量,选择特征值最大(即方差最大)的k个特征所对应的特征向量组成的矩阵。这样就可以将数据矩阵转换到新的空间中,实现数据特征的降维。得到协方差矩阵的特征值向量有两种方法:特征值分解协方差矩阵、奇异值分解协方差矩阵。
2、SVD原理,应用
SVD推导过程
3、SVM
SVM是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔大使它有别于普通的感知机,通过核技巧隐式的在输入空间直接求解映射空间中特征向量的内积,使其成为一个非线性分类器。SVM的学习策略是间隔最大化,可形式化为一个求解凸二次规划问题。
(重点)SVM算法推导过程
4、BN 优缺点 IN
BN的基本思想相当直观:因为深层神经网络在做非线性变换前的激活输入值随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或变动,之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区域的上下限两端靠近,所以这导致反向传播时低层神经网络的梯度消失,这是训练深层神经网络收敛越来越慢的本质原因。而BN就是通过一定的规范化手段,把层神经网络任意神经元这个输入值的分布强行拉回到均值为0方差为1的标准正态分布,其实就是把越来越偏的分布强制拉回到标准的分布,这样使得激活输入值落在非线性函数对输入比较敏感的区域,这样输入的小变化就是导致损失函数较大的变化。
BatchNorm的优点:1)极大提升了训练速度,使得收敛过程大大加快;2)还能增加分类效果,一种解释是这类似于Dropout的一种防止过拟合的正则化表达方式,所以不用Dropout也能达到相当的效果;3)调参过程也简单了,对于初始化要求没那么高,可以使用大的学习率。4)可以代替LRN
缺点:需要计算均值与方差,不适合动态网络或者RNN。计算均值方差依赖每批次,因此数据最好足够打乱。
IN的计算就是把每张图片的HW维度单独拿出来归一化处理,不受通道和batchsize 的影响
5、cross entropy和mse区别,求导
交叉熵是用来评估当前训练得到的概率分布于真实分布的差异情况,减少交叉熵损失就是在提高模型的预测准确率。交叉熵通常用作分类问题的代价函数。
均方误差是指参数估计值与真实值之差的平方的期望值,MSE可以评价数据的变化程度,MSE的值越小,说明预测模型描述实验数据具有更好的精度。通常用来做回归问题的代价函数。
6、sigmoid, softmax用途,区别
Sigmoid函数或Softmax函数可以将分类器的原始输出值映射为概率。Sigmoid函数会分别处理各个原始输出值,因此其结果相互独立,概率总和不一定为1。相反,Softmax函数的输出值相互关联,其概率的总和始终为1。
Sigmoid = 多标签分类问题 = 多个正确答案 = 非独占输出
Softmax = 多类别分类问题 = 只有一个正确答案 = 互斥输出
如果模型输出为非互斥类别,且可以同时选择多个类别,则采用Sigmoid函数计算该网络的原始输出值。
如果模型输出为互斥类别,且只能选择一个类别,则采用Softmax函数计算该网络的原始输出值。
7、单链表快排思想
8、概率题,a服从均匀分布(0,1),b服从均匀分布(0,1),求max(a,b)的期望,拓展为n个独立变量,求max期望

二面:

1、cross entropy和mse能否互相替换?
不能。参见问题(为什么分类问题用 cross entropy,而回归问题用 MSE?)
2、多标签分类是否了解, 多标签分类激活函数?
解答
3、单链表快排实现

2019 海康威视

一面

1、faster-rcnn的整个从输入到输出的框架流程?
参见深度学习基础知识.faster rcnn
2、说一下rpn的原理
参见深度学习基础知识.faster rcnn
3、针对小目标的解决措施
(待解决)
4、如何解决样本不均衡的问题
参考答案
5、focal loss原理
参考解答

2019 旷视

一面

1、讲一下SENet的模块,为什么有性能提升,有什么好处
2、讲一下Focal loss 的2个参数有什么作用
3、讲一下FPN为什么能提升小目标的准确率
4、对One-stage的模型有没有了解
5、说一说One-stage和Two-stage两者有什么特点
6、说一说SSD具体是怎么操作的
7、算法题:leetcode.754。一个人从原点出发,可以往左走可以往右走,每次走的步数递增1,问能不能到达一个位置x?如果能,给出走的步数最少的方案?

蚂蚁金服

二面

1、分类和定位的不一致具体是什么
分类是给定一张图片或一段视频判断里面是否包含某种类别的目标
定位是标注出图片中这个目标的位置
检测是标注出这个目标的位置并知道目标的类别是什么
分割是解决每一个像素属于哪个目标物体或场景。
2、Faster-RCNN比起RCNN有什么改进的地方
3、Faster-RCNN如何进行一次计算
4、RoI Pooling如何操作
根据输入的图片,将ROI映射到feature map对应的位置,映射的规则就是把各个坐标除以输入图片与feature map的大小比值,得到feature map上的box坐标。然后将映射的区域划分为相同大小的sections,对每个sections进行max pooling的操作。
5、如果有很长,很小,或者很宽的目标,应该如何处理
采用FPN网络的多尺度预测方式,将高层特征和低层特征进行融合,增强特征的表达能力。
6、FPN具体是怎么操作的
将高层的特征进行两倍上采样和前一层的特征进行逐元素求和,然后将融合的特征输入3x3卷积(消除上采样的混叠效应)得到最后的特征。
7、FPN的特征融合具体是怎么做的
element-wise逐元素求和
8、FPN的特征融合为什么是相加操作呢
add操作可以看做是加入一种先验知识,通过add操作,网络会得到新的特征,这个心特征会反映原始特征的一些特性,但是原始特征的一些信息也会在这个过程中损失。但是concate就是讲原始的特征直接拼接,让网络学习如何融合特征,这个过程中信息不会损失,但是concate会带来的计算量较大,在明确原始特征的关系可以使用add操作,可以节省计算资源。
9、Soft-NMS是如何操作的
10、Softmax是什么,公式是什么样的
参考答案
11、Softmax的梯度是什么
12、BN原理
13、Pytorch的卷积是如何实现的
解答
14、还知道哪些传统计算机视觉的算法
(待解决)
15、SIFT原理,SIFT如何保持尺度不变性的
(待解决)

三面

1、说一个比较熟悉的网络
(项目中需准备)
2、对移动端的网络有没有什么了解
(待解决)
3、Focal loss怎么操作的
4、IoUNet怎么操作的
(待解决)
5、Soft-NMS怎么操作的
6、FPN的相加操作有没有尝试其他操作
7、对SSD和YOLO有没有什么了解?对SSD做过什么实验
8、对attention有什么了解
9、对跟踪有没有什么了解

四面

1、说一下focal loss的原理
2、介绍一下目标检测有哪些方向,最近的一些新的进展
3、说一下Soft-NMS的原理
4、说一下视觉的attention
5、平常是如何学习的
6、python多线程有了解吗
(待解决)
7、引用和指针的区别
(待解决)整理python相关问题
8、有什么想问的

五面

1、说比赛
2、比赛的数据集数量是怎么分布的
3、小目标占比如何
4、为什么说SENet泛化性好
5、SENet为什么效果好
6、FPN是怎么提升小目标检出率的
7、项目是一个什么问题呢
8、大目标如果有2个候选框和gt重合应该怎么处理
9、BN是解决什么问题的
10、BN为什么有效
11、什么时候开始接触这个方向的
12、学过哪些课程
13、学过机器学习吗

商汤

一面

1、InceptionV1为什么能提升性能
2、RPN哪里也可以提升小目标检出率
3、为什么说resnet101不适用于目标检测
4、小目标在FPN的什么位置检测
5、sigmoid和softmax的区别
6、detnet原理
7、一道算法题:输入一个文件,等概率输出某一行,只能顺序遍历
8、手写nms
(参考答案)[https://zhuanlan.zhihu.com/p/64423753]

二面

1、项目:最大的创新点是什么
2、SENet原理
3、Focal Loss 原理
4、Faster RCNN原理
5、用的roi pooling 还是roi align
6、IoUNet的原理
7、有了解过单阶段的检测器
8、说一下SSD的原理
9、还知道哪些轻量级的检测器
10、知道大网络用来学习小网络的方法吗
11、对分割有没有了解
12、用的什么机器(GPU)
13、比赛排名多少
14、用的什么模型

三面

1、什么时候开始接触CV课程
2、有没有上过CV的相关课程
3、SENet原理
4、SENet接在ResNet还有Inception的什么位置呢
5、IOUNet能有多少提升
6、NMS和soft-NMS原理
7、比赛遇到了什么不work的地方
8、假如一个图片中有一个很大的目标还有一个很小的目标,你会怎么处理呢
9、多尺度训练如何设置
10、为什么设置长边为固定的1600呢
11、介绍一下SSD和Faster-RCNN,说一下异同点
12、Faster-RCNN的回归目标是如何定义的
13、Faster-RCNN用的什么loss
14、smooth L1 loss为什么更有效
15、SGD、Adam之类优化的原理
16、BN、IN、LN、GN原理,BN为什么有效
17、Python有哪些常用的库报一遍
18、说一下使用Pytorch对cifar10数据集分类的整个代码流程,构建模型的过程是怎么样的
19、会C++吗
20、github的常用操作:上传、合并、分支之类的
(待整理)
21、linux的常用操作:查看文件大小、删除文件、查看文件行数、假如文件中有很多文件,每个文件中又有很多文件,如何删除全部文件
(待整理)
22、SiamRPN原理
23、轻量级模型
(待解决)

精锐视觉上海研究院

二面

算法题:1、给定一个乱序的数组,给定一个整数target,要求找出数组中所有和为该target的两个数。2、给定一张100元纸币,兑换为20,5,1元的纸币,要求将100元纸币正好兑换为上述面额的纸币,且每种面额不少于一张,写代码实现总共有多少种兑换方法。
2、生成式模型和判别式模型
(待解决)
3、解释最大似然估计和最大后验概率以及区别
4、神经网络中激活层的作用
5、faster rcnn中为什么要用3x3的巻积核代替5x5和7x7的巻积核。
6、防止过拟合的方式有哪些,请写出不少于五种。
7、请用深度学习的方法设计一套手写签名的识别方案。(不会)
8、请自主设计一套人脸识别方案,详述数据集、backbone、训练策略、模型测试等。
9、opencv的cascade级联分类器的工作原理以及具体的实现方式,训练级联分类器的注意事项。
(待解决)
10、HOG,LBP,Haar特征
(待解决)
11、简单介绍一下deep_sort算法

三面

1、问了一个深度学习的项目,项目简介,自己负责的部分,技术原理细节,创新点,难点。后续改进方案。
2、详细介绍一下deepsort算法,以及和传统跟踪方法的区别。
3、faster rcnn原理
4、详细解释deep_sort算法,与online track的方法有什么区别,目标跟踪丢失怎么办

聚时科技

一面

1、图像处理,中值滤波和平滑滤波的区别
2、说几个机器学习常用的算法,SVM的目标函数是什么,是如何实现分类的,KNN的原理以及分类流程
3、常见的目标分类的基础网络有哪些,mobilenet和shufflenet是如何实现卷积加速的。

二面

1、给了一个7x7的灰度图像和一个2x2的卷积核,分别写出腐蚀和膨胀运算后的图像。
2、手写代码实现二维卷积过程,不得调用TF、Keras等深度学习库。
3、手写代码实现一个8邻接连通域算法,不得调用opencv等图像处理库。
4、写出三个常用的机器学习算法,并介绍其原理和使用场景。
5、请写出如些解决样本不平衡问题。

腾讯

1、YOLO V2 V3你对哪个熟悉,讲一下细节实现
2、多尺度问题
3、anchor基础知识
4、人脸识别现在常用算法
5、语义分割到实例分割怎么做
6、GAN是否了解,如何通俗的讲其原理
7、PCA原理LDA原理
8、SVM+HOG
9、XGBoost
10、CNN、RCNN、FRCNN,有可能问你其中一个细节的关键
11、TensorFlow这些框架你谈一下看法以及对其他框架的了解
12、现在机器学习、深度学习这么火,你有什么看法
13、机器学习、深度学习你对他们的理解是什么
14、Relu比Sigmoid使用多的原因
15、Loss不升反降的原因,如何解决
16、SSD细节
17、Linux 权限的意义
18、块操作的操作的步骤以及快捷方式

搜狗

一面

1、RCNN系列的算法流程和区别;
2、Fast RCNN中 bbox 回归的损失函数什么;
3、解释 ROI Pooling 和 ROI Align;
4、Mask RCNN中 mask branch 如何接入 Faster RCNN中;
5、解释 FPN (没细看,面完补的);
6、优化算法举例和他们的区别(SGD、SGDM、RMSprop、Adam);
7、训练不收敛的原因有哪些;
8、简述 Inception v1-v4;
9、简述 CNN 的演变;
10、BN 的作用和缺陷,以及针对batch_size小的情况的改进(GN)

旷世CV岗

1.自我介绍,项目介绍
2.FCN结构介绍,上采样的具体操作
3.空洞卷积原理,deeplab v1 v2的改进
4.focal loss介绍, lovasz loss数学原理
5.一道题,计算卷积操作的浮点计算量,比较简单
6.介绍下RPN的原理
7.mobile net
8.unet的缺点

排序算法比较

选择排序

算法过程:首先,找到数组中最小的那个元素,其次,将它和数组的第一个元素交换位置(如果第一个元素就是最小元素就和自己交换)。然后在剩下的元素中找到最小的元素,将它与数组的第二个元素交换位置。如此往复,知道整个数组排序。
选择排序过程

1
2
3
4
5
6
7
8
def selectionSort(nums):
for i in range(len(nums)-1):
minIndex = i
for j in range(i+1, len(nums)):
if nums[j]<nums[minIndex]:
minIndex = j
nums[i], nums[minIndex] = nums[minIndex], nums[i]
return nums

性质:时间复杂度:$O(n^2)$,空间复杂度:$O(1)$,非稳定排序,原地排序

插入排序

算法过程:1)从数组第2个元素开始抽取元素。2)把它与左边第一个元素比较,如果左边第一个元素比它大,则继续与左边第二个元素比较,知道遇到不必它大的元素,然后插入到这个元素的右边。3)继续选取第3,4,…,n个元素,重复步骤2,选择适当的位置插入。
插入排序过程

1
2
3
4
5
6
7
8
def insertionSort(nums):
for i in range(len(nums)-1):
curNum, preIndex = nums[i+1], i
while preIndex>=0 and curNum<nums[preIndex]:
nums[preIndex+1] = nums[preIndex]
preIndex -= 1
nums[preIndex+1] = curNum
return nums

性质:时间复杂度:$O(n^2)$,空间复杂度:$O(1)$,稳定排序,原地排序。

冒泡排序

算法过程:1)把第一个元素和第二个元素比较,如果第一个比第二个大,则交换他们的位置。接着比较第二个与第三个元素,如果第二个比第三个大,则交换它们的位置…。这样一趟比较交换之后,排在最右的就会是最大的数。2)除去最右的数,我们对剩余的元素做同样的工作,如此重复下去,直到排序完成。
冒泡排序过程

1
2
3
4
5
6
def bubblesort(nums):
for i in range(len(nums)-1, 0, -1):
for j in range(0, i):
if nums[j]>nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
return nums

性质:时间复杂度:$O(n^2)$,空间复杂度:$O(1)$,稳定排序,原地排序。
优化冒泡排序算法:假如开始的第一队到结尾的最后一对,相邻的元素之间都没有发生交换的操作,这意味着右边的元素总是大于等于左边的元素,此时数组已经是有序的,无须再对剩余的元素重复比较下去。
1
2
3
4
5
6
7
8
9
10
11
12
def bubblesort2(nums):
if not nums:
return
for i in range(len(nums)-1, 0, -1):
swapped = False
for j in range(0, i):
if nums[j]>nums[j+1]:
nums[j], nums[j+1] = nums[j+1], nums[j]
swapped = True
if not swapped:
break
return nums

希尔排序

希尔排序可以说是插入排序的一种变种,无论是插入排序还是冒泡排序,如果数组的最大值刚好是在第一位,要将它挪到正确的位置就需要n-1次移动。也就是说原数组的一个元素如果距离它正确的位置很远,则需要与相邻元素交换很多次才能到达正确的位置,这是相对比较花时间了。
希尔排序就是为了加快速度简单地改进插入排序,交换不相邻的元素对数组的局部进行排序。基本思想是:先将整个待排序列分割成为若干子序列分别进行插入排序,待整个序列中的记录基本有序时再对全体记录进行一次直接插入排序。希尔排序的核心在于间隔序列的设定,既可以提前设定好间隔序列,也可以动态地定义间隔序列。
希尔排序过程

1
2
3
4
5
6
7
8
9
10
11
12
13
def shellSort(nums):
gap = 1
while gap<len(nums)//3:
gap = gap*3+1
while gap>0:
for i in range(gap, len(nums)):
curNum, preIndex = nums[i], i-gap
while preIndex>=0 and curNum<nums[preIndex]:
nums[preIndex+gap] = nums[preIndex]
preIndex -= gap
nums[preIndex+gap] = curNum
gap //= 3
return nums

性质:时间复杂度:$O(nlogn)$,空间复杂度:$O(1)$,非稳定排序,原地排序。

归并排序

归并排序使用递归分治的思想,其基本思想是:把待排序列看成两个有序的子序列,然后合并两个子序列,接着把子序列再看成两个有序的子子序列,…,以此类推。
归并排序过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def mergeSort(nums):
if len(nums)<=1:
return nums
# 归并过程
def merge(left, right):
result = []
i = j = 0
while i<len(left) and j<len(right):
if left[i]<=right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result = result + left[i:] + right[j:]
return result
# 递归过程
mid = len(nums)//2
left = mergeSort(nums[:mid])
right = mergeSort(nums[mid:])
return merge(left, right)

非递归做法
1
2
3
4
5
6
7
8
9
10
11
12
def mergeSort2(nums):
i = 1 # i是步长
while i<len(nums):
left = 0
while left<len(nums):
mid = left+i
right = min(left+2*i, len(nums))
if mid<right:
nums[left:right]=merge(nums[left:mid], nums[mid:right])
left += 2*i
i *= 2
return nums

快速排序

快速排序通常明显比同为$O(nlogn)$的其他算法更快,因此常被采用,而且快排采用了分治的思想,所以在面试中经常看到快排的影子。
步骤:
1)从数列中跳出一个元素作为基准数。
2)分区过程,将比基准数大的放在右边,小于或等于它的数都放在左边。
3)再对左右分区递归执行第二步,直至各区间只有一个数。
快排过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 划分策略,适用于链表
def partition(nums, left, right):
if left == right:
return left
pivot = left
slow, fast = left, left+1
while fast<=right:
if nums[fast]<nums[pivot]:
slow += 1
nums[slow], nums[fast] = nums[fast], nums[slow]
fast += 1
nums[pivot], nums[slow] = nums[slow], nums[pivot]
return slow

# 递归方法
def quickSort(nums, left, right):
if left<=right:
midIndex = partition(nums, left, right)
quickSort(nums, left, midIndex-1)
quickSort(nums, midIndex+1, right)
return nums

非递归方式,利用栈的思想将需要继续排序的首尾下标存入栈中,不断弹栈进行分区操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
def partition2(nums, left, right):
mid = nums[left]
while left<right:
while left<right and nums[right]>mid:
right -= 1
nums[left] = nums[right]
while left<right and nums[left]<=mid:
left += 1
nums[right] = nums[left]
nums[left] = mid
return left
def quickSort2(nums):
if len(nums)<=1:
return nums
stack = []
left, right = 0, len(nums)-1
stack.append(left)
stack.append(right)
while stack:
r = stack.pop()
l = stack.pop()
midIndex = partition2(nums, l, r)
if l<midIndex:
stack.append(l)
stack.append(midIndex-1)
if r>midIndex:
stack.append(midIndex+1)
stack.append(r)
return nums

堆排序

堆排序是借助堆来实现的选择排序,思想同选择排序,以下以大根堆为例。注意:如果想升序排序就使用大根堆,反之使用小根堆。原因是堆顶元素需要交换到序列尾部。
首先,实现堆排序需要解决两个问题:
1)如何由一个无序序列建成一个堆?
2)如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?
第一个问题,可以直接使用线性数组来表示一个堆,由初始的无序序列建成一个堆就需要自底向上从第一个非叶元素开始按个调整成一个堆。
第二个问题,怎么调整成堆?首先是将堆顶元素和最后一个元素交换,然后比较当前堆顶元素的左右孩子节点,因为除了当前的堆顶元素,左右孩子均满足条件,这时需要选择当前堆顶元素与左右孩子节点的较大者(大根堆)交换,直到叶子节点。我们称这个自堆顶至叶子节点的调整为筛选。
从一个无序序列建堆的过程就是一个反复筛选的过程。若将此序列看成是一个完全二叉树,则最后一个非终端节点是n/2(取底)个元素,由此筛选即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 大根堆
def heapSort(nums):
def adjustHeap(nums, i, size):
lchild = 2*i+1
rchild = 2*i+2
largest = i
if lchild<size and nums[lchild]>nums[largest]:
largest = lchild
if rchild<size and nums[rchild]>nums[largest]:
largest = rchild
if largest != i:
nums[largest], nums[i] = nums[i], nums[largest]
adjustHeap(nums, largest, size)
# 建立堆
def buildHeap(nums, size):
for i in range(len(nums)//2)[::-1]: # 从倒数第一个非叶子节点开始建立大根堆
adjustHeap(nums, i, size) # 对所有非叶子节点进行堆的调整
# 堆排序
size = len(nums)
buildHeap(nums, size)
for i in range(len(nums))[::-1]:
nums[0], nums[i] = nums[i], nums[0]
adjustHeap(nums, 0, i)
return nums

计数排序

(待补充)

桶排序

(待补充)

基数排序

(待补充)

深度学习

SGD,Momentum,Adagard,Adam原理

1. SGD、BGD和Mini-BGD:

SGD(stochastic gradient descent):随机梯度下降,算法在每读入一个数据都会立刻计算loss function的梯度来更新参数,假设loss function为L(w),下同。

优点:收敛的速度快,可以实现在线更新
缺点:很容易陷入到局部最优,困在马鞍点
BGD(batch gradient decent):批量梯度下降,算法在读取整个数据集后累加来计算损失函数的梯度。

优点:如果loss function为convex(凸函数),则基本可以找到全局最优解
缺点:数据处理量大,导致梯度下降慢;不能实时增加实例,在线更新;训练占内存
Mini-BGD(mini-batch gradient descent):选择小批量数据进行梯度下降,这是一个折中的方法,采用训练集的子集(mini-batch)来计算loss function的梯度:

这个优化方法用的比较多,计算效率高且收敛稳定,是现在深度学习的主流方法。当使用小批量样本来估计梯度时,由于估计的梯度往往会偏离真实的梯度,这可以视作在学习的过程中加入了噪声扰动,这种扰动会带来一些正则化的效果。
batch size越大,则小批量样本估计总体梯度约可靠,则每次参数更新沿总体梯度的负方向的概率越大。但是,训练误差收敛速度快,并不意味着模型的泛化性能强,此时的噪声太小,不足以将参数推出尖锐极小值的吸引区域。解决方案是:提高学习率,从而放大梯度噪声的贡献。
上面的方法都存在一个问题,就是update更新的方向完全依赖计算出来的梯度,很容易陷入局部最优的马鞍点。能不能改变其走向,又保证原本的梯度方向,就像向量变换一样,我们模拟物理中物体流动的动量概念(惯性),引入Momentum的概念。

2. Momentum

在更新方向的时候保留之前的方向,增加稳定性而且还有摆脱局部最优的能力。

若当前梯度的方向与历史梯度方向一致(表明当前样本不太可能为异常点),则会增强这个方向的梯度,若当前梯度与历史梯度方向不一致,则梯度会衰减。一种形象的解释是:我们把一个球推下山,球在下坡时积聚动量,在途中变得越来越快,可视为空气阻力,若球的方向发生变化,则动量会衰减。

3. Adagrad

Adagrad(adaptive gradient)自适应梯度算法,是一种改进的随机梯度下降算法。以前的算法中,每一个参数都使用相同的学习率,而Adagrad算法能够在训练中自动对learning_rate进行调整,出现频率较低参数采用较大的更新,出现频率较高的参数采用较小的更新,根据描述这个优化方法很适合处理稀疏数据。

这个对角线矩阵的元素代表的是参数的出现频率,每个参数的更新:

4. RMSprop

RMSprop(root mean square propagation)也是一种自适应学习率方法,不同之处在于,Adagrad会累加之前所有的梯度平方,RMSprop仅仅是计算对应的平均值,可以缓解Adagrad算法学习率下降较快的问题。

其中 $\gamma$ 是遗忘因子

参数更新

5. Adam

Adam(adaptive moment estimation)是对RMSprop优化器的更新,利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。优点:每一次迭代学习率都有一个明确的范围,使得参数变化很平稳。Adam记录梯度的一阶矩(first moment),即过往梯度与当前梯度的平均,这体现了惯性保持;另一方面,Adam还记录梯度的二阶矩(second moment),即过往梯度平方与当前梯度平方的平均,这类似AdaGrad方法,为不同参数产生自适应的学习速率。

其中,m为一阶矩估计,v为二阶矩估计,然后进行估计校正,实现无偏估计

拟牛顿法

牛顿法的基本思想是在现有极小点估计值的附近对$f(x)$做二阶泰勒展开,进而找到极小点的一个估计值。这个方法需要目标函数是二阶连续可微的。由于牛顿法中需要求解二阶偏导,这个计算量会比较大,而且又是目标函数求出的海森矩阵无法保持正定,因此提出了拟牛顿法。拟牛顿法是一些算法的总称,它们的目的是通过某种方式来近似表示海森矩阵(或者它的逆矩阵)。和牛顿法的区别是,它在更新参数$w$之后更新一下近似海森矩阵的值,而牛顿法是在更新$w$之前完全的计算一遍海森矩阵。
梯度下降和拟牛顿法的异同?
1)参数更新模式相同。
2)梯度下降法利用误差的梯度来更新参数,拟牛顿法利用海森矩阵的近似来更新参数。
3)梯度下降是泰勒级数的一阶展开,而拟牛顿法是泰勒级数的二阶展开。
4)SGD能保证收敛,但是L-BFGS在非凸时不收敛。

Batch Size

在合理的范围内,增大batch size的好处

1)内存利用率提高了,大矩阵乘法的并行化效率提高
2)跑完一次epoch所需的迭代次数减少,对于相同数据量的处理速度进一步加快。
3)在一定范围内,一般来说batch size越大,其确定的下降方向越准,引起训练振荡越小。

盲目增大batch size的坏处

1)内存利用率提高,但是内存容量可能撑不住
2)跑完一次epoch所需的迭代次数减少,要想达到相同的精度,其所花费的时间大大增加,从而对参数的修正也就显得更加缓慢。
3)batch size增大到一定程度,其确定的下降方向已经基本不再改变。

调节batch size对训练效果的影响

1)batch size太小,模型表现效果很差(error增大)
2)随着batch size增大,处理相同数据量的速度越快
3)随着batch size增大,达到相同精度所需的epoch数量越多。
4)由于上述两种因素的矛盾,batch size增大到某个时候,达到时间上的最优。
5)由于最终收敛精度会陷入不同的局部极值,因此batch size增大到某些时候,达到最终收敛精度上的最优。

L1、L2范数

在机器学习中几乎可以看到在损失函数后面都会添加一个额外项,常用的额外项一般有两种,称为L1正则化和L2正则化,或者L1范数和L2范数。L1范数和L2范数可以看做是损失函数的惩罚项,所谓“惩罚”是指对损失函数中的某些参数做些限制。对于现在的回归模型,使用L1范数的模型叫做Lasso回归,使用L2范数的模型叫做Ridge回归(岭回归)。

L1范数

L1范数是指向量中各个元素绝对值之和,也叫“稀疏规则算子”(Lasso regularization)。稀疏的意思是可以让权重矩阵的一部分值等于0。为什么L1范数会使权值稀疏?有一种回答“它是L0范数的最优凸近似”,还存在一种更优雅的回答:任何的规则算子,如果他在$w_{i}=0$处不可微,并且可以分解为一个“求和”的形式,那么这个规则化算子可以实现稀疏。

L1范数可以实现稀疏,而实现稀疏的作用为:
1) 可解释性:可以看到到底是哪些特征和预测的信息有关
2) 特征选择:输入的x的大部分特征与输出y是没有关系的,如果让参数矩阵w中出现许多0,则可以直接干掉与y无关的元素,也就是选择出于y真正相关的特征。如果不这么做,那么x中本来与y无关的特征也加入到模型中,虽然会更好的减小训练误差,但是在预测新样本时会考虑到无关的信息,干扰了预测。

L2范数

L2范数是指向量中各元素的平方和然后再求平方根,也叫做“岭回归(Ridge Regression)”,或叫做“权值衰减(weight decay)”。

L2范数与L1范数不同,它不会让参数等于0,而是让每个参数都接近于0。L2范数的优点是:
1) 防止过拟合。一般的用法是在损失函数后面加上w的L2范数,即$||x||_{2}$,这是一种规则。
2) 优化求解变得稳定快速。简单地说它可以让$w$在接近全局最优点$w^*$的时候,还保持较大的梯度。这样可以跳出局部最优,也使得收敛速度变快。

L1和L2正则分别有什么特点?为何L1稀疏?

L1范数对异常值更鲁棒,在0点不可导计算不方便,且没有唯一解,L1范数输出稀疏,会把不重要的特征值置0。
L2范数计算方便,对异常值敏感,且有唯一解。
L1范数求导
加入L1正则项后,对带正则项的目标函数求导,正则项部分产生的导数在原点左边部分是−C,在原点右边部分是C,因此,只要原目标函数的导数绝对值小于C,那么带正则项的目标函数在原点左边部分始
终是递减的,在原点右边部分始终是递增的,最小值点自然在原点处。相反,L2正则项在原点处的导数是0,只要原目标函数在原点处的导数不为0,那么最小值点就不会在原点,所以L2只有减小w绝对值的作用,对解空间的稀疏性没有贡献。

L1不可导的时候该怎么办

当损失函数不可导,梯度下降不再有效,可以使用坐标轴下降法。梯度下降是沿着当前点的负梯度方向进行参数更新,而坐标轴下降法是沿着坐标轴的方向。假设有m个特征个数,坐标轴下降法进行参数更新的时候,先固定m-1个值,然后再求另外一个的局部最优解,从而避免损失函数不可导问题。坐标轴下降法每轮迭代都需要O(mn)的计算,和梯度下降算法相同。

激活函数

神经网络的每个神经元接受上一层神经元的输出值作为本神经元的输入值,并将输入值传递给下一层,输入神经元节点会将输入属性值直接传递给下一层(隐藏层或输出层)。上层函数的输出和下层节点的输入之间具有一个函数关系,这个函数称为激活函数。如果不用激活函数,在这种情况下每一层节点的输入都是上一层输出的线性函数,很容易验证,无论神经网络有多少层,输出都是输入的线性组合,与没有隐藏层效果相当,这种情况就是最原始的感知机(Perceptron),那么网络的逼近能力就相当有限。我们引入非线性函数作为激励函数,这样深层神经网络表达能力就更加强大,几乎可以逼近任意函数。

Sigmoid函数

Signoid是常用的非线性的激活函数,它的数学形式如下:

其导数为:

Sigmoid的几何如下:
Sigmoid函数
特点:它能够把输入的连续实值变换为0和1之间的输出,对于非常大的负数则输出为0,非常大的正数则输出为1.
缺点:
1) 在深度神经网络中梯度反向传递时导致梯度爆炸和梯度消失,其中梯度爆炸发生的概率非常小,而梯度消失发生的概率比较大。Sigmoid函数的倒数如下所示:
Sigmoid的导数
如果我们初始化神经网络的权值为[0,1]之间的随机值,由反向传播算法的数学推导可知,梯度从后向前传播时,每传递一层梯度值都会减少为原来的0.25倍,如果神经网络隐藏层特别多时,那么梯度在多层传递之后就变得非常小接近于0,即出现梯度消失现象;当网络权值初始化为$(1,+\infty)$区间的值,则会出现梯度爆炸情况。
2) Sigmoid的输出不是0均值,这会导致后一层的神经元将上一层的神经元输出的非0均值的信号作为输入。产生的结果是:如果$x>0, f=w^Tx+b$,那么对$w$求局部梯度则都为正,这样反向传播的过程中$w$要么都向正方向更新,要么都往负方向更新,使得收敛缓慢。当然,如果按batch训练,那么那个batch可能会得到不同的信号,这个问题可以缓解一下。非0均值问题虽然会产生一些不好的影响,不过跟梯度消失问题相比还是要好很多。
3) 其解析式中含有幂运算,计算求解时相对比较耗时。对于规模较大的深度网络,这回较大地增加训练时间。

tanh函数

tanh函数的解析式:

其导数为:

tanh函数及其导数的几何图像如下:
tanh函数及其导数
tanh函数解决了Sigmoid函数不是zero-centered输出的问题,然而,梯度消失的问题和幂运算的问题仍然存在。

Relu函数

Relu函数的解析式:

Relu函数及其导数的图像如下图所示:
Relu函数及其导数
ReLU其实是一个取最大值函数,注意这并不是全区间可导的,但是我们可以取sub-gradient,如上图所示。
优点:1)解决了梯度消失问题(gradient vanishing)问题(在正区间)。 2)计算速度非常快,只需要判断输入是否大于0。 3)收敛速度远快于sigmoid和tanh函数。
ReLU也有几个需要注意的问题:
1) ReLU的输出不是zero-centered。
2) Dead ReLU Problem,指的是某些神经元可能永远不会被激活,导致相应的参数永远不会被更新。有两个主要原因可能会导致这种情况:参数的初始化或learning_rate太大导致训练过程中参数更新太大进入这种状态。
解决的方法是采用Xavier初始化方法,以及避免将learning rate设置太大或使用adagrad等自动调节learning rate的算法。Xavier初始化方法是一种很有效的神经网络初始化方法,为了使得网络中信息更好的流动,每一层输出的方法应该尽量相等。

Leaky ReLU函数

函数表达式:

Leaky Relu函数及其导数的图像如下图所示:
Leaky ReLU函数及其导数
图中左半边直线斜率非常接近于0,所以看起来像是平的。为了解决Dead ReLU Problem,通过将ReLU的前半段设为$\alpha x$而不是0,通常$\alpha =0.01$。理论上讲,Leaky ReLU有ReLU的所有优点,外加不会有Dead ReLU问题,但在实际操作中,并没有完全证明Leaky ReLU总是好于ReLU。

ELU函数

函数表达式为:

函数及其导数的图像如下:
ELU函数及导数图像
显然,ELU有ReLU的基本所有优点,以及不会有Dead ReLU问题,输出的均值接近于0,是zero-centered。它的一个小问题在于计算量稍大。

神经网络权重初始化方式

在深度学习找那个,神经网络的权重初始化方法(weight initialization)对模型的收敛速度和性能有着至关重要的影响。神经网络其实就是对权重参数w的不停迭代更新,以期达到较好的性能。在深度神经网络中,随着层数的增多,在梯度下降的过程中,极易出现梯度消失或者梯度爆炸。因此,对权重w的初始化显得至关重要,一个好的权重初始化虽然不能完全解决梯度消失和梯度爆炸问题,但是对于处理这两个问题是由很大的帮助的,并且十分有利于模型性能和收敛速度。

初始化为0

在线性回归和logistics回归中可以使用,因为隐藏层只有一层。在超过一层的神经网络中就不能够使用了。因为如果所有的权重参数都为0,那么所有的神经元输出都是一样的,在反向传播时向后传递的梯度也是一致,将无法发挥多层的效果,实际上相当于一层隐藏层。

随机初始化

卷积层的方差为:

使用高斯随机初始化时要把W随机初始化到一个相对较小的值,因为如果X很大的话,W又相对较大,会导致输出值特别大,这样如果激活函数是sigmoid,就会导致sigmoid的输出值为1或0,导致更多的问题。但是随机初始化也有缺点,在均值为0,方差为1的高斯分布中,当神经网络层数增加时,会发现越往高层的激活函数(tanh函数)的输出值几乎都接近于0,使得神经元不被激活。

Xavier初始化

每层的权重初始化为:

服从均匀分布,$nj$为输入层的参数,$n{j+1}$为输出层参数。
Xavier是为了解决随机初始化问题而提出的一种初始化方式,其思想是尽可能让输入和输出服从相同的分布,这样能够避免高层的激活函数的输出值趋向于0。虽然Xavier初始化能很好地用于tanh函数,但是对于目前最常用的ReLU激活函数,还是无能为力,因此引出He initialization。

MSRA/He initialization

Xavier初始化对于Relu激活函数表现非常不好,因此何恺明针对ReLU重新推导,每层的初始化公式为:

是一个均值为0,方差为$\frac{2}{n}$的高斯分布。
缺点是:MSRA方法只考虑一个方向,无法使得正向反向传播时方差变化都很小。

损失函数

损失函数、代价函数与目标函数

损失函数(Loss Function):是定义在单个样本上的,是指一个样本的误差。
代价函数(Cost Function):是定义在整个训练集上,是所有样本误差的平均值,也就是所有损失函数值的平均。
目标函数(Object Function):是指最终需要优化的函数,一般来说是代价函数+正则化项。

常用的损失函数

(1)0-1损失函数(0-1 loss function)

即,当预测错误时,损失函数为1,当预测正确时,损失函数值为0.该损失函数不考虑预测值与真实值之间的误差程度。
(2)平方损失函数(quadratic loss function)

是指预测值与实际值差的平方。
(3)绝对值损失函数(absolute loss function)

该损失函数只是取了绝对值而不是求平方值,差距不会被平方放大。
(4)对数损失函数(logarithmic loss function)

该损失函数用到了极大似然估计思想。P(Y|X)通俗的解释是:在当前模型的基础上,对于样本X,其预测值为Y,也就是预测正确的概率。由于概率之间同时满足需要使用乘法,为了将其转化为加法,我们将其取对数。最后由于是损失函数,所以预测正确的概率越高,其损失值应该越小,因此再加个符号取反。
(5)Hinge loss
Hinge loss一般分类算法中的损失函数,尤其是SVM,其定义为:

其中$y=+1$或$y=-1$,$f(x)=wx+b$,当为SVM的线性核时。

常用的代价函数

(1)均方误差(Mean Squared Error)

均方误差是指参数估计值与真实值之差的平方的期望值,MSE可以评价数据的变化程度,MSE的值越小,说明预测模型描述实验数据具有更好的精度。(i表示第i个样本,N表示样本总数)。
通常用来做回归问题的代价函数。
(2)均方根误差

均方根误差是均方误差的算术平方根,能够直观观测预测值与真实值的离散程度。通常用来作为回归算法的性能指标。
(3)平均绝对误差(Mean Absolute Error)

平均绝对误差是绝对误差的平均值,平均绝对误差能更好地反映预测值误差的实际情况。通常用来作为回归算法的性能指标。
(4)交叉熵代价函数(Cross Entry)

交叉熵是用来评估当前训练得到的概率分布于真实分布的差异情况,减少交叉熵损失就是在提高模型的预测准确率。其中p(x)是指真实分布的概率,q(x)是模型通过数据计算出来的概率估计。
对于二分类模型的交叉熵代价函数:

其中f(x)可以是sigmoid函数或深度学习中的其他激活函数,而$y{(i)}\in 0,1$。
交叉熵通常用作分类问题的代价函数。
常用损失函数

为什么分类问题用 cross entropy,而回归问题用 MSE?

优秀解答
当sigmoid函数和MSE一起使用时会出现梯度消失。原因如下:
1)MSE对参数w,b的偏导,其中预测值为a,真实值为y,则$z=wx+b$,$a=\sigma(z)$,$\sigma$是sigmoid函数,损失函数$J=(y-a)^2$。

2)cross-entry对参数的偏导

使用MSE时,w、b的梯度均与sigmoid函数对z的偏导有关系,而sigmoid函数的偏导在自变量非常大或者非常小时,偏导数的值接近于零,这将导致w、b的梯度将不会变化,也就是出现所谓的梯度消失现象。而使用cross-entropy时,w、b的梯度就不会出现上述的情况。
当MSE和交叉熵同时应用到多分类场景下时,(标签的值为1时表示属于此分类,标签值为0时表示不属于此分类),MSE对于每一个输出的结果都非常看重,而交叉熵只对正确分类的结果看重。交叉熵的损失函数只和分类正确的预测结果有关系,而MSE的损失函数还和错误的分类有关系,该分类函数除了让正确的分类尽量变大,还会让错误的分类变得平均,但实际在分类问题中这个调整是没有必要的。但是对于回归问题来说,这样的考虑就显得很重要了。所以,回归问题熵使用交叉熵并不合适。

Batch Normalization

BN归纳
Batch Normalization就是在训练过程中使得每一层神经网络的输入保持相同分布的。BN的基本思想相当直观:随着网络深度加深或者在训练过程中,神经元的输入值分布逐渐发生偏移或变动,使得整体分布逐渐往非线性函数的取值区域的上下限两端靠近,所以这导致反向传播时低层神经网络的梯度消失,这是训练深层神经网络收敛越来越慢的本质原因。而BN就是通过一定的规范化手段,把该层任意神经元这个输入值的分布强行拉回到均值为0方差为1的标准正态分布,其实就是把越来越偏的分布强制拉回到标准的分布,这样使得激活输入值落在非线性函数对输入比较敏感的区域,这样输入的小变化就是导致损失函数较大的变化,使得梯度变大,避免梯度消失问题产生,而且梯度变大意味着学习收敛速度快,能大大加快训练速度。
如果都通过BN,那么不就跟把非线性函数替换成线性函数效果相同吗?这意味着什么?我们知道,如果是多层的线性函数变换其实这个深层网络就没有意义,因为多层线性网络跟一层线性网络是等价的。这意味着网络的表达能力又下降了。所以BN为了保证非线性的获得,对变换后的满足均值为0方差为1的$x$又进行了scale加上shift操作($y=scale*x+shift$),每个神经元增加了两个参数scale和shift参数,这两个参数是通过训练学习到的,意思是通过scale和shift把输入值从标准正太分布左移或右移一点并拉伸或缩短一点,每个实例的挪动情况不一样,来找到一个线性和非线性的较好平衡点,使得既能享受非线性的较强表达能力的好处,又避免太靠近非线性区两头使得网络的收敛速度太慢。
BatchNorm在网络中的作用:BN层添加在激活函数前,对输入激活函数的输入进行归一化,这样解决了输入数据发生偏移和增大的影响。

BatchNorm的优缺点

优点:1)极大提升了训练速度,使得收敛过程大大加快;2)还能增加分类效果,一种解释是这类似于Dropout的一种防止过拟合的正则化表达方式,所以不用Dropout也能达到相当的效果;3)调参过程也简单了,对于初始化要求没那么高,可以使用大的学习率。
缺点:1)高度依赖于batch size的大小,它要求batch size都比较大,因此不适合batch size较小的场景。2)不适合RNN网络,因为不同样本的长度不同,RNN的深度是不固定的。同一个batch中多个样本会产生不同深度的RNN,因此很难对同一层的样本进行归一化。

BN的计算流程

首先,对(B,W,H)通道计算样本的均值和方差,将样本数据进行标准化处理,然后引入$\gamma$和$\beta$两个参数进行平移和缩放处理,让网络可以学习恢复出原始网络所要学习的特征分布。

BN的训练和测试

对于BN,在训练时,是对每一批的训练数据进行归一化,也即用每一批数据的均值和方差。
而在测试时,比如进行一个样本的预测,就并没有batch的概念,因此,这个时候用的均值和方差是全量训练数据的均值和方差,这个可以通过移动平均法求得。
BN训练时为什么不用全量训练集的均值和方差呢?
因为用全量训练集的均值和方差容易过拟合,对于BN,其实就是对每一批数据进行归一化到一个相同的分布,而每一批数据的均值和方差会有一定的差别,而不是用固定的值,这个差别实际上能够增加模型的鲁棒性,也会在一定程度上减少过拟合。

Dropout

Dropout指的是让这些神经元失效或者状态抑制,可以防止参数过分依赖训练数据,增加参数对数据集的泛化能力。
在训练阶段,Dropout通过在每次迭代中随机丢弃一些神经元来改变网络的结构,以实现训练不同结构的神经网络的目的;而在测试阶段,Dropout则会使用全部的神经元,这相当于之前训练的不同结构的网络全都参与对最终结果的投票,以获得较好的效果。Dropout通过这种方式提供了一种强大、快捷且易实现的近似Bagging方法。需要注意的是,在原始的Bag个ing中所有模型是相互独立的,而Dropout则有所不同,这里不同的网络其实是共享了参数的。
另一方面,Dropout能够减少神经元之间复杂的共适应关系。由于Dropout每次丢弃的神经元是随机选择的,所以每次保留下来的网络会包含不同的神经元,这样在训练过程中,网络权值的更新不会依赖于隐节点之间的固定关系(固定关系可能会产生一些共同作用从而影响网络的学习过程)。换句话说,网络中每个神经元不会对另一个特定神经元的激活非常敏感,这使得网络能够学习到一些更加泛化的特征。

Dropout 在训练和测试时都需要嘛?

Dropout 在训练时采用,是为了减少神经元对部分上层神经元的依赖,类似将多个不同网络结构的模型集成起来,减少过拟合的风险。
而在测试时,应该用整个训练好的模型,因此不需要dropout。

Dropout 如何平衡训练和测试时的差异呢?

Dropout ,在训练时以一定的概率使神经元失活,实际上就是让对应神经元的输出为0
假设失活概率为 p ,就是这一层中的每个神经元都有p的概率失活,如下图的三层网络结构中,如果失活概率为0.5,则平均每一次训练有3个神经元失活,所以输出层每个神经元只有3个输入,而实际测试时是不会有dropout的,输出层每个神经元都有6个输入,这样在训练和测试时,输出层每个神经元的输入和的期望会有量级上的差异。
因此在训练时还要对第二层的输出数据除以(1-p)之后再传给输出层神经元,作为神经元失活的补偿,以使得在训练时和测试时每一层输入有大致相同的期望。

在循环神经网络中如何使用Dropout?

Dropout方法会在训练过程中将网络中的神经元随机丢弃,然而循环神经网络具有记忆功能,其神经元的状态包含了之前时刻的状态信息,如果直接用Dropout删除一些神经元,会导致循环神经网络的记忆力减退。另外,有实验表明,如果循环神经网络不同时刻间的连接层中加入噪声,则噪声会随着序列长度的增加而不断放大,并最终淹没重要的信号信息。
在循环神经网络中,连接层可以分成两种类型:一种是从t时刻的输入一直到t时刻的输出之间的连接,称为前馈连接;另一种是从t时刻到t+1时刻之间的连接,称为循环连接。如果要将Dropout用在循环神经网络上,一个较为直观的思路就是,只将Dropout用在前馈连接上,而不用在循环连接上。注意,这里Dropout随机丢弃的是连接,而不是神经元。
然而,只在前馈连接中应用Dropout对于过拟合问题的缓解效果并不太理想,这是因为循环神经网络中大量参数其实是在循环连接中。因此,论文提出基于变分推理的Dropout方法,即对于同一个序列,在其所有时刻的循环连接上采用相同的丢弃方法,也就是说不同时刻丢弃的连接是相同的。实验结果表明,这种Dropout在语言模型和情感分析中会获得较好的效果。

深度学习几种归一化(BN、LN、IN、GN)

BN、LN、IN和GN这四个归一化的计算流程几乎是一样的,可以分为四步:1)计算出均值;2)计算出方差;3)归一化处理到均值为0,方差为1;4)变化重构,恢复出这一层网络所要学到的分布。
我们先用一个示意图来形象的表现BN、LN、IN和GN的区别,在输入图片的维度为(NCHW)中,HW是被合成一个维度,这个是方便画出示意图,C和N各占一个维度。
BN,LN,IN,GN的区别
Batch Normalization:
1)BN的计算就是把每个通道的NHW单独拿出来归一化处理
2)针对每个channel我们都有一组γ,β,所以可学习的参数为2*C
3)当batch size越小,BN的表现效果也越不好,因为计算过程中所得到的均值和方差不能代表全局。
Layer Normalizaiton:
1)LN的计算就是把每个CHW单独拿出来归一化处理,不受batchsize 的影响
2)常用在RNN网络,但如果输入的特征区别很大,那么就不建议使用它做归一化处理
Instance Normalization:
1)IN的计算就是把每个HW单独拿出来归一化处理,不受通道和batchsize 的影响。
2)常用在风格化迁移,IN的效果优于BN,因为在这类生成式方法中,每张图片自己的风格比较独立,不应该与batch中其他图片产生太大联系。但如果特征图可以用到通道之间的相关性,那么就不建议使用它做归一化处理。
Group Normalization:
1)GN的计算就是把先把通道C分成G组,然后把每个gHW单独拿出来归一化处理,最后把G组归一化之后的数据合并成CHW。
2)GN介于LN和IN之间,当然可以说LN和IN就是GN的特列,比如G的大小为1或者为C。

解决模型欠拟合与过拟合常用方法

欠拟合

欠拟合:欠拟合的原因大多是模型不够复杂、拟合函数的能力不够。
因此,从数据层面考虑,可以增加新特征,例如:组合、泛化、相关性、高次特征等;从模型层面考虑,可以增加模型的复杂度,例如SVM的核函数、DNN等更复杂模型,去掉正则化或减少正则化参数,加深训练轮数等。

过拟合

过拟合:成因是给定的数据集相对过于简单,使得模型在拟合函数时过分考虑了噪声等不必要的数据间关联。
解决方法:
1)数据扩增:人为增加数据量,可以用重采样、上采样、增加随机噪声、GAN、图像数据的空间变换(平移旋转镜像)、尺度变换(缩放裁剪)、颜色变换、改变分辨率、对比度、亮度等。
2)针对神经网络,采用dropout的方法:dropout的思想是当一组参数经过某一层神经元的时候,去掉这一层上的部分神经元,让参数只经过一部分神经元进行计算。这里的去掉不是真正意义上的去除,只是让参数不经过一部分神经元计算,从而减少了神经网络的规模(深度)。
3)提前停止训练
也就是减少训练的迭代次数。从上面的误差率曲线图,理论上可以找到有个训练程度,此时验证集误差率最低,视为拟合效果最好的点。
4)正则化
在所定义的损失函数后面加入一项永不为0的部分,那么经过不断优化损失函数还是会存在的。
L0正则化:损失函数后面加入L0范数,也就是权重向量中非零参数的个数。特点是可以实现参数的稀疏性,使尽可能多的参数都为0;缺点是在优化时是NP难问题,很难优化。
L1正则化:在损失函数后面加入权重向量的L1范数。L1范数是L0范数的最优凸近似,比L0范数容易优化,也可以很好地实现参数稀疏性。
L2正则化:在损失函数后面加入参数L2范数的平方项。与L0、L1不同的是,L2很难使某些参数达到0,它只能是参数接近0。
5)针对DNN,采用batch normalization:即BN,既能提高泛化能力,又大大提高训练速度,现被广泛应用于DNN的激活层之前。主要优势:减少了梯度对参数大小和初始值的依赖,将参数值(特征)缩放在[0,1]区间(若针对Relu还限制了输出的范围),这样反向传播时梯度控制在1左右,使得网络在较高的学习率之下也不易发生梯度爆炸或弥散(也防止了在使用sigmoid作为激活函数时训练容易陷入梯度极小饱和或极大的极端情况)。

多分类问题

one vs rest

由于概率函数$h_{\theta}(x)$所表示的是样本标记为某一类型的概率,但可以将一对一(二分类)扩展为一对多(one vs rest):
1)将类型$class_1$看作正样本,其他类型全部看作负样本,然后我们就可以得到样本类型为该类型的概率$p_1$;
2)然后再讲另外类型$class_2$看作正样本,其他类型全部看作负样本,同理得到$p_2$;
3)以此循环,我们可以得到该待预测样本的标记类型分别为类型$class_i$时的概率$p_i$,最后我们取$p_i$中最大的那个概率对应的样本标记类型为我们的待预测样本类型。

softmax函数

使用softmax函数构造模型解决多分类问题,与logistic回归不同的是,softmax回归分类模型会有多个的输出,且输出个数与类别个数相等,输出为样本$X$的各个类别的概率,最后对样本进行预测的类型为概率最高的那个类别。
softmax反向传播推导

反向传播原理

反向传播过程:将训练集的数据输入到神经网络的输入层,经过隐藏层最终到达输出层输出结果,这是神经网络的前向传播过程;由于神经网络的输出结果和实际结果存在误差,计算估计值和实际值之间的误差,并将误差从输出层向隐藏层反向传播,直至传播至输入层;在反向传播过程中,根据误差调整各种参数的值,不断迭代上述的过程,直至收敛。
推导过程

什么是梯度消失和梯度爆炸

在反向传播过程中需要对激活函数进行求导,如果导数大于1,那么随着网络层数的增加梯度更新将会朝着指数爆炸的方式增加这就是梯度爆炸。同样如果导数小于1,那么随着网络层数的增加梯度更新信息会朝着指数衰减的方式减少这就是梯度消失。因此,梯度消失、爆炸,其根本原因在于反向传播训练法则,属于先天不足。

梯度消失的原因

1)在反向传播过程中梯度呈指数衰减,随着网络层数的加深,梯度减小为0;2)在反向传播过程中,神经元的输入值位于激活函数的非线性区域的上下限两端,使得梯度消失。
而梯度爆炸一般出现在深层网络和权值初始化太大的情况下。

梯度消失解决方案

1)预训练加微调
2)添加正则项
3)使用relu、leakrelu、elu等激活函数
4)使用batch normalization
5)使用残差结构
6)使用LSTM

性能指标

召回率、精确率、准确率

精确率是针对预测结果而言的,它表示的是预测为正的样本中有多少是真正的正样本。那么预测为正就有两种可能,一种是把正类预测为正类(TP),另一种就是把负类预测为正类(FP),也就是:

而召回率是针对原来的样本而言,它表示的是样本中的正例有多少被预测正确,也有两种可能,一种是把原来的正类预测成正类(TP),另一种就是把原来的正类预测为负类(FN)。

准确率就是所有样本中预测出正例的概率。

Accuracy的局限性

当正负样本极度不均衡时存在问题!比如,正样本有99%时,分类器只要将所有样本划分为正样本就可以达到99%的准确率。但显然这个分类器是存在问题的。当正负样本不均衡时,常用的评价指标为ROC曲线和PR曲线。

ROC曲线和PR曲线

定义真正例率(True Positive Rate)为:$TPR=\frac{TP}{TP+FN}$,其刻画真正的正类中,有多少样本预测为正类的概率。定义假正例率(False Positive Rate)为:$FPR=\frac{FP}{TN+FP}$,其刻画了真正的负类中,有多少样本被预测为正类的概率。以真正例率为纵轴、假正例率为横轴作图,就得到ROC曲线。
ROC曲线
具体方法是在不同的分类阈值 (threshold) 设定下分别以TPR和FPR为纵、横轴作图。曲线越靠近左上角,意味着越多的正例优先于负例,模型的整体表现也就越好。
优点:1)兼顾正例和负例的权衡。因为TPR聚焦于正例,FPR聚焦于负例,使其成为一个比较均衡的评估方法。2)ROC曲线选用的两个指标, TPR和FPR,都不依赖于具体的类别分布。
缺点:1)ROC曲线的优点是不会随着类别分布的改变而改变,但这在某种程度上也是其缺点。因为负例N增加了很多,而曲线却没变,这等于产生了大量FP。像信息检索中如果主要关心正例的预测准确性的话,这就不可接受了。
2)在类别不平衡的背景下,负例的数目众多致使FPR的增长不明显,导致ROC曲线呈现一个过分乐观的效果估计。ROC曲线的横轴采用FPR,根据公式 ,当负例N的数量远超正例P时,FP的大幅增长只能换来FPR的微小改变。结果是虽然大量负例被错判成正例,在ROC曲线上却无法直观地看出来。(当然也可以只分析ROC曲线左边一小段)
PR曲线以查准率(精确率P)为纵轴,查全率(召回率R)为横轴作图,就得到查准率-查全率曲线,简称P-R曲线。
PR曲线与ROC曲线的相同点是都采用了TPR (Recall),都可以用AUC来衡量分类器的效果。不同点是ROC曲线使用了FPR,而PR曲线使用了Precision,因此PR曲线的两个指标都聚焦于正例。类别不平衡问题中由于主要关心正例,所以在此情况下PR曲线被广泛认为优于ROC曲线。
PR曲线
使用场景:
ROC曲线由于兼顾正例与负例,所以适用于评估分类器的整体性能,相比而言PR曲线完全聚焦于正例。
如果有多份数据且存在不同的类别分布,比如信用卡欺诈问题中每个月正例和负例的比例可能都不相同,这时候如果只想单纯地比较分类器的性能且剔除类别分布改变的影响,则ROC曲线比较适合,因为类别分布改变可能使得PR曲线发生变化时好时坏,这种时候难以进行模型比较;反之,如果想测试不同类别分布下对分类器的性能的影响,则PR曲线比较适合。
如果想要评估在相同的类别分布下正例的预测情况,则宜选PR曲线。
类别不平衡问题中,ROC曲线通常会给出一个乐观的效果估计,所以大部分时候还是PR曲线更好。
最后可以根据具体的应用,在曲线上找到最优的点,得到相对应的precision,recall,f1 score等指标,去调整模型的阈值,从而得到一个符合具体应用的模型。

AUC曲线

AUC(Area Under the Curve)可解读为:从所有正例中随机选取一个样本A,再从所有负例中随机选取一个样本B,分类器将A判为正例的概率比将B判为正例的概率大的可能性。
我们最直观的有两种计算AUC的方法:1)绘制ROC曲线,ROC曲线下面的面积就是AUC的值。2)假设总共有(m+n)个样本,其中正样本m个,负样本n个,总共有mn个样本对,计数,正样本预测为正样本的概率值大于负样本预测为正样本的概率值记为1,累加计数,然后除以(mn)就是AUC的值。
AUC指标有什么特点?放缩结果对AUC是否有影响?
AUC(Area under Curve)指的是ROC曲线下的面积,介于0和1之间。AUC作为数值可以直观地评价分类器的好坏,值越大越好。它的统计意义是从所有正样本随机抽取一个正样本,从所有负样本随机抽取一个负样本,当前score使得正样本排在负样本前面的概率。放缩结果对AUC没有影响。

训练集、验证集和测试集

1)通常80%为训练集,20%为测试集。
2)当数据量较小时(万级别及以下)的时候将训练集、验证集以及测试集划分为6:2:2;若是数据很大,可以将训练集、验证集、测试集比例调整为98:1:1。
3)当数据量很小时,可以采用K折交叉验证。K折交叉验证将数据集A随机分为k个子集,每个子集均做一次测试集,其余的作为训练集。交叉验证重复k次,每次选择一个子集作为测试集,并将k次的平均交叉验证识别正确率作为结果。
4)划分数据集时可采用随机划分法(当样本比较均衡时),分层采样法(当样本分布极度不均衡时)。分层抽样是指在抽样时,将总体分成互不相交的层,然后按照一定的比例,从各层独立地抽取一定数量的个体,将各层取出的个体合在一起作为样本的方法。

偏差和方差

偏差:描述预测值的期望与真实值之间的差别,偏差越大说明模型的预测结果越差,刻画了学习算法本身的拟合能力。
方差:描述预测值的变化范围,方差越大说明模型的预测越不稳定,刻画了数据扰动造成的影响。
高方差过拟合,高偏差欠拟合。常用交叉验证来权衡模型的方差和偏差。也可以比较均方误差$MSE=E[(\hat{\theta_m}-\theta)^2]=Bias(\hat{\theta_m})^2+Var(\hat{\theta_m})^2$
给定学习任务:
1)在训练不足时模型的拟合能力不够强,训练数据的扰动不足以使模型产生显著变化,此时偏差主导了泛化误差。
2)随着训练程度的加深模型的拟合能力逐渐增强,训练数据发生的扰动逐渐被模型学习到,方差逐渐主导了泛化误差。
3)在训练充分后模型的拟合能力非常强,训练数据发生的轻微扰动都会导致模型发生显著变化。若训练数据自身的、非全局的特性被模型学到了,则将发生过拟合。

深度模型参数调整的一般方法论

1)学习率:遵循小->大->小原则
2)初始化:选择合适的初始化方式,有预训练模型更好
3)优化器选择:adam比较快,sgd较慢
4)loss:回归问题选L2 loss,分类问题选交叉熵
5)从小数据大模型入手,先过拟合,再增加数据并根据需要调整模型复杂度。
6)可视化

pytorch与tensorflow的区别

创建和运行计算图可能是两个框架最不同的地方。在PyTorch中,图结构是动态的,这意味着图在运行时构建。而在TensorFlow中,图结构是静态的,这意味着图先被“编译”然后再运行。
动态图的还有一个好处就是比较容易调试, 在 PyTorch 中, 代码报错的地方, 往往就是代码错写的地方, 而静态图需要先根据代码生成 Graph 对象, 然后在 session.run() 时报错, 但是这种报错几乎很难直接找到代码中对应的出错段落。
pytorch基础知识

卷积神经网络模型

卷积神经网络

卷积层

卷积层是通过特定数目的卷积核(又称滤波器)对输入的多通道特征图进行扫描和运算,从而得到多个拥有更高层语义信息的输出特征图(通道数等于卷积核个数)。
输入图像的维数通常很高,例如,1000x1000大小的彩色图像对应于三百万维特征。因此,继续沿用多层感知机中全连接层会导致庞大的参数量。大参数量需要繁重的计算,而更重要的是大参数量会有更高的过拟合风险。卷积是局部连接、共享参数的全连接层。这两个特征使参数量大大降低。卷积层中的权值通常被称为滤波器(filter)或卷积核(convolution kernel)。
局部连接:所谓局部连接,就是卷积层的节点仅仅和前一层的部分节点相连接,只用来学习局部特征,而全连接层中,每个输出通过权值(weight)和所有输入相连。在计算机视觉中,图像中的某一块区域中,像素之间的相关性与像素之间的距离同样相关,距离较近的像素之间相关性强,距离较远则相关性比较弱,所以局部相关性理论同样适用于计算机视觉的图像处理领域。在卷积层中,每个输出神经元在通道方向保持全连接,在空间方向上只和上一层一部分输入神经元相连。局部感知采用部分神经元接收图像信息,再通过综合全部的图像信息达到增强图像信息的目的。这种局部连接的方式大大减少了参数数量,加快了学习速率,同时也在一定程度上减少过拟合的可能。
共享参数:如果一组权值可以在图像中某个区域提取出有效的表示,那么它们也能在图像中的另外区域提取出有效的表示。也就是说,如果一个模式(pattern)出现在图像中的某个区域,那么它们也可以出现在图像中的其他任何区域。因此,卷积层不同空间位置的神经元共享权值,用于发现图像中不同空间位置的模式。权值共享其实就是整张图片在使用同一个卷积核内的参数提取特征,但是不同的卷积核提取的是不同特征,因此不同卷积核间的神经元权值是不共享的。卷积层在空间方向共享参数,而循环神经网络在时间方向共享参数。
描述卷积的四个量:一个卷积层的配置由如下四个量确定。1)卷积核个数。使用一个卷积核进行卷积可以得到一个二维的特征图(feature map)。使用多个卷积核进行卷积,可以得到不同特征的feature map。2)感受野(receptive field)F,即卷积核的大小。3)零填充(zero-padding)P,随着卷积的进行,图像的大小将缩小,图像边缘的信息将逐渐丢失,因此在卷积前,我们在图像上下左右填补一些0,使得我们可以控制输出特征图的大小。4)步长(stride)S,卷积核在输入图像上每移动S个位置计算一个输出神经元。
假设输入图片大小为$I\times I$,卷积核大小为$K\times K$,步长为S,填充的像素为P,则卷积层输出的特征图大小为:

池化层

池化层根据特征图上的局部统计信息进行下采样,在保留有用信息的同时减少特征图的大小。和卷积层不同,池化层不包含需要学习的参数,最大池化层(max-pooling)在一个局部区域选最大值输出,而平均池化(average pooling)计算一个区域的均值作为输出。
池化层
池化层的作用:1)增加特征平移不变性,池化可以提高网络对微小位移的容忍能力。2)减小特征图大小,池化层对空间局部区域进行下采样,使下一层需要的参数量和计算量减少,并降低过拟合风险。3)最大池化可以带来非线性,这是目前最大池化更常使用的原因。
平均池化和最大池化的区别:最大池化保留了纹理特征,平均池化保留整体的数据特征。最大池化提取边缘等“最重要”的特征,而平均池化提取的特征更加smoothly。

全连接层

全连接层(fully connected layers,FC)在整个卷积神经网络中起到“分类器”的作用。如果说卷积层、池化层和激活函数层等操作是将原始数据映射到隐层特征空间的话,全连接层则起到将学到的“分布式特征表示”映射到样本标记空间的作用。在实际使用中,全连接层可由卷积操作实现:对前层是全连接的全连接层可以转化为卷积核为1×1的卷积;而前层是卷积层的全连接层可以转化为卷积核为hxw的全局卷积,h和w分别为前层卷积结果的高和宽

分组卷积

在普通的卷积操作中,一个卷积核对应输出特征图的一个通道,而每个卷积核又会作用在输入特征图的所有通道上,因此最终输出特征图的每个通道都与输入特征图的所有通道相连。也就是说,在普通的卷积操作,在“通道”这个维度上其实是“全连接”
的。
所谓分组卷积,其实就是讲输入通道和输出通道都划分为同样的组数,然后仅让处于相同组号的输入通道和输出通道相互进行“全连接”。即分组卷积 Group convolution :将多个卷积核拆分为分组,每个分组单独执行一系列运算之后,最终在全连接层再拼接在一起。
通常每个分组会在单独的GPU 中训练,从而利用多GPU 来训练。
分组卷积的重点不在于卷积,而在于分组:在执行卷积之后,将输出的feature map 执行分组。然后在每个组的数据会在各个GPU 上单独训练。
分组卷积
分组卷积在网络的全连接层才进行融合,这使得每个GPU 中只能看到部分通道的数据,这降低了模型的泛化能力。如果每次分组卷积之后,立即融合卷积的结果则可以解决这个问题。
分组卷积降低了模型的参数数量以及计算量。
假设输入feature map具有$C_I$的输入通道、宽/高分别为$W_I,H_I$,假设卷积核的宽/高分别为$W_K,H_K$,有$C_O$个卷积核。则:
参数数量为:$W_K\times H_K\times C_I\times C_O$
计算量为:$W_K\times H_K\times C_I\times W_O\times H_O\times C_O$。
假设采用分组卷积,将输入通道分成G组,则分组后:
参数数量为:$W_K\times H_K\times \frac{C_I}{G}\times \frac{C_O}{G}$
计算量为:$W_K\times H_K\times \frac{C_I}{G}\times W_O\times H_O\times \frac{C_O}{G}$
因此分组卷积的参数数量、计算量均为标准卷积计算的$\frac{1}{G}$。

小卷积核替代

在AlexNet 中用到了一些非常大的卷积核,如11x11、5x5 等尺寸的卷积核。卷积核的尺寸越大,则看到的图片信息越多,因此获得的特征会越好。但是卷积核的尺寸越大,模型的参数数量会爆涨,不利于模型的深度的增加,计算量和存储量也大幅上升。卷积核的尺寸越小,模型的参数数量越少,模型可以越深。但是卷积核的尺寸太小,则只能看到图片的一个非常小的局部区域,获得的特征越差。
解决方案是:用多个小卷积层的堆叠来代替较大的卷积核。
假设大卷积核的宽度是$k$,则每经过一层,输出的宽度减少了$k-1$。假设希望通过$n$个宽度为$k’$的小卷积核来代替,则为了保持输出的大小一致,需要满足:

如:用 2 个 3x3 的卷积核来代替1个 5x5 的卷积核。假设输入通道数为$C_I$,输出通道数为$C_O$,则5x5 卷积核的参数数量为$C_O\times C_I\times 5\times 5$;而 2个 3x3 卷积核的参数数量为$2\times C_O\times C_I\times 3\times 3$,是前者的 72% 。
用多个小卷积层的堆叠代替一个大卷积层的优点:
1)可以实现与大卷积层相同的感受野。
2)具有更大的非线性,网络表达能力更强。虽然卷积是线性的,但是卷积层之后往往跟随一个ReLU 激活函数。这使得多个小卷积层的堆叠注入了更大的非线性。
3)具有更少的参数数量。
小卷积层堆叠的缺点是:加深了网络的深度,容易引发梯度消失等问题,从而使得网络的训练难度加大。
通常选择使用3x3 卷积核的堆叠:
1)1x1 的卷积核:它无法提升感受野,因此多个1x1 卷基层的堆叠无法实现大卷积层的感受野。
2)2x2 的卷积核:如果希望输入的feature map 尺寸和输出的feature map 尺寸不变,则需要对输入执行非对称的padding。此时有四种padding 方式,填充方式的选择又成了一个问题。
3)3x3 的卷积核:可以提升感受野,对称性填充(不需要考虑填充方式),且尺寸足够小。

多尺度卷积核

图像中目标对象的大小可能差别很大。由于信息区域的巨大差异,为卷积操作选择合适的卷积核尺寸就非常困难。信息分布更具有全局性的图像中,更倾向于使用较大的卷积核。信息分布更具有局部性的图像中,更倾向于使用较小的卷积核。
一个解决方案是:分别使用多个不同尺寸的卷积核从而获得不同尺度的特征,然后将这些特征拼接起来。通过多种尺度的卷积核,无论感兴趣的信息区域尺寸多大,总有一种尺度的卷积核与之匹配。这样总可以提取到合适的特征。
多尺寸卷积核存在一个严重的问题:参数数量比单尺寸卷积核要多很多,这就使得计算量和存储量都大幅增长。
多尺度卷积

1x1卷积核

1x1 卷积并不是复制输入,它会进行跨通道的卷积。它有三个作用:
1)实现跨通道的信息整合。
2)进行通道数的升维和降维。
3)在不损失分辨率的前提下(即:feature map 尺寸不变),大幅增加非线性。事实上1x1 卷积本身是通道的线性组合,但是通常会在1x1卷积之后跟随一个ReLU 激活函数。
在前文中,输入feature map 先经过1x1 卷积的压缩,这会导致该段信息容量的下降;然后经过常规卷积,此段信息容量不变;最后经过1x1 卷积的膨胀,恢复了信息容量。整体而言模型的信息容量很像一个bottleneck,因此1x1 卷积层也被称作瓶颈层。

DepthWise卷积

标准的卷积会考虑所有的输入通道,而DepthWise 卷积会针对每一个输入通道进行卷积操作,然后接一个1x1 的跨通道卷积操作。
Depthwise卷积
DepthWise 卷积与分组卷积的区别在于:
1)分组卷积是一种通道分组的方式,它改变的是对输入的feature map 处理的方式。Depthwise 卷积是一种卷积的方式,它改变的是卷积的形式。
2)Depthwise 分组卷积结合了两者:首先沿着通道进行分组,然后每个分组执行DepthWise 卷积。
假设使用标准卷积,输入通道的数量为$C_I$,输出通道的数量为$C_O$,卷积核的尺寸为$W_K\times W_K$。则需要的参数数量为$C_I\times W_K\times H_K\times C_O$。
使用Depthwise卷积时,图像的每个通道先通过一个$W_K\times H_K$的depthwise卷积层,再经过一个1x1、输出为$C_O$的卷积层。
参数量为:

其参数数量是标准卷积的$\frac{1}{C_O}+\frac{1}{W_KH_K}$。因此depthwise 卷积的参数数量远远小于标准卷积。

空洞卷积

在卷积神经网络中,一般会采用池化操作来扩大特征图的感受野,但这同时会降低特征图的分辨率,丢失一些信息(如内部数据结构、空间层级信息等),导致后续上采样操作(如转置卷积)无法还原一些细节,从而限制了最终分割精度的提升。
空洞卷积,顾名思义就是在标准的卷积中注入“空洞”,以增加卷积核的感受野。空洞卷积引入了扩张率(dilation rate)这个超参数来指定相邻采样点之间的间隔:扩展率为r的空洞卷积,卷积核上相邻数据点之间有r-1个空洞。扩张率为1的空洞卷积实际上就是普通卷积。空洞卷积利用空洞结构扩大了卷积核尺寸,不经过下采样操作即可增大感受野,同时还能保留输入数据的内部结构。

可变形卷积

普通的卷积操作是固定的、规则的网格点进行数据采样,这束缚了网络的感受野形状,限制了网络对几何形变的适应能力。为了克服这个限制,可变形卷积在卷积核的每个采样点上添加一个可学习的偏移量(offset),让采样点不再局限于规则的网格点。可变形卷积让网络具有了学习空间几何形变的能力。具体来说,可变形卷积引入了一个平行分支来端到端的学习卷积核采样点的位置偏移量,该平行分支先根据输入特征图计算出采样点的偏移量,然后在输入特征图上采样对应的点进行卷积运算。这种结构让可变形卷积的采样点能根据当前图像的内容进行自适应调整。

图像分类

给定一张图像,图像分类任务旨在判断该图像所属类别。

LeNet-5

LeCun等将BP算法应用到多层神经网络中,提出了LeNet5模型,并将其用于手写数字识别,卷积神经网络才算正式提出。
LeNet5网络模型
LeNet5网络模型参数
网络输入32*32的手写字体图片,这些手写字体包含0~9数字,也就是相当于10个类别的图片;输出:分类结果在0~9之间。LeNet的网络结构十分简单且单一,卷积层C1、C3和C5除了输出维数外采用的是相同的参数,池化层S2和S4采用的也是相同的参数。

AlexNet

2012年Krizhevsky使用卷积神经网络在ILSRC 2012图像分类大赛上夺冠,提出了AlexNet模型。AlexNet网络的提出对于卷积神经网络具有里程碑式的意义,相较于LeNet5的改进有以下几点:
1)数据增强:水平翻转、随机裁剪(平移变换)、颜色光照变换
2)Dropout:Dropout方法和数据增强一样,都是防止过拟合。dropout能按照一定的概率将神经元从网络中丢弃,dropout能在一定程度上防止过拟合,并且加快网络的训练速度。
3)ReLU激活函数:ReLU具有一些优良的特性,在为网络引入非线性的同时,也能引入稀疏性。稀疏性可以选择性激活和分布式激活神经元,能学习到相对稀疏的特征,起到自动化解离的效果。此外,ReLU的导数曲线在输入大于0时,函数的导数为1,这种特性能保证在其输入大于0时梯度不衰减,从而避免或抑制网络训练时梯度消失现象,网络模型的收敛速度会相对稳定。4)Local Response Normalization:局部响应归一化,简称LRN,实际就是利用临近的数据做归一化。5)Overlapping Pooling:即Pooling的步长比Pooling Kernel对应边要小。6)多GPU并行:极大加快网络训练。
AlexNet网络模型
AlexNet网络模型参数

VGGNet

VGGNet是由牛津大学计算机视觉组合Google DeepMind项目的研究员共同研发的卷积神经网络模型,包含VGG16和VGG19两种模型。
VGG16网络模型
从网络模型中可以看出,VGG16相比于AlexNet类的模型具有较深的深度,通过反复堆叠$3\times 3$的卷积层和$2\times 2$的池化层,VGG16构建了较深层次的网络结构。与AlexNet主要有以下不同:
1)Vgg16有16层网络,AlexNet只有8层;
2)在训练和测试时使用了多尺度做数据增强。

GoogleNet(22层)

GoogleNet进一步增加了网络模型的深度,但是单纯的在VGG16的基础上增加网络的宽度会带来以下的缺陷:1)过多的参数容易引起过拟合;2)层数的过深,容易引起梯度消息现象。
GoogleNet的提出受到论文Network in Network(NIN)的启发,NIN有两个贡献:1)提出多层感知卷积层,使用卷积层后加上多层感知机,增强网络提取特征的能力。普通的卷积层和多层感知卷积层结构如图所示,Mlpconv相当于在一般卷积层后加一个1*1的卷积层。2)提出了全局平均池化代替全连接层,全连接层具有大量的参数,使用全局平均池化代替全连接层,能很大程度减少参数空间,便于加深网络,还能防止过拟合。
普通卷积层和多层感知卷积层结构图
GoogleNet根据Mlpconv的思想提出了Inception结构,该结构有两个版本,下图是Inception的naive版,该结构巧妙的将$1\times 1$、$3\times 3$和$5\times 5$三种卷积核和最大池化层结合起来作为一层结构。
Inception结构的native版
而Inception的native版中$5\times 5$的卷积核会带来很大的计算量,因此采用了与NIN类似的结构,在原始的卷积层之后加上$1\times 1$卷积层,最终版本的Inception如下图所示:
降维后的Inception模块

Inception v3/v4

在GoogleNet的基础上进一步降低参数,其和GoogleNet有相似的Inception模块,但将$7\times 7$和$5\times 5$卷积分解为若干等效$3\times 3$卷积,并在网络中后部分把$3\times 3$卷积分解为$1\times 3$和$3\times 1$卷积,这使得在相似的网络参数下网络可以部署到42层。此外,Inception v3使用了批量归一化层。Inception v3是GoogleNet计算量的2.5倍,而错误率较后者下降了3%。Inception v4在Inception模块基础上结合residual模块,进一步降低了0.4%的错误率。
Inceptionv3模块

Inception v1/v2/v3/v4

Inception v1/v2/v3/v4对比

ResNet

卷积神经网络模型的发展历程一次次证明加深网络的深度和宽度能得到更好的效果,但是后来的研究发现,网络层次较深的网络模型的效果反而不如较浅层的网络,称为“退化”现象。退化现象产生的原因在于当模型的结构变得复杂随机梯度下降的优化变得更加困难,导致网络模型的效果反而不如浅层网络。针对这个问题,MSRA何凯明团队提出Residual Network,该网络具有Residual结构如下所示:
Residual结构
ResNet的基本思想是引入了能够跳过一层或多层的“shortcut connection”,即增加一个identity mapping(恒等映射),将原始所需要学的函数H(x)转换为F(x)+x,作者认为这两种表达的效果相同,但是优化的难度却并不相同。这个Residual block通过将shortcut connection实现,通过shortcut将这个block的输入和输出进行一个element-wise的叠加,这个简单地加法并不会给网络增加额外的参数和计算量,同时却可以大大增加模型的训练速度,提高训练效果,并且当模型的层数加深时,这个简单的结构能够很好的解决退化问题。
这种短连接的好处在于:
1)能缩短误差反向传播到各层的路径,有效抑制梯度消失现象,从而使网络在不断加深时性能不会下降。
2)由于有“近道”的存在,若网络在层数加深时性能退化,则它可以通过控制网络中“近道”和“非近道”的组合比例来退回到之前浅层的状态,即“近道”具备自我关闭能力。
对于shortcut的方式,作者提出了三个策略:1)使用恒等映射,如果residual block的输入和输出维度不一致,对增加的维度用0来填充;2)在block输入输出维度一致时使用恒等映射,不一致时使用线性投影以保证维度一致;3)对于所有的block使用线性投影。论文最后用三个$1\times 1,3\times 3, 1\times 1$的卷积层代替前面说的两个$3\times 3$卷积层,第一个$1\times 1$用来降低维度,第三个$1\times 1$用来增加维度,这样可以保证中间的$3\times 3$卷积层拥有比较小的输入输出维度。
更深的residual block

ResNet提出的背景和核心理论是什么?

提出背景:解决或缓解深层的神经网络训练中梯度消失的问题
ResNet的一个重要假设:假设有一个L层的深度神经网络, 如果我们在上面加入一层, 直观来讲得到的L+1层深度神经网络的效果应该至少不会比L层的差。因为可以简单的设最后一层为前一层的拷贝(相当于恒等映射), 并且其它层参数设置不变。
但是最终实验发现: 层数更深的神经网络反而会具有更大的训练误差, 因此, 作者认为深层网络效果差的原因很大程度上也许不在于过拟合, 而在于梯度消失问题。
解决办法:
根据梯度消失问题的根本原因所在(链式法则连乘), ResNet通过调整网络结构来解决上述问题。ResNet的结构设计思想为: 既然离输入近的神经网络层比较难训练, 那么我们可以将它短接到更靠近输出的层。输入x经过两个神经网络的变换得到F(x), 同时也短接到两层之后, 最后这个包含两层的神经网络模块输出H(x)=F(x)+x。这样一来, F(x)被设计为只需要拟合输入x与目标输出H(x)的残差H(x)-x。在梯度更新期间,梯度的相关性会随着层数的变深呈指数性衰减,导致梯度趋近于白噪声,而skip-connections 可以减缓衰减速度,使相关性和之前比起来变大。

DenseNet

DenseNet网络介绍
DenseNet脱离了加深网络层数(ResNet)和旁路网络结构(Inception)来提升网络性能的定式思维,从特征的监督考虑,通过特征重用和旁路(Bypass)设置,既大幅度减少了网络的参数量,又在一定程度上缓解了梯度消失问题的产生。DenseNet作为另一种较深层数的卷积神经网络,具有如下优点:
1)相比于ResNet拥有更少的参数数量;2)旁路加强了特征的重用;3)网络更容易训练,并具有一定的正则效果;4)缓解了gradient vanishing和model degradation的问题。
DenseNet网络结构图
如图所示,第i层的输入不仅与i-1层的输出相关,还与所有之前层的输出相关。记作:
$Xl=H_l([X_0,X_1,…,X{l-1}])$
其中[]代表concatenation(拼接),既将$X0$到$X{l-1}$层的所有输出feature map按Channel组合在一起,这里所用到的非线性变换H为BN+ReLU+Conv(3x3)的组合。
由于在DenseNet中需要对不同层的feature map进行cat操作,所以需要不同层的feature map保持相同的feature size,这就限制了网络中的down sampling的实现。为了使用down sampling在实验中transition layer由BN+Conv(1x1)+average-pooling(2x2)组成。
Denseblock网络图

SENet

Squeeze-and-Excitation Networks(SENet)是由自动驾驶公司Momenta在2017年公布的一种全新的图像识别结构,它通过对特征通道间的相关性进行建模,把重要的特征进行强化来提升准确率。
SE Bolck结构
上图是SENet的Block单元,图中的$F{tr}$是传统的卷积结构,X和U是$F{tr}$的输入(C’xH’xW’)和输出(CxHxW),这些都是以往结构中已存在的。SENet增加的部分是U后的结构:对U先做一个Global Average Pooling(图中的$F{sq(.)}$,作者称为Squeeze过程),输出的1x1xC数据再经过两级全连接(图中的$F{ex(.)}$,作者称为Excitation过程),最后用sigmoid限制到[0,1]的范围,把这个值作为scale乘到U的C个通道上, 作为下一级的输入数据。这种结构的原理是想通过控制scale的大小,把重要的特征增强,不重要的特征减弱,从而让提取的特征指向性更强。
先是Squeeze部分。GAP有很多算法,作者用了最简单的求平均的方法,将空间上所有点的信息都平均成了一个值。这么做是因为最终的scale是对整个通道作用的,这就得基于通道的整体信息来计算scale。另外作者要利用的是通道间的相关性,而不是空间分布中的相关性,用GAP屏蔽掉空间上的分布信息能让scale的计算更加准确。

Excitation部分是用2个全连接来实现 ,第一个全连接把C个通道压缩成了C/r个通道来降低计算量(后面跟了RELU),第二个全连接再恢复回C个通道(后面跟了Sigmoid),r是指压缩的比例。作者尝试了r在各种取值下的性能 ,最后得出结论r=16时整体性能和计算量最平衡。
为什么要加全连接层呢?这是为了利用通道间的相关性来训练出真正的scale。一次mini-batch个样本的squeeze输出并不代表通道真实要调整的scale值,真实的scale要基于全部数据集来训练得出,而不是基于单个batch,所以后面要加个全连接层来进行训练。
可以拿SE Block和下面3种错误的结构比较来进一步理解:
下图最上方的结构,squeeze的输出直接scale到输入上,没有了全连接层,某个通道的调整值完全基于单个通道GAP的结果,事实上只有GAP的分支是完全没有反向计算、没有训练的过程的,就无法基于全部数据集来训练得出通道增强、减弱的规律。
下图中间是经典的卷积结构,有人会说卷积训练出的权值就含有了scale的成分在里面,也利用了通道间的相关性,为啥还要多个SE Block?那是因为这种卷积有空间的成分在里面,为了排除空间上的干扰就得先用GAP压缩成一个点后再作卷积,压缩后因为没有了Height、Width的成分,这种卷积就是全连接了。
下图最下面的结构,SE模块和传统的卷积间采用并联而不是串联的方式,这时SE利用的是$F_{tr}$输入X的相关性来计算scale,X和U的相关性是不同的,把根据X的相关性计算出的scale应用到U上明显不合适。
三种错误的SE结构
下图是两个SENet实际应用的例子,左侧是SE-Inception的结构,即Inception模块和SENet组和在一起;右侧是SE-ResNet,ResNet和SENet的组合,这种结构scale放到了直连相加之前。
SE-Inception和SE-ResNet结构

历年来所有的网络

LeNet-5:第一个卷积,用来识别手写数组,使用的卷积大小为5×5,s=1,就是普通的卷积核池化层结合起来,最后加上全连接层。
AlexNet:在第一个卷积中使用了11×11卷积,第一次使用Relu,使用了NormLayer,但不是我们经常说的BN。使用了dropout,并在两个GPU上进行了训练。
VGGNet:只使用了小卷积3×3(s=1)以及常规的池化层,不过深度比上一个深了一些,最后几层也都是全连接层接一个softmax。为什么使用3×3卷积,是因为三个3×3卷积的有效感受野和7×7的感受野一致,而且更深、更加非线性,卷积层的参数也更加地少,所以速度更快也可以适当加深层数。
GoogleNet:没有使用FC层,参数量相比之前的大大减少,提出了Inception module结构,也就是NIN结构(network within a network)。但是原始的Inception module计算量非常大,所以在每一个分支加了1×1 conv “bottleneck”结构。googlenet网络结构中为了避免梯度消失,在中间的两个位置加了两个softmax损失,所以会有三个loss,整个网络的loss是通过三个loss乘上权重相加后得到。
inception结构的特点:
1)增加了网络的宽度,同时也提高了对于不同尺度的适应程度。
2)使用 1×1 卷积核对输入的特征图进行降维处理,这样就会极大地减少参数量,从而减少计算量。
3)在V3中使用了多个小卷积核代替大卷积核的方法,除了规整的的正方形,我们还有分解版本的 3×3 = 3×1 + 1×3,这个效果在深度较深的情况下比规整的卷积核更好。
4)发明了Bottleneck 的核心思想还是利用多个小卷积核替代一个大卷积核,利用 1×1 卷积核替代大的卷积核的一部分工作。也就是先1×1降低通道然后普通3×3然后再1×1回去。
ResNet:其基本思想是引入了能够跳过一层或多层的“shortcut connection”,即增加一个identity mapping(恒等映射),将原始所需要学的函数H(x)转换为F(x)+x,作者认为这两种表达的效果相同,但是优化的难度却并不相同。这个Residual block通过将shortcut connection实现,通过shortcut将这个block的输入和输出进行一个element-wise的叠加,这个简单地加法并不会给网络增加额外的参数和计算量,同时却可以大大增加模型的训练速度,提高训练效果,并且当模型的层数加深时,这个简单的结构能够很好的解决退化问题。
DenseNet:ResNet是更一般的模型,DenseNet是更特化的模型。DenseNet用于图像处理可能比ResNet表现更好,本质是DenseNet更能和图像的信息分布特点匹配,是使用了多尺度的Kernel。densenet因为需要重复利用比较靠前的feature map,导致显存占用过大,但正是这种通道特征的concat造成densenet能更密集的连接。
SeNet:全称为Squeeze-and-Excitation Networks。核心思想就是去学习每个特征通道的重要程度,然后根据这个重要程度去提升有用的特征并抑制对当前任务用处不大的特征。这个给每一个特征层通道去乘以通过sigmoid得到的重要系数,其实和用bn层去观察哪个系数重要一样。

图像检测

分类任务关系整体,给出的是整张图片的内容描述,而检测则关注特定的物体目标,要求同时获得这一目标的类别信息和位置信息。相比于分类,检测给出的是对图片前景和背景的理解,我们需要从背景中分离出感兴趣的目标,并确定这一目标的描述(类别和位置)。因此,检测模型的输出是一个列表,列表的每一项使用一个数据组给出检测目标的类别和位置(常用矩形检测框的坐标表示)。
目前主流的目标检测算法主要是基于深度模型,大致可以分成两大类别:(1)One-Stage目标检测算法,这类检测算法不需要Region Proposal阶段,可以通过一个Stage直接产生物体的类别概率和位置坐标值,比较典型的算法有YOLO、SSD和CornerNet;(2)Two-Stage目标检测算法,这类检测算法将检测问题划分为两个阶段,第一个阶段首先产生候选区域(Region Proposals),包含目标大概的位置信息,然后第二个阶段对候选区域进行分类和位置精修,这类算法的典型代表有R-CNN、Fast R-CNN、Faster R-CNN等。目标检测模型的主要性能是检测准确度和速度,其中准确度主要考虑物体的定位及分类准确度。一般情况下,Two-Stage算法在准确度上有优势,而One-Stage算法在速度上有优势。不过随着研究的发展,两类算法都在两个方面做改进,均能在准确度及速度上取得较好结果。

IOU的定义

手写IOU
物体检测需要定位出物体的bounding box,对于bounding box的定位精度,存在一个定位精度评价公式:IOU。
IOU定义了两个bounding box的重叠度,如下图所示:
IOU图像
矩形框A、B的一个重合度计算公式为:

就是矩形框A、B的重叠面积$S_I$占A、B并集的面积的比例:

非极大值抑制

在目标检测时一般会采取窗口滑动的方式,在图像上生成很多的候选框,把这些候选框进行特征提取送入到分类器,一般会得到一个得分,然后把这些得分全部排序。选取得分最高的那个框,接下来计算其他框与当前框的重合程度(IOU),如果重合程度大于一定的阈值就删除,这样不停的迭代下去就会得到所有想要找到的目标物体的区域。

Soft-NMS

非极大值抑制算法改进

R-CNN

算法摘要:首先输入一张图片,我们先定位出2000个物体候选框,然后采用CNN提取每个候选框中图片的特征向量,特征向量的维度是4096维,接着采用SVM算法对各个候选框中的物体进行分类识别。
R-CNN算法流程
我们采用selective search算法搜索出候选框,由于搜索到的候选框是矩形的,而且大小各不相同,而CNN对输入的图片大小是固定的,因此需要对每个输入的候选框都要缩放到固定的大小。然而人工标注的图片中就只标注了一个正确的bounding box,我们搜索出来的2000个矩形框也不可能会出现一个与人工标注完全匹配的候选框。因此我们需要用IOU为2000个bounding box打标签,以便下一步CNN训练使用。在CNN阶段,如果用selective search挑选出来的候选框与物体的人工标注矩形框的重叠区域大于0.5,我们就把这个候选框标注成物体类别,否则我们就把它当做背景类别。最后,我们需要对上面预训练的CNN模型进行fine-tuning训练。假设需要检测的物体类别有N类,那么我们就需要将上面与训练的CNN模型的最后一层给替换点,替换成N+1个输出的神经元(还有一个背景),然后这一层直接采用参数随机初始化的方法,其他的网络层数不变。
R-CNN缺点:
1)耗时的selective search,对一帧图像,需要花费2s。
2)耗时的串行式CNN前向传播,对于每一个ROI,都需要经过一个AlexNet提取特征,为所有的ROI提供特征需要花费47s。
3)三个模块分别训练,并且在训练的时候,对于存储空间的消耗很大。
RCNN算法流程框图

SPP-Net

SPPNet网络流程框图
算法特点:1)通过Spatial Pyramid Pooling解决了深度网络固定输入层尺寸的这个限制,使得网络可以享受不限制输入尺寸带来的好处。2)解决了RCNN速度慢的问题,不需要对每个Proposal(2000个左右)进行wrap或crop输入CNN提取feature map,只需要对整图提一次feature map,然后将proposal区域映射到卷积特征层得到全连接层的输入特征。
RCNN与SPPNet对比
一、ROI在特征图上的对应的特征区域的维度不满足全连接层的输入要求怎么办?
事实上,CNN的卷积层不需要固定尺寸的图像,而是全连接层是需要固定大小的输入。根据Pooling规则,每个Pooling bin对应一个输出,所以最终的Pooling后的输出特征由bin的个数来决定。SPP网络就是分级固定bin的个数,调整bin的尺寸来实现多级Pooling固定输出。如图所示,SPP网络中最后一个卷积层的feature map维数为16x24,按照图中所示分为3级:
SPP网络结构
其中,第一级bin个数为1,最终对应的window大小为16x24;第二级bin个数为4个,最终对应的window大小为4x8;第三级bin个数为16个,最终对应的window大小为1x1.5(小数需要舍入处理)。通过融合各级bin的输出,最终每一个feature map经过SPP处理后,得到1+4+16维的feature map,经过融合后输入分类器。这样就可以在任意输入size和scale下获得固定的输出;不同的scale下网络可以提取不同尺度的特征,有利于分类。
二、原始图像的ROI如何映射到特征图?
下面将从感受野、感受野上坐标映射及原始图像的ROI如何映射三方面阐述。
1)感受野
在卷积神经网络中,感受野的定义是卷积神经网络每一层输出的特征图(feature map)的像素点在原始图像上映射的区域大小。

其中output field size是卷积层的输出,input field size是卷积层的输入,反过来卷积层的输入为:

2)感受野上的坐标映射
对于Convolution/Pooling Layer:

对于Neuronlayer(ReLU/Sigmoid/…):

其中$pi$为第$i$层感受野上的坐标,$s_i$为Stride的大小,$k_i$为感受野的大小。
何凯明在SPP-NET中使用的是简化版本,将公式中的Padding都设为$\left \lfloor k_i/2 \right \rfloor$,公式可进一步简化为:$$p_i=s_i \cdot p
{i+1}$$
3)原始图像的ROI如何映射
SPP-NET是把原始ROI的左上角和右下角 映射到Feature Map上的两个对应点。 有了Feature Map上的两队角点就确定了对应的Feature Map 区域(下图中橙色)。
ROI映射过程
左上角取$x’=\left \lfloor x/S \right \rfloor+1, y’=\left \lfloor y/S \right \rfloor+1$;右下角的点取$x’=\left \lceil x/S \right \rceil-1, y’=\left \lceil y/S \right \rceil-1$。其中S为坐标映射的简化计算版本,即$S=\prod_{0}^{i}s_i$。
ROI pooling具体操作:
1)根据输入image,将ROI映射到feature map对应位置。
2)将映射后的区域划分为相同大小的sections(sections数量和输出的维度相同);
3)对每个sections进行max pooling操作。
这样就可以从不同大小的方框得到固定大小的相应的feature maps。
ROI原理

Fast R-CNN

Fast R-CNN针对R-CNN在训练时multi-state pipeline和训练的过程很耗时间和空间的问题进行了改进,主要改进为:
1)在最后一个卷积层加了一个ROI pooling layer。ROI Pooling layer首先可以将image中的ROI定位到feature map,然后用一个单层的SPP layer将这个feature map池化到固定大小的feature map后再传入全连接层。
2)损失函数使用多任务损失函数(multi-task loss),将边框回归直接加入到CNN网络进行训练。
Fast R-CNN流程框图
首先还是采用selective search提取2000个候选框,然后对全图进行特征提取,接着使用一个ROI pooling layer在全体特征上获取每一个ROI对应的特征,再通过全连接层进行分类和检测框修正。即最后得到的ROI feature vector被分开,一个经过全连接层后用作softmax回归,用来分类,另一个经过全连接后用作bbox回归。需要注意的是,输入到后面ROI Pooling layer的feature map是在卷积层上的feature map上提取的,故整个特征提取过程只计算一次卷积。虽然在最开始也提取了大量的ROI,但他们还是作为整体输入到卷积网络的,最开始提取的ROI区域只是为了最后的bounding box回归时使用。
联合训练:联合训练(Joint Training)指如何将分类和边框回归联合到一起在CNN阶段训练,主要难点是损失函数的设计。Fast-RCNN中,有两个输出层:第一个是针对每个ROI区域的分类概率预测,$p=(p_0, p_1, \cdots, p_K)$;第二个则是针对每个ROI区域坐标的偏移优化,$t^k = (t^k_x, t^k_y, t^k_w, t^k_h)$,$0 \le k \le K$是多类检测的类别序号。每个训练ROI都对应着真实类别$u$和边框回归目标$v=(v_x,v_y,v_w,v_h)$,对于类别$u$预测边框为$t^u=(t_x^u,t_y^u,t_w^u,t_h^u)$,使用多任务损失$L$来定义ROI上分类和边框回归的损失:

其中$L_{cls}(p,u)=-\log p_u$表示真实类别的log损失,当$u \ge 1$时,$[u \ge 1]$的值为1,否则为0。下面将重点介绍多任务损失中的边框回归部分(对应坐标偏移优化部分)。
边框回归:假设对于类别$u$,在图片中标注的真实坐标和对应的预测值理论上两者越接近越好,相应的损失函数为:

Fast-RCNN在上面用到的鲁棒$L1$函数对外点比RCNN和SPP-NET中用的$L_2$函数更为鲁棒,该函数在$(-1, 1)$之间为二次函数,其他区域为线性函数。
存在问题:使用Selective Search提取Region Proposals,没有实现真正意义上的端到端,操作耗时。
采用$\text{smooth}
{L_1}$的原因:边框的预测是一个回归问题,通常可以选择平方损失函数(L2损失)$f(x)=x^2$,但这个损失对于比较大的误差的惩罚很高。可以采用稍微缓和一点绝对损失函数(L1损失)$f(x)=|x|$,这个函数在0点处导数不存在,因此可能会影响收敛。因此采用分段函数,在0点附近使用平方函数使得它更加平滑。

Faster R-CNN

Faster R-CNN流程图
算法特点:1)提出了Region Proposal Network(RPN),将Proposal阶段和CNN分类融合到一起,实现了一个完全的End-To-End的CNN目标检测模型。RPN可以快速提取高质量的Proposal,不仅加快了目标检测速度,还提高了目标检测性能。2)将Fast R-CNN和RPN放在同一个网络结构中训练,共享网络参数。

Region Proposal Network

Region Proposal Network(RPN)的核心思想是使用卷积神经网络直接产生Region Proposal,使用的方法本质上就是滑动窗口。RPN的设计比较巧妙,RPN只需要在最后的卷积层上滑动以便,借助Anchor机制和边框回归就可以得到多尺度多长宽比的Region Proposal。下图是RPN的网络结构图。
RPN网络结构图
给定输入图像(假设分辨率为$600*1000$),经过卷积操作得到最后一层卷积特征图(大小约为$40\times 60$)。在这个特征图上使用$3\times 3$的卷积核(滑动窗口)与特征图进行卷积,最后一层卷积层共有256个feature map,那么这个$3\times 3$的区域卷积后可以获得一个256维的特征向量,后边接cls layer和reg layer分别用于分类和边框回归(跟Fast R-CNN类似,只不过这里的类别只有目标和背景两个类别)。$3\times 3$滑窗对应的每个特征区域同时预测输入图像的3种尺度(128,256,512),3中长宽比(1:1,1:2,2:1)的Region Proposal,这种映射的机制称为Anchor。所以对于这个$40\times 60$的feature map,总共有约20000($40\times 60\times 9$)个Anchor,也就是预测20000个Region Proposal。一般来说原始输入图片都要缩放到固定的尺寸才能作为网络的输入,这个尺寸在作者源码里限制成800x600,9种anchor还原到原始图片上基本能覆盖800x600图片上各种尺寸的坐标。
Anchor是在原图上的区域而不是在特征图上
Anchor示例
这样设计的好处是什么?虽然现在也是在用的滑动窗口策略,但是滑动串口操作是在卷积特征图上进行的,维度较原始图像降低了$16\times 16$倍;多尺度使用了9中Anchor,对应了三种尺度和三种长宽比,加上后边接了边框回归,所以几遍是这9种Anchor外的窗口也能得到一个跟目标比较接近的Region Proposal。
anchor讲解

RPN的损失函数

损失函数的定义为:

其中$i$表示一次Mini-Batch中Anchor的索引,$pi$是Anchor $i$是否是一个物体,$L{reg}$即为上面提到的$\text{smooth}{L_1}(x)$函数,$N{cls}$和$N_{reg}$是两个归一化项,分别表示Mini-Batch的大小和Anchor位置的数目。

网络的训练

作者采用了4-step Alternating Training:
1) 用ImageNet模型初始化,独立训练一个RPN网络;
2) 仍然用ImageNet模型初始化,但是使用上一步RPN网络产生的Proposal作为输入,训练一个Fast-RCNN网络,至此,两个网络每一层的参数完全不共享;
3) 使用第二步的Fast-RCNN网络参数初始化一个新的RPN网络,但是把RPN、Fast-RCNN共享的那些卷积层的Learning Rate设置为0,也就是不更新,仅仅更新RPN特有的那些网络层,重新训练,此时,两个网络已经共享了所有公共的卷积层;
4) 仍然固定共享的那些网络层,把Fast-RCNN特有的网络层也加入进来,形成一个Unified Network,继续训练,Fine Tune Fast-RCNN特有的网络层,此时,该网络已经实现我们设想的目标,即网络内部预测Proposal并实现检测的功能。
在训练分类器和RoI边框修正时,步骤如下:
1)首先通过RPN生成约20000个anchor(40x60x9)
2)对20000个anchor进行第一次边框修正,得到修订边框后的proposal;
3)对超过图像边界的proposal的边进行clip,使得该proposal不超过图像范围;
4)忽略掉长或宽太小的proposal;
5)将所有的proposal按照前景分数从高到低排序,选取前12000个proposal;
6)使用阈值为0.7的NMS算法排除掉重叠的proposal;
7)针对上一步剩下的proposal,选取前2000个proposal进行分类和第二次边框修正。
faster rcnn训练过程

Mask R-CNN

ROI Align是在Mask R-CNN这篇论文里提出的一种区域特征聚集方式,很好地解决了ROI Pooling操作中两次量化造成的区域不匹配的问题。在检测阶段将ROI Pooling替换为ROI Align可以提升检测模型的准确性。
在常见的两级检测框架中,ROI Pooling的作用是根据候选框的位置坐标在特征图中将相应的区域池化为固定尺寸的特征图,以便后续的分类和bounding box回归操作。由于预选框的位置通常是由模型回归得到的,一般来讲都是浮点数,而池化后的特征图要求尺寸固定,故ROI Pooling这一操作存在两次量化的过程。1)将候选框的边界量化为整数点坐标值。2)将量化后的边界区域平均分割成$k\times k$个单元(bin),对每一个单元的边界进行量化。经过上述的两次量化,此时的候选框已经和最开始回归出来的位置存在一定的偏差,这个偏差会影响检测或分割的精确度。在论文里作者将它归结为“不匹配问题(misalignment)”。
下面我们用直观的例子具体分析一下上述区域不匹配问题。如图所示,这是一个Faster-RCNN检测框架。输入一张$800\times 800$的图片,图片上有一个$665\times 665$的包围框(框着一只狗)。图片经过主干网络提取特征后,特征图缩放步长(stride)为32。因此,图像和包围框的边长都是输入时的1/32。800正好可以被32整除变为25。但665除以32以后得到20.78,带有小数,于是ROI Pooling 直接将它量化成20。接下来需要把框内的特征池化$7\times 7$的大小,因此将上述包围框平均分割成$7\times 7$个矩形区域。显然,每个矩形区域的边长为2.86,又含有小数。于是ROI Pooling 再次把它量化到2。经过这两次量化,候选区域已经出现了较明显的偏差(如图中绿色部分所示)。更重要的是,该层特征图上0.1个像素的偏差,缩放到原图就是3.2个像素。那么0.8的偏差,在原图上就是接近30个像素点的差别,这一差别不容小觑。
ROIAlign示例
为了解决ROI Pooling的上述缺点,作者提出了ROI Align这一改进的方法。ROI Align的思路很简单:取消量化操作,使用双线性内插的方法获得坐标为浮点数的像素点上的图像数值,从而将整个特征聚集过程转化为一个连续的操作。ROI Align流程如下:
1)遍历每一个候选区域,保持浮点数边界不做量化;2)将候选区域分割成$k\times k$个单元,每个单元的边界也不做量化;3)在每个单元中计算出固定的四个坐标位置,用双线性内插的方法计算出这四个位置的值,然后进行最大池化的操作。
需要注意的是,这个固定位置是指在每一个矩形单元(bin)中按照固定规则确定的位置。比如,如果采样点数是1,那么就是这个单元的中心点。如果采样点数是4,那么就是把这个单元平均分割成四个小方块以后它们分别的中心点。显然这些采样点的坐标通常是浮点数,所以需要使用插值的方法得到它的像素值。事实上,ROI Align 在遍历取样点的数量上没有ROIPooling那么多,但却可以获得更好的性能,这主要归功于解决了misalignment的问题。
ROIAlign示例2
Mask R-CNN结构图
注意的是,在Mask R-CNN中的ROI Align之后有一个“head”部分,主要作用是将ROI Align的输出维度扩大,这样在预测Mask时会更加精确。
在Mask Branch的训练环节,作者没有采用FCN式的SoftmaxLoss,反而是输出了K个Mask预测图(为每一个类都输出一张),并采用average binary cross-entropy loss训练,当然在训练Mask branch的时候,输出K个特征图中,也就是对应ground truth类别的哪一个特征图对Mask loss有贡献。也就是说, Mask RCNN定义为多任务损失为$L=L{class}+L{boxes}+L{mask}$。
$L
{class}$与$L{boxes}$与Faster RCNN没区别。$L{mask}$中,假设一共有K个类别,则mask分割分支的输出维度是$K\times m\times m$,对于$m\times m$中的每个点,都会输出K个二值Mask(每个类别使用sigmoid输出)。需要注意的是,计算loss的时候,并不是每个类别的sigmoid输出都计算二值交叉熵损失,而是该像素属于哪个类,哪个类的sigmoid输出才要计算损失函数,并且在测试的时候,我们通过分类分支预测类别来选择相应的mask预测。这样,mask预测和分类分支预测就彻底解耦了。

FPN

特征金字塔网络FPN
在物体检测里面,在有限的计算量的情况下,网络的深度(对应到感受野)与 stride 通常是一对矛盾的东西,常用的网络结构对应的 stride 一般会比较大(如 32),而图像中的小物体甚至会小于 stride 的大小,造成的结果就是小物体的检测性能急剧下降。传统解决这个问题的思路包括:
(1)多尺度训练和测试,有称图像金字塔,如图(a)所示。目前几乎所有在 ImageNet 和 COCO 检测任务上取得好成绩的方法都使用了图像金字塔方法。然而这样的方法由于很高的时间及计算量消耗,难以在实际中应用。
(2)特征分层,即每层分别预测对应的scale分辨率的检测结果,如图(c)所示。SSD检测框架采用了相似的思想,这样的方法的问题在于直接强行让不同层学习同样的语义信息,而对于卷积神经网络而言,不同的深度对应着不同层次的语义特征,浅层网络的分辨率高,学的更多是细节特征,深层网络分辨率低,学的更多是语义特征。
FPN网络结构
目前多尺度的物体检测主要面临的挑战是:
1)如何学习具有强语义信息的多尺度特征表示?
2)如何设计通用的特征表示来解决物体检测中的多个子问题?如object proposal,box localization,instance segmentation.
3)如何高效计算多尺度的特征表示?
本文针对这些问题,提出了特征金字塔FPN,如图(d)所示,网络直接在原来的按网络上做修改,每个分辨率的feature map引入后,将分辨率缩放两倍的feature map做element-wise相加的操作。通过这样的连接,每一层预测所用的feature map都融合了不同分辨率、不同语义强度的特征,融合的不同分辨率的feature map分别做对应分辨率的物体检测。这样保证了每一层都有合适的分辨率以及强语义特征。同事,由于此方法只是在原网络的基础上家里额外的跨层连接,在实际应用中几乎不增加额外的时间和计算量。
自下而上的路径
CNN的前馈计算就是自下而上的路径,特征图经过卷积核计算,通常是越变越小,也有一些特征层的输出和原来的大小一样,称为“相同网络阶段”。对于本文的特征金字塔,作者为每个阶段定义一个金字塔级别,然后选择每个阶段的最后一层的输出作为特征图的参考集,这是因为每个阶段的最深层应该具有最强的特征。具体来说,对于ResNets,作者使用了每个阶段的最后一个残差结构的特征激活输出。将这些残差模块输出表示为{C2, C3, C4, C5},对应于conv2,conv3,conv4和conv5的输出,并且注意它们相对于输入图像具有{4, 8, 16, 32}像素的步长。考虑到内存占用,没有将conv1包含在金字塔中。
自上而下的路径和横向连接
自上而下的路径(the top-down pathway )是如何去结合低层高分辨率的特征呢?方法就是,把更抽象,语义更强的高层特征图进行上采样,然后把该特征横向连接(lateral connections)至前一层的特征,因此高层特征得到加强。值得注意的是,横向连接的两层特征在空间尺寸上要相同,这样做主要是为了利用底层的定位细节信息。
如图(d)所示,把高层特征做2倍上采样(最近邻上采样),然后将其和对应的前一层特征结合(前一层要经过$1\times 1$的卷积核才能用,目的是改变channels,使之和后一层的channels相同),结合方式就是element-wise相加的操作。重复迭代该过程,直至生成$1\times 1$最精细的特征图。迭代开始阶段,作者在C5层后面加了一个$1\times 1$的卷积核来产生最粗略的特征图,最后,作者用$3\times 3$的卷积核去处理已经融合的特征图(为了消除上采样的混叠效应),以生成最后需要的特征图。{C2, C3, C4, C5}层对应的融合特征层为{P2, P3, P4, P5},对应的层空间尺寸是相通的。
FPN的优点:
1)低层的特征经过卷积和高层的信息上采样之后进行融合,在卷积神经网络中高层的特征具有较强的语义信息,低层的特征具有结构信息,将高层和低层的信息进行结合,是可以增强特征的表达能力。2)将候选框产生和提取特征的位置分散到特征金字塔的每一层,这样可以增加小目标特征映射的分辨率,对最后的预测是有好处的。

Retinanet

基于深度学习的目标检测算法有两类经典的结构:Two Stage 和 One Stage。
Two Stage:例如Faster-RCNN算法。第一级专注于proposal的提取,第二级对提取出的proposal进行分类和精确坐标回归。两级结构准确度较高,但因为第二级需要单独对每个proposal进行分类/回归,速度上就变慢。
One Stage:例如SSD,YOLO算法。此类算法摒弃了提取proposal的过程,只用一级就完成了识别/回归,虽然速度较快但准确率远远比不上两级结构。
产生精度差异的主要原因:类别失衡(Class Imbalance)。One Stage方法在得到特征图后,会产生密集的目标候选区域,而这些大量的候选区域中只有很少一部分是真正的目标,这样就造成了机器学习中经典的训练样本正负不平衡的问题。它往往会造成最终算出的training loss为占绝大多数但包含信息量却很少的负样本所支配,少样正样本提供的关键信息却不能在一般所用的training loss中发挥正常作用,从而无法得出一个能对模型训练提供正确指导的loss(而Two Stage方法得到proposal后,其候选区域要远远小于One Stage产生的候选区域,因此不会产生严重的类别失衡问题)。常用的解决此问题的方法就是负样本挖掘,或其它更复杂的用于过滤负样本从而使正负样本数维持一定比率的样本取样方法。该论文中提出了Focal Loss来对最终的Loss进行校正。
Focal Loss的目的:消除类别不平衡 + 挖掘难分样本
Focal Loss非常简单,就是在原有的交叉熵损失函数上增加了一个因子,让损失函数更加关注hard examples,以下是用于二值分类的交叉熵损失函数。其中$y\in{\pm1}$为类别真实标签,$p\in[0,1]$是模型预测的$y=1$的概率。

可以进行如下定义:

因此交叉熵可以写成如下形式,即如下loss曲线图中蓝色曲线所示,可以认为当模型预测得到的$p_t\ge 0.6$的样本为容易分类的样本,而$p_t$值预测较小的样本为hard examples,最后整个网络的loss就是所有训练样本经过模型预测得到的值的累加,因为hard examples通常为少数样本,所以虽然其对应的loss值较高,但是最后全部累加后,大部分的loss值来自于容易分类的样本,这样在模型优化的过程中就会将更多的优化放到容易分类的样本中,而忽略hard examples。

对于这种类别不均衡问题常用的方法是引入一个权重因子$\alpha$,对于类别1的使用权重$\alpha$,对于类别-1使用权重$1-\alpha$,公式如下所示。但采用这种加权方式可以平衡正负样本的重要性,但无法区分容易分类的样本与难分类的样本。

因此论文中提出在交叉熵前增加一个调节因子$(1-p_t)^{\gamma}$,其中$\gamma$为focusing parameter,且$\gamma \ge 0$,其公式变为如下,当$\gamma$取不同数值时loss曲线如图1所示。通过图中可以看到,当$\gamma$越来越大时,loss函数在容易分类的部分其loss几乎为零,而$p_t$较小的部分(hard examples部分)loss值仍然较大,这样就可以保证在类别不平衡较大时,累加样本loss,可以让hard examples贡献更多的loss,从而可以在训练时给与hard examples部分更多的优化。

在实际使用时,论文中提出在上述公式的基础上,增加一个$\alpha$平衡因子,可以产生一个轻微的精度提升,公式如下所示。

focal loss曲线图
下图是RetinaNet的网络结构,整个网络相对Faster-RCNN简单了很多,主要由ResNet+FPN+2xFCN子网络构成。
RetinaNet网络结构图
首先RetinaNet的Backbone是由ResNet+FPN构成,输入图像经过Backbone的特征提取后,可以得到$P_3~P_7$特征图金字塔,其中下标$l$表示特征金字塔的层数($P_l$特征图的分辨率比输入图像小$2^l$),得到的特征金字塔的每层$C=256$通道。
在得到特征金字塔后,对每层特征金字塔分别使用两个子网络(分类网络+检测框位置回归)。这两个子网络由RPN网络修改得到。
与RPN网络类似,也使用anchors来产生proposals。特征金字塔的每层对应一个anchor面积,为了产生更加密集的coverage,增加了三个面积比例$\begin{Bmatrix}2^0,2^{\frac{1}{2}},2^{\frac{2}{3}} \end{Bmatrix}$(即使用当前anchor对应的面积分别乘以相应的比例,形成三个尺度),然后anchors的长宽比仍为$\begin{Bmatrix}1:2,1:1,2:1 \end{Bmatrix}$,因此特征金字塔的每一层对应A = 9种Anchors。原始RPN网络的分类网络只是区分前景与背景两类,此处将其改为目标类别的个数K。
特征金字塔每层都相应的产生目标类别与位置的预测,最后再将其融合起来,同时使用NMS来得到最后的检测结果。

Faster R-CNN+FPN+Focal loss

Faster R-CNN+FPN结合细节
代码
网络结构
优化方案

YOLO

算法特点:1)将物体检测作为回归问题求解。基于一个单独的End-To-End网络,完成从原始图像的输入到物体位置和类别的输出,输入图像经过一次Inference,便能得到图像中所有物体的位置和其所属类别及相应的置信概率。2)YOLO网络借鉴GoogleNet网络结构,不同的是,YOLO未使用Inception Module,而是使用$1\times 1$卷积层(此处$1\times 1$卷积层的存在是为了跨通道信息整合)+ $3\times 3$卷积层简单代替。3)Fast YOLO使用9个卷积层代替YOLO的24个,网络更轻快,速度从YOLO的45fps提升到155fps,但是同时损失了检测准确率。4)使用全图作为Context信息,背景错误(把背景错认为物体)比较少。5)泛化能力强。在自然图像上训练好的结果在艺术作品中依然具有很好的效果。
YOLO网络结构
一、大致流程
1)对于一个输入图像,首先将图像划分成$7\times 7$的网络。
2)对于每个网格,我们都预测2个边框(包括每个边框是目标的置信度以及每个边框区域在多个类别上的概率)。
3)根据上一步可以预测出$7\times 7\times 2$个目标窗口,然后根据阈值去除可能性比较低的目标窗口,最后NMS去除冗余窗口即可。
Yolo实例
二、训练
1)预训练分类网络:在ImageNet 1000-class Competition Dataset预训练一个分类网络,这个网络时前文网络结构中前20个卷积网络+Average-Pooling Layer+Fully Connected Layer(此时网络的输入是$224\times 224$)。
2)训练检测网络:在预训练网络中增加卷积和全连接层可以改善性能。YOLO添加4个卷积层和2个全连接层,随机初始化权重。检测要求细粒度的视觉信息,所以把网络输入也从$224\times 224$变成$448\times 448$。一幅图像分成$7\times 7$个网格,某个物体的中心落在这个网格中此网络就负责预测这个物体。每个网络预测两个Bounding Box。网格负责类别信息,Bounding Box负责坐标信息(4个坐标信息及一个置信度),所以最后一层输出为$7\times 7\times (2\times (4+1) + 20) = 7\times 7\times 30$的维度。Bounding Box的坐标使用图像的大小进行归一化0-1,Confidence使用$Pr(Object) * IOU{pred}^{truth}$计算,其中第一项表示是否有物体落在网格中,第二项表示预测的框和实际的框之间的IOU值。
3)损失函数的确定:损失函数的定义如下,损失函数的设计目标就是让坐标,置信度和类别这三个方面达到很好的平和。简单地全部采用Sum-Squared Error Loss来做这件事会有以下不足:a)8维的Localization Error和20维的Classification Error同等重要显然是不合理的;b)如果一个网格中没有Object(一幅图中这种玩个很多),那么就会将这些网络中的Box的COnfidence降低到0,相对于较少的有Object的网络,这种做法是Overpowering的,这会导致网络不稳定甚至发散。解决方案如下:
Yolo的损失函数
首先更重视8维的坐标预测,给这些损失前面赋予更大的Loss Weight,记为$\lambda{coord}$,在Pascal VOC训练中取5(上图蓝色框)。对于没有Object的Bbox的Confidence Loss,赋予小的Loss Weight,记为$\lambda{noobj}$,在Pascal VOC训练中取0.5(上图橙色框)。有Object的Bbox的Confidence Loss(上图红色框)和类别的Loss(上图紫色框)的Loss Weight正常取1。对于不同大小的Bbox预测中,相比于大Bbox预测偏一点,小Bbox预测偏一点更不能忍受。而Sum-Square Error Loss中对同样的偏移Loss是一样的。为了缓和这个问题,将Bbox的Width和Height取平方根代替原本的Height和Width。如下图所示:Small Bbox的横轴值较小,发生偏移时,反应到y轴上的Loss(下图绿色)比Big Bbox(下图红色)要大。一个网格预测多个Bbox,在训练时我们希望每个Object(Ground True box)只有一个Bbox专门负责(一个Object一个Bbox)。具体做法是与Ground True Box(Object)的IOU最大的Bbox负责该Ground True Box(Object)的预测。这种做法称作Bbox Predictor的Specialization(专职化)。每个预测器会对特定(Size,Aspect Ratio or Classed of Object)的Ground True Box预测的越来越好。
三、测试
1)计算每个Bbox的Class-Specific Confidence Score:每个网格预测的Class信息($Pr(Classi|Object)$)和Bbox预测的Confidence信息($Pr(Object)\times IOU{pred}^{truth}$)相乘,就得到每个Bbox的Class-Specific Confidence Score。

2)进行Non-Maximum Suppression(NMS):得到每个Bbox的Class-Specific Confidence Score以后,设置阈值,滤掉得分低的Bboxes,对保留的Bboxes进行NMS处理就得到最终的检测结果。
四、存在问题
1)YOLO对相互靠的很近的物体(挨在一起且中点都落在同一个格子上的情况),还有很小的群体检测效果不好,这是因为一个网络中只预测了两个框,并且只属于一类。
2)测试图像中,当同一类物体出现的不常见的长宽比和其他情况时泛化能力偏弱。
3)由于损失函数的问题,定位误差是影响检测效果的主要原因,尤其是大小物体的处理上,还有待加强。

YOLOV2、YOLOV3

YOLOV2/YOLO9000 更准、更快、更强

YOLO v1对于bounding box的定位不是很好,在精度上比同类网络还有一定的差距。作者希望改进的方向是改善 recall,提升定位的准确度,同时保持分类的准确度。YOLO V2在V1基础上做出改进:
1)受到Faster RCNN方法的启发,引入了anchor。使用了K-Means方法,对anchor数量进行了讨论。
2)修改了网络结构,去掉了全连接层,改成了全卷积结构。
3)训练时引入了世界树(WordTree)结构,将检测和分类问题做成了一个统一的框架,并且提出了一种层次性联合训练方法,将ImageNet分类数据集和COCO检测数据集同时对模型训练。
更准
Batch Normalization
使用 Batch Normalization 对网络进行优化,让网络提高了收敛性,同时还消除了对其他形式的正则化(regularization)的依赖。通过对 YOLO 的每一个卷积层增加 Batch Normalization,最终使得 mAP 提高了2%,同时还使模型正则化。使用 Batch Normalization 可以从模型中去掉 Dropout,而不会产生过拟合。
High resolution classifier
目前业界标准的检测方法,都要先把分类器(classifier)放在ImageNet上进行预训练。从 Alexnet 开始,大多数的分类器都运行在小于 $256\times 256$ 的图片上。而现在 YOLO 从 $224\times 224$ 增加到了 $448\times 448$,这就意味着网络需要适应新的输入分辨率。
为了适应新的分辨率,YOLO v2 的分类网络以 $448\times 448$ 的分辨率先在 ImageNet上进行微调,微调 10 个 epochs,让网络有时间调整滤波器(filters),好让其能更好的运行在新分辨率上,还需要调优用于检测的 Resulting Network。最终通过使用高分辨率,mAP 提升了4%。
Convolution with anchor boxes
YOLOV1包含有全连接层,从而能直接预测 Bounding Boxes 的坐标值。 Faster R-CNN 的方法只用卷积层与 Region Proposal Network 来预测 Anchor Box 偏移值与置信度,而不是直接预测坐标值。作者发现通过预测偏移量而不是坐标值能够简化问题,让神经网络学习起来更容易。
收缩网络让其运行在 $416\times 416$ 而不是 $448\times 448$。由于图片中的物体都倾向于出现在图片的中心位置,特别是那种比较大的物体,所以有一个单独位于物体中心的位置用于预测这些物体。YOLO 的卷积层采用 32 这个值来下采样图片,所以通过选择 $416\times 416$ 用作输入尺寸最终能输出一个 $13\times 13$ 的特征图。 使用 Anchor Box 会让精确度稍微下降,但用了它能让 YOLO 能预测出大于一千个框,同时 recall 达到88%,mAP 达到 69.2%。
Dimension clusters
Anchor boxes的宽高维度往往是精选的先验框(hand-picked priors)也就是说人工选定的先验框。虽然在训练过程中网络也会学习调整框的宽高维度,最终得到准确的bounding boxes。但是,如果一开始就选择了更好的、更有代表性的先验框维度,那么网络就更容易学到准确的预测位置。为了优化,在训练集的 Bounding Boxes 上跑一下 k-means聚类,来找到一个比较好的值。
Fine-Grained Features
YOLOv1在对于大目标检测有很好的效果,但是对小目标检测上,效果欠佳。为了改善这一问题,作者参考了Faster R-CNN和SSD的想法,在不同层次的特征图上获取不同分辨率的特征。作者将上层的(前面26×26)高分辨率的特征图(feature map)直接连到13×13的feature map上。把26×26×512转换为13×13×2048,并拼接在一起使整体性能提升1%。
Multi-Scale Training
和GoogleNet训练时一样,为了提高模型的鲁棒性(robust),在训练的时候使用多尺度的输入进行训练。YOLOv2 每迭代几次都会改变网络参数。每 10 个 Batch,网络会随机地选择一个新的图片尺寸,由于使用了下采样参数是 32,所以不同的尺寸大小也选择为 32 的倍数 {320,352,…,608},最小 320x320,最大 608x608,网络会自动改变尺寸,并继续训练的过程。
更快
大多数目标检测的框架是建立在VGG-16上的,VGG-16在ImageNet上能达到90%的top-5,但是单张图片需要30.69 billion 浮点运算,YOLO2是依赖于DarkNet-19的结构,该模型在ImageNet上能达到91%的top-5,并且单张图片只需要5.58 billion 浮点运算,大大的加快了运算速度。
YOLOv2去掉YOLOv1的全连接层,同时去掉YOLO v1的最后一个池化层,增加特征的分辨率,修改网络的输入,保证特征图有一个中心点,这样可提高效率。并且是以每个anchor box来预测物体种类的。
作者将分类和检测分开训练,在训练分类时,以Darknet-19为模型在ImageNet上用随机梯度下降法(Stochastic gradient descent)跑了160epochs,跑完了160 epochs后,把输入尺寸从224×224上调为448×448,这时候学习率调到0.001,再跑了10 epochs, DarkNet达到了top-1准确率76.5%,top-5准确率93.3%。
在训练检测时,作者把分类网络改成检测网络,去掉原先网络的最后一个卷积层,取而代之的是使用3个3×3x1024的卷积层,并且每个新增的卷积层后面接1×1的卷积层,数量是我们要检测的类的数量。
更强
论文提出了一种联合训练的机制:使用识别数据集训练模型识别相关部分,使用分类数据集训练模型分类相关部分。
众多周知,检测数据集的标注要比分类数据集打标签繁琐的多,所以ImageNet分类数据集比VOC等检测数据集高出几个数量级。所以在YOLOv1中,边界框的预测其实并不依赖于物体的标签,YOLOv2实现了在分类和检测数据集上的联合训练。对于检测数据集,可以用来学习预测物体的边界框、置信度以及为物体分类,而对于分类数据集可以仅用来学习分类,但是其可以大大扩充模型所能检测的物体种类。
作者选择在COCO和ImageNet数据集上进行联合训练,遇到的第一问题是两者的类别并不是完全互斥的,比如”Norfolk terrier”明显属于”dog”,所以作者提出了一种层级分类方法(Hierarchical classification),根据各个类别之间的从属关系(根据WordNet)建立一种树结构WordTree,结合COCO和ImageNet建立的词树(WordTree)如下图所示:
词树结构
WordTree中的根节点为”physical object”,每个节点的子节点都属于同一子类,可以对它们进行softmax处理。在给出某个类别的预测概率时,需要找到其所在的位置,遍历这个路径,然后计算路径上各个节点的概率之积。
在训练时,如果是检测样本,按照YOLOv2的loss计算误差,而对于分类样本,只计算分类误差。在预测时,YOLOv2给出的置信度就是 ,同时会给出边界框位置以及一个树状概率图。在这个概率图中找到概率最高的路径,当达到某一个阈值时停止,就用当前节点表示预测的类别。

YOLOv3

改进之处:
1)多尺度预测
2)更好的基础网络(类ResNet)和分类器darknet-53
多尺度预测
原来的YOLO v2有一个层叫:passthrough layer,假设最后提取的feature map的size是$13\times 13$,那么这个层的作用就是将前面一层的$26\times 26$的feature map和本层的$13\times 13$的feature map进行连接,有点像ResNet。这样的操作也是为了加强YOLO算法对小目标检测的精确度。这个思想在YOLO v3中得到了进一步加强,在YOLO v3中采用类似FPN的上采样(upsample)和融合做法(最后融合了3个scale,其他两个scale的大小分别是$26\times 26$和$52\times 52$),在多个scale的feature map上做检测,对于小目标的检测效果提升还是比较明显的。虽然在YOLO v3中每个网格预测3个边界框,看起来比YOLO v2中每个grid cell预测5个边界框要少,但因为YOLO v3采用了多个尺度的特征融合,所以边界框的数量要比之前多很多。
darknet-53
YOLOv3框图

SSD

SSD模型
算法特点
1)SSD结合了YOLO中的回归思想和Faster R-CNN中的Anchor机制,使用全图各个位置的尺度区域特征进行回归,既保持了YOLO速度快的特性,也保证了窗口预测的跟Faster-RCNN一样比较精准。
2)SSD的核心是在特征图上采用卷积核来预测一系列Default Bounding Boxes的类别、坐标偏移。为了提高检测准确率,SSD在不同尺度的特征图上进行预测。
SSD网络结构
一、模型结构
1、多尺度特征图(Multi-scale Feature Map For Detection)
在图像Base Network基础上,将Fc6,Fc7变为Conv6,Conv7两个卷积层,添加了一些卷积层(Conv8,Conv9,Conv10,Conv11),这些层的大小逐渐减小,可以进行多尺度预测。
2、卷积预测器(Convolutional Predictors For Detection)
每个新添加的卷积层和之前的部分卷积层,使用一系列的卷积核进行预测。对于一个大小为$m\times n$,$p$通道的卷积层,使用$3\times 3$的$p$通道卷积核作为基础预测元素进行预测,在某个位置上预测出一个值,该值可以是某一类别的得分,也可是相对于Default Bounding Boxes的偏移量,并且在图像中每个位置都将产生一个值。
3、默认框和比例(Default Boxes And Aspect Ratio)
在特征图的每个位置预测K个Box,对于每个Box,预测C个类别得分,以及相对于Default Bounding Box的4个偏移值,这样需要$(C+4)\times k$个预测器,在$m\times n$的特征图上将产生$(C+4)\times k\times m\times n$个预测值。这里,Default Bounding Box类似于Faster R-CNN中Anchor是,下图所示。
SSD默认框
二、模型训练
1、监督学习的训练关键是人工标注的label,对于包含Default Box(在Faster R-CNN中叫做Anchor)的网络模型(如:YOLO,Faster R-CNN,MultiBox)关键点就是如何把标注信息(Ground True Box,Ground True Category)映射到(Default Box)上。
2、给定输入图像以及每个物体的Ground Truth,首先找到每个Ground True Box对应的Default Box中IOU最大的作为正样本。然后,在剩下的Default Box中找到那些与任意一个Ground truth Box的IOU大于0.5的Default Box作为正样本。其他的作为负样本(每个Default Box要么是正样本Box要么是负样本Box)。如上图中,两个Default Box与猫匹配,一个与狗匹配。在训练过程中,采用Hard Negative Mining 的策略(根据Confidence Loss对所有的Box进行排序,使正负例的比例保持在1:3) 来平衡正负样本的比率。
3、损失函数
与Faster-RCNN中的RPN是一样的,不过RPN是预测Box里面有Object或者没有,没有分类,SSD直接用的Softmax分类。Location的损失,还是一样,都是用Predict box和Default Box/Anchor的差 与Ground Truth Box和Default Box/Anchor的差进行对比,求损失。
SSD loss求解
其中,$x{i,j}^p=1$表示 第i个Default Box与类别p的第j个Ground Truth Box相匹配,否则若不匹配的话,则$x{i,j}^p=0$
4、Default Box的生成
对每一张特征图,按照不同的大小(Scale) 和长宽比(Ratio)生成生成k个默认框(Default Boxes)。
(1)Scale:每一个Feature Map中Default Box的尺寸大小计算如下:

其中,$S{min}$取值0.2,$s{max}$取值0.95,意味着最低层的尺度是0.2,最高层的尺度是0.95。
(2)Ratio:使用不同的Ratio值$a_=1,2,3,\frac{1}{2},\frac{1}{3}$计算Default Box的宽度和高度:$w_k^a=s_k\sqrt{a_r},h_k^a=s_k/\sqrt{a_r}$。另外对于Ratio=1的情况,还增加了一个Default Box,这个Box的Scale为$s’_k=\sqrt{s_ks_k+1}$。也就是总共有6种不同的Default Box。
(3)Default Box中心:每个Default Box的中心位置设置成$(\frac{i+0.5}{|f_k|},\frac{j+0.5}{|f_k|})$,其中$|f_k|$表示第k个特征图的大小$i,j\in[0,|f_k|]$。
5)Data Augmentation:为了模型更加鲁棒,需要使用不同尺寸的输入和形状,作者对数据进行了多种方式的随机采样。

mobile net

图像分割

FCN

UNet

目标追踪

多目标跟踪介绍

多目标跟踪,即MOT(Multi-Object Tracking),顾名思义,就是在一段视频中同时跟踪多个目标。MOT主要应用场景是安防监控和自动驾驶等,这些场景中我们往往需要对众多目标同时进行追踪。
而由于是多目标,自然就会产生新目标进入与旧目标消失的问题,这就是与单目标跟踪算法区别最大的一点。而由于这一点区别,也就导致跟踪策略的不同。在单目标跟踪中,我们往往会使用给定的初始框,在后续视频帧中对初始框内的物体进行位置预测。而多目标跟踪算法,大部分都是不考虑初始框的,原因就是上面的目标消失与产生问题。取而代之,在多目标跟踪领域常用的跟踪策略是TBD(Tracking-by-Detecton),又或者也可叫DBT(Detection-Based-Tracking)。即在每一帧进行目标检测,再利用目标检测的结果来进行目标跟踪,这一步我们一般称之为数据关联(Data Assoiation)。
这里自然引出了多目标跟踪算法的一种分类:TBD(Tracking-by-Detecton)与DFT(Detection-Free Tracking),也即基于检测的多目标跟踪与基于初始框无需检测器的多目标跟踪。基于初始化帧的跟踪,在视频第一帧中选择你的目标,之后交给跟踪算法去实现目标的跟踪。这种方式基本上只能跟踪你第一帧选中的目标,如果后续帧中出现了新的物体目标,算法是跟踪不到的。这种方式的优点是速度相对较快。缺点很明显,不能跟踪新出现的目标。基于目标检测的跟踪,在视频每帧中先检测出来所有感兴趣的目标物体,然后将其与前一帧中检测出来的目标进行关联来实现跟踪的效果。这种方式的优点是可以在整个视频中跟踪随时出现的新目标。TBD则是目前学界业界研究的主流。
TBD与DFT对比
不得不提的是另一种多目标跟踪算法的分类方式:在线跟踪(Online)与离线跟踪(Offline)。上文提到,大家往往会使用数据关联来进行多目标跟踪。而数据关联的效果,与你能使用的数据是有着直接的关系的。在Online跟踪中,我们只能使用当前帧及之前帧的信息来进行当前帧的跟踪。而在Offline跟踪中则没有了这个限制,我们对每一帧的预测,都可以使用整个视频的信息,这样更容易获得一个全局最优解。两种方式各有优劣,一般视应用场合而定,Offline算法的效果一般会优于Online算法。而介于这两者之间,还有一种称之为Near-Online的跟踪方式,即可以部分利用未来帧的信息。下图形象解释了Online与Offline跟踪的区别。
Online与Offline区别

评价指标

Trajectory(轨迹):一条轨迹对应这一目标在一段时间内的位置序列。
Tracklet(轨迹段):形成Trajectory过程中的轨迹片段。完整的Trajectory是由属于同一物理目标的Tracklets构成。
ID switch(ID 切换):又称ID sw.。对于同一个目标,由于跟踪算法误判,导致其ID发生切换的次数。跟踪算法中理想的ID switch应该为0.
对于多目标跟踪,最主要的评价指标就是MOTA。这个指标综合了三点因素:FP、FN、IDsw.。FP即False Postive,为误检测的目标数量;FN即False Negetive,为未检出的真实目标数量;IDsw.即同一目标发生ID切换的次数。

其中$ct$表示帧t中的匹配数,并且$d{t,i}$是假设i与分配的真实对象之间的边界框重叠。此度量标准很少考虑有关跟踪的信息,而侧重于检测的质量。

SORT

现在多目标跟踪算法的效果,与目标检测的结果息息相关。在实际工程中,为了提高多目标跟踪的效果,可以从检测器入手,跟踪效果也会水涨船高。
SORT采用的是在线跟踪的方式,不使用未来帧的信息。在保持100fps以上的帧率的同时,也获得了较高的MOTA(在当时16年的结果中)。
多目标跟踪中SORT算法思想理解流程:
在跟踪之前,对所有目标已经完成检测,实现了特征建模过程。
1)对第一帧使用目标检测模型进行目标检测,得到第一帧中所有目标的分类和位置(假设有M个目标),并标注一个独有id。对每个目标初始化卡尔曼滤波跟踪器,预测每个目标在下一帧的位置;
2)对第二帧使用目标检测模型进行目标检测,得到第二帧中所有目标的分类和位置(假设有N个目标),求第一帧M个目标和第二帧N个目标两两目标之间的IOU,建立代价矩阵,使用匈牙利匹配算法得到IOU最大的唯一匹配(数据关联部分),再去掉匹配值小于iou_threshold的匹配对;
3)用第二帧中匹配到的目标的位置去更新卡尔曼跟踪器,计算第二帧时的卡尔曼增益Kk,状态估计值xk,估计误差协方差Pk。并输出状态估计值xk用来计算下一帧中的预测位置。对于本帧中没有匹配到的目标重新初始化卡尔曼滤波跟踪器;
后面每一帧图像都按第一帧和第二帧的做法进行类似处理即可。其中,卡尔曼跟踪器联合了历史跟踪记录,调节历史box与本帧box的残差,更好的匹配跟踪id。
匈牙利算法是一种寻找二分图的最大匹配的算法,在多目标跟踪问题中可以简单理解为寻找前后两帧的若干目标的匹配最优解的一种算法。而卡尔曼滤波可以看作是一种运动模型,用来对目标的轨迹进行预测,并且使用确信度较高的跟踪结果进行预测结果的修正。
SORT在以往二阶段匹配算法的基础上进行了创新。以往二阶段匹配算法是先使用匈牙利算法对相邻帧之间的目标进行匹配生成很多tracklets,之后使用这些tracklets进行二次匹配,以解决遮挡等问题引起的轨迹中断。但这种二阶段匹配方式弊端也很明显,因为这种方式先天地要求必须以Offline的方法进行跟踪,而无法做到Online。SORT将这种二阶段匹配算法改进为了一阶段方法,并且可以在线跟踪。具体而言,SORT引入了线性速度模型与卡尔曼滤波来进行位置预测,在无合适匹配检测框的情况下,使用运动模型来预测物体的位置。在数据关联的阶段,SORT使用的依旧是匈牙利算法逐帧关联,不过作者还引入了IOU(Intersection-Over-Union)距离。不过SORT用的是带权重的匈牙利算法,其实就是KM算法,用IOU距离作为权重(也叫cost矩阵)。作者代码里是直接用sklearn的linear_assignment实现。并且当IOU小于一定数值时,不认为是同一个目标,理论基础是视频中两帧之间物体移动不会过多。作者在代码中选取的阈值是0.3。
预测模型(卡尔曼滤波器)
作者近似地认为目标的不同帧间地运动是和其他物体及相机运动无关的线性运动。每一个目标的状态可以表示为:

其中u和v分别代表目标的中心坐标,而s和r分别代表目标边界框的比例(面积)和长宽比,长宽比被认为是常数,需要保持不变。
当进行目标关联时,使用卡尔曼滤波器,用上一帧中目标的位置信息预测下一帧中这个目标的位置。若上一帧中没有检测到下一帧中的某个目标,则对于这个目标,重新初始化一个新的卡尔曼滤波器。关联完成后,使用新关联的下一帧中该目标的位置来更新卡尔曼滤波器。
数据关联(匈牙利匹配)
SORT算法中的代价矩阵为上一帧的M个目标与下一帧的N个目标两两目标之间的IOU。当然,小于指定IOU阈值的指派结果是无效的(源码中阈值设置为0.3)。
此外,作者发现使用IOU能够解决目标的短时被遮挡问题。这是因为目标被遮挡时,检测到了遮挡物,没有检测到原有目标,假设把遮挡物和原有目标进行了关联。那么在遮挡结束后,因为在相近大小的目标IOU往往较大,因此很快就可以恢复正确的关联。这是建立在遮挡物面积大于目标的基础上的。

deep SORT

之前的SORT算法使用简单的卡尔曼滤波处理逐帧数据的关联性以及使用匈牙利算法进行关联度量,这种简单的算法在高帧速率下获得了良好的性能。但由于SORT忽略了被检测物体的表面特征,因此只有在物体状态估计不确定性较低是才会准确,在Deep SORT中,我们使用更加可靠的度量来代替关联度量,并使用CNN网络在大规模行人数据集进行训练,并提取特征,已增加网络对遗失和障碍的鲁棒性。
状态估计
使用一个8维空间去刻画轨迹在某时刻的状态:

使用一个kalman滤波器预测更新轨迹,该卡尔曼滤波器采用匀速模型和线性观测模型。通过卡尔曼估计对u,v,r,h进行估计,u,v是物体中心点的位置,r是长宽比,h是高。运动估计对于运动状态变化不是很剧烈和频繁的物体能取得比较好的效果。其观测变量为:

轨迹处理
对于每一个追踪目标,都有一个阈值ak用于记录轨迹从上一次成功匹配到当前时刻的时间(即连续没有匹配的帧数),我们称之为轨迹。当该值大于提前设定的阈值Amax则认为该轨迹终止,直观上说就是长时间匹配不上的轨迹则认为该轨迹已经结束。
在匹配时,对于没有匹配成功的目标都认为可能产生新的轨迹。但由于这些检测结果可能是一些错误警告,所以对这种情形新生成的轨迹标注状态’tentative’,然后观查在接下来的连续若干帧(论文中是3帧)中是否连续匹配成功,是的话则认为是新轨迹产生,标注为’confirmed’,否则则认为是假性轨迹,状态标注为’deleted’。
分配问题
在SORT中,我们直接使用匈牙利算法去解决预测的Kalman状态和新来的状态之间的关联度,现在我们需要将目标运动和表面特征信息相结合,通过融合这两个相似的测量指标。
Motion Metric
使用马氏距离来评测预测的Kalman状态和新来的状态:

表示第j个detection和第i条轨迹之间的运动匹配度,其中$S_i$是轨迹由kalman滤波器预测得到的在当前时刻观测空间的协方差矩阵, $y_i$是轨迹在当前时刻的预测观测量, $d_i$时第j个detection的状态$(u,v,r,h)$
考虑到运动的连续性,可以通过该马氏距离对detections进行筛选,文中使用卡方分布的0.95分位点作为阈值$ t^(1)=0.4877$,我们可以定义一个门限函数。

Appearance Metric
当目标运动不确定性较低时,马氏距离是一个很好的关联度量,但在实际中,如相机运动时会造成马氏距离大量不能匹配,也就会使这个度量失效,因此,我们整合第二个度量标准,对每一个BBox检测框$dj$我们计算一个表面特征描述子 $r_j,|r_j|=1$ , 我们会创建一个gallery用来存放最新的$L_k=100$个轨迹的描述子,即$R_k=\begin{Bmatrix}r_k^{(i)}\end{Bmatrix}{k=1}^{L_k}$,然后我们使用第i个轨迹和第j个轨迹的最小余弦距离作为第二个衡量尺度!

当然,我们也可以用一个门限函数来表示

接着,我们把这两个尺度相融合为:

总之,距离度量对于短期的预测和匹配效果很好,而表观信息对于长时间丢失的轨迹而言,匹配度度量的比较有效。超参数的选择要看具体的数据集,比如文中说对于相机运动幅度较大的数据集,直接不考虑运动匹配程度。
级联匹配
如果一条轨迹被遮挡了一段较长的时间,那么在kalman滤波器的不断预测中就会导致概率弥散。那么假设现在有两条轨迹竞争同一个目标,那么那条遮挡时间长的往往得到马氏距离更小,使目标倾向于匹配给丢失时间更长的轨迹,但是直观上,该目标应该匹配给时间上最近的轨迹。
导致这种现象的原因正是由于kalman滤波器连续预测没法更新导致的概率弥散。假设本来协方差矩阵是一个正态分布,那么连续的预测不更新就会导致这个正态分布的方差越来越大,那么离均值欧氏距离远的点可能和之前分布中离得较近的点获得同样的马氏距离值。
所以本文中才引入了级联匹配的策略将遮挡时间按等级分层,遮挡时间越小的匹配等级更高,即更容易被匹配。
首先是得到追踪框集合T和检测框集合D,设置最大的Amax为轨迹最大允许丢失匹配的帧数。通过计算上面的评价指标(两种度量的加权和)得到成本矩阵,再通过级联条件,设定阈值分别对外观和位置因素进行计算,满足条件则返回1,否则返回0。然后初始化匹配矩阵为空,初始化未匹配矩阵等于D。通过匈牙利算法,对于每个属于追踪框集合的元素T,在检测框里面查找成本最低且满足阈值过滤条件的检测框作为匹配结果,同时更新匹配矩阵和非匹配矩阵。
在匹配的最后阶段还对unconfirmed和age=1的未匹配轨迹进行基于IOU的匹配。这可以缓解因为表观突变或者部分遮挡导致的较大变化。当然有好处就有坏处,这样做也有可能导致一些新产生的轨迹被连接到了一些旧的轨迹上。但这种情况较少。
深度表观描述子
预训练的网络时一个在大规模ReID数据集上训练得到的,这个ReID数据集包含1261个人的1100000幅图像,使得学到的特征很适合行人跟踪。
然后使用该预训练网络作为基础网络,构建wide ResNet,用来提取bounding box的表观特征。

匈牙利算法

(待补充)

卡尔曼滤波器

(待补充)

姿态估计

姿态估计综述

OpenPose

注意机制

self-attention

普通卷积将特征图的每个位置作为中心点,对该位置及其周围的位置进行加权求和,得到新的特征图上该位置对应的滤波结果,这一操作可以有效提取图片的局部信息。随着网络加深,卷积层不断堆叠,每个位置的感受域也越来越大,网络提取到的特征也逐渐由一些low-level的特征,如颜色、纹理,转变到一些high-level的结构信息。但是,简单通过加深网络来获取全局感受域,所带来的计算开销是很大的,并且更深的网络会带来更大的优化难度。
Self-attention操作可以有效地捕获不同位置之间的long-range dependency,每个位置的特征都由所有位置的加权求和得到,这里的权重就是attention weight,使得每个位置都可以获取全局的感受域。与传统的注意力机制不同,self-attention计算的是同一张图片中不同位置之间的注意力分配,从而提取该图片的特征。Self-attention机制在视觉任务解决了卷积神经网络的局部感受野问题,使得每个位置都可以获得全局的感受野。不过,由于在视觉任务中,像素数目极多,利用所有位置来计算每个位置的attention会导致巨大的计算和显存开销;另一方面,由于self-attention简单将图像当成一个序列进行处理,没有考虑不同位置之间的相对位置关系,使得所得到的attention丧失了图像的结构信息。
在NLP任务中,对于Attention机制的整个计算过程,可以总结为以下三个过程:
首先根据 Query 与 Key 计算两者之间的相似性或相关性, 即 socre 的计算。通过一个 softmax 来对值进行归一化处理获得注意力权重值, 即$a{i,j}$的计算。最后通过注意力权重值对value进行加权求和, 即$c{i,j}$ 的计算。

self attention本质上是为序列中每个元素都分配一个权重系数,这也可以理解为软寻址。如果序列中每一个元素都以(K,V)形式存储,那么attention则通过计算Q和K的相似度来完成寻址。Q和K计算出来的相似度反映了取出来的V值的重要程度,即权重,然后加权求和就得到了attention值。
引入Self Attention后会更容易捕获句子中长距离的相互依赖的特征,因为如果是RNN或者LSTM,需要依次序序列计算,对于远距离的相互依赖的特征,要经过若干时间步步骤的信息累积才能将两者联系起来,而距离越远,有效捕获的可能性越小。
但是Self Attention在计算过程中会直接将句子中任意两个单词的联系通过一个计算步骤直接联系起来,所以远距离依赖特征之间的距离被极大缩短,有利于有效地利用这些特征。除此外,Self Attention对于增加计算的并行性也有直接帮助作用。这是为何Self Attention逐渐被广泛使用的主要原因。

scale attention

视觉任务中另一类注意力机制为scale attention。与self-attention不同,scale attention基于每个位置本身的响应。就分类任务而言,每个位置的响应越大,则其对于最终的分类结果影响越大,那么这个位置本身的重要性就越强。根据响应大小有选择地对特征图进行强化或抑制,就可以在空间(或其他维度)上达到分配attention的目的。例如SENet,就相当于channel-wise的attention。这一类注意力机制仅仅基于图像中每个位置本身,对显著区域进行增强,非显著区域进行抑制,比self-attention机制更接近与人类视觉系统的注意力机制。

Bottom-up and top-down形式的scale attention

在分类网络中,网络深层比浅层更关注于被分类的物体,也就是图片的主体内容,这是因为,深层网络具有更大的视野域,可以看到更广的范围;而浅层网络只能看到每个位置及其邻域。因此,如果将网络较深层的信息作为一种mask,作用在较浅层的特征上,就能更好的增强浅层特征中对于最终分类结果有帮助的特征,抑制不相关的特征。如图5所示,将attention作为mask作用在原来特征上,得到的输出就会更加集中在对分类有帮助的区域上。
attention作用机制
文章提出一种bottom-up top-down的前向传播方法来得到图片的attention map,并且将其作用在原来的特征上,使得输出的特征有更强的区分度。图6展示了这种attention的计算方式。由于更大的视野域可以看到更多的内容,从而获得更多的attention信息,因此,作者设计了一条支路,通过快速下采样和上采样来提前获得更大的视野域,将输出的特征进行归一化后作用在原有的特征上,将作用后的特征以残差的形式加到原来的特征上,就完成了一次对原有特征的注意力增强。文章还提出了一个堆叠的网络结构,即residual attention network,中间多次采用这种attention模块进行快速下采样和上采样。
Bottom-up注意力机制

Squeeze and excite形式的注意力

与residual attention不同,squeeze-and-excite通过global pooling来获得全局的视野域,并将其作为一种指导的信息,也就是attention信息,作用到原来的特征上。
SENet提出了channel-wise的scale attention。特征图的每个通道对应一种滤波器的滤波结果,即图片的某种特定模式的特征。对于最终的分类结果,这些模式的重要性是不同的,有些模式更重要,因此其全局的响应更大;有些模式不相关,其全局的响应较小。通过对不同通道的特征根据其全局响应值,进行响应的增强或抑制,就可以起到在channel上进行注意力分配的作用。其网络结构如图7所示,首先对输入特征进行global pooling,即为squeeze阶段,对得到的特征进行线性变换,即为excite阶段,最后将变换后的向量通过广播,乘到原来的特征图上,就完成了对不同通道的增强或抑制。
SE Bolck结构
作为SENet的一个延续,convolutional block attention module (CBAM)将SENet中提出的channel attention扩展到了spatial attention上,通过一个串行的支路,将channel attention和spatial attention连接起来,对原特征进行增强。其网络结构如图9所示,首先进行channel attention,对通道进行增强和抑制,这一过程与SENet的操作完全相同,然后在每个位置上进行通道的squeeze和excite操作,得到与原特征图一样分辨率的1通道spatial attention,再作用到原特征图上,即为spatial attention操作。最终的输出即为spatial attention module的输出。相比SENet,CBAM带来的性能提升有限,在该模块中其主要作用的还是channel attention模块。
CBAM网络结构

多分类

序列神经网络

LSTM

RNN相对传统的ANN网络结构实现了信息的保留,具有一定的记忆功能。可以将过去的信息应用到当前的任务中。
下图展示了RNN的闭环结构和开环结构
闭环和开环结构
完成当前任务如果仅仅需要短期的信息而不需要长期的信息可以使用RNN。但是如果任务需要更多的上下文信息,仅仅依靠少量的过去信息无法完成准确的预测。也就是过去信息和当前任务存在较大的跳动,甚至需要未来的信息才能完成预测。这时经典的RNN就无法满足需要,而需要特殊的时间序列模型LSTM。LSTMs 就是用来解决长期依赖问题,这类模型可以记住长期信息。
RNN和LSTM结构
典的RNN模型中的激活函数可能就是一个简单的tanh函数,但是LSTMs引入了四个门结构,具有增加或者移除信息状态的功能。门限可以有选择的让信息通过,它是由sigmoid神经网络层和pointwise乘法操作构成的。sigmoid层输入数值0-1 代表可以通过的比例,输入为0时代表不允许通过,输出为1时代表允许全部通过。
1)forget gate 遗忘门
遗忘门结构
遗忘门输入是$h{t-1}$,输出是$f_t$,$f_t$作用于$C{t-1}$。当$ft$为1时,代表保留该值,当$f_t$为0时,代表完全舍去该值。
2)input gate 输入门
输入门结构
存储什么样的新信息包括两步,第一步输入门决定哪些值可以更新,第二步tanh层创造候选向量。$i_t$是sigmoid函数输出结果表示是否产生输入,其取值范围是0-1。$\tilde{C_t}$是新产生的候选向量。忘记门$f_t$乘$C
{t-1}$:忘掉决定忘掉的早期信息,其结果加上$i_t * \tilde{C_t}$,候选向量通过$i_t$缩放后表示多大程度上更新状态值。
遗忘门和输入门组合
通过忘记门和输入门的组合可以表达出这样的信息:多大程度上忘记旧的信息以及多大程度上更新新的信息。
3)Output gate 输出门
输出门
首先sigmoid函数决定输出的缩放比例$o_t$,然后cell 状态通过tanh函数,其结果与$o_t$相乘。

RNN、LSTM、GRU区别

RNN引入了循环的概念,但是在实际过程中却出现了初始信息随时间消失的问题,即长期依赖(Long-Term Dependencies)问题,所以引入了LSTM。
LSTM:因为LSTM有进有出且当前的cell informaton是通过input gate控制之后叠加的,RNN是叠乘,因此LSTM可以防止梯度消失或者爆炸。
GRU是LSTM的变体,将忘记门和输入们合成了一个单一的更新门。
GRU结构

LSTM防止梯度消失和爆炸

LSTM用加和的方式取代了乘积,使得很难出现梯度弥散。但是相应的更大的几率会出现梯度爆炸,但是可以通过给梯度加门限解决这一问题。

TCN

时序问题的建模大家一般习惯性的采用循环神经网络(RNN)来建模,这是因为RNN天生的循环自回归的结构是对时间序列的很好的表示。传统的卷积神经网络一般认为不太适合时序问题的建模,这主要由于其卷积核大小的限制,不能很好的抓取长时的依赖信息。一种特殊的卷积神经网络——时序卷积网络(Temporal convolutional network, TCN)与多种RNN结构相对比,发现在多种任务上TCN都能达到甚至超过RNN模型。

因果卷积(Causal Convolution)

因果卷积
因果卷积可以用上图直观表示。 即对于上一层t时刻的值,只依赖于下一层t时刻及其之前的值。和传统的卷积神经网络的不同之处在于,因果卷积不能看到未来的数据,它是单向的结构,不是双向的。也就是说只有有了前面的因才有后面的果,是一种严格的时间约束模型,因此被成为因果卷积。

膨胀卷积(Dilated Convolution)

单纯的因果卷积还是存在传统卷积神经网络的问题,即对时间的建模长度受限于卷积核大小的,如果要想抓去更长的依赖关系,就需要线性的堆叠很多的层。为了解决这个问题,研究人员提出了膨胀卷积。如下图所示。
膨胀卷积
和传统卷积不同的是,膨胀卷积允许卷积时的输入存在间隔采样,采样率受图中的d控制。 最下面一层的d=1,表示输入时每个点都采样,中间层d=2,表示输入时每2个点采样一个作为输入。一般来讲,越高的层级使用的d的大小越大。所以,膨胀卷积使得有效窗口的大小随着层数呈指数型增长。这样卷积网络用比较少的层,就可以获得很大的感受野。

空洞卷积

残差连接(Residual Connections)

残差连接
残差链接被证明是训练深层网络的有效方法,它使得网络可以以跨层的方式传递信息。本文构建了一个残差块来代替一层的卷积。如上图所示,一个残差块包含两层的卷积和非线性映射,在每层中还加入了WeightNorm和Dropout来正则化网络。

TCN优点和缺点

优点
(1)并行性。当给定一个句子时,TCN可以将句子并行的处理,而不需要像RNN那样顺序的处理。
(2)灵活的感受野。TCN的感受野的大小受层数、卷积核大小、扩张系数等决定。可以根据不同的任务不同的特性灵活定制。
(3)稳定的梯度。RNN经常存在梯度消失和梯度爆炸的问题,这主要是由不同时间段上共用参数导致的,和传统卷积神经网络一样,TCN不太存在梯度消失和爆炸问题。
(4)内存更低。RNN在使用时需要将每步的信息都保存下来,这会占据大量的内存,TCN在一层里面卷积核是共享的,内存使用更低。
缺点
(1)TCN 在迁移学习方面可能没有那么强的适应能力。这是因为在不同的领域,模型预测所需要的历史信息量可能是不同的。因此,在将一个模型从一个对记忆信息需求量少的问题迁移到一个需要更长记忆的问题上时,TCN 可能会表现得很差,因为其感受野不够大。
(2)论文中描述的TCN还是一种单向的结构,在语音识别和语音合成等任务上,纯单向的结构还是相当有用的。但是在文本中大多使用双向的结构,当然将TCN也很容易扩展成双向的结构,不使用因果卷积,使用传统的卷积结构即可。
(3)TCN毕竟是卷积神经网络的变种,虽然使用扩展卷积可以扩大感受野,但是仍然受到限制,相比于Transformer那种可以任意长度的相关信息都可以抓取到的特性还是差了点。TCN在文本中的应用还有待检验。

图网络

图卷积网络
卷积是通过计算中心像素点以及相邻像素点的加权和来构成feature map实现空间特征提取。图卷积主要分为空域图卷积和谱域图卷积。
空域图卷积,其思想是将每个节点与其相邻节点的特征进行加权,使用领域来确定每个节点的加权平均范围(卷积范围),使用label策略来为参与卷积的节点分配权重,其层间传播矩阵为:

其中A是邻接矩阵,包含邻居节点信息,A中节点相连为1否则为0。通过矩阵乘法可以使得每个节点和其邻居节点特征进行聚合。W是权重矩阵,主要是对边进行加权。这样的传播方法是没有对邻接矩阵进行归一化,这使得A在传播过程中不断扩张(每次乘A会导致特征值越来越大)。此外,由于A对角线元素为0,每个节点自身在进行卷积时不能将自己的特征计算在内。因此,在空域图卷积中引入了谱域图卷积的思想来解决这两个问题。谱域图卷积主要借助图的拉普拉斯矩阵和傅里叶变换来进行的,最后得到的层间传播公式为:

其中矩阵$\land$用于对扩展邻接矩阵(A+I)进行归一化。
首先和单纯的空域图卷积相比,对邻接矩阵做了自环操作(A+I),通过加单位矩阵的方法,使得中心节点自身的特征能参与卷积。然后通过对邻接矩阵实现归一化,解决了A随层数不断增长的问题。其他的部分就和公式(1)完全一致了。因此我们可以说公式(2)是利用谱域图卷积的思想来弥补了空域图卷积的缺点,本质上是两种卷积思想的结合。

图像处理方法

Canny边缘检测

Canny边缘检测是通过灰度值的变化(梯度)来判断边缘信息的。主要包含以下四个步骤:
1)高斯滤波
滤波的主要目的是降噪,一般的图像处理算法都需要先进行降噪。而高斯滤波主要使图像变得平滑(模糊),同时也有可能增大了边缘的宽度。
对于一个位置(m,n)的像素点,其灰度值(这里只考虑二值图)为f(m,n)。那么经过高斯滤波后的灰度值将变为:

简单说就是用一个高斯矩阵乘以每一个像素点及其邻域,取其带权重的平均值作为最后的灰度值。
2)计算梯度值和梯度方向
边缘就是灰度值变化较大的的像素点的集合。在图像中,用梯度来表示灰度值的变化程度和方向。
它可以通过点乘一个sobel或其它算子得到不同方向的梯度值 $g_x(m,n)$ , $g_y(m,n)$ 。
综合梯度通过以下公式计算梯度值和梯度方向:

3)过滤非最大值
在高斯滤波过程中,边缘有可能被放大了。这个步骤使用一个规则来过滤不是边缘的点,使边缘的宽度尽可能为1个像素点:如果一个像素点属于边缘,那么这个像素点在梯度方向上的梯度值是最大的。否则不是边缘,将灰度值设为0。

4)使用上下阈值来检测边缘
一般情况下,使用一个阀值来检测边缘,但是这样做未免太武断了。如果能够使用启发式的方法确定一个上阀值和下阀值,位于下阀值之上的都可以作为边缘,这样就可能提高准确度。

它设置两个阀值(threshold),分别为maxVal和minVal。其中大于maxVal的都被检测为边缘,而低于minval的都被检测为非边缘。对于中间的像素点,如果与确定为边缘的像素点邻接,则判定为边缘;否则为非边缘。

机器学习

传统的机器学习算法

常见的机器学习算法包括:

  1. 回归算法:回归算法是试图采用对误差的衡量来探索变量之间的关系的一类算法。回归算法是统计机器学习的利器,常见的回归算法包括:最小二乘法(Ordinary Least Square),逻辑回归(Logistic Regression),逐步式回归(Stepwise Regression),多元自适应回归样条(Multivariate Adaptive Regression Splines)以及本地散点平滑估计(Locally Estimated Scatterplot Smoothing)。
  2. 基于实例的算法:基于实例的算法常常用来对决策问题建立模型,这样的模型常常先选择一批样本数据,然后根据某些近似性把新数据与样本数据进行比较通过这种方式来寻找最佳的匹配。因此,基于实例的算法也被称为“基于记忆的学习”。常见的算法包括k-Nearest Neighbor(KNN),学习矢量量化(Learning Vector Quantization, LVQ)以及自组织映射算法(Self-Organizing Map, SOM)。
  3. 决策树学习:决策树算法根据数据的属性采用树状结构建立决策模型,决策树模型常常用来解决分类和回归问题。常见的算法包括:分类及回归树(Classification And Regression Tree, CART),随机森林(Random Forest),多元自适应回归样条(MARS)以及梯度推进及(Gradient Boosting Machine, GBM)。
  4. 贝叶斯方法:贝叶斯算法是基于贝叶斯定理的一类算法,主要用来解决分类和回归问题。常见算法包括:朴素贝叶斯算法,平均单依赖估计(Averaged One-Dependence Estimators, AODE)以及Bayesian Belief Network(BBN)。
  5. 基于核的算法:基于核的算法中最著名的莫过于支持向量机(SVM)。基于核的算法吧输入数据映射到高阶的向量空间,在这些高阶向量空间里,有些分类或者回归问题能够更容易的解决。常见的基于核的算法包括:支持向量机(Support Vector Machine, SVM),径向基函数(Radial Basis Function, RBF)以及线性判别分析(Linear Discriminate Analysis, LDA)等。
  6. 聚类算法:聚类,就像回归一样,有时候人们描述的是一类问题,有时候描述的是一类算法。聚类算法通常按照中心店或者分层的方式对输入数据进行归并。所有的聚类算法都试图找到数据的内在结构,以便按照最大的共同点进行归类。常见的聚类算法包括k-means算法以及期望最大化算法(Expectation Maximization, EM)。
  7. 降低维度算法:像聚类算法一样,降低维度算法试图分析数据的内在结构,不过降低维度算法是以非监督学习的方式试图利用较少的信息来归纳或者解释数据。这类数据可以用高维数据的可视化或者用来简化数据以便监督式学习使用。常见的算法包括:主成份分析(Principle Component Analysis, PCA),偏最小二乘回归(Partial Least Square Regression, PLS),Sammon映射,多维尺度(Multi-Dimensional Scaling, MDS),投影追踪(Projection Pursuit)等。
  8. 关联规则学习:关联规则学习通过寻找最能够解释数据变量之间关系的规则,来找出大量多元数据集中有用的关联规则。常见算法包括Apriori算法和Eclat算法等。
  9. 集成算法:集成算法用一些相对较弱的学习模型队里地就同样的样本进行训练,然后把结果整合起来进行整体预测。集成算法的主要难点在于究竟集成哪些独立的较弱的学习模型以及如何把学习结果整合起来。常见的算法包括:Boosting,Bootstrapped Aggregation(Bagging),AdaBoost,堆叠泛化(Stacked Generalization, Blending),梯度推进及(Gradient Boosting Machine, GBM),随机森林(Random Forest)。
  10. 人工神经网络:人工神经网络算法模拟生物神经网络,是一类模式匹配算法, 通常用于解决分类和回归问题。重要的人工神经网络算法包括:感知器神经网络(Perceptron Neural Network),反向传递(Back Propagation),自组织映射(Self-Organizing Map,SOM)以及学习矢量量化(Learning Vector Quantization, LVQ)。

    生成模型和判别模型

    判别模型:由数据直接学习决策函数Y=f(X)或者条件概率分布P(Y|X)作为预测的模型,即判别模型。基本思想是有限样本条件下建立判别函数,不考虑样本的产生模型,直接研究预测模型。典型的判别模型包括k近邻,感知级,决策树,支持向量机等。
    生成模型:由数据学习联合概率密度分布P(X,Y),然后求出条件概率分布P(Y|X)作为预测的模型,即生成模型:P(Y|X)= P(X,Y)/ P(X)。基本思想是首先建立样本的联合概率概率密度模型P(X,Y),然后再得到后验概率P(Y|X),再利用它进行分类。常见的有HMM模型

    特征工程

    本质上来说,呈现给算法的数据应该能拥有基本数据的相关结构或属性。当你做特征工程时,其实是将数据属性转换为数据特征的过程,属性代表了数据的所有维度,在数据建模时,如果对原始数据的所有属性进行学习,并不能很好的找到数据的潜在趋势,而通过特征工程对你的数据进行预处理的话,你的算法模型能够减少受到噪声的干扰,这样能够更好的找出趋势。
    特征工程分哪几步?1)数据预处理;2)特征选择;3)特征提取。

    特征选择和特征提取区别

    都是降维的方法。特征选择:不改变变量的含义,仅仅只是做出筛选,留下对目标影响较大的变量;特征提取:通过映射(变换)的方法,将高维的特征向量变换为低维特征向量。

    样本不均衡如何处理?

    简单通用的方式:
    1)对较多的那个类别进行欠采样(under-sampling),舍弃一部分数据,使其与较少类别的数据相当。
    2)对较少的类别进行过采样(over-sampling),重复使用一部分数据,使其与较多类别的数据相当。
    3)阈值调整(threshold moving),将原本默认为0.5的阈值调整到 较少类别/(较少类别+较多类别)即可。
    类别不平衡的处理方式如下:
    1)采样
    这里的采样可以分为上采样和下采样,简单说就是从类别少的多采样或者类别多的少采样。
    2)转化为One-class问题
    把它看做一分类(One Class Learning)或异常检测(Novelty Detection)问题。这类方法的重点不在于捕捉类间的差别,而是为其中一类进行建模,经典的工作包括One-class SVM等。
    3)聚类+采样
    对数据先进行聚类,再将大的簇进行随机欠采样或者小的簇进行数据生成。
    4)模型惩罚
    简单说就是对分类器的小类样本数据增加权值,降低大类样本的权值。
    5)换模型
    使用一些如Bagging和Boosting的集成方法

    对于数据异常值,我们一般如何处理?

    1)视为无效信息(噪声点): 结合异常值检测算法,检测出后直接丢弃;
    2)视为有效信息(信号点):作为缺失值,用缺失值的方式处理;
    3)用平均值(中位数)等统计特征进行修正,结合前后观测值;
    4)不处理,直接在具有异常值的数据上进行数据挖掘;

    简单介绍特征选择

    当数据预处理完成后,我们需要选择有意义的特征输入机器学习的算法和模型进行训练。通常来说,从两个方面考虑来选择特征:
    1)特征是否发散:如果一个特征不发散,例如方差接近于0,也就是说样本在这个特征上基本上没有差异,这个特征对于样本的区分并没有什么用。
    2)特征与目标的相关性:这点比较显见,与目标相关性高的特征,应当优选选择。除方差法外,下文介绍的其他方法均从相关性考虑。
    根据特征选择的形式又可以将特征选择方法分为3种:
    1)Filter:过滤法,按照发散性或者相关性对各个特征进行评分,设定阈值或者待选择阈值的个数,选择特征。如方差选择法,先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征。
    2)Wrapper:包装法,根据目标函数(通常是预测效果评分),每次选择若干特征,或者排除若干特征。递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。
    3)Embedded:嵌入法,先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据系数从大到小选择特征。类似于Filter方法,但是是通过训练来确定特征的优劣。使用带惩罚项L1的基模型,除了筛选出特征外,同时也进行了降维。L1惩罚项降维的原理在于保留多个对目标值具有同等相关性的特征中的一个,所以没选到的特征不代表不重要。

    简单介绍特征提取

    可能由于特征矩阵过大,导致计算量大,训练时间长的问题,因此降低特征矩阵维度也是必不可少的。常见的降维方法除了以上提到的基于L1惩罚项的模型以外,另外还有主成分分析法(PCA)和线性判别分析(LDA),线性判别分析本身也是一个分类模型。PCA和LDA有很多的相似点,其本质是要将原始的样本映射到维度更低的样本空间中,但是PCA和LDA的映射目标不一样:PCA是为了让映射后的样本具有最大的发散性;而LDA是为了让映射后的样本有最好的分类性能。所以说PCA是一种无监督的降维方法,而LDA是一种有监督的降维方法。

    特征相关性问题

    逻辑回归在训练的过程当中,如果有很多特征高度相关或者说有一个特征重复了100遍,会造成怎样的影响?
    如果在损失函数最终收敛的情况下,就算有很多特征高度相关也不会影响分类器的效果。
    为什么我们还是会在训练的过程当中将高度相关的特征去除?1)去掉高度相关的特征会让模型的可解释性更好;2)可以大大提高训练的速度。如果模型当中有很多特征高度相关的话,就算损失函数本身收敛了,但实际上参数是没有收敛的,这样会拉低训练的速度。其次是特征多了,本身就会增大训练的时间。

    特征离散化

    逻辑回归为什么要对特征进行离散化?
    在工业界,很少直接将连续值作为特征送入逻辑回归模型,而是将连续特征离散化为一系列0、1特征交给逻辑回归模型,这样做的优势有以下几点:
    1)稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展。
    2)离散化后的特征对异常数据由很强的鲁棒性,如果特征没有离散化,一个异常数据会给模型造成很大的干扰。
    3)逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合。
    4)离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力。
    5)特征离散化后,模型会更稳定。
    李沐少帅指出,模型是使用离散特征还是连续特征,其实是一个“海量离散特征+简单模型”同“少量连续特征+复杂模型”的权衡。既可以离散化用线性模型,也可以用连续特征加深学习。

    特征组合

    什么是组合特征?
    为了提高复杂关系的拟合能力,特征工程时候,将两个或两个以上特征通过一定方式组合构成高阶组合特征。
    在逻辑回归模型中,为什么常常要做特征组合(特征交叉)?
    逻辑回归模型属于线性模型,线性模型不能很好处理非线性特征,特征组合可以引入非线性特征,提升模型的表达能力。另外,基本特征可以认为是全局建模,组合特征更加精细,是个性化建模,但对全局建模会对部分样本有偏,对每一个样本建模又会导致数据爆炸,过拟合,所以基本特征+特征组合兼顾了全局和个性化。

    逻辑回归问题

    (重点)逻辑回归假设数据服从伯努利分布,通过极大似然函数的方法,运用梯度下降来求解参数,达到将数据二分类的目的。

    逻辑回归的基本假设

    任何的模型都有自己的假设,在这个假设下模型才是适用的。逻辑回归的第一个假设是假设服从伯努利分布。伯努利分布有一个简单地例子是抛硬币,投中正面的概率为p,抛中负面的概率为1-p。
    在逻辑回归模型中假设$h{\theta}(x)$为样本为正的概率,$1-h{\theta}(x)$为样本为负的概率。那么这个模型可以描述为:逻辑回归的第二个假设是假设样本为正的概率是:所以逻辑回归的最终形式:

    逻辑回归的损失函数

    逻辑回归的损失函数是它的极大似然函数:逻辑回归的损失函数为什么要使用极大似然函数作为损失函数?
    将极大似然函数取对数以后等同于对数损失函数。在逻辑回归这个模型下,对数损失函数的训练求参数的速度是比较快的。可以求得这个公式的梯度更新为:梯度的更新速度只和$x_{ij},y_i$相关,和sigmoid函数本身的梯度是无关的,这样的更新速度是可以自始至终都比较稳定。
    为什么不选平方损失函数呢?其一是因为如果你使用平方损失函数,会发现梯度更新的速度和sigmoid函数本身的梯度是相关的。sigmoid函数在它定义域内梯度都不大于0.25,这样训练会非常慢。

    逻辑回归的目的

    目的是将数据二分类,提高准确率。
    逻辑回归作为一个回归(也就是y值是连续的),如何应用到分类上呢。y值确实是一个连续的变量,逻辑回归的做法是划分一个阈值,y值大于这个阈值的是一类,y值小于这个阈值的是另外一类。阈值具体如何调整根据实际情况选择,一般选择0.5作为阈值来划分。

    逻辑回归的优缺点

    这里总结了逻辑回归应用在工业界当中的一些优点:
    1)形式简单,模型的可解释性非常好。从特征的权重可以看到不同的特征对最后结果的影响,某个特征的权重值比较高,那么这个特征最后对结果的影响比较大。
    2)模型效果不错。在工程上是可以接受的(作为baseline),如果特征工程做的好,效果不会太差,并且特征工程可以大家并行开发,大大加快开发的速度。
    3)训练速度快。分类的时候,计算量仅仅只和特征的数目相关,并且逻辑回归的分布式优化sgd发展比较成熟,训练的速度可以通过对机器进一步提高,这样我们可以在短时间内迭代好几个版本的模型。
    4)资源占用小,尤其是内存。因为只需要存储各个维度的特征值。
    5)方便输出结果调整。逻辑回归可以很方便的得到最后的分类结果,因为输出的是每个样本的概率分数,我们可以很容易的对这些概率分数进行cutoff,也就是划分阈值(大于阈值的是一类,小于阈值的是一类)
    当然逻辑回归本身也有很多的缺点:
    1)准确率并不是很高。因为形式非常的简单(非常类似线性模型),很难去拟合数据的真实分布。
    2)很难处理数据不平衡的问题。举个例子:如果我们对于一个正负样本非常不平衡的问题比如正负样本比10000:1,我们把所有样本都预测为正也能使损失函数的值比较小,但是作为一个分类器,它对正负样本的区分能力不会很好。
    3)处理非线性数据较麻烦。逻辑回归在不引入其他方法的情况下,只能处理线性可分的数据,或者进一步说,处理二分类的问题。
    4)逻辑回归本身无法筛选特征。有时候,我们会有GDBT来筛选特征,然后再上逻辑回归。

    逻辑回归是线性模型吗?

    1)逻辑回归是一种广义线性模型,它引入了Sigmoid函数,是非线性模型,但本质上还是一个线性回归模型,因为除去Sigmoid函数映射关系,其他的算法原理、步骤都是线性回归的。
    2)逻辑回归和线性回归首先都是广义的线性回归,区别在于逻辑回归多了个Sigmoid函数,使样本映射到[0,1]之间的数值,从而来处理分类问题。另外逻辑回归是假设变量服从伯努利分布,线性回归假设变量服从高斯分布。逻辑回归输出的是离散型变量,用于分类,线性回归输出的是连续值,用于预测。逻辑回归是用最大似然法去计算预测函数中最优参数值,而线性回归是用最小二乘法去对自变量因变量关系进行拟合。

    逻辑回归输出值的意义

    逻辑回归输出的值是0到1之间的值,这个值是真实的概率吗?
    推导过程
    结论:逻辑回归模型之所以是sigmoid的形式,源于我们假设y服从伯努利分布,伯努利分布又属于指数分布族,经过推导,将伯努利分布变为指数分布族的形式后。我们发现伯努利分布的唯一参数$\Phi$与指数分布族中的参数$\eta$具有sigmoid函数关系,于是我们转而求$\eta$与$x$的关系,此时,我们又假设$\eta$与$x$具有线性关系。至此找到了我们要用的模型的样子,也就是逻辑回归。即只有在满足:$y$服从伯努利分布;$n$和$x$之间存在线性关系时,输出值才是概率值。不满足的情况下,得到的输出值,只是置信度。

    欠拟合和过拟合

    过拟合:其实就是所建的机器学习模型或者深度学习模型在训练样本中表现过于优越,导致在验证数据集以及测试数据集中表现不佳。
    欠拟合:由于训练样本被提取的特征比较少,导致训练出来的模型不能很好地匹配,表现很差。
    判断方法是从训练集中随机选一部分作为验证集,采用K折交叉验证的方式,用训练集训练的同时在验证集上测试算法效果。在缺少有效预防欠拟合和过拟合措施的情况下,随着模型拟合能力的增强,错误率在训练集上逐渐减小,而在验证集上先减小后增大;当两者的误差率都较大时,处于欠拟合状态;当验证集误差率达到最低点,说明拟合效果最好,有最低点增大时,处于过拟合状态。
    ![误差率曲线图])(/images/拟合状态.png)

    解决模型欠拟合与过拟合常用方法

    欠拟合:欠拟合的原因大多是模型不够复杂、拟合函数的能力不够。
    因此,从数据层面考虑,可以增加新特征,例如:组合、泛化、相关性、高次特征等;从模型层面考虑,可以增加模型的复杂度,例如SVM的核函数、DNN等更复杂模型,去掉正则化或减少正则化参数,加深训练轮数等。
    过拟合:成因是给定的数据集相对过于简单,使得模型在拟合函数时过分考虑了噪声等不必要的数据间关联。
    解决方法:
    1)数据扩增:人为增加数据量,可以用重采样、上采样、增加随机噪声、GAN、图像数据的空间变换(平移旋转镜像)、尺度变换(缩放裁剪)、颜色变换、改变分辨率、对比度、亮度等。
    2)针对神经网络,采用dropout的方法:dropout的思想是当一组参数经过某一层神经元的时候,去掉这一层上的部分神经元,让参数只经过一部分神经元进行计算。这里的去掉不是真正意义上的去除,只是让参数不经过一部分神经元计算,从而减少了神经网络的规模(深度)。
    3)提前停止训练
    也就是减少训练的迭代次数。从上面的误差率曲线图,理论上可以找到有个训练程度,此时验证集误差率最低,视为拟合效果最好的点。
    4)正则化
    在所定义的损失函数后面加入一项永不为0的部分,那么经过不断优化损失函数还是会存在的。
    L0正则化:损失函数后面加入L0范数,也就是权重向量中非零参数的个数。特点是可以实现参数的稀疏性,使尽可能多的参数都为0;缺点是在优化时是NP难问题,很难优化。
    L1正则化:在损失函数后面加入权重向量的L1范数。L1范数是L0范数的最优凸近似,比L0范数容易优化,也可以很好地实现参数稀疏性。
    L2正则化:在损失函数后面加入参数L2范数的平方项。与L0、L1不同的是,L2很难使某些参数达到0,它只能是参数接近0。
    5)针对DNN,采用batch normalization:即BN,既能提高泛化能力,又大大提高训练速度,现被广泛应用于DNN的激活层之前。主要优势:减少了梯度对参数大小和初始值的依赖,将参数值(特征)缩放在[0,1]区间(若针对Relu还限制了输出的范围),这样反向传播时梯度控制在1左右,使得网络在较高的学习率之下也不易发生梯度爆炸或弥散(也防止了在使用sigmoid作为激活函数时训练容易陷入梯度极小饱和或极大的极端情况)。

    多分类问题

    在上面,我们主要使用逻辑回归解决二分类的问题,那对于多分类的问题,也可以用逻辑回归来解决吗?

    one vs rest

    由于概率函数$h_{\theta}(x)$所表示的是样本标记为某一类型的概率,但可以将一对一(二分类)扩展为一对多(one vs rest):
    1)将类型$class_1$看作正样本,其他类型全部看作负样本,然后我们就可以得到样本类型为该类型的概率$p_1$;
    2)然后再讲另外类型$class_2$看作正样本,其他类型全部看作负样本,同理得到$p_2$;
    3)以此循环,我们可以得到该待预测样本的标记类型分别为类型$class_i$时的概率$p_i$,最后我们取$p_i$中最大的那个概率对应的样本标记类型为我们的待预测样本类型。

    softmax函数

    使用softmax函数构造模型解决多分类问题,与logistic回归不同的是,softmax回归分类模型会有多个的输出,且输出个数与类别个数相等,输出为样本$X$的各个类别的概率,最后对样本进行预测的类型为概率最高的那个类别。

    选择方案

    当标签类别之间是互斥时,适合选择softmax回归分类器;当标签类别之间不完全互斥时,适合选择建立多个独立的logistic回归分类器。

模型之间的对比

线性回归与逻辑回归

逻辑回归和线性回归首先都是广义的线性回归,其次经典线性模型的优化目标函数是最小二乘,二逻辑回归则是似然函数。另外线性回归在整个实数域范围内进行预测,敏感度一致,而分类范围则需要在[0,1];逻辑回归就是一种减小预测范围,将预测值限定在[0,1]间的一种回归模型,因而对于这类问题来说,逻辑回归的鲁棒性比线性回归好。
逻辑回归的模型本质是一个线性回归模型,逻辑回归都是以线性回归为理论支持的。但线性回归模型无法做到sigmoid的非线性形式,sigmoid可以轻松处理0/1分类问题。逻辑回归在线性回归的实数范围输出值上施加sigmoid函数将值收敛到0~1范围,其目标函数也因此从差平方和变为对数损失函数,以提供最优化所需导数(sigmoid函数是softmax函数的二元特例)。注意,逻辑回归玩玩是解决二元0/1分类问题,只是它和线性回归耦合太紧,也被冠以回归的名字。若要求多分类,就要把sigmoid换成softmax。

最大熵与逻辑回归

最大熵原理是概率模型学习的一个准则,最大熵认为,学习概率模型时,在所有可能分布中,熵最大的模型是最好的模型。
最大熵推导过程
最大熵在解决二分类问题时就是逻辑回归,在解决多分类问题时就是多项逻辑回归。此外,最大熵与逻辑回归都称为对数线性模型。

SVM与逻辑回归

这两个模型应用广泛且有很多相同点,所以把SVM和LR放在一起比较。
相同点:
1)都是线性分类器,本质上都是求一个最佳分类超平面
2)都是监督学习算法。
3)都是判别模型。通过决策函数,判别输入特征之间的差别来进行分类。
常见的判别模型:KNN、SVM、LR。
常见的生成模型:朴素贝叶斯、隐马尔科夫模型。
不同点:
1)本质上的损失函数不同
LR的损失函数是交叉熵:

SVM的目标函数是hinge loss:

逻辑回归基于概率理论,假设样本为正样本的概率可以用sigmoid函数来表示极大似然估计的方法估计出参数的值。支持向量机基于几何间隔最大化原理,认为存在最大间隔的分类面为最优分类面。
2)两个模型对数据和参数的敏感程度不同
SVM考虑分类边界线附近的样本(决定分类超平面的样本),在支持向量外添加或减少任何样本点对分类决策面没有任何影响。LR受所有数据点的影响,每个样本点都会影响决策面的结果。如果训练数据不同类别严重不平衡,则一般需要先对数据做平衡处理,让不同类别的样本尽量平衡。
3)SVM基于距离分类,LR基于概率分类
SVM依赖数据表达的距离测度,所以需要先对数据先做normalization;LR不受其影响。
4)逻辑回归是处理经验风险最小化,SVM是结构风险最小化。这点体现在SVM自带L2正则化项,而逻辑回归需要在损失函数之外添加正则项。
5)逻辑回归通过非线性变换减弱分离平面较远点的影响,SVM则只支持向量从而消去较远点的影响。

支持向量机SVM

SVM是一种二分类模型,其基本思想是在特征空间中寻找间隔最大的分离超平面使数据得到高效的二分类,具体来讲有三种情况(不加核函数的话就是个线性模型,加了之后会升级为一个非线性模型):
当训练样本线性可分时,通过硬间隔最大化,学习一个线性分类器,即线性可分支持向量机;
当训练函数近似线性可分时,引入松弛变量,通过软间隔最大化,学习一个线性分类器,即线性支持向量机;
当训练数据线性不可分时,通过使用核技巧及软间隔最大化,学习非线性支持向量机。

一句话介绍SVM

SVM是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔大使它有别于普通的感知机,通过核技巧隐式的在输入空间直接求解映射空间中特征向量的内积,使其成为一个非线性分类器。SVM的学习策略是间隔最大化,可形式化为一个求解凸二次规划问题。

SVM的几个核心概念

确定超平面及函数间隔

由空间上的平面公式确定超平面$wx+b=0$,且$|wx+b|$表示点$x$到平面上的距离。正例负例位于分割平面两侧,因此$y(wx+b)$可同时表示分类正确性以及距离置信度。这也就是函数间隔,其被定义为训练集中所有点到超平面距离的最小值。

几何间隔

由于成比例地缩放$w$和$b$会使得$|wx+b|$跟着成比例缩放,因此需要对法向量w加上约束,使得间隔是确定的,也就是函数间隔整体除以$||w||$,也就得到了几何间隔。

间隔最大化

分为硬间隔最大和软间隔最大
SVM的基本思想就是求解可以正确划分数据集并且几何间隔最大的分离超平面,其原因是线性可分超平面有无数个,但是间隔最大超平面使唯一的。

支持向量

与超平面最近的点被称为支持向量,也就是使得原始问题约束项成立的点。

核函数

核函数本质不是将特征映射到高维空间,而是找到一种直接在低维空间对高维空间中向量做点积运算的简便方法。

SVM推导

(重点)SVM算法推导过程
SVM常考问题

SVM为什么采用间隔最大化(与感知机的区别)

当训练数据线性可分时,存在无穷个分离超平面可以将两类数据正确分开。感知机利用误分类最小策略,求得分离超平面,不过此时的解有无穷多个。线性可分支持向量机利用间隔最大化求得最优分离超平面,这个解是唯一的。另一方面,此时的间隔超平面所产生的分类结果是最鲁棒的,对未知实例的泛化能力最强。

SVM的目标(硬间隔)

有两个目标:第一是使间隔最大化,第二是使样本正确分类,由此推出目标函数:

目标以是从点到面的距离公式简化来的,目标二相当于感知机,只是把大于等于0进行缩放变成了大于等于1,方便后面的推导。

将原始问题转化为对偶问题

做所以说对偶问题更容易求解,其原因在于降低了算法的计算复杂度。在原问题下,算法的复杂度与样本维度相关,即等于权重w的维度,而在对偶问题下,算法复杂度与样本数量相关,即为拉格朗日算子的个数。
因此,如果你是做线性分类,且样本维度低于样本数量的话,在原问题下求解就好了,Liblinear之类的线性SVM默认都是这样的;但如果你是做非线性分类,那就会涉及到升维(比如使用高斯核做核函数,其实是将样本升到无穷维),升维后的样本维度往往会远大于样本数量,此时显然在对偶问题下求解会更好。

软间隔

不管在原特征空间,还是在映射的高维空间,我们都假设样本是线性可分的。虽然理论上我们总能找到一个高维映射使数据线性可分,但在实际任务中,寻找一个合适的核函数很困难。此外,由于数据通常有噪声存在,一味追求数据线性可分可能会使模型陷入过拟合,因此我们放宽对样本的要求,允许少量样本分类错误。这样的想法就意味着对目标函数的改变,之前推导的目标函数里不允许任何错误,并且让间隔最大,现在给之前的目标函数加上一个误差,就相当于允许原先的目标出错,引入松弛变量,公式变为:

在松弛变量中引入合页损失(hinge loss):

但是这个代价需要一个控制的因子,引入C>0,惩罚参数。C越大说明把错误放的越大,说明对错误地容忍度就小,反之亦然。当C无穷大时,就变成一点错误都不能容忍,即变成硬间隔。实际应用时我们要合理选取C,C越小越容易欠拟合,C越大越容易过拟合。
所以软间隔的目标函数为:

其中:$\xi_i=max(0,1-y_i(w^Tx_i+b))$

核函数

为什么要引入核函数?
当样本在原始空间线性不可分时,可将样本从原始空间映射到一个更高维的特征空间,使得样本在这个特征空间内线性可分。而引入这样的映射后,所要求解的对偶问题中,无需求解真正的映射函数,而只需要知道其核函数。核函数的定义:$K(x,y)=<(x),(y)>$,即在特征空间的内积等于它们在原始样本空间中通过核函数K计算的结果。一方面数据变成高维空间中线性可分的数据,另一方面不需要求解具体的映射函数,只需要给定具体的核函数即可,这样使得求解的难度大大降低。因为核函数求得的值等于将两个低维空间找那个的向量映射到高维空间后的内积。
常用的核函数:
1)线性核函数;2)多项式核;3)径向基核(RBF);4)傅里叶核;5)样条核;6)Sigmoid核函数。
其中Gauss径向基函数则是局部性强的核函数,其外推能力随着参数的增大而减弱;多项式形式的核函数具有良好的全局性质,局部性差。
如何选择和函数呢?
1)当特征维数远远大于样本数的情况下,使用线性核就可以
2)当特征维数和样本数都很大,例如文本分类,一般使用线性核
3)当特征维数远小于样本数,一般使用RBF。

为什么SVM对缺失数据敏感?

这里说的缺失数据是指缺失某些特征数据,向量数据不完整。SVM没有处理缺失值的策略。而SVM希望样本在特征空间中线性可分,所以特征空间的好坏对SVM的性能很重要,缺失特征数据将影响训练结果的好坏。

SVM的优缺点

优点

1)由于SVM是一个凸优化问题,所以求得的解一定是全局最优而不是局部最优。
2)不仅适用于线性问题还适用于非线性问题(用核技巧)
3)同游高维样本空间的数据也能用SVM,这是因为数据集的复杂度只取决于支持向量而不是数据集的维度,这在某种意义上避免了“维数灾难”。
4)理论基础比较完善

缺点

1)二次规划问题求解将涉及m阶矩阵的计算(m为样本的个数),因此SVM不适用于超大数据集。(SMO算法可以缓解这个问题)
2)只适用于二分类问题。(SVM的退岗SVR也适用于回归问题;可以通过多个SVM的组合来解决多分类问题)

常用的距离公式

曼哈顿距离

点$P_1(x_1,y_1)$和点$P_2(x_2,y_2)$的距离为:

欧式距离

欧式空间中两点的距离。点$P_1(x_1,x_2,…,x_n)$和点$P_2(y_1,y_2,…,y_n)$的距离如下:

切比雪夫距离

两点之间的距离定义为其各坐标数值差的最大值。点$P_1(x_1,x_2,…,x_n)$和点$P_2(y_1,y_2,…,y_n)$的距离如下:

余弦相似度

余弦相似度是通过测量两个向量夹角的度数来度量他们之间的相似度。对于A和B的距离是:

数据降维

数据降维原理

降维就是一种对高维度特征数据预处理方法。降维是将高维度的数据保留下最重要的一些特征,去除噪声和不重要的特征,从而实现提升数据处理速度的目的。在实际的生产和应用中,降维在一定的信息损失范围内,可以为我们节省大量的时间和成本。
降维具有如下一些优点:
1) 使得数据集更易使用。
2) 降低算法的计算开销。
3) 去除噪声。
4) 使得结果容易理解。
降维的算法有很多,比如奇异值分解(SVD)、主成分分析(PCA)、因子分析(FA)、独立成分分析(ICA)。

线性判别分析LDA

LDA是一种监督学习的降维技术,也就是说它的数据集的每个样本是有类别输出的。这点和PCA不同,PCA是不考虑样本类别输出的无监督降维技术。LDA的思想可以用一句话来概括,就是“投影后类内方差最小,类间方差最大”。我们要将数据在低维度上进行投影,投影后希望每一种类别数据的投影点尽可能地接近,而不同类别的数据的类别中心之间的距离尽可能地大。
LDA求解方法:
1)计算数据集中每个类别样本的均值向量$\mu_j$,及总体均值向量$\mu$。
2)计算类内散度矩阵$S_w$,全局散度矩阵$S_t$,并得到类间散度矩阵$S_b=S_t-S_w$。
3)对矩阵$S_w^{-1}S_b$进行特征值分解,将特征值从大到小排列。
4)取特征值前k大的对应的特征向量组成新的矩阵,将n维样本
映射到k维。
从目标出发,PCA选择的是投影后数据方差最大的方向。由于它是无监督的,因此PCA假设方差越大,信息量越多,用主成分来表示原始数据可以去除冗余的维度,达到降维。而LDA选择的是投影后类内方差小、 类间方差大的方向。其用到了类别标签信息,为了找到数据中具有判别性的维度,使得原始数据在这些方向上投影后,不同类别尽可能区分开。

PCA原理

PCA介绍
PCA(Principal Component Analysis),即主成分分析方法,是一种使用最广泛的数据降维算法。PCA的主要思想是将n维特征映射到k维上,这k维是全新的正交特征也被称为主成分,是在原有n维特征的基础上重新构造出来的k维特征。PCA的工作就是从原始的空间中顺序地找一组相互正交的坐标轴,新的坐标轴的选择与数据本身是密切相关的。其中,第一个新坐标轴选择是原始数据中方差最大的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴。通过这种方式获得的新的坐标轴,我们发现,大部分方差都包含在前面k个坐标轴中,后面的坐标轴所含的方差几乎为0。于是,我们可以忽略余下的坐标轴,只保留前面k个含有绝大部分方差的坐标轴。事实上,这相当于只保留包含绝大部分方差的维度特征,而忽略包含方差几乎为0的特征维度,实现对数据特征的降维处理。
思考:我们如何得到这些包含最大差异性的主成分方向呢?
1)对样本数据进行中心化处理。
2)求样本协方差矩阵。
3)对协方差矩阵进行特征值分解, 将特征值从大到小排列。
4)取特征值前k大对应的特征向量ω1,ω2,…,ωk组成新的矩阵,将n维样本映射到k维。
由于得到协方差矩阵的特征值特征向量有两种方法:特征值分解协方差矩阵、奇异值分解协方差矩阵,所以PCA算法有两种实现方法:基于特征值分解协方差矩阵实现PCA算法、基于SVD分解协方差矩阵实现PCA算法。
PCA算法的主要优点有:
1)仅仅需要以方差衡量信息量,不受数据集以外的因素影响。
2)各主成分之间正交,可消除原始数据成分间的相互影响的因素。
3)计算方法简单,主要运算是特征值分解,易于实现。
PCA算法的主要缺点有:
1)主成分各个特征维度的含义具有一定的模糊性,不如原始样本特征的解释性强。
2)方差小的非主成分也可能含有对样本差异的重要信息,因降维丢弃可能对后续数据处理有影响。

SVD奇异值分解

SVD是一种矩阵分解方法,相当于因式分解,其目的就是将一个矩阵拆分成多个矩阵相乘的形式。能用小得多的数据集来表示原始数据集,这样可以去除噪声和冗余信息。适用于数值型数据。
优点:简化数据,去除噪声,提高算法的结果;
缺点:数据的转换可能难以理解,分解出的矩阵解释性往往不强。

LDA、PCA、SVD推导过程

简述LDA、PCA、SVD推导过程

K近邻算法(KNN)

对于新的样本,根据其K个最近邻的训练样本的标签,通过多数表决等方式进行预测。
k近邻的三要数:k值选择;距离度量;决策规则。
K近邻的优点:第一就就是k近邻算法是一种在线技术,新数据可以直接加入数据集而不必进行重新训练,第二就是k近邻算法理论简单,容易实现。第三就是准确性高,对异常值和噪声有较高的容忍度。第四就是k近邻算法天生就支持多分类,区别与感知机、逻辑回归、SVM。
K近邻算法的缺点:基本的 k近邻算法每预测一个“点”的分类都会重新进行一次全局运算,对于样本容量大的数据集计算量比较大。而且K近邻算法容易导致维度灾难,在高维空间中计算距离的时候,就会变得非常远;样本不平衡时,预测偏差比较大,k值大小的选择得依靠经验或者交叉验证得到。k的选择可以使用交叉验证,也可以使用网格搜索。k的值越大,模型的偏差越大,对噪声数据越不敏感,当k的值很大的时候,可能造成模型欠拟合。k的值越小,模型的方差就会越大,当k的值很小的时候,就会造成模型的过拟合。
K值的选择:
1)K值较小,则模型复杂度较高,容易发生过拟合,学习的估计误差会增大,预测结果对近邻的实例点非常敏感。
2)K值较大可以减少学习的估计误差,但是学习的近似误差会增大,与输入实例较远的训练实例也会对预测起作用,使预测发生错误,k值增大模型的复杂度会下降。
3)在应用中,k值一般取一个比较小的值,通常采用交叉验证法来选取最优的K值。

集成学习

集成学习能够通过训练数据集产生多个学习模型,然后通过一定的结合策略生成强学习模型。
集成学习

Bagging

算法流程
1)从训练样本集中随机可放回抽样(Bootstrapping )N次,得到与训练集相同大小的训练集,重复抽样K次,得到K个训练集 。
2) 每个训练集得到一个最优模型,K个训练集得到K个最优模型。
3) 分类问题:对K个模型采用投票的方式得到分类结果;回归问题:对K个模型的值求平均得到分类结果。

Boosting

Boosting是一种可将弱学习器提升为强学习器的算法。Boosting的每一次抽样的样本分布是不一样的,每一次迭代,都是根据上一次迭代的结果,增加被错误分类的样本的权重。使模型在之后的迭代中更加注重难以分类的样本。这是一个不断学习的过程,也是一个不断提升的过程,这就是Boosting思想的本质所在。迭代之后,将每次迭代的基分类器进行集成,那么如何进行样本权重的调整和分类器的集成是我们需要考虑的关键问题。
1)每一轮如何改变训练数据的权值和概率分布?
通过提高那些在前一轮被弱学习器分错样例的权值,减小前一轮正确样例的权值,使学习器重点学习分错的样本,提高学习器的性能。
2)通过什么方式来组合弱学习器?
通过加法模型将弱学习器进行线性组合,学习器准确率大,则相应的学习器权值大;反之,则学习器的权值小。即给学习器好的模型一个较大的确信度,提高学习器的性能。

结合策略

集成学习得到多个学习器后,结合策略得到最终的结果。通常用到最多的是平均法,投票法和学习法。
1) 平均法
对于数值类的回归预测,通常使用的结合策略是平均法,即对K个学习器的学习结果求平均,得到最终的预测结果。
2)投票法
对于分类问题的预测,通常使用的结合策略是投票法,也就是我们常说的少数服从多数。即对K个学习器的分类结果作一个统计,出现次数最多的类作为预测类。
3) 学习法
上面两种结合策略方法比较简单,可能学习误差较大。因此,我们尝试用学习法去预测结果,学习法是将K个学习器的分类结果再次作为输入,将训练集的输出作为输出,重新训练一个学习器来得到最终结果。

Bagging和Boosting的区别:

1)样本选择上:
Bagging:训练集是在原始集中有放回选取的,从原始集中选出的各轮训练集之间是独立的。
Boosting:每一轮的训练集不变,只是训练集中每个样例在分类器中的权重发生变化,而权值是根据上一轮的分类结果进行调整。
2)训练样本权重:
Bagging:使用均匀取样,每个样例的权重相等。
Boosting:根据错误率不断调整样例的权值,错误率越大则权重越大。
3)预测函数权重:
Bagging:所有预测函数的权重相等。
Boosting:学习器性能好的分配较大的权重,学习器性能差的分配较小的权重。
4)并行计算:
Bagging:各个预测函数可以并行生成。
Boosting:各个预测函数只能顺序生成,因为后一个模型参数需要前一轮模型的结果。

决策树

决策树是一种自上而下,对样本数据进行树形分类的过程,由节点和有向边组成。 节点分为内部节点和叶节点,其中每个内部节点表示一个特征或属性,叶节点表示类别。 从顶部节点开始,所有样本聚在一起,经过根节点的划分,样本被分到不同的子节点中,再根据子节点的特征进一步划分,直至所有样本都被归到某个类别。

决策树的实现 ID3、C4.5、CART

ID3使用信息增益来指导树的分裂:
ID3
C4.5通过信息增益比来指导树的分裂:
C4.5
CART的话既可以是分类树,也可以是回归树。当是分类树时,使用基尼系数来指导树的分裂:
CART
当是回归树时,则使用的是平方损失最小:
CART

CART分类树和ID3以及C4.5的区别

1)首先是决策规则的区别,CART分类树使用基尼系数、ID3使用的是信息增益,而C4.5使用的是信息增益比。ID3采用信息增益作为评价标准,模型的泛化能力非常弱。C4.5通过引入信息增益比,一定程度上对取值比较多的特征进行惩罚, 避免ID3出现过拟合的特性,提升决策树的泛化能力。
2)ID3和C4.5可以是多叉树,但是CART分类树只能是二叉树。
3)从样本类型的角度,ID3只能处理离散型变量,而C4.5和CART都可以处理连续型变量。C4.5处理连续型变量时,通过对数据排序之后找到类别不同的分割线作为切分点,根据切分点把连续属性转换为布尔型,从而将连续型变量转换多个取值区间的离散型变量。而对于CART,由于其构建时每次都会对特征进行二值划分,因此可以很好地适用于连续性变量。

决策树剪枝

决策树剪枝可分为前剪枝和后剪枝,剪枝可防止过拟合。前剪枝本质就是早停止,后剪枝通常是通过衡量剪枝后损失函数变化来决定是否剪枝。
为什么要对决策树进行减枝? 如何进行减枝?
剪枝是决策树解决过拟合问题的方法。 在决策树学习过程中,为了尽可能正确分类训练样本,结点划分过程将不断重复,有时会造成决策树分支过多,于是可能将训练样本学得太好,以至于把训练集自身的一些特点当作所有数据共有的一般特点而导致测试集预测效果不好,出现了过拟合现象。 因此,可以通过剪枝来去掉一些分支来降低过拟合的风险。
决策树剪枝的基本策略有“预剪枝”和“后剪枝”。 预剪枝是指在决策树生成过程中,对每个结点在划分前先进行估计,若当前结点的划分不能带来决策树泛化性能提升,则停止划分并将当前结点标记为叶结点; 后剪枝则是先从训练集生成一棵完整的决策树,然后自底向上地对非叶结点进行考察,若将该结点对应的子树替换为叶结点能带来决策树泛化性能提升,则将该子树替换为叶结点。
前剪枝的几种停止条件:
1)当树到达一定深度的时候,停止树的生长。
2)当到达当前结点的样本数量小于某个阈值的时候,停止树的生长。
3)计算每次分裂对测试集的准确度提升,当小于某个阈值的时候,不再继续扩展。
常见的后剪枝方法包括错误率降低剪枝(Reduced Error Pruning,REP)、悲观剪枝(Pessimistic Error Pruning,PEP)、代价复杂度剪枝(Cost Complexity Pruning,CCP)、最小误差剪枝(Minimum Error Pruning, MEP)、CVP(Critical Value Pruning)、OPP(Optimal Pruning)等方法。

随机森林的随机体现在哪些方面

随机森林的随机主要体现在两个方面:一个是建立每棵树时所选择的特征是随机选择的;二是生成每棵树的样本也是通过有放回抽样产生的。

GDBT,Xgboost,lightGBM的区别

1)传统 GBDT 以 CART 作为基分类器,xgboost 还支持线性分类器,这个时候 xgboost 相当于带 L1 和 L2 正则化项的逻辑回归(分类问题)或者线性回归(回归问题)。
2)传统 GBDT 在优化时只用到一阶导数信息,xgboost 则对代价函数进行了二阶泰勒展开,同时用到了一阶和二阶导数。
3)xgboost 在代价函数里加入了正则项,用于控制模型的复杂度。正则项里包含了树的叶子节点个数、每个叶子节点上输出的 score 的 L2 模的平方和。从 Bias-variance tradeoff 角度来讲,正则项降低了模型的variance,使学习出来的模型更加简单,防止过拟合,这也是 xgboost 优于传统GBDT 的一个特性。
4)Shrinkage(缩减),相当于学习速率(xgboost 中的eta)。xgboost 在进行完一次迭代后,会将叶子节点的权重乘上该系数,主要是为了削弱每棵树的影响,让后面有更大的学习空间。实际应用中,一般把 eta 设置得小一点,然后迭代次数设置得大一点。(补充:传统 GBDT 的实现也有学习速率)
5)列抽样(column subsampling)。xgboost 借鉴了随机森林的做法,支持列抽样,不仅能降低过拟合,还能减少计算,这也是 xgboost异于传统gbdt 的一个特性。
6)对缺失值的处理。对于特征的值有缺失的样本,xgboost 可以自动学习出它的分裂方向。
7)xgboost 工具支持并行。boosting 不是一种串行的结构吗?怎么并行的? 注意 xgboost 的并行不是 tree 粒度的并行,xgboost 也是一次迭代完才能进行下一次迭代的(第t次迭代的代价函数里包含了前面 t-1 次迭代的预测值)。xgboost 的并行是在特征粒度上的。我们知道,决策树的学习最耗时的一个步骤就是对特征的值进行排序(因为要确定最佳分割点),xgboost在训练之前,预先对数据进行了排序,然后保存为 block 结构,后面的迭代中重复地使用这个结构,大大减小计算量。这个block结构也使得并行成为了可能,在进行节点的分裂时,需要计算每个特征的增益,最终选增益最大的那个特征去做分裂,那么各个特征的增益计算就可以开多线程进行。
8)可并行的近似直方图算法。树节点在进行分裂时,我们需要计算每个特征的每个分割点对应的增益,即用贪心法枚举所有可能的分割点。当数据无法一次载入内存或者在分布式情况下,贪心算法效率就会变得很低,所以 xgboost 还提出了一种可并行的近似直方图算法,用于高效地生成候选的分割点。
Xgboost和LightGBM,二者的区别如下:
1)由于在决策树在每一次选择节点特征的过程中,要遍历所有的属性的所有取 值并选择一个较好的。XGBoost 使用的是近似算法算法,先对特征值进行排序 Pre-sort,然后根据二阶梯度进行分桶,能够更精确的找到数据分隔点;但是 复杂度较高。LightGBM 使用的是 histogram 算法,这种只需要将数据分割成不同的段即可,不需要进行预先的排序。占用的内存更低,数据分隔的复杂度更低。
2)决策树生长策略,我们刚才介绍过了,XGBoost采用的是 Level-wise 的树 生长策略,LightGBM 采用的是 leaf-wise 的生长策略。
3)并行策略对比,XGBoost 的并行主要集中在特征并行上,而 LightGBM 的并 行策略分特征并行,数据并行以及投票并行。

聚类算法

​ 聚类就是按照某个特定标准把一个数据集分割成不同的类或簇,使得同一个簇内的数据对象的相似性尽可能大,同时不在同一个簇中的数据对象的差异性也尽可能地大。主要的聚类算法可以划分为如下几类:划分方法、层次方法、基于密度的方法、基于网格的方法以及基于模型的方法。

聚类和降维有什么区别和联系

​ 聚类用于找寻数据内在的分布结构,既可以作为一个单独的过程,也可作为分类等其他学习任务的前驱过程。聚类是标准的无监督学习。在一些推荐系统中需确定新用户的类型,但定义“用户类型”却可能不太容易,此时往往可先对原有的用户数据进行聚类,根据聚类结果将每个簇定义为一个类,然后再基于这些类训练分类模型,用于判别新用户的类型。
降维则是为了缓解维数灾难的一个重要方法,就是通过某种数学变换将原始高维属性空间转变为一个低维“子空间”。从而通过最主要的几个特征维度就可以实现对数据的描述,对于后续的分类很有帮助。
聚类和降维都可以作为分类等问题的预处理步骤。虽然他们都能实现对数据的约减,但二者适用的对象不同,聚类针对的是数据点,而降维则是对于数据的特征。

k-means聚类算法

k-means算法以k为参数,把n个对象分成k个簇,使簇内具有较高的相似度,而簇间的相似度较低。k-means算法的处理过程如下:首先,随机地选择k个对象,每个对象初始地代表了一个簇的平均值或中心;对剩余的每个对象,根据其与各簇中心的距离,将它赋给最近的簇;然后重新计算每个簇的平均值。这个过程不断重复,直到准则函数收敛。通常,采用平方误差准则,其定义如下:

k-means++算法

k个初始化的质心的位置选择对最后的聚类结果和运行时间都有很大的影响,因此需要选择合适的k个质心。如果仅仅是完全随机的选择,有可能导致算法收敛很慢。K-Means++算法就是对K-Means随机初始化质心的方法的优化。
K-Means++的对于初始化质心的优化策略也很简单,如下:
a)从输入的数据点集合中随机选择一个点作为第一个聚类中心μ1
b)对于数据集中的每一个点$x_i$,计算它与已选择的聚类中心中最近聚类中心的距离$D(x_i)=arg min||x_i−μ_r||_2^2, r=1,2,…kselected$。
c)选择一个新的数据点作为新的聚类中心,选择的原则是:D(x)较大的点,被选取作为聚类中心的概率较大
d) 重复b和c直到选择出k个聚类质心
e) 利用这k个质心来作为初始化质心去运行标准的K-Means算法

ISODATA算法

当K值的大小不确定时,可以使用ISODATA算法。ISODATA的全称是迭代自组织数据分析法。在K均值算法中,聚类个数K的值需要预先人为地确定,并且在整个算法过程中无法更改。而当遇到高维度、海量的数据集时,人们往往很难准确地估计出K的大小。ISODATA算法就是针对这个问题进行了改进,它的思想也很直观。当属于某个类别的样本数过少时,把该类别去除;当属于某个类别的样本数过多、分散程度较大时,把该类别分为两个子类别。ISODATA算法在K均值算法的基础之上增加了两个操作,一是分裂操作,对应着增加聚类中心数;二是合并操作,对应着减少聚类中心数。ISODATA算法是一个比较常见的算法,其缺点是需要指定的参数比较多,不仅仅需要一个参考的聚类数量$Ko$,还需要制定3个阈值。
1) 预期的聚类中心数目$K_o$。在ISODATA运行过程中聚类中心数可以变化,$K_o$是一个用户指定的参考值,该算法的聚类中心数目变动范围也由其决定。具体地,最终输出的聚类中心数目常见范围是从$K_o$的一半,到两倍$K_o$。
2)每个类所要求的最少样本数目$N
{min}$。如果分裂后会导致某个子类别所包含样本数目小于该阈值,就不会对该类别进行分裂操作。
3)最大方差Sigma。用于控制某个类别中样本的分散程度。当样本的分散程度超过这个阈值时,且分裂后满足(1),进行分裂操作。
4) 两个聚类中心之间所允许最小距离$D_{min}$。如果两个类靠得非常近(即这两个类别对应聚类中心之间的距离非常小),小于该阈值时,则对这两个类进行合并操作。

K-Means与KNN的区别

K-Means是无监督学习的聚类算法,没有样本输出;而KNN是监督学习的分类算法,有对应的类别输出。KNN基本不需要训练,对测试集里面的点,只需要找到在训练集中最近的k个点,用这最近的k个点的类别来决定测试点的类别。而K-Means则有明显的训练过程,找到k个类别的最佳质心,从而决定样本的簇类别。
当然,两者也有一些相似点,两个算法都包含一个过程,即找出和某一个点最近的点。两者都利用了最近邻(nearest neighbors)的思想。

k-means的优缺点

K-Means的主要优点有:
1)原理比较简单,实现也是很容易,收敛速度快。
2)聚类效果较优。
3)算法的可解释度比较强。
4)主要需要调参的参数仅仅是簇数k。

K-Means的主要缺点有:
1)K值的选取不好把握
2)对于不是凸的数据集比较难收敛
3)如果各隐含类别的数据不平衡,比如各隐含类别的数据量严重失衡,或者各隐含类别的方差不同,则聚类效果不佳。
4)采用迭代方法,得到的结果只是局部最优。
5)对噪音和异常点比较的敏感。

层次聚类算法

根据层次分解的顺序是自底向上的还是自上向下的,层次聚类算法分为凝聚的层次聚类算法和分裂的层次聚类算法。凝聚型层次聚类的策略是先将每个对象作为一个簇,然后合并这些原子簇为越来越大的簇,直到所有对象都在一个簇中,或者某个终结条件被满足。
算法流程:
以采用最小距离的凝聚层次聚类算法为例:
(1) 将每个对象看作一类,计算两两之间的最小距离;
(2) 将距离最小的两个类合并成一个新类;
(3) 重新计算新类与所有类之间的距离;
(4) 重复(2)、(3),直到所有类最后合并成一类。

当机器学习性能不是很好时,你会如何优化?

参考答案

智能辅助健身锻炼系统

项目介绍

本项目(是和深圳趣感科技公司委托下)搭建的一个远程的在线辅助健身锻炼系统,类似于keep软件,但项目核心通过评分机制促进用户的锻炼效果。项目针对的是需要在家完成健身锻炼的人群,通过2D摄像头采集用户的锻炼视频,实时上传到系统的业务后台,利用服务器端提供的姿态估计和行为识别算法,评估用户的锻炼效果,辅助用户在家完成健身锻炼。

项目背景

深圳趣感科技公司是开发一款穿戴式跑步训练指导系统,通过传感器采集人体运动数据,对跑步姿态进行科学评估,根据评估结果生成个性化的力量训练和跑姿改善计划,并实时监测训练完成度和质量。
深圳趣感科技提供了的一系列标准5-6分钟的锻炼视频,作为锻炼动作的模板。每个动作持续时间是3-5s,

项目架构

本项目是分模块设计的,前端是网页版智能健身指导平台,结合摄像头采集用户的锻炼视频及标准的锻炼视频和用户锻炼效果的展示。中间是系统的业务后台,负责对视频流数据和姿态数据的处理、封装和转发。最后是服务器端,主要运行姿态估计和行为识别等算法,通过人体关键点评估用户的锻炼效果。

技术选型

项目的前端是基于vue.js的Element框架搭建,业务后台是基于java的SpringBoot+Mybatis框架;算法层大多基于python实现,主要完成2D的人体姿态估计和基于关键点的行为识别算法。利用openpose获取人体关键点,然后将17个关键点坐标输入HCN网络进行行为分类。在三次准备动作中,若两次动作类别正确,则判断用户开始锻炼。最后采用DTW算法对该段时间内用户锻炼的关键点和正确指导视频的关键点进行匹配,判断用户锻炼的效果。
HCN网络一种端到端的共现特征学习框架,其使用了 CNN 来自动地从骨架序列中学习分层的共现特征。我们首先使用核大小为 n×1 的卷积层独立地为每个关节学习了点层面的特征。然后我们再将该卷积层的输出转置,以将关节的维度作为通道。如果一个骨架的每个关节都被当作是一个通道,那么卷积层就可以轻松地学习所有关节的共现。在这个转置运算之后,后续的层分层地聚合来自所有关节的全局特征。通过构建这种结构,在使用7层的网络就能获得较高的行为识别的准确率。
共现性:人的某个行为动作常常和骨架的一些特定关节点构成的集合,以及这个集合中节点的交互密切相关。如要判别是否在打电话,关节点“手腕”、“手肘”、“肩膀”和“头”的动作最为关键。我们将这种几个关节点同时影响和决定判别的特性称为共现性。

创新点

(1)结合openpose和基于骨骼的行为识别算法,实现了真实场景下具有较高鲁棒性的动作分类及评估。(2)算法层与平台之间的视频通信采用redis的消息中间件,采用发布/订阅模式为不同的任务订阅不同channel,实行并行的任务处理。(3)算法模块中行为识别算法创新主要体现在论文中,我可以给你详细介绍一下我的论文。

难点

遇到的最大的问题是什么?怎么解决的?学到了什么?
项目遇到的最大问题是系统方案的设计,针对项目的需求,实时的姿态估计前后端、服务器间的通信机制的实现,单一动作样本实现动作评估的算法模型选择等等,都花费大量的时间进行前期的项目调研,查询相关的资料文献、咨询有开发经验的师兄们后才最终明确系统的设计方案。
系统后台和服务器端间的通信,为了满足项目的实时性需求,采用redis做二者的消息中间件,利用redis的发布/订阅模式,完成图像信息的传输。
在这次的项目中,培养了我对现有资料的总结和应用能力,对系统方案宏观考虑和设计的能力,以及在团队合作时成员间协作配合的能力。

项目负责人带领多少人的团队?从哪些维度规划项目?项目时间节点把握?

项目是四个人的团队。主要从功能模块和时间线规划项目。功能模块分为三个部分,主要是前端、后台和算法模块。前端和后台各安排一位同学完成,还有两个同学专注于算法模块的设计,分别负责姿态估计和行为识别算法。在时间上主要是半个月的调研以及和客户的沟通、方案的设计。然后是两个月的系统框架的初步搭建,以及最后三个月的界面调整和算法优化。

项目最大的收获是什么?

在完整负责了一个项目设计和开发过程后,培养了我对现有资料的学习、总结和应用能力,对系统方案宏观考虑和设计的能力,以及在团队合作时成员间任务分配和协作配合能力。

项目的后期扩展有哪些?

项目后期扩展可以从功能角度考虑,比如和专业健身教练合作为每个用户定制个性化的健身计划,通过系统评分有效促进健身锻炼。从算法层面考虑,通过更细粒度的标注信息,如动作锻炼的次数,关节角度等,提高动作评估的多样性和科学性。

选取openpose的指标?为什么采用openpose?

使用姿态估计算法时,主要考虑了openpose和alphapose两种技术,在准确率和速度上,openpose达到60mAP和10FPS,alphapose达到71mAP和20FPS,二者性能差距不大,但从系统的后期扩展性考虑,使用openpose可以估计半身或局部的人体姿态,便于系统功能扩展。
采用openpose的最大原因是视频数据单一,只有单人的标准健身视频,在通过深度学习判断动作类别时存在大量的背景干扰,采用姿态估计获得人体的关键点进行动作评估,可以有效减低背景干扰,增强系统的鲁棒性。

动作相似度是怎么判断的?动作时间规划在项目中如何实现?

DTW动态时间规划,可以计算两个时间序列的相似度,尤其适合用于不同长度、不同节奏的时间序列。DTW将自动对时间序列进行缩放,使得两个序列的形态尽可能一致,得到最大可能的相似度。
我们假设用户的动作序列为Q和标准的锻炼动作序列为C,其中Q和C分别代表动作开始到结束期间人体的关键点坐标序列,即序列中的每一时刻都是由人体的17个关键点的坐标构成,标准动作序列事先通过openpose采集存放在数据库,根据行为识别判断的人体动作类别来获取,所有的人体关键点的空间坐标转换为相对人体的重心位置的相对坐标。动作匹配就转化为比较在动作发生时间内用户动作的关键点序列与标准模板的关键点序列的相似度进行评分。我们需要构造一个nxm的矩阵网格,n和m分别表征两段动作序列的长度。矩阵元素(i, j)表示两段动作序列qi和cj两帧的空间距离d(qi, cj)(每一帧都是在计算17个对应的人体关键点空间距离,也就是序列Q的每一个点和序列C的每一个点之间的相似度,距离越小则相似度越高。)。从开始时间匹配这两个动作序列Q和C,每经过一帧,之前所计算的空间距离都会累加。到达终点(n, m)后,这个累积距离就是我们上面说的最后的总的距离,也就是序列Q和C的相似度。通过使累积的距离最小,来判断两动作序列的最大可能相似度。

常见的消息队列有哪些及其功能是什么?

当前使用较多的消息队列有RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMQ等,而部分数据库如Redis、MySQL以及phxsql也可以实现消息队列的功能。
消息队列是指利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下提供应用解耦、弹性伸缩、冗余存储、流量削峰、异步通信、数据同步等等功能,其作为分布式系统架构中的一个重要组件,有着举足轻重的地位。
消息队列的特点:
1)采用异步处理模式
消息发送者 可以发送一个消息而无须等待响应。消息发送者 将消息发送到一条 虚拟的通道(主题 或 队列)上,消息接收者 则 订阅 或是 监听 该通道。一条信息可能最终转发给 一个或多个 消息接收者,这些接收者都无需对 消息发送者 做出 同步回应。整个过程都是异步的。
2)应用系统之间解耦合
发送者和接受者不必了解对方、只需要 确认消息;发送者和接受者 不必同时在线。

电信亿迅智慧安防系统

项目介绍

北邮和电信集团亿迅公司合作的智慧安防系统,系统采用基于深度学习的目标检测算法,对场景中的人脸和人体进行结构化分析和特征提取,分析人员的头部特征和穿着特征以及运动特征。

主要工作

主要负责多目标跟踪模块,采用YoloV3算法检测出人体位置,利用deepsort算法进行在线的多目标追踪。

国家电网能力开放平台(项目核心成员)

项目介绍

北邮与国家电网电车公司合作的,面向服务提供方ISP和应用开发方ISV设计开发的能力开放平台,能有效整合服务资源,通过能力开放的形式吸引服务提供商在平台完成注册、发布API,并与应用开发方建立商业联系。平台则需完成对ISP、ISV的服务监管和计量计费。

项目架构

基于微服务架构设计,分为协议转换模块、控制接入模块和平台管理模块。

主要工作

主要负责与国家电网南瑞子公司进行需求对接,并且主导了整体架构微服务的设计,完成了“协议转换模块”、“流量控制模块”、“权限管理模块”和“计费管理模块”的设计与实现。
1)协议转换模块:因为系统之间通信使用的基于阿里的HSF通信协议,所以需要将http接口转换为hsf需要的接口样式,对于用户的http调用,采用Spring AOP做了一个拦截,具体采用的Around方法,在调用之前,调用之中,调用之后分别对请求做了一些操作。
2)接入控制模块:对应协议控制模块中的拦截之前,对用户请求做了:鉴权、流控。在请求之后,做了计量计费操作。
鉴权:用户表,角色表,权限表,可见的菜单表,页面元素、文件表。
限流:基于Redis+Lua脚本,实现了基于固定时间分片的分布式限流。(1)通过spring AOP方法拦截的用户请求中获得用户调用的appid和apiid。根据appid和apiid组合的key值,在redis缓存中查找用户的流量控制策略。 (2)将appid、apiid和当前时间戳组合作为key值,将最大访问次数作为value值存入redis中,用户调用一次接口,将value值加一,在限制时间内达到最大次数,返回-1,否则返回+1,根据返回的状态对请求进行放行或拒绝。(3)Redis中不存在该流控策略,根据apiid从数据库中查找对应的流量控制策略,并将appid和apiid作为key值,流控策略作为value值存入redis中,并执行一次方法(2)。
计费:采用是工厂模式,产生三种计费方式:“按照时间计费(包年包月)”、“按照流量计费”、“按照次数计费”。

亮点:

设计模式:
1)代理模式:协议转换和访问控制模块之间采用了代理模式,整个协议控制模块都相当于中介。使用了AOP对原始方法做了处理,AOP本身利用反射对目标方法使用了动态代理模式。
2)策略模式:定义一系列控制算法,并且使得他们之间可以互换。访问控制模块使用该设计模式,分为“正常状态下的访问”和“测试状态下的访问”,这两种不同的状态,包含了不同的控制策略。“正常状态下的访问”:鉴权、流控、计费,“测试状态下的访问”:鉴权、流控,不包计费。利用Spring将这些策略注入到不同的List中,然后依次进行调用。
3)工厂模式:将“包年包月计费”、“按照流量计费”、“按照次数计费”实现同一个接口,并用工厂类,利用反射来调用这些计费方式。
微服务:
多个模块独立部署:对于不同的模块,进行不同方向上的拓展。项目中:协议转换模块承载最大的用户接入,需要做X轴拓展,通过负载均衡,对请求进行分发。而访问控制模块则分为不同的小模块,可以做数据库的Y轴上业务的拓展(功能性拆分)。可以按照服务队硬件资源不同的需求进行升级!
模块解耦,本项目是采用基于RPC的HSF协议进行模块之间的通信,面向连接。HSF是阿里内部使用的模块间通信方式。在调通分布式模块之间通信的遇到过一个比较大的问题,后来通过分析Edas平台各个服务器IP地址和服务端口以及redis日志文件,判断到最后是因为一个模块之间的redis地址出错导致没有启动redis,引发的后续通信的问题,体现了排错能力和自主解决问题的能力。

2018广东工业智造大数据创新大赛—铝型材表面瑕疵识别

项目介绍

在铝型材的实际生产过程中,由于各方面的影响,铝型材表面会产生
裂纹、起皮、划伤等瑕疵,影响铝型材的质量。为保证产品质量,需要人工进行肉眼目测。然而,铝型材的表面自身会含有纹路,与瑕疵的区分度不高。传统人工肉眼检查十分费力,不能及时准确的判断出表面瑕疵,质检的效率难以把控。铝型材制造商迫切希望采用最新的AI技术来革新现有质检流程,自动完成质检任务,减少漏检发生率,提高产品的质量。

数据集

瑕疵类型:不导电、檫花、角位漏底、桔皮、漏底、喷流、漆泡、起坑、杂色和脏点10类。训练时加上“正常”类别和“其他”类别一共12类。
初赛数据量:3000张图片,包含所有的瑕疵类型。初赛的图片结果为单标签的,即一张图片只有一种瑕疵。“其他”的类别文件夹中瑕疵初赛不要求细分,只是统一划分为一类。
初赛是分类任务,初赛提交的文件是csv文件,只需要提交测试集中图片名称和对应的类别标签。
分辨率统一为2056x1920
训练集:guangdong_round1_train1_20180903,约240M,约250张图片
guangdong_round1_train2_20180916,约1G。
分为无瑕疵图片1018张,和有瑕疵图片1120张
测试集:a榜:guangdong_round1_test_a_20180916,约384兆,440张图片
b榜:guangdong_round1_test_b_20181009,约825兆,1000张图片。
复赛图片大约有4356张图片,包含单瑕疵图片、多瑕疵图片和无瑕疵图片。单瑕疵图片指所含瑕疵类型只有一种的图片,但图片中可能出现多处相同类型的瑕疵;多瑕疵图片指所含瑕疵类型多于一种的图片;无瑕疵图片指瑕疵可忽略不计的图片,这些图片不需要标注。
复赛是检测任务,需要检测测试集中每幅图像所有瑕疵的位置和类型,瑕疵的位置通过矩形检测框进行标记,需给出各个矩形检测框的置信度,并将检测结果保存为utf-8编码的json文件。
训练集:guangdong_round1_train2_20181011,约6G,分为单瑕疵图片2776张,多瑕疵图片229张和无瑕疵图片1351张。
测试集:a榜:guangdong_round2_test_a_20181011,约420M,500张图片
b榜:guangdong_round2_test_b_20181106,约841M,1000张图片

比赛内容

在给定的生产线上铝型材监测影像上标注出瑕疵的位置并判断瑕疵类型。在前期的数据统计中,我们发现瑕疵的IOU占比小于0.1的样本数量占总体的63%左右,但还有5%的数据IOU占比达到0.7-0.8左右。样本数据存在瑕疵的尺寸大小变化大,两极分化严重,且微小瑕疵的样本数量多等问题。针对瑕疵的长宽比例不均匀,我们首先采用faster R-CNN来适应更多的长宽比,并采用resnet101作为骨干网络减少漏检的概率。针对微小瑕疵样本数量多的问题,我们采用特征金字塔网络(FPN)来对网络进行改进,并进一步采用ROIAlign替换ROIPooling,来提高小目标的检测效果。在训练过程中,我们使用数据增强和多尺度训练的方法,提高了模型的泛化能力及稳定性。在测试阶段,我们采用soft NMS替换NMS,实现更精细的回归框,提高检测结果。

方案设计

在前期的数据统计过程中,我们发现瑕疵的IOU占比小于0.1的数量占到总体的60%,但还有5%左右的数据主要是IOU占比达到0.7-0.8左右。瑕疵的矩形框尺寸大小变化大,两极分化严重,而且微小瑕疵的样本数量多。针对瑕疵的长宽比例不均匀,我们采用faster R-CNN两阶段来适应更多的长宽比。在单模的网络中我们首先会将图片缩小两倍之后才作为网络的输入。主干网络选取的是Resnet-101,在整个卷积的过程中,提取到特征的大小相对于输入图片是缩小了16倍。也就是说,从原图到最后一层的卷积特征,空间大小一共下降了32倍。 由于之后每一个候选框特征会被缩放到7x7的大小,如果说本身缩放前的特征就非常的小,那么缩放之后的特征是不具有判别力的。我们统计了一下数据集中边长<=64的样本,发现这类小样本占了整个数据集的10%,这会严重地影响性能。为了解决这个问题,我们采用了特征金字塔结构(FPN)来对网络进行改进。把高层特征做2倍上采样(最近邻上采样),然后将其和对应的前一层特征结合(前一层要经过$1\times 1$的卷积核才能用,目的是改变channels,使之和后一层的channels相同),结合方式就是element-wise相加的操作。最后,使用$3\times 3$的卷积核去处理已经融合的特征图(为了消除上采样的混叠效应),以生成最后需要的特征图。我们总结了一下,特征金字塔在这个任务中具有两个优点:第一,在卷积神经网络中,高层的特征具有强的语义信息,低层的特征具有结构信息,因此 将高低层的信息进行结合,是可以增强特征的表达能力的。第二,我们将候选框产生和提取特征的位置分散到了特征金字塔的每一层,这样可以增加小目标的特征映射分辨率,对最后的预测也是有好处的。
Faster RCNN+FPN网络
考虑到模型训练时间,实际的资源消耗以及网络的性能,采用多尺度训练,一方面增强了网络在多个尺度上的表现能力,另一方面提升了模型的泛化能力及稳定性。预先定义几个固定的尺度[(960,1024),(720,960),(640,800)],每个epoch随机选择一个尺度进行训练。
考虑到小瑕疵的定位不准确导致误检,采用RoiAlign替代RoiPooling,取消量化误差,即遍历每一个候选区域,保持浮点数边界不做量化;将候选区域分割成$k\times k$个单元,每个单元的边界也不做量化;从而将整个特征聚集过程转化为一个连续的操作来细化检测框。
在测试阶段采用soft NMS替换NMS精细化检测框。
NMS:候选框的得分进行排序,选取得分最高的那个框,接下来计算其他框与当前框的重合程度(IOU),如果重合程度大于一定的阈值就删除,这样不停的迭代下去就会得到所有想要找到的目标物体的区域。
而soft nms只是将重叠度大于一定的阈值进行抑制,最后再按分数过滤,从而使得重叠的物体被更大程度的保留下来。

比赛的评价指标是什么?如何计算的?

比赛的评价指标是参照PASCALVOC的评估标准IOU=0.5,计算10类瑕疵的mAP值作为最终的分数。本次大赛计算mAP时,对同一个ground-truth框,重复预测n次,取置信度(confidence)最高的预测框作为TP(true positive)样本,其余的n-1个框都作为FP(False positive)样本进行处理。大赛参照2010年之后的PASCAL VOC评分标准,检测框和真实框的交并比(IOU)阈值设定为0.5,同时,采用Interpolating all points方法插值获得PR曲线,并在此基础上计算mAP的值。
mAP计算过程:
1)mAP是对各目标类计算得到的AP取平均值
2)某类目标(假设为car)的AP可以理解为car类PR曲线(Precision-Recall)下的面积。
3)为了画出car的PR曲线,要对测试集上所有预测出的类别为car的bounding box按照置信度降序排列,然后标记每个bounding box是TP(True Positive)还是FP(False Positive),并分别对TP和FP的数目进行累计,计算相应的Precision和Recall。Precision是指在所有的预测中有多少是正确的, P=TP/(TP+FP)=TP/(all detections);Recall是指在所有正确的(目标检测中指GT)中有多少被预测出来了,R=TP/(TP+FN)=TP/(all GTs)。
4)TP和FP的判断:VOC中对每个类别为car的bounding box,计算该bounding box与其所在的image中所有的GT的IoU(交并比),如果其中最大的IoU>=0.5,则记为TP;否则记为FP(包括IoU都为0的情况)。

论文《基于注意增强的密集图卷积网络的人体行为识别》

论文介绍

论文的研究方向是基于骨骼关键点的人体行为识别,是对视频中人体动作进行分类。论文的创新点主要有两点,首先现有的分层图卷积网络在聚合节点更宽范围的领域信息时,在长期扩散中弱化了局部邻域的特征,针对这个问题,我们提出了残差图卷积操作,增强局部领域的特征,接着利用密集连接结合通道注意模块重用不同模块间的上下文信息,增强节点的全局和局部特征。其次,在现有的算法认为所有的节点和帧信息对行为的判断具有相同的贡献,但是视频序列中存在大量的冗余的信息干扰模型的判断,带来准确率的下降,因此我们引入注意模块,通过计算不同特征间的相关性,增强有效的特征,抑制无关的特征。

创新点

残差图卷积操作,原始的图卷积操作层间传播可简化为$X^{l+1}=\delta(W^lX^lA)$,图中的邻接矩阵是根据拓扑结构预先设置的,我们认为邻接矩阵也是可以通过网络来自动学习的,为邻接矩阵中的每个元素都设置一个权重,我们提出$X^{l+1}=\delta(W^lX^l(A\cdot M)+X^l)$,使网络在学习一般的权重矩阵W外,还要学习一个邻接矩阵的权重矩阵M,使得网络可以学习节点更多的局部领域特征。并且我们将短连接将输入和输出进行逐元素求和,使得网络在聚合更宽范围领域的信息时也能更好的保留局部信息。
密集连接,网络的结构类似于densenet模型,和densenet将不同模块的信息在channel通道concate来重用上下文特征不同,在密集连接的图卷积网络中结合通道注意模块将不同模块的上下文信息逐元素求和,来降低图卷积网络层数过深容易梯度消失带来的影响,从而增强了节点的全局和局部特征。
注意模块,主要分为channel attention module和spatial-temporal attention module。通道注意模块主要是计算不同特征通道间的相关性,把重要的特征进行增强,结合密集连接可以有效增强上下文的时空特征。时空注意模块是计算不同节点和不同帧间的相关性,和通道注意模块不同,我们认为关节的重要性和时间是耦合关系,因此我们联合关节维度和时间维度来计算模块间的相关性,增强关键帧和关键关节的信息。
多流信息融合,通过关节信息计算人体骨架的二阶骨骼信息和运动信息,可以有效的补充单模态信息的不足,促进行为识别的准确率。

基于机器学习的多模态手势识别算法研究与实现

项目介绍

本科毕设项目,选取美国手势语言数据集ASL为研究对象,研究基于卷积神经网络的手势图像处理和识别方法,主要的研究多模态的手势识别方法。基于RGB图像和深度图像的联合使用,能有效提高手势识别的准确率。

项目内容

手部区域分割
首先是手部区域的分割,将RGB图像转换到YCrCb颜色空间下,其Y通道表示明亮度,即灰阶值;Cr,Cb通道则表示色度,用于描述色彩及饱和度,其中Cr反映了图像信号红色部分与亮度值之间的差异,而Cb反应信号中蓝色部分与亮度之间的差异。在Cr通道下,手部区域和其他区域有较大差别,利用该特性对手部区域进行分割。采用Ostu阈值分割,将手部的前景区域提取出来。对于存在的一些噪声,通过先腐蚀再膨胀操作,可以去除其中的一些毛边。去除毛边后我们从中挑选出最大的contour,基本可以得到手部的前景区域。其次,上述的算法在存在人脸肤色干扰时,不能得到干净的手部区域,故采用深度图像来去除人脸肤色的干扰。将提取的人体肤色的mask和深度图像相乘,去除背景的干扰,然后利用k-means聚类算法从深度图像中分离出人脸区域的位置,只保留手部的区域。将手部的mask和RGB图像相乘,然后利用VGG16网络进行手势动作的分类,有效提高了手势识别的精确性和鲁棒性。
Ostu算法:假定图像包含两类像素(前景像素和背景像素),直方图为双峰直方图,然后计算使得两类像素能分开的最佳阈值(类内方差),或等价的间类间方差最大。

自我介绍

(一分钟版本)
您好,很感谢你在百忙中抽出时间给我这次面试的机会。我叫高信凯,今年25岁,是北京邮电大学信息与通信工程专业2018级硕士研究生,想要应聘的职位是xxx。本科阶段由于我保送到北邮读研究生,在毕设期间参加了实验室关于手势识别方面的项目,坚定了我从事这方面工作的决心。在2018年9月份我和同学组队参加了天池的铝材瑕疵检测比赛,激烈的比赛使我对视觉领域有了更深入的认识和了解。后来我开始负责实验室的智能辅助健身锻炼系统的项目,参与并完成了整个平台的设计和搭建过程,在深入的研究过程中进一步发掘出自己的研究方向,因此我决定将计算机视觉方向作为我未来事业的起点。我非常喜欢贵公司的这个职位,相信它能充分满足我的兴趣并体现自身的价值,我也有信心有能力做好这份工作。非常感谢贵公司给与我这次面试学习的机会,谢谢。
(简短版本)
您好,我叫高信凯,今年25岁,是北京邮电大学信息与通信工程专业2018级硕士研究生,想要应聘的职位是xxx。我非常喜欢贵公司的这个职位,相信它能充分满足我的兴趣并体现自身的价值,我也有信心有能力做好这份工作。非常感谢贵公司给与我这次面试学习的机会,谢谢。

平常如何学习

在研究生阶段,除了学科知识的学习外,在课余阶段我通常以问题导向型进行知识的积累。比如在学习视觉算法中分类问题的时候,我先开始了解经典的网络模型,学习不同的网络模型的思路及创新点,然后查阅各种资料论文关注模型的参数设置,损失函数的收敛等等知识,从而是自己能够高效的学习该方面的知识。

上过哪些CV课程

研究生课程:视频大数据的机器学习、模式识别与机器学习、神经网络与模糊系统、图像分析与机器视觉
自学课程:吴恩达教授的CS229网课、李飞飞教授的CS231n网课。

项目中遇到的重大问题

在完成国家电网能力开发平台时,由于项目是采用的基于RPC的 HSF协议进行模块之间的通信。HSF是阿里内部使用的模块间通信的方式,我之前没有使用过,通过短期的学习能力,调通分布式模块之间通信的问题,在这个项目即将上线部署的时候,我们需要到电网公司内部进行压力测试,我遇到的比较大的一个难题是项目上线部署后各个模块之间无法通信。最后通过分析Edas平台各个服务IP地址和服务端口,以及redis日志文件,判断到最后是因为一个模块之中的redis地址出错导致没有启动redis。引发的后续通信的问题。体现了排错能力和自主解决问题的能力。

项目如何跟别人合作

在项目合作过程中,小组成员集体讨论项目需求,提出具体的项目方案后,根据每人的研究方向和动手能力分配工作量,并统一使用github进行代码的托管和维护。

有什么想问的(一面、二面、三面分别问什么)

请问我应聘的这个职位所在团队面对着怎样的问题和挑战?另外,贵公司对这个职务有怎样的期待呢?
在刚入职的两三个月里,公司更希望这个岗位的信任把工作重心放在哪个方向呢?
公司针对实习生的培养机制是怎样的呢?团队的技术栈是什么样的?在去实习前应该重点学习哪些技术呢?

基于姿态估计的康复健身平台

大纲

  • 系统架构
    • 前端设计
    • 后台架构
  • 算法模块
    • 2D姿态估计
    • 3D姿态估计
    • 动作识别

项目介绍

本项目来源是北京邮电大学的创新创业项目,主要目的是为了搭建基于人体姿态估计算法的平台,为用户在家中使用普通的2D摄像头,辅助进行健身和康复训练提供检测和指导。

系统架构

系统架构
摄像头采集图片,每秒采集10帧,将视频帧(600, 400)转为base64编码格式,和用户的user-token、任务的task-id一起封装成Json对象。然后通过stomp通信协议,利用activemq消息中间件进行前端和后台之间的信息传递。前端模拟生产者发送消息,通过设定的topics发送到对应的队列Queues中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var client, destination, login, passcode;
url = "ws://10.103.238.165:61614";
destination = "/queue/video";
login = "admin";
passcode = "password";

client = Stomp.client(url);
'''
timer = setInterval(
function () {
ctx.drawImage(video, 0, 0, 600, 400);
var data = canvas.toDataURL('image/jpeg', 1.0); //将获取到的图像转换为base64编码
//newblob = dataURItoBlob(data);
//添加状态判断,当为OPEN时,发送消息
//var message = {};
client.send(destination, {'user-token': 123, 'task': 0x01}, data);//发送消息, 0x02 -> 0000 0010
//接收图片时间
var timestamp = new Date().getTime();
}, 100);

后台是是SpringBoot+Mybatis框架搭建,通过模拟一个消费者,应用监听器监听消息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@JmsListener(destination = "video",containerFactory = "jmsListenerContainerQueue")
public void processImage(Message message) {
// if (message instanceof BytesMessage) {
BytesMessage bytesMessage = (BytesMessage)message;
try {
// 得到一些参数:
String user_token = String.valueOf(bytesMessage.getByteProperty("user-token")); // user-token
String task = String.valueOf(bytesMessage.getByteProperty("task")); // task
String image_base64 =""; // origin image
UUID uuid= UUID.randomUUID();
String imageID = uuid.toString(); // imageID

byte[] buffer = new byte[1024*1024];
int len = 0;
while((len=bytesMessage.readBytes(buffer))!=-1){
image_base64 = new String(buffer,0,len);
}

Jedis jedis = jedisPool.getResource();
Map<String,String> imageData = new HashMap<String, String>();
imageData.put("imageID",imageID);
imageData.put("userToken",user_token);
imageData.put("taskList",task);
imageData.put("image",image_base64);

JSONObject jsonObject = JSONObject.fromObject(imageData);

// 比较task列表并分发存入对应的redis的list
int taskToDo = Integer.parseInt(task);
if((taskToDo&0x01)>0) { //pose
jedis.rpush("image_queue_to_pose_estimation", jsonObject.toString());
}
if((taskToDo&0x02)>0){ //face
jedis.rpush("image_queue_to_face_recognition", jsonObject.toString());
}
if((taskToDo&0x04)>0){ //object
jedis.rpush("image_queue_to_object_recognition", jsonObject.toString());
}

//手动释放资源,不然会因为jedisPool里面的maxActive=200的限制,只能创建200个jedis资源。
jedis.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}

后台将前端发送的Json对象解析出来,为了保证每张图片不重复,使用UUID随机生成图片ID(image_id),将image_id、user_token、task_id和image_base64放入Hashmap中,转成Json对象存入redis中,通过redis的发布/订阅模式,和服务器算法进行信息传输。算法一直运行在服务器上,订阅redis的频道channel,来监听redis中存储的消息。将消息中的Json对象解析出来后,将base_64编码的图片数据解码转变成numpy矩阵类型,送入深度学习模型,将得到的human对象的list,human对象里包含BodyParts,是每个对象的x,y坐标和置信度值。然后human对象封装成Json对象,通过发布新的通道存储在redis中,业务后台同样订阅该通道,将算法模块返回的结果通过websocket方式提供给前端展示。

消息中间件

消息中间件事一种夸进程的通信方式,这种方式使得系统之间上下游进行了逻辑和物理解耦合。使用消息中间件,在C/S或B/S模型中,请求可以由服务器端主动发起;消息中间件与上下游相对独立,不关心系统的技术架构,更适用于异构系统之间的通信和数据交互。

  1. ActiveMQ
    ActiveMQ支持两种消息队列模型为点对点(Point To Point)模型和发布-订阅(Publisher To Subscriber)模型。其中,点对点模型以queue作为消息的承载,消息生产者发送消息到queue中,然后消费者获取消息。对于已经被消费过的信息,queue中不再存储,虽然支持多个消费者,但一个消息只能被消费一次。发布-订阅模型,以topic承载消息,生产者发布消息到topic中,订阅该topic的多个消费者能同时接收到该消息。消息的两种传送机制为推送和拉取。推送的方式有消息中间件主动将消息发送给消费者,能处理快速即时的消息,但如果消费者处理消息能力较弱,消息推送不断,则有可能造成缓冲区的溢出;拉取的方式由消费者主动向消息中间件拉取信息,会造成消息的部分延迟。
  2. Redis
    Redis作为key-value内存型存储系统,一般作为缓存中间件使用,在某些场景下也可以作为消息中间件进行数据的缓存和传输。Redis具有基于内存的高速读写特性,支持String、List、Hash、Set、Zset五种类型的操作,支持原子性事务,还具有较为丰富的特性。将Redis作为消息中间件使用,一般利用其列表的数据结构,生产者通过Ipush来发布消息,消费者通过brpop来获取消息。此种实现方式,在Redis宕机且数据没有持久化的情况下可能造成数据的丢失,对于数据可靠性要求不是太高的场景中适用。

算法模块

2D姿态估计

CPM模型
openpose姿态估计
openpose
匈牙利算法
使用openpose获得人体的18个关键点坐标,然后输入HCN网络进行动作分类,然后使用动态时间规划进行动作匹配。
数据集:公司给的健身动作视频,每个动作是3-5s,每组动作锻炼30次,总共进行8组,每段视频都是是单一的健身指导视频。由于视频中只有单一的参与者指导动作,训练数据背景单一,估考虑采用基于骨骼关键点的行为识别。
算法流程:
1.从redis中读取图片,将base64编码转为矩阵数据
2.输入openpose算法,得到人体的18个关键点。
BODY_PARTS = { “Nose”: 0, “Neck”: 1, “RShoulder”: 2, “RElbow”:3, “RWrist”: 4, “LShoulder”: 5, “LElbow”: 6, “LWrist”: 7, “RHip”: 8, “RKnee”: 9, “RAnkle”: 10, “LHip”: 11, “LKnee”: 12, “LAnkle”: 13, “REye”: 14, “LEye”: 15, “REar”: 16, “LEar”: 17, “Background”: 18 }
3.将得到的17个关键点输入到HCN网络进行动作分类。
关键点:在训练动作分类网络时,可以对关键点信息进行数据增强。
1)归一化处理,将其他通道求平均,只保留C和V通道求数据的均值和方差,然后对每一个数据进行归一化处理。
2)时间片段选取,在150帧时间序列中选择有效的64帧
3)坐标旋转变换,随机设一个角度,对构建旋转矩阵,对关键点进行旋转变换。
CNN 模型在提取高层面信息方面能力出色,并且也已经被用于根据骨架学习空间-时间特征。这些基于 CNN 的方法可以通过将时间动态和骨架关节分别编码成行和列而将骨架序列表示成一张图像,然后就像图像分类一样将图像输入 CNN 来识别其中含有的动作。但是,在这种情况下,只有卷积核内的相邻关节才被认为是在学习共现特征。尽管感受野(receptive field)能在之后的卷积层中覆盖骨架的所有关节,但我们很难有效地从所有关节中挖掘共现特征。由于空间维度中的权重共享机制,CNN 模型无法为每个关节都学习自由的参数。这促使我们设计一个能获得所有关节的全局响应的模型,以利用不同关节之间的相关性。
着眼于人的行为动作的特点,我们将行为动作中关节点具有的共现性特性引入到网络设计中,将其作为网络参数学习的约束来优化识别性能。人的某个行为动作常常和骨架的一些特定关节点构成的集合,以及这个集合中节点的交互密切相关。如要判别是否在打电话,关节点“手腕”、“手肘”、“肩膀”和“头”的动作最为关键。不同的行为动作与之密切相关的节点集合有所不同。例如对于“走路”的行为动作,“脚腕”、“膝盖”、“臀部”等关节点构成具有判别力的节点集合。我们将这种几个关节点同时影响和决定判别的特性称为共现性(Co-occurrence)。
我们提出了一种端到端的共现特征学习框架,其使用了 CNN 来自动地从骨架序列中学习分层的共现特征。我们发现一个卷积层的输出是来自所有输入通道的全局响应。如果一个骨架的每个关节都被当作是一个通道,那么卷积层就可以轻松地学习所有关节的共现。更具体而言,我们将骨架序列表示成了一个形状帧×关节×3(最后一维作为通道)的张量。我们首先使用核大小为 n×1 的卷积层独立地为每个关节学习了点层面的特征。然后我们再将该卷积层的输出转置,以将关节的维度作为通道。在这个转置运算之后,后续的层分层地聚合来自所有关节的全局特征。

我们提出的分层式共现网络(HCN:Hierarchical Co-occurrence Network)的概况。绿色模块是卷积层,其中最后一维表示输出通道的数量。后面的「/2」表示卷积之后附带的最大池化层,步幅为 2。转置层是根据顺序参数重新排列输入张量的维度。conv1、conv5、conv6 和 fc7 之后附加了 ReLU 激活函数以引入非线性。
4.动作匹配,采用动态时间规划(DTW)算法,判断用户的运动视频和标准动作是否匹配。
DTW介绍
比较在该段时间内用户的姿态的(x,y)坐标点与标准模板的相似度进行评分。我们需要构造一个nxm的矩阵网格,矩阵元素(i, j)表示qi和cj两个点的距离d(qi, cj)(也就是序列Q的每一个点和序列C的每一个点之间的相似度,距离越小则相似度越高。)我们定义一个累加距离cumulative distances。从(0, 0)点开始匹配这两个序列Q和C,每到一个点,之前所有的点计算的距离都会累加。到达终点(n, m)后,这个累积距离就是我们上面说的最后的总的距离,也就是序列Q和C的相似度。