-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcontent.json
More file actions
1 lines (1 loc) · 133 KB
/
Copy pathcontent.json
File metadata and controls
1 lines (1 loc) · 133 KB
1
{"pages":[],"posts":[{"title":"DBSCAN聚类","text":"DBSCAN(Density-Based Spatial Clustering of Applications with Noise,具有噪声的基于密度的聚类方法)是一种很典型的密度聚类算法,和K-Means,BIRCH这些一般只适用于凸样本集的聚类相比,DBSCAN既可以适用于凸样本集,也可以适用于非凸样本集。下面我们就对DBSCAN算法的原理做一个总结。 1. 密度聚类原理DBSCAN是一种基于密度的聚类算法,这类密度聚类算法一般假定类别可以通过样本分布的紧密程度决定。同一类别的样本,他们之间的紧密相连的,也就是说,在该类别任意样本周围不远处一定有同类别的样本存在。 通过将紧密相连的样本划为一类,这样就得到了一个聚类类别。通过将所有各组紧密相连的样本划为各个不同的类别,则我们就得到了最终的所有聚类类别结果。 2. DBSCAN密度定义在上一节我们定性描述了密度聚类的基本思想,本节我们就看看DBSCAN是如何描述密度聚类的。DBSCAN是基于一组邻域来描述样本集的紧密程度的,参数$(ϵ, MinPts)$用来描述邻域的样本分布紧密程度。其中,$ϵ$描述了某一样本的邻域距离阈值,$MinPts$描述了某一样本的距离为$ϵ$的邻域中样本个数的阈值。 假设我的样本集是$D=(x1,x2,…,xm)$,则DBSCAN具体的密度描述定义如下: 1) $ϵ$-邻域:对于x_j∈Dxj∈D,其$ϵ$-邻域包含样本集D中与$x_j$的距离不大于$ϵ$的子样本集,即$Nϵ(x_j)={x_i∈D|distance(x_i,x_j)≤ϵ}$, 这个子样本集的个数记为$|Nϵ(x_j)|$ 2) 核心对象:对于任一样本$x_j∈D$,如果其$ϵ$-邻域对应的$Nϵ(x_j)$至少包含$MinPts$个样本,即如果$|Nϵ(x_j)|≥MinPts$,则$x_j$是核心对象。 3)密度直达:如果$x_i$位于$x_j$的$ϵ$-邻域中,且$x_j$是核心对象,则称$x_i$由$x_j$密度直达。注意反之不一定成立,即此时不能说$x_j$由$x_i$密度直达, 除非且$x_i$也是核心对象。 4)密度可达:对于$x_i$和$x_j$,如果存在样本样本序列$p1,p2,…,pT$满足$p1=x_i,pT=x_j$, 且$pt+1$由$pt$密度直达,则称$x_j$由$x_i$密度可达。也就是说,密度可达满足传递性。此时序列中的传递样本$p1,p2,…,pT−1$均为核心对象,因为只有核心对象才能使其他样本密度直达。注意密度可达也不满足对称性,这个可以由密度直达的不对称性得出。 5)密度相连:对于$x_i$和$x_j$,如果存在核心对象样本xkxk,使$x_i$和$x_j$均由xkxk密度可达,则称$x_i$和$x_j$密度相连。注意密度相连关系是满足对称性的。 从下图可以很容易看出理解上述定义,图中$MinPts=5$,红色的点都是核心对象,因为其$ϵ$-邻域至少有5个样本。黑色的样本是非核心对象。所有核心对象密度直达的样本在以红色核心对象为中心的超球体内,如果不在超球体内,则不能密度直达。图中用绿色箭头连起来的核心对象组成了密度可达的样本序列。在这些密度可达的样本序列的$ϵ$-邻域内所有的样本相互都是密度相连的。 有了上述定义,DBSCAN的聚类定义就简单了。 3. DBSCAN密度聚类思想DBSCAN的聚类定义很简单:由密度可达关系导出的最大密度相连的样本集合,即为我们最终聚类的一个类别,或者说一个簇。 这个DBSCAN的簇里面可以有一个或者多个核心对象。如果只有一个核心对象,则簇里其他的非核心对象样本都在这个核心对象的$ϵ$-邻域里;如果有多个核心对象,则簇里的任意一个核心对象的$ϵ$-邻域中一定有一个其他的核心对象,否则这两个核心对象无法密度可达。这些核心对象的$ϵ$-邻域里所有的样本的集合组成的一个DBSCAN聚类簇。 那么怎么才能找到这样的簇样本集合呢?DBSCAN使用的方法很简单,它任意选择一个没有类别的核心对象作为种子,然后找到所有这个核心对象能够密度可达的样本集合,即为一个聚类簇。接着继续选择另一个没有类别的核心对象去寻找密度可达的样本集合,这样就得到另一个聚类簇。一直运行到所有核心对象都有类别为止。 基本上这就是DBSCAN算法的主要内容了,是不是很简单?但是我们还是有三个问题没有考虑。 第一个是一些异常样本点或者说少量游离于簇外的样本点,这些点不在任何一个核心对象在周围,在DBSCAN中,我们一般将这些样本点标记为噪音点。 第二个是距离的度量问题,即如何计算某样本和核心对象样本的距离。在DBSCAN中,一般采用最近邻思想,采用某一种距离度量来衡量样本距离,比如欧式距离。这和KNN分类算法的最近邻思想完全相同。对应少量的样本,寻找最近邻可以直接去计算所有样本的距离,如果样本量较大,则一般采用KD树或者球树来快速的搜索最近邻。如果大家对于最近邻的思想,距离度量,KD树和球树不熟悉,建议参考之前写的另一篇文章K近邻法(KNN)原理小结。 第三种问题比较特殊,某些样本可能到两个核心对象的距离都小于$ϵ$,但是这两个核心对象由于不是密度直达,又不属于同一个聚类簇,那么如果界定这个样本的类别呢?一般来说,此时DBSCAN采用先来后到,先进行聚类的类别簇会标记这个样本为它的类别。也就是说BDSCAN的算法不是完全稳定的算法。 4. DBSCAN聚类算法下面我们对DBSCAN聚类算法的流程做一个总结。 输入:样本集$D=(x1,x2,…,xm)$,邻域参数$(ϵ,MinPts)$, 样本距离度量方式 输出: 簇划分$C$. 1)初始化核心对象集合$Ω=∅$, 初始化聚类簇数$k=0$,初始化未访问样本集合$Γ = D$, 簇划分$C = ∅$ 2) 对于$j=1,2,…m$, 按下面的步骤找出所有的核心对象: a) 通过距离度量方式,找到样本$x_j$的$ϵ$-邻域子样本集$Nϵ(x_j)$ b) 如果子样本集样本个数满足$|Nϵ(x_j)|≥MinPts$, 将样本$x_j$加入核心对象样本集合:$Ω=Ω∪{x_j}$ 3)如果核心对象集合$Ω=∅$,则算法结束,否则转入步骤4. 4)在核心对象集合$Ω$中,随机选择一个核心对象$o$,初始化当前簇核心对象队列$Ωcur={o}$, 初始化类别序号$k=k+1$,初始化当前簇样本集合$Ck={o}$, 更新未访问样本集合$Γ=Γ−{o}$ 5)如果当前簇核心对象队列$Ωcur=∅$,则当前聚类簇$Ck$生成完毕, 更新簇划分$C={C1,C2,…,Ck}$, 更新核心对象集合$Ω=Ω−Ck$, 转入步骤3。 6)在当前簇核心对象队列$Ωcur$中取出一个核心对象$o′$,通过邻域距离阈值$ϵ$找出所有的$ϵ$-邻域子样本集$Nϵ(o′)$,令$Δ=Nϵ(o′)∩Γ$, 更新当前簇样本集合$Ck=Ck∪Δ$ 更新未访问样本集合$Γ=Γ−Δ$, 更新$Ωcur=Ωcur∪(Nϵ(o′)∩Ω)$,转入步骤5. 输出结果为: 簇划分$C={C1,C2,…,Ck}$ 5. DBSCAN小结和传统的K-Means算法相比,DBSCAN最大的不同就是不需要输入类别数k,当然它最大的优势是可以发现任意形状的聚类簇,而不是像K-Means,一般仅仅使用于凸的样本集聚类。同时它在聚类的同时还可以找出异常点,这点和BIRCH算法类似。 那么我们什么时候需要用DBSCAN来聚类呢?一般来说,如果数据集是稠密的,并且数据集不是凸的,那么用DBSCAN会比K-Means聚类效果好很多。如果数据集不是稠密的,则不推荐用DBSCAN来聚类。 下面对DBSCAN算法的优缺点做一个总结。 DBSCAN的主要优点有: 1) 可以对任意形状的稠密数据集进行聚类,相对的,K-Means之类的聚类算法一般只适用于凸数据集。 2) 可以在聚类的同时发现异常点,对数据集中的异常点不敏感。 3) 聚类结果没有偏倚,相对的,K-Means之类的聚类算法初始值对聚类结果有很大影响。 DBSCAN的主要缺点有: 1)如果样本集的密度不均匀、聚类间距差相差很大时,聚类质量较差,这时用DBSCAN聚类一般不适合。 2) 如果样本集较大时,聚类收敛时间较长,此时可以对搜索最近邻时建立的KD树或者球树进行规模限制来改进。 3) 调参相对于传统的K-Means之类的聚类算法稍复杂,主要需要对距离阈值$ϵ$,邻域样本数阈值MinPts联合调参,不同的参数组合对最后的聚类效果有较大影响。","link":"/2018/09/13/DBSCAN聚类/"},{"title":"Implicit 3D Orientation Learning for 6D Object Detection from RGB Images论文笔记","text":"论文地址 注:本文谨代表笔者观点,水平有限,若有不足及疏忽之处,诚请批评指正 Abstract提出一个RGB-based的实时目标检测和6D姿态估计pipeline。这个新型3D的目标朝向估计(orientation estimation)方法基于去噪自编码器的一个变体,其采用域随机(Domain Randomization)在3D模型的模拟视图上训练,称之为Augmented Autoencoder。与现有方法相比,它的优势在于:无需真实、带姿态标注的训练数据;可泛化至多种测试传感器,并能够内在地(inherently)处理目标与视图的对称性问题。不需学习一个从输入图像到目标姿态的显式映射,本方法给出了目标朝向的隐式表征(implicit representations),由隐空间(latent space)的样本定义。基于 T-LESS 和 LineMOD 数据集的实验表明所提的方法不仅比类似的基于模型的方法有更好的表现,而且表现也接近目前顶级的、需要真实的姿态标注图像的方法。 1 Introduction现有方法的劣势: 对于像目标遮挡、不同的杂乱背景(background clutter)和环境的动态变化等典型难题,不够鲁棒 通常需要目标的特定属性(如足够的表面纹理结构、非对称形状)来避免混淆 运行效率不理想,not efficient in带标注训练数据的数量 本文的方法在单张RGB图像上操作,由于不需深度信息,大大提高了其可用性。同时也注意到,深度图可选择性地合并,以改进估计结果。算法第一步应用SSD做目标检测,获得目标的bbox和identifiers。接着,在生成的scence crops上,使用本文提出的3D朝向估计算法,基于一个预先训练好的深度网络架构。虽然现有方法也在使用深度网络,但是在训练过程中,本算法并未显式地从3D姿态标注中学习 ,而是隐式地从渲染的3D模型中学习表征。这个学习过程利用域随机策略,训练了一个generalized版本的去噪自编码器,称之为Augmented Autoencoder(AAE)。这个方法的优势如下: 训练过程无需用特殊正交群SO(3)表示的物体朝向,由于避免了从图像到物体朝向间的一对多映射,本方法可以解决由对称视图引起的姿态模糊不清问题 本方法学习到的表征,特别地编码了3D朝向,故而在面对遮挡、杂乱背景等问题有着不错的鲁棒性,同时对于不同的环境和传感器也有着良好的泛化性 AAE不需要任何真实的姿态标记好的训练数据,而是以一个自监督的方式训练,完成对3D模型视图的编码。 整个算法的原理图如下: 2 Related WorkDepth-based方法(如使用Point Pair Features,PPF)在多个数据集上验证了其在姿态估计问题上的鲁棒性,但是通常依赖对许多pose hypotheses进行评估,计算负载较大。另外与RGB相机相比,现有的深度传感器通常对阳光或特定物体表面敏感度更高。 相较于传统的2D目标检测,对6D物体姿态的真实图像进行标注需要耗费成数量级增加的精力,要求标注者有专业的知识和一个复杂的setup。大部分姿态估计算法使用真实标注图像,不过也因此被限制在pose-annotated datasets 有些研究也已经提出从3D模型渲染得到合成图像,不费吹灰之力就得到了大量的数据集。但是,单纯在合成图像上训练往往不能泛化到真实的测试图像。因此,问题的关键在于将模拟视图与真实拍照之间的domain gap,想办法连结 2.1 Simulation to Reality Transfer从合成数据到真实数据的转换,存在三种主要策略: Photo-Realistic Rendering 对诸如目标检测和视角估计(viewpoint estimation)任务有着混合泛化性能。适合环境简单的场景,并在用相对小数量的真实标注图像联合训练的条件下性能不错。但该方法总是有瑕疵,也需更多精力。 Domain Adaptation (DA) 利用训练数据从source domain向target domain映射,其中包含小部分标记数据(监督DA)或未标记数据( 非监督DA)。对于非监督DA,应用GANs从合成图像生成现实图像,用以训练分类器、3D姿态检测器和grasping algorithms。在构建一个promising方法时,GANs经常生成fragile训练结果。监督DA能降低对真实标注数据的需求,但无法完全放弃。 Domain Randomization (DR) 在多种semi-realistic settings(通过随机的光照条件、背景、饱和度等增广)下渲染得到视图(由于随机增广,故亦会产生真实环境图像),再利用这些视图训练一个模型,视为hypothesis,DR构建即构建在其之上。在使用CNNs解决3D形状检测问题上,Tobin等人验证了DR的潜力。Hinterstoisser等人的工作显示,用一个纹理3D模型的随机合成视图,仅仅训练FasterRCNN的头部网络也能泛化至真实图像。必须提及的是,他们的渲染基本属于photo-realistic,纹理3D模型有着极高的质量。最近,Kehl等人首创了一个专门针对6D目标检测,端到端的CNN–‘SSD6D’,使用一个moderate DR策略来利用合成训练数据。他们将纹理3D物体以随机姿态渲染得到的视图,重建在改变过亮度和对比度的MSCOCO背景图像上。这些操作使网络能够泛化到真实图像之上,并enables 6D detection at 10Hz。跟我们一样,对于十分精确的距离估计,依赖Iterative Closest Point(ICP),利用深度数据作后处理。但是,我们不将3D朝向估计视为一个分类任务。 2.2 Learning representations of 3D orientations由于固定的SO(3)参数化会促进对特定对象表征的学习,故而我们认为训练的难点在此。 Regression 由于旋转存在于连续空间,用固定的SO(3)参数化方法(如:四元数)直接做回归似乎是理所当然的。然而,表示方法的各式约束和模糊位姿会导致收敛问题。实际中,直接回归的方法在3D物体朝向估计问题上的表现不太理想。 Classification 对3D目标朝向做分类 ,需要对SO(3)进行离散化。即使在粗粒度的区间$\\sim 5^o$下,也能产生超过50.000个可能的类。因此,每个类别在训练集上只是稀疏地出现,阻碍了收敛过程。SSD6D通过分别分类离散化视角与结构平面内的旋转,学习3D朝向信息,将复杂度降到了$\\mathcal O(n^2)$。 但对于非规范视角,例如,当从上方观察物体时,视角的变化几乎等同于结构平面内旋转的变化,这会产生模糊的类组合。总之,使用one-hot分类忽略了不同朝向间的关联。 Symmetries 3D朝向的固定表征,在面对对称性时,会产生严重的位姿歧义。如果不人工介入,同一训练图片可能被分配不同的朝向label,这会严重干扰模型的学习过程。为了处理模糊物体,大部分方法都经过了人工调整。这样的策略在依据目标调整离散化时,忽略了一个维度的旋转,并需额外再训练一个CNN来预测对称性。用这样繁冗的人工方法来提前过滤物体对称性,对于由遮挡和自我遮挡产生歧义的情景更难解决。对称性不仅影响回归和分类方法,对于任何learning-based方法也有很大影响,因为这些方法仅仅通过固定的SO(3)表征分辨目标。 Descriptor Learning 描述子学习可用来学习与目标在低维空间相关的表征。Wohlhart等人提出了一个CNN-based描述子学习方法,使用了triplet-loss。尽管也混合进了合成数据,训练过程仍然依赖pose-annotated传感器数据。而且,该方法不能解决对称问题,因为对于像一些,看起来相同但朝向完全相反的ambiguous object,可能在loss中占主要部分。Baltnas等人在这个研究基础上,enforce描述子与位姿距离间的平衡。利用cosidered poses下,深度信息的差异,为位姿距离损失计算权重,能够很好地解决目标对称问题。这个启发式的方法(heuristic)提升了对称物体的精度,参见[40]。我们的方法也是基于学习描述子,但是我们训练了自监督Augmented Autoencoders(AAEs),其学习过程本身是与任何固定的SO(3)表征独立的。也就是说,描述子的学习仅仅基于目标视图的表观,因此对称歧义问题也被内在地顾及。描述子3D朝向的分配已是在训练结束之后了。另外,不像上述两种方法,我们的方法可以避免训练时真实标注数据的使用。 Kehl等人在LineMOD数据集上的随机RGB-D scene patches的基础上,构建了一个自编码结构。在测试过程时,场景的描述子与object patches相互比较,找寻最合适的6D 位姿。尽管这些方法需要评估很多patches, 每次预测大约需要670ms。另外,使用局部patches意味着忽略了目标特征间的全局关联,这对只有很少texture的情景是很致命的。相反,我们是在整体的目标视图上训练,显式地学习域不变性(invariance)。 3 Method以下主要介绍新型的基于AAE的3D朝向估计方法。 3.1 AutoencodersHinton等人提出的原始Autoencoder,是针对图像、音频或深度信息这样的高维数据,进行降维的的技术。它由一个编码器$\\Phi$和一个解码器$\\Psi$组成,都是任意可学习的function approximators,通常是神经网络。训练的目标是,重建通过低维bottleneck的输入$x\\in \\mathcal {R^D}$ ,被称为潜在表征$z \\in \\mathcal {R^n}$,其中$\\mathcal {n \\ll D}$: $$\\hat x = (\\Psi \\circ \\Phi)(x) = \\Psi(z) \\tag 1$$ per-sample损失函数是pixel-wise L2距离的单纯求和: $$\\ell_2=\\sum_{(i\\in \\mathcal D)}||x_{(i)} - \\hat x_{(iu)}||_2 \\tag 2$$ 得到的latent space举例来说,可用来做非监督聚类。Denoising Autoencoders相比之下在训练过程中由些许调整。在输入图像$x\\in\\mathcal{R^D}$上人工添加随机噪声,而重建目标却stays clean。训练的模型,可用来重建降噪后的测试图像。但是,latent表征在其中产生了何种影响呢? Hypothesis 1: 由于降噪自编码器生成的latent表征有助于重建去噪图像,故它对噪声是不变(invariant)的。 我们将会论证,这样的训练策略实际不仅仅加强了对抗噪声的不变性,也在对抗大量不同的输入扩增时保持良好的不变性。最终,它使模拟数据与真实数据之间的domain gap得以连接。 3.2 Augmented AutoencoderAAE的初衷是为了控制latent表征编码过程中,哪些属性保留,哪些丢掉。我们对输入图像$x\\in\\mathcal{R^D}$做随机增广$f_{augm}(.)$,其编码当为不变量。重建的目标仍以eq.(2)衡量,但是eq.(1)变为: $$\\hat x = (\\Psi \\circ \\Phi \\circ f_{augm})(x) = (\\Psi \\circ \\Phi )(x’) = \\Psi(z’) \\tag 3$$ 为了证明Hypothesis 1适用于几何变换,我们在不同尺度、平面内平移和旋转描绘一个正方形的许多二值图像上,学习latent表征。我们的目的是将平面内的旋转$r \\in [0, 2\\pi]$编码在2维latent空间 $z\\in\\mathcal R^2$,编码要能与缩放或者平移独立。Fig.3说明了训练一个CNN-based AE框架(与Fig.5的模型相似)的结果。在固定尺度和平移(1)或随机尺度和平移(2)训练AEs来重建正方形,可以观察到,AEs并不能单独地清楚编码旋转信息,而且对其它latent因素也比较敏感。相反,AAE的编码下,所有朝向一致的正方形也被map到了相同的编码下,对平移和尺度有着不变性。另外,latent表征也更加平滑,latent维度也分别imitate一个频率为$f =\\frac 4{2\\pi}$的shifted sin和cos函数(latent dimensions imitate a shifted sine and cosine function with frequency $f =\\frac 4{2\\pi}$ respectively)。原因在于,正方形有两条互相垂直的对称轴。例如,当旋转$\\frac {\\pi}{2}$之后,正方形看起来就一样了。在3D目标朝向问题上避免由于对称引起的混淆问题,用基于目标appearance而不是固定的parametrization来表征朝向显得尤为有价值。 3.3 Learnig 3D Orientation from Sythetic Object Views我们的toy problem显示,可以利用一个几何扩增技术,显式地学习目标的平面内的旋转。使用相同的几何输入增强方法,我们可以从3D目标模型(CAD or 3D重建)把整个SO(3)空间编码,且能对不准确的目标检测结果有着良好的鲁棒性。然而,编码器仍然无法与从真实的RGB传感器crop得到的图像关联,原因如下: 3D模型与真实目标间的差异 模拟与真实光照条件之间的差异 网络无法从背景杂乱与前景遮挡分辨目标 为了使编码过程对于无关的环境信息、传感器差异等保持不变性,我们在AAE框架下提出了一个域随机(Domain Randomization, DR)技术,而不是尝试在模拟中模仿特定真实传感器记录的每个细节。其目标是训练编码器将不同相机图像的差异,视为另一个无关的偏差。因此,当保持重建目标clean,我们向训练视图的输入,随机地作额外增广: 在随机光源位置和随机反射与漫反射的条件下(simple Phong model in OpenGL),渲染视图 嵌入Pascal VOC数据集中的随机图像,作为背景 变化图像对比度、亮度、高斯模糊和颜色扭曲 使用随机物体mask或者黑色正方形产生遮挡 Fig.4展示了一个典型训练过程,训练数据为T-LESS数据集中object 5的合成视图。 3.4 Network Architecture and Training Details我们实验中采用的卷积自编码框架如Fig.5所示。我们使用了一个bootstrapped pixel-wise L2损失函数,仅仅在最大误差的像素点上计算loss(每张图片的bootstrap因子 b=4)。因此,重建出了更好的细节,训练过程也没有收敛到局部最优。在OpenGL的帮助下,我们统一为每个目标沿相机轴恒定距离(700mm),以随机3D朝向渲染了20000张视图。将得到的图像进行二次裁剪,并将尺寸调整为$128\\times 128\\times 3$,如Fig.4所示。在随机光照渲染之外,对输入的所有几何和颜色增广在训练时在线应用,采用uniform random strength,参数详情见补充从材料。优化器采用了Adam,lr为$2\\times10^{-4}$,Xavier初始化,batch size为64,30000次迭代。在一张Nvidia Geforce GTX 1080上需$\\sim 4$ 小时。 3.5 Codebook Creation and Test Procedure训练之后,AAE便有能力从许多不同摄像头传感器(Fig.8)裁切得到的真实场景提取3D目标。编码器重建的清晰程度和朝向,能够表明编码质量的高低。为了从测试场景裁切图像上确定3D目标朝向,我们创建了 一个codebook(Fig.6 (top)): 在完整的view空间,用等距的视角渲染clean、合成的目标视图 以固定间隔在每个视图中旋转,以覆盖整个SO(3) 通过对所有结果图像生成latent codes $z \\in \\mathcal{R}^{128}$,和设定相应旋转$R_{cam2obj}\\in \\mathcal R^{3x3}$ 创建一个codebook 测试过程中,在RGB场景中先检测出研究目标,目标所在区域被二次裁剪并resize,使其满足encoder的input size。在编码之后,计算测试编码$z_{test}\\in \\mathcal R^{128}$与codebook中的所有编码$z_i\\in \\mathcal R^{128}$间的余弦相似度: $$cos_i = \\frac {z_iz_{test}}{||z_i||||z_{test}||} \\tag 4$$ 利用kNN搜索最高相似度,返回从codebook中得到对应的旋转矩阵$\\{R_{kNN}\\}$,作为3D目标朝向的估计。我们采用余弦相似度的原因是: 即使非常大的codebook,也能在一块GPU上有效计算。在实验中,我们有2562个等距视角$\\times$36个平面内旋转$=$总共92232项。 可观察到,可能是由于旋转的圆形性质,对测试latent code的缩放操作,并不改变decoder重建的目标朝向(Fig.7) 3.6 Extending to 6D Object DetectionTraining the Oabject Detector我们finetune了VGG16 base的SSD,所用图像是将LineMOD和T-LESS的训练集的目标,从不同视角得到的recordings嵌在了黑色背景上。我们也训练了backbone为ResNet50的RetinaNet,虽然慢些但是有更高的精度。多个物体以随机朝向、尺度和平移,复制在了一个场景下,并以此调整BBox的标注。至于AAE,黑色背景要用Pascal VOC图像替换。在训练60000个场景后,引入各种颜色和几何增广。 Projective Distance Estimation我们预估完整的从相机到物体中心的3D变换$t_{pred}$,跟[17]一样。因此,对codebook中每个合成的目标视图,保存其2D BBox的对角线长度$l_{syn, i}$。测试时,计算检测的BBox对角线长度$l_{test}$与对应codebook中(如相似朝向)对角长度$l_{syn,max_cos}$之间的比率。针孔相机模型产生的距离估计$t_{pred,z}$为: $$t_{pred,z } = t_{syn, z} \\times \\frac{l_{syn,max_cos}}{l_{test}} \\times \\frac {f_{test}}{f_{syn}} \\tag 5$$ 其中,$t_{syn,z}$为synthetic recdering distance,$f_{test}, f_{syn}$分别为测试传感器和渲染视图的焦距。 $$\\begin{pmatrix} t_{pred,x} \\ t_{pred,y} \\end{pmatrix} = \\frac{t_{pred,z}}{f_{test}} \\begin{pmatrix} (bb_{cent,test,x}-p_{test,x})-(bb_{cent,syn,x}-p_{syn,x}) \\ (bb_{cent,test,y}-p_{test,y})-(bb_{cent,syn,y}-p_{syn,y}) \\end{pmatrix} \\tag 6$$ 其中,$p_{test},p_{syn}$为主点(principal points),$bb_{cent,syn}$、$bb_{cent,syn}$为BBox中心。与[17]相比,我们可以预测不同的test intrinsics的3D变换。 ICP Refinementoptionally,评估结果在深度数据上进行微调,使用标准的ICP方法在CPU需要$\\sim 200ms$。详见补充材料 Inference TimeVGG16 base的SSD、31个类别加上codebook大小为$92232\\times128$的AAE(Fig.5),平均的inference times如Table 1所示。RGB-based pipeline可以在Nvidia GTX 1080上以$\\sim 42Hz$达到实时处理。这使AR和机器人应用有更多的余地留给tracking算法。多个编码器和对应codebooks可以放入显存,可以实现多物体的姿态预估。 4 Evaluation我们在T-LESS和LIneMOD数据集上,测试了AAE和整个6D检测pipeline。 4.1 Test Conditions一些RGB-based位姿估计方法仅依赖于3D模型的信息。大部分方法使用了真实的标注数据,甚至测试时采用了与训练时相同的场景(如,在轻微不同的视角下)。通常的做法是,忽略in-plane旋转或者仅考虑在数据集中出现的目标位姿,这样的做法限制了其适用性。对称目标经常被单独看待,或者直接忽略。SIXD challenge尝试通过禁止使用测试集场景pixels的使用,以一个更公平的方式来比较6D localization算法。我们遵循他们这些严格的评估标准来处理难度更高的6D detection问题,而6D detection问题的特点是,不知道场景中存在哪些研究对象。T-LESS数据集中的目标十分相似,故对于T-LESS来说尤为困难。 4.2 MetricsVisible Surface Discrepancy ($err_{vsd}$)是一个anbiguity-invariant pose error function,由预估的和ground truth的visible object depth surface间的距离来决定。正如SIXD challenge的guideline,在偏差$\\tau = 20mm$和object visibility$>10\\%$的前提下,我们记录$err_{vsd} <0.3$的正确6D目标位姿的召回率。尽管Average Distance of Model Points(ADD)不能反映位姿歧义的问题,我们仍然将其记录,因为LIneMOD dataset遵循[11]的protocol($k_m = 0.1$)。那些对称的物体,[11]计算了到最近model point的平均距离。在我们的ablation studies里,也测试了$AUC_{vsd}$。 4.3 Ablation Studies为了单独评估AAE,这一节我们仅仅预测T-LESS数据集(on Primesense and Kinect RGB scene crops)中的Object 5的3D朝向。Table 3展示了输入augmentations的影响。可观察到,不同的颜色扩增的影响是累积的。对于结构不清的物体,甚至色彩反转也是有益的,因为这样防止了模型过拟合到合成的颜色信息上去。另外,用T-LESS数据集中提供的真实物体,嵌在随机选取的Pascal VOC中的背景图像上,再作增广获得数据集,在这样的真实数据上训练,仅仅比在合成数据上训练有轻微的性能提升。Fig. 9a描述了用不同latent space size下的3D pose estimation的准确率,结果显示$dim=64$时性能最优。Fig. 9b证明了,我们的Domain Randomization策略甚至可以泛化使用untextured CAD模型。 4.4 6D Object Detection首先,我们测试的RGB-only的结果包含了2D detection、3D orientation estimation和projective distance estimation。虽然这些结果看起来不错,还是用了简单的cloud-based ICP方法对distance estimation进行微调,再与目前state-pf-the-art depth-based方法进行比较。Table 4展示了在T-LESS数据集(该数据集包含许多pose ambiguities)上所有场景下的测试结果,我们微调之后的结果与最近Kehl等人的local patch descriptor方法相比,性能出众。","link":"/2018/09/25/Implicit-3D-Orientation-Learning-for-6D-Object-Detection-from-RGB-Images论文笔记/"},{"title":"Knowing When to Look: Adaptive Attention via A Visual Sentinel for Image Captioning","text":"论文链接:https://arxiv.org/abs/1612.01887 Abstract基于注意力的神经编码-译码框架(Attention-based neural encoder-decoder frameworks)已经在图像标注任务中广泛采用。大部分方法在生成词语时,强行令视觉注意信息发挥效力。然而,译码器在预测诸如“the”和“of”等“非视觉”(non-visual)词汇时,可能几乎不需要从图片中获取视觉信息。其它看上来像视觉词汇的词语,例如在“behind a red stop”之后的“sign”,或者“talking on a cell”之后的“phone”,经常可依赖语言模型进行可靠的预测。在本文中,我们提出了一个新的带视觉哨兵(visual sentinel)的自适应注意力模型(adaptive attention model)。在预测的每一步,我们的 模型会决定是注意图像(如果是,具体到哪块区域),还是注意视觉哨兵。模型决定了是否要聚焦于图像以及其具体区域,以为序列词语的生成提取出有效信息。在COCO和Flickr30K上的测试结果显示,我们的方法以显著优势重置了新的state-of-the-art水准。 1. Introduction自动生成图像标注问题,在学界和工业界已经成为一个著名的跨学科研究问题。它可以帮助视障人群,相对容易地去组织和浏览大量的典型非结构化的视觉数据。为了生成高质量的摘要,模型需要从图像中结合细粒度的视觉线索。最近,随着基于注意力的视觉神经编码-译码模型的研究,引入注意力机制,生成一个空间图(spatial map),标识了与每个生成的词语相关的图像区域。 在图像标注与视觉问答任务中,大部分注意力模型在每一步预测时都在关注图像,根本不考虑下一个预测(emitted)的是哪个词。然而,标注里不是所有的词都有对应的视觉信息。拿图1中的例子来说,所示图像生成的标注为“A white bird perched on top of a red stop sign”。“a”和“of”没有对应的典型视觉信息。另外,在生成诸如跟在“perched”之后的“on”和“top”时,或者跟在“a red stop”之后的“sign”时,语言之间的关联性会使预测过程不怎么需要视觉信息。事实上,非视觉词汇的梯度,会误导和减弱视觉信息在控制标注语句生成过程的整体效果。 在本文中,我们提出了一个自适应注意力编码-译码框架,能够自动决定何时依赖视觉信息、何时依赖语言模型。当然,在依赖视觉信息时,模型也决定了具体应该关注图像的哪块区域。为了提取空间图像特征,我们首次提出了一个新型的空间关注模型。然后,正如我们提出的自适应注意力机制,我们采用了一个新的LSTM扩展方法,能够生成一个额外的视觉哨兵向量,而不是一个单一的隐藏状态。视觉哨兵是一个额外的对译码器存储的隐式表示,为译码器提供了一个回退选项。我们进一步设计了一个新的哨兵门(sentinel gate),决定了译码器在生成下一词语时从图像中获取信息的多寡,而不是依赖视觉哨兵。例如图1所示,我们的模型在生成诸如“white”、“bird”、“red”和“stop”的词语时,更多地关注图像;在生成诸如“top”、“of”和“sign”时,更多地依赖视觉哨兵。 总的来说,本文的主要贡献如下: 我们提出了一个自适应编码-译码框架,在生成词语时自动决定何时关注图像,何时依赖语言模型; 我们首次提出了一个新的空间关注模型,并依赖它来设计了我们带视觉哨兵的新型自适应注意力模型; 在COCO和Flicker30K上,我们的模型明显优于其它state-of-the-art方法; 对我们自适应注意力模型我们做了扩展分析,包括词语的视觉基础概率(visual grounding probabilities)和生成的注意力图(attention maps)的弱监督定位(weakly supervised localization)。 2. Method在2.1节,首先介绍了图像标注问题的普通神经编码-译码框架。在2.2和2.3节中介绍我们提出的基于注意力的图像标注模型 2.1. Encoder-Decodedr for Image Captioning我们先简要描述编码-译码图像标注框架。在给定图像和其对应标注的情况下,编码-译码模型直接最大化下式的目标:$$\\theta ^ * = arg max_\\theta \\sum_{(I,y)}\\log p(y|I;\\theta)$$其中$\\theta$是模型的参数,$I$为图像,$y =\\{y_1,\\dots,y_t\\}$是对应的标注。使用链式法则,联合概率分布的数似然可以分解成有序的条件概率:$$\\log p(y)=\\sum^T_{t=1}\\log p(y_t|y_1,\\dots,y_{t-1},I)$$为了方便起见,我们丢弃了对模型参数的依赖。 在编码-译码框架中,RNN网络下,每个条件概率定义如下:$$\\log p(y_t|y_1,\\dots,y_{t-1},I)=f(h_t, c_t)$$其中$f$是一个非线性函数,输出概率$y_t$ 。$c_t$是$t$时刻从图像$I$中提取的视觉上下文(visual context)向,$h_t$是RNN在$t$时刻隐藏状态。本文的RNN采用了LSTM,在各种序列模型任务中已被证明有着不错(state-of-the-art)的表现。$h_t$定义为:$$h_t=LSTM(x_t,h_{t-1},m_{t-1})$$其中$x_t$是输入向量,$m_{t-1}$是$t-1$时刻的存储单元向量。 一般来讲,上下文向量$c_t$在神经编码-译码框架中是个重要因素,为标注生成提供了视觉依据。构建上下文向量的不同方法就划分出:普通编码-译码结构和基于注意力的编码译码框架: 首先,在普通的编码-译码框架中,$c_t $仅依赖于编码器CNN。输入图像$I$被输入CNN网络,将最后一个全连接层提取作为整体图像的特征。在生成词语的整个过程中,上下文向量$c_t$保持不变,也不依赖于译码器的隐藏状态。 然后在基于注意力的框架中,$c_t$同时依赖编码器和译码器。在$t$时刻,在隐藏状态的基础上,译码器能够关注图像的特定区域,并利用CNN网络中的一个卷积层的空间图像特征来计算$c_t$。注意力模型能明显提升图像标注的性能。 为了计算上下文向量$c_t$,在2.2节首先提出我们的空间注意力模型,之后在2.3节扩展模型,构建自适应的注意力模型。 2.2. Spatial Attention Model首先,为了计算上下文向量 $c_t$提出了一个空间注意力模型,定义如下:$$c_t = g(V,h_t)$$其中$g$是注意力函数,$V=[v_1,\\dots,v_k],v_i\\in {R}^d$是空间图像特征,每个都是对对应图像部分的$d$维表示。$h_t$是RNN在$t$时刻的隐藏状态。 给定空间图像特征$V\\in { R}^{d \\times k}$和LSTM的隐藏状态$h_t\\in { R}^d$,我们将其供给一个单层神经网络,在其后连接了一个softmax函数,这样就生成了图像$k$个区域的注意力分布:$$z_t=w^T_h tanh(W_vV+(W_gh_t){\\mathbb{I}^T })$$ $$\\alpha_t=softmax(z_t)$$ 其中$\\mathbb{I} \\in { R}^k$是所有元素都为$1$的向量,$W_v,W_g\\in{ R}^{k\\times d}$和$w_h\\in{ R}^k$为要训练的参数,$\\alpha \\in{ R}^k$是$V$中特征的注意力权重。在得到注意力分布的基础上,上下文向量$c_t$可由下式得到:$$c_t=\\sum_{i=1}^k\\alpha_{t_i}v_{t_i}$$然后便可利用式3来结合$c_t$和$h_t$,去预测下一个词语$y_{t+1}$。 不同于《Show, attend and tell: Neural image caption generation with visual attention》一文提出的结构,如图2所示,我们利用当前的隐藏状态$h_t$来判断看向哪(如:生成上下文向量$c_t$),然后结合两者信息来预测下一个词语。我们这样设计是由于《Deep captioning with multimodal recurrent neural networks》一文中显示的残差网络(residual network)的优越性能。生成的上下文向量$c_t$可被看作当前隐藏状态$h_t$的剩余视觉信息(residual visual information),削弱了不确定性并为预测下一词语补充了当前隐藏状态的信息量。由实验结果来看(如表1所示),我们的空间注意力模型性能更优。 2.3. Adaptive Attention Model在基于空间注意力的译码器被证明在图像标注问题上有效之后,它还不能决定何时依赖视觉信息,何时依赖语言模型。在这一节,受《Pointer sentinel mixture models》一文启发,我们提出了一个新概念——视觉哨兵, 潜在表示了译码器已经知道的信息。利用视觉哨兵,我们扩展了空间注意力模型,并提出了一个自适应模型,在预测下一词语时能够决定是否需要关注图像。 什么是视觉哨兵呢?译码器的存储区保存了长期和短期内的视觉和语义信息,我们的模型学习从这些信息中提取一个新的组件(component)。当选择不关注图像时,模型可以回退。这个新的组件就被称为视觉哨兵,决定是关注图像信息还是视觉哨兵的门就是哨兵门。当译码器RNN是LSTM时,我们认为这些信息保留在了它的存储细胞元中,因此,为了得到视觉哨兵向量$s_t$,我们将LSTM作以下扩展:$$g_t=\\sigma (W_xx_t+W_hh_{t-1})$$ $$s_t=g_t\\bigodot \\tanh(m_t)$$ 其中,$W_x$和$W_h$是要训练的权重参数,$x_t$是$t$时刻LSTM的输入,$g_t$是在存储单元$m_t$上的控制门。$\\sigma$表示经过logistic sigmoid激活操作,$\\bigodot$表示元素积操作。 基于视觉哨兵,我们提出了一个自适应注意力模型来计算上下文向量。在我们提出的架构(见图3),我们新的自适应上下文向量定义为$\\hat c_t$,由模型生成,是空间注意力图像特征(如空间注意力模型的上下文向量)和视觉哨兵向量的结合。译码器存储中已知不少信息(如视觉哨兵),网络需要考虑到底从图像中获取多少新信息,上下文向量就权衡了这个问题。组成方式定义如下:$$\\hat c_t=\\beta_ts_t+(1-\\beta_t)c_t$$其中,$\\beta_t$是$t$时刻新的哨兵门。在结合模型中(mixture model)中, $\\beta_t$生成一个$[0,1]$之间的标量。当取值为$1$时,表示在生成下一词语时,只使用视觉哨兵信息;当值为0时,只使用空间图像信息。 为了计算新的哨兵门$\\beta_t $,我们修改了空间注意力模块。特别的,我们向由式6定义的,表示注意力scores的向量$z$添加一额外元素。这一元素阐明了有多少网络的注意力放在了哨兵(与图像特征相比)。添加这个额外元素的过程,是将式7转变为:$$\\hat \\alpha_t = softmax([z_t;w^T_h \\tanh(W_ss_t+(W_gh_t))])$$其中$[\\cdot;\\cdot]$表示连接运算,$W_s$和$W_g$是权重参数。特别的,$W_g$同式6是相同的权重参数。$\\hat \\alpha_t\\in {R}^{k+1}$是基于空间图像特征和视觉哨兵向量得到的注意力分布","link":"/2018/09/13/Knowing-When-to-Look-Adaptive-Attention-via-A-Visual-Sentinel-for-Image-Captioning/"},{"title":"Markdown语法初入","text":"Markdown 是一种轻量级的「标记语言」,使用用特殊的 Markdown 文档处理器将 Markdown 语法翻译成预设的文档格式、标题大小等,一般用于展示时输出的是 HTML。 标题 # 后加上要设的标题就可出现效果,#与标题之间需加入空格。123456# 一级标题## 二级标题### 三级标题#### 四级标题##### 五级标题###### 六级标题 换行两个回车表示新的段落 两个空格再加回车表示换行第二行 列表+ -表示无序列表,1. 2.表示有序列表。12345678910无序列表+ 无序列表1 + 无序列表1.1+ 无序列表2- 无序列表3- 无序列表4有序列表1. 有序列表12. 有序列表2 效果:无序列表 无序列表1 无序列表 1.1 无序列表2 无序列表3 无序列表4 有序列表 有序列表1 有序列表2 引用>后接引用内容。12> 引用内容1> 引用内容2 效果: 引用内容1引用内容2 粗体/斜体两个* *之间的内容为斜体,两个成对** **之间的内容为粗体。12*斜体***粗体** 效果:斜体粗体 链接与图片链接:[显示文本](链接地址)1[Google](https://www.google.com) Google 图片 : 1 参考Markdown:指南 by binarization Markdown 语法说明","link":"/2017/12/16/Markdown语法初入/"},{"title":"Deep Forest论文笔记","text":"论文链接https://arxiv.org/abs/1702.08835 注:本文谨代表笔者观点,水平有限,若有不足及疏忽之处,诚请批评指正 Abstract 尝试用不可微模块建立深度模型 推测DNNs成功的秘密在于: layer by layer processing in-model feature transformation sufficient model complexity 提出gcForest,决策树集合方法,比DNNs更少的超参数,模型复杂度依据数据自动调整。采用默认参数设置,对于不同领域各种类型的数据,在大部分情况下相当鲁棒。 IntroductionDNNs缺点: 太多超参数,学习表现极度依赖细致的调参过程。训练过程太tricky,涉及到的factor太多几乎有无限种可能,理论分析十分困难。 对数据极度饥饿,在小数据量问题上很难应用。对于一些大数据量领域,由于标注的成本较高,数据还是不够充足。 黑箱,无法理解决策过程,无法理论分析学习过程 训练之前必须先确定网络结构,故而模型复杂度是预先确定的。实际中模型复杂度overly complicated,shortcut connection、剪枝、二值化 有助于提高DNNs的性能。 一些任务上,DNNs仍然不够出众,有时甚至不够充分 神经网络是多层的参数化可微的非线性模块,但是现实世界中不是所有的properties都是可微或者best modelled as differentiable。文章在尝试解决这样一个问题: Can deep learning be realized with non-differentiable modules? 这个问题的结果有助于解答: 深度模型是否只能用可微的模块构建 不用反向传播可以训练深度模型吗 在RandomForest或者XGBoost表现强劲的task上,能否让深度模型表现更好 由layer-by-layer结构启发,采用级联的forest结构,提出gcforest,有着这样的优点: 模型复杂度data-dependent,小数据问题表现良好 更少的超参数 即使是不同领域的各种数据,采用默认参数设置,表现依然出色 InspirationInspiration from DNNsDNNs成功最重要的是representation learning,表征学习最重要的是layer-by-layer processing。从底层往上,特征的抽象程度越高。 其它条件一定的情况下,模型较高的复杂度一般代表着较强的学习能力。但不能解释浅层网络表现一般的原因,即使添加无限个隐层节点,故模型复杂度本身无法解释DNNs的成功,而在于layer-by-layer。不管flat network复杂度有多高,它们并不能学到逐层处理的特征。 虽然决策树和Boosting方法有逐层处理,但是在学习过程中,总是work on 原始的特征表示,并没有创造新特征,即没有模型内的特征转换。与被武断地赋予足够复杂度的DNNs相比,决策树和Boosting方法的复杂度还是相对有限。 Inspiration from Ensemble Learning对于集成学习来说,每个子学习器需要accurate和diverse。在融合的过程中,与单纯的精确度相比,子学习器间的互补性更重要。从error-ambiguity decomposition可得到: $$E = \\overline E - \\overline A \\tag1$$ $E$为集成error,$\\overline E$为子分类器的平均误差,$\\overline A$为子分类器间的平均ambiguity,之后称为多样性。子分类器的精确度和多样性越高,集成效果越好。ambiguity的定义说法不一,故无法将该式子拿来作为优化的函数。 实际中,多样性是通过训练时添加随机性实现的,主要有四种机制: data sample manipulation input feature manipulation learning parameter manipulation output representation manipulation 不同的机制可以融合使用,但是并不是很有效。 The gcForest ApproachCascade Forest Structure由layer-by-layer processing启发,gcForest采用一个级联结构。 每一层是一个决策树森林的集成,包含two random forests (black) and two completely-random tree forests(blue)。每个completely-random tree forests 包含500个完全随机的树,其中,每棵树随机选择属性分裂节点,直至纯净的叶子节点。每个random forest包含500个树,随机选择$\\sqrt d$个属性作为候选集,再依据jini分裂。 给定一个实例,通过计算实例所落在的叶子节点上,不同类别训练样本的比例,之后将该森林中每棵树的结果作以平均,每个forest就可以得到类别分布的估计。这个类别分布构成一个类别向量,再与之前原始特征拼接,作为下一层的输入。 假设有3个类别,那么四个森林,每个都会产生一个3维的类别向量,下一层会接收到12($=3\\times4$)个扩增特征。 这里类别特征太简单,这样的小的增广特征,所能传递的增广信息很有限,会被原始特征淹没。很明显,更可以合并更多特征,比如父节点的类别分布(先验分布),兄弟节点的类别分布(互补分布)。 每个森林的类别向量利用k折交叉验证得到,以防止过拟合。扩展完一层后,整个级联结构可在验证集上面测试性能,若没有显著提高,训练过程会终止,故而层数可以自动确定。这也是gcForest能够自动决定模型复杂度的原因。 Multi-Grained Scanning由CNNs和RNNs启发,利用多粒度扫描加强cascade forest。 如上图,利用滑动窗口扫描原始特征。假设原始特征为400,窗口大小为100。对于序列数据,一次滑动得到一个100维的特征向量,共生成301个特征向量。如果原始数据存在空间联系,例如400图像像素可视为$20\\times 20$的panel,$10\\times10$的窗口会生成121个特征向量。特征向量的类别标签与原始数据的标签保持一致。以上图序列数据为例,假设有3类的前提下,每棵树利用100维的窗口生成了301个特征向量,同时就有301个3维的类别向量,即对应原始400维的特征向量就生成了一个1806维的转换特征。 每个特征向量的标签,分配成原始数据的标签不可避免的有问题。可采用Flipping Output方法,随机改变一些输入样本的标签。 当转换特征太长时,可作特征采样,具体来说是对滑动窗口扫描所得instances的二次采样。此采样过程与Random Subsapace方法有关(从初始属性集中抽取若干个属性子集, 再基于每个属性子集训练一个基学习器)。 上图仅仅展示了一个size的滑动窗口,通过使用多尺度的滑动窗口,便能生成多尺度下的特征向量,如下图。 Overall Procedure and Hyper-Parameters上图展示了gcForest的整体算法结构。假定原始输入为400原始特征,多粒度扫描采用三个窗口size。对于$m$个训练样本,窗口大小为100时会生成$301\\times m$个100维的训练数据集。用这些数据去训练一个completely-random tree forest和一个random forest,每个都包含500个树。当要预测3个类别时,一个instance会有一个1806维的特征向量。做过如此转换的训练集,之后将在cascade forest的第一级上训练。 同样的,大小为200、300的滑动窗口对于每一个原始的训练样本,会分别生成一个1206维、606维的特征向量。转换的特征向量($12-dim$),用前一级生成的类别向量增广之后,分别在第二级和第三级的cascade forest中训练。一直重复这个过程,直到验证集的性能收敛。最终的模型可视为是一个cascade of cascades,每个cascade包括多个level,每个level对应于一个扫描粒度。对于复杂问题,在计算资源允许的前提下可尝试更多粒度。 对于test instance,在经过多粒度扫描之后得到其对应转换表示,然后进入cascade直到最后一层。最后的预测结果是通过融合最后一层四个forest的四个3维类别向量,然后选取最大值的那个类别。 Table 1 总结了DNN和gcForest的超参数,我们实验中参数的默认值也已给出。 ExperimentsConfigurationgcForest默认参数: 4 completely-random tree forest, 4 random forest 500 trees three-fold cross validation 0.8 growing set, 0.2 estimating set, get number of levels, then retrian all 多粒度扫描过程,窗口大小$\\lfloor d/16\\rfloor$、$\\lfloor d/8\\rfloor$、$\\lfloor d/4\\rfloor$ 若原始数据由panel结构,按图4方法处理 针对不同任务的参数精调对精度有帮助,为了显示超参数设置比DNNs更容易,故采用相同的参数 对应DNNs,ReLU、cross-entropy、adadelta、droupout 0.25 or 0.5 ResultsImage CategorizationMNIST(train/val : 60000/1000):LeNet-5,SVM with rbf, Random Forest with 2000 trees, Deep Belief Nets Face RecognitionORL (400 gray-scale face images from 40 people): CNN 2个卷积层,32个$3\\times3$kernel的feature maps,每个卷积层后一个$2\\times 2$的最大池化,128的线性层和40的softmax ReLU, cross-entropy loss, dropout rate of 0.25, adadelta, batch size 10, epochs 50 kNN with k=3 Music ClassificationGTZAN (10个流派,每个流派100个flips,train/val : 700/300 MFCC feature $1280\\times 13$) CNN 32个$13\\times 8$kernel的feature maps,跟着一个池化层,2个全连接层1024,512,最后一个softmax Relu, categorical cross-entropy Random Forest, Logistic Regression, SVM Hand Movement RecognitionsEMG (time series, 1800 records for 6 classes) MLP input-1024-512-output LSTM 128 hidden units, sequence length 6 Sentiment ClassificationIMDB (movie reviews, tf-idf features, train/val : 25000/25000) MLP imput-1024-1024-512-256-output tf-idf 没有空间或者序列特征,故gcForest跳过多粒度扫描 Low-Dimensional DataUCI-datasets: LETTER 16 dim, 16000/4000 ADULT 14 dim, 32561/16281 YEAST 8 dim, 1038/446 对比算法: MLPs input-70\\30\\50-50\\20\\30-output gcForest 跳过多粒度扫描 High-Dimensional DataCIFAR-10 (50000/10000 10 classes) 增广特征在高维的原始特征前,出现淹没现象。效果提高:task-specific tuning, more grains, gbdt(简单替换最后一层) Influence of Multi-Grained Scanning Influence of Cascade Structure图5介绍了cascade of cascades的多粒度融合方法,当然,也可将各粒度的特征直接拼接,结构如下图: 下表是gcForest与gcForest$_{conc}$的性能比较: Influence of Larger Models由于计算资源限制,并未尝试更大模型。GPU目前无法加速gcForest!!!!!! Running TImePC with 2 Intel E5 2695 v4 CPUs (18 cores) IMDB dataset (25,000 examples with 5,000 features): 267.1 seconds per cascade level, automatically terminates with 9 cascade levels, amounting to 2,404 seconds or 40 minutes. amounting to 4,650 seconds or 77.5 minutes. if using GPU (Nvidia Titan X pascal), amounting to 700 seconds or 11.6 minutes 多粒度扫描会增加时间成本,但是不同的粒度扫描过程内在并行,每一级内部的foerst生成过程也是并行的。故以后效率提升可用更好的并行化方法。 Related Work Future Issues 加强特征re-representation过程:为了解决高维数据的特征淹没问题,可引入父节点,兄弟节点,决策路径等特征 加速,内存占用优化: 借用相关硬件架构,如Intel KNL; 算法分布式实现; feature sample以减少内存占用,还能增加多样性 reuse some components completely-random tree forests不仅增加了多样性, 也有可能利用unlabeled data 实机测试EnvironmentCPU: Intel i5-7300HQ(4 cores, 2.50GHz) Mem: 8GB total ($\\sim 5$GB for testing) Datasets: MNIST Official Codes:Github/gcForest Test a: XGBoost: n_estimators = 10, max_depth = 5 Radom Forest: n_estimators = 10 Extra-Trees: n_estimators = 10 Logistic Regression each module cv = 5 Results: Test b XGBoost: n_estimators = 10, max_depth = 5 Radom Forest: n_estimators = 15 Extra-Trees: n_estimators = 15 Extra-Trees: n_estimators = 15 each module cv = 5 Results: 在此只做了两个简单的测试,需要提前说明的是,在测试过程中,个人电脑上还在进行其它一些简单的工作,故测试环境十分不严谨,以下结果仅供参考。 从运行时间的表现来看,Test a中生成一个cascade layer平均需要2h50m左右,Test b则平均需要4m27s左右。 这样的时间差距,主要在于Test a的LogisticRegression运行十分耗时。 另外从memory consumption来看,对于MNIST这样的数据集,内存占用竟然超过了5G,并且,无论是Test a 还是Test b 都因为内存不足无法完成训练,在第7个cascade layer自动终止。 这里需要说明的是,官方源码中提供了设置set_keep_model_in_mem,default为True,故可将其改为False应当可以完成训练过程。但是,其内存占用还是过于庞大。 个人思考 采用原始特征拼接前一层转换特征的的方式,引入了类似shortcut-connection[1],但是这个shortcut只引入原始特征,中间层级的转换特征并没有shortcut到之后层以供学习 多粒度扫描像1D和2D的convolution,但是这样的特征转换,对于label的处理个人觉得有些武断,对于序列与空间信息不够完善 由于data-dependent,如何进行迁移学习,如何fine-tune是个很大的问题[2] 大数据集上面到底表现如何 周志华老师的这项工作本意是“打开深度学习的大门”(if success),让人不禁有些新的想象。但就如文章中所说,目前的工作只是个萌芽,与成熟的DNNs在很多领域无法扳手腕,只是希望其以后成为一个alternative,而不是干掉DNNs。","link":"/2018/09/25/Deep-Forest论文笔记/"},{"title":"OPTICS算法基础","text":"在前面介绍的DBSCAN算法中,有两个初始参数E(邻域半径)和minPts(E邻域最小点数)需要用户手动设置输入,并且聚类的类簇结果对这两个参数的取值非常敏感,不同的取值将产生不同的聚类结果,其实这也是大多数其他需要初始化参数聚类算法的弊端。 为了克服DBSCAN算法这一缺点,提出了OPTICS算法(Ordering Points to identify the clustering structure)。OPTICS并不显示的产生结果类簇,而是为聚类分析生成一个增广的簇排序(比如,以可达距离为纵轴,样本点输出次序为横轴的坐标图),这个排序代表了各样本点基于密度的聚类结构。它包含的信息等价于从一个广泛的参数设置所获得的基于密度的聚类,换句话说,从这个排序中可以得到基于任何参数E和minPts的DBSCAN算法的聚类结果。 定义OPTICS算法的基础有两点, 参数(半径,最少点数): 一个是输入的参数,包括:半径ε,和最少点数MinPts。 定义(核心点,核心距离,可达距离,直接密度可达): 另一个是相关概念的定义:核心点的定义,如果一个点的半径内包含点的数量不少于最少点数,则该点为核心点,数学描述即 Nε(P)>=MinPts 在这个基础上可以引出核心距离的定义,即对于核心点,距离其第MinPtsth近的点与之的距离 coreDist(P)={UNDIFED, MinPtsth Distance in N(P),if N(P)<=MinPtselse 可达距离,对于核心点P,O到P的可达距离定义为O到P的距离或者P的核心距离,即公式 reachDist(O,P)={UNDIFED, max(coreDist(P), dist(O,P)),if N(P)<=MinPtselse O到P直接密度可达,即P为核心点,且P到O的距离小于半径。 算法OPTICS算法的难点在于维护核心点的直接可达点的有序列表。算法的计算过程如下: 输入:数据样本D,初始化所有点的可达距离和核心距离为MAX,半径ε,和最少点数MinPts。 1、建立两个队列,有序队列(核心点及该核心点的直接密度可达点),结果队列(存储样本输出及处理次序) 2、如果D中数据全部处理完,则算法结束,否则从D中选择一个未处理且未核心对象的点,将该核心点放入结果队列,该核心点的直接密度可达点放入有序队列,直接密度可达点并按可达距离升序排列; 3、如果有序序列为空,则回到步骤2,否则从有序队列中取出第一个点; 3.1 判断该点是否为核心点,不是则回到步骤3,是的话则将该点存入结果队列,如果该点不在结果队列; 3.2 该点是核心点的话,找到其所有直接密度可达点,并将这些点放入有序队列,且将有序队列中的点按照可达距离重新排序,如果该点已经在有序队列中且新的可达距离较小,则更新该点的可达距离。 3.3 重复步骤3,直至有序队列为空。 4、算法结束。 输出结果给定半径ε,和最少点数MinPts,就可以输出所有的聚类。 计算过程为: 给定结果队列 1、从结果队列中按顺序取出点,如果该点的可达距离不大于给定半径ε,则该点属于当前类别,否则至步骤2; 2、如果该点的核心距离大于给定半径ε,则该点为噪声,可以忽略,否则该点属于新的聚类,跳至步骤1; 3、结果队列遍历结束,则算法结束。","link":"/2018/01/06/OPTICS算法基础/"},{"title":"Numpy and Matplotlib Tutorial","text":"NumpyNumpy is the core library for scientific computing in Python. It provides a high-performance multidimensional array object, and tools for working with these arrays. ArraysA numpy array is a grid of values, all of the same type, and is indexed by a tuple of nonnegative integers. The number of dimensions is the rank of the array; the shape of an array is a tuple of intergers giving the size of the array along each dimension. Initailize numpy arrays from nested Python lists, and access elements using square brackets: 123456789101112import numpy as npa = np.array([1, 2, 3]) # Create a rank 1 arrayprint(type(a)) # Prints \"<class 'numpy.ndarray'>\"print(a.shape) # Prints \"(3,)\"print(a[0], a[1], a[2]) # Prints \"1 2 3\"a[0] = 5 # Change an element of the arrayprint(a) # Prints \"[5, 2, 3]\"b = np.array([[1,2,3],[4,5,6]]) # Create a rank 2 arrayprint(b.shape) # Prints \"(2, 3)\"print(b[0, 0], b[0, 1], b[1, 0]) # Prints \"1 2 4\" Numpy also provides many functions to create arrays: 1234567891011121314151617181920import numpy as npa = np.zeros((2,2)) # Create an array of all zerosprint(a) # Prints \"[[ 0. 0.] # [ 0. 0.]]\"b = np.ones((1,2)) # Create an array of all onesprint(b) # Prints \"[[ 1. 1.]]\"c = np.full((2,2), 7) # Create a constant arrayprint(c) # Prints \"[[ 7. 7.] # [ 7. 7.]]\"d = np.eye(2) # Create a 2x2 identity matrixprint(d) # Prints \"[[ 1. 0.] # [ 0. 1.]]\"e = np.random.random((2,2)) # Create an array filled with random valuesprint(e) # Might print \"[[ 0.91940167 0.08143941] # [ 0.68744134 0.87236687]]\" Array indexingNumpy offers several ways to index into arrays. Slicing: Similaer to Python lists, numpy arrays can be sliced. Since arrays may be multidimensional, you must specify a slice for each dimension of the array: 12345678910111213141516171819import numpy as np# Create the following rank 2 array with shape (3, 4)# [[ 1 2 3 4]# [ 5 6 7 8]# [ 9 10 11 12]]a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])# Use slicing to pull out the subarray consisting of the first 2 rows# and columns 1 and 2; b is the following array of shape (2, 2):# [[2 3]# [6 7]]b = a[:2, 1:3]# A slice of an array is a view into the same data, so modifying it# will modify the original array.print(a[0, 1]) # Prints \"2\"b[0, 0] = 77 # b[0, 0] is the same piece of data as a[0, 1]print(a[0, 1]) # Prints \"77\" You can also mix integer indexing with slice indexing. However, doing so will yield an array of lower rank than the original array: 123456789101112131415161718192021222324import numpy as np# Create the following rank 2 array with shape (3, 4)# [[ 1 2 3 4]# [ 5 6 7 8]# [ 9 10 11 12]]a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])# Two ways of accessing the data in the middle row of the array.# Mixing integer indexing with slices yields an array of lower rank,# while using only slices yields an array of the same rank as the# original array:row_r1 = a[1, :] # Rank 1 view of the second row of arow_r2 = a[1:2, :] # Rank 2 view of the second row of aprint(row_r1, row_r1.shape) # Prints \"[5 6 7 8] (4,)\"print(row_r2, row_r2.shape) # Prints \"[[5 6 7 8]] (1, 4)\"# We can make the same distinction when accessing columns of an array:col_r1 = a[:, 1]col_r2 = a[:, 1:2]print(col_r1, col_r1.shape) # Prints \"[ 2 6 10] (3,)\"print(col_r2, col_r2.shape) # Prints \"[[ 2] # [ 6] # [10]] (3, 1)\" Integer array indexing:When you index into numpy arrays using slicing, the resulting array view will always be a subarray of the original array. In contrast, integer array indexing allows you to construct arbitrary arrays using the data from another array. 1234567891011121314151617import numpy as npa = np.array([[1,2], [3, 4], [5, 6]])# An example of integer array indexing.# The returned array will have shape (3,) andprint(a[[0, 1, 2], [0, 1, 0]]) # Prints \"[1 4 5]\"# The above example of integer array indexing is equivalent to this:print(np.array([a[0, 0], a[1, 1], a[2, 0]])) # Prints \"[1 4 5]\"# When using integer array indexing, you can reuse the same# element from the source array:print(a[[0, 0], [1, 1]]) # Prints \"[2 2]\"# Equivalent to the previous integer array indexing exampleprint(np.array([a[0, 1], a[0, 1]])) # Prints \"[2 2]\" One usful trick with integer array indexing is selecting or mutating one element from each row of a matrix: 1234567891011121314151617181920212223import numpy as np# Create a new array from which we will select elementsa = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])print(a) # prints \"array([[ 1, 2, 3], # [ 4, 5, 6], # [ 7, 8, 9], # [10, 11, 12]])\"# Create an array of indicesb = np.array([0, 2, 0, 1])# Select one element from each row of a using the indices in bprint(a[np.arange(4), b]) # Prints \"[ 1 6 7 11]\"# Mutate one element from each row of a using the indices in ba[np.arange(4), b] += 10print(a) # prints \"array([[11, 2, 3], # [ 4, 5, 16], # [17, 8, 9], # [10, 21, 12]]) Boolean array indexing: Boolean array indexing lets you pick out arbitary elements of an array. Frequently this type of indexing is used to select the elements of an array that satisfy some condition. 1234567891011121314151617181920import numpy as npa = np.array([[1,2], [3, 4], [5, 6]])bool_idx = (a > 2) # Find the elements of a that are bigger than 2; # this returns a numpy array of Booleans of the same # shape as a, where each slot of bool_idx tells # whether that element of a is > 2.print(bool_idx) # Prints \"[[False False] # [ True True] # [ True True]]\"# We use boolean array indexing to construct a rank 1 array# consisting of the elements of a corresponding to the True values# of bool_idxprint(a[bool_idx]) # Prints \"[3 4 5 6]\"# We can do all of the above in a single concise statement:print(a[a > 2]) # Prints \"[3 4 5 6]\" DatatypesEvery numpy array is a grid of elements of the same type. Numpy provides a large set of numeric datatypes that you can use to construct arrays. Numpy tries to guess a datatype when you create an array, but functions that construct arrays usually also include an optional argument to explictly specify the datatype. 12345678910import numpy as npx = np.array([1, 2]) # Let numpy choose the datatypeprint(x.dtype) # Prints \"int64\"x = np.array([1.0, 2.0]) # Let numpy choose the datatypeprint(x.dtype) # Prints \"float64\"x = np.array([1, 2], dtype=np.int64) # Force a particular datatypeprint(x.dtype) # Prints \"int64\" Array mathBasic mathematical functions operate elementwise on arrays, and are available both as operator overloads and as functions in the numpy module: 123456789101112131415161718192021222324252627282930313233import numpy as npx = np.array([[1,2],[3,4]], dtype=np.float64)y = np.array([[5,6],[7,8]], dtype=np.float64)# Elementwise sum; both produce the array# [[ 6.0 8.0]# [10.0 12.0]]print(x + y)print(np.add(x, y))# Elementwise difference; both produce the array# [[-4.0 -4.0]# [-4.0 -4.0]]print(x - y)print(np.subtract(x, y))# Elementwise product; both produce the array# [[ 5.0 12.0]# [21.0 32.0]]print(x * y)print(np.multiply(x, y))# Elementwise division; both produce the array# [[ 0.2 0.33333333]# [ 0.42857143 0.5 ]]print(x / y)print(np.divide(x, y))# Elementwise square root; produces the array# [[ 1. 1.41421356]# [ 1.73205081 2. ]]print(np.sqrt(x)) Note that unlike MATLAB, * is elementwise multiplication, not matrix multiplication. We instead use the dot function to compute inner products of vectors, to multiply a vector by a matrix, and to multiply matices. dot is available both as a function in the numpy module and as an instance method of array objects: 123456789101112131415161718192021import numpy as npx = np.array([[1,2],[3,4]])y = np.array([[5,6],[7,8]])v = np.array([9,10])w = np.array([11, 12])# Inner product of vectors; both produce 219print(v.dot(w)) # An instance methodprint(np.dot(v, w)) # A function in the numpy module# Matrix / vector product; both produce the rank 1 array [29 67]print(x.dot(v))print(np.dot(x, v))# Matrix / matrix product; both produce the rank 2 array# [[19 22]# [43 50]]print(x.dot(y))print(np.dot(x, y)) Numpy provides many useful functions for performing computations on arrays; one of the most useful if sum: 1234567import numpy as npx = np.array([[1,2],[3,4]])print(np.sum(x)) # Compute sum of all elements; prints \"10\"print(np.sum(x, axis=0)) # Compute sum of each column; prints \"[4 6]\"print(np.sum(x, axis=1)) # Compute sum of each row; prints \"[3 7]\" Apart from computing mathematical functions using arrays, we frequently need to reshape or otherwise manipulate data in arrays. The simplest example of this type of operation is transposing a matrix; to transpose a matrix, simply use the T attribute of an array object: 123456789101112import numpy as npx = np.array([[1,2], [3,4]])print(x) # Prints \"[[1 2] # [3 4]]\"print(x.T) # Prints \"[[1 3] # [2 4]]\"# Note that taking the transpose of a rank 1 array does nothing:v = np.array([1,2,3])print(v) # Prints \"[1 2 3]\"print(v.T) # Prints \"[1 2 3]\" BroadcastingBroadcasting is a powerful mechanism that allows numpy to work with arrays of different shapes when performing arithmetic operations. Frequently we have a smaller array and a larger array, and we want to use the smaller array multiple times to perform some operation on the larger array. For example, suppose that we want to add a constant vector to each row of a matrix. We could do it like this: 123456789101112131415161718import numpy as np# We will add the vector v to each row of the matrix x,# storing the result in the matrix yx = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])v = np.array([1, 0, 1])y = np.empty_like(x) # Create an empty matrix with the same shape as x# Add the vector v to each row of the matrix x with an explicit loopfor i in range(4): y[i, :] = x[i, :] + v# Now y is the following# [[ 2 2 4]# [ 5 5 7]# [ 8 8 10]# [11 11 13]]print(y) This works; however when the matrix x is very large, computing an explicit loop in Python could be slow. Note that adding the vector v to each row of the matrix x is equivalent to forming a matrix vv by stacking multiple copies of v vertically, then performing elementwise summation of x and vv. We could implement this approach like this: 12345678910111213141516import numpy as np# We will add the vector v to each row of the matrix x,# storing the result in the matrix yx = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])v = np.array([1, 0, 1])vv = np.tile(v, (4, 1)) # Stack 4 copies of v on top of each otherprint(vv) # Prints \"[[1 0 1] # [1 0 1] # [1 0 1] # [1 0 1]]\"y = x + vv # Add x and vv elementwiseprint(y) # Prints \"[[ 2 2 4 # [ 5 5 7] # [ 8 8 10] # [11 11 13]]\" Numpy broadcasting allows us to perform this computation without actually creating multiple copies of v. Consider this version, using broadcasting: 1234567891011 import numpy as np# We will add the vector v to each row of the matrix x,# storing the result in the matrix yx = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])v = np.array([1, 0, 1])y = x + v # Add v to each row of x using broadcastingprint(y) # Prints \"[[ 2 2 4] # [ 5 5 7] # [ 8 8 10] # [11 11 13]]\" The line y = x + x works even though x has shape (4, 3) and v has shape (3, ) due to broadcasting; this line works as if v actually had shape(4, 3), where each row was a copy of v, and the sum was performed elementwise. Broadcasting two arrays together follows these rules: If the arrays do not have the same rank, prepend the shape of the lower rank array with 1s util both shapes have the same length. The two arrays are said to be compatible in a dimension if they have the same size in the dimension, or if one of the arrays has size 1 in that dimension. The arrays can be broadcast together if they are compatible in all dimensions. After broadcasting, each array behaves as if it had shape equal to the elementwise maximum of the shapes of the two input arrays. In any dimension where one array had size 1 and the other array had size greater than 1, the first array behaves as if it were copied along that dimension. Functions that support broadcasting are known as universal functions. Here are some applications of brodcasting: 123456789101112131415161718192021222324252627282930313233343536373839404142import numpy as np# Compute outer product of vectorsv = np.array([1,2,3]) # v has shape (3,)w = np.array([4,5]) # w has shape (2,)# To compute an outer product, we first reshape v to be a column# vector of shape (3, 1); we can then broadcast it against w to yield# an output of shape (3, 2), which is the outer product of v and w:# [[ 4 5]# [ 8 10]# [12 15]]print(np.reshape(v, (3, 1)) * w)# Add a vector to each row of a matrixx = np.array([[1,2,3], [4,5,6]])# x has shape (2, 3) and v has shape (3,) so they broadcast to (2, 3),# giving the following matrix:# [[2 4 6]# [5 7 9]]print(x + v)# Add a vector to each column of a matrix# x has shape (2, 3) and w has shape (2,).# If we transpose x then it has shape (3, 2) and can be broadcast# against w to yield a result of shape (3, 2); transposing this result# yields the final result of shape (2, 3) which is the matrix x with# the vector w added to each column. Gives the following matrix:# [[ 5 6 7]# [ 9 10 11]]print((x.T + w).T)# Another solution is to reshape w to be a column vector of shape (2, 1);# we can then broadcast it directly against x to produce the same# output.print(x + np.reshape(w, (2, 1)))# Multiply a matrix by a constant:# x has shape (2, 3). Numpy treats scalars as arrays of shape ();# these can be broadcast together to shape (2, 3), producing the# following array:# [[ 2 4 6]# [ 8 10 12]]print(x * 2) Numpy DocumentationThis brief overview has touched on many of the important things need to know about numpy, but is far from complete. Check out the numpy [numpy reference][http://docs.scipy.org/doc/numpy/reference/] to find out much more. MatplotlibMatplotlib is a plotting library. In this section give a brief introduction to the matplotlib.pyplot module, which provides a plotting system similar to that of MATLAB. PlottingThe most important function in matplotlib is plot, which allows you to plot 2D data. Here is a simple example: 12345678910import numpy as npimport matplotlib.pyplot as plt# Compute the x and y coordinates for points on a sine curvex = np.arange(0, 3 * np.pi, 0.1)y = np.sin(x)# Plot the points using matplotlibplt.plot(x, y)plt.show() # You must call plt.show() to make graphics appear. Runnig this code produces the following plot: With just a little bit of extra work we can easily plot multiple lines at once, and add a title, legend, and axis labels: 12345678910111213141516import numpy as npimport matplotlib.pyplot as plt# Compute the x and y coordinates for points on sine and cosine curvesx = np.arange(0, 3 * np.pi, 0.1)y_sin = np.sin(x)y_cos = np.cos(x)# Plot the points using matplotlibplt.plot(x, y_sin)plt.plot(x, y_cos)plt.xlabel('x axis label')plt.ylabel('y axis label')plt.title('Sine and Cosine')plt.legend(['Sine', 'Cosine'])plt.show() SubplotsYou can plot different things in the same figure using the subplot function. Here is an example: 1234567891011121314151617181920212223mport numpy as npimport matplotlib.pyplot as plt# Compute the x and y coordinates for points on sine and cosine curvesx = np.arange(0, 3 * np.pi, 0.1)y_sin = np.sin(x)y_cos = np.cos(x)# Set up a subplot grid that has height 2 and width 1,# and set the first such subplot as active.plt.subplot(2, 1, 1)# Make the first plotplt.plot(x, y_sin)plt.title('Sine')# Set the second subplot as active, and make the second plot.plt.subplot(2, 1, 2)plt.plot(x, y_cos)plt.title('Cosine')# Show the figure.plt.show() ImagesYou can use the imshow function to show images. Here is an example: 12345678910111213141516171819import numpy as npfrom scipy.misc import imread, imresizeimport matplotlib.pyplot as pltimg = imread('assets/cat.jpg')img_tinted = img * [1, 0.95, 0.9]# Show the original imageplt.subplot(1, 2, 1)plt.imshow(img)# Show the tinted imageplt.subplot(1, 2, 2)# A slight gotcha with imshow is that it might give strange results# if presented with data that is not uint8. To work around this, we# explicitly cast the image to uint8 before displaying it.plt.imshow(np.uint8(img_tinted))plt.show() Reference[Python numpy tutorial][http://cs231n.github.io/python-numpy-tutorial/]","link":"/2018/05/21/Python Numpy Tutorial/"},{"title":"Shutil与OS常用文件操作","text":"Python 常用文件操作接口。 shutil常用功能shutil模块对文件和文件集合提供了许多高级操作,支持文件复制和删除。 copyfile(src, dst) 从源src复制到dst中去,当然前提是目标地址是具备可写权限,抛出的异常信息为IOException。 如果当前的dst已存在的话就会被覆盖掉 copymode(src, dst) 只是会复制其权限,其他的东西是不会被复制的 copystat(src, dst) 复制权限、最后访问时间、最后修改时间 copy(src, dst) 复制一个文件到一个文件或一个目录 copy2(src, dst) 在copy上的基础上,将文件最后访问时间与修改时间也复制过来,类似于cp –p move(src, dst) 如果两个位置的文件系统一样,相当于rename操作;如果是不相同,就是move操作 copytree(olddir,newdir,True/Flase) 把olddir拷贝一份到newdi。如果第3个参数是True,则复制目录时将保持文件夹下的符号连接;如果是False,则将在复制的目录下生成物理副本来替代符号连接 shutil.copyfileobj(fsrc, fdst[, length]) 将文件内容拷贝到另一个文件中 1234567891011121314151617181920212223242526os.sep #可以取代操作系统特定的路径分隔符。windows下为 '\\\\'os.name #字符串指示你正在使用的平台。比如对于Windows,它是'nt',而对于Linux/Unix用户,它是 'posix'os.getcwd() #函数得到当前工作目录,即当前Python脚本工作的目录路径os.getenv() #获取一个环境变量,如果没有返回noneos.putenv(key, value) #设置一个环境变量值os.listdir(path) #返回指定目录下的所有文件和目录名os.remove(path) #函数用来删除一个文件os.system(command) 函数用来运行shell命令os.linesep 字符串给出当前平台使用的行终止符。例如,Windows使用 '\\r\\n',Linux使用 '\\n' 而Mac使用 '\\r'os.path.split(path) 函数返回一个路径的目录名和文件名os.path.isfile() 和os.path.isdir()函数分别检验给出的路径是一个文件还是目录os.path.exists() 函数用来检验给出的路径是否真地存在os.curdir 返回当前目录 ('.')os.mkdir(path) 创建一个目录os.makedirs(path) 递归的创建目录os.chdir(dirname) 改变工作目录到dirname os.path.getsize(name) 获得文件大小,如果name是目录返回0Los.path.abspath(name) 获得绝对路径os.path.normpath(path) 规范path字符串形式os.path.splitext() 分离文件名与扩展名os.path.join(path,name) 连接目录与文件名或目录os.path.basename(path) 返回文件名os.path.dirname(path) 返回文件路径os.walk(top,topdown=True,onerror=None) 遍历迭代目录os.rename(src, dst) 重命名file或者directory src到dst 如果dst是一个存在的directory, 将抛出OSError. 在Unix, 如果dst在存且是一个file, 如果用户有权限的话,它将被安静的替换. 操作将会失败在某些Unix 中如果src和dst在不同的文件系统中. 如果成功, 这命名操作将会是一个原子操作 (这是POSIX 需要). 在 Windows上, 如果dst已经存在, 将抛出OSError,即使它是一个文件. 在unix,Windows中有效。os.renames(old, new) 递归重命名文件夹或者文件。像rename()","link":"/2018/09/13/Shutil与OS常用文件操作/"},{"title":"Restricted Boltzmann Machine","text":"受限玻尔兹曼机(Restricted Boltzmann Machine,RBM)是G.Hinton教授的一宝。Hinton教授是深度学习的开山鼻祖,也正是他在2006年的关于深度信念网络DBN的工作,以及逐层预训练的训练方法,开启了深度学习的序章。其中,DBN中在层间的预训练就采用了RBM算法模型。RBM是一种无向图模型,也是一种神经网络模型。 RBM具有两层:可见层(V层),以及隐藏层(H层)。可以看到,两层神经元之间都是全连接的,但是每一层各自的神经元之间并没有连接,也就是说,RBM的图结构是一种二分图(bipartite graph)。正是这个特点,才叫受限玻尔兹曼机,玻尔兹曼机是允许同一层之间的神经元相连的。RBM其实是一种简化了的BM模型。 还有一个特点,RBM中的神经元都是二值化的,也就是说只有激活和不激活两种状态,也就是0或者1;可见层和隐藏层之间的边的权重可以用$W$来表示,$W$是一个$|V|×|H|$大小的实数矩阵。算法难点主要就是对$W$求导(当然还有bias参数),用于梯度下降的更新;但是因为$V$和$H$都是二值化的,没有连续的可导函数去计算,实际中采用的sampling的方法来计算,这里面就可以用比如Gibbs sampling的方法,当然,Hinton提出了对比散度DC方法,比Gibbs方法更快,已经成为求解RBM的标准解法。 RBM模型结构因为RBM隐层和可见层是全连接的,为了描述清楚与容易理解,把每一层的神经元展平即可,见下图[7],本文后面所有的推导都采用下图中的标记来表示。 再重提一下,经典的RBM模型中的神经元都是binary的,也就是说上面图中的神经元取值都是$(0,1) $的。实际上RBM也可以做实数性的model,不过这一块可以先放一放,先来看binary的基本model。 RBM能量函数RBM是一个能量模型(Energy based model, EBM),是从物理学能量模型中演变而来;能量模型需要做的事情就是先定义一个合适的能量函数,然后基于这个能量函数得到变量的概率分布,最后基于概率分布去求解一个目标函数(如最大似然)。RBM的过程如下: 我们现在有的变量是$(v,h)$,包括隐层和可见层神经元;参数包括$θ=(W,a,b)$。能量函数定义: $$ E_θ(v,h)=−∑_{i=1}^{n_v}a_iv_i−∑_{j=1}^{n_h}b_jh_j−∑_{i=1}^{n_v}∑_{j=1}^{n_h}h_jw_{j,i}v_i$$ 如果写成向量/矩阵的形式,则为: $$E_θ(v,h)=−a^Tv−b^Th−h^TWv$$ 那么,可以得到变量$(v,h)$的联合概率分布是: $$P_θ(v,h)=\\frac {1}{Z_θ}e^{−E_θ(v,h)}$$ 其中,$Z_θ$称为归一化因子,作用是使得概率之和(或者积分)为1,形式为: $$Z_θ=∑_{v,h}e^{−E_θ(v,h)}$$ 在上面所有算式中,下标$θ$都表示在参数$θ=(W,a,b)$下的表达,为了书写的简洁性,本文余下部分如果没有特殊指定说明,就省略下标$θ$了,但含义不变。 当我们有了联合概率分布,如果想求观察数据(可见层)的概率分布$P(v)$,则求边缘分布: $$P(v)=∑_hP(v,h)=\\frac1Z∑_he^{−E(v,h)}$$ 相对应的,如果想求隐层单元的概率分布$P(h)$,则求边缘分布: $$P(h)=∑_vP(v,h)=\\frac1Z∑_ve^{−E(v,h)}$$ 当然,我们不太可能直接计算$Z$,因为$Z$的求和中存在指数项种可能——$2^{n_v+n_h}$种取值。接下来考虑条件概率,即可见层神经元状态给定时,(任意)隐藏层神经元状态为1的概率,即$P(h_k=1|v)$。类似的也可以求$P(v_k=1|h)$,方法也是差不多的,下面就只对$P(h_k=1|v)$进行描述。我们可以推导出: $$P(h_k=1|v)=sigmoid(b_k+∑_{j=1}^{n_v}w_{kj}v_j)$$ 以及 $$P(v_k=1|h)=sigmoid(b_k+∑_{j=1}^{n_v}w_{kj}h_j)$$ 可以直接知道结果即可,证明可以跳过。这里我们可以看到,sigmoid是一种激励函数,因此才把RBM也叫做一种神经网络模型。 证:以下记$h_{−k}$表示隐藏层神经元k以外的神经元。 $$P(h_k=1|v)=P(h_k=1|h_{−k},v)$$ $$=\\frac {P(h_k=1,h_{−k},v)}{P(h_{−k},v))}$$ $$=\\frac {P(h_k=1,h_{−k},v)}{P(h_k=1,h_{−k},v))+P(h_k=0,h_{−k},v))}$$ $$=\\frac{\\frac1Ze^{−E(h_k=1,h_{−k},v)}}{\\frac1Ze^{−E(h_k=1,h_{−k},v))}+\\frac 1Ze^{−E(h_k=0,h_{−k},v))}}$$ $$=\\frac1{1+e^{−E(h_k=0,h_{−k},v))+E(h_k=1,h_{−k},v)}}$$ $$=\\frac 1{1+e^{−(b_k+∑^{n_v}_{j=1}w_{kj}v_j)}}$$ $$=sigmoid(b_k+∑_{j=1}^{n_v}w_{kj}v_j)$$ 因为假设同层神经元之间相互独立,所以有: $$P(h|v)=∏_{j=1}^{n_h}P(h_j|v)$$ $$P(v|h)=∏_{i=1}^{n_v}P(v_i|h)$$ RBM目标函数假设给定的训练集合是$S=\\{v^i\\}$,总数是$n_s$,其中每个样本表示为$v^i=(v^i_1,v^i_2,…,v^i_{n_v})$,且都是独立同分布i.i.d的。RBM采用最大似然估计,即最大化 $$\\ln L_S=\\ln ∏_{i=1}^{n_s}P(v^i)=∑_{i=1}^{n_s}\\ln P(v^i)$$ 参数表示为$θ=(W,a,b)$,因此统一的参数更新表达式为: $$θ=θ+η\\frac{∂\\ln LS}{∂θ}$$ 其中,$η$表示学习速率。因此,很明显,只要我们可以求解出参数的梯度,我们就可以求解RMB模型了。我们先考虑任意单个训练样本$(v^0)$的情况,即 $$L_S=\\ln P(v^0)=\\ln (\\frac1Z∑_he^{−E(v^0,h)})$$ $$=\\ln∑_he^{−E(v^0,h)}−\\ln∑_{v,h}e^{−E(v,h)}$$ 其中$v$表示任意的训练样本,而$v^0$则表示一个特定的样本。 $$\\frac {∂L_S}{∂θ}=\\frac {∂\\ln P(v^0)}{∂θ}$$ $$=\\frac ∂{∂θ}(\\ln ∑_he^{−E(v^0,h)})−\\frac ∂{∂θ}(\\ln∑_{v,h}e^{−E(v,h)})$$ $$=−\\frac1{∑_he^{−E(v^0,h)}}∑_he^{−E(v^0,h)}\\frac {∂E(v^0,h)}{∂θ}+\\frac 1{∑_{v,h}e^{−E(v,h)}}∑_{v,h}e^{−E(v,h)}\\frac {∂E(v,h)}{∂θ}$$ $$=−∑_hP(h|v^0)\\frac {∂E(v^0,h)}{∂θ}+∑_{v,h}P(h,v)\\frac {∂E(v,h)}{∂θ}$$ (其中第3个等式左边内条件概率$P(h|v^0)$,因为$\\frac {e^−E(v^0,h)}{∑_he^{−E(v^0,h)}}=\\frac {e^{−E(v^0,h)}/Z}{∑_he^{−E(v^0,h)}/Z}=\\frac{P(v^0,h)}{P(v^0)}=P(h|v^0)$) 上面式子的两个部分的含义是期望——左边是梯度$\\frac{∂E(v0,h)}{∂θ}$在条件概率分布$P(h|v^0)$下的期望;右边是梯度$\\frac {∂E(v,h)}{∂θ}$在联合概率分布$P(h,v)$下的期望。要求前面的条件概率是比较容易一些的,而要求后面的联合概率分布是非常困难的,因为它包含了归一化因子$Z$(对所有可能的取值求和,连续的情况下是积分),因此我们采用一些随机采样来近似求解。把上面式子再推导一步,可以得到, $$\\frac{∂L_S}{∂θ}=−∑_hP(h|v^0)\\frac{∂E(v^0,h)}{∂θ}+∑_vP(v)∑_hP(h|v)\\frac{∂E(v,h)}{∂θ}$$ 因此,我们重点就是需要就算$∑_hP(h|v)\\frac{∂E(v,h)}{∂θ}$,特别的,针对参数$W$,$a$,$b$来说,有 $$∑_hP(h|v)\\frac {∂E(v,h)}{∂w_{ij}}=−∑_hP(h|v)h_iv_j$$ $$=−∑_hP(h_i|v)P(h_{−i}|v)h_iv_j$$ $$=−∑_{h_i}P(h_i|v)∑_{h_−i}P(h_{−i}|v)h_iv_j$$ $$=−∑_{h_i}P(h_i|v)h_iv_j$$ $$=−(P(h_i=1|v)⋅1⋅v_j+P(h_i=0|v)⋅0⋅v_j)$$ $$=−P(h_i=1|v)v_j$$ 类似的,我们可以很容易得到: $$\\sum _hP(h|v)\\frac{∂E(v,h)}{∂a_i}=−v_i$$ $$∑_hP(h|v)\\frac{∂E(v,h)}{∂b_j}=−P(h_i=1|v)$$ 于是,我们很容易得到, $$\\frac {∂\\ln P(v^0)}{∂w_{ij}}=−∑_hP(h|v^0)\\frac {∂E(v^0,h)}{∂w_{ij}}+∑_vP(v)∑_hP(h|v)\\frac {∂E(v,h)}{∂w_{ij}}$$ $$=P(h_i=1|v^0)v^0_j−∑_vP(v)P(h_i=1|v)v_j$$ $$\\frac{∂\\ln P(v^0)}{∂a_i}=v^0_i−∑_vP(v)v_i$$ $$\\frac {∂\\ln P(v^0)}{∂b_i}=P(h_i=1|v^0)−∑_vP(v)P(h_i=1|v)$$ 上面求出了一个样本的梯度,对于$n_s$个样本有 $$\\frac {∂L_S}{∂w_{ij}}=\\sum _{m=1}^{n_s}[P(h_i=1|v^m)v^m_j−∑_vP(v)P(h_i=1|v)v_j]$$ $$\\frac{∂L_S}{∂a_i}=\\sum _{m=1}^{n_s}[v^m_i−∑_vP(v)v_i]$$ $$\\frac {∂L_S}{∂b_i}=\\sum _{m=1}^{n_s}[P(h_i=1|v^m)−∑_vP(v)P(h_i=1|v)]$$ 到这里就比较明确了,主要就是要求出上面三个梯度;但是因为不好直接求概率分布$P(v)$,前面分析过,计算复杂度非常大,因此采用一些随机采样的方法来得到近似的解。看这三个梯度的第二项实际上都是求期望,而我们知道,样本的均值是随机变量期望的无偏估计。 Gibbs Sampling吉布斯采样(Gibbs sampling),是MCMC方法的一种。总的来说,Gibbs采样可以从一个复杂概率分布$P(X)$下生成数据,只要我们知道它每一个分量的相对于其他分量的条件概率$P(X_k|X_{−k})$,就可以对其进行采样。而RBM模型的特殊性,隐藏层神经元的状态只受可见层影响(反之亦然),而且同一层神经元之间是相互独立的,那么就可以根据如下方法依次采样: 也就是说$hi$是以概率$P(h_i|v_0)$为1,其他的都类似。这样当我们迭代足够次以后,我们就可以得到满足联合概率分布$P(v,h)$下的样本$(v,h)$,其中样本$(v)$可以近似认为是$P(v)$下的样本,下图也说明了这个迭代采样的过程:有了样本$(v)$就可以求出上面写到的三个梯度$(\\frac {∂L_S}{∂w_{ij}},\\frac {∂L_S}{∂a_i},\\frac{∂L_S}{∂b_i})$了,用梯度上升就可以对参数进行更新了。(实际中,可以在k次迭代以后,得到样本集合${v}$,比如迭代100次取后面一半,带入上面梯度公式的后半部分计算平均值。) 看起来很简单是不是?但是问题是,每一次gibbs采样过程都需要反复迭代很多次以保证马尔科夫链收敛,而这只是一次梯度更新,多次梯度更新需要反复使用gibbs采样,使得算法运行效率非常低。为了加速RBM的训练过程,Hinton等人提出了对比散度(Contrastive Divergence)方法,大大加快了RBM的训练速度。 Contrastive Divergence我们希望得到$P(v)$分布下的样本,而我们有训练样本,可以认为训练样本就是服从$P(v)$的。因此,就不需要从随机的状态开始gibbs采样,而从训练样本开始。 CD算法大概思路是这样的,从样本集任意一个样本$v^0$开始,经过k次Gibbs采样(实际中k=1往往就足够了),即每一步是: $$h^{t−1}∼P(h|v^{t−1)}$$ $$v^t∼P(v|h^{t−1})$$ 得到样本{v^k}然后对应于三个单样本的梯度,用$v^k$去近似: $$\\frac {∂\\ln P(v)}{∂w_{ij}}\\approx P(h_i=1|v^0)v^0_j-P(h_i=1|v^k)v^k_j$$ $$\\frac {∂\\ln P(v)}{∂a_{i}}\\approx v^0_i-v^k_i $$ $$\\frac {∂\\ln P(v)}{∂b_{i}}\\approx P(h_i =1| v^0)-P(h_i=1|v^k)$$ 上述近似的含义是说,用一个采样出来的样本来近似期望的计算。到这里,我们就可以计算$L_S$的梯度了,上面的CD-k算法是用于在一次梯度更新中计算梯度近似值的。下面给出CD-k的算法执行流程,这里小偷懒一下,就借用截图了[7]。 其中,sample_h_given_v(v,W,a,b),做的事情是这样(sample_v_given_v(h,W,a,b)类似):记$q_j=P(h_j|v),j=1,2,…,n_h$,产生一个[0,1]的随机数$r_j$,对每一个$h_j$,如果$r_j<q_j$,则$h_j=1$,否则$h_j=0$。 OK, 有了CD-k算法,我们也可以总结RMB整个算法了[7], 参考资料Bin的专栏 张春霞,受限波兹曼机简介","link":"/2018/05/22/Restricted Boltzmann Machine/"},{"title":"TF-IDF原理","text":"原文 TF-IDF(Term Frequency-Inverse Document Frequency, 词频-逆文件频率). 是一种用于资讯检索与资讯探勘的常用加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。 上述引用总结就是, 一个词语在一篇文章中出现次数越多, 同时在所有文档中出现次数越少, 越能够代表该文章. 这也就是TF-IDF的含义. 词频 (term frequency, TF) 指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化(一般是词频除以文章总词数), 以防止它偏向长的文件。(同一个词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否。) 但是, 需要注意, 一些通用的词语对于主题并没有太大的作用, 反倒是一些出现频率较少的词才能够表达文章的主题, 所以单纯使用是TF不合适的。权重的设计必须满足:一个词预测主题的能力越强,权重越大,反之,权重越小。所有统计的文章中,一些词只是在其中很少几篇文章中出现,那么这样的词对文章的主题的作用很大,这些词的权重应该设计的较大。IDF就是在完成这样的工作. $$TF_w=\\frac{在某一类中词条w出现的次数}{该类中所有的词条数目}$$ 逆向文件频率 (inverse document frequency, IDF)IDF的主要思想是:如果包含词条t的文档越少, IDF越大,则说明词条具有很好的类别区分能力。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。 $$IDF=log(\\frac{语料库的文档总数}{包含词条w的文档数+1}),(加1以避免分母为0)$$ 某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。 $$TF-IDF=TF∗IDF$$ 实例以《中国的蜜蜂养殖》为例,假定该文长度为1000个词,”中国”、”蜜蜂”、”养殖”各出现20次,则这三个词的”词频”(TF)都为0.02。然后,搜索Google发现,包含”的”字的网页共有250亿张,假定这就是中文网页总数。包含”中国”的网页共有62.3亿张,包含”蜜蜂”的网页为0.484亿张,包含”养殖”的网页为0.973亿张。则它们的逆文档频率(IDF)和TF-IDF如下: 从上表可见,”蜜蜂”的TF-IDF值最高,”养殖”其次,”中国”最低。(如果还计算”的”字的TF-IDF,那将是一个极其接近0的值。)所以,如果只选择一个词,”蜜蜂”就是这篇文章的关键词。","link":"/2018/09/13/TF-IDF原理/"},{"title":"LightGBM使用","text":"xgboost的出现,让数据民工们告别了传统的机器学习算法们:RF、GBM、SVM、LASSO……..。微软推出了一个新的boosting框架,想要挑战xgboost的江湖地位。 顾名思义,lightGBM包含两个关键点:light即轻量级,GBM 梯度提升机。 LightGBM 是一个梯度 boosting 框架,使用基于学习算法的决策树。它可以说是分布式的,高效的,有以下优势: 更快的训练效率 低内存使用 更高的准确率 支持并行化学习 可处理大规模数据 xgboost缺点其缺点,或者说不足之处: 每轮迭代时,都需要遍历整个训练数据多次。如果把整个训练数据装进内存则会限制训练数据的大小;如果不装进内存,反复地读写训练数据又会消耗非常大的时间。 预排序方法(pre-sorted):首先,空间消耗大。这样的算法需要保存数据的特征值,还保存了特征排序的结果(例如排序后的索引,为了后续快速的计算分割点),这里需要消耗训练数据两倍的内存。其次时间上也有较大的开销,在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。 对cache优化不友好。在预排序后,特征对梯度的访问是一种随机访问,并且不同的特征访问的顺序不一样,无法对cache进行优化。同时,在每一层长树的时候,需要随机访问一个行索引到叶子索引的数组,并且不同特征访问的顺序也不一样,也会造成较大的cache miss。 lightGBM特点以上与其说是xgboost的不足,倒不如说是lightGBM作者们构建新算法时着重瞄准的点。解决了什么问题,那么原来模型没解决就成了原模型的缺点。 概括来说,lightGBM主要有以下特点: 基于Histogram的决策树算法 带深度限制的Leaf-wise的叶子生长策略 直方图做差加速 直接支持类别特征(Categorical Feature) Cache命中率优化 基于直方图的稀疏特征优化 多线程优化 前2个特点使我们尤为关注的。 Histogram算法 直方图算法的基本思想:先把连续的浮点特征值离散化成k个整数,同时构造一个宽度为k的直方图。遍历数据时,根据离散化后的值作为索引在直方图中累积统计量,当遍历一次数据后,直方图累积了需要的统计量,然后根据直方图的离散值,遍历寻找最优的分割点。 带深度限制的Leaf-wise的叶子生长策略 Level-wise过一次数据可以同时分裂同一层的叶子,容易进行多线程优化,也好控制模型复杂度,不容易过拟合。但实际上Level-wise是一种低效算法,因为它不加区分的对待同一层的叶子,带来了很多没必要的开销,因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。 Leaf-wise则是一种更为高效的策略:每次从当前所有叶子中,找到分裂增益最大的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。 Leaf-wise的缺点:可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度限制,在保证高效率的同时防止过拟合。 xgboost和lightgbm决策树算法 XGBoost使用的是pre-sorted算法,能够更精确的找到数据分隔点; 首先,对所有特征按数值进行预排序。 其次,在每次的样本分割时,用O(# data)的代价找到每个特征的最优分割点。 最后,找到最后的特征以及分割点,将数据分裂成左右两个子节点。 优缺点: 这种pre-sorting算法能够准确找到分裂点,但是在空间和时间上有很大的开销。 i. 由于需要对特征进行预排序并且需要保存排序后的索引值(为了后续快速的计算分裂点),因此内存需要训练数据的两倍。 ii. 在遍历每一个分割点的时候,都需要进行分裂增益的计算,消耗的代价大。 LightGBM使用的是histogram算法,占用的内存更低,数据分隔的复杂度更低。 其思想是将连续的浮点特征离散成k个离散值,并构造宽度为k的Histogram。然后遍历训练数据,统计每个离散值在直方图中的累计统计量。在进行特征选择时,只需要根据直方图的离散值,遍历寻找最优的分割点。 Histogram 算法的优缺点: Histogram算法并不是完美的。由于特征被离散化后,找到的并不是很精确的分割点,所以会对结果产生影响。但在实际的数据集上表明,离散化的分裂点对最终的精度影响并不大,甚至会好一些。原因在于decision tree本身就是一个弱学习器,采用Histogram算法会起到正则化的效果,有效地防止模型的过拟合。 时间上的开销由原来的O(#data * #features)降到O(k * #features)。由于离散化,#bin远小于#data,因此时间上有很大的提升。 Histogram算法还可以进一步加速。一个叶子节点的Histogram可以直接由父节点的Histogram和兄弟节点的Histogram做差得到。一般情况下,构造Histogram需要遍历该叶子上的所有数据,通过该方法,只需要遍历Histogram的k个捅。速度提升了一倍。 决策树生长策略 XGBoost采用的是按层生长level(depth)-wise生长策略,如Figure 1所示,能够同时分裂同一层的叶子,从而进行多线程优化,不容易过拟合;但不加区分的对待同一层的叶子,带来了很多没必要的开销。因为实际上很多叶子的分裂增益较低,没必要进行搜索和分裂。 LightGBM采用leaf-wise生长策略,如Figure 2所示,每次从当前所有叶子中找到分裂增益最大(一般也是数据量最大)的一个叶子,然后分裂,如此循环。因此同Level-wise相比,在分裂次数相同的情况下,Leaf-wise可以降低更多的误差,得到更好的精度。Leaf-wise的缺点是可能会长出比较深的决策树,产生过拟合。因此LightGBM在Leaf-wise之上增加了一个最大深度的限制,在保证高效率的同时防止过拟合。 网络通信优化 XGBoost由于采用pre-sorted算法,通信代价非常大,所以在并行的时候也是采用histogram算法;LightGBM采用的histogram算法通信代价小,通过使用集合通信算法,能够实现并行计算的线性加速。 LightGBM支持类别特征 实际上大多数机器学习工具都无法直接支持类别特征,一般需要把类别特征,转化one-hotting特征,降低了空间和时间的效率。而类别特征的使用是在实践中很常用的。基于这个考虑,LightGBM优化了对类别特征的支持,可以直接输入类别特征,不需要额外的0/1展开。并在决策树算法上增加了类别特征的决策规则。 lightGBM调参所有的参数含义,参考:http://lightgbm.apachecn.org/cn/latest/Parameters.html 调参过程: num_leaves LightGBM使用的是leaf-wise的算法,因此在调节树的复杂程度时,使用的是num_leaves而不是max_depth。大致换算关系:$num_leaves = 2^{(max_depth)}$; 样本分布非平衡数据集:可以param[‘is_unbalance’]=’true’; Bagging参数:bagging_fraction+bagging_freq(必须同时设置)、feature_fraction。bagging_fraction可以使bagging的更快的运行出结果,feature_fraction设置在每次迭代中使用特征的比例; min_data_in_leaf、min_sum_hessian_in_leaf:调大它的值可以防止过拟合,它的值通常设置的比较大。 sklearn接口形式的LightGBM示例这里主要以sklearn的使用形式来使用lightgbm算法,包含建模,训练,预测,网格参数优化。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849import lightgbm as lgbimport pandas as pdfrom sklearn.metrics import mean_squared_errorfrom sklearn.model_selection import GridSearchCVfrom sklearn.datasets import load_irisfrom sklearn.model_selection import train_test_splitfrom sklearn.datasets import make_classification# 加载数据print('Load data...')iris = load_iris()data=iris.datatarget = iris.targetX_train,X_test,y_train,y_test =train_test_split(data,target,test_size=0.2)# df_train = pd.read_csv('../regression/regression.train', header=None, sep='\\t')# df_test = pd.read_csv('../regression/regression.test', header=None, sep='\\t')# y_train = df_train[0].values# y_test = df_test[0].values# X_train = df_train.drop(0, axis=1).values# X_test = df_test.drop(0, axis=1).valuesprint('Start training...')# 创建模型,训练模型gbm = lgb.LGBMRegressor(objective='regression',num_leaves=31,learning_rate=0.05,n_estimators=20)gbm.fit(X_train, y_train,eval_set=[(X_test, y_test)],eval_metric='l1',early_stopping_rounds=5)print('Start predicting...')# 测试机预测y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration_)# 模型评估print('The rmse of prediction is:', mean_squared_error(y_test, y_pred) ** 0.5)# feature importancesprint('Feature importances:', list(gbm.feature_importances_))# 网格搜索,参数优化estimator = lgb.LGBMRegressor(num_leaves=31)param_grid = { 'learning_rate': [0.01, 0.1, 1], 'n_estimators': [20, 40]}gbm = GridSearchCV(estimator, param_grid)gbm.fit(X_train, y_train)print('Best parameters found by grid search are:', gbm.best_params_) 原生形式使用lightgbm123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657# coding: utf-8# pylint: disable = invalid-name, C0111import jsonimport lightgbm as lgbimport pandas as pdfrom sklearn.metrics import mean_squared_errorfrom sklearn.datasets import load_irisfrom sklearn.model_selection import train_test_splitfrom sklearn.datasets import make_classificationiris = load_iris()data=iris.datatarget = iris.targetX_train,X_test,y_train,y_test =train_test_split(data,target,test_size=0.2)# 加载你的数据# print('Load data...')# df_train = pd.read_csv('../regression/regression.train', header=None, sep='\\t')# df_test = pd.read_csv('../regression/regression.test', header=None, sep='\\t')## y_train = df_train[0].values# y_test = df_test[0].values# X_train = df_train.drop(0, axis=1).values# X_test = df_test.drop(0, axis=1).values# 创建成lgb特征的数据集格式lgb_train = lgb.Dataset(X_train, y_train)lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train)# 将参数写成字典下形式params = { 'task': 'train', 'boosting_type': 'gbdt', # 设置提升类型 'objective': 'regression', # 目标函数 'metric': {'l2', 'auc'}, # 评估函数 'num_leaves': 31, # 叶子节点数 'learning_rate': 0.05, # 学习速率 'feature_fraction': 0.9, # 建树的特征选择比例 'bagging_fraction': 0.8, # 建树的样本采样比例 'bagging_freq': 5, # k 意味着每 k 次迭代执行bagging 'verbose': 1 # <0 显示致命的, =0 显示错误 (警告), >0 显示信息}print('Start training...')# 训练 cv and traingbm = lgb.train(params,lgb_train,num_boost_round=20,valid_sets=lgb_eval,early_stopping_rounds=5)print('Save model...')# 保存模型到文件gbm.save_model('model.txt')print('Start predicting...')# 预测数据集y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)# 评估模型print('The rmse of prediction is:', mean_squared_error(y_test, y_pred) ** 0.5) 参数速查: xgb lgb xgb.sklearn lgb.sklearn booster=’gbtree’ boosting=’gbdt’ booster=’gbtree’ boosting_type=’gbdt’ objective=’binary:logistic’ application=’binary’ objective=’binary:logistic’ objective=’binary’ max_depth=7 num_leaves=2**7 max_depth=7 num_leaves=2**7 eta=0.1 learning_rate=0.1 learning_rate=0.1 learning_rate=0.1 num_boost_round=10 num_boost_round=10 n_estimators=10 n_estimators=10 gamma=0 min_split_gain=0.0 gamma=0 min_split_gain=0.0 min_child_weight=5 min_child_weight=5 min_child_weight=5 min_child_weight=5 subsample=1 bagging_fraction=1 subsample=1.0 subsample=1.0 colsample_bytree=1.0 feature_fraction=1 colsample_bytree=1.0 colsample_bytree=1.0 alpha=0 lambda_l1=0 reg_alpha=0.0 reg_alpha=0.0 lambda=1 lambda_l2=0 reg_lambda=1 reg_lambda=0.0 scale_pos_weight=1 scale_pos_weight=1 scale_pos_weight=1 scale_pos_weight=1 seed bagging_seed feature_fraction_seed random_state=888 random_state=888 nthread num_threads n_jobs=4 n_jobs=4 evals valid_sets eval_set eval_set eval_metric metric eval_metric eval_metric early_stopping_rounds early_stopping_rounds early_stopping_rounds early_stopping_rounds verbose_eval verbose_eval verbose verbose","link":"/2018/09/13/LightGBM使用/"},{"title":"XGBoost完全指南","text":"原文地址:Complete Guide to Parameter Tuning in XGBoost (with codes in Python) XGBoost(eXtreme Gradient Boosting)是Gradient Boosting算法的一个优化的版本。因为我在前一篇文章,基于Python的Gradient Boosting算法参数调整完全指南,里面已经涵盖了Gradient Boosting算法的很多细节了。我强烈建议大家在读本篇文章之前,把那篇文章好好读一遍。它会帮助你对Boosting算法有一个宏观的理解,同时也会对GBM的参数调整有更好的体会。 XGBoost的优势XGBoost算法可以给预测模型带来能力的提升。当我对它的表现有更多了解的时候,当我对它的高准确率背后的原理有更多了解的时候,我发现它具有很多优势: 1. 正则化 标准GBM的实现没有像XGBoost这样的正则化步骤。正则化对减少过拟合也是有帮助的。 实际上,XGBoost以“正则化提升(regularized boosting)”技术而闻名。 2. 并行处理 XGBoost可以实现并行处理,相比GBM有了速度的飞跃。 不过,众所周知,Boosting算法是顺序处理的,它怎么可能并行呢?每一课树的构造都依赖于前一棵树,那具体是什么让我们能用多核处理器去构造一个树呢?我希望你理解了这句话的意思。如果你希望了解更多,点击这个链接。 XGBoost 也支持Hadoop实现。 3. 高度的灵活性 XGBoost 允许用户定义自定义优化目标和评价标准 它对模型增加了一个全新的维度,所以我们的处理不会受到任何限制。 4. 缺失值处理 XGBoost内置处理缺失值的规则。 用户需要提供一个和其它样本不同的值,然后把它作为一个参数传进去,以此来作为缺失值的取值。XGBoost在不同节点遇到缺失值时采用不同的处理方法,并且会学习未来遇到缺失值时的处理方法。 5. 剪枝 当分裂时遇到一个负损失时,GBM会停止分裂。因此GBM实际上是一个贪心算法。 XGBoost会一直分裂到指定的最大深度(max_depth),然后回过头来剪枝。如果某个节点之后不再有正值,它会去除这个分裂。 这种做法的优点,当一个负损失(如-2)后面有个正损失(如+10)的时候,就显现出来了。GBM会在-2处停下来,因为它遇到了一个负值。但是XGBoost会继续分裂,然后发现这两个分裂综合起来会得到+8,因此会保留这两个分裂。 6. 内置交叉验证 XGBoost允许在每一轮boosting迭代中使用交叉验证。因此,可以方便地获得最优boosting迭代次数。 而GBM使用网格搜索,只能检测有限个值。 7. 在已有的模型基础上继续 XGBoost可以在上一轮的结果上继续训练。这个特性在某些特定的应用上是一个巨大的优势。 sklearn中的GBM的实现也有这个功能,两种算法在这一点上是一致的。 深入了解相关信息,可参考:XGBoost Guide - Introduce to Boosted TreesWords from the Auther of XGBoost [Viedo] XGBoost的参数XGBoost的作者把所有的参数分成了三类:1、通用参数:宏观函数控制。2、Booster参数:控制每一步的booster(tree/regression)。3、学习目标参数:控制训练目标的表现。 通用参数 这些参数用来控制XGBoost的宏观功能。 1、booster[默认gbtree] 选择每次迭代的模型,有两种选择:gbtree:基于树的模型gbliner:线性模型 2、silent[默认0] 当这个参数值为1时,静默模式开启,不会输出任何信息。 一般这个参数就保持默认的0,因为这样能帮我们更好地理解模型。 3、nthread[默认值为最大可能的线程数] 这个参数用来进行多线程控制,应当输入系统的核数。 如果你希望使用CPU全部的核,那就不要输入这个参数,算法会自动检测它。 还有两个参数,XGBoost会自动设置。 booster参数尽管有两种booster可供选择,我这里只介绍tree booster,因为它的表现远远胜过linear booster,所以linear booster很少用到。 1. eta[默认0.3] 和GBM中的 learning rate 参数类似。 通过减少每一步的权重,可以提高模型的鲁棒性。 典型值为0.01-0.2。 2. min_child_weight[默认1] 决定最小叶子节点样本权重和。 和GBM的 min_child_leaf 参数类似,但不完全一样。XGBoost的这个参数是最小样本权重的和,而GBM参数是最小样本总数。 这个参数用于避免过拟合。当它的值较大时,可以避免模型学习到局部的特殊样本。 但是如果这个值过高,会导致欠拟合。这个参数需要使用CV来调整。 3. max_depth[默认6] 和GBM中的参数相同,这个值为树的最大深度。 这个值也是用来避免过拟合的。max_depth越大,模型会学到更具体更局部的样本。 需要使用CV函数来进行调优。 典型值:3-10 4. max_leaf_nodes 树上最大的节点或叶子的数量。 可以替代max_depth的作用。因为如果生成的是二叉树,一个深度为n的树最多生成$n^2$个叶子。 如果定义了这个参数,GBM会忽略max_depth参数。 5. gamma[默认0] 在节点分裂时,只有分裂后损失函数的值下降了,才会分裂这个节点。Gamma指定了节点分裂所需的最小损失函数下降值。 这个参数的值越大,算法越保守。这个参数的值和损失函数息息相关,所以是需要调整的。 6. max_delta_step[默认0] 这参数限制每棵树权重改变的最大步长。如果这个参数的值为0,那就意味着没有约束。如果它被赋予了某个正值,那么它会让这个算法更加保守。 通常,这个参数不需要设置。但是当各类别的样本十分不平衡时,它对逻辑回归是很有帮助的。 这个参数一般用不到,但是你可以挖掘出来它更多的用处。 7. subsample[默认1] 和GBM中的subsample参数一模一样。这个参数控制对于每棵树,随机采样的比例。 减小这个参数的值,算法会更加保守,避免过拟合。但是,如果这个值设置得过小,它可能会导致欠拟合。 典型值:0.5-1 8. colsample_bytree[默认1] 和GBM里面的max_features参数类似。用来控制每棵随机采样的列数的占比(每一列是一个特征)。 典型值:0.5-1 9. colsample_bylevel[默认1] 用来控制树的每一级的每一次分裂,对列数的采样的占比。 我个人一般不太用这个参数,因为subsample参数和colsample_bytree参数可以起到相同的作用。但是如果感兴趣,可以挖掘这个参数更多的用处。 10. lambda[默认1] 权重的L2正则化项。(和Ridge regression类似)。 这个参数是用来控制XGBoost的正则化部分的。虽然大部分数据科学家很少用到这个参数,但是这个参数在减少过拟合上还是可以挖掘出更多用处的。 11. alpha[默认1] 权重的L1正则化项。(和Lasso regression类似)。 可以应用在很高维度的情况下,使得算法的速度更快。 12. scale_pos_weight[默认1] 在各类别样本十分不平衡时,把这个参数设定为一个正值,可以使算法更快收敛。 学习目标参数这个参数用来控制理想的优化目标和每一步结果的度量方法 1. objective[默认reg:linear]这个参数定义需要被最小化的损失函数。最常用的值有: binary:logistic 二分类的逻辑回归,返回预测的概率(不是类别)。 multi:softmax 使用softmax的多分类器,返回预测的类别(不是概率)。 在这种情况下,你还需要多设一个参数:num_class(类别数目)。 multi:softprob 和multi:softmax参数一样,但是返回的是每个数据属于各个类别的概率。 2. eval_metric[默认值取决于objective参数的取值] 对于有效数据的度量方法。 对于回归问题,默认值是rmse,对于分类问题,默认值是error。 典型值有: rmse 均方根误差 mae 平均绝对误差 logloss 负对数似然函数值 error 二分类错误率(阈值为0.5) merror 多分类错误率 mlogloss 多分类logloss损失函数 auc 曲线下面积 3. seed(默认0) 随机数的种子 设置它可以复现随机数据的结果,也可以用于调整参数","link":"/2018/09/13/XGBoost完全指南/"},{"title":"Beam Search算法过程","text":"在sequence2sequence模型中,beam search的方法只用在测试的情况,因为在训练过程中,每一个decoder的输出是有正确答案的,也就不需要beam search去加大输出的准确率。 假设现在我们用机器翻译作为例子来说明, 我们需要翻译中文“我是中国人”—>英文“I am Chinese” 假设我们的词表大小只有三个单词就是I am Chinese。那么如果我们的beam size为2的话,我们现在来解释, 如下图所示,我们在decoder的过程中,有了beam search方法后,在第一次的输出,我们选取概率最大的”I”和”am”两个单词,而不是只挑选一个概率最大的单词。 然后接下来我们要做的就是,把“I”单词作为下一个decoder的输入算一遍得到y2的输出概率分布,把“am”单词作为下一个decoder的输入算一遍也得到y2的输出概率分布。 比如将“I”单词作为下一个decoder的输入算一遍得到y2的输出概率分布如下: 比如将“am”单词作为下一个decoder的输入算一遍得到y2的输出概率分布如下: 那么此时我们由于我们的beam size为2,也就是我们只能保留概率最大的两个序列,此时我们可以计算所有的序列概率: “I I” = 0.40.3 “I am” = 0.40.6 “I Chinese” = 0.4*0.1 “am I” = 0.50.3 “am am” = 0.50.3 “am Chinese” = 0.5*0.4 我们很容易得出俩个最大概率的序列为 “I am”和“am Chinese”,然后后面会不断重复这个过程,直到遇到结束符为止。 最终输出2个得分最高的序列。 这就是seq2seq中的beam search算法过程,但是可能有些同学有一个疑问,就是但i-1时刻选择的单词不同的时候,下一时刻的输出概率分布为什么会改变? 这是由于解码的过程中,第i时刻的模型的输入,包括了第i-1时刻模型的输出,那么很自然在第i-1时刻模型的输出不同的时候,就会导致下一时刻模型的输出概率分布会不同,因为第 i-1时刻的输出作为参数影响了后一时刻模型的学习。 如下图用了一个slides的法语翻译为英文的例子,可以更容易理解上面的解释。 致谢: 凌志、刘洋、皓宇、萧瑟师兄 参考:谁能解释下seq2seq中的beam search算法过程?","link":"/2018/04/16/seq2seq中的beam search算法过程/"},{"title":"理解ResNet、Inception与Xception","text":"转载自机器之心,原文作者Joyce Xu 过去几年来,计算机视觉领域中深度学习的诸多进展都可以归结到几种神经网络架构。除开其中的所有数学内容、代码和实现细节,我想探索一个简单的问题:这些模型的工作方式和原因是什么? 在本文写作时,Keras 已经将这 6 种预训练模型集成到了库中: VGG16 VGG19 ResNet50 Inception v3 Xception MobileNet VGG 网络以及从 2012 年以来的 AlexNet 都遵循现在的基本卷积网络的原型布局:一系列卷积层、最大池化层和激活层,最后还有一些全连接的分类层。MobileNet 本质上是为移动应用优化后的 Xception 架构的流线型(streamline)版本。但剩下的三个却真正重新定义了我们看待神经网络的方式。 本文的剩余部分将聚焦于 ResNet、Inception 和 Xception 架构背后的直观原理,并将解释为什么它们成为了计算机视觉领域后续许多成果的构建模块。 ResNetResNet 诞生于一个美丽而简单的观察:为什么非常深度的网络在增加更多层时会表现得更差? 直觉上推测,更深度的网络不会比更浅度的同类型网络表现更差吧,至少在训练时间上是这样(当不存在过拟合的风险时)。让我们进行一个思想实验,假设我们已经构建了一个 n 层网络,并且实现了一定准确度。那么一个 n+1 层网络至少也应该能够实现同样的准确度——只要简单复制前面 n 层,再在最后一层增加一层恒等映射就可以了。类似地,n+2、n+3 和 n+4 层的网络都可以继续增加恒等映射,然后实现同样的准确度。但是在实际情况下,这些更深度的网络基本上都会表现得更差。 ResNet 的作者将这些问题归结成了一个单一的假设:直接映射是难以学习的。而且他们提出了一种修正方法:不再学习从 x 到 H(x) 的基本映射关系,而是学习这两者之间的差异,也就是「残差(residual)」。然后,为了计算 H(x),我们只需要将这个残差加到输入上即可。 假设残差为 F(x)=H(x)-x,那么现在我们的网络不会直接学习 H(x) 了,而是学习 F(x)+x。 这就带来了你可能已经见过的著名 ResNet(残差网络)模块: ResNet 的每一个「模块(block)」都由一系列层和一个「捷径(shortcut)」连接组成,这个「捷径」将该模块的输入和输出连接到了一起。然后在元素层面上执行「加法(add)」运算,如果输入和输出的大小不同,那就可以使用零填充或投射(通过 1×1 卷积)来得到匹配的大小。 回到我们的思想实验,这能大大简化我们对恒等层的构建。直觉上就能知道,比起从头开始学习一个恒等变换,学会使 F(x) 为 0 并使输出仍为 x 要容易得多。一般来说,ResNet 会给层一个「参考」点 x,以 x 为基础开始学习。 这一想法在实践中的效果好得让人吃惊。在此之前,深度神经网络常常会有梯度消失问题的困扰,即来自误差函数的梯度信号会在反向传播回更早的层时指数级地下降。本质上讲,在误差信号反向回到更早的层时,它们会变得非常小以至于网络无法学习。但是,因为 ResNet 的梯度信号可以直接通过捷径连接回到更早的层,所以我们一下子就可以构建 50 层、101 层、152 层甚至 1000 层以上的网络了,而且它们的表现依然良好。那时候,这在当时最佳的基础上实现了巨大的飞跃——这个 22 层的网络赢得了 ILSVRC 2014 挑战赛。 ResNet 是神经网络领域我个人最喜欢的进展之一。很多深度学习论文都是通过对数学、优化和训练过程进行调整而取得一点点微小的进步,而没有思考模型的底层任务。ResNet 则从根本上改变了我们对神经网络及其学习方式的理解。 有趣的事实: 上面提到的 1000 多层的网络是开源的!我并不推荐你尝试再训练,但如果你就是要上:https://github.com/KaimingHe/resnet-1k-layers 如果你觉得这有用而且有点跃跃欲试,我最近将 ResNet50 移植到了开源的 Clojure ML 库 Cortex 中:https://github.com/thinktopic/cortex。你可以试试,看与 Keras 比较如何! Inception如果 ResNet 是为了更深,那么 Inception 家族就是为了更宽。Inception 的作者对训练更大型网络的计算效率尤其感兴趣。换句话说:我们怎样在不增加计算成本的前提下扩展神经网络? Inception 最早的论文关注的是一种用于深度网络的新型构建模块,现在这一模块被称为「Inception module」。究其核心,这种模块源自两种思想见解的交汇。 第一个见解与对层的操作有关。在传统的卷积网络中,每一层都会从之前的层提取信息,以便将输入数据转换成更有用的表征。但是,不同类型的层会提取不同种类的信息。5×5 卷积核的输出中的信息就和 3×3 卷积核的输出不同,又不同于最大池化核的输出……在任意给定层,我们怎么知道什么样的变换能提供最「有用」的信息呢? 见解 1:为什么不让模型选择? Inception 模块会并行计算同一输入映射上的多个不同变换,并将它们的结果都连接到单一一个输出。换句话说,对于每一个层,Inception 都会执行 5×5 卷积变换、3×3 卷积变换和最大池化。然后该模型的下一层会决定是否以及怎样使用各个信息。 这种模型架构的信息密度更大了,这就带来了一个突出的问题:计算成本大大增加。不仅大型(比如 5×5)卷积过滤器的固有计算成本高,并排堆叠多个不同的过滤器更会极大增加每一层的特征映射的数量。而这种计算成本增长就成为了我们模型的致命瓶颈。 想一下,每额外增加一个过滤器,我们就必须对所有输入映射进行卷积运算以计算单个输出。如下图所示:从单个过滤器创建一个输出映射涉及到在之前一层的每个单个映射上执行计算。 假设这里有 M 个输入映射。增加一个过滤器就意味着要多卷积 M 次映射;增加 N 个过滤器就意味着要多卷积 N*M 次映射。换句话说,正如作者指出的那样:「过滤器数量的任何统一增长都会导致计算量的 4 倍增长。」我们的朴素 Inception 模块只是将过滤器的数量增加了三四倍。但从计算成本上看,这简直就是一场大灾难。 这就涉及到了见解 2:使用 1×1 卷积来执行降维。为了解决上述计算瓶颈,Inception 的作者使用了 1×1 卷积来「过滤」输出的深度。一个 1×1 卷积一次仅查看一个值,但在多个通道上,它可以提取空间信息并将其压缩到更低的维度。比如,使用 20 个 1×1 过滤器,一个大小为 64×64×100(具有 100 个特征映射)的输入可以被压缩到 64×64×20。通过减少输入映射的数量,Inception 可以将不同的层变换并行地堆叠到一起,从而得到既深又宽(很多并行操作)的网络。 这能达到多好的效果?Inception 的第一个版本是 GoogLeNet,也就是前面提及的赢得了 ILSVRC 2014 比赛的 22 层网络。一年之后,研究者在第二篇论文中发展出了 Inception v2 和 v3,并在原始版本上实现了多种改进——其中最值得一提的是将更大的卷积重构成了连续的更小的卷积,让学习变得更轻松。比如在 v3 中,5×5 卷积被替换成了两个 连续的 3×3 卷积。 Inception 很快就变成了一种具有决定性意义的模型架构。最新的版本 Inception v4 甚至将残差连接放进了每一个模组中,创造出了一种 Inception-ResNet 混合结构。但更重要的是,Inception 展现了经过良好设计的「网中有网」架构的能力,让神经网络的表征能力又更上了一层楼。 有趣的事实: 最早的 Inception 论文确实引用了来自电影《盗梦空间(Inception)》的「我们需要更深」的互联网模因作为其名字的来源,参见:http://knowyourmeme.com/memes/we-need-to-go-deeper。这肯定是 knowyourmeme.com (http://knowyourmeme.com/) 第一次出现在一篇谷歌论文的参考文献里。 第二篇 Inception 论文(提出 v2 和 v3)是在最早的 ResNet 论文发布之后的第二天发布的。2015 年 12 月真是深度学习的好日子。 XceptionXception 表示「extreme inception」。和前面两种架构一样,它重塑了我们看待神经网络的方式——尤其是卷积网络。而且正如其名字表达的那样,它将 Inception 的原理推向了极致。 它的假设是:「跨通道的相关性和空间相关性是完全可分离的,最好不要联合映射它们。」 这是什么意思?在传统的卷积网络中,卷积层会同时寻找跨空间和跨深度的相关性。让我们再看一下标准的卷积层: 在上图中,过滤器同时考虑了一个空间维度(每个 2×2 的彩色方块)和一个跨通道或「深度」维度(4 个方块的堆叠)。在输入图像的输入层,这就相当于一个在所有 3 个 RGB 通道上查看一个 2×2 像素块的卷积过滤器。那问题来了:我们有什么理由去同时考虑图像区域和通道? 在 Inception 中,我们开始将两者稍微分开。我们使用 1×1 的卷积将原始输入投射到多个分开的更小的输入空间,而且对于其中的每个输入空间,我们都使用一种不同类型的过滤器来对这些数据的更小的 3D 模块执行变换。Xception 更进一步。不再只是将输入数据分割成几个压缩的数据块,而是为每个输出通道单独映射空间相关性,然后再执行 1×1 的深度方面的卷积来获取跨通道的相关性。 其作者指出这本质上相当于一种已有的被称为「深度方面可分的卷积(depthwise separable convolution)」的运算,它包含一个深度方面的卷积(一个为每个通道单独执行的空间卷积),后面跟着一个逐点的卷积(一个跨通道的 1×1 卷积)。我们可以将其看作是首先求跨一个 2D 空间的相关性,然后再求跨一个 1D 空间的相关性。可以看出,这种 2D+1D 映射学起来比全 3D 映射更加简单。 而且这种做法是有效的!在 ImageNet 数据集上,Xception 的表现稍稍优于 Inception v3,而且在一个有 17000 类的更大规模的图像分类数据集上的表现更是好得多。最重要的是,它的模型参数的数量和 Inception 一样多,说明它的计算效率也更高。Xception 非常新(2017 年 4 月才公开),但正如前面提到的那样,这个架构已经在通过 MobileNet 助力谷歌的移动视觉应用了。 有趣的事实: Xception 的作者也是 Keras 的作者。Francois Chollet 是真正的大神。 未来发展这就是 ResNet、Inception 和 Xception!我坚信我们需要对这些网络有很好的直观理解,因为它们在研究界和产业界的应用越来越普遍。我们甚至可以通过所谓的迁移学习将它们用在我们自己的应用中。 迁移学习是一种机器学习技术,即我们可以将一个领域的知识(比如 ImageNet)应用到目标领域,从而可以极大减少所需要的数据点。在实践中,这通常涉及到使用来自 ResNet、Inception 等的预训练的权重初始化模型,然后要么将其用作特征提取器,要么就在一个新数据集上对最后几层进行微调。使用迁移学习,这些模型可以在任何我们想要执行的相关任务上得到重新利用,从自动驾驶汽车的目标检测到为视频片段生成描述。","link":"/2017/12/18/理解ResNet、Inception与Xception/"},{"title":"梯度下降法","text":"梯度下降法(Gradient descent)是一个一阶最优化算法,通常也称最速下降法。要使用梯度下降法找到一个函数的局部极小值,必须向函数上当前点对应梯度(或者是近似梯度)的反方向的规定步长距离点进行迭代搜索。如果相反地向梯度正方向迭代进行搜索,则会接近函数的局部极大值点,这个过程被称为梯度上升法。 描述梯度下降法基于以下的观察:如果实值函数 $F(x)$在点$a$处可微且有定义,那么函数$F(x)$在$a$点沿着梯度相反的方向$-\\nabla F(a)$下降最快。 因而,如果$b=a-\\gamma \\nabla F(a)$ ,对于$\\gamma >0$为一个够小数值时成立,那么$F(a)\\geq F(b)$ 。 考虑到这一点,我们可以从函数$F$代码局部极小值的初始估计$x_0$出发,并考虑如下序列$x_0, x_1, x_2, \\ldots$使得$x_{n+1}=x_n - \\gamma_n\\nabla F(x_n), n\\geq 0$ ,因此可得到$F(x_0)\\ge F(x_1) \\ge F(x_2) \\ge \\cdots$ 。 如果顺利的话序列$(x_n)$收敛到期望的极值。每次的迭代步长$\\gamma$可以改变。 下面的图片示例了这一过程,这里假设$F$定义在平面上,并且函数图像是一个碗形。蓝色的曲线是等高线,即函数$F$为常数的集合构成的曲线。红色的箭头指向该点梯度的反方向。需要注意的是,某一点处的梯度方向与通过该点的等高线垂直。沿着梯度下降方向,将最终到达碗底,即函数$F$值最小的点。 梯度下降法处理一些复杂的非线性函数会出现问题,例如Rosenbrock函数$f(x, y) =(1-x)^2 + 100(y-x^2)^2$ 。其最小值在$(x,y)=(1,1)$处,数值为$f(x,y)=0$。但是此函数具有狭窄弯曲的山谷,最小值$(x,y)=(1,1)$就在这些山谷之中,并且谷底很平。优化过程是之字形的向极小值点靠近,速度非常缓慢。利用梯度下降法求解需要很多次的迭代。 梯度下降法实现简单,当目标函数是凸函数时,梯度下降法的解是全局解。一般情况下,其解不保证是全局最优解,梯度下降法的速度也未必是最快的。梯度下降法的缺点包括: 靠近极小值时速度减慢。 直线搜索可能会产生一些问题。 可能会“之字型”地下降。 Batch Gradient Descent在优化目标函数的时候,Batch Gradient Descent(BGD)是先计算整个数据集上的梯度,然后再进行更新操作。对于参数$\\theta$来说,每更新一次其中的某一位权重$\\theta_j$,BGD都需要遍历整个数据集。 对于目标函数$h_θ(x)$用公式来表示就是$θ_j:=θ_j+\\alpha \\sum_{i=1}^m(y^i−h_θ(x^i))x_j^i$ 其中的$(y^i−h_θ(x^i))x^i_j$其实就是对于训练样例$x^i$的$j$属性的梯度,$m$是训练集的大小。从上式中可以看到,BGD是对整个数据集进行扫描,然后计算整体梯度($\\sum$求和过程),再进行更新。其实,这才是真正的梯度. BGD的优点在于对于凸问题,它是能够保证收敛到全局最优点的。而缺点就是,计算量很大,计算每一位的权重都要遍历整个数据集,这代价未免太大了,计算量是无法接受的。随之而来的另外一个缺点就是BGD是无法进行online训练的,它必须要知道全部的训练集的情况下才能进行训练,这对于一些线上系统也是一个问题。 Stochastic Gradient DescentSGD是对BGD的一个改进方案,改变之处在更新时不需要遍历整个数据集,而是每一个实例都进行更新。具体公式$θ_j:=θ_j+α(y^i−h_θ(x^i))x^i_j$ 比较上式和BGD的公式,我们可以发现区别就在省略了求和过程$\\sum$,也就是说更新权重的时候,不需要计算整体的梯度,而是仅仅依靠当前实例的梯度进行更新。 如此改变之后,速度明显提高了很多,但是这也是有风险的。由于进行频繁的梯度更新,很有可能直接跳过了最优点。因此,SGD实际上是无法保证收敛到全局最优点的,而且不是那么的稳定。 Mini-Batch Gradient Descent而Mini-Batch是对上述两种策略的一种中和,它的基本思想就是从整个训练集上选取一个子集,对这个自己进行BGD的更新。具体公式可以表示为:$θ_j:=θ_j+α\\sum_{i=1}^n(y^i−h_θ(x^i))x^i_j$ 与BGD的式子相比,会发现唯一的区别在于求和时的项数不一样,此处的$n$不再是训练集的大小,而是一个小于或等于$m$的数,通常范围在于32-256(一般取2的整数幂)。 简单来说,先把大小为$m$的训练集分为大小为$n$的$m_n$个子集(一般最后一个batch的样本个数小于$n$),每次读入一个子集进行梯度计算,更新权重。 相比SGD来说,它更加稳定;相比BGD来说,它计算量较小。 总结 BGD SGD mini-Batch 训练集 固定 固定 固定 单次迭代样本数 整个训练集 固定 训练集的子集 算法复杂度 高 低 一般 收敛性 稳定 不稳定 较稳定 参考梯度下降法wiki 三种梯度下降法 最优化方法:梯度下降","link":"/2018/01/28/梯度下降法/"}],"tags":[{"name":"cluster","slug":"cluster","link":"/tags/cluster/"},{"name":"6D Object Detection","slug":"6D-Object-Detection","link":"/tags/6D-Object-Detection/"},{"name":"Pose Estimation","slug":"Pose-Estimation","link":"/tags/Pose-Estimation/"},{"name":"Autoencoder","slug":"Autoencoder","link":"/tags/Autoencoder/"},{"name":"attention","slug":"attention","link":"/tags/attention/"},{"name":"LSTM","slug":"LSTM","link":"/tags/LSTM/"},{"name":"Markdown","slug":"Markdown","link":"/tags/Markdown/"},{"name":"Deep Forest","slug":"Deep-Forest","link":"/tags/Deep-Forest/"},{"name":"gcForest","slug":"gcForest","link":"/tags/gcForest/"},{"name":"Ensemble","slug":"Ensemble","link":"/tags/Ensemble/"},{"name":"OPTICS","slug":"OPTICS","link":"/tags/OPTICS/"},{"name":"Numpy","slug":"Numpy","link":"/tags/Numpy/"},{"name":"Matplotlib","slug":"Matplotlib","link":"/tags/Matplotlib/"},{"name":"file operation","slug":"file-operation","link":"/tags/file-operation/"},{"name":"RBM","slug":"RBM","link":"/tags/RBM/"},{"name":"CD","slug":"CD","link":"/tags/CD/"},{"name":"MCMC","slug":"MCMC","link":"/tags/MCMC/"},{"name":"NLP","slug":"NLP","link":"/tags/NLP/"},{"name":"Word Vector","slug":"Word-Vector","link":"/tags/Word-Vector/"},{"name":"GBDT","slug":"GBDT","link":"/tags/GBDT/"},{"name":"boosting","slug":"boosting","link":"/tags/boosting/"},{"name":"ensemble learning","slug":"ensemble-learning","link":"/tags/ensemble-learning/"},{"name":"ResNet","slug":"ResNet","link":"/tags/ResNet/"},{"name":"CNN","slug":"CNN","link":"/tags/CNN/"},{"name":"Inception","slug":"Inception","link":"/tags/Inception/"},{"name":"Xception","slug":"Xception","link":"/tags/Xception/"},{"name":"Gradient descent","slug":"Gradient-descent","link":"/tags/Gradient-descent/"},{"name":"BGD","slug":"BGD","link":"/tags/BGD/"},{"name":"SGD","slug":"SGD","link":"/tags/SGD/"},{"name":"Mini-Batch","slug":"Mini-Batch","link":"/tags/Mini-Batch/"}],"categories":[{"name":"Machine Learning","slug":"Machine-Learning","link":"/categories/Machine-Learning/"},{"name":"Deep Learning","slug":"Deep-Learning","link":"/categories/Deep-Learning/"},{"name":"Basic Tools","slug":"Basic-Tools","link":"/categories/Basic-Tools/"},{"name":"Python","slug":"Python","link":"/categories/Python/"},{"name":"Deep Learing","slug":"Deep-Learing","link":"/categories/Deep-Learing/"}]}