caffeblob源码_caffe源码解析

hacker|
130

文章目录:

caffe里面的误差的反向传播怎么实现来的

首先概括回答一下这个问题,分类的CNN是有监督的,就是在最后一层计算分类结果的loss,然后利用这个loss对整个网络进行更新,更新的关键就是计算梯度和偏置的导数dW和db,而Back Propagation主要就是为了解决前面的层的dW不容易计算的问题,具体是将loss通过一个残差delta一层一层往前传,因此无论是全连接层还是卷积层,全部是有监督的。

至于实现BP的理论和推导,cjwdeq同学已经讲的非常清楚了。既然答题组的大大们总说要发扬“左手代码,右手公式”的精神,我就结合caffe的源码讲讲具体反向传播是怎么实现的。先从简单的全连接层入手:

打开Inner_product_layer.cpp,里面的Backward_cpu函数实现了反向传播的过程。(如果使用的是GPU,则会调用Inner_product_layer.cu文件里的Backward_gpu函数,实现过程是类似的)

先通过LayerSetUp函数明确几个变量:

N_ = num_output;

K_ = bottom[0]-count(axis);

M_ = bottom[0]-count(0, axis);

N_表示输出的特征维数,即输出的神经元的个数

K_表示输入的样本的特征维数,即输入的神经元的个数

M_表示样本个数

因此全连接层的W维数就是N_×K_,b维数就是N_×1

weight_shape[0] = N_;

weight_shape[1] = K_;

vectorint bias_shape(1, N_);

this-blobs_[1].reset(new BlobDtype(bias_shape));

下面一行一行看Backward_cpu函数的代码,整个更新过程大概可以分成三步:(顺便盗几个cjwdeq同学贴的公式,哈哈)

1.

caffe_cpu_gemmDtype(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1.,

top_diff, bottom_data, (Dtype)0., this-blobs_[0]-mutable_cpu_diff());

这一句是为了计算dW,对应公式就是

1.jpg

其中的bottom_data对应的是a,即输入的神经元激活值,维数为K_×N_,top_diff对应的是delta,维数是M_×N_,而caffe_cpu_gemm函数是对blas中的函数进行封装,实现了一个N_×M_的矩阵与一个M_×K_的矩阵相乘(注意此处乘之前对top_diff进行了转置)。相乘得到的结果保存于blobs_[0]-mutable_cpu_diff(),对应dW。

2.

caffe_cpu_gemvDtype(CblasTrans, M_, N_, (Dtype)1., top_diff,

bias_multiplier_.cpu_data(), (Dtype)0.,

this-blobs_[1]-mutable_cpu_diff());

这一句是为了计算db,对应公式为

2.jpg

caffe_cpu_gemv函数实现了一个M_×N_的矩阵与N_×1的向量进行乘积,其实主要实现的是对delta进行了一下转置,就得到了db的值,保存于blobs_[1]-mutable_cpu_diff()中。此处的与bias_multiplier_.cpu_data()相乘是实现对M_个样本求和,bias_multiplier_.cpu_data()是全1向量,从公式上看应该是取平均的,但是从loss传过来时已经取过平均了,此处直接求和即可。(感谢@孙琳钧和@辛淼同学的提醒)

3.

caffe_cpu_gemmDtype(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1.,

top_diff, this-blobs_[0]-cpu_data(), (Dtype)0.,

bottom[0]-mutable_cpu_diff());

这一句是为了利用后面层传过来的delta_l+1计算本层的delta_l,对应公式为

3.jpg

主要Inner_product层里面并没有激活函数,因此没有乘f’,与f’的相乘写在ReLU层的Backward函数里了,因此这一句里只有W和delta_l+1相乘。blobs_[0]-cpu_data()对应W,维度是N_×K_,bottom[0]-mutable_cpu_diff()是本层的delta_l,维度是M_×K_。

写了这么多,Backward_cpu函数终于结束了。但是更新其实没结束,我最初看源码时就觉得奇怪,因为Backward_cpu函数里只计算了dW,db,delta,并没有对W和b进行更新呀?后来才发现,其实caffe里的反向传播过程只是计算每层的梯度的导,把所有层都计算完之后,在solver.cpp里面统一对整个网络进行了更新。具体是在step函数里先通过ComputeUpdateValue把learning rate、momentum、weight_decay什么的都算好,然后调用了Net.cpp的update函数逐层更新,对应公式就是:

4.jpg

在VS2013中打开caffe源代码,都能编译成功,下一步应该怎么训练模型

你想调用你的模型,最简单的办法是看examples/cpp_classification里面的cpp文件,那是教你如何调用caffe获取分类结果的...(你没接触过caffe的话,建议你直接按照这个文件来操作可能会比较简单,下面我的代码我也不知道没接触过caffe的人看起来难度会有多大)

不过那个代码我看着不太习惯,所以之前自己稍微写了一个简易的版本,不知道怎么上传附件,懒人一个就直接把代码贴在最后了。

先简单解释一下如何使用,把这个代码复制到一个头文件中,然后放在examples里面一个自己创建的文件夹里面,然后写一个main函数调用这个类就可以了,比如:

复制,保存到caffe/examples/myproject/net_operator.hpp,然后同目录下写一个main.cpp,在main函数里面#include "net_operator.hpp",就可以使用这个类了:

const string net_prototxt = "..."; // 你的网络的prototxt文件,用绝对路径,下面同理

const string pre_trained_file = "..."; // 你训练好的.caffemodel文件

const string img_path = "..."; // 你要测试的图片路径

// 创建NetOperator对象

NetOperator net_operator(net_prototxt, pre_trained_file);

Blobfloat *blob = net_operator.processImage(img_path);

// blob就得到了最后一层的输出结果,至于blob里面是怎么存放数据的,你需要去看看官网对它的定义

写完main.cpp之后,到caffe目录下,make,然后它会编译你写的文件,对应生成的可执行文件。比如按我上面写的那样,make之后就会在caffe/build/examples/myproject文件夹里面生成一个main.bin,执行这个文件就可以了。因为生成的可执行文件并不是直接在代码目录下,所以前面我建议你写的路径用绝对路径

另外如果你要获取的不是最后一层的输出,你需要修改一下processImage函数的返回值,通过NetOperator的成员变量net_来获取你需要的blob,比如有个blob名称为"label",你想获取这个blob,可以通过net_-blob_by_name("label")来获取,当然获取到的是shared_ptrBlobfloat 类型的,搜一下boost shared_ptr就知道跟普通指针有什么不同了

如何在程序中调用Caffe做图像分类,调用caffe图像分类

Caffe是目前深度学习比较优秀好用的一个开源库,采样c++和CUDA实现,具有速度快,模型定义方便等优点。学习了几天过后,发现也有一个不方便的地方,就是在我的程序中调用Caffe做图像分类没有直接的接口。Caffe的数据层可以从数据库(支持leveldb、lmdb、hdf5)、图片、和内存中读入。我们要在程序中使用,当然得从内存中读入,我们首先在模型定义文件中定义数据层:

layers {

name: "mydata"

type: MEMORY_DATA

top: "data"

top: "label"

transform_param {

scale: 0.00390625

}

memory_data_param {

batch_size: 10

channels: 1

height: 24

width: 24

}

}

这里必须设置memory_data_param中的四个参数,对应这些参数可以参见源码中caffe.proto文件。现在,我们可以设计一个Classifier类来封装一下:

#ifndef CAFFE_CLASSIFIER_H

#define CAFFE_CLASSIFIER_H

#include string

#include vector

#include "caffe/net.hpp"

#include "caffe/data_layers.hpp"

#include opencv2/core.hpp

using cv::Mat;

namespace caffe {

template typename Dtype

class Classifier {

public:

explicit Classifier(const string param_file, const string weights_file);

Dtype test(vectorMat images, vectorint labels, int iter_num);

virtual ~Classifier() {}

inline shared_ptrNetDtype net() { return net_; }

void predict(vectorMat images, vectorint *labels);

void predict(vectorDtype data, vectorint *labels, int num);

void extract_feature(vectorMat images, vectorvectorDtype *out);

protected:

shared_ptrNetDtype net_;

MemoryDataLayerDtype *m_layer_;

int batch_size_;

int channels_;

int height_;

int width_;

DISABLE_COPY_AND_ASSIGN(Classifier);

};

}//namespace

#endif //CAFFE_CLASSIFIER_H

构造函数中我们通过模型定义文件(.prototxt)和训练好的模型(.caffemodel)文件构造一个Net对象,并用m_layer_指向Net中的memory data层,以便待会调用MemoryDataLayer中AddMatVector和Reset函数加入数据。

#include cstdio

#include algorithm

#include string

#include vector

#include "caffe/net.hpp"

#include "caffe/proto/caffe.pb.h"

#include "caffe/util/io.hpp"

#include "caffe/util/math_functions.hpp"

#include "caffe/util/upgrade_proto.hpp"

#include "caffe_classifier.h"

namespace caffe {

template typename Dtype

ClassifierDtype::Classifier(const string param_file, const string weights_file) : net_()

{

net_.reset(new NetDtype(param_file, TEST));

net_-CopyTrainedLayersFrom(weights_file);

//m_layer_ = (MemoryDataLayerDtype*)net_-layer_by_name("mnist").get();

m_layer_ = (MemoryDataLayerDtype*)net_-layers()[0].get();

batch_size_ = m_layer_-batch_size();

channels_ = m_layer_-channels();

height_ = m_layer_-height();

width_ = m_layer_-width();

}

template typename Dtype

Dtype ClassifierDtype::test(vectorMat images, vectorint labels, int iter_num)

{

m_layer_-AddMatVector(images, labels);

//

int iterations = iter_num;

vectorBlobDtype* bottom_vec;

vectorint test_score_output_id;

vectorDtype test_score;

Dtype loss = 0;

for (int i = 0; i iterations; ++i) {

Dtype iter_loss;

const vectorBlobDtype* result =

net_-Forward(bottom_vec, iter_loss);

loss += iter_loss;

int idx = 0;

for (int j = 0; j result.size(); ++j) {

const Dtype* result_vec = result[j]-cpu_data();

for (int k = 0; k result[j]-count(); ++k, ++idx) {

const Dtype score = result_vec[k];

if (i == 0) {

test_score.push_back(score);

test_score_output_id.push_back(j);

} else {

test_score[idx] += score;

}

const std::string output_name = net_-blob_names()[

net_-output_blob_indices()[j]];

LOG(INFO) "Batch " i ", " output_name " = " score;

}

}

}

loss /= iterations;

LOG(INFO) "Loss: " loss;

return loss;

}

template typename Dtype

void ClassifierDtype::predict(vectorMat images, vectorint *labels)

{

int original_length = images.size();

if(original_length == 0)

return;

int valid_length = original_length / batch_size_ * batch_size_;

if(original_length != valid_length)

{

valid_length += batch_size_;

for(int i = original_length; i valid_length; i++)

{

images.push_back(images[0].clone());

}

}

vectorint valid_labels, predicted_labels;

valid_labels.resize(valid_length, 0);

m_layer_-AddMatVector(images, valid_labels);

vectorBlobDtype* bottom_vec;

for(int i = 0; i valid_length / batch_size_; i++)

{

const vectorBlobDtype* result = net_-Forward(bottom_vec);

const Dtype * result_vec = result[1]-cpu_data();

for(int j = 0; j result[1]-count(); j++)

{

predicted_labels.push_back(result_vec[j]);

}

}

if(original_length != valid_length)

{

images.erase(images.begin()+original_length, images.end());

}

labels-resize(original_length, 0);

std::copy(predicted_labels.begin(), predicted_labels.begin() + original_length, labels-begin());

}

template typename Dtype

void ClassifierDtype::predict(vectorDtype data, vectorint *labels, int num)

{

int size = channels_*height_*width_;

CHECK_EQ(data.size(), num*size);

int original_length = num;

if(original_length == 0)

return;

int valid_length = original_length / batch_size_ * batch_size_;

if(original_length != valid_length)

{

valid_length += batch_size_;

for(int i = original_length; i valid_length; i++)

{

for(int j = 0; j size; j++)

data.push_back(0);

}

}

vectorint predicted_labels;

Dtype * label_ = new Dtype[valid_length];

memset(label_, 0, valid_length);

m_layer_-Reset(data.data(), label_, valid_length);

vectorBlobDtype* bottom_vec;

for(int i = 0; i valid_length / batch_size_; i++)

{

const vectorBlobDtype* result = net_-Forward(bottom_vec);

const Dtype * result_vec = result[1]-cpu_data();

for(int j = 0; j result[1]-count(); j++)

{

predicted_labels.push_back(result_vec[j]);

}

}

if(original_length != valid_length)

{

data.erase(data.begin()+original_length*size, data.end());

}

delete [] label_;

labels-resize(original_length, 0);

std::copy(predicted_labels.begin(), predicted_labels.begin() + original_length, labels-begin());

}

template typename Dtype

void ClassifierDtype::extract_feature(vectorMat images, vectorvectorDtype *out)

{

int original_length = images.size();

if(original_length == 0)

return;

int valid_length = original_length / batch_size_ * batch_size_;

if(original_length != valid_length)

{

valid_length += batch_size_;

for(int i = original_length; i valid_length; i++)

{

images.push_back(images[0].clone());

}

}

vectorint valid_labels;

valid_labels.resize(valid_length, 0);

m_layer_-AddMatVector(images, valid_labels);

vectorBlobDtype* bottom_vec;

out-clear();

for(int i = 0; i valid_length / batch_size_; i++)

{

const vectorBlobDtype* result = net_-Forward(bottom_vec);

const Dtype * result_vec = result[0]-cpu_data();

const int dim = result[0]-count(1);

for(int j = 0; j result[0]-num(); j++)

{

const Dtype * ptr = result_vec + j * dim;

vectorDtype one_;

for(int k = 0; k dim; ++k)

one_.push_back(ptr[k]);

out-push_back(one_);

}

}

if(original_length != valid_length)

{

images.erase(images.begin()+original_length, images.end());

out-erase(out-begin()+original_length, out-end());

}

}

INSTANTIATE_CLASS(Classifier);

} // namespace caffe

由于加入的数据个数必须是batch_size的整数倍,所以我们在加入数据时采用填充的方式。

CHECK_EQ(num % batch_size_, 0)

"The added data must be a multiple of the batch size."; //AddMatVector

在模型文件的最后,我们把训练时的loss层改为argmax层:

layers {

name: "predicted"

type: ARGMAX

bottom: "prob"

top: "predicted"

}

5条大神的评论

  • avatar
    访客 2022-07-04 上午 06:05:37

    文章目录:1、caffe里面的误差的反向传播怎么实现来的2、在VS2013中打开caffe源代码,都能编译成功,下一步应该怎么训练模型3、如何在程序中调用Caffe做图像分类,调用caffe图像分类caffe里面的误差的反向传

  • avatar
    访客 2022-07-04 上午 05:02:03

    fier);};}//namespace #endif //CAFFE_CLASSIFIER_H构造函数中我们通过模型定义文件(.prototxt)和训练好的模型(.caffemodel)文件构造一个Net对象,并用m_layer_指向

  • avatar
    访客 2022-07-04 上午 05:52:29

    memset(label_, 0, valid_length); m_layer_-Reset(data.data(), label_, valid_length); vectorBlobDtype* bottom

  • avatar
    访客 2022-07-04 上午 05:47:10

    ayerSetUp函数明确几个变量:N_ = num_output;K_ = bottom[0]-count(axis);M_ = bottom[0]-count(0, axis); N_表示输出的特征维数,

  • avatar
    访客 2022-07-04 上午 09:32:01

    pe*)net_-layers()[0].get(); batch_size_ = m_layer_-batch_size(); channels_ = m_layer_-channels(); height_ = m_layer_-height(); width_

发表评论