Caffe框架整理

原创
2023/11/07 10:10
阅读数 717

Caffe安装

下载老版本的Anaconda:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/

这里我们要下载的是Anaconda2-2019.10-Linux-x86_64.sh

Caffe框架下载地址:https://pan.baidu.com/s/1-Bqb8p8pPk6lUOepwyG9JA
提取码:17ah

下载完成后解压,进入主目录,执行

cp Makefile.config.example Makefile.config

安装依赖

sudo apt update
sudo apt-get install libleveldb-dev libsnappy-dev libhdf5-serial-dev 
sudo apt-get install libgoogle-glog-dev liblmdb-dev libboost-thread-dev
sudo apt-get install libatlas-base-dev
sudo apt install python2.7-dev

安装opencv 3.4.15,下载地址:https://github.com/opencv/opencv/archive/3.4.15.zip,这里可以参考模型部署篇 中的编译 tensorrtx/yolov5

安装protobuff,这里我们要安装的版本为3.11.4,源码下载地址:https://github.com/protocolbuffers/protobuf/releases/tag/v3.11.4

下载完成后解压,执行

cd protobuf-3.11.4/
./configure
make
make check
sudo make install
cd python
python setup.py build
python setup.py test
python setup.py install
cp -r google ~/anaconda2/lib/python2.7/site-packages/
cd /usr/lib
sudo ln -s /usr/local/lib/libprotobuf.so.22 libprotobuf.so.22
sudo ln -s /usr/local/lib/libprotoc.so.22 libprotoc.so.22

如果是GPU版本的,可以直接使用以下命令安装

sudo apt-get install libprotobuf-dev protobuf-compiler

安装boost python 2.7,下载地址:https://sourceforge.net/projects/boost/files/boost/1.63.0/

解压后,执行

cd boost_1_63_0
./bootstrap.sh --with-libraries=all --with-python=/home/dell/anaconda2/bin/python2.7m

如果未安装Anaconda,则为

./bootstrap.sh --with-libraries=all --with-python=/usr/bin/python2.7

编辑

vim project-config.jam 

添加如下内容

using python : 2.7 : /home/dell/anaconda2/bin/python2.7m : /usr/include/python2.7 : /home/dell/anaconda2/lib ;

如果未安装Anaconda,则为

using python : 2.7 : /usr/bin/python2.7 : /usr/include/python2.7 : /usr/lib/python2.7 ;

编译

sudo ./b2 install --with-python include="/usr/include/python2.7"

安装完成后在/etc/profile末尾添加

export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
export CPLUS_INCLUDE_PATH=/usr/local/include:$CPLUS_INCLUDE_PATH

执行

source /etc/profile
cd /usr/lib
sudo ln -s /usr/lib/x86_64-linux-gnu/libboost_system.so.1.58.0 libboost_system.so
sudo ln -s /usr/lib/x86_64-linux-gnu/libboost_filesystem.so.1.58.0 libboost_filesystem.so

进入src/caffe/proto文件夹,执行(有关 protobuffer 的内容可以参考 Netty 整合 Protobuffer)

protoc caffe.proto --cpp_out=./

回到主目录,修改Makefile.config,这里是使用CPU进行安装。

## Refer to http://caffe.berkeleyvision.org/installation.html
# Contributions simplifying and improving our build system are welcome!

# cuDNN acceleration switch (uncomment to build with cuDNN).
# USE_CUDNN := 1

# CPU-only switch (uncomment to build without GPU support).
CPU_ONLY := 1

# uncomment to disable IO dependencies and corresponding data layers
# USE_OPENCV := 0
# USE_LEVELDB := 0
# USE_LMDB := 0
# This code is taken from https://github.com/sh1r0/caffe-android-lib
# USE_HDF5 := 0

# uncomment to allow MDB_NOLOCK when reading LMDB files (only if necessary)
#	You should not set this flag if you will be reading LMDBs with any
#	possibility of simultaneous read and write
# ALLOW_LMDB_NOLOCK := 1

# Uncomment if you're using OpenCV 3
OPENCV_VERSION := 3

# To customize your choice of compiler, uncomment and set the following.
# N.B. the default for Linux is g++ and the default for OSX is clang++
CUSTOM_CXX := g++

# CUDA directory contains bin/ and lib/ directories that we need.
# CUDA_DIR := /usr/local/cuda
# On Ubuntu 14.04, if cuda tools are installed via
# "sudo apt-get install nvidia-cuda-toolkit" then use this instead:
# CUDA_DIR := /usr

# CUDA architecture setting: going with all of them.
# For CUDA < 6.0, comment the *_50 through *_61 lines for compatibility.
# For CUDA < 8.0, comment the *_60 and *_61 lines for compatibility.
# For CUDA >= 9.0, comment the *_20 and *_21 lines for compatibility.
# CUDA_ARCH := -gencode arch=compute_20,code=sm_20 
# CUDA_ARCH := -gencode arch=compute_30,code=sm_30 
# CUDA_ARCH := -gencode arch=compute_35,code=sm_35 
# CUDA_ARCH := -gencode arch=compute_50,code=sm_50 \
		-gencode arch=compute_52,code=sm_52 \
		-gencode arch=compute_60,code=sm_60 \
		-gencode arch=compute_61,code=sm_61 \
		-gencode arch=compute_61,code=compute_61

# BLAS choice:
# atlas for ATLAS (default)
# mkl for MKL
# open for OpenBlas
BLAS := atlas
# Custom (MKL/ATLAS/OpenBLAS) include and lib directories.
# Leave commented to accept the defaults for your choice of BLAS
# (which should work)!
# BLAS_INCLUDE := /path/to/your/blas
# BLAS_LIB := /path/to/your/blas

# Homebrew puts openblas in a directory that is not on the standard search path
# BLAS_INCLUDE := $(shell brew --prefix openblas)/include
# BLAS_LIB := $(shell brew --prefix openblas)/lib

# This is required only if you will compile the matlab interface.
# MATLAB directory should contain the mex binary in /bin.
# MATLAB_DIR := /usr/local
# MATLAB_DIR := /Applications/MATLAB_R2012b.app

# NOTE: this is required only if you will compile the python interface.
# We need to be able to find Python.h and numpy/arrayobject.h.
# PYTHON_INCLUDE := /usr/include/python2.7 \
		/usr/lib/python2.7/dist-packages/numpy/core/include
# Anaconda Python distribution is quite popular. Include path:
# Verify anaconda location, sometimes it's in root.
ANACONDA_HOME := $(HOME)/anaconda2/
PYTHON_INCLUDE := $(ANACONDA_HOME)/include \
		  $(ANACONDA_HOME)/include/python2.7 \
		  $(ANACONDA_HOME)/lib/python2.7/site-packages/numpy/core/include

# Uncomment to use Python 3 (default is Python 2)
PYTHON_LIBRARIES := boost_python python2.7
# PYTHON_INCLUDE := /usr/include/python3.5m \
#                 /usr/lib/python3.5/dist-packages/numpy/core/include

# We need to be able to find libpythonX.X.so or .dylib.
# PYTHON_LIB := /usr/lib
PYTHON_LIB := $(ANACONDA_HOME)/lib /usr/local/lib

# Homebrew installs numpy in a non standard path (keg only)
# PYTHON_INCLUDE += $(dir $(shell python -c 'import numpy.core; print(numpy.core.__file__)'))/include
# PYTHON_LIB += $(shell brew --prefix numpy)/lib

# Uncomment to support layers written in Python (will link against Python libs)
WITH_PYTHON_LAYER := 1

# Whatever else you find you need goes here.
INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/local/include /usr/include/hdf5/serial/ /usr/local/include/opencv4/ /usr/include/python2.7/ /home/dell/anaconda2/lib/python2.7/site-packages/numpy/core/include
LIBRARY_DIRS := $(PYTHON_LIB) /usr/local/lib /usr/lib /usr/local/lib/opencv4/ /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu/hdf5/serial

# If Homebrew is installed at a non standard location (for example your home directory) and you use it for general dependencies
# INCLUDE_DIRS += $(shell brew --prefix)/include
# LIBRARY_DIRS += $(shell brew --prefix)/lib

# NCCL acceleration switch (uncomment to build with NCCL)
# https://github.com/NVIDIA/nccl (last tested version: v1.2.3-1+cuda8.0)
# USE_NCCL := 1

# Uncomment to use `pkg-config` to specify OpenCV library paths.
# (Usually not necessary -- OpenCV libraries are normally installed in one of the above $LIBRARY_DIRS.)
# USE_PKG_CONFIG := 1

# N.B. both build and distribute dirs are cleared on `make clean`
BUILD_DIR := build
DISTRIBUTE_DIR := distribute

# Uncomment for debugging. Does not work on OSX due to https://github.com/BVLC/caffe/issues/171
# DEBUG := 1

# The ID of the GPU that 'make runtest' will use to run unit tests.
# TEST_GPUID := 0

# enable pretty build (comment to see full commands)
Q ?= @

GPU版本

## Refer to http://caffe.berkeleyvision.org/installation.html
# Contributions simplifying and improving our build system are welcome!

# cuDNN acceleration switch (uncomment to build with cuDNN).
USE_CUDNN := 1

# CPU-only switch (uncomment to build without GPU support).
# CPU_ONLY := 1

# uncomment to disable IO dependencies and corresponding data layers
USE_OPENCV := 1
# USE_LEVELDB := 0
# USE_LMDB := 0
# This code is taken from https://github.com/sh1r0/caffe-android-lib
# USE_HDF5 := 0

# uncomment to allow MDB_NOLOCK when reading LMDB files (only if necessary)
#	You should not set this flag if you will be reading LMDBs with any
#	possibility of simultaneous read and write
# ALLOW_LMDB_NOLOCK := 1

# Uncomment if you're using OpenCV 3
OPENCV_VERSION := 3

# To customize your choice of compiler, uncomment and set the following.
# N.B. the default for Linux is g++ and the default for OSX is clang++
CUSTOM_CXX := g++

# CUDA directory contains bin/ and lib/ directories that we need.
CUDA_DIR := /usr/local/cuda
# On Ubuntu 14.04, if cuda tools are installed via
# "sudo apt-get install nvidia-cuda-toolkit" then use this instead:
# CUDA_DIR := /usr

# CUDA architecture setting: going with all of them.
# For CUDA < 6.0, comment the *_50 through *_61 lines for compatibility.
# For CUDA < 8.0, comment the *_60 and *_61 lines for compatibility.
# For CUDA >= 9.0, comment the *_20 and *_21 lines for compatibility.
CUDA_ARCH := -gencode arch=compute_20,code=sm_20 
CUDA_ARCH := -gencode arch=compute_30,code=sm_30 
CUDA_ARCH := -gencode arch=compute_35,code=sm_35 
CUDA_ARCH := -gencode arch=compute_50,code=sm_50 \
		-gencode arch=compute_52,code=sm_52
#		-gencode arch=compute_60,code=sm_60 \
		-gencode arch=compute_61,code=sm_61 \
		-gencode arch=compute_61,code=compute_61

# BLAS choice:
# atlas for ATLAS (default)
# mkl for MKL
# open for OpenBlas
BLAS := atlas
# Custom (MKL/ATLAS/OpenBLAS) include and lib directories.
# Leave commented to accept the defaults for your choice of BLAS
# (which should work)!
# BLAS_INCLUDE := /path/to/your/blas
# BLAS_LIB := /path/to/your/blas

# Homebrew puts openblas in a directory that is not on the standard search path
# BLAS_INCLUDE := $(shell brew --prefix openblas)/include
# BLAS_LIB := $(shell brew --prefix openblas)/lib

# This is required only if you will compile the matlab interface.
# MATLAB directory should contain the mex binary in /bin.
# MATLAB_DIR := /usr/local
# MATLAB_DIR := /Applications/MATLAB_R2012b.app

# NOTE: this is required only if you will compile the python interface.
# We need to be able to find Python.h and numpy/arrayobject.h.
PYTHON_INCLUDE := /usr/include/python2.7 \
		/home/kc/.local/lib/python2.7/site-packages/numpy/core/include
# Anaconda Python distribution is quite popular. Include path:
# Verify anaconda location, sometimes it's in root.
# ANACONDA_HOME := $(HOME)/anaconda2/
# PYTHON_INCLUDE := $(ANACONDA_HOME)/include \
		  $(ANACONDA_HOME)/include/python2.7 \
		  $(ANACONDA_HOME)/lib/python2.7/site-packages/numpy/core/include

# Uncomment to use Python 3 (default is Python 2)
PYTHON_LIBRARIES := boost_python python2.7
# PYTHON_INCLUDE := /usr/include/python3.5m \
#                 /usr/lib/python3.5/dist-packages/numpy/core/include

# We need to be able to find libpythonX.X.so or .dylib.
# PYTHON_LIB := /usr/lib
PYTHON_LIB := /usr/local/lib

# Homebrew installs numpy in a non standard path (keg only)
# PYTHON_INCLUDE += $(dir $(shell python -c 'import numpy.core; print(numpy.core.__file__)'))/include
# PYTHON_LIB += $(shell brew --prefix numpy)/lib

# Uncomment to support layers written in Python (will link against Python libs)
WITH_PYTHON_LAYER := 1

# Whatever else you find you need goes here.
INCLUDE_DIRS := $(PYTHON_INCLUDE) /usr/include /usr/local/include /usr/include/hdf5/serial/ /usr/local/include/opencv4/ /usr/include/python2.7/ /home/kc/.local/lib/python2.7/site-packages/numpy/core/include/
LIBRARY_DIRS := $(PYTHON_LIB) /usr/local/lib /usr/lib /usr/local/lib/opencv4/ /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu/hdf5/serial

# If Homebrew is installed at a non standard location (for example your home directory) and you use it for general dependencies
# INCLUDE_DIRS += $(shell brew --prefix)/include
# LIBRARY_DIRS += $(shell brew --prefix)/lib

# NCCL acceleration switch (uncomment to build with NCCL)
# https://github.com/NVIDIA/nccl (last tested version: v1.2.3-1+cuda8.0)
# USE_NCCL := 1

# Uncomment to use `pkg-config` to specify OpenCV library paths.
# (Usually not necessary -- OpenCV libraries are normally installed in one of the above $LIBRARY_DIRS.)
USE_PKG_CONFIG := 1

# N.B. both build and distribute dirs are cleared on `make clean`
BUILD_DIR := build
DISTRIBUTE_DIR := distribute

# Uncomment for debugging. Does not work on OSX due to https://github.com/BVLC/caffe/issues/171
# DEBUG := 1

# The ID of the GPU that 'make runtest' will use to run unit tests.
TEST_GPUID := 0

# enable pretty build (comment to see full commands)
Q ?= @

修改Makefile

LIBRARIES += glog gflags protobuf boost_system boost_filesystem m hdf5_serial_hl hdf5_serial
PYTHON_LIBRARIES ?= boost_python

# Complete build flags.
COMMON_FLAGS += $(foreach includedir,$(INCLUDE_DIRS),-I$(includedir))
CXXFLAGS += -pthread -fPIC $(COMMON_FLAGS) $(WARNINGS) -std=c++11
NVCCFLAGS += -ccbin=$(CXX) -Xcompiler -fPIC $(COMMON_FLAGS) -std=c++11
# mex may invoke an older gcc that is too liberal with -Wuninitalized
MATLAB_CXXFLAGS := $(CXXFLAGS) -Wno-uninitialized
LINKFLAGS += -pthread -fPIC $(COMMON_FLAGS) $(WARNINGS) -std=c++11

执行

sudo make all -j16

如果是重新安装需要先执行

sudo make clean

然后执行make all。

安装PyCaffe

进入python文件夹

cd python
wget https://bootstrap.pypa.io/pip/2.7/get-pip.py
python get-pip.py
pip install --upgrade pip
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

添加环境变量

sudo vim /etc/profile

在末尾添加

export PYTHONPATH=/home/user/Downloads/caffe/python:$PYTHONPATH

这里以你自己的caffe目录为准。

回到主目录

sudo make pycaffe -j16

编辑

sudo vim /etc/ld.so.conf

在最后一行添加内容

/usr/local/lib

执行命令

sudo ldconfig
sudo ldconfig /usr/local/cuda/lib64

在conda2的状态下,执行

python
Python 2.7.16 |Anaconda, Inc.| (default, Sep 24 2019, 21:51:30) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import caffe
>>> 

 表示pycaffe安装成功。

模型开发

HelloWorld

进入手写数字识别文件夹

cd data/mnist

执行

./get_mnist.sh

这样我们就得到了基础数据,再转换为Caffe可用的数据。我们回到主目录下,执行

./examples/mnist/create_mnist.sh

此时会在example/mnist目录下出现两个文件夹mnist_train_lmdb以及mnist_test_lmdb表示训练数据和测试数据。

Caffe创建的神经网络跟Pytorch或者TensorFlow不一样,它不需要编写代码,而是书写一种结构文本,它是以.prototxt为后缀名的文件。

hbk_mnist.prototxt

name:"hbk_mnist"

# train/test lmdb数据层
layer {
  name:"mnist"
  type:"Data"
  top:"data"
  top:"label"
  include {
    phase:TRAIN
  }
  transform_param {
    scale:0.00390625
  }
  data_param {
    source:"/home/dell/下载/caffe-master/examples/mnist/mnist_train_lmdb"
    batch_size:64
    backend:LMDB
  }
}
layer {
  name:"mnist"
  type:"Data"
  top:"data"
  top:"label"
  include {
    phase:TEST
  }
  transform_param {
    scale:0.00390625
  }
  data_param {
    source:"/home/dell/下载/caffe-master/examples/mnist/mnist_test_lmdb"
    batch_size:100
    backend:LMDB
  }
}

# 全连接层,激活层为ReLU 784->500->10
layer {
  name:"ip1"
  type:"InnerProduct"
  bottom:"data"
  top:"ip1"
  param {
    lr_mult:2
  }
  inner_product_param {
    num_output:500
    weight_filler {
      type:"xavier"
    }
    bias_filler {
      type:"constant"
    }
  }
}

layer {
  name:"relu1"
  type:"ReLU"
  bottom:"ip1"
  top:"re1"
}
layer {
  name:"ip2"
  type:"InnerProduct"
  bottom:"re1"
  top:"ip2"
  param {
    lr_mult:1
  }
  param {
    lr_mult:2
  }
  inner_product_param {
    num_output:10
    weight_filler {
      type:"xavier"
    }
    bias_filler {
      type:"constant"
    }
  }
}

# 验证测试用,不必须,输出准确率
layer {
  name:"accuracy"
  type:"Accuracy"
  bottom:"ip2"
  bottom:"label"
  top:"accuracy"
  include {
    phase:TEST
  }
}

# 损失函数输出
layer {
  name:"loss"
  type:"SoftmaxWithLoss"
  bottom:"ip2"
  bottom:"label"
  top:"loss"
}

以上就是用于训练手写数据集MNIST的神经网络结构。我们可以对其进行可视化。

执行

sudo find / -name libstdc++.so.6.*

将查找到的最高版本的libstdc++.so.6.*进行拷贝,我这里是

cd ~/anaconda2/lib
cp /usr/lib/i386-linux-gnu/libstdc++.so.6.0.30 ./
ln -sf libstdc++.so.6.0.30 libstdc++.so.6

安装需要的Python组件

sudo apt-get install graphviz
pip install pydot -i https://pypi.tuna.tsinghua.edu.cn/simple

在主目录执行

python python/draw_net.py examples/mnist/hbk_mnist.prototxt aa.png --rankdir=BT

生成的可视化图像aa.png如下

这里的BT代表从底向上进行生成,上图中,最底下的是数据层,最上面的是损失函数。除了BT还有LR(从左向右)。

配置Solver,主要是用来训练的参数配置的,同样也是通过一个.prototxt为后缀名的文件来描述。

hbk_mnist_solver.prototxt

# tran/test net文件路径
net:"examples/mnist/hbk_mnist.prototxt"

# 测试样本数量
test_iter:100

# 训练迭代多少次进行一次Test验证
test_interval:500

# 学习率,缩进,权重
base_lr:0.01
momentum:0.9
weight_decay:0.0005

# 学习率规则
lr_policy:"inv"
gamma:0.0001
power:0.75

# 多少次迭代输出一次信息
display:100
# 最大迭代次数
max_iter:10001
# 存储中间结果
snapshot:5000
snapshot_prefix:"snapshot"

# 运算模式:CPU or GPU
solver_mode:CPU

添加环境变量

sudo vim /etc/profile

添加内容如下

export PATH=/home/dell/下载/caffe-master/build/tools:$PATH

 执行

source /etc/profile

在任意目录执行

caffe

有如下的输出

caffe: command line brew
usage: caffe <command> <args>

commands:
  train           train or finetune a model
  test            score a model
  device_query    show GPU diagnostic information
  time            benchmark model execution time

  Flags from tools/caffe.cpp:
    -gpu (Optional; run in GPU mode on given device IDs separated by ','.Use
      '-gpu all' to run on all available GPUs. The effective training batch
      size is multiplied by the number of devices.) type: string default: ""
    -iterations (The number of iterations to run.) type: int32 default: 50
    -level (Optional; network level.) type: int32 default: 0
    -model (The model definition protocol buffer text file.) type: string
      default: ""
    -phase (Optional; network phase (TRAIN or TEST). Only used for 'time'.)
      type: string default: ""
    -sighup_effect (Optional; action to take when a SIGHUP signal is received:
      snapshot, stop or none.) type: string default: "snapshot"
    -sigint_effect (Optional; action to take when a SIGINT signal is received:
      snapshot, stop or none.) type: string default: "stop"
    -snapshot (Optional; the snapshot solver state to resume training.)
      type: string default: ""
    -solver (The solver definition protocol buffer text file.) type: string
      default: ""
    -stage (Optional; network stages (not to be confused with phase), separated
      by ','.) type: string default: ""
    -weights (Optional; the pretrained weights to initialize finetuning,
      separated by ','. Cannot be set simultaneously with snapshot.)
      type: string default: ""

查看训练时间,在主目录输入

caffe time -model examples/mnist/hbk_mnist.prototxt -iterations 10

这里输出显示

Total Time: 25 ms.

表示迭代10次的时间为25ms。

模型训练,在主目录输入

caffe train -solver examples/mnist/hbk_mnist_solver.prototxt

Python接口

无论是数据层还是网络层都是Layer的实现,Net是一堆Layer的组合,Solver是迭代算法。

现在我们来实现一下之前用prototxt样例的Python接口。

# -*- coding: UTF-8 -*-

import caffe
from pylab import *
from caffe import layers as L
from caffe import params as P

def net(dbfile, batch_size, mean_value=0):
    # caffe模型
    n = caffe.NetSpec()
    # 创建第一层,数据层 传入两类数据:图片数据和标签
    n.data, n.label = L.Data(source=dbfile, backend=P.Data.LMDB, batch_size=batch_size, ntop=2)
    # 创建第一个全连接层
    n.ip1 = L.InnerProduct(n.data, num_output=500, weight_filler=dict(type='xavier'))
    # 创建激活函数层
    n.relu1 = L.ReLU(n.ip1, in_place=True)
    # 创建第二个全连接层
    n.ip2 = L.InnerProduct(n.relu1, num_output=10, weight_filler=dict(type='xavier'))
    # 创建softmax层
    n.loss = L.SoftmaxWithLoss(n.ip2, n.label)
    # 模型精度计算
    n.accu = L.Accuracy(n.ip2, n.label, include={'phase': caffe.TEST})
    return n.to_proto()

if __name__ == '__main__':

    with open('auto_train00.prototxt', 'w') as f:
        f.write(str(net('/home/dell/下载/caffe-master/examples/mnist/mnist_train_lmdb', 64)))
    with open('auto_test00.prototxt', 'w') as f:
        f.write(str(net('/home/dell/下载/caffe-maser/examples/mnist/mnist_test_lmdb', 100)))
    # 读取训练配置参数
    solver = caffe.SGDSolver('hbk_mnist_solver.prototxt')
    # 前向推理
    solver.net.forward()
    solver.test_nets[0].forward()

    solver.step(1)

这里的hbk_mnist_solver.prototxt是修改过的,内容如下

# tran/test net文件路径
net:"auto_train00.prototxt"

# 测试样本数量
test_iter:100

# 训练迭代多少次进行一次Test验证
test_interval:500

# 学习率,缩进,权重
base_lr:0.01
momentum:0.9
weight_decay:0.0005

# 学习率规则
lr_policy:"inv"
gamma:0.0001
power:0.75

# 多少次迭代输出一次信息
display:100
# 最大迭代次数
max_iter:10001
# 存储中间结果
snapshot:5000
snapshot_prefix:"snapshot"

# 运算模式:CPU or GPU
solver_mode:CPU

执行后生成了 auto_train00.prototxt和auto_test00.prototxt两个文件,内容与之前hbk_mnist.prototxt大致相同,有些许差异。

数据各层详解

  • DummyData

DummyData是数据层的一种,主要用来构建虚拟数据使用,一般在proto中构造如下

layer {
  name:"data"
  type:"DummyData"
  top:"data"
  dummy_data_param {
    shape:{dim:10 dim:1 dim:28 dim:28}
    data_filler:{type:"gaussian"}
  }
}

这里的shape中第一个维度为batch_size,第二维为channel,第三维为height,第四维为width。data_filler为数据填充的分布,这里为高斯分布,也就是正态分布,也可以设置成常数。

import caffe
from caffe import layers as L

def net():
    n = caffe.NetSpec()
    n.data = L.DummyData(shape=dict(dim=[10, 1, 28, 28]), data_filler=dict(type='gaussian'))
    n.label = L.DummyData(shape=dict(dim=[10, 1, 1, 1]), data_filler=dict(type='gaussian'))
    n.ip1 = L.InnerProduct(n.data, num_output=50, weight_filler=dict(type='xavier'))
    n.relu1 = L.ReLU(n.ip1, in_place=True)
    n.ip2 = L.InnerProduct(n.relu1, num_output=4, weight_filler=dict(type='xavier'))
    n.loss = L.SoftmaxWithLoss(n.ip2, n.label)
    return n.to_proto()

if __name__ == '__main__':

    with open('dm_py.prototxt', 'w') as f:
        f.write(str(net()))

    solver = caffe.SGDSolver('dm_solver.prototxt')

    print solver.net.blobs['data'].data.shape
    print solver.net.blobs['label'].data.shape

这里的dm_solver.prototxt就是将之前的hbk_mnist_solver.prototxt的net改成dm_py.prototxt即可。这里由于是Python 2.7的,所以print写法与Python3不同。

运行结果

(10, 1, 28, 28)
(10, 1, 1, 1)
  • ImageData

ImageData也是数据层的一种,表示直接输入原始图像信息,而不需要转化为lmdb格式,一般在proto中构造如下

layer {
  name:"data"
  type:"ImageData"
  top:"data"
  top:"label"
  image_data_param {
    source:"xxx"
    batch_size:200
    new_height:28
    new_width:28
  }
}

这里的source为所有图像所存储的文件夹,new_height和new_width为图像进入网络后resize的大小。

安装Python 2.7支持的OpenCV

pip install opencv-python==4.1.1.26 -i https://pypi.tuna.tsinghua.edu.cn/simple

原始图像数据集下载地址链接: https://pan.baidu.com/s/1nMcDHsI2lm_g6AypaIfYCw 提取码: bgyd

生成训练集与验证集

import os
import cv2
import numpy as np
import pdb

def write_img_list(data, filename):
    with open(filename, 'w') as f:
        for i in xrange(len(data)):
            f.write(data[i][0] + ' ' + str(data[i][1]) + '\n')

if __name__ == '__main__':

    image_size = 28
    s = 'ABCDEFGHIJ'

    filedir = '/home/dell/notMNIST_small/'

    filedir2 = os.listdir(filedir)

    datasets = []
    data = []
    for subdir in filedir2:
        if os.path.isdir(filedir + subdir):
            files = os.listdir(filedir + subdir)
            dataset = np.ndarray(shape=(len(files), image_size, image_size), dtype=np.float32)

            num_image = 0
            for file in files:
                if file[-3:] == 'png':
                    tmp = cv2.imread(filedir + subdir + '/' + file, cv2.IMREAD_GRAYSCALE)
                    try:
                        if tmp.shape == (image_size,image_size):
                            datasets.append((filedir + subdir + '/' + file, s.rfind(subdir)))
                            data.append(tmp)
                            num_image += 1
                        else:
                            print subdir, file, tmp.shape
                    except:
                        print subdir, file, tmp
                else:
                    print file

    np.random.shuffle(datasets)
    print np.mean(np.array(data))

    TRAN_NUM = 4 * len(datasets) / 5

    write_img_list(datasets[0:TRAN_NUM], 'train00.imglist')
    write_img_list(datasets[TRAN_NUM:], 'test00.imglist')

 '/home/dell/notMNIST_small/'是我这里的数据集存放路径,你可以更换成你自己的,其中不要包含中文。

生成后的train00.imglist大致内容如下

/home/dell/notMNIST_small/I/Q2hhbGV0Qm9vayBCb2xkIEl0YWxpYy50dGY=.png 8
/home/dell/notMNIST_small/J/RW5nbGlzaCAxMTEgQWRhZ2lvIEJULnR0Zg==.png 9
/home/dell/notMNIST_small/E/RGlhbWFudGUtQm9sZC5vdGY=.png 4
/home/dell/notMNIST_small/J/Q2hvcmQtQmxhY2sub3Rm.png 9
/home/dell/notMNIST_small/J/RnV0dXJhIE1kIEJUIE1lZGl1bSBJdGFsaWMudHRm.png 9
/home/dell/notMNIST_small/A/RFRMRG9jdW1lbnRhVC1NZWRpdW0ub3Rm.png 0
/home/dell/notMNIST_small/E/QXJpYWxNVFN0ZC1MaWdodEl0YWxpYy5vdGY=.png 4
/home/dell/notMNIST_small/G/Q2xlYXJ2aWV3SHd5LTYtQi50dGY=.png 6
/home/dell/notMNIST_small/G/RnJvc3R5c0hhbmQgUmVndWxhci50dGY=.png 6
/home/dell/notMNIST_small/A/QWt6aWRlbnpHcm90ZXNrQkUtTWQub3Rm.png 0

后面的数字代表该图像文件的标签。

import caffe
from pylab import *
from caffe import layers as L

def net(img_list, batch_size, mean_value=0):
    n = caffe.NetSpec()
    n.data, n.label = L.ImageData(source=img_list, batch_size=batch_size, new_width=28, new_height=28, ntop=2)
    n.ip1 = L.InnerProduct(n.data, num_output=50, weight_filler=dict(type='xavier'))
    n.relu1 = L.ReLU(n.ip1, in_place=True)
    n.ip2 = L.InnerProduct(n.relu1, num_output=4, weight_filler=dict(type='xavier'))
    n.loss = L.SoftmaxWithLoss(n.ip2, n.label)
    return n.to_proto()

if __name__ == '__main__':

    with open('auto_train01.prototxt', 'w') as f:
        f.write(str(net('train00.imglist', 200)))
    with open('auto_test01.prototxt', 'w') as f:
        f.write(str(net('test00.imglist', 50)))

    solver = caffe.SGDSolver('img_solver.prototxt')
    # 开始训练
    solver.solve()

这里的img_solver.prototxt就是将之前的hbk_mnist_solver.prototxt的net改成img_solver.prototxt

  • MemoryData

MemoryData也是数据层的一种,表示直接从内存中读取数据进入Caffe,一般在 proto 中构造如下

Layer {
  name:"data"
  type:"MemoryData"
  top:"data"
  top:"label"
  memory_data_param {
    batch_size:200
    channel:1
    height:28
    width:28
  }
}

这里就不再编写Python生成网络结构的代码了,我们来看一下该数据层的使用。

先编写出最终的网络结构的proto文本

mem_hbk_mnist.prototxt

name:"mem_hbk_mnist"

# train/test lmdb数据层
layer {
  name:"mnist"
  type:"MemoryData"
  top:"data"
  top:"label"
  include {
    phase:TRAIN
  }
  memory_data_param {
    batch_size:50
    channels:2
    height:1
    width:1
  }
}
layer {
  name:"mnist"
  type:"MemoryData"
  top:"data"
  top:"label"
  include {
    phase:TEST
  }
  memory_data_param {
    batch_size:50
    channels:2
    height:1
    width:1
  }
}

# 全连接层,激活层为ReLU 784->50->10
layer {
  name:"ip1"
  type:"InnerProduct"
  bottom:"data"
  top:"ip1"
  param {
    lr_mult:1
  }
  param {
    lr_mult:2
  }
  inner_product_param {
    num_output:50
    weight_filler {
      type:"xavier"
    }
    bias_filler {
      type:"constant"
    }
  }
}

layer {
  name:"relu1"
  type:"ReLU"
  bottom:"ip1"
  top:"re1"
}
layer {
  name:"ip2"
  type:"InnerProduct"
  bottom:"re1"
  top:"ip2"
  param {
    lr_mult:1
  }
  param {
    lr_mult:2
  }
  inner_product_param {
    num_output:2
    weight_filler {
      type:"xavier"
    }
    bias_filler {
      type:"constant"
    }
  }
}

# 验证测试用,不必须,输出准确率
layer {
  name:"accuracy"
  type:"Accuracy"
  bottom:"ip2"
  bottom:"label"
  top:"accuracy"
  include {
    phase:TEST
  }
}

# 损失函数输出
layer {
  name:"loss"
  type:"SoftmaxWithLoss"
  bottom:"ip2"
  bottom:"label"
  top:"loss"
}

mem_hbk_mnist_solver.prototxt

# tran/test net文件路径
net:"mem_hbk_mnist.prototxt"

# 测试数量
test_iter:100

# 训练迭代多少次进行一次Test验证
test_interval:500

# 学习率,缩进,权重
base_lr:0.01
momentum:0.9
weight_decay:0.0005

# 学习率规则
lr_policy:"inv"
gamma:0.0001
power:0.75

# 多少次迭代输出一次信息
display:100
# 最大迭代次数
max_iter:10001
# 存储中间结果
snapshot:5000
snapshot_prefix:"snapshot"

# 运算模式:CPU or GPU
solver_mode:CPU
# -*- coding: UTF-8 -*-

import caffe
import numpy as np
from pylab import *

if __name__ == '__main__':

    solver = caffe.SGDSolver('mem_hbk_mnist_solver.prototxt')

    N = 1000
    # 创建10000的数据序列
    t = np.linspace(0, 2 * np.pi, N)
    # 构建负样本数据
    x1 = np.array([t, 30 * np.cos(t)])
    # 构建正样本数据
    x2 = np.array([t, 29 * np.cos(t)])
    # 构建负样本标签
    y1 = np.zeros((N, 1))
    # 构建正样本标签
    y2 = np.ones((N, 1))
    # 合并正负数据集
    X = np.concatenate((x1.T, x2.T)).astype('float32')
    # 合并正负标签
    Y = np.concatenate((y1, y2)).astype('float32')
    # 建立数据索引集,2000
    idx = np.arange(len(Y))

    _, ax1 = subplots()
    ax1.scatter(X[0:N, 0], X[0:N, 1], c='r')
    ax1.scatter(X[N:, 0], X[N:, 1], c='b')
    ax1.set_xlabel('t')
    ax1.set_ylabel('X')
    _.savefig('input.png')

    X_train = X[idx, :].reshape(X.shape[0], 2, 1, 1)
    Y_train = Y[idx].reshape(Y.shape[0], 1, 1, 1)

    solver.net.set_input_arrays(X_train, Y_train)
    solver.test_nets[0].set_input_arrays(X_train, Y_train)

    for i in range(1001):
        solver.step(1)

这里我们构建了一组数据,并传入到网络中,input.png如下

这里红色的为负样本数据,蓝色的为正样本数据。

  • HDF5Data

HDF5Data也是数据层的一种。HDF5是一种用于存储和组织大规模科学数据的文件格式,它具有高效的I/O操作和灵活的数据结构,因此非常适合在深度学习中使用。一般在 proto 中构造如下

Layer {
  name:"data"
  type:"HDF5Data"
  top:"data"
  top:"label"
  hdf5_data_param {
    source:"xxx.h5list"
    batch_size:100
  }
}

这里我们看一下如何将上一小节的余弦波数据转换成HDF5数据。

# -*- coding: UTF-8 -*-

import h5py
import numpy as np

if __name__ == '__main__':

    N = 1000
    # 创建10000的数据序列
    t = np.linspace(0, 2 * np.pi, N)
    # 构建负样本数据
    x1 = np.array([t, 30 * np.cos(t)])
    # 构建正样本数据
    x2 = np.array([t, 29 * np.cos(t)])
    # 构建负样本标签
    y1 = np.zeros((N, 1))
    # 构建正样本标签
    y2 = np.ones((N, 1))
    # 合并正负数据集
    X = np.concatenate((x1.T, x2.T)).astype('float32')
    # 合并正负标签
    Y = np.concatenate((y1, y2)).astype('float32')

    for i in range(2):
        X[:, i] = (X[:, i] - np.min(X[:, i])) / (np.max(X[:, i]) - np.min(X[:, i]))

    idx = np.arange(len(Y))
    np.random.shuffle(idx)

    filenum = 10
    filelen = len(Y) / 10
    # 创建数据文件
    for i in range(filenum):
        with h5py.File('train' + str(i) + '.h5', 'w') as f:
            f.create_dataset('data', data=X[idx[i * filelen: (i + 1) * filelen], :])
            f.create_dataset('label', data=Y[idx[i * filelen: (i + 1) * filelen], :], dtype="i")
    # 划分训练集和验证集
    filelist = range(filenum)
    with open('train.h5list', 'w') as f:
        for i in filelist[0: filenum * 4 / 5]:
            f.write('train' + str(i) + '.h5\n')

    with open('test.h5list', 'w') as f:
        for i in filelist[filenum * 4 / 5:]:
            f.write('train' + str(i) + 'h5\n')

这样就创建了train0.h5到train9.h5十个数据文件,数据集的划分为前8个为训练集,后2个为验证集。

现在我们可以将生成的这些数据集读取进网络进行训练。

import caffe
from pylab import *
from caffe import layers as L

def net(hdf5, batch_size):
    n = caffe.NetSpec()
    n.data, n.label = L.HDF5Data(batch_size=batch_size, source=hdf5, ntop=2)
    n.ip1 = L.InnerProduct(n.data, num_output=50, weight_filler=dict(type='xavier'))
    n.relu1 = L.ReLU(n.ip1, in_place=True)
    n.ip2 = L.InnerProduct(n.relu1, num_output=4, weight_filler=dict(type='xavier'))
    n.loss = L.SoftmaxWithLoss(n.ip2, n.label)
    n.accu = L.Accuracy(n.ip2, n.label)
    return n.to_proto()

if __name__ == '__main__':

    with open('auto_train02.prototxt', 'w') as f:
        f.write(str(net('train.h5list', 100)))
    with open('auto_test02.prototxt', 'w') as f:
        f.write(str(net('test.h5list', 50)))

    solver = caffe.SGDSolver('hdf5_solver.prototxt')

    niter = 1001

    for it in range(niter):
        solver.step(1)

这里hdf5_solver.prototxt的内容如下

train_net: "auto_train02.prototxt"
test_net: "auto_test02.prototxt"
test_iter: 100
test_interval: 200
base_lr: 0.1
momentum: 0.9
weight_decay: 0.0001
lr_policy: "inv"
gamma: 0.001
power: 0.75
display: 100
max_iter: 10000
snapshot: 5000
snapshot_prefix: "sed"
solver_mode: CPU
  • LMDBData

LMDBData是Caffe主要使用的一种数据层,采用内存-映射文件(memory-mapped files),所以拥有非常好的I/O性能,而且对于大型数据库来说,HDF5的文件常常整个写入内存,所以HDF5的文件大小就受限于内存大小,当然也可以通过文件分割来解决问题,但其I/O性能就不如LMDB的页缓存(page cachiing)策略了。一般在 proto 中构造如下

Layer {
  name:"data"
  type:"Data"
  top:"data"
  top:"label"
  data_param {
    source:"xxx"
    batch_size:100
    backend:LMDB
  }
}

它的type跟其他类型的数据层都不同,它直接使用Data即可。这里我们看一下如何将数据转换成LMDB格式的文件。

安装LMDB Python组件

pip install lmdb -i https://pypi.tuna.tsinghua.edu.cn/simple
# -*- coding: UTF-8 -*-

import lmdb
import numpy as np
import caffe

def write_lmdb(filename, X, Y):
    N = len(Y)
    map_size = X.nbytes * 10

    env = lmdb.open(filename, map_size=map_size)

    with env.begin(write=True) as txn:
        for i in range(N):
            # datumlmdb的数据格式,这里将数据Xdatum进行转换
            datum = caffe.io.array_to_datum(X[i, :, :, :])
            datum.label = int(Y[i])
            txn.put('{:0>10d}'.format(i), datum.SerializeToString())

def read_lmdb(filename):
    env = lmdb.open(filename, readonly=True)
    with env.begin() as txn:
        cursor = txn.cursor()
        datum = caffe.proto.caffe_pb2.Datum()

        i = 0
        # 这里是全部遍历完读取最后一个数value
        for key, value in cursor:
            i += 1
        datum.ParseFromString(value)

        x = caffe.io.datum_to_array(datum)
        y = datum.label
        return x, y

if __name__ == '__main__':

    N = 1000
    # 创建一个(1000,3,32,32)110随机的张量
    X1 = np.random.randint(1, 10, (N, 3, 32, 32))
    Y1 = np.zeros(N, dtype=np.int64)
    X2 = np.random.randint(1, 10, (N, 3, 32, 32)) * 10
    Y2 = np.ones(N, dtype=np.int64)
    X3 = np.random.randint(1, 10, (N, 3, 32, 32)) * 20
    Y3 = np.ones(N, dtype=np.int64) * 2
    X4 = np.random.randint(1, 10, (N, 3, 32, 32)) * 30
    Y4 = np.ones(N, dtype=np.int64) * 3
    # 按垂直方向合并X,按水平方向合并Y
    X = np.vstack((X1, X2, X3, X4))
    Y = np.hstack((Y1, Y2, Y3, Y4))

    idx = np.arange(len(Y))
    np.random.shuffle(idx)

    TRAIN_NUM = 4 * len(Y) / 5

    write_lmdb("hbk_lmdb_train", X[idx[0: TRAIN_NUM], :, :, :], Y[idx[0: TRAIN_NUM]])
    write_lmdb("hbk_lmdb_test", X[idx[TRAIN_NUM:], :, :, :], Y[idx[TRAIN_NUM:]])

    X1, Y1 = read_lmdb("hbk_lmdb_train")

    print X1.shape, Y1
    print np.mean(X)

运行结果

(3, 32, 32) 3
76.25537662760416

此时会产生两个文件夹hbk_lmdb_train和hbk_lmdb_test,其中就包含了lmdb的数据文件data.mdb和lock.mdb。现在将生成的数据读取到网络中进行训练。

import caffe
from pylab import *
from caffe import layers as L
from caffe import params as P


def net(datafile, batch_size, mean_value=0):
    n = caffe.NetSpec()
    n.data, n.label = L.Data(source=datafile, backend=P.Data.LMDB, batch_size=batch_size, ntop=2,
                    transform_param=dict(scale=1.0 / 30.0, mean_value=mean_value))
    n.ip1 = L.InnerProduct(n.data, num_output=50, weight_filler=dict(type='xavier'))
    n.relu1 = L.ReLU(n.ip1, in_place=True)
    n.ip2 = L.InnerProduct(n.relu1, num_output=10, weight_filler=dict(type='xavier'))
    n.loss = L.SigmoidCrossEntropyLoss(n.ip1, n.label)
    return n.to_proto()

if __name__ == '__main__':

    imgdata_mean = 76.25
    with open('auto_train03.prototxt', 'w') as f:
        f.write(str(net('hbk_lmdb_train', 200, imgdata_mean)))
    with open('auto_test03.prototxt', 'w') as f:
        f.write(str(net('hbk_lmdb_test', 50, imgdata_mean)))

    solver = caffe.SGDSolver('lmdb_solver.prototxt')

    niter = 1001

    for it in range(niter):
        solver.step(1)

这里的 lmdb_solver.prototxt跟之前的hdf5_solver.prototxt是一样的,只需要替换

train_net: "auto_train03.prototxt"
test_net: "auto_test03.prototxt"

Solver Python接口

这里依然以手写数据集mnist来说明

# -*- coding: UTF-8 -*-

import caffe
from pylab import *
from caffe import layers as L
from caffe import params as P
from caffe.proto import caffe_pb2

def net(dbfile, batch_size, mean_value=0):
    n = caffe.NetSpec()
    n.data, n.label = L.Data(source=dbfile, backend=P.Data.LMDB, batch_size=batch_size, ntop=2, transform_param=dict(scale=0.00390625))
    n.ip1 = L.InnerProduct(n.data, num_output=500, weight_filler=dict(type='xavier'))
    n.relu1 = L.ReLU(n.ip1, in_place=True)
    n.ip2 = L.InnerProduct(n.relu1, num_output=10, weight_filler=dict(type='xavier'))
    n.loss = L.SoftmaxWithLoss(n.ip2, n.label)
    n.accu = L.Accuracy(n.ip2, n.label, include={'phase': caffe.TEST})
    return n.to_proto()

if __name__ == '__main__':

    train_net_file = 'auto_train00.prototxt'
    test_net_file = 'auto_test00.prototxt'
    solver_file = "mnist_solver.prototxt"

    with open(train_net_file, 'w') as f:
        f.write(str(net('/home/dell/下载/caffe-master/examples/mnist/mnist_train_lmdb', 64)))
    with open(test_net_file, 'w') as f:
        f.write(str(net('/home/dell/下载/caffe-master/examples/mnist/mnist_test_lmdb', 100)))

    # 创建solver参数解析器
    s = caffe_pb2.SolverParameter()
    # 设置各项训练参数
    s.train_net = train_net_file
    s.test_net.append(test_net_file)
    s.test_interval = 500
    s.test_iter.append(100)
    s.display = 500
    s.max_iter = 10000
    s.weight_decay = 0.005
    s.base_lr = 0.1
    s.lr_policy = "step"
    s.gamma = 0.1
    s.stepsize = 5000
    s.solver_mode = caffe_pb2.SolverParameter.CPU

    with open(solver_file, 'w') as f:
        f.write(str(s))

    solver = caffe.get_solver(solver_file)

    niter = 2001

    for it in range(niter):
        solver.step(1)

其中生成的mnist_solver.prototxt内容如下

train_net: "auto_train00.prototxt"
test_net: "auto_test00.prototxt"
test_iter: 100
test_interval: 500
base_lr: 0.10000000149
display: 500
max_iter: 10000
lr_policy: "step"
gamma: 0.10000000149
weight_decay: 0.00499999988824
stepsize: 5000
solver_mode: CPU

网络各层详解

之前我们一直使用的有全连接层、激活层,现在我们来看它们以及其他网络层的说明。

  • 全连接层

全连接层的含义可以参考Tensorflow深度学习算法整理 中的全连接层,这里只说明在Caffe中的用法。一般在 proto 中构造如下

layer {
  name:"ip1"
  type:"InnerProduct" #全连接层
  bottom:"data" #输入
  top:"ip1" #输出,名字可任意命名,下一层的bottom接改名称
  param {
    lr_mult:1 #权重学习率倍数
    decay_mult:1 #正则化权重,防止过拟合
  }
  param {
    lr_mult:2 #偏置学习率倍数
    decay_mult:1
  }
  inner_product_param {
    num_output:500  #输出一维向量维度
    weight_filler {
      type:"xavier" #权重初始化方式的一种,可以是gaussion,msra
    }
    bias_filler {
      type:"constant" #偏置初始化方式的一种
      value:0
    }
  }
}
  • 激活层

激活层其实有很多种,ReLU只是其中之一,具体的含义可以参考Tensorflow深度学习算法整理 中的激活函数。一般在 proto 中构造如下

layer {
  name:"relu"
  type:"ReLU"
  bottom:"ip1"
  top:"re1"
  relu_param {
    negative_slope:0.1 #这里指除了正常的relu激活函数外,还有LeakReLU,为YOLOV3使用的激活函数
  }
}
  • 卷积层

卷积层的含义可以参考Tensorflow深度学习算法整理 中的卷积神经网络。一般在 proto 中构造如下

layer {
  name:"conv1"
  type:"Convolution"
  bottom:"data"
  top:"conv1"
  param {}
  param {}
  convolution_param {
    num_output:32 #输出通道数
    kernel_size:7 #卷积核大小
    pad:3 #填充大小
    stride:2 #步长
    weight_filler {}
    bias_filleer {}
  }
}
  • 池化层

池化层的含义可以参考Tensorflow深度学习算法整理 中的池化。一般在 proto 中构造如下

layer {
  name:"pool1"
  type:"Pooling"
  bottom:"relu1"
  top:"pool1"
  pooling_param {
    kernel_size:2 
    pad:1
    stride:2
    pool:MAX #最大池化
  }
}

现在我们用Python接口来实现卷积层与池化层。

# -*- coding: UTF-8 -*-

import caffe
from caffe import layers as L
from caffe import params as P
import caffe.draw
from caffe.proto import caffe_pb2
from google.protobuf import text_format

def conv_pool_net():
    n = caffe.NetSpec()
    n.data = L.DummyData(shape=dict(dim=[20, 1, 64, 64]), data_filler=dict(type='gaussian'))
    n.label = L.DummyData(shape=dict(dim=[20, 10, 1, 1]), data_filler=dict(type='gaussian'))
    n.conv1 = L.Convolution(n.data, num_output=20, kernel_size=4, stride=3, pad=0)
    n.relu1 = L.ReLU(n.conv1, in_place=True)
    n.pool1 = L.Pooling(n.relu1, pool=P.Pooling.MAX, kernel_size=2, stride=2)
    for i in range(2):
        n.conv1 = L.Convolution(n.pool1, num_output=10, kernel_size=4, stride=2, pad=3)
        n.relu1 = L.ReLU(n.conv1, in_place=True)
        n.pool1 = L.Pooling(n.relu1, pool=P.Pooling.MAX, kernel_size=2, stride=2)
    n.ip2 = L.InnerProduct(n.pool1, num_output=10, weight_filler=dict(type='xavier'))
    n.loss = L.SigmoidCrossEntropyLoss(n.ip2, n.label)
    return n.to_proto()

def gen_solver(solver_file, net_file, test_net_file=None):
    s = caffe_pb2.SolverParameter()
    s.train_net = net_file
    if not test_net_file:
        s.test_net.append(net_file)
    else:
        s.test_net.append(test_net_file)

    s.test_interval = 500  # 每训练500次,执行一次测试
    s.test_iter.append(100)  # 测试迭代次数,假设测试数据有8000个,那batch size=80
    s.max_iter = 20000  # 最大迭代次数

    s.base_lr = 0.001  # 基础学习率
    s.momentum = 0.9  # momentum系数
    s.weight_decay = 5e-4  # 正则化权值衰减因子,防止过拟合

    s.lr_policy = 'step'  # 学习率衰减方法
    s.stepsize = 1000  # 只对step方法有效, base_lr*gamma^floor(iter/stepsize)
    s.gamma = 0.1
    s.display = 500  # 输出日志间隔迭代次数
    s.snapshot = 10000  # 在指定迭代次数时保存模型
    s.snapshot_prefix = 'shapshot'
    s.type = 'SGD'  # 迭代算法类型, ADADELTA, ADAM, ADAGRAD, RMSPROP, NESTEROV
    s.solver_mode = caffe_pb2.SolverParameter.CPU

    with open(solver_file, 'w') as f:
        f.write(str(s))

    solver = caffe.SGDSolver(solver_file)
    return solver

def print_net_shape(net):
    print "======data and diff output shape======"
    for layer_name, blob in net.blobs.iteritems():
        print layer_name + ' out \t' + str(blob.data.shape)
        print layer_name + ' diff\t' + str(blob.diff.shape)

    print "======   weight and bias shape  ======"
    for layer_name, param in net.params.iteritems():
        print layer_name + ' weight\t' + str(param[0].data.shape), str(param[1].data.shape)
        print layer_name + ' diff  \t' + str(param[0].diff.shape), str(param[1].diff.shape)

def draw_net(net_file, jpg_file):
    net = caffe_pb2.NetParameter()
    text_format.Merge(open(net_file).read(), net)
    caffe.draw.draw_net_to_file(net, jpg_file, 'BT')

if __name__ == '__main__':

    solver_file = 'covn_solver.prototxt'
    net_file = 'conv_pool_py.prototxt'
    # 创建网络文件
    with open(net_file, 'w') as f:
        f.write(str(conv_pool_net()))
    # 创建solver文件
    solver = gen_solver(solver_file, net_file)
    print_net_shape(solver.net)
    draw_net(net_file, "a.jpg")

生成的 conv_pool_py.prototxt内容如下

layer {
  name: "data"
  type: "DummyData"
  top: "data"
  dummy_data_param {
    data_filler {
      type: "gaussian"
    }
    shape {
      dim: 20
      dim: 1
      dim: 64
      dim: 64
    }
  }
}
layer {
  name: "label"
  type: "DummyData"
  top: "label"
  dummy_data_param {
    data_filler {
      type: "gaussian"
    }
    shape {
      dim: 20
      dim: 10
      dim: 1
      dim: 1
    }
  }
}
layer {
  name: "Convolution1"
  type: "Convolution"
  bottom: "data"
  top: "Convolution1"
  convolution_param {
    num_output: 20
    pad: 0
    kernel_size: 4
    stride: 3
  }
}
layer {
  name: "ReLU1"
  type: "ReLU"
  bottom: "Convolution1"
  top: "Convolution1"
}
layer {
  name: "Pooling1"
  type: "Pooling"
  bottom: "Convolution1"
  top: "Pooling1"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "Convolution2"
  type: "Convolution"
  bottom: "Pooling1"
  top: "Convolution2"
  convolution_param {
    num_output: 10
    pad: 3
    kernel_size: 4
    stride: 2
  }
}
layer {
  name: "ReLU2"
  type: "ReLU"
  bottom: "Convolution2"
  top: "Convolution2"
}
layer {
  name: "Pooling2"
  type: "Pooling"
  bottom: "Convolution2"
  top: "Pooling2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "Pooling2"
  top: "conv1"
  convolution_param {
    num_output: 10
    pad: 3
    kernel_size: 4
    stride: 2
  }
}
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "conv1"
  top: "conv1"
}
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"
  top: "pool1"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "pool1"
  top: "ip2"
  inner_product_param {
    num_output: 10
    weight_filler {
      type: "xavier"
    }
  }
}
layer {
  name: "loss"
  type: "SigmoidCrossEntropyLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}

生成的 covn_solver.prototxt内容如下

train_net: "conv_pool_py.prototxt"
test_net: "conv_pool_py.prototxt"
test_iter: 100
test_interval: 500
base_lr: 0.0010000000475
display: 500
max_iter: 20000
lr_policy: "step"
gamma: 0.10000000149
momentum: 0.899999976158
weight_decay: 0.000500000023749
stepsize: 1000
snapshot: 10000
snapshot_prefix: "shapshot"
solver_mode: CPU
type: "SGD"

网络结构可视化a.jpg如下

结果输出

======data and diff output shape======
data out 	(20, 1, 64, 64)
data diff	(20, 1, 64, 64)
label out 	(20, 10, 1, 1)
label diff	(20, 10, 1, 1)
Convolution1 out 	(20, 20, 21, 21)
Convolution1 diff	(20, 20, 21, 21)
Pooling1 out 	(20, 20, 11, 11)
Pooling1 diff	(20, 20, 11, 11)
Convolution2 out 	(20, 10, 7, 7)
Convolution2 diff	(20, 10, 7, 7)
Pooling2 out 	(20, 10, 4, 4)
Pooling2 diff	(20, 10, 4, 4)
conv1 out 	(20, 10, 4, 4)
conv1 diff	(20, 10, 4, 4)
pool1 out 	(20, 10, 2, 2)
pool1 diff	(20, 10, 2, 2)
ip2 out 	(20, 10)
ip2 diff	(20, 10)
loss out 	()
loss diff	()
======   weight and bias shape  ======
Convolution1 weight	(20, 1, 4, 4) (20,)
Convolution1 diff  	(20, 1, 4, 4) (20,)
Convolution2 weight	(10, 20, 4, 4) (10,)
Convolution2 diff  	(10, 20, 4, 4) (10,)
conv1 weight	(10, 10, 4, 4) (10,)
conv1 diff  	(10, 10, 4, 4) (10,)
ip2 weight	(10, 40) (10,)
ip2 diff  	(10, 40) (10,)
  • 批归一层

批归一层的含义可以参考Tensorflow技术点整理 中的批归一化,一般在 proto 中构造如下

layer {
  name: "conv1_bn"
  type: "BatchNorm"
  bottom: "conv1"
  top: "conv1"
  batch_norm_param {  
        use_global_stats: true #训练时为false,测试时为true 
  }  
  #参数mean,variance是由输入数据计算直接计算得到的,moving_average_fraction是指定的,
  #因此都与学习率和衰减率无关,所以lr_mult和decay_mult都为0
  param {	#参数mean均值
    lr_mult: 0.0  #网络当前层的局部学习率.当前层学习率=base_lr*lr_mult;base_lr为网络基础学习率
    decay_mult: 0 #网络当前层的局部衰减率	
  }
  param {	#参数variance方差
    lr_mult: 0.0
  }
  param {	#参数moving_average_fraction滑动系数,默认0.999
    lr_mult: 0.0
  }
}
  • Scale层

Scale是将BatchNorm得到的数据做线性变换,一般在 proto 中构造如下

layer {
  name: "conv1_scale"
  type: "Scale"
  bottom: "conv1"
  top: "conv1"
  param {
    lr_mult: 1.0
    decay_mult: 0
  }
  param {
    lr_mult: 1.0
    decay_mult: 0
  }
  scale_param {
    bias_term: true
  }
}
  • Dropout层

Dropout是一个防止过拟合的层,具体可以参考Tensorflow深度学习算法整理 两个全连接层上使用了 dropout 技术,一般在 proto 中构造如下

layer {
  name: "drop7"
  type: "Dropout"
  bottom: "conv2"
  top: "conv1"
  dropout_param {
    dropout_ratio: 0.5
  }
}

模型推理

我们这里将使用LeNet对手写数字的数据集进行训练,然后使用训练完成的模型参数进行推理。

LeNet由两个卷积层,两个池化层,外加三个全连接层组成,各层结构如下

层名称 输入大小 核大小 核个数 padding stride 输出大小
Conv1 (3,32,32) 5*5 6 0 1 (6,28,28)
Pool1 (6,28,28) 2*2 None 0 2 (6,14,14)
Conv2 (6,14,14) 5*5 16 0 1 (16,10,10)
Pool2 (16,10,10) 2*2 None 0 2 (16,5,5)
FC1 16*5*5 120 None None None 120
FC2 120 84 None None None 84
FC3 84 10 None None None 10

在Caffe的examples/mnist下已经提供了LeNet网络结构的proto文件lenet_train_test.prototxt

name: "LeNet"
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "examples/mnist/mnist_train_lmdb"
    batch_size: 64
    backend: LMDB
  }
}
layer {
  name: "mnist"
  type: "Data"
  top: "data"
  top: "label"
  include {
    phase: TEST
  }
  transform_param {
    scale: 0.00390625
  }
  data_param {
    source: "examples/mnist/mnist_test_lmdb"
    batch_size: 100
    backend: LMDB
  }
}
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 20 #这里跟原始结构不同,输出通道数为20,而不是6
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"
  top: "pool1"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 50 #这里跟原始结构不同,输出通道数为50,而不是16
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "ip1"
  type: "InnerProduct"
  bottom: "pool2"
  top: "ip1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 500 #这里的输出向量维度与原始结构不同,为500,而不是120
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "ip1"
}
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 10 #这里只有两层全连接层,故直接输出最终分类个数10,原始结构中有三层全连接层
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "accuracy"
  type: "Accuracy"
  bottom: "ip2"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "ip2"
  bottom: "label"
  top: "loss"
}

它的模型训练参数proto文件lenet_solver.prototxt如下

# The train/test net protocol buffer definition
net: "examples/mnist/lenet_train_test.prototxt"
# test_iter specifies how many forward passes the test should carry out.
# In the case of MNIST, we have test batch size 100 and 100 test iterations,
# covering the full 10,000 testing images.
test_iter: 100
# Carry out testing every 500 training iterations.
test_interval: 500
# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# The learning rate policy
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# Display every 100 iterations
display: 100
# The maximum number of iterations
max_iter: 10000
# snapshot intermediate results
snapshot: 5000
snapshot_prefix: "examples/mnist/lenet"
# solver mode: CPU or GPU
solver_mode: CPU #这里原始的为GPU,由于我们只安装了CPU版本的Caffe,故这里改成CPU

回到Caffe主目录,执行

examples/mnist/train_lenet.sh

就可以开始训练了。训练完成后的验证精度为

I1115 10:26:03.350658  4281 solver.cpp:414]     Test net output #0: accuracy = 0.9912
I1115 10:26:03.350670  4281 solver.cpp:414]     Test net output #1: loss = 0.0284735 (* 1 = 0.0284735 loss)

训练完成后会在examples/mnist文件夹下产生这样两个文件——lenet_iter_5000.caffemodel和lenet_iter_10000.caffemodel,它们表示训练5000次得到的模型参数以及训练10000次得到的模型参数,现在我们使用lenet_iter_10000.caffemodel来进行模型推理。

在examples/mnist有一个lenet.prototxt的文件,做出稍微修改后内容如下

name: "LeNet"
layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 1 dim: 1 dim: 28 dim: 28 } } #这里的batchsize原本为64,因为我们只对一张图片进行推理,故改成1
}
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 20
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool1"
  type: "Pooling"
  bottom: "conv1"
  top: "pool1"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "pool1"
  top: "conv2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  convolution_param {
    num_output: 50
    kernel_size: 5
    stride: 1
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "pool2"
  type: "Pooling"
  bottom: "conv2"
  top: "pool2"
  pooling_param {
    pool: MAX
    kernel_size: 2
    stride: 2
  }
}
layer {
  name: "ip1"
  type: "InnerProduct"
  bottom: "pool2"
  top: "ip1"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 500
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "ip1"
  top: "ip1"
}
layer {
  name: "ip2"
  type: "InnerProduct"
  bottom: "ip1"
  top: "ip2"
  param {
    lr_mult: 1
  }
  param {
    lr_mult: 2
  }
  inner_product_param {
    num_output: 10
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {
  name: "prob"
  type: "Softmax"
  bottom: "ip2"
  top: "prob"
}
  • Python推理
# -*- coding: UTF-8 -*-

import numpy as np
import cv2
import caffe

if __name__ == '__main__':

    net_file = '/home/dell/下载/yolov5_caffe-master/examples/mnist/lenet.prototxt'
    caffe_model = '/home/dell/下载/yolov5_caffe-master/examples/mnist/lenet_iter_10000.caffemodel'
    # 创建caffe模型,并读取模型训练参数
    net = caffe.Net(net_file, caffe_model, caffe.TEST)

    img = cv2.imread("/home/dell/下载/yolov5_caffe-master/examples/images/d9c05d3a57bfe986ec37b1566591301c.png", cv2.IMREAD_GRAYSCALE)
    img = cv2.resize(img, (28, 28))
    img_data = img.reshape((1, 1, 28, 28))
    net.blobs['data'].data[...] = img_data
    # 执行推理
    out = net.forward()
    print out
    print np.argmax(out['prob'])

这里的推理图片 d9c05d3a57bfe986ec37b1566591301c.png

运行结果

{'prob': array([[0., 0., 0., 0., 0., 0., 1., 0., 0., 0.]], dtype=float32)}
6
  • C++推理

在examples/cpp_classification有一个classification.cpp的文件,我们将其拷贝到项目文件夹下,我这里为/home/dell/文档/caffelenet,其内容如下

#include <caffe/caffe.hpp>
#ifdef USE_OPENCV
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#endif  // USE_OPENCV
#include <algorithm>
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>
 
#ifdef USE_OPENCV
using namespace caffe;  // NOLINT(build/namespaces)
using std::string;
 
/* Pair (label, confidence) representing a prediction. */
typedef std::pair<string, float> Prediction;//记录每一个类的名称以及概率
 
//Classifier为构造函数,主要进行模型初始化,读入训练完毕的模型参数,均值文件和标签文件
class Classifier {
 public:
  Classifier(const string& model_file,//model_file为测试模型时记录网络结构的prototxt文件路径
             const string& trained_file,//trained_file为训练完毕的caffemodel文件路径
             const string& mean_file,//mean_file为记录数据集均值的文件路径,数据集均值的文件的格式通常为binaryproto
             const string& label_file);//label_file为记录类别标签的文件路径,标签通常记录在一个txt文件中,一行一个
 
  std::vector<Prediction> Classify(const cv::Mat& img, int N = 5);//Classify函数去进行网络前传,得到img属于各个类的概率
 
 private:
  void SetMean(const string& mean_file);//SetMean函数主要进行均值设定,每张检测图输入后会进行减去均值的操作,这个均值可以是模型使用的数据集图像的均值
 
  std::vector<float> Predict(const cv::Mat& img);//Predict函数是Classify函数的主要组成部分,将img送入网络进行前向传播,得到最后的类别
 
  void WrapInputLayer(std::vector<cv::Mat>* input_channels);//WrapInputLayer函数将img各通道(input_channels)放入网络的输入blob中
 
  void Preprocess(const cv::Mat& img,
                  std::vector<cv::Mat>* input_channels);//Preprocess函数将输入图像img按通道分开(input_channels)
 
 private:
  shared_ptr<Net<float> > net_;//net_表示caffe中的网络
  cv::Size input_geometry_;//input_geometry_表示了输入图像的高宽,同时也是网络数据层中单通道图像的高宽
  int num_channels_;//num_channels_表示了输入图像的通道数
  cv::Mat mean_;//mean_表示了数据集的均值,格式为Mat
  std::vector<string> labels_;//字符串向量labels_表示了各个标签
};
 
//构造函数Classifier进行了各种各样的初始化工作,并对网络的安全进行了检验
Classifier::Classifier(const string& model_file,
                       const string& trained_file,
                       const string& mean_file,
                       const string& label_file) {
#ifdef CPU_ONLY
  Caffe::set_mode(Caffe::CPU);//如果caffe是只在cpu上运行的,将运行模式设置为CPU
#else
  Caffe::set_mode(Caffe::GPU);//一般我们都是用的GPU模式
#endif
 
  /* Load the network. */
  net_.reset(new Net<float>(model_file, TEST));//从model_file路径下的prototxt初始化网络结构
  net_->CopyTrainedLayersFrom(trained_file);//从trained_file路径下的caffemodel文件读入训练完毕的网络参数
 
  CHECK_EQ(net_->num_inputs(), 1) << "Network should have exactly one input.";//核验是不是只输入了一张图像,输入的blob结构为(N,C,H,W),在这里,N只能为1
  CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output.";//核验输出的blob结构,输出的blob结构同样为(N,C,W,H),在这里,N同样只能为1
 
  Blob<float>* input_layer = net_->input_blobs()[0];//获取网络输入的blob,表示网络的数据层
  num_channels_ = input_layer->channels();//获取输入的通道数
  CHECK(num_channels_ == 3 || num_channels_ == 1)//核验输入图像的通道数是否为3或者1,网络只接收3通道或1通道的图片
    << "Input layer should have 1 or 3 channels.";
  input_geometry_ = cv::Size(input_layer->width(), input_layer->height());//获取输入图像的尺寸(宽与高)
 
  /* Load the binaryproto mean file. */
  SetMean(mean_file);//进行均值的设置
 
  /* Load labels. */
  std::ifstream labels(label_file.c_str());//从标签文件路径读入定义的标签文件
  CHECK(labels) << "Unable to open labels file " << label_file;
  string line;//line获取标签文件中的每一行(每一个标签)
  while (std::getline(labels, line))
    labels_.push_back(string(line));//将所有的标签放入labels_
 
  /*output_layer指向网络最后的输出,举个例子,最后的分类器采用softmax分类,且类别有10类,那么,输出的blob就会有10个通道,每个通道的长
  宽都为1(因为是10个数,这10个数表征输入属于10类中每一类的概率,这10个数之和应该为1),输出blob的结构为(1,10,1,1)*/
  Blob<float>* output_layer = net_->output_blobs()[0];
  CHECK_EQ(labels_.size(), output_layer->channels())//在这里核验最后网络输出的通道数是否等于定义的标签的通道数
    << "Number of labels is different from the output layer dimension.";
}
 
static bool PairCompare(const std::pair<float, int>& lhs,
                        const std::pair<float, int>& rhs) {
  return lhs.first > rhs.first;
}//PairCompare函数比较分类得到的物体属于某两个类别的概率的大小,若属于lhs的概率大于属于rhs的概率,返回真,否则返回假
 
/* Return the indices of the top N values of vector v. */
/*Argmax函数返回前N个得分概率的类标*/
static std::vector<int> Argmax(const std::vector<float>& v, int N) {
  std::vector<std::pair<float, int> > pairs;
  for (size_t i = 0; i < v.size(); ++i)
    pairs.push_back(std::make_pair(v[i], i));//按照分类结果存储输入属于每一个类的概率以及类标
  std::partial_sort(pairs.begin(), pairs.begin() + N, pairs.end(), PairCompare);/*partial_sort函数按照概率大
  小筛选出pairs中概率最大的N个组合,并将它们按照概率从大到小放在pairs的前N个位置*/
 
  std::vector<int> result;
  for (int i = 0; i < N; ++i)
    result.push_back(pairs[i].second);//将前N个较大的概率对应的类标放在result中
  return result;
}
 
/* Return the top N predictions. */
std::vector<Prediction> Classifier::Classify(const cv::Mat& img, int N) {
  std::vector<float> output = Predict(img);//进行网络的前向传输,得到输入属于每一类的概率,存储在output中
 
  N = std::min<int>(labels_.size(), N);//找到想要得到的概率较大的前N类,这个N应该小于等于总的类别数目
  std::vector<int> maxN = Argmax(output, N);//找到概率最大的前N类,将他们按概率由大到小将类标存储在maxN中
  std::vector<Prediction> predictions;
  for (int i = 0; i < N; ++i) {
    int idx = maxN[i];
    predictions.push_back(std::make_pair(labels_[idx], output[idx]));//在labels_找到分类得到的概率最大的N类对应的实际的名称
  }
 
  return predictions;
}
 
/* Load the mean file in binaryproto format. */
void Classifier::SetMean(const string& mean_file) {//设置数据集的平均值
  BlobProto blob_proto;
  ReadProtoFromBinaryFileOrDie(mean_file.c_str(), &blob_proto);//用定义的均值文件路径将均值文件读入proto中
 
  /* Convert from BlobProto to Blob<float> */
  Blob<float> mean_blob;
  mean_blob.FromProto(blob_proto);//将proto中存储的均值文件转移到blob中
  CHECK_EQ(mean_blob.channels(), num_channels_)//核验均值的通道数是否等于输入图像的通道数,如果不相等的话则为异常
    << "Number of channels of mean file doesn't match input layer.";
 
  /* The format of the mean file is planar 32-bit float BGR or grayscale. */
  std::vector<cv::Mat> channels;//将mean_blob中的数据转化为Mat时的存储向量
  float* data = mean_blob.mutable_cpu_data();//指向均值blob的指针
  for (int i = 0; i < num_channels_; ++i) {
    /* Extract an individual channel. */
    cv::Mat channel(mean_blob.height(), mean_blob.width(), CV_32FC1, data);//存储均值文件的每一个通道转化得到的Mat
    channels.push_back(channel);//将均值文件的所有通道转化成的Mat一个一个地存储到channels中
    data += mean_blob.height() * mean_blob.width();//在均值文件上移动一个通道
  }
 
  /* Merge the separate channels into a single image. */
  cv::Mat mean;
  cv::merge(channels, mean);//将得到的所有通道合成为一张图
 
  /* Compute the global mean pixel value and create a mean image
   * filled with this value. */
  cv::Scalar channel_mean = cv::mean(mean);//求得均值文件的每个通道的平均值,记录在channel_mean中
  mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean);//用上面求得的各个通道的平均值初始化mean_,作为数据集图像的均值
}
 
std::vector<float> Classifier::Predict(const cv::Mat& img) {
  Blob<float>* input_layer = net_->input_blobs()[0];//input_layer是网络的输入blob
  input_layer->Reshape(1, num_channels_,
                       input_geometry_.height, input_geometry_.width);//表示网络只输入一张图像,图像的通道数是num_channels_,高为input_geometry_.height,宽为input_geometry_.width
  /* Forward dimension change to all layers. */
  net_->Reshape();//初始化网络的各层
 
  std::vector<cv::Mat> input_channels;//存储输入图像的各个通道
  WrapInputLayer(&input_channels);//将存储输入图像的各个通道的input_channels放入网络的输入blob中
  Preprocess(img, &input_channels);//将img的各通道分开并存储在input_channels中
 
  net_->Forward();//进行网络的前向传输
 
  /* Copy the output layer to a std::vector */
  Blob<float>* output_layer = net_->output_blobs()[0];//output_layer指向网络输出的数据,存储网络输出数据的blob的规格是(1,c,1,1)
  const float* begin = output_layer->cpu_data();//begin指向输入数据对应的第一类的概率
  const float* end = begin + output_layer->channels();//end指向输入数据对应的最后一类的概率
  return std::vector<float>(begin, end);//返回输入数据经过网络前向计算后输出的对应于各个类的分数
}
 
/* Wrap the input layer of the network in separate cv::Mat objects
 * (one per channel). This way we save one memcpy operation and we
 * don't need to rely on cudaMemcpy2D. The last preprocessing
 * operation will write the separate channels directly to the input
 * layer. */
void Classifier::WrapInputLayer(std::vector<cv::Mat>* input_channels) {
  Blob<float>* input_layer = net_->input_blobs()[0];//input_layer指向网络输入的blob
 
  int width = input_layer->width();//得到网络指定的输入图像的宽
  int height = input_layer->height();//得到网络指定的输入图像的高
  float* input_data = input_layer->mutable_cpu_data();//input_data指向网络的输入blob
  for (int i = 0; i < input_layer->channels(); ++i) {
    cv::Mat channel(height, width, CV_32FC1, input_data);//将网络输入blob的数据同Mat关联起来
    input_channels->push_back(channel);//将上面的Mat同input_channels关联起来
    input_data += width * height;//一个一个通道地操作
  }
}
 
void Classifier::Preprocess(const cv::Mat& img,
                            std::vector<cv::Mat>* input_channels) {
  /* Convert the input image to the input image format of the network. */
  cv::Mat sample;
  if (img.channels() == 3 && num_channels_ == 1)
    cv::cvtColor(img, sample, cv::COLOR_BGR2GRAY);
  else if (img.channels() == 4 && num_channels_ == 1)
    cv::cvtColor(img, sample, cv::COLOR_BGRA2GRAY);
  else if (img.channels() == 4 && num_channels_ == 3)
    cv::cvtColor(img, sample, cv::COLOR_BGRA2BGR);
  else if (img.channels() == 1 && num_channels_ == 3)
    cv::cvtColor(img, sample, cv::COLOR_GRAY2BGR);
  else
    sample = img;//if-else嵌套表示了要将输入的img转化为num_channels_通道的
 
  cv::Mat sample_resized;
  if (sample.size() != input_geometry_)
    cv::resize(sample, sample_resized, input_geometry_);//将输入图像的尺寸强制转化为网络规定的输入尺寸
  else
    sample_resized = sample;
 
  cv::Mat sample_float;
  if (num_channels_ == 3)
    sample_resized.convertTo(sample_float, CV_32FC3);
  else
    sample_resized.convertTo(sample_float, CV_32FC1);//将输入图像转化成为网络前传合法的数据规格
 
  cv::Mat sample_normalized;
  cv::subtract(sample_float, mean_, sample_normalized);//将图像减去均值
 
  /* This operation will write the separate BGR planes directly to the
   * input layer of the network because it is wrapped by the cv::Mat
   * objects in input_channels. */
  cv::split(sample_normalized, *input_channels);/*将减去均值的图像分散在input_channels中,由于在WrapInputLayer函数中,
  input_channels已经和网络的输入blob关联起来了,因此在这里实际上是把图像送入了网络的输入blob*/
 
  CHECK(reinterpret_cast<float*>(input_channels->at(0).data)
        == net_->input_blobs()[0]->cpu_data())
    << "Input channels are not wrapping the input layer of the network.";//核验图像是否被送入了网络作为输入
}
 
int main(int argc, char** argv) {//主函数
  if (argc != 6) {/*核验命令行参数是否为6,这6个参数分别为classification编译生成的可执行文件,测试模型时记录网络结构的prototxt文件路径,
  训练完毕的caffemodel文件路径,记录数据集均值的文件路径,记录类别标签的文件路径,需要送入网络进行分类的图片文件路径*/
    std::cerr << "Usage: " << argv[0]
              << " deploy.prototxt network.caffemodel"
              << " mean.binaryproto labels.txt img.jpg" << std::endl;
    return 1;
  }
 
  ::google::InitGoogleLogging(argv[0]);//InitGoogleLogging做了一些初始化glog的工作
 
  //取四个参数
  string model_file   = argv[1];
  string trained_file = argv[2];
  string mean_file    = argv[3];
  string label_file   = argv[4];
  Classifier classifier(model_file, trained_file, mean_file, label_file);//进行检测网络的初始化
 
  string file = argv[5];//取得需要进行检测的图片的路径
 
  std::cout << "---------- Prediction for "
            << file << " ----------" << std::endl;
 
  cv::Mat img = cv::imread(file, -1);//读入图片
  CHECK(!img.empty()) << "Unable to decode image " << file;
  std::vector<Prediction> predictions = classifier.Classify(img);//进行网络的前向计算,并且取到概率最大的前N类对应的类别名称
 
  /* Print the top N predictions. */
  for (size_t i = 0; i < predictions.size(); ++i) {//打印出概率最大的前N类并给出概率
    Prediction p = predictions[i];
    std::cout << std::fixed << std::setprecision(4) << p.second << " - \""
              << p.first << "\"" << std::endl;
  }
}
#else
int main(int argc, char** argv) {
  LOG(FATAL) << "This example requires OpenCV; compile with USE_OPENCV.";
}
#endif  // USE_OPENCV

通过以上代码,我们发现,除了模型proto文件、训练模型参数文件、数据集标签以及单张图片外还需要一个均值文件。

在主文件夹执行

build/tools/compute_image_mean examples/mnist/mnist_train_lmdb/ examples/mnist/mean.binaryproto

就可以生成手写数据集的均值文件mean.binaryproto。

在examples/mnist文件夹下编写label.txt,内容如下

0
1
2
3
4
5
6
7
8
9

我们将classification.cpp修改如下

#include <caffe/caffe.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <algorithm>
#include <iosfwd>
#include <memory>
#include <string>
#include <utility>
#include <vector>

using namespace caffe;  // NOLINT(build/namespaces)
using std::string;

/* Pair (label, confidence) representing a prediction. */
typedef std::pair<string, float> Prediction;

class Classifier {
 public:
  Classifier(const string& model_file,
             const string& trained_file,
             const string& mean_file,
             const string& label_file);

  std::vector<Prediction> Classify(const cv::Mat& img, int N = 5);

 private:
  void SetMean(const string& mean_file);

  std::vector<float> Predict(const cv::Mat& img);

  void WrapInputLayer(std::vector<cv::Mat>* input_channels);

  void Preprocess(const cv::Mat& img,
                  std::vector<cv::Mat>* input_channels);

 private:
  shared_ptr<Net<float> > net_;
  cv::Size input_geometry_;
  int num_channels_;
  cv::Mat mean_;
  std::vector<string> labels_;
};

Classifier::Classifier(const string& model_file,
                       const string& trained_file,
                       const string& mean_file,
                       const string& label_file) {
  Caffe::set_mode(Caffe::CPU);

  /* Load the network. */
  net_.reset(new Net<float>(model_file, TEST));
  net_->CopyTrainedLayersFrom(trained_file);

  CHECK_EQ(net_->num_inputs(), 1) << "Network should have exactly one input.";
  CHECK_EQ(net_->num_outputs(), 1) << "Network should have exactly one output.";

  Blob<float>* input_layer = net_->input_blobs()[0];
  num_channels_ = input_layer->channels();
  CHECK(num_channels_ == 3 || num_channels_ == 1)
    << "Input layer should have 1 or 3 channels.";
  input_geometry_ = cv::Size(input_layer->width(), input_layer->height());

  /* Load the binaryproto mean file. */
  SetMean(mean_file);

  /* Load labels. */
  std::ifstream labels(label_file.c_str());
  CHECK(labels) << "Unable to open labels file " << label_file;
  string line;
  while (std::getline(labels, line))
    labels_.push_back(string(line));

  Blob<float>* output_layer = net_->output_blobs()[0];
  CHECK_EQ(labels_.size(), output_layer->channels())
    << "Number of labels is different from the output layer dimension.";
}

static bool PairCompare(const std::pair<float, int>& lhs,
                        const std::pair<float, int>& rhs) {
  return lhs.first > rhs.first;
}

/* Return the indices of the top N values of vector v. */
static std::vector<int> Argmax(const std::vector<float>& v, int N) {
  std::vector<std::pair<float, int> > pairs;
  for (size_t i = 0; i < v.size(); ++i)
    pairs.push_back(std::make_pair(v[i], i));
  std::partial_sort(pairs.begin(), pairs.begin() + N, pairs.end(), PairCompare);

  std::vector<int> result;
  for (int i = 0; i < N; ++i)
    result.push_back(pairs[i].second);
  return result;
}

/* Return the top N predictions. */
std::vector<Prediction> Classifier::Classify(const cv::Mat& img, int N) {
  std::vector<float> output = Predict(img);

  N = std::min<int>(labels_.size(), N);
  std::vector<int> maxN = Argmax(output, N);
  std::vector<Prediction> predictions;
  for (int i = 0; i < N; ++i) {
    int idx = maxN[i];
    predictions.push_back(std::make_pair(labels_[idx], output[idx]));
  }

  return predictions;
}

/* Load the mean file in binaryproto format. */
void Classifier::SetMean(const string& mean_file) {
  BlobProto blob_proto;
  ReadProtoFromBinaryFileOrDie(mean_file.c_str(), &blob_proto);

  /* Convert from BlobProto to Blob<float> */
  Blob<float> mean_blob;
  mean_blob.FromProto(blob_proto);
  CHECK_EQ(mean_blob.channels(), num_channels_)
    << "Number of channels of mean file doesn't match input layer.";

  /* The format of the mean file is planar 32-bit float BGR or grayscale. */
  std::vector<cv::Mat> channels;
  float* data = mean_blob.mutable_cpu_data();
  for (int i = 0; i < num_channels_; ++i) {
    /* Extract an individual channel. */
    cv::Mat channel(mean_blob.height(), mean_blob.width(), CV_32FC1, data);
    channels.push_back(channel);
    data += mean_blob.height() * mean_blob.width();
  }

  /* Merge the separate channels into a single image. */
  cv::Mat mean;
  cv::merge(channels, mean);

  /* Compute the global mean pixel value and create a mean image
   * filled with this value. */
  cv::Scalar channel_mean = cv::mean(mean);
  mean_ = cv::Mat(input_geometry_, mean.type(), channel_mean);
}

std::vector<float> Classifier::Predict(const cv::Mat& img) {
  Blob<float>* input_layer = net_->input_blobs()[0];
  input_layer->Reshape(1, num_channels_,
                       input_geometry_.height, input_geometry_.width);
  /* Forward dimension change to all layers. */
  net_->Reshape();

  std::vector<cv::Mat> input_channels;
  WrapInputLayer(&input_channels);

  Preprocess(img, &input_channels);

  net_->Forward();

  /* Copy the output layer to a std::vector */
  Blob<float>* output_layer = net_->output_blobs()[0];
  const float* begin = output_layer->cpu_data();
  const float* end = begin + output_layer->channels();
  return std::vector<float>(begin, end);
}

/* Wrap the input layer of the network in separate cv::Mat objects
 * (one per channel). This way we save one memcpy operation and we
 * don't need to rely on cudaMemcpy2D. The last preprocessing
 * operation will write the separate channels directly to the input
 * layer. */
void Classifier::WrapInputLayer(std::vector<cv::Mat>* input_channels) {
  Blob<float>* input_layer = net_->input_blobs()[0];

  int width = input_layer->width();
  int height = input_layer->height();
  float* input_data = input_layer->mutable_cpu_data();
  for (int i = 0; i < input_layer->channels(); ++i) {
    cv::Mat channel(height, width, CV_32FC1, input_data);
    input_channels->push_back(channel);
    input_data += width * height;
  }
}

void Classifier::Preprocess(const cv::Mat& img,
                            std::vector<cv::Mat>* input_channels) {
  /* Convert the input image to the input image format of the network. */
  cv::Mat sample;
  if (img.channels() == 3 && num_channels_ == 1)
    cv::cvtColor(img, sample, cv::COLOR_BGR2GRAY);
  else if (img.channels() == 4 && num_channels_ == 1)
    cv::cvtColor(img, sample, cv::COLOR_BGRA2GRAY);
  else if (img.channels() == 4 && num_channels_ == 3)
    cv::cvtColor(img, sample, cv::COLOR_BGRA2BGR);
  else if (img.channels() == 1 && num_channels_ == 3)
    cv::cvtColor(img, sample, cv::COLOR_GRAY2BGR);
  else
    sample = img;

  cv::Mat sample_resized;
  if (sample.size() != input_geometry_)
    cv::resize(sample, sample_resized, input_geometry_);
  else
    sample_resized = sample;

  cv::Mat sample_float;
  if (num_channels_ == 3)
    sample_resized.convertTo(sample_float, CV_32FC3);
  else
    sample_resized.convertTo(sample_float, CV_32FC1);

  cv::Mat sample_normalized;
  cv::subtract(sample_float, mean_, sample_normalized);

  /* This operation will write the separate BGR planes directly to the
   * input layer of the network because it is wrapped by the cv::Mat
   * objects in input_channels. */
  cv::split(sample_normalized, *input_channels);

  CHECK(reinterpret_cast<float*>(input_channels->at(0).data)
        == net_->input_blobs()[0]->cpu_data())
    << "Input channels are not wrapping the input layer of the network.";
}

int main(int argc, char** argv) {
  /*if (argc != 6) {
    std::cerr << "Usage: " << argv[0]
              << " deploy.prototxt network.caffemodel"
              << " mean.binaryproto labels.txt img.jpg" << std::endl;
    return 1;
  }*/

  ::google::InitGoogleLogging(argv[0]);

  string model_file   = "/home/dell/下载/yolov5_caffe-master/examples/mnist/lenet.prototxt";
  string trained_file = "/home/dell/下载/yolov5_caffe-master/examples/mnist/lenet_iter_10000.caffemodel";
  string mean_file    = "/home/dell/下载/yolov5_caffe-master/examples/mnist/mean.binaryproto";
  string label_file   = "/home/dell/下载/yolov5_caffe-master/examples/mnist/label.txt";
  Classifier classifier(model_file, trained_file, mean_file, label_file);

  string file = "/home/dell/下载/yolov5_caffe-master/examples/images/d9c05d3a57bfe986ec37b1566591301c.png";

  std::cout << "---------- Prediction for "
            << file << " ----------" << std::endl;

  cv::Mat img = cv::imread(file, -1);
  CHECK(!img.empty()) << "Unable to decode image " << file;
  std::vector<Prediction> predictions = classifier.Classify(img);

  /* Print the top N predictions. */
  for (size_t i = 0; i < predictions.size(); ++i) {
    Prediction p = predictions[i];
    std::cout << std::fixed << std::setprecision(4) << p.second << " - \""
              << p.first << "\"" << std::endl;
  }
}

Makefile

EXE=classification

PKGS=opencv4
INCLUDE=/home/dell/下载/yolov5_caffe-master/include/
LIBPATH=/home/dell/下载/yolov5_caffe-master/build/lib/
CFLAGS= -I$(INCLUDE)
CFLAGS+= `pkg-config --cflags $(PKGS)`
CFLAGS+= -I/usr/local/cuda/include -I/home/dell/下载/yolov5_caffe-master/src
LIBS= -lcaffe -lglog -lboost_system
LIBS+= -L$(LIBPATH)
LIBS+= `pkg-config --libs $(PKGS)`

CXX_OBJECTS := $(patsubst %.cpp,%.o,$(shell find . -name "*.cpp"))
DEP_FILES  =$(patsubst  %.o,  %.d, $(CXX_OBJECTS))

$(EXE): $(CXX_OBJECTS)
	$(CXX)  $(CXX_OBJECTS) -o $(EXE) $(LIBS)

%.o: %.cpp
	$(CXX) -c -o $@ $(CFLAGS) $(LIBS) $<

clean: 
	rm  -rf  $(CXX_OBJECTS)  $(DEP_FILES)  $(EXE)

/etc/ld.so.conf.d文件夹下添加一个caffe.conf文件,内容为libcaffe.so文件的路经,我这里为

/home/dell/下载/yolov5_caffe-master/build/lib

执行

sudo ldconfig

在项目文件夹下使用make编译后,执行结果为

---------- Prediction for /home/dell/下载/caffe-master/examples/images/d9c05d3a57bfe986ec37b1566591301c.png ----------
1.0000 - "6"
0.0000 - "1"
0.0000 - "3"
0.0000 - "4"
0.0000 - "0"

Pytorch模型转Caffe

下载转换工具,下载地址:https://github.com/xxradon/PytorchToCaffe

现在我们将之前使用Pytorch自己训练过的ResNet50的模型参数文件转化成Caffe的网络结构Proto以及.caffemodel文件。具体的训练过程可以参考PyTorch技术点整理 中的使用 ResNet50 训练自己的数据集

我们将Pytorch的模型参数.pt文件以及PytorchToCaffe主目录下的Caffe文件夹和pytorch_to_caffe.py拷贝到项目文件夹中,我这里为/home/dell/文档/py2caffe。

在Pytorch框架内的torchvison.models.resnet.py进行修改,在class ResNet(nn.Module):中的def _forward_impl(self, x: Tensor) -> Tensor:方法,修改如下

def _forward_impl(self, x: Tensor) -> Tensor:
    # See note [TorchScript super()]
    x = self.conv1(x)
    x = self.bn1(x)
    x = self.relu(x)
    x = self.maxpool(x)

    x = self.layer1(x)
    x = self.layer2(x)
    x = self.layer3(x)
    x = self.layer4(x)

    x = self.avgpool(x)
    # x = torch.flatten(x, 1)
    x = x.view(x.size(0), -1)
    x = self.fc(x)

    return x

模型转换(注意,这里的执行环境为Conda3.X的Pytorch环境,而不是Conda2.7的Caffe环境)

import torch
import pytorch_to_caffe
from torch.autograd import Variable

if __name__ == '__main__':

    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    name = 'resnet50'
    model = torch.load('_model_37.pt')
    model.eval()

    input = Variable(torch.ones([1, 3, 224, 224]))
    pytorch_to_caffe.trans_net(model, input.to(device), name)
    pytorch_to_caffe.save_prototxt('{}.prototxt'.format(name))
    pytorch_to_caffe.save_caffemodel('{}.caffemodel'.format(name))

执行完成后,可以生成resnet50.prototxt和resnet50.caffemodel两个文件,将resnet50.prototxt稍作修改,内容如下

name: "resnet50"
layer {
  name: "data"
  type: "Input"
  top: "data"
  input_param { shape: { dim: 1 dim: 3 dim: 224 dim: 224 } }
}
layer {
  name: "conv1"
  type: "Convolution"
  bottom: "data"
  top: "conv_blob1"
  convolution_param {
    num_output: 64
    bias_term: false
    pad: 3
    kernel_size: 7
    group: 1
    stride: 2
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm1"
  type: "BatchNorm"
  bottom: "conv_blob1"
  top: "batch_norm_blob1"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale1"
  type: "Scale"
  bottom: "batch_norm_blob1"
  top: "batch_norm_blob1"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu1"
  type: "ReLU"
  bottom: "batch_norm_blob1"
  top: "relu_blob1"
}
layer {
  name: "max_pool1"
  type: "Pooling"
  bottom: "relu_blob1"
  top: "max_pool_blob1"
  pooling_param {
    pool: MAX
    kernel_size: 3
    stride: 2
    pad: 1
    # ceil_mode: false pooling没有该属性,注释掉
  }
}
layer {
  name: "conv2"
  type: "Convolution"
  bottom: "max_pool_blob1"
  top: "conv_blob2"
  convolution_param {
    num_output: 64
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm2"
  type: "BatchNorm"
  bottom: "conv_blob2"
  top: "batch_norm_blob2"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale2"
  type: "Scale"
  bottom: "batch_norm_blob2"
  top: "batch_norm_blob2"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu2"
  type: "ReLU"
  bottom: "batch_norm_blob2"
  top: "relu_blob2"
}
layer {
  name: "conv3"
  type: "Convolution"
  bottom: "relu_blob2"
  top: "conv_blob3"
  convolution_param {
    num_output: 64
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm3"
  type: "BatchNorm"
  bottom: "conv_blob3"
  top: "batch_norm_blob3"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale3"
  type: "Scale"
  bottom: "batch_norm_blob3"
  top: "batch_norm_blob3"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu3"
  type: "ReLU"
  bottom: "batch_norm_blob3"
  top: "relu_blob3"
}
layer {
  name: "conv4"
  type: "Convolution"
  bottom: "relu_blob3"
  top: "conv_blob4"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm4"
  type: "BatchNorm"
  bottom: "conv_blob4"
  top: "batch_norm_blob4"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale4"
  type: "Scale"
  bottom: "batch_norm_blob4"
  top: "batch_norm_blob4"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "conv5"
  type: "Convolution"
  bottom: "max_pool_blob1"
  top: "conv_blob5"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm5"
  type: "BatchNorm"
  bottom: "conv_blob5"
  top: "batch_norm_blob5"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale5"
  type: "Scale"
  bottom: "batch_norm_blob5"
  top: "batch_norm_blob5"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add1"
  type: "Eltwise"
  bottom: "batch_norm_blob4"
  bottom: "batch_norm_blob5"
  top: "add_blob1"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu4"
  type: "ReLU"
  bottom: "add_blob1"
  top: "relu_blob4"
}
layer {
  name: "conv6"
  type: "Convolution"
  bottom: "relu_blob4"
  top: "conv_blob6"
  convolution_param {
    num_output: 64
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm6"
  type: "BatchNorm"
  bottom: "conv_blob6"
  top: "batch_norm_blob6"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale6"
  type: "Scale"
  bottom: "batch_norm_blob6"
  top: "batch_norm_blob6"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu5"
  type: "ReLU"
  bottom: "batch_norm_blob6"
  top: "relu_blob5"
}
layer {
  name: "conv7"
  type: "Convolution"
  bottom: "relu_blob5"
  top: "conv_blob7"
  convolution_param {
    num_output: 64
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm7"
  type: "BatchNorm"
  bottom: "conv_blob7"
  top: "batch_norm_blob7"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale7"
  type: "Scale"
  bottom: "batch_norm_blob7"
  top: "batch_norm_blob7"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu6"
  type: "ReLU"
  bottom: "batch_norm_blob7"
  top: "relu_blob6"
}
layer {
  name: "conv8"
  type: "Convolution"
  bottom: "relu_blob6"
  top: "conv_blob8"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm8"
  type: "BatchNorm"
  bottom: "conv_blob8"
  top: "batch_norm_blob8"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale8"
  type: "Scale"
  bottom: "batch_norm_blob8"
  top: "batch_norm_blob8"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add2"
  type: "Eltwise"
  bottom: "batch_norm_blob8"
  bottom: "relu_blob4"
  top: "add_blob2"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu7"
  type: "ReLU"
  bottom: "add_blob2"
  top: "relu_blob7"
}
layer {
  name: "conv9"
  type: "Convolution"
  bottom: "relu_blob7"
  top: "conv_blob9"
  convolution_param {
    num_output: 64
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm9"
  type: "BatchNorm"
  bottom: "conv_blob9"
  top: "batch_norm_blob9"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale9"
  type: "Scale"
  bottom: "batch_norm_blob9"
  top: "batch_norm_blob9"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu8"
  type: "ReLU"
  bottom: "batch_norm_blob9"
  top: "relu_blob8"
}
layer {
  name: "conv10"
  type: "Convolution"
  bottom: "relu_blob8"
  top: "conv_blob10"
  convolution_param {
    num_output: 64
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm10"
  type: "BatchNorm"
  bottom: "conv_blob10"
  top: "batch_norm_blob10"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale10"
  type: "Scale"
  bottom: "batch_norm_blob10"
  top: "batch_norm_blob10"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu9"
  type: "ReLU"
  bottom: "batch_norm_blob10"
  top: "relu_blob9"
}
layer {
  name: "conv11"
  type: "Convolution"
  bottom: "relu_blob9"
  top: "conv_blob11"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm11"
  type: "BatchNorm"
  bottom: "conv_blob11"
  top: "batch_norm_blob11"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale11"
  type: "Scale"
  bottom: "batch_norm_blob11"
  top: "batch_norm_blob11"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add3"
  type: "Eltwise"
  bottom: "batch_norm_blob11"
  bottom: "relu_blob7"
  top: "add_blob3"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu10"
  type: "ReLU"
  bottom: "add_blob3"
  top: "relu_blob10"
}
layer {
  name: "conv12"
  type: "Convolution"
  bottom: "relu_blob10"
  top: "conv_blob12"
  convolution_param {
    num_output: 128
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm12"
  type: "BatchNorm"
  bottom: "conv_blob12"
  top: "batch_norm_blob12"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale12"
  type: "Scale"
  bottom: "batch_norm_blob12"
  top: "batch_norm_blob12"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu11"
  type: "ReLU"
  bottom: "batch_norm_blob12"
  top: "relu_blob11"
}
layer {
  name: "conv13"
  type: "Convolution"
  bottom: "relu_blob11"
  top: "conv_blob13"
  convolution_param {
    num_output: 128
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 2
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm13"
  type: "BatchNorm"
  bottom: "conv_blob13"
  top: "batch_norm_blob13"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale13"
  type: "Scale"
  bottom: "batch_norm_blob13"
  top: "batch_norm_blob13"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu12"
  type: "ReLU"
  bottom: "batch_norm_blob13"
  top: "relu_blob12"
}
layer {
  name: "conv14"
  type: "Convolution"
  bottom: "relu_blob12"
  top: "conv_blob14"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm14"
  type: "BatchNorm"
  bottom: "conv_blob14"
  top: "batch_norm_blob14"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale14"
  type: "Scale"
  bottom: "batch_norm_blob14"
  top: "batch_norm_blob14"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "conv15"
  type: "Convolution"
  bottom: "relu_blob10"
  top: "conv_blob15"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 2
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm15"
  type: "BatchNorm"
  bottom: "conv_blob15"
  top: "batch_norm_blob15"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale15"
  type: "Scale"
  bottom: "batch_norm_blob15"
  top: "batch_norm_blob15"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add4"
  type: "Eltwise"
  bottom: "batch_norm_blob14"
  bottom: "batch_norm_blob15"
  top: "add_blob4"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu13"
  type: "ReLU"
  bottom: "add_blob4"
  top: "relu_blob13"
}
layer {
  name: "conv16"
  type: "Convolution"
  bottom: "relu_blob13"
  top: "conv_blob16"
  convolution_param {
    num_output: 128
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm16"
  type: "BatchNorm"
  bottom: "conv_blob16"
  top: "batch_norm_blob16"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale16"
  type: "Scale"
  bottom: "batch_norm_blob16"
  top: "batch_norm_blob16"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu14"
  type: "ReLU"
  bottom: "batch_norm_blob16"
  top: "relu_blob14"
}
layer {
  name: "conv17"
  type: "Convolution"
  bottom: "relu_blob14"
  top: "conv_blob17"
  convolution_param {
    num_output: 128
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm17"
  type: "BatchNorm"
  bottom: "conv_blob17"
  top: "batch_norm_blob17"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale17"
  type: "Scale"
  bottom: "batch_norm_blob17"
  top: "batch_norm_blob17"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu15"
  type: "ReLU"
  bottom: "batch_norm_blob17"
  top: "relu_blob15"
}
layer {
  name: "conv18"
  type: "Convolution"
  bottom: "relu_blob15"
  top: "conv_blob18"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm18"
  type: "BatchNorm"
  bottom: "conv_blob18"
  top: "batch_norm_blob18"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale18"
  type: "Scale"
  bottom: "batch_norm_blob18"
  top: "batch_norm_blob18"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add5"
  type: "Eltwise"
  bottom: "batch_norm_blob18"
  bottom: "relu_blob13"
  top: "add_blob5"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu16"
  type: "ReLU"
  bottom: "add_blob5"
  top: "relu_blob16"
}
layer {
  name: "conv19"
  type: "Convolution"
  bottom: "relu_blob16"
  top: "conv_blob19"
  convolution_param {
    num_output: 128
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm19"
  type: "BatchNorm"
  bottom: "conv_blob19"
  top: "batch_norm_blob19"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale19"
  type: "Scale"
  bottom: "batch_norm_blob19"
  top: "batch_norm_blob19"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu17"
  type: "ReLU"
  bottom: "batch_norm_blob19"
  top: "relu_blob17"
}
layer {
  name: "conv20"
  type: "Convolution"
  bottom: "relu_blob17"
  top: "conv_blob20"
  convolution_param {
    num_output: 128
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm20"
  type: "BatchNorm"
  bottom: "conv_blob20"
  top: "batch_norm_blob20"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale20"
  type: "Scale"
  bottom: "batch_norm_blob20"
  top: "batch_norm_blob20"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu18"
  type: "ReLU"
  bottom: "batch_norm_blob20"
  top: "relu_blob18"
}
layer {
  name: "conv21"
  type: "Convolution"
  bottom: "relu_blob18"
  top: "conv_blob21"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm21"
  type: "BatchNorm"
  bottom: "conv_blob21"
  top: "batch_norm_blob21"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale21"
  type: "Scale"
  bottom: "batch_norm_blob21"
  top: "batch_norm_blob21"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add6"
  type: "Eltwise"
  bottom: "batch_norm_blob21"
  bottom: "relu_blob16"
  top: "add_blob6"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu19"
  type: "ReLU"
  bottom: "add_blob6"
  top: "relu_blob19"
}
layer {
  name: "conv22"
  type: "Convolution"
  bottom: "relu_blob19"
  top: "conv_blob22"
  convolution_param {
    num_output: 128
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm22"
  type: "BatchNorm"
  bottom: "conv_blob22"
  top: "batch_norm_blob22"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale22"
  type: "Scale"
  bottom: "batch_norm_blob22"
  top: "batch_norm_blob22"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu20"
  type: "ReLU"
  bottom: "batch_norm_blob22"
  top: "relu_blob20"
}
layer {
  name: "conv23"
  type: "Convolution"
  bottom: "relu_blob20"
  top: "conv_blob23"
  convolution_param {
    num_output: 128
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm23"
  type: "BatchNorm"
  bottom: "conv_blob23"
  top: "batch_norm_blob23"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale23"
  type: "Scale"
  bottom: "batch_norm_blob23"
  top: "batch_norm_blob23"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu21"
  type: "ReLU"
  bottom: "batch_norm_blob23"
  top: "relu_blob21"
}
layer {
  name: "conv24"
  type: "Convolution"
  bottom: "relu_blob21"
  top: "conv_blob24"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm24"
  type: "BatchNorm"
  bottom: "conv_blob24"
  top: "batch_norm_blob24"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale24"
  type: "Scale"
  bottom: "batch_norm_blob24"
  top: "batch_norm_blob24"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add7"
  type: "Eltwise"
  bottom: "batch_norm_blob24"
  bottom: "relu_blob19"
  top: "add_blob7"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu22"
  type: "ReLU"
  bottom: "add_blob7"
  top: "relu_blob22"
}
layer {
  name: "conv25"
  type: "Convolution"
  bottom: "relu_blob22"
  top: "conv_blob25"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm25"
  type: "BatchNorm"
  bottom: "conv_blob25"
  top: "batch_norm_blob25"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale25"
  type: "Scale"
  bottom: "batch_norm_blob25"
  top: "batch_norm_blob25"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu23"
  type: "ReLU"
  bottom: "batch_norm_blob25"
  top: "relu_blob23"
}
layer {
  name: "conv26"
  type: "Convolution"
  bottom: "relu_blob23"
  top: "conv_blob26"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 2
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm26"
  type: "BatchNorm"
  bottom: "conv_blob26"
  top: "batch_norm_blob26"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale26"
  type: "Scale"
  bottom: "batch_norm_blob26"
  top: "batch_norm_blob26"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu24"
  type: "ReLU"
  bottom: "batch_norm_blob26"
  top: "relu_blob24"
}
layer {
  name: "conv27"
  type: "Convolution"
  bottom: "relu_blob24"
  top: "conv_blob27"
  convolution_param {
    num_output: 1024
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm27"
  type: "BatchNorm"
  bottom: "conv_blob27"
  top: "batch_norm_blob27"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale27"
  type: "Scale"
  bottom: "batch_norm_blob27"
  top: "batch_norm_blob27"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "conv28"
  type: "Convolution"
  bottom: "relu_blob22"
  top: "conv_blob28"
  convolution_param {
    num_output: 1024
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 2
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm28"
  type: "BatchNorm"
  bottom: "conv_blob28"
  top: "batch_norm_blob28"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale28"
  type: "Scale"
  bottom: "batch_norm_blob28"
  top: "batch_norm_blob28"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add8"
  type: "Eltwise"
  bottom: "batch_norm_blob27"
  bottom: "batch_norm_blob28"
  top: "add_blob8"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu25"
  type: "ReLU"
  bottom: "add_blob8"
  top: "relu_blob25"
}
layer {
  name: "conv29"
  type: "Convolution"
  bottom: "relu_blob25"
  top: "conv_blob29"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm29"
  type: "BatchNorm"
  bottom: "conv_blob29"
  top: "batch_norm_blob29"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale29"
  type: "Scale"
  bottom: "batch_norm_blob29"
  top: "batch_norm_blob29"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu26"
  type: "ReLU"
  bottom: "batch_norm_blob29"
  top: "relu_blob26"
}
layer {
  name: "conv30"
  type: "Convolution"
  bottom: "relu_blob26"
  top: "conv_blob30"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm30"
  type: "BatchNorm"
  bottom: "conv_blob30"
  top: "batch_norm_blob30"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale30"
  type: "Scale"
  bottom: "batch_norm_blob30"
  top: "batch_norm_blob30"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu27"
  type: "ReLU"
  bottom: "batch_norm_blob30"
  top: "relu_blob27"
}
layer {
  name: "conv31"
  type: "Convolution"
  bottom: "relu_blob27"
  top: "conv_blob31"
  convolution_param {
    num_output: 1024
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm31"
  type: "BatchNorm"
  bottom: "conv_blob31"
  top: "batch_norm_blob31"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale31"
  type: "Scale"
  bottom: "batch_norm_blob31"
  top: "batch_norm_blob31"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add9"
  type: "Eltwise"
  bottom: "batch_norm_blob31"
  bottom: "relu_blob25"
  top: "add_blob9"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu28"
  type: "ReLU"
  bottom: "add_blob9"
  top: "relu_blob28"
}
layer {
  name: "conv32"
  type: "Convolution"
  bottom: "relu_blob28"
  top: "conv_blob32"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm32"
  type: "BatchNorm"
  bottom: "conv_blob32"
  top: "batch_norm_blob32"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale32"
  type: "Scale"
  bottom: "batch_norm_blob32"
  top: "batch_norm_blob32"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu29"
  type: "ReLU"
  bottom: "batch_norm_blob32"
  top: "relu_blob29"
}
layer {
  name: "conv33"
  type: "Convolution"
  bottom: "relu_blob29"
  top: "conv_blob33"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm33"
  type: "BatchNorm"
  bottom: "conv_blob33"
  top: "batch_norm_blob33"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale33"
  type: "Scale"
  bottom: "batch_norm_blob33"
  top: "batch_norm_blob33"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu30"
  type: "ReLU"
  bottom: "batch_norm_blob33"
  top: "relu_blob30"
}
layer {
  name: "conv34"
  type: "Convolution"
  bottom: "relu_blob30"
  top: "conv_blob34"
  convolution_param {
    num_output: 1024
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm34"
  type: "BatchNorm"
  bottom: "conv_blob34"
  top: "batch_norm_blob34"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale34"
  type: "Scale"
  bottom: "batch_norm_blob34"
  top: "batch_norm_blob34"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add10"
  type: "Eltwise"
  bottom: "batch_norm_blob34"
  bottom: "relu_blob28"
  top: "add_blob10"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu31"
  type: "ReLU"
  bottom: "add_blob10"
  top: "relu_blob31"
}
layer {
  name: "conv35"
  type: "Convolution"
  bottom: "relu_blob31"
  top: "conv_blob35"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm35"
  type: "BatchNorm"
  bottom: "conv_blob35"
  top: "batch_norm_blob35"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale35"
  type: "Scale"
  bottom: "batch_norm_blob35"
  top: "batch_norm_blob35"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu32"
  type: "ReLU"
  bottom: "batch_norm_blob35"
  top: "relu_blob32"
}
layer {
  name: "conv36"
  type: "Convolution"
  bottom: "relu_blob32"
  top: "conv_blob36"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm36"
  type: "BatchNorm"
  bottom: "conv_blob36"
  top: "batch_norm_blob36"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale36"
  type: "Scale"
  bottom: "batch_norm_blob36"
  top: "batch_norm_blob36"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu33"
  type: "ReLU"
  bottom: "batch_norm_blob36"
  top: "relu_blob33"
}
layer {
  name: "conv37"
  type: "Convolution"
  bottom: "relu_blob33"
  top: "conv_blob37"
  convolution_param {
    num_output: 1024
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm37"
  type: "BatchNorm"
  bottom: "conv_blob37"
  top: "batch_norm_blob37"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale37"
  type: "Scale"
  bottom: "batch_norm_blob37"
  top: "batch_norm_blob37"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add11"
  type: "Eltwise"
  bottom: "batch_norm_blob37"
  bottom: "relu_blob31"
  top: "add_blob11"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu34"
  type: "ReLU"
  bottom: "add_blob11"
  top: "relu_blob34"
}
layer {
  name: "conv38"
  type: "Convolution"
  bottom: "relu_blob34"
  top: "conv_blob38"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm38"
  type: "BatchNorm"
  bottom: "conv_blob38"
  top: "batch_norm_blob38"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale38"
  type: "Scale"
  bottom: "batch_norm_blob38"
  top: "batch_norm_blob38"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu35"
  type: "ReLU"
  bottom: "batch_norm_blob38"
  top: "relu_blob35"
}
layer {
  name: "conv39"
  type: "Convolution"
  bottom: "relu_blob35"
  top: "conv_blob39"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm39"
  type: "BatchNorm"
  bottom: "conv_blob39"
  top: "batch_norm_blob39"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale39"
  type: "Scale"
  bottom: "batch_norm_blob39"
  top: "batch_norm_blob39"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu36"
  type: "ReLU"
  bottom: "batch_norm_blob39"
  top: "relu_blob36"
}
layer {
  name: "conv40"
  type: "Convolution"
  bottom: "relu_blob36"
  top: "conv_blob40"
  convolution_param {
    num_output: 1024
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm40"
  type: "BatchNorm"
  bottom: "conv_blob40"
  top: "batch_norm_blob40"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale40"
  type: "Scale"
  bottom: "batch_norm_blob40"
  top: "batch_norm_blob40"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add12"
  type: "Eltwise"
  bottom: "batch_norm_blob40"
  bottom: "relu_blob34"
  top: "add_blob12"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu37"
  type: "ReLU"
  bottom: "add_blob12"
  top: "relu_blob37"
}
layer {
  name: "conv41"
  type: "Convolution"
  bottom: "relu_blob37"
  top: "conv_blob41"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm41"
  type: "BatchNorm"
  bottom: "conv_blob41"
  top: "batch_norm_blob41"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale41"
  type: "Scale"
  bottom: "batch_norm_blob41"
  top: "batch_norm_blob41"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu38"
  type: "ReLU"
  bottom: "batch_norm_blob41"
  top: "relu_blob38"
}
layer {
  name: "conv42"
  type: "Convolution"
  bottom: "relu_blob38"
  top: "conv_blob42"
  convolution_param {
    num_output: 256
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm42"
  type: "BatchNorm"
  bottom: "conv_blob42"
  top: "batch_norm_blob42"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale42"
  type: "Scale"
  bottom: "batch_norm_blob42"
  top: "batch_norm_blob42"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu39"
  type: "ReLU"
  bottom: "batch_norm_blob42"
  top: "relu_blob39"
}
layer {
  name: "conv43"
  type: "Convolution"
  bottom: "relu_blob39"
  top: "conv_blob43"
  convolution_param {
    num_output: 1024
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm43"
  type: "BatchNorm"
  bottom: "conv_blob43"
  top: "batch_norm_blob43"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale43"
  type: "Scale"
  bottom: "batch_norm_blob43"
  top: "batch_norm_blob43"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add13"
  type: "Eltwise"
  bottom: "batch_norm_blob43"
  bottom: "relu_blob37"
  top: "add_blob13"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu40"
  type: "ReLU"
  bottom: "add_blob13"
  top: "relu_blob40"
}
layer {
  name: "conv44"
  type: "Convolution"
  bottom: "relu_blob40"
  top: "conv_blob44"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm44"
  type: "BatchNorm"
  bottom: "conv_blob44"
  top: "batch_norm_blob44"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale44"
  type: "Scale"
  bottom: "batch_norm_blob44"
  top: "batch_norm_blob44"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu41"
  type: "ReLU"
  bottom: "batch_norm_blob44"
  top: "relu_blob41"
}
layer {
  name: "conv45"
  type: "Convolution"
  bottom: "relu_blob41"
  top: "conv_blob45"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 2
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm45"
  type: "BatchNorm"
  bottom: "conv_blob45"
  top: "batch_norm_blob45"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale45"
  type: "Scale"
  bottom: "batch_norm_blob45"
  top: "batch_norm_blob45"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu42"
  type: "ReLU"
  bottom: "batch_norm_blob45"
  top: "relu_blob42"
}
layer {
  name: "conv46"
  type: "Convolution"
  bottom: "relu_blob42"
  top: "conv_blob46"
  convolution_param {
    num_output: 2048
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm46"
  type: "BatchNorm"
  bottom: "conv_blob46"
  top: "batch_norm_blob46"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale46"
  type: "Scale"
  bottom: "batch_norm_blob46"
  top: "batch_norm_blob46"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "conv47"
  type: "Convolution"
  bottom: "relu_blob40"
  top: "conv_blob47"
  convolution_param {
    num_output: 2048
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 2
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm47"
  type: "BatchNorm"
  bottom: "conv_blob47"
  top: "batch_norm_blob47"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale47"
  type: "Scale"
  bottom: "batch_norm_blob47"
  top: "batch_norm_blob47"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add14"
  type: "Eltwise"
  bottom: "batch_norm_blob46"
  bottom: "batch_norm_blob47"
  top: "add_blob14"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu43"
  type: "ReLU"
  bottom: "add_blob14"
  top: "relu_blob43"
}
layer {
  name: "conv48"
  type: "Convolution"
  bottom: "relu_blob43"
  top: "conv_blob48"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm48"
  type: "BatchNorm"
  bottom: "conv_blob48"
  top: "batch_norm_blob48"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale48"
  type: "Scale"
  bottom: "batch_norm_blob48"
  top: "batch_norm_blob48"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu44"
  type: "ReLU"
  bottom: "batch_norm_blob48"
  top: "relu_blob44"
}
layer {
  name: "conv49"
  type: "Convolution"
  bottom: "relu_blob44"
  top: "conv_blob49"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm49"
  type: "BatchNorm"
  bottom: "conv_blob49"
  top: "batch_norm_blob49"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale49"
  type: "Scale"
  bottom: "batch_norm_blob49"
  top: "batch_norm_blob49"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu45"
  type: "ReLU"
  bottom: "batch_norm_blob49"
  top: "relu_blob45"
}
layer {
  name: "conv50"
  type: "Convolution"
  bottom: "relu_blob45"
  top: "conv_blob50"
  convolution_param {
    num_output: 2048
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm50"
  type: "BatchNorm"
  bottom: "conv_blob50"
  top: "batch_norm_blob50"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale50"
  type: "Scale"
  bottom: "batch_norm_blob50"
  top: "batch_norm_blob50"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add15"
  type: "Eltwise"
  bottom: "batch_norm_blob50"
  bottom: "relu_blob43"
  top: "add_blob15"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu46"
  type: "ReLU"
  bottom: "add_blob15"
  top: "relu_blob46"
}
layer {
  name: "conv51"
  type: "Convolution"
  bottom: "relu_blob46"
  top: "conv_blob51"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm51"
  type: "BatchNorm"
  bottom: "conv_blob51"
  top: "batch_norm_blob51"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale51"
  type: "Scale"
  bottom: "batch_norm_blob51"
  top: "batch_norm_blob51"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu47"
  type: "ReLU"
  bottom: "batch_norm_blob51"
  top: "relu_blob47"
}
layer {
  name: "conv52"
  type: "Convolution"
  bottom: "relu_blob47"
  top: "conv_blob52"
  convolution_param {
    num_output: 512
    bias_term: false
    pad: 1
    kernel_size: 3
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm52"
  type: "BatchNorm"
  bottom: "conv_blob52"
  top: "batch_norm_blob52"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale52"
  type: "Scale"
  bottom: "batch_norm_blob52"
  top: "batch_norm_blob52"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "relu48"
  type: "ReLU"
  bottom: "batch_norm_blob52"
  top: "relu_blob48"
}
layer {
  name: "conv53"
  type: "Convolution"
  bottom: "relu_blob48"
  top: "conv_blob53"
  convolution_param {
    num_output: 2048
    bias_term: false
    pad: 0
    kernel_size: 1
    group: 1
    stride: 1
    weight_filler {
      type: "xavier"
    }
    dilation: 1
  }
}
layer {
  name: "batch_norm53"
  type: "BatchNorm"
  bottom: "conv_blob53"
  top: "batch_norm_blob53"
  batch_norm_param {
    use_global_stats: true
    eps: 1e-05
  }
}
layer {
  name: "bn_scale53"
  type: "Scale"
  bottom: "batch_norm_blob53"
  top: "batch_norm_blob53"
  scale_param {
    bias_term: true
  }
}
layer {
  name: "add16"
  type: "Eltwise"
  bottom: "batch_norm_blob53"
  bottom: "relu_blob46"
  top: "add_blob16"
  eltwise_param {
    operation: SUM
  }
}
layer {
  name: "relu49"
  type: "ReLU"
  bottom: "add_blob16"
  top: "relu_blob49"
}
layer {
  name: "ave_pool1"
  type: "Pooling"
  bottom: "relu_blob49"
  top: "ave_pool_blob1"
  pooling_param {
    pool: AVE
    kernel_size: 14  # 这里转换出来,原始为7,nn.AdaptiveAvgPool2d((1, 1))最终的feature map的尺寸为(1,1)
    stride: 14  # 7只能输出(2,2),故改为14
    # ceil_mode: false 无此属性,注释
  }
}
layer {
  name: "view1"
  type: "Reshape"
  bottom: "ave_pool_blob1"
  top: "view_blob1"
  reshape_param {
    shape {
      dim: 0
      dim: -1
    }
  }
}
layer {
  name: "fc1"
  type: "InnerProduct"
  bottom: "view_blob1"
  top: "fc_blob1"
  inner_product_param {
    num_output: 2
    bias_term: true
    weight_filler {
      type: "xavier"
    }
    bias_filler {
      type: "constant"
    }
  }
}
layer {  #此处转换出来是没有的,需要手动添加
  name: "prob"
  type: "Softmax"
  bottom: "fc_blob1"
  top: "prob"
}

转换完成后应该将Pytorch的源码复原。

模型推理,此处需要换回conda2.7的caffe环境

# -*- coding: UTF-8 -*-

import numpy as np
import cv2
import caffe

if __name__ == '__main__':

    net_file = 'resnet50.prototxt'
    caffe_model = 'resnet50.caffemodel'
    # 创建caffe模型,并读取模型训练参数
    net = caffe.Net(net_file, caffe_model, caffe.TEST)

    img = cv2.imread("10.20.190.192_01_2023082114575167.jpg")
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, (224, 224))
    img_data = img.reshape((1, 3, 224, 224))
    net.blobs['data'].data[...] = img_data
    # 执行推理
    out = net.forward()
    print out
    print np.argmax(out['prob'])

运行结果

{'prob': array([[1., 0.]], dtype=float32)}
0

ONNX转Caffe

下载转换工具,下载地址:https://github.com/Wulingtian/yolov5_onnx2caffe

这里有关ONNX的内容可以参考ONNX整理 ,我们以YOLOV5 4.0的ONNX为例来进行Caffe的转化。YOLOV5模型对ONNX的转化可以参考模型部署篇(二) 中的C++OpenCV 部署 YOLOV5 ONNX 模型.

安装conda2.7环境的onnx

pip install onnx==1.6.0 -i https://pypi.tuna.tsinghua.edu.cn/simple
  • 训练一个YOLOV5 4.0的人体检测模型

YOLOV5 4.0下载地址:https://github.com/ultralytics/yolov5/tree/v4.0

数据集下载链接: https://pan.baidu.com/s/1pt_UD_eZCQCmAoRycBXl_Q 提取码: b9z7

具体训练方式可以参考YOLO系列介绍(二) 中的训练自己的数据集

这里有区别的地方,修改models/yolov5s.yaml

# parameters
nc: 2  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.50  # layer channel multiple

# anchors
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

# YOLOv5 backbone
backbone:
  # [from, number, module, args]
  #[[-1, 1, Focus, [64, 3]],  # 0-P1/2
   [[-1, 1, Conv, [64, 3, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 9, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 1, SPP, [1024, [5, 9, 13]]],
   [-1, 3, C3, [1024, False]],  # 9
  ]

# YOLOv5 head
head:
  [[-1, 1, Conv, [512, 1, 1]],
#   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [-1, 1, nn.ConvTranspose2d, [256, 256, 2, 2]],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, C3, [512, False]],  # 13

   [-1, 1, Conv, [256, 1, 1]],
#   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [-1, 1, nn.ConvTranspose2d, [128, 128, 2, 2]],
   [[-1, 4], 1, Concat, [1]],  # cat backbone P3
   [-1, 3, C3, [256, False]],  # 17 (P3/8-small)

   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, C3, [512, False]],  # 20 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],
   [[-1, 10], 1, Concat, [1]],  # cat head P5
   [-1, 3, C3, [1024, False]],  # 23 (P5/32-large)

   [[17, 20, 23], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

修改models/common.py

class Conv(nn.Module):
    # Standard convolution
    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True):  # ch_in, ch_out, kernel, stride, padding, groups
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        # self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
        self.act = nn.ReLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def fuseforward(self, x):
        return self.act(self.conv(x))


class BottleneckCSP(nn.Module):
    # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks
    def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super(BottleneckCSP, self).__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False)
        self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False)
        self.cv4 = Conv(2 * c_, c2, 1, 1)
        self.bn = nn.BatchNorm2d(2 * c_)  # applied to cat(cv2, cv3)
        # self.act = nn.LeakyReLU(0.1, inplace=True)
        self.act = nn.ReLU()
        self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)])

    def forward(self, x):
        y1 = self.cv3(self.m(self.cv1(x)))
        y2 = self.cv2(x)
        return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1))))


class SPP(nn.Module):
    # Spatial pyramid pooling layer used in YOLOv3-SPP
    def __init__(self, c1, c2, k=(5, 9, 13)):
        super(SPP, self).__init__()
        c_ = c1 // 2  # hidden channels
        self.cv1 = Conv(c1, c_, 1, 1)
        self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1)
        # self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k])
        self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2, ceil_mode=True) for x in k])

    def forward(self, x):
        x = self.cv1(x)
        return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1))

修改一些版本错误问题,修改utils/datasets.py,将所有的np.int改成np.int32;删除高版本的numpy(在Conda3.X环境中)

pip uninstall numpy
pip install numpy==1.23.5 -i https://pypi.tuna.tsinghua.edu.cn/simple

修改utils/loss.py,将

indices.append((b, a, gj.clamp_(0, gain[3] - 1), gi.clamp_(0, gain[2] - 1)))

修改为

indices.append((b, a, gj.clamp_(0, gain[3].long() - 1), gi.clamp_(0, gain[2].long() - 1)))

下载预训练模型参数,下载地址:https://github.com/ultralytics/yolov5/releases/download/v4.0/yolov5s.pt

训练,在主文件夹下的train.py添加执行参数

--img 640 --batch 32 --epochs 50 --data data/voc-person.yaml --cfg models/yolov5s.yaml --weights yolov5s.pt --noautoanchor

转onnx,在models/export.py中添加执行参数

--weight best.pt --img 640

将工作目录设为主目录,执行后得到best.onnx

在终端窗口的主目录中执行

python -m onnxsim best.onnx best-sim.onnx
  • 模型转换

将best-sim.onnx拷贝到yolov5_onnx2caffe主目录下。修改onnx2caffe目录下的_weightloader.py,添加

# -*- coding: ascii -*-

并删除所有的中文注释。修改_operators.py,添加

# -*- coding: UTF-8 -*-

修改onnx文件的node名称

import onnx

if __name__ == '__main__':

    model = onnx.load('best-sim.onnx')
    nodes = model.graph.node
    i = 1
    for node in nodes:
        node.name = str(i)
        i += 1
    for node in nodes:
        print node.name
    onnx.save_model(model, 'best.onnx')

修改主目录下的convertCaffe.py

if __name__ == "__main__":

    onnx_path = "best.onnx"
    prototxt_path = "best.prototxt"
    caffemodel_path = "best.caffemodel"

    graph = getGraph(onnx_path)
    # convertToCaffe(graph, prototxt_path, caffemodel_path, exis_focus=True, focus_concat_name="Concat_40", focus_conv_name="Conv_41")
    #convertToCaffe(graph, prototxt_path, caffemodel_path, exis_focus=True, focus_concat_name="Concat_40")
    #convertToCaffe(graph, prototxt_path, caffemodel_path, focus_conv_name="Conv_41")
    convertToCaffe(graph, prototxt_path, caffemodel_path)

 在conda2.7中执行,可以转化成caffe的网络结构文件best.prototxt跟模型参数文件best.caffemodel

  • C++模型推理

新建一个项目文件夹,位置随意。将best.prototxt和best.caffemodel拷贝到该文件夹。在该文件夹下新建C++文件

caffe_yolov5s.cpp

#include "caffe/caffe.hpp"
#include <string>
#include <vector>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

// 用于计时
#include <boost/date_time/posix_time/posix_time.hpp>

#define INPUT_W 640
#define INPUT_H 640
#define IsPadding 1
#define NUM_CLASS 2
#define NMS_THRESH 0.6
#define CONF_THRESH 0.3
std::string prototxt_path = "best.prototxt";
std::string caffemodel_path = "best.caffemodel";
std::string pic_path = "bus.jpg";

using namespace cv;
//using namespace std;
using namespace caffe;
using std::string;

using caffe::Blob;
using caffe::Caffe;
using caffe::Layer;
using caffe::Net;
using caffe::shared_ptr;
using caffe::string;
using caffe::vector;
using std::cout;
using std::endl;
using std::ostringstream;

struct Bbox{
    float xmin;
    float ymin;
    float xmax;
    float ymax;
    float score;
    int cid;
};

struct Anchor{
    float width;
    float height;
};

std::vector<Anchor> initAnchors(){
    std::vector<Anchor> anchors;
    Anchor anchor;
    // 10,13, 16,30, 33,23, 30,61, 62,45, 59,119, 116,90,  156,198,  373,326
    anchor.width = 10;
    anchor.height = 13;
    anchors.emplace_back(anchor);
    anchor.width = 16;
    anchor.height = 30;
    anchors.emplace_back(anchor);
    anchor.width = 33;
    anchor.height = 23;
    anchors.emplace_back(anchor);
    anchor.width = 30;
    anchor.height = 61;
    anchors.emplace_back(anchor);
    anchor.width = 62;
    anchor.height = 45;
    anchors.emplace_back(anchor);
    anchor.width = 59;
    anchor.height = 119;
    anchors.emplace_back(anchor);
    anchor.width = 116;
    anchor.height = 90;
    anchors.emplace_back(anchor);
    anchor.width = 156;
    anchor.height = 198;
    anchors.emplace_back(anchor);
    anchor.width = 373;
    anchor.height = 326;
    anchors.emplace_back(anchor);
    return anchors;
}

template <typename T>
T clip(const T &n, const T &lower, const T &upper){
    return std::max(lower, std::min(n, upper));
}

template<class ForwardIterator>
inline size_t argmax(ForwardIterator first, ForwardIterator last)
{
    return std::distance(first, std::max_element(first, last));
}

void transform(const int &ih, const int &iw, const int &oh, const int &ow, std::vector<Bbox> &bboxes,
               bool is_padding) {
    if(is_padding){
        float scale = std::min(static_cast<float>(ow) / static_cast<float>(iw), static_cast<float>(oh) / static_cast<float>(ih));
        int nh = static_cast<int>(scale * static_cast<float>(ih));
        int nw = static_cast<int>(scale * static_cast<float>(iw));
        int dh = (oh - nh) / 2;
        int dw = (ow - nw) / 2;
        for (auto &bbox : bboxes){
            bbox.xmin = (bbox.xmin - dw) / scale;
            bbox.ymin = (bbox.ymin - dh) / scale;
            bbox.xmax = (bbox.xmax - dw) / scale;
            bbox.ymax = (bbox.ymax - dh) / scale;
        }
    }else{
        for (auto &bbox : bboxes){
            bbox.xmin = bbox.xmin * iw / ow;
            bbox.ymin = bbox.ymin * ih / oh;
            bbox.xmax = bbox.xmax * iw / ow;
            bbox.ymax = bbox.ymax * ih / oh;
        }
    }
}


cv::Mat renderBoundingBox(cv::Mat image, const std::vector<Bbox> &bboxes){
    for (auto it: bboxes){
        float score = it.score;
        cv::rectangle(image, cv::Point(it.xmin, it.ymin), cv::Point(it.xmax, it.ymax), cv::Scalar(255, 0,0), 2);
        cv::putText(image, std::to_string(score), cv::Point(it.xmin, it.ymin), cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0,0,0));
    }
    return image;
}

void nms_cpu(std::vector<Bbox> &bboxes, float threshold) {
    if (bboxes.empty()){
        return ;
    }
    // 1.之前需要按照score排序
    std::sort(bboxes.begin(), bboxes.end(), [&](Bbox b1, Bbox b2){return b1.score>b2.score;});
    // 2.先求出所有bbox自己的大小
    std::vector<float> area(bboxes.size());
    for (int i=0; i<bboxes.size(); ++i){
        area[i] = (bboxes[i].xmax - bboxes[i].xmin + 1) * (bboxes[i].ymax - bboxes[i].ymin + 1);
    }
    // 3.循环
    for (int i=0; i<bboxes.size(); ++i){
        for (int j=i+1; j<bboxes.size(); ){
            float left = std::max(bboxes[i].xmin, bboxes[j].xmin);
            float right = std::min(bboxes[i].xmax, bboxes[j].xmax);
            float top = std::max(bboxes[i].ymin, bboxes[j].ymin);
            float bottom = std::min(bboxes[i].ymax, bboxes[j].ymax);
            float width = std::max(right - left + 1, 0.f);
            float height = std::max(bottom - top + 1, 0.f);
            float u_area = height * width;
            float iou = (u_area) / (area[i] + area[j] - u_area);
            if (iou>=threshold){
                bboxes.erase(bboxes.begin()+j);
                area.erase(area.begin()+j);
            }else{
                ++j;
            }
        }
    }
}
template <typename T>
T sigmoid(const T &n) {
    return 1 / (1 + exp(-n));
}
void postProcessParall(const int height, const int width, int scale_idx, float postThres, float * origin_output, vector<int> Strides, vector<Anchor> Anchors, vector<Bbox> *bboxes)
{
    Bbox bbox;
    float cx, cy, w_b, h_b, score;
    int cid;
    const float *ptr = (float *)origin_output;
    for(unsigned long a=0; a<3; ++a){
        for(unsigned long h=0; h<height; ++h){
            for(unsigned long w=0; w<width; ++w){
                const float *cls_ptr =  ptr + 5;
                cid = argmax(cls_ptr, cls_ptr+NUM_CLASS);
                score = sigmoid(ptr[4]) * sigmoid(cls_ptr[cid]);
                if(score>=postThres){
                    cx = (sigmoid(ptr[0]) * 2.f - 0.5f + static_cast<float>(w)) * static_cast<float>(Strides[scale_idx]);
                    cy = (sigmoid(ptr[1]) * 2.f - 0.5f + static_cast<float>(h)) * static_cast<float>(Strides[scale_idx]);
                    w_b = powf(sigmoid(ptr[2]) * 2.f, 2) * Anchors[scale_idx * 3 + a].width;
                    h_b = powf(sigmoid(ptr[3]) * 2.f, 2) * Anchors[scale_idx * 3 + a].height;
                    bbox.xmin = clip(cx - w_b / 2, 0.F, static_cast<float>(INPUT_W - 1));
                    bbox.ymin = clip(cy - h_b / 2, 0.f, static_cast<float>(INPUT_H - 1));
                    bbox.xmax = clip(cx + w_b / 2, 0.f, static_cast<float>(INPUT_W - 1));
                    bbox.ymax = clip(cy + h_b / 2, 0.f, static_cast<float>(INPUT_H - 1));
                    bbox.score = score;
                    bbox.cid = cid;
                    //std::cout<< "bbox.cid : " << bbox.cid << std::endl;
                    bboxes->push_back(bbox);
                }
                ptr += 5 + NUM_CLASS;
            }
        }
    }
}
vector<Bbox> postProcess(vector<float *> origin_output, float postThres, float nmsThres) {

    vector<Anchor> Anchors = initAnchors();
    vector<Bbox> bboxes;
    vector<int> Strides = vector<int> {8, 16, 32};
    for (int scale_idx=0; scale_idx<3; ++scale_idx) {
        const int stride = Strides[scale_idx];
        const int width = (INPUT_W + stride - 1) / stride;
        const int height = (INPUT_H + stride - 1) / stride;
        //std::cout << "width : " << width << " " << "height : " << height << std::endl;
        float * cur_output_tensor = origin_output[scale_idx];
        postProcessParall(height, width, scale_idx, postThres, cur_output_tensor, Strides, Anchors, &bboxes);
    }
    nms_cpu(bboxes, nmsThres);
    return bboxes;
}

cv::Mat preprocess_img(cv::Mat& img) {
    int w, h, x, y;
    float r_w = INPUT_W / (img.cols*1.0);
    float r_h = INPUT_H / (img.rows*1.0);
    if (r_h > r_w) {
        w = INPUT_W;
        h = r_w * img.rows;
        x = 0;
        y = (INPUT_H - h) / 2;
    } else {
        w = r_h * img.cols;
        h = INPUT_H;
        x = (INPUT_W - w) / 2;
        y = 0;
    }
    cv::Mat re(h, w, CV_8UC3);
    cv::resize(img, re, re.size(), 0, 0, cv::INTER_LINEAR);
    cv::Mat out(INPUT_H, INPUT_W, CV_8UC3, cv::Scalar(128, 128, 128));
    re.copyTo(out(cv::Rect(x, y, re.cols, re.rows)));
    return out;
}

int main()
{
    ::google::InitGoogleLogging("caffe"); //初始化日志文件,不调用会给出警告,但不会报错
    Caffe::set_mode(Caffe::CPU);
    //Caffe::set_solver_rank(1); //不进行日志输出
    Net<float> caffe_net(prototxt_path, caffe::TEST, 0, nullptr);
    caffe_net.CopyTrainedLayersFrom(caffemodel_path);
    // 读入图片
    cv::Mat img = cv::imread(pic_path);
    //cv::Mat img = cv::imread("../model/21.jpg");
    CHECK(!img.empty()) << "Unable to decode image ";
    cv::Mat showImage = img.clone();

    // 图片预处理,并加载图片进入blob
    Blob<float> *input_layer = caffe_net.input_blobs()[0];
    float *input_data = input_layer->mutable_cpu_data();

    static float data[3 * INPUT_H * INPUT_W];
    cv::Mat pre_img = preprocess_img(img);
    std::cout << "preprocess_img finished!\n";
    int i = 0;
    for (int row = 0; row < INPUT_H; ++row) {
        uchar* uc_pixel = pre_img.data + row * pre_img.step;
        for (int col = 0; col < INPUT_W; ++col) {
            data[i] = (float)uc_pixel[2] / 255.0;
            data[i + INPUT_H * INPUT_W] = (float)uc_pixel[1] / 255.0;
            data[i + 2 * INPUT_H * INPUT_W] = (float)uc_pixel[0] / 255.0;
            uc_pixel += 3;
            ++i;
        }
    }

    memcpy((float *) (input_data),
           data, 3 * INPUT_H * INPUT_W * sizeof(float));

    //boost::posix_time::ptime start_time_ = boost::posix_time::microsec_clock::local_time(); //开始计时
    float total_time = 0;    
    //前向运算
    boost::posix_time::ptime start_time_1 = boost::posix_time::microsec_clock::local_time();
    caffe_net.Forward();
    boost::posix_time::ptime end_time_1 = boost::posix_time::microsec_clock::local_time();
    total_time += (end_time_1 - start_time_1).total_milliseconds(); 
    std::cout << (end_time_1 - start_time_1).total_milliseconds() << " ms." << std::endl;

    //boost::posix_time::ptime end_time_ = boost::posix_time::microsec_clock::local_time(); //结束计时

    Blob<float> *output_layer0 = caffe_net.output_blobs()[2];
    const float *output0 = output_layer0->cpu_data();
    //cout << "output shape: " << output_layer0->shape(0) << " " << output_layer0->shape(1) << " " <<  output_layer0->shape(2) << " " <<  output_layer0->shape(3) <<  " " <<  output_layer0->shape(4) << endl;

    Blob<float> *output_layer1 = caffe_net.output_blobs()[0];
    const float *output1 = output_layer1->cpu_data();
    //cout << "371 shape: " << output_layer1->shape(0) << " " << output_layer1->shape(1) << " " <<  output_layer1->shape(2) << " " <<  output_layer1->shape(3) <<  " " <<  output_layer1->shape(4) << endl;

    Blob<float> *output_layer2 = caffe_net.output_blobs()[1];
    const float *output2 = output_layer2->cpu_data();
    //cout << "391 shape: " << output_layer2->shape(0) << " " << output_layer2->shape(1) << " " <<  output_layer2->shape(2) << " " <<  output_layer2->shape(3) <<  " " <<  output_layer2->shape(4) << endl;

    vector<float *> cur_output_tensors;
    cur_output_tensors.push_back(const_cast<float *>(output0));
    cur_output_tensors.push_back(const_cast<float *>(output1));
    cur_output_tensors.push_back(const_cast<float *>(output2));

    vector<Bbox> bboxes = postProcess(cur_output_tensors, CONF_THRESH, NMS_THRESH);
    transform(showImage.rows, showImage.cols, INPUT_W, INPUT_H, bboxes, IsPadding);
    showImage = renderBoundingBox(showImage, bboxes);
    cv::imwrite("reslut.jpg", showImage);


    std::cout << "total time : " << total_time << " ms" << std::endl;
}

这里需要注意的是

#define NUM_CLASS 2

需要改成跟你的模型分类数相匹配。

Makefile跟之前的LeNet是一样的,只需要改

EXE=caffe_yolov5s

编译后运行结果result.jpg如下

运行日志

preprocess_img finished!
517 ms.
total time : 517 ms
  • 训练一个Yolov3的人体检测模型

修改models/yolov3.yaml,内容如下

# YOLOv3 🚀 by Ultralytics, AGPL-3.0 license

# Parameters
nc: 2  # number of classes
depth_multiple: 1.0  # model depth multiple
width_multiple: 1.0  # layer channel multiple
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

# darknet53 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [32, 3, 1]],  # 0
   [-1, 1, Conv, [64, 3, 2]],  # 1-P1/2
   [-1, 1, Bottleneck, [64]],
   [-1, 1, Conv, [128, 3, 2]],  # 3-P2/4
   [-1, 2, Bottleneck, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 5-P3/8
   [-1, 8, Bottleneck, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 7-P4/16
   [-1, 8, Bottleneck, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 9-P5/32
   [-1, 4, Bottleneck, [1024]],  # 10
  ]

# YOLOv3 head
head:
  [[-1, 1, Bottleneck, [1024, False]],
   [-1, 1, Conv, [512, 1, 1]],
   [-1, 1, Conv, [1024, 3, 1]],
   [-1, 1, Conv, [512, 1, 1]],
   [-1, 1, Conv, [1024, 3, 1]],  # 15 (P5/32-large)

   [-2, 1, Conv, [256, 1, 1]],
#   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [-1, 1, nn.ConvTranspose2d, [256, 2, 2]],
   [[-1, 8], 1, Concat, [1]],  # cat backbone P4
   [-1, 1, Bottleneck, [512, False]],
   [-1, 1, Bottleneck, [512, False]],
   [-1, 1, Conv, [256, 1, 1]],
   [-1, 1, Conv, [512, 3, 1]],  # 22 (P4/16-medium)

   [-2, 1, Conv, [128, 1, 1]],
#   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [-1, 1, nn.ConvTranspose2d, [128, 2, 2]],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P3
   [-1, 1, Bottleneck, [256, False]],
   [-1, 2, Bottleneck, [256, False]],  # 27 (P3/8-small)

   [[27, 22, 15], 1, Detect, [nc, anchors]],   # Detect(P3, P4, P5)
  ]

修改models/common.py

class Conv(nn.Module):
    # Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)
    # default_act = nn.SiLU()  # default activation
    default_act = nn.ReLU()

    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

    def forward(self, x):
        return self.act(self.bn(self.conv(x)))

    def forward_fuse(self, x):
        return self.act(self.conv(x))

下载预训练模型参数: https://pan.baidu.com/s/1rKGrNjzEQQ_JGqHmlxnTMQ 提取码: qxxf

训练,在主文件夹下的 train.py 添加执行参数

--img 416 --batch 64 --epochs 50 --data data/voc_person.yaml --cfg models/yolov3.yaml --weights yolov3.pt --noautoanchor

转 onnx,在主文件夹下的 export.py添加执行参数

--weight best.pt --img 416 --include onnx

在终端窗口的主目录中执行

python -m onnxsim best.onnx best-sim.onnx

模型转换

将 best-sim.onnx 拷贝到 yolov5_onnx2caffe 主目录下

修改onnx文件的node名称

import onnx

if __name__ == '__main__':

    model = onnx.load('best-sim.onnx')
    nodes = model.graph.node
    i = 1
    for node in nodes:
        node.name = str(i)
        i += 1
    for node in nodes:
        print node.name
    onnx.save_model(model, 'best-rename.onnx')

去除某些节点

import onnx

if __name__ == '__main__':

    model = "best-rename.onnx"
    onnx_model = onnx.load(model)
    graph = onnx_model.graph
    nodes = graph.node

    for node in nodes:
        if node.op_type == "Sigmoid":
            nodes.remove(node)
        if node.op_type == "Split":
            nodes.remove(node)
        if node.op_type == "Pow":
            nodes.remove(node)

    onnx.save_model(onnx_model, 'best.onnx')

转换,在convertCaffe.py中执行

if __name__ == "__main__":

    onnx_path = "best.onnx"
    prototxt_path = "best.prototxt"
    caffemodel_path = "best.caffemodel"

    graph = getGraph(onnx_path)
    # convertToCaffe(graph, prototxt_path, caffemodel_path, exis_focus=True, focus_concat_name="Concat_40", focus_conv_name="Conv_41")
    #convertToCaffe(graph, prototxt_path, caffemodel_path, exis_focus=True, focus_concat_name="Concat_40")
    #convertToCaffe(graph, prototxt_path, caffemodel_path, focus_conv_name="Conv_41")
    convertToCaffe(graph, prototxt_path, caffemodel_path)

使用Yolov3训练自己的数据集

环境搭建

这里我们不再使用yolov5-caffe这套框架,而是需要下载另一个版本的caffe,下载地址:https://gitee.com/junfengchen2016/conner99_caffe/tree/ssd/

编译boost_regex,进入文件夹

cd boost_1_63_0/libs/regex/build
make -f gcc.mak
make -f gcc-shared.mak
cd gcc
ln -sf libboost_regex-gcc-1_53.so libboost_regex.so

设置环境变量

sudo vim /etc/profile

添加内容如下

export LD_LIBRARY_PATH=/home/kc/Downloads/boost_1_63_0/libs/regex/build/gcc:$LD_LIBRARY_PATH

执行

source /etc/profile

conner99_caffe-ssd的Makefile.config与之前的GPU版本的类似,这里需要添加boost_regex的lib路径

LIBRARY_DIRS := $(PYTHON_LIB) /usr/local/lib /usr/lib /usr/local/lib/opencv4/ /usr/lib/x86_64-linux-gnu /usr/lib/x86_64-linux-gnu/hdf5/serial /home/kc/Downloads/yolov5_caffe-master/GFPQLibs/lib /home/kc/Downloads/boost_1_63_0/libs/regex/build/gcc

Makefile也与之前GPU版本的类似,增加如下内容

LIBRARIES +=  boost_regex boost_atomic boost_thread stdc++

编译该版本的Caffe只需要按照前面的方式即可。

制作Pascal Voc格式数据集

创建相应的文件夹

cd Downloads
mkdir VOCdevkit
cd VOCdevkit
mkdir MyDataSet
cd MyDataSet
mkdir Annotation
mkdir JPEGImages
mkdir ImageSets
cd ImageSets
mkdir Main

将所有我们需要训练的jpg图像放入到JPEGImages目录中,将所有的原始.xml文件放入到Annotations目录中。如何标注这些图片可以参考YOLO系列介绍(二) 中的数据标注

划分训练集和测试集

import os
import random

xmlfilepath = r'/home/kc/Downloads/VOCdevkit/MyDataSet/Annotations/'  # change xml path
saveBasePath = r"/home/kc/Downloads/VOCdevkit/"  # change base path
trainval_percent = 0.8  # adjust trainval percentage
train_percent = 0.8  # adjust train percentage
total_xml = os.listdir(xmlfilepath)
num = len(total_xml)
list = range(num)
tv = int(num * trainval_percent)
tr = int(tv * train_percent)
trainval = random.sample(list, tv)
train = random.sample(trainval, tr)

print("train and val size", tv)
print("traub suze", tr)
ftrainval = open(os.path.join(saveBasePath, 'MyDataSet/ImageSets/Main/trainval.txt'), 'w')
ftest = open(os.path.join(saveBasePath, 'MyDataSet/ImageSets/Main/test.txt'), 'w')
ftrain = open(os.path.join(saveBasePath, 'MyDataSet/ImageSets/Main/train.txt'), 'w')
fval = open(os.path.join(saveBasePath, 'MyDataSet/ImageSets/Main/val.txt'), 'w')

for i in list:
    name = total_xml[i][:-4] + '\n'
    if i in trainval:
        ftrainval.write(name)
        if i in train:
            ftrain.write(name)
        else:
            fval.write(name)
    else:
        ftest.write(name)

ftrainval.close()
ftrain.close()
fval.close()
ftest.close()

注意,这里并没有重新拷贝图片和xml文件,只是对它们的序号进行了划分成文本文件。执行后会在VOCdevkit/MyDataSet/ImageSets/Main下面生成四个文本文件——test.txt,train.txt,trainval.txt,val.txt。

进入conner99_caffe-ssd的目录

cd conner99_caffe-ssd/data/VOC0712

修改create_list.sh,内容如下

#!/bin/bash

root_dir=$HOME/Downloads/VOCdevkit/
sub_dir=ImageSets/Main
bash_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for dataset in trainval test
do
  dst_file=$bash_dir/$dataset.txt
  if [ -f $dst_file ]
  then
    rm -f $dst_file
  fi
  for name in MyDataSet
  do
    # if [[ $dataset == "test" && $name == "VOC2012" ]]
    # then
    #   continue
    # fi
    echo "Create list for $name $dataset..."
    dataset_file=$root_dir/$name/$sub_dir/$dataset.txt

    img_file=$bash_dir/$dataset"_img.txt"
    cp $dataset_file $img_file
    sed -i "s/^/$name\/JPEGImages\//g" $img_file
    sed -i "s/$/.jpg/g" $img_file

    label_file=$bash_dir/$dataset"_label.txt"
    cp $dataset_file $label_file
    sed -i "s/^/$name\/Annotations\//g" $label_file
    sed -i "s/$/.xml/g" $label_file

    paste -d' ' $img_file $label_file >> $dst_file

    rm -f $label_file
    rm -f $img_file
  done

  # Generate image name and size infomation.
  if [ $dataset == "test" ]
  then
    $bash_dir/../../build/tools/get_image_size $root_dir $dst_file $bash_dir/$dataset"_name_size.txt"
  fi

  # Shuffle trainval file.
  if [ $dataset == "trainval" ]
  then
    rand_file=$dst_file.random
    cat $dst_file | perl -MList::Util=shuffle -e 'print shuffle(<STDIN>);' > $rand_file
    mv $rand_file $dst_file
  fi
done

执行

./create_list.sh

此时会在当前文件夹创建三个文本文件——test_name_size.txt,test.txt,trainval.txt。

创建labelmap_voc.prototxt文件,内容如下

item {
  name: "none_of_the_above"
  label: 0
  display_name: "background"
}
item {
  name: "person"
  label: 1
  display_name: "person"
}
item {
  name: "face"
  label: 2
  display_name: "face"
}

这里根据你自己的分类去修改,但需要保留第一个背景分类,即label为0的分类。

修改create_data.sh,内容如下

cur_dir=$(cd $( dirname ${BASH_SOURCE[0]} ) && pwd )
root_dir=$cur_dir/../..

cd $root_dir

redo=1
data_root_dir="$HOME/Downloads/VOCdevkit"
dataset_name="VOC0712"
mapfile="$root_dir/data/$dataset_name/labelmap_voc.prototxt"
anno_type="detection"
db="lmdb"
min_dim=0
max_dim=0
width=0
height=0

extra_cmd="--encode-type=jpg --encoded"
if [ $redo ]
then
  extra_cmd="$extra_cmd --redo"
fi
for subset in test trainval
do
  python $root_dir/scripts/create_annoset.py --anno-type=$anno_type --label-map-file=$mapfile --min-dim=$min_dim --max-dim=$max_dim --resize-width=$width --resize-height=$height --check-label $extra_cmd $data_root_dir $root_dir/data/$dataset_name/$subset.txt $data_root_dir/$dataset_name/$db/$dataset_name"_"$subset"_"$db examples/$dataset_name
done

执行

./create_data.sh

此时会在VOCdevkit/VOC0712/lmdb目录下产生两个lmdb文件——VOC0712_test_lmdb和VOC0712_trainval_lmdb。这就是我们需要用Caffe来训练的数据集格式。

模型训练

这里我们进行模型训练需要另外一个caffe框架,下载地址:https://gitee.com/madobet/MobileNet-YOLO

使用之前GPU的Makefile.config以及Makefile即可开始编译。

转化Darknet的YoloV3模型

有关darknet的模型训练可以参考YOLO系列介绍 中的使用 Darknet 来训练 YoloV3。

转换工具下载地址:https://pan.baidu.com/s/1Wlfm0dvV1mM1tVYxT7kWZg
提取码:kr9y

执行

pip install torchvision
pip install future
pip install opencv-python==3.4.1.15

修改model_convert/yolov3_darknet2caffe.py

caffe_root='/home/user/Downloads/caffe/'

将之前训练好的darknet的模型参数以及配置文件拷贝到model_convert下,执行

python yolov3_darknet2caffe.py yolov3-voc.cfg yolov3-voc_last.weights yolov3-voc.prototxt yolov3-voc.caffemodel

Caffe模型验证

修改CMakeLists.txt

# build C/C++ interface
include_directories(${PROJECT_INCLUDE_DIR} ${GIE_PATH}/include)
include_directories(${PROJECT_INCLUDE_DIR} 
	/home/user/Downloads/caffe/include 
	/home/user/Downloads/caffe/build/include 
	/home/user/Downloads/caffe/src
)


file(GLOB inferenceSources *.cpp *.cu )
file(GLOB inferenceIncludes *.h )

cuda_add_library(sysDetectSpeed SHARED ${inferenceSources})
target_link_libraries(sysDetectSpeed 
	/home/user/Downloads/caffe/build/lib/libcaffe.so  
	/usr/lib/x86_64-linux-gnu/libglog.so  
	/usr/lib/x86_64-linux-gnu/libgflags.so
    	/usr/lib/x86_64-linux-gnu/libboost_system.so  
	/usr/local/cuda/lib64/libcudart.so.8.0
	/usr/local/cuda/lib64/libcublas.so.8.0
	/usr/local/cuda/lib64/libcurand.so.8.0
	/usr/local/cuda/lib64/libcudnn.so.7
)

修改detectnet/detectnet.cpp

const char* imgFilename = "./RQ_001_133202_0457.jpg";

修改yolo_layer.h

const int classes = 2;

创建build文件夹,进入该文件夹,执行

cmake ..
make -j8

编译完成后,进入build目录下的x86_64/bin目录,并将转化好的yolov3-voc.prototxt,yolov3-voc.caffemodel,RQ_001_133202_0457.jpg复制到该目录下,执行

./detectnet 0 yolov3-voc.prototxt yolov3-voc.caffemodel

 

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部