深度学习入门知识整理-训练技巧以及模型调优:
https://blog.csdn.net/ForgetThatNight/article/details/91856052
一、static有什么用途?
1、定义静态变量
1)定义静态全局变量
在普通全局变量前加关键字static就声明成了静态全局变量。如果没有初始化,则其默认值为0.二者存储方式一样,均存储在静态存储区,然而二者的作用域发生了变化。非静态的全局变量作用域是整个源程序,比如说一个源程序中包含多个文件,则非静态的全局变量在各个文件中均有效,而static全局变量则限制了其作用域只能在定义了该变量的文件内,在其他文件中不能使用它(其他文件不可以通过将它定义为extern而使用它)
2)定义静态局部变量
在局部变量前面加上关键字static,该局部变量就成了静态局部变量,如果没有初始化,则其默认值为0.在函数内以static 声明的变量虽然与自动局部变量的作用域相同(即作用域都只限于函数内),但存储空间是以静态分配而非默认的自动分配方式获取的。静态局部变量只能初始化一次,这是由编译器来保证实现。
3)定义静态成员变量
在c++中,在类的定义中以static声明的成员变量属于类变量,也即在所有类实例中共享。普通成员变量每个类实例有一份,而静态成员变量一个类只有一份,被所有类实例共享。静态数据成员在定义时需要分配空间,所以不能在类中进行初始化。静态数据成员没有this指针。静态成员变量本质上还是全局变量。
4)定义静态函数
在函数的返回类型上加上关键字static,函数就被定义成静态函数,函数的作用域被限制在当前文件下。
5)定义静态成员函数
在c++中,在类的定义中以static声明的成员函数属于类函数,静态成员函数不具体作用于某个类实例,所以静态成员函数内部不能访问
非静态成员变量,也不能调用非静态成员函数。非静态数据成员属于特定的类实例。主要用于对静态数据成员的操作。静态成员函数没有this指针
二、引用与指针的区别
1)指针
指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能够找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元,使用*定义指针。
2)引用
引用是给对象取的一个别名。定义引用的表示方法与定义指针相似,只是用&代替了*。引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样
3)指针和引用的区别
1、指针是一个实体,而引用仅是个别名
2、引用必须初始化,指针不必
3、引用只能在定义时被初始化一次,之后不可变;指针可以改变所指的对象
4、可以有const指针,但是没有const 引用;
5、不存在指向空值的引用,但是存在指向空值的指针,即引用不能为空,指针可以为空
6、“sizeof 引用”得到的是所指向的变量(对象)的大小,而"sizeof 指针"得到的是指针本身(所指向的变量或对象的地址)的大小
7、指针和引用的自增(++)运算意义不一样;指针自增指向下一个地址,而引用是对变量本身的值的增加。
8、程序为指针变量分配内存区域, 而引用不需要分配内存区域
9、指针可以有多级,但是引用只能是一级,例如int **p是合法的, 而int &&a是不合法的
10、指针和引用作为函数参数进行传递时也不同。用指针传递参数,可以实现对实参进行改变的目的;在将引用作为函数参数进行传递时,实质上传递的是实参本身,而不是实参的一个拷贝,因此对形参的修改其实是对实参的修改
总之,指针与引用的一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则是总是指向在初始化时被指定的对象,以后不能改变。
4)指针和引用的相同点:
两者都是地址的概念,指针指向一块内存,其内容为所指内存的地址;引用是某块内存的别名。
5)使用引用的情况
1、常引用,用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性,函数体不能对引用型参数修改目标变量。声明方式:
const 类型标识符 &引用名 = 目标变量名
2、作为函数的返回值,当类A的数据成员包含B类的对象,可以在类A中定义一个方法,返回B类的对象的引用,返回的是引用就不会有副本产生,可以直接修改类对应的B的对象。
三、程序内存分配知识:
1、内存栈区:存放局部变量名
2、内存堆区:存放new或者malloc出来的对象
3、常数区:存放局部变量或者全局变量的值;
4、静态区:用于存放全局变量或者静态变量
5、代码区:二进制代码
四、c++ 在继承中虚函数,纯虚函数,普通函数,三者的区别
https://www.cnblogs.com/cj2014/p/7692707.html
1、虚函数
c++的虚函数主要作用是“运行时多态”, 父类中提供虚函数的实现,为子类提供默认的函数实现
子类可以重写父类的虚函数实现子类的特殊化
例子:
class A
{
public:
virtual void out(string s )
{
cout<<"A(out):"<<s<<endl;
}
};
2、纯虚函数
1)c++中包含纯虚函数的类,被称为是“抽象类”。抽象类不能使用new出对象,只有实现了这个纯虚函数的子类才能new出对象
2)c++中的纯虚函数更像是“只提供声明,没有实现”,是对子类的约束,是“接口继承”
3)c++中的纯虚函数也是一种“运行时多态”
抽象类的例子:
class A{
public:
virtual void out(string s)=0;
virtual void out(string s)
{
cout<<"A(out):"<<s<<endl;
}
}
3、普通函数
普通函数是静态编译的,没有运行时多态,只会根据指针或引用的“字面值”类对象,调用自己的普通函数
定义它为虚函数是为了允许用基类的指针来调用子类的这个函数
yolov1理解
https://blog.csdn.net/liuxiaoheng1992/article/details/81983280
https://luckmoonlight.github.io/2018/11/28/yoloV1yolov2yoloV3/
五、图像分类算法优化技巧:
一)模型训练部分
1、增大学习率:更大的batch_size计算得到的梯度会更加贴近整个数据集,一般而言将batch_size修改为原来的几倍,那么初始学习率也需要修改为原来的几倍
2、用一个小的学习率先训练几个epoch(warmup),因为网络的参数是随机初始化的,加入一开始就采用较大的学习率容易出现数值不稳定,这是使用warnup的原因。等到训练过程基本稳定了就可以使用原先设定的初始学习率进行训练了。作者在实现warmup的过程中采用线性增加的策略
3、weight decay 的主要作用就是通过对网络层的参数(weight和bias)做约束(L2正则化会使得网络层的参数更加平滑)达到减小模型过拟合的效果。
二)优化网络结构部分
1、在深层卷积网络中将downsample操作从第一个1x1卷积层换成换成到第二个3x3卷积层,如果downsample操作放在stride=2的1x1卷积层,那么会丢失较多特征信息,而将downsample操作放在3x3卷积层则能够减少这种损失。
2、借鉴inception v2的思想,将5x5大卷积核用2个3x3卷积层替换。
3、例如Resnet-D,将stage部分做downsample的residual block的支路从stride为2的1x1卷积层换成stride为1的卷积层,并在前面添加一个池化层来做downsample.这部分我个人理解是池化层也会丢失信息,但至少是经过选择(均值操作)后再丢失冗余信息,相比stride设置为2的1x1卷积层要好一些。
三)模型训练调优部分
1)学习率衰减策略cosine函数,目前比较常用的是step_decay,表示训练到指定的epoch时才衰减学习率
2)采用label smoothing, 这部分是将原来的one-hot类型标签做软化,这样在计算损失值时能够在一定程度上减少过拟合。从交叉熵损失函数可以看出,只有真实标签对应的类别概率才会对损失值计算有所帮助,因此label smoothing 相当于减少真实标签的类别概率在计算损失值时的权重,同时增加其他类别的预测概率在最终损失函数中的权重。这样真实类别概率和其他类别的概率均值之间的gap(倍数)就会下降一些。
3、知识蒸馏
知识蒸馏其实是模型加速压缩领域的一个重要分支,表示用一个效果更好的teacher model训练student model,使得student model在模型结构不改变的情况下提升效果。作者采用ResNet-152作为teacher model, 用ResNet-50作为student model,代码上通过在ResNet网络后添加一个蒸馏损失函数实现,这个损失函数用来评价teacher 输出和student model输出的差异,因此整体的损失函数
4、引入mixup, mixup其实也是一种数据增强方式,假如采用mixup训练方式,那么每次读取2张输入图像,
六、优化算法的选择
1)对于稀疏数据,尽量使用学习率可适应的算法,不用手动调节,而且最好采用默认参数
2)SGD通常训练时间最长,但是在好的初始化和学习率调度方案下,结果往往更可靠。但是SGD容易困在鞍点,这个缺点也不能忽略
3)Adagrad、Adadelta和RMSprop是比较相近的算法,表现都差不多
七、其他策略
1、shuffling打乱数据,可以避免训练样本的先后次序影响优化的结果。
2、batch normalization
在训练工程中,参数会更新到不同的数值范围,使得normalization的效果消失,从而导致训练速度变慢或者梯度爆炸等问题。
BN给每个batch的数据恢复了normalization,同时这些对数据的更改都是可还原的,即normalization了中间层的参数,又没丢失中间层的表达能力。
使用BN之后,我们就可以使用更高的学习率,也不用再在参数初始化上花费那么多注意力。
BN还有正则化的作用,同时也削弱了对Dropout的需求。
八、精确率(Precision)、准确率(accuracy)、召回率(Recall)(都是针对某个类而言的)
精确率:正样本的精确率表示你预测为正的样本中有多少预测对了
P = Tp/(Tp + Fp)
召回率:正样本的召回率表示真实标签为正的样本中多少被你预测对了
Recall = Tp/(Tp+Fn)
准确率:表示有多少比例的样本预测对了
Accurate = (Tp + Tn) / (Tp+Tn+Fp+Fn)
九、normalization ,batch normalization理解
1)中间层神经元激活输入x从变化不拘一格的正态分布通过BN操作拉回了均值为0,方差为1的高斯分布。这有两个好处:1、避免分布数据偏移;2、远离导数饱和区
在所有情况下,BN都能显著提高训练速度
2)BN的基本思想
因为深层神经网络你在做非线性变换前的激活输入值,随着网络深度加深或者在训练过程中,其分布逐渐发生偏移或者变动,之所以训练收敛慢,一般是整体分布逐渐往非线性函数的取值区间的上下限两端靠近,所以这导致反向传播时低层神经网络的梯度消失,这是训练深层神经网络收敛越来越慢的本质原因,而BN就是通过一定的规范手段,把每层神经网络任意神经元这个输入值分布强行拉回到平均值为0,方差为1的标准正态分布,其实就是把越来越偏的分布强制拉回比较标准的分布,这样使得激活输入值落在非线性函数对输入比较敏感的区域,这样输入的小变化就会导致损失函数较大的变化,意思是这样让梯度变大,避免梯度消失问题产生,而且梯度变大意味着学习收敛速度快,能大大加快训练速度。
总结:
培养良好的工程习惯至关重要,前期可能需要一些学习成本,但到后期你会发现养成了好习惯反而能节省很多时间,尤其是当前任务量比较大或者是需要开展持续的工作的时候,你会发现之前的付出真的很有意义。
1)网络模型先简单后复杂
通常我会使用一个简单的CNN模型(CV领域做5个卷积层,NLP领域做1个卷积层),将数据扔进去训练跑出一个baseline,这一步工作主要是为了验证数据集的质量。如果这个模型训练结果很差就不要先调试模型,需要检查一下你的训练集数据,看看图像的质量,图像标签是否正确,模型代码是否正确。
2)确认模型损失
模型损失是评估模型性能的主要方式,也是模型设置重要参数以进行评估的依据,因此需要确保:模型损失适用于任务(使用分类交叉熵损失(cross-entropy loss)进行多分类问题或使用focalloss以解决不平衡问题)
3)检查中间输出和连接
为了调试神经网络,你需要理解神经网络内部的动态,不同中间层所起的作用,以及层与层之间是如何连接起来的。不过,你可能遇到以下问题:
不正确的梯度更新, 表达式权重未得到应用, 梯度消失或爆发,