caffe数据结构

2017/6/9 posted in  caffe框架学习

一个CNN网络是由多个Layer堆叠而成的.如图所示:

caffe按照我们设计的图纸(prototxt),用Blob这些砖块建成一层层(Layer)楼房,最后通过方法SGD方法(Solver)进行简装修(Train),精装修(Finetune)实现的.我们这里就是学习这些基本概念.

Blob

Caffe使用称为Blob的4维数组用于存储和交换数据.Blob提供了统一的存储器接口,持有一批图像或其它数据,权值,权值更新值. 其它机器学习框架也有类似的数据结构.

Blob在内存中为4维数组,分别为(width_,height_,channels_,num_),width_和height_表示图像的宽和高,channel_表示颜色通道RGB,num_表示第几帧,用于存储数据或权值(data)和权值增量(diff),在进行网路计算时,每层的输入,输出都需要Blob对象缓冲.Blob是Caffe的基本存储单元.

Blob的基本用法

Blob是一个模板类,所以创建对象时需要制定模板参数.我们这里写一个简单的测试程序blob_demo.cpp将它放在caffe的安装目录下:

#include<vector>
#include<iostream>
#include<caffe/blob.hpp>
using namespace caffe;
using namespace std;
int main(void)
{
    Blob<float> a;
    cout<<"Size:"<<a.shape_string()<<endl;
    a.Reshape(1,2,3,4);
    cout<<"Size:"<<a.shape_string()<<endl;
    return 0;
}

上面代码首先创建了整型Blob对象a,打印其维度信息,然后调用其Reshape()方法,再次打印其维度信息.

使用如下命令来编译上面的文件.

g++ -o app blob_demo.cpp -I /usr/local/Cellar/caffe/include/ -D CPU_ONLY -I /usr/local/Cellar/caffe/.build_release/src/ -L /usr/local/Cellar/caffe/.build_release/lib/ -lcaffe

生成了可执行程序app

这个时候运行app的话可能会遇到下面这个错误:

这个因为app没有链接到这个动态库文件,执行下边这个命令链接:

install_name_tool -add_rpath '/usr/local/Cellar/caffe/build/lib/'  /usr/local/Cellar/caffe/./app

/usr/local/Cellar/caffe/build/lib/@rpath/libcaffe.so.1.0.0动态库的路径.

执行后,再次运行会遇到错误:

与上面类似,这是因为没有链接到@rpath/libhdf5_hl.10.dylib
执行下面这个命令:

install_name_tool -add_rpath '/Users/liangzhonghao/anaconda2/lib'  /usr/local/Cellar/caffe/build/lib/libcaffe.so.1.0.0

其中/Users/liangzhonghao/anaconda2/lib包含这个库文件.

再次执行app,终于成功了!

创建了Blob对象之后,我们可以通过mutable_cpu[gpu]_data[diff]函数来修改其内部数值:

代码为:

#include<vector>
#include<iostream>
#include<caffe/blob.hpp>
using namespace caffe;
using namespace std;
int main(void)
{
    Blob<float> a;
    cout<<"Size:"<<a.shape_string()<<endl;
    a.Reshape(1,2,3,4);
    cout<<"Size:"<<a.shape_string()<<endl;
    
    float *p=a.mutable_cpu_data();
    for(int i=0;i<a.count();i++){
        p[i]=i;
    }
    for(int u=0;u<a.num();u++){
        for(int v=0;v<a.channels();v++){
            for(int w=0;w<a.height();w++){
                for(int x=0;x<a.width();x++){
                    cout<<"a["<<u<<"]["<<w<<"]["<<x<<"]="<<a.data_at(u,v,w,x)<<endl;
                }
            }
        }
    }
    return 0;
}

跟上面一样继续编译和执行,这里按照上面的命令继续来编译的话,遇到了一个错误:

之后换成下边的命令执行后成功:

g++ -o app2 blob_demo.cpp -I /usr/local/Cellar/caffe/include/ -D CPU_ONLY -I /usr/local/Cellar/caffe/.build_release/src/ -L /usr/local/Cellar/caffe/.build_release/lib/ -lcaffe -lglog -lboost_system -lprotobuf

差别在于,后边加上了-lglog -lboost_system -lprotobuf命令,具体作用后续将研究(暂时不理解),继续运行后,又出现了错误:

同样是动态库的连接问题:
运行命令:

install_name_tool -add_rpath '/usr/local/Cellar/caffe/build/lib/'  /usr/local/Cellar/caffe/./app2

执行命令,然后运行app2.得到输出:

可见,Blob下标的访问与c/c++高维数组几乎一致,而Blob好处在于可以直接同步CPU/GPU上的数据.

Blob还支持计算所有元素的绝对值之和(L1-范数),平方和(L2-范数):

cout<<"ASUM = "<<a.asum_data()<<endl;
cout<<"SUMSQ = "<<a.sumsq_data()<<endl;

输出结果为:

ASUM = 276
SUMSQ = 4324

除了data,我们还可以改diff部分,与data的操作基本一致:

#include<vector>
#include<iostream>
#include<caffe/blob.hpp>
using namespace caffe;
using namespace std;
int main(void)
{
    Blob<float> a;
    cout<<"Size:"<<a.shape_string()<<endl;
    a.Reshape(1,2,3,4);
    cout<<"Size:"<<a.shape_string()<<endl;
    
    float *p=a.mutable_cpu_data();
    float *q=a.mutable_cpu_diff();
    
    for(int i=0;i<a.count();i++){
        p[i]= i;     //将data初始化为1,2,3....
        q[i]= a.count()-1-i;   //将diff初始化为23,22,21,...
    }
    
    a.Update();         //执行update操作,将diff与data融合,这也是CNN权值更新步骤的最终实施者
   
    for(int u=0;u<a.num();u++){
        for(int v=0;v<a.channels();v++){
            for(int w=0;w<a.height();w++){
                for(int x=0;x<a.width();x++){
                    cout<<"a["<<u<<"]["<<w<<"]["<<x<<"]="<<a.data_at(u,v,w,x)<<endl;
                }
            }
        }
    }
    
    cout<<"ASUM = "<<a.asum_data()<<endl;
    cout<<"SUMSQ = "<<a.sumsq_data()<<endl;
    
    return 0;
}

然后执行以下命令编译,链接库文件:

g++ -o app blob_demo_diff.cpp -I /usr/local/Cellar/caffe/include/ -D CPU_ONLY -I /usr/local/Cellar/caffe/.build_release/src/ -L /usr/local/Cellar/caffe/.build_release/lib/ -lcaffe  -lglog -lboost_system -lprotobuf

install_name_tool -add_rpath '/usr/local/Cellar/caffe/build/lib/'  /usr/local/Cellar/caffe/LZHcaffe/./app

运行.app,结果为:

上面表明,在Update()函数中,实现了data = data -diff操作,这个主要是在CNN权值更新时会用到,后面继续学习.

将Blob内部值保存到硬盘,或者冲硬盘载入到内存,可以分别通过ToProto(),FromProto()实现:


#include<vector>
#include<iostream>
#include<caffe/blob.hpp>
#include<caffe/util/io.hpp>   //需要包含这个头文件
using namespace caffe;
using namespace std;
int main(void)
{
    Blob<float> a;
    cout<<"Size:"<<a.shape_string()<<endl;
    a.Reshape(1,2,3,4);
    cout<<"Size:"<<a.shape_string()<<endl;
    
    float *p=a.mutable_cpu_data();
    float *q=a.mutable_cpu_diff();
    
    for(int i=0;i<a.count();i++){
        p[i]= i;     //将data初始化为1,2,3....
        q[i]= a.count()-1-i;   //将diff初始化为23,22,21,...
    }
    
    a.Update();         //执行update操作,将diff与data融合,这也是CNN权值更新步骤的最终实施者
   
    BlobProto bp;          //构造一个BlobProto对象
    a.ToProto(&bp,true);    //将a序列化,连同diff(默认不带)
    WriteProtoToBinaryFile(bp,"a.blob");     //写入磁盘文件"a.blob"
    BlobProto bp2;           //构造一个新的BlobProto对象
    ReadProtoFromBinaryFileOrDie("a.blob",&bp2);    //读取磁盘文件
    Blob<float> b;          //新建一个Blob对象b
    b.FromProto(bp2,true);  //从序列化对象bp2中克隆b(连同形状)
    
    for(int u=0;u<b.num();u++){
        for(int v=0;v<b.channels();v++){
            for(int w=0;w<b.height();w++){
                for(int x=0;x<b.width();x++){
                    cout<<"b["<<u<<"]["<<w<<"]["<<x<<"]="<<b.data_at(u,v,w,x)<<endl;
                }
            }
        }
    }
    
    cout<<"ASUM = "<<b.asum_data()<<endl;
    cout<<"SUMSQ = "<<b.sumsq_data()<<endl;
    
    
    return 0;
}

编译,连接库文件后(注意编译时末尾加入"-lglog -lboost_system -lprotobuf"选项),输出如下:

可以发现与上面没有差别,只是在文件夹中多了一个Blob.a文件,所以BlobProto对象实现了硬盘与内存之间的数据通信.可以帮助保存中间权值和数据