语义分割主要遇到的问题是低分辨率和语义信息不足等。
(1). 可以融合不同层的语义信息(MLFGAN就是融合多层信息防止细微但鉴别性很强的物体在下采样时丢失,比如帽子,脸以及衣服logo等)
(2). 还可以利用多尺度分辨率的上采样模块,有几种思路:
目标检测存在的问题:
基础分类损失为Softmax,形式为:
为了改进正负样本不均衡问题,将CE损失改进为Balance CE损失,即对正负类别分别进行一个系数进行加权,形式如下:
其中 α ∈ [ 0 , 1 ] \alpha \in [0, 1] α∈[0,1]。
一般正例较少,负例较多,因此CE中 y = 1 y=1 y=1部分损失较小,而 y = 0 y=0 y=0部分损失较大,于是加入 α \alpha α给 y = 1 y=1 y=1加权使得这部分损失变大一些,而利用 1 − α 1-\alpha 1−α给 y = 0 y=0 y=0加权使得这部分损失变小一些,于是网络也就更加关注那些正样本。
但这并不能解决全部问题。根据正、负、难、易,样本一共可以分为以下四类:
其中易分样本(即置信度高的样本)对模型的提升效果非常小,模型应该主要关注与那些难分样本(这个假设是有问题的,是GHM的主要改进对象,而Focal loss之前的Online Hard Example Mining (OHEM)就是把难样本捞出来组成一个集合,然后用他们专门训练网络,也就能让网络关注到这些难样本),而Focal Loss则是改进为:
其中 γ > 1 \gamma>1 γ>1,以下假设 γ = 2 \gamma=2 γ=2。
def py_sigmoid_focal_loss(pred, target, weight=None, gamma=2.0, alpha=0.25, reduction='mean', avg_factor=None):
pred_sigmoid = pred.sigmoid()
target = target.type_as(pred)
pt = (1 - pred_sigmoid) * target + pred_sigmoid * (1 - target)
focal_weight = (alpha * target + (1 - alpha) * (1 - target)) * pt.pow(gamma)
loss = F.binary_cross_entropy_with_logits(pred, target, reduction='none') * focal_weight
loss = weight_reduce_loss(loss, weight, reduction, avg_factor)
return loss
GHM(gradient harmonizing mechanism)
Focal Loss存在什么问题呢?
首先,让模型过多关注那些特别难分的样本肯定是存在问题的,样本中有离群点(outliers),可能模型已经收敛了但是这些离群点还是会被判断错误,让模型去关注这样的样本,怎么可能是最好的呢?
看下图梯度模长与样本数量的关系:
可以看到,梯度模长接近于0的样本数量最多,随着梯度模长的增长,样本数量迅速减少,但是在梯度模长接近于1时,样本数量也挺多。
GHM的想法是,我们确实不应该过多关注易分样本,但是特别难分的样本(outliers,离群点)也不该关注啊!这些离群点的梯度模长d要比一般的样本大很多,如果模型被迫去关注这些样本,反而有可能降低模型的准确度!况且,这些样本的数量也很多!
**那怎么同时衰减易分样本和特别难分的样本呢?太简单了,谁的数量多衰减谁呗!**那怎么衰减数量多的呢?简单啊,定义一个变量,让这个变量能衡量出一定梯度范围内的样本数量——这不就是物理上密度的概念吗?
后续略。
参考链接
ICS严重吗?会导致什么问题?
前面提到过了,会导致两个问题,分别是:(1) 后层网络需要不停调整来适应前层输入数据分布的变化,导致网络学习速度的变慢;(2) 网络的训练过程容易陷入梯度饱和区,减缓网络收敛速度(这个问题一方面可以使用ReLU代替Sigmoid,tanh这些激活来改进,另一方面做Norm也有帮助)
ICS问题需要被解决,那有什么方法?
ICS产生的原因是由于参数更新带来的网络中每一层输入值分布的改变,并且随着网络层数的加深而变得更加严重,因此我们可以通过固定每一层网络输入值的分布来对减缓ICS问题。两种思路:
(1) 白化(Whitening): 机器学习里面常用的一种规范化数据分布的方法,主要是PCA白化与ZCA白化。白化是对输入数据分布进行变换,进而达到以下两个目的:1) 使得输入特征分布具有相同的均值与方差。其中PCA白化保证了所有特征分布均值为0,方差为1;而ZCA白化则保证了所有特征分布均值为0,方差相同;2) 去除特征之间的相关性。通过白化操作,我们可以减缓ICS的问题,进而固定了每一层网络输入分布,加速网络训练过程的收敛。但白化有一定的缺陷,主要有以下两个问题:1) 白化过程计算成本太高,并且在每一轮训练中的每一层我们都需要做如此高成本计算的白化操作;2) 白化过程由于改变了网络每一层的分布,因而改变了网络层中本身数据的表达能力, 底层网络学习到的参数信息会被白化操作丢失掉。
(2) Batch Norm:怎么想到Batch Norm的呢?
我们在做神经网络训练前,对输入的像素进行标准化处理,有效降低模型的训练难度。受此启发,作者想到,既然输入层可以加标准化有好处,那么网络里的隐层为什么不可以标准化?于是通过对每层加权和进行标准化来改变输入的分布,使得分布达到白化类似的norm效果,然后再通过缩放平移来“适度还原”,防止底层学到的信息被改变分布的操作给毁了。这样,做到了既不过分破坏输入信息,又抑制了各batch之间各位置点像素分布的剧烈变化带来的学习难度。
结论:
- 只要损失函数有需要, γ , β \gamma, \beta γ,β那个公式赋予了BN层还原原始分别的能力,而且上限是完全还原;
- 具体对每一层还原多少,则是由损失函数对每一层这两个系数 γ , β \gamma, \beta γ,β的梯度来决定;
- 损失通过梯度来控制还原的程度,较好利于减少损失,就多还原;较少利于减少损失,就少还原(让网络自己学习还原多少)。
问题一:可导吗?能否反向传播?
答:可以。链式法则,每个部分都可导,总体也可导,且求出各部分的导数用链式法则合并即可(串联相乘并联相加)得到最终的导数(五项,分别是对 γ , β , x i \gamma, \beta, x_i γ,β,xi的导数用于更新他们,而 x i x_i xi还进一步传到前面的网络中更新上一层的权重W和b,还有两个导数是对 μ , σ \mu, \sigma μ,σ的导数,但不用来更新他们,因为他们是统计量,下次的结果有下次的输入计算而来),整个过程如下所示:
问题二:参数的初始化和更新?
分别由W,b, μ , σ , γ , β \mu,\sigma,\gamma, \beta μ,σ,γ,β六个参数,其中W和b其实是BN前面接的Conv的参数,估计也一起说:其中W与经典网络的初始化相同,初始化用标准正态分布(即Xavier方法),更新用梯度下降。b则直接省略掉,这是因为b的作用完全被BN中的shift参数 β \beta β取代了。 μ , σ \mu,\sigma μ,σ初始化取决于统计量,仅更新梯度,但不更新值本身。 γ , β \gamma, \beta γ,β初始化为1、0,更新用梯度下降。至于将要变成什么值,起多大作用,那就交给后续的训练。即采用梯度下降进行更新。
问题三:测试时和训练时参数有什么不同?
γ , β \gamma, \beta γ,β是在整个训练集上训练出来的,与W一样,训练结束就可获得。但 μ , σ \mu,\sigma μ,σ是依据一个mini-batch的统计得到,因为评估时只有一条样本,batch_size相当于是1,在只有1个向量的数据组上进行标准化后,成了一个全0向量,这可咋办?
其实这就涉及训练和测试时BN的差异了,其实W,b, γ , β \gamma, \beta γ,β四个参数的差异仅仅就是测试时固定下来不更新,但 μ , σ \mu,\sigma μ,σ一直没有用梯度更新过,而在测试的时候又不能用样本自己的统计量赋值,就只能用训练集的统计量了,当然必须是整个训练集的统计量。于是就有两种方法:
(1) 简单平均法:即把每个mini-batch的均值和方差都保存下来,然后训练完了求均值的均值,方差的均值即可。
(2) 移动指数平均(ExponentialMoving Average):这其实是对均值的近似。即引入一个动量(惯性)来保留上一个mini-batch的均值和方差,再利用下面的公式进行更新:x’ = (1-momentum) * x + momentum * x’'
目前使用较多的是后者,因为在训练时mini-batch不大时,容易有噪声造成统计量不准,用这种方式也能稍微缓解。
有人做了实验,结论是:BN的加入使得损失函数曲面变得平滑,而平滑的损失函数进行梯度下降法变得非常容易(可参见下图)。
显然,对于左侧的损失函数,梯度下降将是异常困难;而对于右侧,即经过平滑的损失函数,将大大提升训练效率。
- BN层的位置能不能调整?如果能调整哪个位置更好?
能。位置并不限于ReLU之前。也有测试表明,BN放在上一层ReLU之后,效果更好。- 在训练时为什么不直接使用整个训练集的均值/方差?
使用 BN 的目的就是为了保证每批数据的分布稳定,使用全局统计量反而违背了这个初衷。- 在预测时为什么不直接使用整个训练集的均值/方差?
完全可以。由于神经网络的训练数据量一般很大,所以内存装不下,因此才用指数滑动平均方法去近似值来近似代替整个训练集的均值和方差,好处是不占内存,计算方便,但其结果不如整个训练集的均值/方差那么准确。- batch_size的配置
不适合batch_size较小的学习任务。因为batch_size太小,每一个step里前向计算中所统计的本batch上的方差和均值,噪音声量大,与总体方差和总体均值相差太大。前向计算已经不准了,反向传播的误差就更大了。尤其是最极端的在线学习(batch_size=1),原因为无法获得总体统计量。- 对学习率有何影响?
由于BN对损失函数的平滑作用,因此可以采用较大的学习率。- BN是正则化吗?
在深度学习中,正则化一般是指为避免过拟合而限制模型参数规模的做法。即正则化=简化。BN能够平滑损失函数的曲面,显然属于正则化。不过,除了在过拟合时起正则作用,在欠拟合状况下,BN也能提升收敛速度。- 与Dropout的有何异同?
BN由于平滑了损失函数的梯度函数,不仅使得模型训练精度提升了,而且收敛速度也提升了;Dropout是一种集成策略,只能提升模型训练精度。因此BN更受欢迎。- 能否和Dropout混合使用?
虽然混合使用较麻烦,但是可以。不过现在主流模型已经全面倒戈BN。Dropout之前最常用的场合是全连接层,也被全局池化日渐取代。既生瑜何生亮。- BN可以用在哪些层?
所有的层。从第一个隐藏层到输出层,均可使用,而且全部加BN效果往往最好。- BN可以用在哪些类型的网络?
MLP、CNN均ok,几乎成了这类网络的必选项。RNN网络不ok,因为无论训练和测试阶段,每个batch上的输入序列的长度都不确定,均值和方差的统计非常困难。- BN的缺点
在训练时前向传播的时间将增大。(但是迭代次数变少了,总的时间反而少了)
BN,LN,IN,GN从学术化上解释差异:
BatchNorm:batch方向做归一化,算NHW的均值,对小batchsize效果不好;BN主要缺点是对batchsize的大小比较敏感,由于每次计算均值和方差是在一个batch上,所以如果batchsize太小,则计算的均值、方差不足以代表整个数据分布
LayerNorm:channel方向做归一化,算CHW的均值,主要对RNN作用明显;
InstanceNorm:一个channel内做归一化,算H*W的均值,用在风格化迁移;因为在图像风格化中,生成结果主要依赖于某个图像实例,所以对整个batch归一化不适合图像风格化中,因而对HW做归一化。可以加速模型收敛,并且保持每个图像实例之间的独立。
GroupNorm:将channel方向分group,然后每个group内做归一化,算(C//G)HW的均值;这样与batchsize无关,不受其约束。
SwitchableNorm是将BN、LN、IN结合,赋予权重,让网络自己去学习归一化层应该使用什么方法。
传统的是softmax损失,但center loss用mnist上的实验证明了其分类结果呈现辐射状的角分类特点,而靠近角中心的类间样本距离很小,而类内靠近角中心的样本和偏离角中心(辐射外围)的样本之间距离很大,这样很容易误分,即鉴别性不够强,尤其是很多类别的分类任务的时候。于是就有一些改进,但多数集中在联合损失,比如center loss约束类内样本距离,triplet loss在BN前优化pull类内样本push类间样本。另一思路则是改善softmax损失本身,改进思路如下所示:
(1) 传统softmax损失为:
其中 f = w x + b f=wx+b f=wx+b
(2) 把权重进行归一化,这样可以实模长和方向不再耦合,收敛更快,效果更好,于是有 f = w x + b = ∣ w ∣ ∗ ∣ x ∣ ∗ c o s θ + ∣ b ∣ = ∣ x ∣ c o s θ f=wx+b=|w|*|x|*cos\theta+|b|=|x|cos\theta f=wx+b=∣w∣∗∣x∣∗cosθ+∣b∣=∣x∣cosθ。得到结果为:
即为W-Norm Softmax损失(注意x其实就是特征f)。
(3) 这里开始就出现了思想变化了,作者实验证明了其实样本都是围绕在权重w的周围,因此把w尽量分开,softmax的效果也就会得到提升。而w在这里已经变成了 c o s θ cos\theta cosθ,于是目的就是让样本属于某一类的空间分布更紧凑,采取的方法是降低样本属于这一类的置信度/概率,这样损失就会变大,再去优化的话就能让模型提取到更有鉴别性的特征。具体做法是,施加一个margin的约束,类似triplet和SVM那种,让不同类别之间尽量分开,这里添加的是角度 θ \theta θ的乘法margin,即从 c o s θ cos\theta cosθ变为 c o s m θ cosm\theta cosmθ,那为什么能压低属于某一类的概率呢?因为最少二分类,即 θ \theta θ在0~pi区间,而这个区间内cos是单间的,因此会压低概率。损失也就变为:
此即为SphereFace或A-Softmax(两家公司同时提出,名字不同,公式相同)。
(4) 如果只对权重归一化而特征不归一化,那导致结果几乎不受权重影响,该层网络的训练也就失去了意义,比如x的范围是0-1000,w是0-1,那x随便变化一点都比w从0变为1带来的影响大。于是对特征也做归一化,得到F-Norm SphereFace:
其实就是上面(3)中的 ∣ ∣ x ∣ ∣ ||x|| ∣∣x∣∣变成了归一化为s了。
(5) 上面也有问题,这个 c o s m θ cosm\theta cosmθ在反向求导时要用倍角公式,太浪费时间了,于是作者改成:
也就是 c o s m θ cosm\theta cosmθ变为 c o s θ − m cos\theta - m cosθ−m,同样压低了属于某一类的概率,提高损失,然后来优化它,但这个梯度和 c o s θ cos\theta cosθ是一样的,计算更简单。记为AM-Softmax损失,也叫CosineFace,其实就是增加了余弦margin。
(6) ArcFace (后因重名修改为InsightFace),其出发点是:AM-Softmax优化的是余弦距离,没有直接优化角度距离来得直接,来得效果好(即余弦margin没有角度margin香)。于是将 c o s θ cos\theta cosθ改会 c o s ( θ + m ) cos(\theta+m) cos(θ+m),这样既可以压低概率,又能计算梯度简单,即角度加法margin。损失为:
(7)对比:
L1正则和L2正则都可以缓解过拟合,前者使用了拉普拉斯分布先验,后者则使用高斯分布先验。前者会使得得到稀疏解,很多参数为0,从而实现特征选择;后者则是整体参数会降低一些,但不像L1那么稀疏,而是比较平滑。
产生稀疏解的原因?
L1更容易在尖角处和损失等高线碰撞(最突出)。L1更适合特征之间有关联的情况,从而稀疏化其中的一部分,实现特征选择,而L2更适合特征之间没有关联的情况,进行整体压低而又不压低到0(球形并凸出都差不多,很难碰到w=0的情况)
在每次训练的时候使用dropout,每个神经元有一定的概率被移除,这样本身可以算是将网络做的简单了一些,此外还可以使得一个神经元的训练不依赖于另外一个神经元,同样也就使得特征之间的协同作用被减弱,即通过阻止某些特征的协同作用来缓解。不过采用dropout会使得训练时间大大延长,而BN基本也可以视为一种正则,还能加速收敛,目前用的更多,dropout则更多的慢慢被pooling层取代。
(1) SGD:抽取一个小批量(独立同分布)的样本,通过计算他们的平均梯度均值。
缺点:学习率太小则收敛速度很慢,但太大损失函数可能在极小值附近震荡甚至偏离,需要手动调学习率;容易被困在鞍点。优点:收敛结果非常好。
(2) Momentum(动量): 其实就是加入惯性保持历史梯度的更新方向。因此要是当前时刻的梯度与历史时刻相似,这种趋势就会加强;要是不同则被减弱。
(3) AdaGrad:设置全局学习率之后,每次通过全局学习率逐参数的除以历史梯度平方和的平方根,使得每个参数的学习率不同。即优点:可以实现学习率自适应减小。缺点:学习率可能会过早、过量的减少。
(4) RMSProp:相比于AdaGrad的改进是改变梯度累积为指数衰减的移动平均以丢弃遥远的过去历史,增加了一个衰减系数来控制历史信息的获取多少。
(5) Adam:可以看作修正后的Momentum+ RMSProp算法,收敛很快。
先是审核机制,然后是:
(1) 冷启动:
抖音的推荐算法机制是著名的信息流漏斗算法,也是今日头条的核心算法。通过审核后,**第一步叫冷启动流量池曝光,比如你今天上传一个视频,通过双重审核的作品,系统将会分配给你一个初始流量池:200-300在线用户(也可能有上千个曝光)。**不论你是不是大号,只要你有能力产出优质内容,就有机会跟大号竞争。然后根据用户反馈进行数据加权决定是利用热门视频带动的二轮推荐,还是停止推荐,推荐一段时间后权重进行衰减。
(2) 数据加权
抖音会根据前面冷启动给的曝光数据,结合你账号分值来分析是否给你加权,比如完播率、点赞、关注、评论、转发、转粉、游览深度等,然后会挑选比较靠前的视频如前10%,再增加曝光(比如1万次曝光)。常用特征包括:
淘宝和拼多多这些可能还需要考虑商品价格,用户购买力以及商铺广告之类的信息。
(3) 加大推荐
这一步会给数据好的短视频进行更大的加权,并且会在第三步强化人群标签分发,让内容分发的更精准,这类似猜你喜欢的打标,视频是有标签的,用户也是有标签的,两者之间会做标签匹配。常用标签有:
(4) 进入精品推荐池
进入精品推荐池,大规模曝光,一旦进入精品推荐后,人群标签就被弱化了,就像当年温婉的视频,几乎每个抖音用户都会刷到温婉的视频。
不少抖音运营者会发现,有些内容发布的当天、一周甚至一个月内都数据平平,但突然有一天就火了,为什么?两种原因:第一种是被戏称为“挖坟”的机制(b站叫考古)。是指抖音会重新挖掘数据库里的“优质老内容”,并给它更多的曝光。这些老作品之所以能被“引爆”,首当其冲是它的内容够好,其次是你的账号已经发布了很多足够垂直的内容,标签变得更清晰,系统能够匹配给你更精准的用户。优质内容+精准用户,老作品重新火爆起来就不意外了。第二种是“爆款效应”,即你的某一个作品在获得大量曝光(几百万,甚至千万级)时,会带来巨量用户进入你的个人主页,去翻看你之前的作品。如果你的某一个作品,能够获得足够多的关注(转评赞),系统将会把这些视频重新放入推荐池。很多垂直内容的创作者,往往都是因为某一个视频的“火爆”,直接把其他几个优质视频“点燃”,形成多点开花,全盘爆炸引流的盛况。
抖音作品经过双重审核、初始推荐、叠加推荐层层引爆之后,通常会给账号带来大量的曝光、互动和粉丝。而这种高推荐曝光的时间,一般不会超过一周。之后,爆款视频乃至整个账号会迅速冷却下来,甚至后续之后发布的一些作品也很难有较高的推荐量。为什么?因为抖音每天的日活是有上限的,也就是说总的推荐量是基本固定的:一方面,跟你内容相关标签的人群基本完成推荐,其他非精准标签人群反馈效果差,所以停止推荐;另一方面,抖音也不希望某个账号迅速火起来,而是通过一轮轮考验,考验你的内容再创新能力和持续输出优质内容的能力。
(1) del和update的区别:
del删除字典元素,update更新/合并字典
(2) python2和3中range的区别:
python2返回的是list,但python3返回的是迭代器,可以节省内存
(3) python内建数据类型:
int, bool, str, list, tuple, dict, set
(4) 面向对象中__new__和__init__区别:
__init__是初始化方法,创建对象后,就立刻被默认调用,可接收参数;__new__至少要有一个参数cls,代表当前类,此参数在实例化时由Python解释器自动识别 ;__new__必须要有返回值,返回实例化出来的实例;__init__有一个参数self,就是__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值 ;如果__new__创建的是当前类的实例,会自动调用__init__函数。
(5) 如何查看类中方法:
help(), dir(),.__doc__方法, .__all__方法。
(6) python实现乘方的几种方式:
- 列表生成式
[x * x for x in range(1, 11)]
- 使用列表推导式
[x2 for x in range(1,21)]
[(lambda x:x2)(x) for x in range(1,21)]
- 使用map函数
def cube(x): return x**2
list(map(cube,range(1,21)))
- 使用map+lambda
list(map(lambda x:x*x,range(1,21)))
(7) python元组与数组的区别
元组不可以随意增加或者删除元素,数组可以。
(8) python三个点是什么意思?
它是省略所有的冒号来用省略号代替,大家看这个a[:, :, None]和a[…, None]的输出是一样的,就是因为…代替了前面两个冒号,即一般用在不知道前面有多少个冒号的情况。
知乎专业详细介绍,下面是简要介绍:
(1) YOLO v.s Faster R-CNN
(2) YOLO v1
1> 给个一个输入图像,首先将图像得到的特征图划分成7*7的网格
2> 对于每个网格,我们都预测2个边框(包括每个边框是目标的置信度以及每个边框区域在多个类别上的概率)
3> 根据上一步可以预测出 7 ∗ 7 ∗ 2 7*7*2 7∗7∗2个目标窗口,然后根据阈值去除可能性比较低的目标窗口,最后NMS去除冗余窗口即可
(3) YOLO v2
对比v1的改进有:
从上到下分别是:
YOLOv2相对v1版本,预测更准确(Better),速度更快(Faster),识别对象更多(Stronger)。其中识别更多对象也就是扩展到能够检测9000种不同对象,称之为YOLO9000。
(4) YOLO v3
YOLO v3的模型比之前的模型复杂了不少,可以通过改变模型结构的大小来权衡速度与精度。
改进:
(1) DeepLab v1: DNN + CRF后处理
(2) DeepLab v2:
为了感受野需要做下采样,比如max pool,对小物体很不友好,可能逐步下降过程中就没了,于是作者提出空洞卷积代替池化,这样特征图不变小但感受野还能上去。即:
此外,做成多尺度,即空洞空间卷积池化金字塔(atrous spatial pyramid pooling (ASPP))对所给定的输入以不同采样率的空洞卷积并行采样,相当于以多个比例捕捉图像的上下文,对多尺度输入更友好。
(3) DeepLab v3:
修改了空洞空间金字塔池化模块,但是 CRF不再使用。整个结构为:
a是图像金字塔再融合,b是FCN和UNet这种先下采样再上采样,c是本文即不同层用不同空洞卷积,d是PSPnet结构。改进后的 ASPP 比原来多了个 1*1 的 conv 和 global avg pool,其实思想可能来自Inception和PSPnet,然后就是空洞卷积层后加BN和ReLU这种常用的技术。
(4) DeepLab v3+
UNet和FCN这种Encoder-Decoder结构真香,不过直接从低分特征图32X到原图跨度太大,要慢慢来,于是一层一层的恢复,类似于我们论文中的步进超分思想。然后就是对Xception结构进行了一些修改。
(1) Static关键字
用来控制变量的存储方式和可见性。在函数内部定义的变量会在程序执行到其定义处时,编译器为它在栈上分配空间,而该空间在此函数执行结束时会释放掉,因此如果想此变量的值保存至下一次调用时,如何实现? 简单,即全局变量,但定义全局变量破坏了此变量的访问范围(使得在此函数/类中定义的变量,不仅仅只受此函数/类控制)。而static 关键字很好的解决这个问题,静态数据存储在全局(静态)存储区,可以节省内存,因为它是所有对象所公有的,即对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。在 C/C++ 中static的特点:
- static在修饰变量的时候,static 修饰的静态局部变量只执行一次初始化,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。
- static 修饰全局变量/函数的时候,则这个全局变量/函数只能在本文件中访问,不能在其它文件中访问,即便是 extern 外部声明也不可以,其它文件中可以定义相同名字的变量,不会发生冲突。static 修饰的变量存放在全局数据区的静态变量区,包括全局静态变量和局部静态变量,都在全局数据区分配内存,不过局部静态变量的作用域不是全局的,而是局部的。初始化时的默认初始化值为 0(普通变量是随机初始化)。
- 不想被释放的时候,可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用 static 修饰。
- 考虑到数据安全性(当程序想要使用全局变量的时候应该先考虑使用 static)。
注:全局变量是不显式用 static 修饰的全局变量,全局变量默认是有外部链接性的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过 extern 全局变量名的声明,就可以使用全局变量,而全局静态变量是显式用 static 修饰的全局变量,作用域是声明此变量所在的文件,其他的文件即使用 extern 声明也不能使用。
`
总结:
- 不能通过类名来调用类的非静态成员函数,但可以通过类名调用静态成员函数,而类对象可以调用两者。即Point::init()是错的,Point::output()是对的,Point point; point.init()和point.output()都正确。
- 静态成员函数中不能引用非静态成员。因为前者在实例化对象之前就被创建在全局存储区,而后者只有在对象被实例化之后才能被创建。但注意反过来调用是可以的。
- 类的静态成员变量必须先初始化再使用(不是很懂,不应该默认初始化为0吗?)。即下图是错的(需要在main前加上int Point::m_nPointCount = 0;即可,输出为1)
static关键字的参考链接
(2) new-delete和malloc-free的用法区别
每个程序在执行时都会占用一块可用的内存空间,用于存放动态分配的对象,此内存空间称为自由存储区(new)或堆(malloc)。它们之间的区别如下:
- 属性:new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持c
- 参数:使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
- 返回类型:new操作符内存分配成功时,返回的是对象类型的指针,无须进行类型转换。而malloc内存分配成功则是返回void * ,需要通过强制类型转换成我们需要的类型。
- 分配失败:new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
- 自定义类型:new会先调用operator new函数,申请足够的内存(底层是malloc实现)。然后调用构造函数初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(底层是free实现)。malloc/free是库函数,只能动态地申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
- 重载:C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。
- 内存区域:new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
PS:在C++中,内存区有5个,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区;在C中,C内存区有堆、栈、全局/静态存储区、常量存储区;
new和malloc区别的相应参考链接
(3) C++多态,继承
继承的作用是代码重用,多态是为了接口重用。
- 继承:用类派生从一个类继承另一个类,派生类继承基类的成员。
- 多态:编译或运行时决定使用基类中定义的函数还是使用派生类中定义的函数。‘一个接口,多种方法’。多态又分为静态多态和动态多态。静态多态是指在编译期间就可以确定函数调用的地址,并生成代码。静态多态往往通过函数重载来实现,调用速度快,效率高但缺乏灵活性。动态多态则需要在运行的时候才可以确定函数调用的地址。动态多态通过虚函数来实现,虚函数允许通过派生类重新定义成员函数而派生类重新定义基类的做法称为覆盖或者重写。
(4) 虚函数
成员函数分为静态成员函数和非静态成员函数,非静态成员函数分为普通函数和虚函数。
反卷积又叫做转置卷积,其实就是在将普通卷积核转换为稀疏矩阵C,然后在正向传播的时候左乘这个稀疏矩阵C的转置,反向传播的时候左乘这个稀疏矩阵C。
(1)从模型结构上来说分为:模型剪枝,模型蒸馏,NAS自动学习模型结构等。
(2)模型参数量化上包括数值精度量化到FP16等。
注:模型剪枝的例子很多出现在轻量化网络上面,比如mobilenet v3里面出现的group conv,更改网络末端计算量大的层,减少网络头部的卷积核的数量,再如深度分离卷积等。模型蒸馏就是迁移学习。
Hessian矩阵是n*n, 在高维情况下这个矩阵非常大,计算和存储都是问题。
dropout的原理就是在网络前向传播的时候,让神经元的激活值以一定的概率变为零(训练有,测试无),这样可以使模型的泛化性能更强。其有作用的原因如下:
代码实现:
def dropout(x,p):
if p<0 or p>1:
raise Exception('The p must be in interval [0,1]')
retrain_prob = 1-p
sample = np.random.binomial(n=1,p=retrain_prob,size=x.shape)
x *=sample
x /=retrain_prob
return x
pooling操作虽然能增大感受野,但是会丢失一些空间信息。而空洞卷积在卷积核中插入权重为0的值,因此每次卷积中会skip掉一些像素点,从而不会像pooling那样丢失空间信息,但却增大了卷积输出每个点的感受野。在图像需要全局信息或者需要较长sequence依赖的语音序列问题上有着较广泛的应用。
特点:在零点不可导,因此导数不连续,于是可能存在多个解,当数据集有一个微小的变化时(噪声点)都会导致损失波动较大(L2把损失平方了,较小的损失越平方越小),从而导致解的波动很大,因此解不稳定;但L1损失不像L2那样对误差平方,因此L1对异常值比较鲁棒(较大的变化),会趋向于产生少量的特征,而其他的特征都是0,产生稀疏权重矩阵,服从拉普拉斯分布。而优点是无论对于什么样的输入值,都有着稳定的梯度,不会导致梯度爆炸问题,具有较为稳健性的解,做正则可以防止过拟合。
特点: L2损失的梯度为 2 ( y i − f ( x i ) ) 2(y_i-f(x_i)) 2(yi−f(xi)),容易造成梯度爆炸;L2对误差进行平方,因此对异常值(变化较大)和离群点(远离中心)比较敏感,会导致结果不理想;L2损失服从高斯分布,得到的结果比较平滑,不会很sharp;L2处处可到,存在解析解,做正则可以防止过拟合。L2会选择更多的特征,这些特征都会接近于0但不为0。
代码实现:
import numpy as np
#定义L1损失函数
def L1_loss(y_true,y_pre):
return np.sum(np.abs(y_true-y_pre))
#定义L2损失函数
def L2_loss(y_true,y_pre):
return np.sum(np.square(y_true-y_pre))
#假设我们得到了真实值和预测值
y_true = np.array([1,2,3,4,5,6,7,8,9])
y_pre = np.array([1.2,2.3,3.5,4.3,4.6,5.6,6.1,7.1,8.8])
#定义函数
print('L1 loss is {}'.format(L1_loss(y_true,y_pre))) # L1 loss is 4.1000000000000005
print('L2 loss is {}'.format(L2_loss(y_true,y_pre))) # L2 loss is 2.450000000000001
再讨论几个问题:
- 为什么参数越小代表模型越简单?
越是复杂的模型,越是尝试对所有样本进行拟合,包括异常点。这就会造成在较小的区间中产生较大的波动,这个较大的波动也会反映在这个区间的导数比较大。只有较大的参数才可能产生较大的导数。因此参数越小,模型就越简单。- 实现参数的稀疏有什么好处?
因为参数的稀疏,在一定程度上实现了特征的选择。一般而言,大部分特征对模型是没有贡献的。这些没有用的特征虽然可以减少训练集上的误差,但是对测试集的样本,反而会产生干扰。稀疏参数的引入,可以将那些无用的特征的权重置为0。- L1范数和L2范数为什么可以避免过拟合?
加入正则化项就是在原来目标函数的基础上加入了约束。当目标函数的等高线和L1,L2范数函数第一次相交时,得到最优解。
分析: 其实红绿曲线就是L1,而smooth L1就是把x<1的结果从L1变成了蓝色的L2损失,因为小于1的部分是变化比较小的微小变化不像L1那样敏感,而是像L2那样压低敏感度,而x>1的部分则是利用L1对异常点和离群值不敏感的优点。
L1对误差<1的小波点敏感(缺),对误差>1的异常值和离群点不敏感(优)
L2对误差<1的小波点不敏感(优),对误差>1的异常值和离群点敏感(缺)
smooth L1则是误差<1的部分取L2的优点,误差>1的部分取L1的优点
梯度:误差>1部分的梯度不是L2那种的 2 ( x i − f ( x i ) ) 2(x_i-f(x_i)) 2(xi−f(xi)),不会造成梯度爆炸,防止训练时跑飞。
主要分为离线增强和在线增强的方法。
激活函数分为两类,饱和激活函数和不饱和激活函数。
饱和激活函数的代表是sigmoid,tanh。特点是:收敛慢,容易梯度消失。
非饱和激活函数的特点是:收敛快,抑制梯度消失,抑制过拟合, 如ReLU,leakyReLU,ELU
sigmoid:计算量大。梯度消失,会改变原始数据分布。
tanh:计算量大,梯度消失比sigmoid好点
relu:计算简单,有效防止了梯度消失和梯度爆炸,会出现神经元死亡。
leakrelu:解决了神经元死亡的问题,但是多了一个参数a
ELU:避免dying神经元,并且处处连续,从而加速SGD,但是计算比较复杂
激活函数需要满足的性质:
非线性,几乎处处可导, 单调性(保证单层网络是凸函数),f(x) ≈ x防止梯度消失和爆炸,输出范围有限能使梯度优化更稳定(不然会使结果受权重影响小)
信号前向传播,误差反向传播,通过不断调节网络的权重,使得网络的最终输出与期望输出尽可能接近。前项过程中通过与正确的标签计算损失,反向传递损失,更新参数,优化至最后的参数。
KL散度又叫做相对熵,如下:
通常说的softmax损失叫做交叉熵,如下:
而熵是一个定值,如下:
它们之间的关系有:交叉熵 = 熵 + 相对熵。而熵是一个定值常数,因此优化交叉熵和优化KL散度其实没有什么区别。
Finetune就是通过修改预训练网络模型结构(如修改模型类别输出个数等)选择性的载入预训练网络模型的权重(载入除最后的全连接层之前的所有层)再用自己的数据集重新训练模型,可以达到缓解过拟合的目的。Finetune的实践建议:
第一个feature_map的感受野默认为1,感受野的计算公式: r = r ∗ s t r i d e + ( k − 1 ) r=r*stride+(k-1) r=r∗stride+(k−1),这里 r r r是上一层的感受野,stride是步长, k k k是kernel_size。全局stride等于所有stride的累乘。全局padding:
W 2 , H 2 W_2,H_2 W2,H2是输出宽和高, W 1 , H 1 W_1,H_1 W1,H1是输入宽和高。
对输入图像用多个不同尺寸的卷积核,池化操作进行同时处理,让网络自己学习哪个的结果更重要,然后将输出结果进行通道拼接。
核函数将数据映射到更高维的空间后处理,但是不用这种显式的映射,而是现将两个向量做内积,然后再用核函数做映射。这样做等价于先做映射,再做内积,而且避免了高维空间复杂的内积运算。
mobileNet虽然在计算量上减少了很多,但是由于深度可分离卷积的操作,使得网络的层数增加了很多,而我们的GPU计算是并行数据处理,假设GPU内存足够大的话所以GPU计算速度的瓶颈是是网络的层数。
最naive的方法,先都设置为1然后训一段时间之后看loss的收敛情况,然后调整权重使得这些loss传回去的梯度都在一个数量级上。
SGD可以看做是mini-batch梯度下降,而Momentum则是利用惯性的思想,记录了上一次的梯度信息,避免更新方向出现大的偏差导致更新出错;AdaGrad则是记录历史梯度信息,然后更新权重的时候除以该历史梯度值,达到自动调节学习率的目的(不同权重使用不同大小的学习率),所以Adagrad对低频的参数有较大的更新,对高频的参数有较小的更新,对于稀疏的数据的表现很好,缺点是Adagrad的学习率会不断收缩,最终变得非常小。而RMSProp算法就在AdaGrad基础上引入了衰减因子,即很久远的梯度不要一直记得那么清楚,进行exp衰减。Adam则是Momentum+RMSProp,大多数情况下Adam都可以取得比较好的效果,收敛也更快。但SGD在有比较好的初始化条件下,也可以更快的收敛,且SGD的可以收敛得更好。
其实这两个是一样的,公式推导一下就知道了。推导过程
【表现】
【产生原因】
【解决方案】
需要解决非常复杂的问题时,能需要训练一个非常深的DNN,可能需要几十层或者上百层,每层包含数百个神经元,通过成千上万个连接进行连接,我们会遇到以下问题:首先,梯度消失或梯度爆炸;其次,训练缓慢;第三,训练参数大于训练集的风险。
【产生原因】
【解决方案】
在实际应用中,在相同时间内使用异步模式训练的模型不一定比同步模式差。所以这两种训练模式在实践中都有非常广泛的应用。
a、loss出现nan,是否loss函数的使用不正确。
b、loss震荡,检查数据是否归一化,调整学习率,查看是否有梯度回传,使用大的模型。
(1) KNN:计算你要预测的点的周围最近的K个点,然后取这k个点中最多的类定义为你要预测的这个点所属的类型。
(2) 朴素贝叶斯:如下图,又因为特征之间发生的概率是独立的,所以叫做朴素贝叶斯。
(3) 感知机:感知机对应于输入空间中将实例划分为两类的分离超平面。感知机旨在求出该超平面,为求得超平面导入了基于误分类的损失函数(0-1损失),利用梯度下降法对损失函数进行最优化。
(4) SVM:支持向量机有三个部分的内容,线性可分支持向量机,软间隔支持向量机,核函数。SVM的目标就是找到最大间隔超平面。
(5) 逻辑回归:
可以采用多个二分类组合的方式。例如:1对1,1对多,多对多等。
随机选择K个样本作为类中心,将样本随机划分成K个簇然后计算类中心。可以多次随机选取平均以减少随机初始化可能选择不好的问题。
偏差描述的是网络的真实输出和期望输出之间的差距,方差描述的是训练模型中各个预测结果之间的差异。所以:低偏差(高方差)说明拟合程度好,但是过拟合风险较高;低方差(高偏差)说明拟合程度差,欠拟合,有很大的误差。当模型较为复杂的时候。偏差变小,方差变大。(模型复杂容易导致过拟合)
valid不padding,same则用0进行padding,具体padding多少要看实际情况,其本质是使得卷积输入的W和输出的W_out之间满足W_out = W / S 。
其实就是 ( W − F + 2 P ) / S + 1 (W-F+2P) / S + 1 (W−F+2P)/S+1,两者差别在于P是否为0,而卷积这里的除法一律向下取整,pool向上取整。上面两个公式向上取整是因为+1舍去了。
F1 score的含义就是认为召回率和准确率一样重要,F2就是认为召回率比准确率重要2倍。F-score公式如下:
当 β = 1 \beta=1 β=1时,成为F1-Score,这时召回率和精确率都很重要,权重相同。当有些情况下我们认为精确率更为重要,那就调整 β \beta β的值小于 1 ,比如F0.5-Score;如果我们认为召回率更加重要,那就调整 β的值大于1,比如F2-Score。
当特征数量较少时,增加特征可以提高算法的精度,但是当向量的维数增加到一定的数量之后,再增加特征,算法的精度反而会下降。
合页损失的损失函数为 L = m a x ( 0 , 1 − y ∗ y ^ ) L=max(0, 1-y*\hat{y}) L=max(0,1−y∗y^),可以看得出来以SVM为模型的话,距离支持向量越远,其损失越大。
原问题不易求解,含有大量不易处理的不等式约束。原问题满足slater条件,强对偶成立,因此原问题与对偶问题成立。
【slater条件,KKT条件,对偶问题参考链接】
真 实 概 率 ∗ l o g ( 1 / 预 测 概 率 ) 真实概率 * log(1 / 预测概率) 真实概率∗log(1/预测概率)
首先sigmoid是一个非线性的激活函数,神经网络的非线性是通过非线性的激活函数和多层网络的融合叠加实现的。
【参考链接】
增加数据集,数据增广,降低iou阈值(放入更快的框),模型融合。
Adam是一阶动量和二阶动量的组合共同决定。SGD的话就是简单地梯度更新。Adam会出现不收敛和错过全局最优解的现象,所以比较好的方案是采用Adam+SGD的方案。
引入计数,垃圾回收,内存池机制
变量名通过引用对象获取对象的类型和值。只要调用这个引用,引用计数就会增加,引用计数为0的时候就会启动垃圾回收机制。容器对象说明了两个对象通过赋值语句进行调用的时候是指向同一块内存空间的,所以其中一个变量增加另一个变量也会增加。内存池机制以256k为界限,大内存使用malloc进行内存分配,小内存使用内存池进行分配。
一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。
两者得到的都是迭代器,用*list()转换一下得到输出
举例:
m = nn.Sequential(nn.Linear(2,2),
nn.ReLU(),
nn.Sequential(nn.Sigmoid(),
nn.ReLU()))
*list(m.children())
返回的是:
[Linear(in_features=2, out_features=2), ReLU(), Sequential((0): Sigmoid() (1): ReLU() )]
一共3个元素:linear,relu,sequential
*list(m.modules())
返回的是:
[Sequential((0): Linear(in_features=2, out_features=2)
(1): ReLU()
(2): Sequential((0): Sigmoid()
(1): ReLU())),
Linear(in_features=2, out_features=2),
ReLU(),
Sequential((0): Sigmoid()
(1): ReLU() ),
Sigmoid(),
ReLU()]
一共包括6个元素:整体的一个sequential,里面的一个linear,一个relu,一个子sequential,以及sequential里的sigmoid和relu。
用list举例就是:
a=[1,2,[3,4]]
children返回
1,2,[3,4]
modules返回
[1,2,[3,4]], 1, 2, [3,4], 3, 4
前者一般用的多,后者可以理解为一层一层拨开你的心
pytorch和caffe都是NCHW
,而tensorflow默认是NHWC
,也有NCHW
为什么呢?因为NCHW计算时需要的存储更多,适合GPU运算,正好利用了GPU内存带宽较大并且并行性强的特点,其访存与计算的控制逻辑相对简单(对cuDNN的计算更友好)。而NHWC更适合多核CPU运算(当然GPU也可以算,但没NCHW模式在GPU下的快),CPU的内存带宽相对较小,每个像素计算的时延较低,临时空间也很小,有时计算机采取异步的方式边读边算来减小访存时间,因此计算控制灵活且复杂。因此,深度学习以cuDNN为底层的pytoch和caffe框架默认使用了 “NCHW” 格式,而Tensorflow采用了"NHWC",据说是由于早期主要使用CPU加速,这也解释了为何面向移动端部署的TFLite只采用了"NHWC" 格式。
对于非1*1的卷积,需要算的是max pooling类似的操作,因此也是对NCHW更友好,而网络中这种层才是大多数,因次也会更快。
【参考链接】
telnet:23
http:80
https:443,
DNS:53
mysql:3306
sql server:1433
oracle:1521
DB:50000
Postgre SQL:5432
【参考链接】