0326----更新之前的caffe学习(一)

Segnet-Caffe C++

学习笔记

从之前的Segnet-Slam代码中设置断点进行调试:
// OpenCV
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/calib3d/calib3d.hpp>
#include <opencv2/core/eigen.hpp>
#include "opencv2/imgproc/imgproc.hpp"
//...............此处省略若干代码
// Semantic now
cv::Mat new_frame;
cv::resize(frame->rgb, new_frame, cv::Size(480,360));
cv::resize(frame->rgb,frame->rgb,cv::Size(480,360));
std::vector<Prediction> predictions = classifier.Classify(new_frame);
cv::Mat segnet(new_frame.size(), CV_8UC3, cv::Scalar(0,0,0));
/* Return the top N predictions. */
//预测函数,输入一张图片img,希望预测的前N种概率最大的,我们一般取N等于1  
//输入预测结果为std::make_pair,每个对包含这个物体的名字,及其相对于的概率
class Classifier 
{
public:
    Classifier();
    std::vector<Prediction> Classify(const cv::Mat& img, int N = 1);
    //..............省略
};//在.h文件中N已经定义为1
std::vector<Prediction> Classifier::Classify(const cv::Mat& img, int N) 
{
    std::vector<float> output = Predict(img);//将图片给Predict函数
    //【1】Predict函数进行网络的前向传输,得到输入属于每一类的概率,存储在output中
    std::vector<int> maxN = Argmax(output, N);
    std::vector<Prediction> predictions;
    predictions.reserve(input_geometry_.height * input_geometry_.width);
    for (int i = 0; i < input_geometry_.height * input_geometry_.width; ++i)
    {
        int idx = maxN[i];
        predictions.push_back(std::make_pair(labels_[idx], idx));
    }

    return predictions;
}
断点调试时GDB窗口所示图像数据:
img = {const cv::Mat &} {
 flags = {int} 1124024336
 dims = {int} 2 //dims:Mat所代表的矩阵的维度,如3*4的矩阵为2维,3*4*5的为3维
 rows = {int} 360
 cols = {int} 480
 data = {uchar * | 0x7fffafa8e020} "\363\265\203\355\262\........
 //data:Mat对象中的一个指针,指向内存中存放矩阵数据的一块内存 (uchar* data)
 refcount = {int * | 0x7fffafb0c920} 0x7fffafb0c920 //引用计数器
 datastart = {uchar * | 0x7fffafa8e020} "\363\265\203\355\262\.....
 dataend = {uchar * | 0x7fffafb0c920} "\001"
 datalimit = {uchar * | 0x7fffafb0c920} "\001"
 allocator = {cv::MatAllocator * | 0x0} NULL
 size = {cv::Mat::MSize} 
 step = {cv::Mat::MStep} 
 }

net类数据结构调试 【net类的一些说明

Net类

//通过合成和自动微分,网络同时定义了一个函数和其对应的梯度。通过合成各层的输出来计算这个函数,来执行给定的任务,
//并通过合成各层的向后传播过程来计算来自损失函数的梯度,从而学习任务。Caffe模型是端到端的机器学习引擎。
//Net是由一系列层组成的有向五环(DAG)计算图,Caffe保留了计算图中所有的中间值以确保前向和反向迭代的准确性。
//一个典型的Net开始于data layer--从磁盘中加载数据,终止于loss layer--计算分类和重构这些任务的目标函数。
//Net由一系列层和它们之间的相互连接构成,用的是一种文本建模语言(protobuf)。
//Net是通过protobuf文件来描述整个Net是怎么由layer组成的。
//Caffe中网络的构建与设备无关。网络构建完之后,通过设置Caffe::mode()函数中的Caffe::set_mode()即可
//实现在CPU或GPU上的运行。CPU与GPU无缝切换并且独立于模型定义。
//前传(forward)过程为给定的待推断的输入计算输出。在前传过程中,Caffe组合每一层的计算以得到整个模型的计算”函数”。
//本过程自底向上进行。
//反传(backward)过程根据损失来计算梯度从而进行学习。在反传过程中,Caffe通过自动求导并反向组合每一层的梯度
//来计算整个网络的梯度。这就是反传过程的本质。本过程自顶向下进行。
//反传过程以损失开始,然后根据输出计算梯度。根据链式准则,逐层计算出模型其余部分的梯度。
//有参数的层,会在反传过程中根据参数计算梯度。
//只要定义好了模型,Caffe中前传和反传的计算就可以立即进行,Caffe已经准备好了前传和反传的实现方法。
////实现方法:
//(1)、Net::Forward()和Net::Backward()方法实现网络的前传和反传,而Layer::Forward()和Layer::Backward()计算每一层的前传和后传。
//(2)、每一层都有forward_{cpu,gpu}()和backward_{cpu,gpu}方法来适应不同的计算模式。由于条件限制
//或者为了使用便利,一个层可能仅实现了CPU或者GPU模式。
//与大多数的机器学习模型一样,在Caffe中,学习是由一个损失函数驱动的(通常也被称为误差、代价或者目标函数)。
//因此,学习的目的是找到一个网络权重的集合,使得损失函数最小。
//在Caffe中,损失是通过网络的前向计算得到的。每一层由一系列的输入blobs(bottom),
//典型的一对多分类任务的损失函数是softMaxWithLoss函数。
//Loss weights:对于含有多个损失层的网络(例如,一个网络使用一个softMaxWithLoss输入分类
//并使用EuclideanLoss层进行重构),损失权值可以被用来指定它们之间的相对重要性。
//按照惯例,有着Loss后缀的Caffe层对损失函数有贡献,其它层被假定仅仅用于中间计算。
//对于带后缀Loss的层来说,其对于该层的第一个top blob含有一个隐式的loss_weight:1;其它层对应于所有top blob有一个隐式的loss_weight: 0。
//然而,任何可以反向传播的层,可允许给予一个非0的loss_weight,例如,如果需要,
//对网络的某些中间层所产生的激活进行正则化。对于具有相关非0损失的非单输出,损失函数可以通过对所有blob求和来进行简单地计算。
//那么,在Caffe中最终的损失函数可以通过对整个网络中所有的权值损失进行求和计算获得。
//为了创建一个Caffe模型,需要在一个protobuf(.prototxt)文件中定义模型的结构。
//在Caffe中,层和相应的参数都定义在caffe.proto文件里。

深度网络是组成模型,自然地表示为在大量数据上工作的互连层集合。 Caffe在其自己的模型模式中定义了一个网络层。 网络将整个模型从输入数据自下而上地定义为损失。 随着数据和派生物在前向和后向流经网络,Caffe存储,传达和操纵信息为blobblobCaffe框架的标准阵列和统一内存接口。 该层次是模型和计算的基础。 网络作为图层的收集和连接。 blob的详细信息描述了信息在层和网络中如何存储和传输。


Blob存储和通信

Blob是Caffe处理和传递的实际数据的封装,Blob是Caffe的基本数据结构,并且还提供CPU和GPU之间的同步功能。 在数学上,Blob是以C语言风格的连续方式存储的N维的数组。
Caffe使用Blob存储和传输数据。 Blob提供了保存数据的统一的存储器接口; 例如批量的图像,模型参数以及用于优化的衍生物。
Blob通过根据需要从CPU主机同步到GPU设备来隐藏混合CPU / GPU操作的计算和心理开销。 主机和设备上的内存按需分配以提高内存使用率。
批量图像数据的常规Blob尺寸是

数量N×通道K×高度H×宽度W ———-(Number N是batch size)

Blob在布局中主要以行进行存储,所以最后/最右维度变化最快。 例如,在4D blob中,index(n,k,h,w)处的值在物理上位于索引

((n * K + k)* H + h)* W + w

数量N是数据的批量大小。 批处理为通信和设备处理实现更好的吞吐量。 对于256个图像的N = 256的ImageNet训练批次。
通道K是特征维度,例如对于RGB图像K = 3

Blob会使用SyncedMem自动决定什么时候去copy data以提高运行效率

Blob同时保存了data和diff(梯度),访问data或diff有两种方法:

const Dtype* cpu_data() const; //不修改值
Dtype* mutable_cpu_data();     //修改值

Layer(层)计算和通信

Layer是模型的本质和计算的基本单位。一个Layer通过bottom 链接获取输入Blob,并通过top 链接进行数据输出。所有的Pooling,Convolve,apply,nonlinearities等操作都在这里实现。每个Layer层类型定义了三种操作setup(Layer初始化),forward(正向传导,根据input计算output),backward(反向传导计算,根据output计算input的梯度)。forward和backward有GPU和CPU两个版本的实现。
Layer结构

net_ = {boost::shared_ptr(智能指针)<caffe::Net>}  
//net类:Net由多个Layer组成。通常网络从data layer(从磁盘中载入数据)出发到loss layer结束。
  name_ = {字符串类型} "VGG_ILSVRC_16_layer"//网络的名称,从.prototxt文件中载入
  phase_ = {caffe::Phase} caffe::TEST

  layer_names_ =  //layer是Caffe的基本计算单元
   [0] =  "input"   
   [1] =  "conv1_1"
   [2] =  "conv1_1_bn"
//..................省略
   [86] = "conv1_1_D"
   [87] = "argmax"
//网络中大部分功能都是以Layer的形式去展开的,如convolute,pooling,loss等等。
//在创建一个Caffe模型的时候,也是以Layer为基础进行的,需按照src/caffe/proto/caffe.proto中定义的网络及参数格式定义网络的.prototxt文件

  blobs_ = {std::vector<boost::shared_ptr, std::allocator>} 
   [0] = {boost::shared_ptr<caffe::Blob>}  | 0x8d4fb80} 
     data_ = {boost::shared_ptr<caffe::SyncedMemory>} 
/*
    Blob在实现上是对SyncedMemory进行了一层封装。
    shape_为blob维度,data_为原始数据,diff_为梯度信息,count_为该blob的总容量(即数据的size),
    函数count(x,y)(或count(x))返回某个切片[x,y]([x,end])内容量,
    本质上就是shape[x]shape[x+1]......*shape[y]的值
*/
     diff_ = 
     shape_data_ = 
     shape_ = {std::vector<int, std::allocator>} 
     count_ = {int} 518400
     capacity_ = {int} 518400
   [1] = {boost::shared_ptr<caffe::Blob>} 

  blob_names_ = //blob作为Layer的输入数据,也作为输出数据交互
   [0] = "data"
   [1] = "conv1_1"
   [2] = "conv1_2"
   [42] ="argmax"
   [41] ="conv1_1_D"

  net_input_blobs_ = {std::vector<caffe::Blob*, std::allocator>} 
   [0] = {caffe::Blob<float> * | 0x8d4fb80} 0x8d4fb80 //---------->同于Blob[0]的地址
    data_ = {boost::shared_ptr<caffe::SyncedMemory>} //数据
      cpu_ptr_ = {void * | 0x0} NULL
      gpu_ptr_ = {void * | 0x0} NULL
      size_ = {size_t} 2073600
      head_ = {caffe::SyncedMemory::SyncedHead} caffe::SyncedMemory::UNINITIALIZED
      own_cpu_data_ = {bool} false
      cpu_malloc_use_cuda_ = {bool} false
      own_gpu_data_ = {bool} false
      gpu_device_ = {int} -1

    diff_ = {boost::shared_ptr<caffe::SyncedMemory>} //梯度信息,跟data_都是caffe::SyncedMemory

    shape_data_ = {boost::shared_ptr<caffe::SyncedMemory>} 

    shape_ = {std::vector<int, std::allocator>} //维度
     [0] = {int} 1
     [1] = {int} 3
     [2] = {int} 360
     [3] = {int} 480
    count_ = {int} 518400 //容量大小 = 3*360*480

Predict()函数

/*神经网络的前向传播函数,得到输入属于每一类的概率,存储在output中*/
std::vector<float> Classifier::Predict(const cv::Mat& img) 
{
    Blob<float>* input_layer = net_->input_blobs()[0];
//【1】input_layer是网络的输入Blob,第一个Layer
    input_layer->Reshape(1, num_channels_, input_geometry_.height, input_geometry_.width);
//【2】表示网络只输入一张图像,通道数为num,高为height,宽为width;
// 将num=1,channels,height,width传递给vector shape_ 
    /* Forward dimension change to all layers. */
    net_->Reshape();//重塑从Bottom到Top的所有Layer层,change其维度
//【3】初始化所有图层
    std::vector<cv::Mat> input_channels;
//【4】输入带预测的图片数据,然后进行预处理,包括归一化、缩放等操作
// 存储输入图像的各个通道
    WrapInputLayer(&input_channels); 
//【5】将存储输入图像的各个通道的input_channels放入网络的输入Blobs中
    Preprocess(img, &input_channels);
//【6】将img的各通道分开并存储在input_channels中
    net_->ForwardPrefilled();
//【7】进行网络的前向传导 Run Forward and return the result
    /* Copy the output layer to a std::vector */
    Blob<float>* output_layer = net_->output_blobs()[0];
//【8】output_layer指向网络输出的数据,存储网络输出数据的Blob格式是(1,c,1,1)
//【8】把最后一层输出值,保存到vector中,结果就是返回每个类的概率 
    const float* begin = output_layer->cpu_data();
//【9】begin指向输入数据对应的第一类的概率
//【10】end指向输入数据对应的最后一类的概率
    const float* end = begin + output_layer->height() * output_layer->width() * output_layer->channels();
    return std::vector<float>(begin, end);
//【11】返回输入数据经过网络前向计算后输出的对应于各个类的分数
}

//将网络的输入层封装在单独的cv::Mat对象中(每个通道一个),这样我们节省了一个memcpy操作,
//我们不需要依赖cudaMemcpy2D。最后的预处理操作将直接将单独通道写入输入层。
void Classifier::WrapInputLayer(std::vector<cv::Mat>* input_channels) 
{ //此处相当于将三个通道的数据依次存储在Mat类input—_channels中
    Blob<float>* input_layer = net_->input_blobs()[0];//指向网络输入的Blob
    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);
//将网络输入的Blob的数据同Mat类channel关联起来
        input_channels->push_back(channel);
//将Mat类channel同input_channels关联起来
        input_data += width * height; }
}
/*  将彩色RGB图转换为灰度图;函数cvtColor可以将一个图像从一个颜色空间转换到另一个颜色空间
    cv::Mat convertimg ;
    cv::cvtColor(img,convertimg,CV_RGB2GRAY);
    cv::imshow("oldimg",img);
    cv::imshow("cvtColor",convertimg);
*/

Blob使用SyncedMem类来同步CPU和GPU之间的值,以隐藏同步细节并最大限度地减少数据传输。一个经验法则是,如果你不想改变这些值,总是使用const调用,并且不要将指针存储在你自己的对象中。每次你在一个blob上工作时,调用函数来获取指针,因为SyncedMem需要这个来确定何时复制数据。实际上,当GPU存在时,会将数据从磁盘加载到CPU代码中的Blob中,调用设备内核来执行GPU计算,并将Blob传送到下一层,忽略低级细节,同时保持高性能水平。只要所有图层都具有GPU实现,所有中间数据和渐变将保留在GPU中。

如果想查看Blob何时会复制数据,请看下面的例子:

// Assuming that data are on the CPU initially, and we have a blob.
const Dtype* foo;
Dtype* bar;
foo = blob.gpu_data(); // data copied cpu->gpu.
foo = blob.cpu_data(); // no data copied since both have up-to-date contents.
//没有数据复制因为两者都有最新的内容
bar = blob.mutable_gpu_data(); // no data copied.
// ... some operations ...
bar = blob.mutable_gpu_data(); // no data copied when we are still on GPU.
foo = blob.cpu_data(); // data copied gpu->cpu, since the gpu side has modified the data
//数据复制gpu-> cpu,因为gpu端已经修改了数据
foo = blob.gpu_data(); // no data copied since both have up-to-date contents
bar = blob.mutable_cpu_data(); // still no data copied.
bar = blob.mutable_gpu_data(); // data copied cpu->gpu.
bar = blob.mutable_cpu_data(); // data copied gpu->cpu.
  • Caffe
  • Segnet
  • 未完待续。。。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值