一个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
对象实现了硬盘与内存之间的数据通信.可以帮助保存中间权值和数据