文章目录:
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"
}
文章目录:1、caffe里面的误差的反向传播怎么实现来的2、在VS2013中打开caffe源代码,都能编译成功,下一步应该怎么训练模型3、如何在程序中调用Caffe做图像分类,调用caffe图像分类caffe里面的误差的反向传
fier);};}//namespace #endif //CAFFE_CLASSIFIER_H构造函数中我们通过模型定义文件(.prototxt)和训练好的模型(.caffemodel)文件构造一个Net对象,并用m_layer_指向
memset(label_, 0, valid_length); m_layer_-Reset(data.data(), label_, valid_length); vectorBlobDtype* bottom
ayerSetUp函数明确几个变量:N_ = num_output;K_ = bottom[0]-count(axis);M_ = bottom[0]-count(0, axis); N_表示输出的特征维数,
pe*)net_-layers()[0].get(); batch_size_ = m_layer_-batch_size(); channels_ = m_layer_-channels(); height_ = m_layer_-height(); width_