FCN语义分割训练自己数据不收敛处理记录

前言

最近在做FCN语义分割方面的项目,在finetune的时候遇到了训练loss不下降的情况,在经过自己的摸索之后,最后loss曲线下降下去,这里将这个过程记录下来,希望对大家有所帮助。

1. 数据集问题

数据集的问题在按照自己的需求进行语义分割的时候都会遇到。其实数据集的制作很简单,只需要按照自己的需求设计好分类就行了,可以参考本人之前的博客进行修改。需要提醒的是每个0~255的像素值代表的是一个分类,灰度图像就有256个分类。
在进行训练自己数据的时候,分类数目和Github上下载下的文件里面定义的不一样,这里就需要在train.prototxt和val.prototxt文件中将num_output: 21改为num_output: +自己的分类数目。也可以通过train.prototxt中删除Data层Loss层得到网络的定义文件deploy.prototxt

2. 基础学习率问题

博主在进行训练自己的数据的时候在开始进行训练的时候设置的base_lr为1e-4,结果发现loss曲线就是从开始到结束的一条直线,根本没有丝毫的下降,但是将base_lr设置为1e-10的时候loss就下降很快了-_-||

3. 网络权重初始化问题

这是原来调用训练好的模型进行finretune的代码

solver = caffe.SGDSolver('solver.prototxt')
solver.net.copy_from(weights)

要将其改成

solver = caffe.SGDSolver('solver.prototxt')  
# 这里开始的3行都是我们需要增加的
vgg_net = caffe.Net(vgg_proto,vgg_weights,caffe.TRAIN)  
surgery.transplant(solver.net,vgg_net)  
del vgg_net  

其实它是先把这个权重值放到了VGG16的网络中,就是vgg_net = caffe.Net(vgg_proto, vgg_weights, caffe.TRAIN)这一句话
然后把vgg_net的权值通过一个函数转化到我现在这个solver.net里面去,surgery.transplant(solver.net, vgg_net)
就是这么一个过程,附上transplant函数的源码以供参考

def transplant(new_net, net, suffix=''):
    """
    Transfer weights by copying matching parameters, coercing parameters of
    incompatible shape, and dropping unmatched parameters.

    The coercion is useful to convert fully connected layers to their
    equivalent convolutional layers, since the weights are the same and only
    the shapes are different.  In particular, equivalent fully connected and
    convolution layers have shapes O x I and O x I x H x W respectively for O
    outputs channels, I input channels, H kernel height, and W kernel width.

    Both  `net` to `new_net` arguments must be instantiated `caffe.Net`s.
    """
    for p in net.params:
        p_new = p + suffix
        if p_new not in new_net.params:
            print 'dropping', p
            continue
        for i in range(len(net.params[p])):
            if i > (len(new_net.params[p_new]) - 1):
                print 'dropping', p, i
                break
            if net.params[p][i].data.shape != new_net.params[p_new][i].data.shape:
                print 'coercing', p, i, 'from', net.params[p][i].data.shape, 'to', new_net.params[p_new][i].data.shape
            else:
                print 'copying', p, ' -> ', p_new, i
            new_net.params[p_new][i].data.flat = net.params[p][i].data.flat

4. 反卷积层初始化问题

loss不下降,开始训练什么样子,最后还是什么样子

# surgeries  
interp_layers = [k for k in solver.net.params.keys() if 'up' in k]  
surgery.interp(solver.net, interp_layers)  

在上面的代码中,原作者对层名称中有”up”字样的层做了操作,这恰好是训练文件中的反卷积层。
这是对应文件中的代码

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  
    else:  
        center = factor - 0.5  
    og = np.ogrid[:size, :size]  
    return (1 - abs(og[0] - center) / factor) * \  
           (1 - abs(og[1] - center) / factor) 

原来,官方自带的solve.py文件中的interp函数中的upsample_filt函数已经对反卷积层参数进行了双线性插值初始化。在网上博客中看到有博主使用的是.prototxt文件中对反卷积层进行初始化,这是不对的。应该使用官方例子中的方式进行。

5. 结果

这是本人最后经过2W多次训练之后得到的结果:
FCN语义分割训练自己数据不收敛处理记录_第1张图片

你可能感兴趣的:([3]机器学习)