在深度学习领域,双线性插值经常出现在研究者的视野中。 在笔者主要研究的深度学习图像处理中,很多地方也用到了双线性插值,比如下面的例子:
1、在作者之前关于FCN训练不收敛原因分析以及FCN训练和测试最详细的数据程序配置的博文中,FCN训练不收敛的原因是作者没有使用双线性插值初始化反卷积层。 结果,模型训练不收敛。
2.在著名的图像分割模型中,使用双线性插值来放大最终的特征图来计算损失。
3、何老师2017年重磅炸弹Mask R-CNN的这一创新点,在将特征图上的目标与输入图像上的目标逐像素对齐的操作中使用了双线性插值。
如上所述,双线性插值在深度学习领域有着广泛的应用,因此作者计划借用本博客中的一些源代码,向读者分析双线性插值初始化的神经网络的可训练参数。 不过,在这篇博客之前,笔者想提醒各位读者,如果您对双线性插值的原理不太了解,请先了解一下双线性插值算法的原理。 这个原理比较简单,这里不再赘述。 我给大家提供一些相关入口来理解双线性插值的原理:
1. 博士这篇博文来自他的专栏
2. 维基百科:双线性插值
3.百度百科:双线性插值原理
接下来,作者将分析双线性插值在初始化神经网络可训练参数中的应用。 作者打算用两个例子来分析双线性插值初始化神经网络(逆)卷积层的参数。 第一个例子是caffe官方提供的双线性插值参数初始化方法(c++)。 第二个例子是作者在上一篇博文中提到的FCN源码提供的数据层使用的双线性插值参数初始化方法()。 我们先从干的东西开始吧。
1.通过caffe官方提供的双线性插值初始化卷积层参数。
使用过深度学习框架caffe的读者应该知道,我们在训练模型的时候,会使用. 定义网络架构的文件。 在这个文件中,卷积层可以这样定义:
layer {
name: "conv"
type: "Convolution"
bottom: "bottom_blob"
top: "top_blob"
param {
lr_mult: 0
}
convolution_param {
num_output: 256
bias_term: false
kernel_size: 4
stride: 2
weight_filler {type: "bilinear"}
}
}
如上面代码所示,在卷积层中,我们使用了卷积层权重参数的初始化操作,传入“”对可训练的权重参数进行双线性插值初始化。 那么我们来看看caffe是如何进行双线性插值初始化参数的。 打开caffe根目录下的文件夹,然后打开名为caffe的文件夹,在里面找到.hpp。 其中我们可以看到以下双线性插值初始化参数的代码,注释如下。
template
class Filler {//父类
public:
explicit Filler(const FillerParameter& param) : filler_param_(param) {}//空的构造函数
virtual ~Filler() {}//空的虚构函数
virtual void Fill(Blob* blob) = 0;//纯虚函数,需要在子类中重写
protected:
FillerParameter filler_param_;
}; // class Filler
//...
template
class BilinearFiller : public Filler {//子类
public:
explicit BilinearFiller(const FillerParameter& param)
: Filler(param) {}
virtual void Fill(Blob* blob) {
CHECK_EQ(blob->num_axes(), 4) << "Blob must be 4 dim.";//核验一下参数必须是四维的
CHECK_EQ(blob->width(), blob->height()) << "Filter must be square";//卷积核必须是正四边形
Dtype* data = blob->mutable_cpu_data();//在这里获取到需要被写入初始化值的可训练参数的起始地址
int f = ceil(blob->width() / 2.);//f是卷积核宽的一半,卷积核宽如果是奇数的话,则对商进行上取整操作
float c = (2 * f - 1 - f % 2) / (2. * f);//求得系数c,一个略小于1的值
for (int i = 0; i < blob->count(); ++i) {
float x = i % blob->width();//求得待写入值的行标
float y = (i / blob->width()) % blob->height();//求得待写入值的列标
data[i] = (1 - fabs(x / f - c)) * (1 - fabs(y / f - c));//写入被双线性插值初始化的权重参数,data[i] = (1-|(x/f)-c|)*(1-|((y/f)-c|)
}
CHECK_EQ(this->filler_param_.sparse(), -1)
<< "Sparsity not supported by this Filler.";
}
};
看完上面的注释,我们用一个例子来分析一下::Fill(Blob
*blob)函数,我们首先假设某个卷积层的参数规格为[1,1,3,3],即该层的输出通道数为1,输入通道数为1,卷积核的大小为3×3。 (不熟悉caffe框架下卷积层参数规范的读者可以参考作者的这篇博客)。
那么,在上面的代码中,首先查出f为2,然后查出c为0.75(3/4),然后进入for循环:
当i分别为0、1、2时,由于blob->width()为3,则x分别为0、1、2,y始终为0。卷积核的第一行值初始化为值 0.0625、0.1875 和 0.1875。 见下文:
当i分别为3、4和5时,x分别为0、1和2,y始终为1。卷积核的第二行值初始化为值0.1875、0.1875和0.5625。 见下文:
当i分别为6、7和8时,x分别为0、1和2,y始终为1。卷积核的第二行值初始化为值0.1875、0.1875和0.5625。 见下文:
从代码中可以看出,最终初始化的参数在卷积核的长宽方向上与f对称。 这里,也确认了代码中验证卷积核的长宽必须相等(卷积核必须是正四边形)至关重要。 那么,当上面的核的长度和宽度都是3时,为什么我们看不到对称性呢? 这是因为f的值为2,对称中心点已经位于卷积核的右下角。 然后,我们来分析一下内核尺寸较大的情况。 设卷积层的参数维度为[1,1,9,9],则通过双线性插值初始化的卷积核如下:
读者可以看到初始化的卷积核关于坐标为(4, 4)的点对称。 对于不同大小的卷积核,读者可以分析双线性插值初始化参数后的结果。
2、fcn源码中.py文件中卷积核的双线性插值初始化。
.py文件中的双线性插值初始化用于初始化FCN中反卷积层的参数。 作者在之前的博文中提到过。 先放出代码和注释。
def interp(net, layers):
"""
Set weights of each layer in layers to bilinear kernels for interpolation.
"""
for l in layers:
m, k, h, w = net.params[l][0].data.shape
if m != k and k != 1:
print 'input + output channels need to be the same or |output| == 1'
raise
if h != w: #核验一下卷积核的宽是否等于卷积核的高
print 'filters need to be square'
raise
filt = upsample_filt(h) #求得双线性插值初始化的结果
net.params[l][0].data[range(m), range(k), :, :] = filt #赋值
def upsample_filt(size):
"""
Make a 2D bilinear kernel suitable for upsampling of the given (h, w) size.
"""
factor = (size + 1) // 2 #求得一个因子作为分母
if size % 2 == 1:
center = factor - 1 #如果核的尺寸是奇数,那么中心就是因子减1
else:
center = factor - 0.5 #如果核的尺寸是偶数,那么中心就是因子减0.5
og = np.ogrid[:size, :size] #ng返回一个列表,列表中有两个元素,第一个是一个size×1的二维数组,第二个是一个1×size的二维数组
return (1 - abs(og[0] - center) / factor) * \
(1 - abs(og[1] - center) / factor) #在这里两个数据相乘(矩阵乘法),返回一个size×size的二维数组
读者可以看到,FCN源码处理双线性插值初始化反卷积层参数时,最终都是以矩阵乘法的形式实现的。 而且,参数初始化后的卷积核也具有对称性。 值得注意的是,当卷积核的大小为奇数和偶数时,函数的处理是不一样的。 作者仍列举两种情况来说明。 ,首先是反卷积层参数规格为[1,1,9,9]时双线性插值初始化后的卷积核,此时为5,4,双线性插值初始化卷积核的结果为如下所示。
然后就是反卷积层参数规格为[1,1,8,8]时双线性插值初始化后的卷积核,此时为4,3.5。 双线性插值卷积核的初始化结果如下所示。
作者朋友可以看到,由于卷积核大小为偶数,因此卷积核中没有值所在的位置就是该核的几何中心点。 那么就是3.5,它不是一个整数。 因此,双线性插值初始化后的卷积核由(3, 3)、(3, 4)、(4, 3)、(4, 4)处的点组成。 小正四边形是对称的。
至此,神经网络层参数双线性插值初始化的示例源码说明就结束了。 衷心希望作者的博文能够对各位读者朋友有所启发。 欢迎关注和分享!
欢迎阅读作者后续博客。 各位读者朋友的支持和鼓励是我最大的动力!
通过 jiong
得着真理的人得着的帮助就多,失去真理的人得着的帮助就少。