讲解caffe的网络定义文件, 开启caffe的源码阅读系列
Protocol Buffers
Protocol Buffers
是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了C++、Java、Python
三种语言的 API。
Protobuf的优点
Protobuf 有如 XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。
Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。
使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。
Protobuf的不足
Protbuf 与 XML 相比也有不足之处。它功能简单,无法用来表示复杂的概念。
XML 已经成为多种行业标准的编写工具,Protobuf 只是 Google 公司内部使用的工具,在通用性上还差很多。
由于文本并不适合用来描述数据结构,所以 Protobuf 也不适合用来对基于文本的标记文档(如 HTML)建模。另外,由于 XML 具有某种程度上的自解释性,它可以被人直接读取编辑,在这一点上 Protobuf 不行,它以二进制的方式存储,除非你有 .proto 定义,否则你没法直接读出 Protobuf 的任何内容
.proto
caffe.proto
位于…\src\caffe\proto目录下,在这个文件夹下还有一个.pb.cc和一个.pb.h文件,这两个文件都是由caffe.proto编译而来的。
caffe.proto的内容
在caffe.proto中定义了很多结构化数据,包括:
- BlobProto
- Datum
- FillerParameter
- NetParameter
- SolverParameter
- SolverState
- LayerParameter
- ConcatParameter
- ConvolutionParameter
- DataParameter
- DropoutParameter
- HDF5DataParameter
- HDF5OutputParameter
- ImageDataParameter
- InfogainLossParameter
- InnerProductParameter
- LRNParameter
- MemoryDataParameter
- PoolingParameter
- PowerParameter
- WindowDataParameter
- V0LayerParameter
.proto
文件定义我们程序中需要处理的结构化数据,在protobuf
的术语中,结构化数据被称为Message
.proto
文件的文件名命名规则为packageName.MessageName.proto
, 比如caffe.protorepeated
表示必选的数据,optional
表示可选的数据, 后面紧跟数据类型protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/caffe.proto
编译.proto文件, 命令将生成两个文件:caffe.pb.h
定义了 C++ 类的头文件和caffe.pb.cc
C++ 类的实现文件, 在生成的头文件中,定义了一个C++类caffe,后面的 Writer 和 Reader 将使用这个类来对消息进行操作。诸如对消息的成员进行赋值,将消息序列化等等都有相应的方法。查看 caffe.pb.h内容
查看 caffe.pb.cc编写Writer将把一个结构化数据写入磁盘,以便其他人来读取, Writer 需要处理的结构化数据由 .proto 文件描述,经过上一节中的编译过程后,该数据化结构对应了一个C++的类,并定义在 caffe.pb.h 中, Writer 需要 include 该头文件,然后便可以使用这个类, 在 Writer 代码中,将要存入磁盘的结构化数据由一个 caffe 类的对象表示,它提供了一系列的
get/set
函数用来修改和读取结构化数据中的数据成员,或者叫field
, 当我们需要将该结构化数据保存到磁盘上时,caffe类
已经提供相应的方法来把一个复杂的数据变成一个字节序列,我们可以将这个字节序列写入磁盘。
caffe.pb.cc
其实caffe.pb.cc里面的东西都是从caffe.proto编译而来的,无非就是一些关于这些数据结构(类)的标准化操作,比如
1 | void CopyFrom(); |
我们用caffe.prototxt定义了网络的各个层, 层与层之间的数据流动是以Blobs的数据结构传递的, 下面我们从作为入口的数据层开始介绍
数据层
数据层是每个模型的最底层,是模型的入口,不仅提供数据的输入,也提供数据从Blobs转换成别的格式进行保存输出。通常数据的预处理(如减去均值, 放大缩小, 裁剪和镜像等),也在这一层设置参数实现。
1 | layer { |
data 与 label:
在数据层中,至少有一个命名为data的top。如果有第二个top,一般命名为label。 这种(data,label)配对是分类模型所必需的。
data_param
部分根据数据的来源不同,来进行不同的设置
视觉层
视觉层(Vision Layers)包括Convolution, Pooling, Local Response Normalization (LRN), im2col等层。
- 卷积层(Convolution) : 卷积神经网络(CNN)的核心层
1 | layer { |
- 池化层(Pooling) : 为了减少运算量和数据维度而设置的一种层
1 | layer { |
- 局部区域归一化(LRN) : 对一个输入的局部区域进行归一化,达到“侧抑制”的效果, 参考AlexNet或GoogLenet
1 | layers { |
查看LRN归一化公式
im2col层
该操作先将一个大矩阵,重叠地划分为多个子矩阵,对每个子矩阵序列化成向量,最后得到另外一个矩阵
在caffe中,卷积运算就是先对数据进行im2col操作,再进行内积运算(inner product)。这样做,比原始的卷积操作速度更快
激活层
在激活层中,选用不同的激活函数对输入数据逐元素进行激活操作, 本质上是一种函数变换
- Sigmoid
1 | layer { |
- TanH
1 | layer { |
- ReLU族
1 | layer { |
negative_slope
:默认为0. 对标准的ReLU函数进行变化,如果设置了这个值,那么数据为负数时,就不再设置为0,而是用原始数据乘以negative_slopeAbsolute Value : 求每个输入数据的绝对值
1 | layer { |
- Power : 对每个输入数据进行幂运算
定义如下:
$$f(x) = (shift + {scale} * {x})^{power} $$
1 | layer { |
- BNLL
定义如下:
$$ f(x) = log(1 + e^x) $$
1 | layer { |
常用层
- Softmax : softmax是计算的是类别的概率(Likelihood),是Logistic Regression 的一种推广
1 | layers { |
- Softmax-Loss :
1 | layer { |
不管是softmax layer还是softmax-loss layer,都是没有参数的,只是层类型不同
Inner Product :
1 | layer { |
全连接层实际上也是一种卷积层,只是它的卷积核大小和原数据大小一致, 因此它的参数基本和卷积层的参数一样。
accuracy : 输出预测精确度,只有test阶段才有,需要加入include参数
1 | layer { |
- reshape : 在不改变数据的情况下,改变输入的维度
1 | layer { |
- Dropout : 以某种概率随机地让网络某些隐含层节点的权重失活
1 | layer { |