美文网首页
Caffe 从零开始搭建网络并训练

Caffe 从零开始搭建网络并训练

作者: 菜鸟瞎编 | 来源:发表于2018-12-11 18:09 被阅读0次

简介

Caffe网络通常由数据层、网络层(通常我们认为的网络结构)、Loss层组成。Caffe中数据以Blob的形式在Layer之间传播。
每个Layer包含三个基本的操作Setup、Forward、Backward。
Setup: 在模型初始化时重置 layers 及其相互之间的连接 ;
Forward: 从 bottom 层中接收数据,进行计算后将输出送入到 top 层中;
Backward: 给定相对于 top 层输出的梯度,计算其相对于输入的梯度,并传递到 bottom层。
Caffe中各层的参数可以查阅 https://github.com/BVLC/caffe/blob/master/src/caffe/proto/caffe.proto

Caffe的安装参考:https://www.jianshu.com/p/992351958ed4
下面以特征点检测模型为例。新建模型定义文件landmark.prototxt
首先给模型起一个名字

name:"ONet"

一、定义网络结构

1、数据层

Caffe又7种数据层(Data、MemoryData、HDF5Data、HDF5Output、ImageData、WindowData、DummyData),除此之外还可以自定义数据层。
这里我们自定义数据层。
在landmark.prototxt中定义数据层:

layer{
    name: "data"
    type: "Python"
    top: "data"
    top: "label"   
    python_param{
        module: "my_data_layer"
        layer: "ImageDataLayer"
        param_str:"{\'source\':\'../data/preproc/data/112/landmark_aug.txt\', \'batch_size\':384, \'shuffle\':True, \'size\':(112,112)}"
    }
    include: {phase: TRAIN}
}

layer{
    name: "data"
    type: "Python"
    top: "data"
    top: "label"   
    python_param{
        module: "my_data_layer"
        layer: "ImageDataLayer"
        param_str:"{\'source\':\'../data/preproc/data/112/landmark_aug.txt\', \'batch_size\':384, \'shuffle\':False, \'size\':(112,112)}"
    }
    include: {phase: TEST}
}

name表示该层的名字,可以随意取。
其中type:"Python"需要在编译安装Caffe的时候开启Python Layer的支持,在Makefile.config中把这一行WITH_PYTHON_LAYER=1的注释去掉。
top表示该层的输出
bottom表示该层的输入,这里没有输入。
可以有多个top和bottom。
注意:在数据层中,至少有一个命名为data的top https://zhuanlan.zhihu.com/p/34606014
module表示自定义Layer的python代码路径,这个文件需要和landmark.prototxt在同一目录下,注意不要加后缀.py。
layer表示python代码中的类名。
param_str用来传递自定义的变量,类型为字符串
include: {phase: TRAIN}表示训练时使用的Layer.
include: {phase: TEST}表示测试时使用的Layer.
对应的Python代码,文件名为my_data_layer.py:

import caffe
import numpy as np
import random
import sys
class ImageDataLayer(caffe.Layer):
    def setup(self, bottom, top):
        self.top_names = ['data', 'label']
        params = eval(self.param_str)
        self.batch_size =params['batch_size']
        self.shuffle = params['shuffle']
        self.batch_loader = BatchLoader(params)
        height, width = params['size']
        top[0].reshape(self.batch_size, 3, height, width)
        top[1].reshape(self.batch_size, 127*2)

    def forward(self, bottom, top):
        batch = self.batch_loader.next()
        imgs = []
        labels = []
        if self.shuffle:
            random.shuffle(batch)
        for it in batch:
            items = it.split()
            img = cv2.imread(items[0])
            if img is None:
                print("cv2.read %s is None, exit.".format(items[0]))
                sys.exit(1)

            img = np.transpose(img, (2,0,1))    #convert (height, width, 3) to (3, height, width)
            label = map(float, items[1:])

            imgs.append(img)
            labels.append(label)
        top[0].data = np.array(imgs)
        top[1].data = np.array(labels)
        

    def reshape(self, bottom, top):
        pass

    def backward(self, top, propagate_down, bottom):
        pass


class BatchLoader(object):
    def __init__(self, params):
        self.source = params['source']
        self.batch_size = params['batch_size']
        self.size = params['size']
        self.isshuffle = params['shuffle']
        self.datalist = open(self.source,"r").read().splitlines()

    def __iter__(self):
        return self

    def next(self):
        len_data = len(self.datalist)
        for i in range(0, len_data, self.batch_size):
            pad = i+self.batch_size - len_data
            pad = pad if pad >0 else 0
            batch = self.datalist[i:i+self.batch_size] + random.sample(self.datalist[0:i], pad)

            yield batch
2、中间网络层

定义第一层卷积

layer{
    name: "conv1"
    type: "Convolution"
    bottom: "data"
    top: "conv1"
    param{
        lr_mult: 1
    }
    param{
        lr_mult: 2
    }
    convolution_param{
        num_output: 32
        kernel_size: 3
        stride: 1
        weight_filler:{
            type: "xavier"
        }
        bias_filler{
            type: "constant"
        }

    }
}

其中第一个lr_mult:1表示权重的学习率为:1base_lr (在solver.prototxt中定义)
第二个lr_mult:2表示偏置的学习率为:2
base_lr。
num_output表示输出通道数
kernel_size表示卷积核大小,宽高都为kernel_size。如果宽高不相等就分别设置kernel_h、kernel_w。
stride表示步长。
weight_filler type权重初始化方式
bias_filler type偏置初始化方式,设置为constant时默认值为0
pad:填充值,默认为0。设置为2时左右各填充2个。

池化层:
layer{
    name: "pool1"
    type: "Pooling"
    bottom: "conv1"
    top: "pool1"
    pooling_param{
        pool: MAX
        kernel_size: 3
        stride: 2
        pad: 1
    }
}

prelu层:

layer{
    name: "prelu1"
    type: "PReLU"
    bottom: "pool1"
    top: "pool1"
}

用Reshape层实现Flatten操作(当然有专用的Flatten层):

layer{
    name: "flatten"
    type: "Reshape"
    bottom: "prelu4"
    top: "flatten"
    reshape_param{
        shape{
            dim: 0
            dim: -1
        }
    }
}

全连接层:

layer{
    name: "landmark_pred"
    type: "InnerProduct"
    bottom: "prelu5"
    top: "landmark_pred"
    param{
        lr_mult: 1
    }
    param{
        lr_mult: 2
    }
    inner_product_param{
        num_output: 254
        weight_filler:{
            type: "xavier"
        }
        bias_filler{
            type: "constant"
        }
    }
}

自定义Loss层:

layer{
    name: "landmark_loss"
    type: "Python"
    top: "landmark_loss"
    bottom: "landmark_pred"
    bottom: "label"
    python_param{
        module: "wing_loss_layer"
        layer: "WingLossLayer"
        param_str : "{\'w\':1.0, \'eplison\':0.2}"
    }
    # set loss weight so Caffe knows this is a loss layer.
    # since PythonLayer inherits directly from Layer, this isn't automatically
    # known to Caffe
    loss_weight: 1
}

需要加上loss_weight参数,否则会不收敛
自定义Loss(wing loss)的python代码:

import caffe
import numpy as np

class WingLossLayer(caffe.Layer):
    def setup(self, bottom, top):
        if len(bottom) != 2:
            raise Exception("Need two bottom for WingLossLayer")
        params = eval(self.param_str)
        self.w = params['w']
        self.eplison = params['eplison']
        
    def reshape(self, bottom, top):
        if bottom[0].count != bottom[1].count:
            raise Exception("Inputs must have the save dimension")
        self.diff = np.zeros_like(bottom[0].data, dtype=np.float32)
        top[0].reshape(1)

    def forward(self, bottom, top):
        #tag,need reshape bottom[0] and bottom[1],maybe lmdb don't need
      
        self.diff = bottom[0].data - bottom[1].data

        idx = np.abs(self.diff) < self.w
        idx1 = np.abs(self.diff) >= self.w

        top[0].data[...] = (\
            np.sum(self.w * np.log(1.0/self.eplison * np.abs(self.diff[idx]) + 1.)) +\
             np.sum(np.abs(self.diff[idx1]) - (self.w - self.w * np.log(1.0 + self.w/self.eplison)))\
             ) / bottom[0].num

    def backward(self, top, propagate_down, bottom):
        idx0 = (0. < self.diff) & (self.diff < self.w)
        idx1 = (-self.w < self.diff) & (self.diff < 0.)
        idx2 = self.diff >= self.w
        idx3 = self.diff <= -self.w
        #print "idx2"

        for i in range(0,2):
            if not propagate_down[i]:
                continue
            if i == 0:
                sign = 1
            else:
                sign = -1

            bottom[i].diff[idx0] = sign * 1.0 * (self.w / (1. + 1.0/self.eplison * np.abs(self.diff[idx0]))) / bottom[i].num
            bottom[i].diff[idx1] = sign * (-1.0) * (self.w / (1. + 1.0/self.eplison * np.abs(self.diff[idx1]))) / bottom[i].num
            bottom[i].diff[idx2] = sign * 1.0 / bottom[i].num
            bottom[i].diff[idx3] = sign * (-1.0) / bottom[i].num

3、Caffe的BN层

Caffe的BN层由BatchNorm 层和Scale层组成。BatchNorm减均值,Scale层除方差。示例如下:

layer{
    name: "conv1/bn"
    type: "BatchNorm"
    bottom: "conv1"
    top: "conv1/bn"
    batch_norm_param{
        moving_average_fraction: 0.997
        eps: 1e-3
    }
}

layer{
    name: "conv1/scale"
    type: "Scale"
    bottom: "conv1/bn"
    top: "conv1/scale"
    scale_param{
        bias_term: true
    }
}

【参考】
Caffe 中 BN(BatchNorm ) 层的参数均值、方差和滑动系数解读
caffe中的BatchNorm层
Caffe中的BatchNorm实现
浅谈Batch Normalization及其Caffe实现

4、定义DepthwiseConv层

【参考】
https://mc.ai/depthwise-separable-convolution%E2%80%8A-%E2%80%8Ain-caffe-framework/
How to get Depthwise Separable Convolution in Caffe ?
In caffe framework, We can use normal convolution layer as depthwise convolution layer by specifying number of groups as equal to number of input channels.

https://github.com/shicai/MobileNet-Caffe/blob/master/mobilenet_deploy.prototxt
https://github.com/farmingyard/caffe-mobilenet/blob/master/mobilenet_1by2_deploy.prototxt

二、定义优化参数

设置优化参数,文件名定为solver.prototxt

net: "landmark.prototxt"
test_iter: 100
test_interval: 500
base_lr: 0.0001
momentum: 0.9
momentum2: 0.999
type: "Adam"
lr_policy: "fixed"
display: 100
max_iter: 30000
snapshot: 5000
snapshot_prefix: "../../checkpoint/caffe"
solver_mode: GPU

三、训练

执行命令:caffe.bin train --solver=solver.prototxt -gpu

四、部署

部署的时候需用部署专用的模型结构,其实就是去掉了训练阶段的数据层和loss层(一般如此),然后在首层加上Input层,Input层如下:

layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 1 dim: 3 dim: 112 dim: 112 } }
}
1、pycaffe部署,就是用python API进行推理
import caffe
import numpy as np
import cv2
import random
import os
caffe.set_mode_cpu()

class Inference():
    def __init__(self, deploy_proto, model):
        self.net = caffe.Net(deploy_proto, model, caffe.TEST)
        self.transformer = caffe.io.Transformer({'data':self.net.blobs['data'].data.shape})
        self.transformer.set_transpose('data', (2,0,1))
        #self.transformer.set_mean('data', np.array([127.5,127.5,127.5])) 
        #self.transformer.set_raw_scale('data', 1/128.0)
        #self.transformer.set_channel_swap('data', (2,1,0))

    def forward(self, img):
        self.net.blobs['data'].data[...] = self.transformer.preprocess('data',img)        
        out = self.net.forward()
        landmarks = self.net.blobs['landmark_pred'].data[0]
        return landmarks

#调用
if __name__ == "__main__":
    model_path = "onet1_deploy.prototxt"
    weight_path = "../../checkpoint/caffe-onet1/onet_iter_50000.caffemodel"
    net = Inference(model_path, weight_path)
    img_orig = cv2.imread(path)
    img = np.asarray(img_orig).astype(np.float32)
    img = (img-127.5)/128.0
    landmarks = net.forward(img)

【参考】
Caffe for Python 官方教程(翻译)
http://www.voidcn.com/article/p-pgjwtpri-st.html
caffe学习(六):使用python调用训练好的模型来分类(Ubuntu)
Caffe学习笔记(七):使用训练好的model做预测(mnist)
Caffe学习系列(20):用训练好的caffemodel来进行分类
Caffe python layer方法执行时机

参考

http://caffecn.cn/?/page/tutorial
http://manutdzou.github.io/2016/05/15/Caffe-Document.html
caffe添加python数据层(ImageData)
caffe中添加Python层
【caffe中添加C++层】Caffe添加自定义层-自定义loss C++ caffe layer: https://github.com/JunrQ/caffe-layer (内含wing loss layer、depthwise conv layer、coord2heatmap layer、heatmap loss layer)
【自定义Loss】caffe-python-layer 的自定义
用python自定义caffe loss层
https://github.com/BVLC/caffe/blob/master/examples/pycaffe/layers/pyloss.py
【wing loss】 : https://github.com/DaChaoXc/caffe-layer-code/blob/master/wingLoss.py
caffe常见优化器使用参数
caffe的特殊层
caffe solver文件个参数的意义
caffe solver参数详解
caffe学习笔记3:Loss和多个Loss合并问题

Windows Caffe 学习笔记(四)搭建自己的网络,训练和测试MNIST手写字体库
https://github.com/RiweiChen/DeepFace/blob/master/FaceAlignment/try1_2/train_val.prototxt
caffe 中base_lr、weight_decay、lr_mult、decay_mult代表什么意思?
caffe入门应用方法(一)——网络层参数配置解析
Caffe 中 BN(BatchNorm ) 层的参数均值、方差和滑动系数解读
Caffe傻瓜系列(3):激活层(Activiation Layers)及参数
https://gist.github.com/jyegerlehner/b2f073aa8e213f0a9167
CAFFE官方教程学习笔记
Caffe通过代码生成prototxt网络文件:Caffe的深度学习训练全过程

caffe1——图像转换成lmdb(ldeveldb)、hdf5文件

https://blog.csdn.net/u012426298/article/details/80743284

相关文章

网友评论

      本文标题:Caffe 从零开始搭建网络并训练

      本文链接:https://www.haomeiwen.com/subject/zlyehqtx.html