caffe-数据&模型-模型输出log

当执行训练后:

1
2
~/caffe-master/build/tools/caffe train \
--solver=/media/junhui/DATA/caffe_workspace/my_linearReggresion/lr_solver.prototxt

训练过程会在终端打印,终端日志信息以glog的格式输出:这个格式包括当前时间,进程号,源码行号,代码行号,以及输出信息,这个信息用于观察网络当前执行到哪一步。来分析一下使用Linear Reggresion对mnist分类,这个例子虽小,但五脏俱全。在必要的地方用做了注释,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
Reggresion/lr_solver.prototxt 
I0610 04:53:35.880447 13919 caffe.cpp:204] Using GPUs 0
I0610 04:53:35.903647 13919 caffe.cpp:209] GPU 0: GeForce GTX 1050
# 解析训练超参数文件lr_solver.prototxt,并初始化solver
I0610 04:53:36.096128 13919 solver.cpp:45] Initializing solver from parameters:
test_iter: 100
test_interval: 500
base_lr: 0.01
max_iter: 10000
lr_policy: "inv"
gamma: 0.0001
power: 0.75
momentum: 0.9
weight_decay: 0.0005
snapshot: 5000
snapshot_prefix: "my_lr"
solver_mode: GPU
device_id: 0
net: "/media/junhui/DATA/caffe_workspace/my_linearReggresion/mylr.prototxt"
train_state {
level: 0
stage: ""
}

# ~~~~~~~~~~~~~~~~~训练网络构建开始~~~~~~~~~~~~~~~~~
# 创建蓝图中的Net
I0610 04:53:36.096751 13919 solver.cpp:102] Creating training net from net file: /media/junhui/DATA/caffe_workspace/my_linearReggresion/mylr.prototxt
# 这里指出,用于TEST的数据层和accuracy层,的phase值为“TEST”,将不在“TRAIN”阶段使用
I0610 04:53:36.097002 13919 net.cpp:296] The NetState phase (0) differed from the phase (1) specified by a rule in layer mnist
I0610 04:53:36.097012 13919 net.cpp:296] The NetState phase (0) differed from the phase (1) specified by a rule in layer accuracy
# 解析网络结构参数文件mylr.prototxt,并初始化用作TRAIN的Net
I0610 04:53:36.097085 13919 net.cpp:53] Initializing net from parameters:
# 这里的3层layer堆叠起来才是用于训练的网络,从下面看,TEST网络就很清晰了
name: "lrNet"
state {
phase: TRAIN
level: 0
stage: ""
}
# 1. 数据层,生成两个top: LMDB->“data”&“label”
layer {
name: "mnist"
type: "Data"
top: "data"
top: "label"
include {
phase: TRAIN
}
transform_param {
scale: 0.0039063
}
data_param {
source: "/media/junhui/DATA/caffe_workspace/my_linearReggresion/mnist_train_lmdb"
batch_size: 64
backend: LMDB
}
}
# 2. 全连接层,"data"->"ip"
layer {
name: "ip"
type: "InnerProduct"
bottom: "data"
top: "ip"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param {
num_output: 10
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
# 3. softmax 层:"ip"&"label"->"loss"
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip"
bottom: "label"
top: "loss"
}
# 开始训练
# 从LMDB文件中读取训练数据,
I0610 04:53:36.097178 13919 layer_factory.hpp:77] Creating layer mnist
I0610 04:53:36.097410 13919 db_lmdb.cpp:35] Opened lmdb /media/junhui/DATA/caffe_workspace/my_linearReggresion/mnist_train_lmdb
# 1. 创建数据层,产生两个数据对象,“data”&“label”
I0610 04:53:36.097434 13919 net.cpp:86] Creating Layer mnist
I0610 04:53:36.097456 13919 net.cpp:382] mnist -> data
I0610 04:53:36.097476 13919 net.cpp:382] mnist -> label
# 数据行输出的大小为[64,1,28,28]
I0610 04:53:36.098232 13919 data_layer.cpp:45] output data size: 64,1,28,28
I0610 04:53:36.099445 13919 net.cpp:124] Setting up mnist
I0610 04:53:36.099474 13919 net.cpp:131] Top shape: 64 1 28 28 (50176)
I0610 04:53:36.099484 13919 net.cpp:131] Top shape: 64 (64)
# 统计内存占用,这个值会在train过程中累积
I0610 04:53:36.099489 13919 net.cpp:139] Memory required for data: 200960
# 2. 创建ip,全连接层
I0610 04:53:36.099498 13919 layer_factory.hpp:77] Creating layer ip
I0610 04:53:36.099509 13919 net.cpp:86] Creating Layer ip
# 从“data”生层“ip”,就是这层的输出
I0610 04:53:36.099529 13919 net.cpp:408] ip <- data
I0610 04:53:36.099541 13919 net.cpp:382] ip -> ip
I0610 04:53:36.100448 13919 net.cpp:124] Setting up ip
I0610 04:53:36.100461 13919 net.cpp:131] Top shape: 64 10 (640)
I0610 04:53:36.100478 13919 net.cpp:139] Memory required for data: 203520
# 3. 创建最后一层得到loss
I0610 04:53:36.100493 13919 layer_factory.hpp:77] Creating layer loss
I0610 04:53:36.100505 13919 net.cpp:86] Creating Layer loss
# 该层输入为“ip”&“label”输出为“loss”
I0610 04:53:36.100510 13919 net.cpp:408] loss <- ip
I0610 04:53:36.100517 13919 net.cpp:408] loss <- label
I0610 04:53:36.100523 13919 net.cpp:382] loss -> loss
I0610 04:53:36.100535 13919 layer_factory.hpp:77] Creating layer loss
I0610 04:53:36.643620 13919 net.cpp:124] Setting up loss
# 输出的loss大小为1,其权值为1
I0610 04:53:36.643661 13919 net.cpp:131] Top shape: (1)
I0610 04:53:36.643664 13919 net.cpp:134] with loss weight 1
# 目前所占内存空间 200MB
I0610 04:53:36.643699 13919 net.cpp:139] Memory required for data: 203524
# 从后先前执行反向计算,哪里需要计算,就算哪里
I0610 04:53:36.643705 13919 net.cpp:200] loss needs backward computation.
I0610 04:53:36.643714 13919 net.cpp:200] ip needs backward computation.
I0610 04:53:36.643719 13919 net.cpp:202] mnist does not need backward computation.
# TRAIN网络只输出loss
I0610 04:53:36.643726 13919 net.cpp:244] This network produces output loss
I0610 04:53:36.643734 13919 net.cpp:257] Network initialization done.
# ~~~~~~~~~~~~~~~~~训练网络构建结束~~~~~~~~~~~~~~~~~

# ~~~~~~~~~~~~~~~~~测试网络构建开始~~~~~~~~~~~~~~~~~
I0610 04:53:36.644055 13919 solver.cpp:190] Creating test net (#0) specified by net file: /media/junhui/DATA/caffe_workspace/my_linearReggresion/mylr.prototxt
# 这里指出,用于TRAIN的数据层的phase值为“TRAIN”,将不在“TEST”阶段使用
I0610 04:53:36.644089 13919 net.cpp:296] The NetState phase (1) differed from the phase (0) specified by a rule in layer mnist
I0610 04:53:36.644132 13919 net.cpp:53] Initializing net from parameters:
# 同样的,给出完整的TEST网络的结构
name: "lrNet"
state {
phase: TEST # 用于TEST
}
# 数据层
layer {
name: "mnist"
type: "Data"
top: "data"
top: "label"
include {
phase: TEST
}
transform_param {
scale: 0.0039063
}
data_param {
source: "/media/junhui/DATA/caffe_workspace/my_linearReggresion/mnist_test_lmdb"
batch_size: 100
backend: LMDB
}
}
# 全连接层
layer {
name: "ip"
type: "InnerProduct"
bottom: "data"
top: "ip"
param {
lr_mult: 1
}
param {
lr_mult: 2
}
inner_product_param {
num_output: 10
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
}
}
}
# 计算accuracy
layer {
name: "accuracy"
type: "Accuracy"
bottom: "ip"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
# 计算loss
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "ip"
bottom: "label"
top: "loss"
}
I0610 04:53:36.644286 13919 layer_factory.hpp:77] Creating layer mnist
I0610 04:53:36.644841 13919 db_lmdb.cpp:35] Opened lmdb /media/junhui/DATA/caffe_workspace/my_linearReggresion/mnist_test_lmdb
I0610 04:53:36.644881 13919 net.cpp:86] Creating Layer mnist
I0610 04:53:36.644889 13919 net.cpp:382] mnist -> data
I0610 04:53:36.644898 13919 net.cpp:382] mnist -> label
I0610 04:53:36.645038 13919 data_layer.cpp:45] output data size: 100,1,28,28
I0610 04:53:36.646373 13919 net.cpp:124] Setting up mnist
I0610 04:53:36.646389 13919 net.cpp:131] Top shape: 100 1 28 28 (78400)
I0610 04:53:36.646394 13919 net.cpp:131] Top shape: 100 (100)
I0610 04:53:36.646397 13919 net.cpp:139] Memory required for data: 314000
# 这一层由caffe解析后自动加上,label_mnist_1_split
I0610 04:53:36.646402 13919 layer_factory.hpp:77] Creating layer label_mnist_1_split
I0610 04:53:36.646409 13919 net.cpp:86] Creating Layer label_mnist_1_split
I0610 04:53:36.646426 13919 net.cpp:408] label_mnist_1_split <- label
# 复制两个label_mnist_1_split,一个用于计算最终accuracy,一个用于计算最终loss
I0610 04:53:36.646445 13919 net.cpp:382] label_mnist_1_split -> label_mnist_1_split_0
I0610 04:53:36.646454 13919 net.cpp:382] label_mnist_1_split -> label_mnist_1_split_1
I0610 04:53:36.646559 13919 net.cpp:124] Setting up label_mnist_1_split
I0610 04:53:36.646585 13919 net.cpp:131] Top shape: 100 (100)
I0610 04:53:36.646590 13919 net.cpp:131] Top shape: 100 (100)
I0610 04:53:36.646595 13919 net.cpp:139] Memory required for data: 314800
I0610 04:53:36.646598 13919 layer_factory.hpp:77] Creating layer ip
I0610 04:53:36.646606 13919 net.cpp:86] Creating Layer ip
I0610 04:53:36.646611 13919 net.cpp:408] ip <- data
I0610 04:53:36.646617 13919 net.cpp:382] ip -> ip
I0610 04:53:36.646811 13919 net.cpp:124] Setting up ip
I0610 04:53:36.646819 13919 net.cpp:131] Top shape: 100 10 (1000)
I0610 04:53:36.646824 13919 net.cpp:139] Memory required for data: 318800
# 这一层由caffe解析后自动加上,ip_ip_0_split,
I0610 04:53:36.646834 13919 layer_factory.hpp:77] Creating layer ip_ip_0_split
I0610 04:53:36.646840 13919 net.cpp:86] Creating Layer ip_ip_0_split
I0610 04:53:36.646845 13919 net.cpp:408] ip_ip_0_split <- ip
# 复制两个ip_ip_0_split,一个用于计算最终accuracy,一个用于计算最终loss
I0610 04:53:36.646852 13919 net.cpp:382] ip_ip_0_split -> ip_ip_0_split_0
I0610 04:53:36.646859 13919 net.cpp:382] ip_ip_0_split -> ip_ip_0_split_1
I0610 04:53:36.646891 13919 net.cpp:124] Setting up ip_ip_0_split
I0610 04:53:36.646898 13919 net.cpp:131] Top shape: 100 10 (1000)
I0610 04:53:36.646914 13919 net.cpp:131] Top shape: 100 10 (1000)
I0610 04:53:36.646919 13919 net.cpp:139] Memory required for data: 326800
# _0 计算accuracy
I0610 04:53:36.646940 13919 layer_factory.hpp:77] Creating layer accuracy
I0610 04:53:36.646947 13919 net.cpp:86] Creating Layer accuracy
I0610 04:53:36.646952 13919 net.cpp:408] accuracy <- ip_ip_0_split_0
I0610 04:53:36.646957 13919 net.cpp:408] accuracy <- label_mnist_1_split_0
I0610 04:53:36.646963 13919 net.cpp:382] accuracy -> accuracy
I0610 04:53:36.646972 13919 net.cpp:124] Setting up accuracy
I0610 04:53:36.646977 13919 net.cpp:131] Top shape: (1)
I0610 04:53:36.646981 13919 net.cpp:139] Memory required for data: 326804
# _0 计算loss
I0610 04:53:36.646986 13919 layer_factory.hpp:77] Creating layer loss
I0610 04:53:36.646992 13919 net.cpp:86] Creating Layer loss
I0610 04:53:36.646997 13919 net.cpp:408] loss <- ip_ip_0_split_1
I0610 04:53:36.647002 13919 net.cpp:408] loss <- label_mnist_1_split_1
I0610 04:53:36.647024 13919 net.cpp:382] loss -> loss
I0610 04:53:36.647034 13919 layer_factory.hpp:77] Creating layer loss
I0610 04:53:36.647753 13919 net.cpp:124] Setting up loss
I0610 04:53:36.647764 13919 net.cpp:131] Top shape: (1)
I0610 04:53:36.647770 13919 net.cpp:134] with loss weight 1
I0610 04:53:36.647779 13919 net.cpp:139] Memory required for data: 326808
# 给出哪些需要后向传播,哪些不需要!!!!
I0610 04:53:36.647785 13919 net.cpp:200] loss needs backward computation.
I0610 04:53:36.647792 13919 net.cpp:202] accuracy does not need backward computation.
I0610 04:53:36.647799 13919 net.cpp:200] ip_ip_0_split needs backward computation.
I0610 04:53:36.647804 13919 net.cpp:200] ip needs backward computation.
I0610 04:53:36.647809 13919 net.cpp:202] label_mnist_1_split does not need backward computation.
I0610 04:53:36.647814 13919 net.cpp:202] mnist does not need backward computation.
# TEST网络输出accuracy和loss
I0610 04:53:36.647819 13919 net.cpp:244] This network produces output accuracy
I0610 04:53:36.647826 13919 net.cpp:244] This network produces output loss
I0610 04:53:36.647835 13919 net.cpp:257] Network initialization done.
I0610 04:53:36.647861 13919 solver.cpp:57] Solver scaffolding done.
# ~~~~~~~~~~~~~~~~~测试网络构建结束~~~~~~~~~~~~~~~~~

# ~~~~~~~~~~~~~~~~~训练测试开始执行~~~~~~~~~~~~~~~~~
I0610 04:53:36.647935 13919 caffe.cpp:239] Starting Optimization
I0610 04:53:36.647941 13919 solver.cpp:289] Solving lrNet
I0610 04:53:36.647945 13919 solver.cpp:290] Learning Rate Policy: inv
# 第一次迭代,打印测试结果
I0610 04:53:36.647997 13919 solver.cpp:347] Iteration 0, Testing net (#0)
I0610 04:53:36.648779 13919 blocking_queue.cpp:49] Waiting for data
I0610 04:53:36.698768 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:36.699136 13919 solver.cpp:414] Test net output #0: accuracy = 0.1184
I0610 04:53:36.699177 13919 solver.cpp:414] Test net output #1: loss = 2.31538 (* 1 = 2.31538 loss)
# 训练迭代500次后,打印测试结果
I0610 04:53:36.872504 13919 solver.cpp:347] Iteration 500, Testing net (#0)
I0610 04:53:36.922243 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:36.923786 13919 solver.cpp:414] Test net output #0: accuracy = 0.8987
I0610 04:53:36.923828 13919 solver.cpp:414] Test net output #1: loss = 0.378121 (* 1 = 0.378121 loss)
I0610 04:53:37.032925 13919 blocking_queue.cpp:49] Waiting for data
I0610 04:53:37.075943 13925 data_layer.cpp:73] Restarting data prefetching from start.
# 训练迭代又500次后,打印测试结果
I0610 04:53:37.098423 13919 solver.cpp:347] Iteration 1000, Testing net (#0)
I0610 04:53:37.149353 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:37.149734 13919 solver.cpp:414] Test net output #0: accuracy = 0.9064
I0610 04:53:37.149776 13919 solver.cpp:414] Test net output #1: loss = 0.3422 (* 1 = 0.3422 loss)
I0610 04:53:37.316992 13919 solver.cpp:347] Iteration 1500, Testing net (#0)
I0610 04:53:37.366453 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:37.366799 13919 solver.cpp:414] Test net output #0: accuracy = 0.912
I0610 04:53:37.366842 13919 solver.cpp:414] Test net output #1: loss = 0.321062 (* 1 = 0.321062 loss)
# 迭代过程的打印信息,略...
I0610 04:53:38.582134 13919 blocking_queue.cpp:49] Waiting for data
I0610 04:53:38.680619 13919 solver.cpp:347] Iteration 4500, Testing net (#0)
I0610 04:53:38.731168 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:38.731528 13919 solver.cpp:414] Test net output #0: accuracy = 0.9195
I0610 04:53:38.731568 13919 solver.cpp:414] Test net output #1: loss = 0.292936 (* 1 = 0.292936 loss)
I0610 04:53:38.796696 13925 data_layer.cpp:73] Restarting data prefetching from start.
# 保存这个时候的模型和快照
I0610 04:53:38.909629 13919 solver.cpp:464] Snapshotting to binary proto file my_lr_iter_5000.caffemodel
I0610 04:53:38.910568 13919 sgd_solver.cpp:284] Snapshotting solver state to binary proto file my_lr_iter_5000.solverstate
I0610 04:53:38.910995 13919 solver.cpp:347] Iteration 5000, Testing net (#0)
I0610 04:53:38.961858 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:38.962236 13919 solver.cpp:414] Test net output #0: accuracy = 0.9202
I0610 04:53:38.962280 13919 solver.cpp:414] Test net output #1: loss = 0.289039 (* 1 = 0.289039 loss)
I0610 04:53:38.978883 13919 blocking_queue.cpp:49] Waiting for data
I0610 04:53:39.144309 13919 solver.cpp:347] Iteration 5500, Testing net (#0)
I0610 04:53:39.193158 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:39.193552 13919 solver.cpp:414] Test net output #0: accuracy = 0.92
I0610 04:53:39.193594 13919 solver.cpp:414] Test net output #1: loss = 0.290407 (* 1 = 0.290407 loss)
I0610 04:53:39.237748 13925 data_layer.cpp:73] Restarting data prefetching from start.
# 迭代过程的打印信息,略...
I0610 04:53:40.892292 13919 blocking_queue.cpp:49] Waiting for data
I0610 04:53:40.902132 13925 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:40.947559 13919 solver.cpp:347] Iteration 9500, Testing net (#0)
I0610 04:53:40.996812 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:40.997177 13919 solver.cpp:414] Test net output #0: accuracy = 0.9214
I0610 04:53:40.997220 13919 solver.cpp:414] Test net output #1: loss = 0.282299 (* 1 = 0.282299 loss)
# 保存这时候的模型和快照
I0610 04:53:41.165763 13919 solver.cpp:464] Snapshotting to binary proto file my_lr_iter_10000.caffemodel
I0610 04:53:41.166873 13919 sgd_solver.cpp:284] Snapshotting solver state to binary proto file my_lr_iter_10000.solverstate
I0610 04:53:41.167291 13919 solver.cpp:347] Iteration 10000, Testing net (#0)
I0610 04:53:41.218529 13926 data_layer.cpp:73] Restarting data prefetching from start.
I0610 04:53:41.220217 13919 solver.cpp:414] Test net output #0: accuracy = 0.922
I0610 04:53:41.220261 13919 solver.cpp:414] Test net output #1: loss = 0.281314 (* 1 = 0.281314 loss)
I0610 04:53:41.220270 13919 solver.cpp:332] Optimization Done.
I0610 04:53:41.220276 13919 caffe.cpp:250] Optimization Done.
# ~~~~~~~~~~~~~~~~~训练测试执行结束~~~~~~~~~~~~~~~~~
# 完

总结:

  • 过程是先从网络结构超参数文件和训练超参数文件中解析出TRAIN网络和TEST网络,后构建两个网络,最后开始训练。
  • 根据每条信息在源码中的执行位置,可以追踪源码执行细节。比如Memory required for data如何统计内存使用量的,看net.cpp139行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// memory_used_ 保存当前net占用内存空间
// 遍历当前net的每一个Layer
for (int layer_id = 0; layer_id < param.layer_size(); ++layer_id) {
...
// 遍历当前Layer的top
for (int top_id = 0; top_id < top_vecs_[layer_id].size(); ++top_id) {
...
// 内存用量累加
memory_used_ += top_vecs_[layer_id][top_id]->count();
}
// 打印这一层内存用量
LOG_IF(INFO, Caffe::root_solver()) // 打印log
<< "Memory required for data: " << memory_used_ * sizeof(Dtype);
...
}

问题

  1. TEST网络为什么也需要后传计算?
  2. 上述结果是使用GPU计算的,但是在log中并没有显示使用了哪个.cu文件。肯定执行了CUDA文件,应该是没有记录。

caffe-数据&模型-模型

模型三部分参数

模型是caffe学习系统包含数据与模型两个核心组件之一。一个caffe模型含有三部分参数:

  • 要学习的参数:

即权值,通过 初始化和反向传播得到更新。随着训练的结束,要学习的参数就此确定,也意味着确定了一个具体的模型。这个模型或者说是最终的学习参数会保存在.caffemodel文件中。我们可以直接使用这个模型,或者是在这个模型的基础上继续学习。

  • 网络结构超参数:

就是Net的蓝图,即如何构建一个具体的Net,是一种构建策略。比如当前卷积层的kernel数量,kernel大小,步长等参数,显然,结构参数是在训练网络前就确定了的。注意:同一个Net的训练结构和测试结构可能不同。结构参数由Net的蓝图文件.prototxt提供,读取这个文件,得到Net的结构细节,从而指导caffe构建制定Net。

  • 训练超参数:

控用于制训练过程的参数,如learning rate,迭代次数,CPU或GPU训练,等。其描述存在于一个.prototxt文件,一般使用solver.prototxt这个文件名。

先准备数据。在将原始数据(raw data)转换为LMDB格式之后,就可以由数据层(DataLayer)不断从LMDB(磁盘上)读取数据进入网络。

为了对caffe的pipline有个清晰的理解,在工作目录创建文件夹my_linearReggresion 存放关于这个网络的所有内容,将之前创建好的LMDB数据文件放入 my_linearReggresion。之后创建网络结构文件 mylr.prototxt 和训练超参数文件 lr_solver.prototxt

此时的my_linearReggresion目录tree如下:

1
2
3
4
5
6
7
8
9
.
├── lr_solver.prototxt
├── mnist_test_lmdb
│   ├── data.mdb
│   └── lock.mdb
├── mnist_train_lmdb
│   ├── data.mdb
│   └── lock.mdb
└── mylr.prototxt

上述是以一个简单的logistic reggresion为例。其网络结构如下图(包含训练和测试两个结构,两者共同的层只需定义一遍,不同阶段的要分别定义):
[插图结构图]
参照结构图,定义网络结构超参数文件和训练超参数文件。

编辑mylr.prototxt

先编辑网络结构超参数文件,就是编辑mylr.prototxt文件。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
name: "lrNet"
# 数据层,用于train
layer{
name: "mnist"
type: "Data"
top: "data"
top: "label"
include {
phase: TRAIN
}
transform_param{
scale: 0.0039063
}
data_param{
source: "MNIST_LMDB_PATH/mnist_train_lmdb"
batch_size: 64
backend: lmdb
}
}
# 数据层,用于test
layer{
name: "mnist"
type: "Data" # 从哪里定义的“Data”
top: "data"
top: "label"
include {
phase: TEST
}
transform_param{ # 从哪里定义
scale: 0.0039063
}
data_param{ # 从哪里定义
source: "MNIST_LMDB_PATH/mnist_test_lmdb"
batch_size: 100
backend: lmdb
}
}
# 内积层
layer{
name: "ip"
type: "InnerProduct" # 从哪里定义
bottom: "data"
top: "ip"
param{ # 从哪里定义
lr_mult: 1
}
param{
lr_mult: 2
}
inner_product_param{ # 从哪里定义
num_output: 10
weight_filler{
type: "xavier"
}
bias_filler{
type: "constant"
}
}
}
# 在Test阶段使用
layer{
name: "accuracy"
type: "Accuracy"
bottom: "ip"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}

layer{
name: "loss"
type: "SoftMaxWithLoss"
bottom: "ip"
bottom: "label"
top: "loss"
}

其中source提供LMDB数据的路径,提供绝对路径不用担心出错。

编辑lr_solver.prototxt

按照上述蓝图构建用于训练的网络和用于测试的网络。之后编辑训练超参数文件lr_solver.prototxt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 结构文件的路径,告诉caffe,构建蓝图在哪
net: "PATH/lr.prototxt"
test_iter: 100
# 每500次做一次测试
test_interval: 500
base_lr: 0.01
momentum: 0.9
weight_delay: 0.0005
lr_policy: "inv"
gamma: 0.0001
power: 0.75
max_iter: 10000
# 每迭代5000次拍个快照
snapshot: 5000
snapshot_prefix: "PATH/mnist/lrNet"
solver_mode: GPU

note: 想上添加type关键字,用来指定所使用的优化方法,如type: "Nesterov"type: "AdaGrad"type: "AdaDelta"等。默认使用SGD
有了数据有了模型就可以训练了:

1
2
CAFFE train \
--solver=PATH/lr_solver.prototxt

其中CAFFE,表示caffe_master提前编译好的caffe工具,就像在caffe_master中的命令./build/tools/caffe

总结,要想正确运行,必须保证所配置的路径是有效的:

  • 用于执行指令的build/tools/caffe命令的路径,
  • 训练超参数文件的路径lr_solver.prototxt
  • LMDB数据路径,
  • 模型结构超参数文件路径mylr.prototxt

对于Layer的类型

caffe现有的Layer type

1
2
3
4
5
6
7
8
9
10
known types: AbsVal, Accuracy, ArgMax, BNLL, BatchNorm, 
BatchReindex, Bias, Clip, Concat, ContrastiveLoss, Convolution,
Crop, Data, Deconvolution, Dropout, DummyData, ELU, Eltwise,
Embed, EuclideanLoss, Exp, Filter, Flatten, HDF5Data,
HDF5Output, HingeLoss, Im2col, ImageData, InfogainLoss,
InnerProduct, Input, LRN, LSTM, LSTMUnit, Log, MVN, MemoryData,
MultinomialLogisticLoss, PReLU, Parameter, Pooling, Power,
Python, RNN, ReLU, Reduction, Reshape, SPP, Scale, Sigmoid,
SigmoidCrossEntropyLoss, Silence, Slice, Softmax,
SoftmaxWithLoss, Split, Swish, TanH, Threshold, Tile, WindowData

当训练并测试完毕,会有模型文件和快照文件保存于当前目录:

1
2
3
4
5
6
7
8
9
10
11
12
13
.
├── lr_solver.prototxt
├── mnist_test_lmdb
│   ├── data.mdb
│   └── lock.mdb
├── mnist_train_lmdb
│   ├── data.mdb
│   └── lock.mdb
├── my_lr_iter_10000.caffemodel
├── my_lr_iter_10000.solverstate
├── my_lr_iter_5000.caffemodel
├── my_lr_iter_5000.solverstate
└── mylr.prototxt

问题
这些关键字是在哪了与Layer的实现对应起来的呢?当将自己实现的Layer或其他组件键入到caffe源码中时,就需要在某处添加对应关键字。

caffe-Net.hpp

从prototxt文件解析Net的结构,以 Lenet 为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <vector>
#include <caffe/blob.hpp>
#include <caffe/net.hpp>
#include <caffe/util/io.hpp>

using namespace caffe;
using namespace std;

int main() {
// prototxt文件路径
std::string proto("../Lenet.prototxt");
// 创造一个Net对象。phase设为TEST,测试网络
Net<float> nn(proto, caffe::TEST);
// 获取这个Net的layer names:
vector<string> ln = nn.layer_names();
for(auto i:ln){
cout<<i<<" ";
}
cout<<endl;
// 取这个Net的`blob_names`
vector<string> bn = nn.blob_names();
for(auto i:bn){
cout<<i<<" ";
}
cout<<endl;
return 0;
}

返回这个 TEST Net 中的blob name:

1
2
3
4
# layer names
mnist conv1 pool1 conv2 pool2 ip1 relu1 ip2 loss
# blob names
data label conv1 pool1 conv2 pool2 ip1 ip2 loss

BLob对象存放每个Layer的输出和输入结果。每个Layer对输入Blob进行某种计算。layer name 和 blob names 不存在任何关系。所有的Layer和Blob用名字区分,

先看一个Net对象都有什么成员属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
protected
// 这个 Net 的名称
string name_;
// 这个 Net 用于 TRAIN or TEST
Phase phase_;
// 记录这个 Net 中的每一层
vector<shared_ptr<Layer<Dtype>>> layers_;
// 记录每一层的名称
vector<string> layer_names_;
// 记录每一层与其位置Index的对应关系
map<string, int> layer_names_index_;
// 记录每层是否需要反向传播
vector<bool> layer_need_backward_;

// 存储层之间的中间结果
vector<shared_ptr<Blob<Dtype>>> blobs_;
// 记录每个blob的名称
vector<string> blob_names_;
// 记录每个Blob与其位置Index的对应关系
map<string, int> blob_names_index_;
// 记录每个blob是否需要反向计算
vector<bool> blob_need_backward_;
/// bottom_vecs stores the vectors containing the input for each layer.
/// They don't actually host the blobs (blobs_ does), so we simply store
/// pointers.
// 记录每个Layer的输入Blob,但并不是Blob的内容,而是Blob的地址。
vector<vector<Blob<Dtype>*>> bottom_vecs_;
// 与上者相关,
vector<vector<int>> bottom_id_vecs_;
vector<vector<bool>> bottom_need_backward_;
/// top_vecs stores the vectors containing the output for each layer
vector<vector<Blob<Dtype>*>> top_vecs_;
vector<vector<int>> top_id_vecs_;
/// Vector of weight in the loss (or objective) function of each net blob,
/// indexed by blob_id.
vector<Dtype> blob_loss_weights_;

vector<vector<int>> param_id_vecs_;
vector<int> param_owners_;
vector<string> param_display_names_;
vector<pair<int, int>> param_layer_indices_;
map<string, int> param_names_index_;

/// blob indices for the input and the output of the net
// 这个Net 的输入输出Blob 在 blobs_ 中的索引
vector<int> net_input_blob_indices_;
vector<int> net_output_blob_indices_;
// 输入输出Blob
vector<Blob<Dtype>*> net_input_blobs_;
vector<Blob<Dtype>*> net_output_blobs_;

// 这个 Net 中的参数,权值
vector<shared_ptr<Blob<Dtype>>> params_;
// 这个 Net 中可训练的权值
vector<Blob<Dtype>*> learnable_params_;
/**
* The mapping from params_ -> learnable_params_: we have
* learnable_param_ids_.size() == params_.size(),
* and learnable_params_[learnable_param_ids_[i]] == params_[i].get()
* if and only if params_[i] is an "owner"; otherwise, params_[i] is a sharer
* and learnable_params_[learnable_param_ids_[i]] gives its owner.
*/
vector<int> learnable_param_ids_;
/// the learning rate multipliers for learnable_params_
vector<float> params_lr_;
vector<bool> has_params_lr_;
/// the weight decay multipliers for learnable_params_
vector<float> params_weight_decay_;
vector<bool> has_params_decay_;
/// The bytes of memory used by this net
size_t memory_used_;
/// Whether to compute and display debug info for the net.
bool debug_info_;
// Callbacks
vector<Callback*> before_forward_;
vector<Callback*> after_forward_;
vector<Callback*> before_backward_;
vector<Callback*> after_backward_;

说明一下:

  • 其实并不能单单从属性的名字中知道它是干啥的。其与原理相关,需要在试验中分析每个属性的作用。
  • 上述属性中大部分是vector容器,所以猜想,每个容器存放这个Net的所有Layer的对应属性。
  • 上述大部分的类属性都有一个getter()函数,返回这个Net中每个对象。

Net中由两类Blob,以param开头的是权值Blob;以blob开头的是数据Blob。前者决定了模型是什么样的,后者是每一个Layer的输入和输出(样本数据),是这个Net中被处理的数据。

cpp-void型指针

下面这个函数什么意思:

1
2
3
void* foo(void* a){
return a;
}

他表示foo接受任何类型的指针,并输出任何类型的指针。使用方式总结如下:

1. void指针可以指向任何类型指针,但反过来就不对了

1
2
3
4
float f = 5.5;
float* pf = &f;
void* pv = pf; // void指针可以指向float型指针
float* pf2 = pv; // 错,float指针不能指向void指针

2.void指针只有强制转换类型后才可以取值,而且要转换成所保存地址中,内容的类型

1
2
3
4
5
6
float x = 4;
void* yv = foo(&x);
cout<<yv<<" "<<*(float*)yv<<endl; // 返回0x7ffc78a54e1c 4
//cout<<*yv<<endl; // 编译错误
cout<<*(double*)yv<<endl; // 返回1.44068e+273。返回值错误
cout<<(double)(*(float*)yv)<<endl; // 返回4。正确

3. void指针可以使用nullptr初始化

1
2
void* vPtr = nullptr;
cout<<*vPtr<<endl; // 编译错误,vPtr不指向任何对象,所以取不到任何内容

4. 接受任何类型的指针,并输出任何类型的指针

1
2
3
void* vPtr = nullptr;
void* vFunPtr = foo(vPtr);
cout<<vFunPtr<<endl; // 空指针,所以返回0

caffe-Layer中有什么

一个Layer对象以一个Blob为输入(bottom),另一个Blob为输出(top)。主要机选包括前向计算和后向计算:前向计算对输入blob进行处理,得到输出blob。后向计算对输出blob的diff部分做处理得到输入blob的diff。

注意了,caffe中的topbottom都是vector<shared_ptr<Blob<Dtype>>>其元素为多个指向blob的指针。而并非值一个blob对象!

既然blobs_是训练参数,那么向该层输入的数据在哪???

下面内容位于Layer.hpp文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
class Layer {
// 先看类成员属性,以下划线结尾的变量,对类内可见,对该类之外不可见。
protected:
// 保存该层参数的 protobuf
// LayerParameter类声明在这里:
// .build_release/src/caffe/proto/caffe.pb.h 这是编译后自动生成的文件
LayerParameter layer_param_;
// 这层所处是哪个阶段,train OR test
Phase phase_;
// 多个Blob指针,指向这层内部的学习参数
vector<shared_ptr<Blob<Dtype>>> blobs_;
// 是否计算对应参数的误差梯度
vector<bool> param_propagate_down_;
// 目标函数中是否每个Top blob都有非零权值
vector<Dtype> loss_;

// 上述三个vector的长度一样!

public:
// 构造,从LayerParameter对象中加载参数
explicit Layer(const LayerParameter& param)
: layer_param_(param) {
// 设置阶段
phase_ = param.phase();
// 如果有数据,则设置blob,具体是从磁盘读取到这个Layer的blob
if (layer_param_.blobs_size() > 0) {
// WHY blob的个数resize到blob的大小 ???
blobs_.resize(layer_param_.blobs_size());
for (int i = 0; i < layer_param_.blobs_size(); ++i) {
// 这个blob[i]指针接管一个新的blob指针
blobs_[i].reset(new Blob<Dtype>());
// 从磁盘读取数据到当前blob[i]
blobs_[i]->FromProto(layer_param_.blobs(i));
}
}
}
virtual ~Layer() {}

// 不能覆盖这个方法,提供4个功能
void SetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
//1. 检查bottom 和topblob是否满足这层的要求
CheckBlobCounts(bottom, top);
//2. 调用自己实现的层配置函数
LayerSetUp(bottom, top);
//3. 对输出blob 变形
Reshape(bottom, top);
//4.
SetLossWeights(top);
}

// 层的相关配置,由自己实现(子类实现)
virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {}

// 自己实现(子类实现),调整top blob和中间buffer的形状
// 以适应bottom blob的形状。纯虚函数
virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) = 0;


// 根据bottom blob 计算top blob和loss。返回这一层的总loss。
// 该函数调用Forward_cpu()和Forward_gpu()执行真正的计算;
// 如果该层有非零权值,则计算并返回loss。
// 在子类实现Forward_cpu()和Forward_gpu()。毕竟不同层的计算方式不同。
inline Dtype Forward(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top);


// 反向传播计算,给定top 的梯度,计算bottom的梯度。
// 参数中top 中含有上层来的梯度误差diff
// propagate_down 其长度与bottom长度相同,
// 其中每一个值表示是否将对应的误差传到对应的bottom。
// bottom 输入blobs,经过Backward()计算后, 其diff 保存误差梯度,
// 实际上的后向传播的执行由Backward_cpu() 和 Backward_gpu()实现。
// 子类需要实现Backward_cpu() 和 Backward_gpu()
inline void Backward(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down,
const vector<Blob<Dtype>*>& bottom);

// 返回这层中可训练参数 blob向量
vector<shared_ptr<Blob<Dtype> > >& blobs() {
return blobs_;
}

// 返回该层的 曾参数(由protobuff提供)
const LayerParameter& layer_param() const {
return layer_param_;
}

// 将该层层参数写入 protobuff
virtual void ToProto(LayerParameter* param, bool write_diff = false);

// 返回top指定index的blob的loss值
inline Dtype loss(const int top_index) const {
return (loss_.size() > top_index) ? loss_[top_index] : Dtype(0);
}

// 为top指定index的blob的loss 设值
inline void set_loss(const int top_index, const Dtype value) {
if (loss_.size() <= top_index) {
loss_.resize(top_index + 1, Dtype(0));
}
loss_[top_index] = value;
}

// 返回该层的类型
virtual inline const char* type() const { return ""; }

// 返回该层需要输入或输出的blobs数,由子类实现。
virtual inline int ExactNumBottomBlobs() const { return -1; }
virtual inline int MinBottomBlobs() const { return -1; }
virtual inline int MaxBottomBlobs() const { return -1; }
virtual inline int ExactNumTopBlobs() const { return -1; }
virtual inline int MinTopBlobs() const { return -1; }
virtual inline int MaxTopBlobs() const { return -1; }

// 该层的top blobs个数和bottom blobs个数是否相同。子类实现
virtual inline bool EqualNumBottomTopBlobs() const { return false; }


// 是否需要自动创造匿名top blobs,
// 如果返回true,Net::Init()会创建足够多的匿名top blobs来满足
// ExactNumTopBlobs() 或MinTopBlobs().
virtual inline bool AutoTopBlobs() const { return false; }


virtual inline bool AllowForceBackward(const int bottom_index) const {
return true;
}

inline bool param_propagate_down(const int param_id) {
return (param_propagate_down_.size() > param_id) ?
param_propagate_down_[param_id] : false;
}

inline void set_param_propagate_down(const int param_id, const bool value) {
if (param_propagate_down_.size() <= param_id) {
param_propagate_down_.resize(param_id + 1, true);
}
param_propagate_down_[param_id] = value;
}

protected:

// cpu和gpu 前行计算,其具体实现在具体的层中,将一直看到
virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) = 0;
virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
// LOG(WARNING) << "Using CPU code as backup.";
return Forward_cpu(bottom, top);
}
virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down,
const vector<Blob<Dtype>*>& bottom) = 0;
virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
const vector<bool>& propagate_down,
const vector<Blob<Dtype>*>& bottom) {
// LOG(WARNING) << "Using CPU code as backup.";
Backward_cpu(top, propagate_down, bottom);
}

/**
* Called by the parent Layer's SetUp to check that the number of bottom
* and top Blobs provided as input match the expected numbers specified by
* the {ExactNum,Min,Max}{Bottom,Top}Blobs() functions.
*/
// 最后两个函数由父类Layer 的SetUp()函数调用
virtual void CheckBlobCounts(const vector<Blob<Dtype>*>& bottom,
const vector<Blob<Dtype>*>& top) {
if (ExactNumBottomBlobs() >= 0) {
CHECK_EQ(ExactNumBottomBlobs(), bottom.size())
<< type() << " Layer takes " << ExactNumBottomBlobs()
<< " bottom blob(s) as input.";
}
if (MinBottomBlobs() >= 0) {
CHECK_LE(MinBottomBlobs(), bottom.size())
<< type() << " Layer takes at least " << MinBottomBlobs()
<< " bottom blob(s) as input.";
}
if (MaxBottomBlobs() >= 0) {
CHECK_GE(MaxBottomBlobs(), bottom.size())
<< type() << " Layer takes at most " << MaxBottomBlobs()
<< " bottom blob(s) as input.";
}
if (ExactNumTopBlobs() >= 0) {
CHECK_EQ(ExactNumTopBlobs(), top.size())
<< type() << " Layer produces " << ExactNumTopBlobs()
<< " top blob(s) as output.";
}
if (MinTopBlobs() >= 0) {
CHECK_LE(MinTopBlobs(), top.size())
<< type() << " Layer produces at least " << MinTopBlobs()
<< " top blob(s) as output.";
}
if (MaxTopBlobs() >= 0) {
CHECK_GE(MaxTopBlobs(), top.size())
<< type() << " Layer produces at most " << MaxTopBlobs()
<< " top blob(s) as output.";
}
if (EqualNumBottomTopBlobs()) {
CHECK_EQ(bottom.size(), top.size())
<< type() << " Layer produces one top blob as output for each "
<< "bottom blob input.";
}
}

/**
* Called by SetUp to initialize the weights associated with any top blobs in
* the loss function. Store non-zero loss weights in the diff blob.
*/
inline void SetLossWeights(const vector<Blob<Dtype>*>& top) {
const int num_loss_weights = layer_param_.loss_weight_size();
if (num_loss_weights) {
CHECK_EQ(top.size(), num_loss_weights) << "loss_weight must be "
"unspecified or specified once per top blob.";
for (int top_id = 0; top_id < top.size(); ++top_id) {
const Dtype loss_weight = layer_param_.loss_weight(top_id);
if (loss_weight == Dtype(0)) { continue; }
this->set_loss(top_id, loss_weight);
const int count = top[top_id]->count();
Dtype* loss_multiplier = top[top_id]->mutable_cpu_diff();
caffe_set(count, loss_weight, loss_multiplier);
}
}
}

private:
DISABLE_COPY_AND_ASSIGN(Layer);
}; // class Layer

Layer.cpp文件内容:可见Layer的真正实现都在其子类中:src/caffe/layers/*.cpp

1
2
3
4
#include "caffe/layer.hpp"
namespace caffe {
INSTANTIATE_CLASS(Layer);
}

其中:

1
2
3
4
5
// Instantiate a class with float and double specifications.
#define INSTANTIATE_CLASS(classname) \
char gInstantiationGuard##classname; \
template class classname<float>; \
template class classname<double>

caffe-Blob.cpp文件

定义Blob类中每个成员函数,void Blob<Dtype>::Reshape(const vector<int>&)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
template <typename Dtype>
void Blob<Dtype>::Reshape(const vector<int>& shape) {
// 确保Shape中元素少于Blob允许的最大维度数
CHECK_LE(shape.size(), kMaxBlobAxes);
count_ = 1;
// 调用vector.resize(),
shape_.resize(shape.size());
// shape_data_为空指针,或这个指针多指向的内存小于变形后的大小,则:
if (!shape_data_ || shape_data_->size() < shape.size() * sizeof(int)) {
// 让shape_data_接管一个新的指针,它指向一块新的内存
shape_data_.reset(new SyncedMemory(shape.size() * sizeof(int)));
}

// 开辟临时空间向其传入当前CPU数据,返回一个指针shape_data
int* shape_data = static_cast<int*>(shape_data_->mutable_cpu_data());
// 遍历Shape中每一元素(每一维度)
for (int i = 0; i < shape.size(); ++i) {
CHECK_GE(shape[i], 0);
// 只要这个Blob中的count_(元素个数)不是0,即这个Blob存在元素,则???
// 若Blob中不存在元素,则???。
if (count_ != 0) {
CHECK_LE(shape[i], INT_MAX / count_) << "blob size exceeds INT_MAX";
}
// 更新count_, 记录变形后的这个Blob元素个数
count_ *= shape[i];
// 更新shape_,用新的这个维度值替换旧的
shape_[i] = shape[i];
// ????????
shape_data[i] = shape[i];
}

// 当数据个数超过Blob容量,怎更新容量大小
if (count_ > capacity_) {
capacity_ = count_;
// 让data_和diff_分别接管一块新的内存
data_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
}
}

这个函数的功能,当新的reshape后所有维度元素个数N之和小于原来元素个数M,只取M的前N个元素;
当等于时,元素不变。当大于时,所有元素变为零。不过正常的使用是元素个数相同间的reshape。

有了上述的方法,下面的方法调用上面的方法:
void Blob<Dtype>::ReshapeLike(const Blob<Dtype>&)
void Blob<Dtype>::Reshape(int,int,int,int)

读取Blob中已有的cpu_data

1
2
3
4
5
6
template <typename Dtype>
const Dtype* Blob<Dtype>::cpu_data() const {
CHECK(data_);
// 成员data_为共享指针,其指向的存储空间含有cpu_data
return (const Dtype*)data_->cpu_data();
}

为Blob设置cpu_data

1
2
3
4
5
6
7
8
9
10
11
12
template <typename Dtype>
void Blob<Dtype>::set_cpu_data(Dtype* data) {
CHECK(data);
// Make sure CPU and GPU sizes remain equal
size_t size = count_ * sizeof(Dtype);
if (data_->size() != size) {
data_.reset(new SyncedMemory(size));
diff_.reset(new SyncedMemory(size));
}
// 用传入参数'data'设置data_成员变量
data_->set_cpu_data(data);
}

可写访问CPU_data

1
2
3
4
5
template <typename Dtype>
Dtype* Blob<Dtype>::mutable_cpu_data() {
CHECK(data_);
return static_cast<Dtype*>(data_->mutable_cpu_data());
}

其他关于访问,设置cpu,gpu的 data和diff都类似,略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// const只读访问
const Dtype* cpu_data() const;
const Dtype* cpu_diff() const;
// 只读访问GPU数据形状
const int* gpu_shape() const;
const Dtype* gpu_data() const;
const Dtype* gpu_diff() const;
// mutable读写访问
Dtype* mutable_cpu_data();
Dtype* mutable_gpu_data();
Dtype* mutable_cpu_diff();
Dtype* mutable_gpu_diff();
// 设置cpu和gpu数据
void set_cpu_data(Dtype* data);
void set_gpu_data(Dtype* data);

共享BLob数据data:共享diff与下面代码一样。

1
2
3
4
5
6
template <typename Dtype>
void Blob<Dtype>::ShareData(const Blob& other) {
CHECK_EQ(count_, other.count());
// 将这个BLob的data_设为与other一样的值,共享
data_ = other.data();
}

执行Updata():
其中:caffe_axpysrc/caffe/util/math_functions.cpp中,
caffe_gpu_axpysrc/caffe/util/math_functions.cu中。
这两个操作实际是:data_[i] = data_[i] - diff_[i], 其中i=0,1,2,3,4...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
template <typename Dtype>
void Blob<Dtype>::Update() {
// 执行计算取决于数据在哪
switch (data_->head()) { // 获得当前SyncedMemory对象状态
case SyncedMemory::HEAD_AT_CPU: // 如果在cpu则
// 执行在CPU上的计算
caffe_axpy<Dtype>(count_,
Dtype(-1),
static_cast<const Dtype*>(diff_->cpu_data()),
static_cast<Dtype*>(data_->mutable_cpu_data()));
break;
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
// 若使用GPU,则执行在GPU上update()
caffe_gpu_axpy<Dtype>(count_,
Dtype(-1),
static_cast<const Dtype*>(diff_->gpu_data()),
static_cast<Dtype*>(data_->mutable_gpu_data()));
#else
NO_GPU;
#endif
break;
default:
LOG(FATAL) << "Syncedmem not initialized.";
}
}

其他类似的函数结构相同,只是核心操作不同,略

1
2
3
4
5
6
7
8
9
// 计算l1范数 元素和
Dtype asum_data() const;
Dtype asum_diff() const;
// 计算l2范数 元素平方和
Dtype sumsq_data() const;
Dtype sumsq_diff() const;
// 元素可以一个常数
void scale_data(Dtype scale_factor);
void scale_diff(Dtype scale_factor);

共享数据很直接,略

1
2
3
// 共享other这个Blob的data_和diff_
void ShareData(const Blob& other);
void ShareDiff(const Blob& other);

从其他blob拷贝数据到当前Blob:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
template <typename Dtype>
void Blob<Dtype>::CopyFrom(const Blob& source, bool copy_diff,
bool reshape) {
// 必要时reshape
if (source.count() != count_ || source.shape() != shape_) {
if (reshape) {
ReshapeLike(source);
} else {
LOG(FATAL) << "Trying to copy blobs of different sizes.";
}
}
switch (Caffe::mode()) {
// 如果是GPU模式就拷贝GPU数据
case Caffe::GPU:
if (copy_diff) {
caffe_copy(count_, source.gpu_diff(),
static_cast<Dtype*>(diff_->mutable_gpu_data()));
} else {
caffe_copy(count_, source.gpu_data(),
static_cast<Dtype*>(data_->mutable_gpu_data()));
}
break;
// 如果是CPU模式就拷贝CPU数据
case Caffe::CPU:
if (copy_diff) {
caffe_copy(count_, source.cpu_diff(),
static_cast<Dtype*>(diff_->mutable_cpu_data()));
} else {
caffe_copy(count_, source.cpu_data(),
static_cast<Dtype*>(data_->mutable_cpu_data()));
}
break;
default:
LOG(FATAL) << "Unknown caffe mode.";
}
}

反序列化数据,将磁盘数据读入protobuff:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
template <typename Dtype>
void Blob<Dtype>::FromProto(const BlobProto& proto, bool reshape) {
// 如果需要reshape,先reshape
if (reshape) {
vector<int> shape;
if (proto.has_num() || proto.has_channels() ||
proto.has_height() || proto.has_width()) {
// Using deprecated 4D Blob dimensions --
// shape is (num, channels, height, width).
shape.resize(4);
shape[0] = proto.num();
shape[1] = proto.channels();
shape[2] = proto.height();
shape[3] = proto.width();
} else {
shape.resize(proto.shape().dim_size());
for (int i = 0; i < proto.shape().dim_size(); ++i) {
shape[i] = proto.shape().dim(i);
}
}
Reshape(shape); // 按照维度信息变换
} else {
CHECK(ShapeEquals(proto)) << "shape mismatch (reshape not set)";
}
// 从protobuff拷贝数据到当前Blob:
// 获取当前Blob的mutable_cpu_data的地址data_vec,
// 将protobuff中double或float数据 data写入到地址data_vec
// diff 与data一样:
Dtype* data_vec = mutable_cpu_data();
if (proto.double_data_size() > 0) {
CHECK_EQ(count_, proto.double_data_size());
for (int i = 0; i < count_; ++i) {
data_vec[i] = proto.double_data(i);
}
} else {
CHECK_EQ(count_, proto.data_size());
for (int i = 0; i < count_; ++i) {
data_vec[i] = proto.data(i);
}
}
if (proto.double_diff_size() > 0) {
CHECK_EQ(count_, proto.double_diff_size());
Dtype* diff_vec = mutable_cpu_diff();
for (int i = 0; i < count_; ++i) {
diff_vec[i] = proto.double_diff(i);
}
} else if (proto.diff_size() > 0) {
CHECK_EQ(count_, proto.diff_size());
Dtype* diff_vec = mutable_cpu_diff();
for (int i = 0; i < count_; ++i) {
diff_vec[i] = proto.diff(i);
}
}
}

将数据序列化(写入磁盘):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
template <>
void Blob<double>::ToProto(BlobProto* proto, bool write_diff) const {
// 重置protobuff维度,清理原有的数据 并写入新的cpu_data数据
proto->clear_shape();
for (int i = 0; i < shape_.size(); ++i) {
proto->mutable_shape()->add_dim(shape_[i]);
}
proto->clear_double_data();
proto->clear_double_diff();
const double* data_vec = cpu_data();
for (int i = 0; i < count_; ++i) {
proto->add_double_data(data_vec[i]);
}
// 如果需要写入diff,也要写入cpu_diff数据
if (write_diff) {
const double* diff_vec = cpu_diff();
for (int i = 0; i < count_; ++i) {
proto->add_double_diff(diff_vec[i]);
}
}
}

caffe-SyncedMemory

结构:caffe命名空间中包含两个inline函数和类SyncedMemory的声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#ifndef CAFFE_SYNCEDMEM_HPP_
#define CAFFE_SYNCEDMEM_HPP_

#include <cstdlib>

#ifdef USE_MKL
#include "mkl.h" // 使用intel数学运算库MKL
#endif

#include "caffe/common.hpp"

namespace caffe {

// 使用cudaMallocHost()方法从Host内存开辟空间。从这里开辟空间对于单个GPU性能提升不明显,但是对于大的模型在多GPU上的训练,有较大的性能提升。
inline void CaffeMallocHost(void** ptr, size_t size, bool* use_cuda) {
#ifndef CPU_ONLY // 如果没有指明CPU_ONLY,则:
if (Caffe::mode() == Caffe::GPU) {
CUDA_CHECK(cudaMallocHost(ptr, size));
*use_cuda = true;
return;
}
#endif
#ifdef USE_MKL // 如果使用MKL,则:
*ptr = mkl_malloc(size ? size:1, 64);
#else // 否则:
*ptr = malloc(size);
#endif
*use_cuda = false;
CHECK(*ptr) << "host allocation of size " << size << " failed";
}

// 与开辟空间对应,用于释放内存。
inline void CaffeFreeHost(void* ptr, bool use_cuda) {
#ifndef CPU_ONLY
if (use_cuda) {
CUDA_CHECK(cudaFreeHost(ptr));
return;
}
#endif
#ifdef USE_MKL
mkl_free(ptr);
#else
free(ptr);
#endif
}


// 负责Host和Device内存的分配,和两者间内存同步
class SyncedMemory {
public:
// 构造和析构函数
SyncedMemory();
explicit SyncedMemory(size_t size);
~SyncedMemory();
// 只读方式只读数据
const void* cpu_data();
const void* gpu_data();
// 设置数据
void set_cpu_data(void* data);
void set_gpu_data(void* data);
// 读写方式读取数据
void* mutable_cpu_data();
void* mutable_gpu_data();
// 状态变量:未初始化,CPU数据有效,GPU数据有效,已同步
enum SyncedHead { UNINITIALIZED,
HEAD_AT_CPU,
HEAD_AT_GPU,
SYNCED };
// 获取当前状态变量值
SyncedHead head() const { return head_; }
// 返回当前存储空间的大小
size_t size() const { return size_; }

#ifndef CPU_ONLY
void async_gpu_push(const cudaStream_t& stream);
#endif

private:
void check_device();

void to_cpu(); // 同步数据到host
void to_gpu(); // 同步数据到Device
void* cpu_ptr_; // 位于Host的数据指针
void* gpu_ptr_; // 位于Device的数据指针
size_t size_; // 存储空间的大小
SyncedHead head_; // 当前状态变量
bool own_cpu_data_; // 是否有cpu数据的所有权
bool cpu_malloc_use_cuda_; // 是否
bool own_gpu_data_; // 是否有gpu数据的所有权
int device_; // 设备号

DISABLE_COPY_AND_ASSIGN(SyncedMemory);
}; // class SyncedMemory

} // namespace caffe

#endif // CAFFE_SYNCEDMEM_HPP_

补充,条件编译预编译指令

#define
#undef
#if
#ifdef
#ifndef
#elif
#else
#endif
defined:与#if, #elif配合使用,判断某个宏是否被定义

USE_MKL的定义在文件include/caffe/util/mkl_alternate.hpp中。
CPU_ONLY的定义在文件``?????

caffe-Blob.hpp文件

读Blob的头文件,注释每个方法的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#ifndef CAFFE_BLOB_HPP_
#define CAFFE_BLOB_HPP_

#include <algorithm>
#include <string>
#include <vector>

#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/syncedmem.hpp"

// Blob的最大维数 32
const int kMaxBlobAxes = 32;

namespace caffe {
template <typename Dtype>
class Blob {
public:
// 构造函数
Blob(): data_(), diff_(), count_(0), capacity_(0) {}
explicit Blob(const int num, const int channels,
const int height, const int width);
explicit Blob(const vector<int>& shape);

// 改变blob形状
void Reshape(const int num, const int channels,
const int height,const int width);
void Reshape(const vector<int>& shape);
void Reshape(const BlobShape& shape);
void ReshapeLike(const Blob& other);

// 打印形状信息
inline string shape_string() const {
ostringstream stream;
for (int i = 0; i < shape_.size(); ++i) {
stream << shape_[i] << " ";
}
stream << "(" << count_ << ")";
return stream.str();
}

// 返回形状,维度相关信息
inline const vector<int>& shape() const { return shape_; }
inline int shape(int index) const {
return shape_[CanonicalAxisIndex(index)];
}
inline int num_axes() const { return shape_.size(); }
inline int count() const { return count_; }
// 返回制定维度间的元素个数:
// 返回Blob中从start_axis到end_axis间制定维度的元素个数
inline int count(int start_axis, int end_axis) const {
CHECK_LE(start_axis, end_axis); // 确保start_axis <= end_axis
CHECK_GE(start_axis, 0); // >=
CHECK_GE(end_axis, 0);
CHECK_LE(start_axis, num_axes());
CHECK_LE(end_axis, num_axes());
int count = 1;
for (int i = start_axis; i < end_axis; ++i) {
count *= shape(i);
}
return count;
}
// 返回从start_axis到最后维度的元素个数
inline int count(int start_axis) const {
return count(start_axis, num_axes());
}

// 传入负索引,从后向前的访问。
inline int CanonicalAxisIndex(int axis_index) const {
CHECK_GE(axis_index, -num_axes())
<< "axis " << axis_index << " out of range for " << num_axes()
<< "-D Blob with shape " << shape_string();
CHECK_LT(axis_index, num_axes())
<< "axis " << axis_index << " out of range for " << num_axes()
<< "-D Blob with shape " << shape_string();
if (axis_index < 0) {
return axis_index + num_axes();
}
return axis_index;
}

// 返回某个维度大小
inline int num() const { return LegacyShape(0); }
inline int channels() const { return LegacyShape(1); }
inline int height() const { return LegacyShape(2); }
inline int width() const { return LegacyShape(3); }
inline int LegacyShape(int index) const {
CHECK_LE(num_axes(), 4)
<< "Cannot use l4egacy accessors on Blobs with > 4 axes.";
CHECK_LT(index, 4);
CHECK_GE(index, -4);
if (index >= num_axes() || index < -num_axes()) {
return 1;
}
return shape(index);
}

// 根据n,c,h,w计算全局Index
inline int offset(const int n, const int c = 0,
const int h = 0,const int w = 0) const {
CHECK_GE(n, 0);
CHECK_LE(n, num());
CHECK_GE(channels(), 0);
CHECK_LE(c, channels());
CHECK_GE(height(), 0);
CHECK_LE(h, height());
CHECK_GE(width(), 0);
CHECK_LE(w, width());
// 看到了,最右边变化是最快的
return ((n * channels() + c) * height() + h) * width() + w;
}
// 也是计算全局index,只是传入的是vector
inline int offset(const vector<int>& indices) const {
CHECK_LE(indices.size(), num_axes());
int offset = 0;
for (int i = 0; i < num_axes(); ++i) {
offset *= shape(i);
if (indices.size() > i) {
CHECK_GE(indices[i], 0);
CHECK_LT(indices[i], shape(i));
offset += indices[i];
}
}
return offset;
}

// 拷贝一个Blob到当前Blob
void CopyFrom(const Blob<Dtype>& source, bool copy_diff = false,
bool reshape = false);

// 一下4函数均是访问制定位置的数据。提供局部Index,调用offset计算全局index,后可访问
inline Dtype data_at(const int n, const int c,
const int h,const int w) const {
return cpu_data()[offset(n, c, h, w)];
}
inline Dtype diff_at(const int n, const int c,
const int h, const int w) const {
return cpu_diff()[offset(n, c, h, w)];
}
inline Dtype data_at(const vector<int>& index) const {
return cpu_data()[offset(index)];
}
inline Dtype diff_at(const vector<int>& index) const {
return cpu_diff()[offset(index)];
}

// 取data_
inline const shared_ptr<SyncedMemory>& data() const {
CHECK(data_);
return data_;
}
// 取diff_
inline const shared_ptr<SyncedMemory>& diff() const {
CHECK(diff_);
return diff_;
}

// const只读访问
const Dtype* cpu_data() const;
const Dtype* cpu_diff() const;
// 只读访问GPU数据形状
const int* gpu_shape() const;
const Dtype* gpu_data() const;
const Dtype* gpu_diff() const;
// mutable读写访问
Dtype* mutable_cpu_data();
Dtype* mutable_gpu_data();
Dtype* mutable_cpu_diff();
Dtype* mutable_gpu_diff();
// 设置cpu和gpu数据
void set_cpu_data(Dtype* data);
void set_gpu_data(Dtype* data);
// 执行data = data-diff操作,即学习操作
void Update();
// 从磁盘读取数据到blob,反序列化
void FromProto(const BlobProto& proto, bool reshape = true);
// 保存Blob数据到磁盘,序列化
void ToProto(BlobProto* proto, bool write_diff = false) const;

// 计算l1范数 元素和
Dtype asum_data() const;
Dtype asum_diff() const;
// 计算l2范数 元素平方和
Dtype sumsq_data() const;
Dtype sumsq_diff() const;
// 元素可以一个常数
void scale_data(Dtype scale_factor);
void scale_diff(Dtype scale_factor);
// 共享other这个Blob的data_和diff_
void ShareData(const Blob& other);
void ShareDiff(const Blob& other);
// 当前blob是否与other有相同的内容
bool ShapeEquals(const BlobProto& other);

protected:
// 成员属性:
// 均指针,指向data,diff和shape_data的内存位置
shared_ptr<SyncedMemory> data_;
shared_ptr<SyncedMemory> diff_;
shared_ptr<SyncedMemory> shape_data_;
vector<int> shape_;
int count_; // 当前blob的元素个数
int capacity_; // blob容量

// 就Blob类,禁用其copy构造函数和赋值运算符。
DISABLE_COPY_AND_ASSIGN(Blob);
}; // Blob 类结束

} // namespace caffe 结束

#endif // CAFFE_BLOB_HPP_

Blob类使用到syncedmem.hpp,其与Host和Device内存即内存同步相关操作。

caffe-Blob-(1)

先看读lob.hpp, 再读Blob.cpp。

若要调用caffe文件,可以使用g++命令,也可以使用cmake建立一个项目:

1
2
3
4
5
.
└── test_blob
├── build
├── CMakeLists.txt
└── main.cpp

这个项目可以位于任何位置。接着编辑CMakeLists.tx,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cmake_minimum_required(VERSION 3.5)
project(test_blob)

set(Caffe_INCLUDE_DIRS ~/caffe-master/include \
/usr/local/cuda/include ~/caffe-master/build/src)

set(Caffe_LIBRARIES caffe boost_system glog)
set(CMAKE_CXX_STANDARD 11)

include_directories(${Caffe_INCLUDE_DIRS})
link_directories(~/caffe-master/build/lib)

add_executable(test_blob main.cpp)
target_link_libraries(test_blob ${Caffe_LIBRARIES})

install(TARGETS test_blob RUNTIME DESTINATION bin)

使用下面命令编译执行:

1
2
3
4
5
$ rm -rf build/*
$ cd build
$ cmake ..
$ make
$ ./test_blob

在代码文件头部添加基本内容:

1
2
3
4
5
6
#include <iostream>
#include <vector>
#include <caffe/blob.hpp>

using namespace caffe; // 代码中依旧加上这个namespace
using namespace std;

之后就可以在main函数中,进行探索测试了。

基本地,Blob在内存中是4为数组,维度从高到低为(num,channels,height,width)。并包含data(待学习参数)和diff(增量)。包含一些基本操作。

帮助函数

先在main.cpp外自定义了两个函数:向Blob中写入数据,从Blob读取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 向Blob中 写入数据
void writeInto(caffe::Blob<float>& a){
// 获取cpu数据data部分
float* ptr = a.mutable_cpu_data();
// 给每个位置赋值。count()表示元素个数
for(int i=0;i<a.count();i++){
ptr[i] = i;
}
}
// 打印blob信息 读取数据
void printBlob(const caffe::Blob<float>& blob){

// .shape_string() 返回一个string,包含shape和元素个数
cout<<"Size: "<<blob.shape_string()<<endl;

if (blob.shape_string() == "(0)") {
cout<<"No data initialized"<<endl;
return;
}
// 从内向外打印数据
for(int u=0; u<blob.num(); u++){
for(int v=0; v<blob.channels(); v++){
for(int w=0; w<blob.height(); w++){
for(int x=0; x<blob.width(); x++){
// .data_at(,,,): 访问某个位置数据
cout<<"blob: "<<u<<v<<w<<x<<"->"<<blob.data_at(u,v,w,x)<<endl;
}
}
}
}
// 打印l1范数(绝对值之和) l2范数(平方和)
cout<<"ASUM = "<<blob.asum_data()<<endl;
cout<<"SUMQ = "<<blob.sumsq_data()<<endl;
return;
}

定义Blob

Blob.hpp中声明了构造函数,其用法见下例:

如果没有特殊指明,以下代码均在main.cpp中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 默认构造函数
caffe::Blob<float> a;

//指明shape,1*2*3*4=24 共有24个数值,初始值为0
caffe::Blob<float> b(1,2,3,4);

// 传入Shape每个维度为元素的vector
int shape_c[] = {1,3,2,4};
caffe::Blob<float> c(vector<int>(shape_c, shape_c+4));

writeInto(c);

// 将c大小从(1,3,2,4)变为(1,2,3,4)
c.Reshape(1,1,1,1);
printBlob(c);

// 将c变为与相同大小
c.ReshapeLike(b);
printBlob(c);

如果只打印最后一次数的c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Size: 1 2 3 4 (24)
Position: 0000-> value: 0
Position: 0001-> value: 1
Position: 0002-> value: 2
Position: 0003-> value: 3
Position: 0010-> value: 4
Position: 0011-> value: 5
Position: 0012-> value: 6
Position: 0013-> value: 7
Position: 0020-> value: 8
Position: 0021-> value: 9
Position: 0022-> value: 10
Position: 0023-> value: 11
Position: 0100-> value: 12
Position: 0101-> value: 13
Position: 0102-> value: 14
Position: 0103-> value: 15
Position: 0110-> value: 16
Position: 0111-> value: 17
Position: 0112-> value: 18
Position: 0113-> value: 19
Position: 0120-> value: 20
Position: 0121-> value: 21
Position: 0122-> value: 22
Position: 0123-> value: 23
ASUM = 276
SUMQ = 4324

从上可以明显看出,(num,channels,height,width)最右端变化最快,逐渐向左。

更新参数data

更新参数由Blob的Update()成员函数实现。具体是data=data-diff。所以要测试Updata(),首先就需要向data和diff分别读入数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义blob
caffe::Blob<float> a;
a.Reshape(1,2,3,4);

// 获取cpu数据data部分 和diff部分
float* dataPtr = a.mutable_cpu_data();
float* diffPtr = a.mutable_cpu_diff();

// 分别为data和diff写入值
for(int i=0;i<a.count();i++){
dataPtr[i] = i;
diffPtr[i] = a.count()-1-i;
}

// 执行更新操作
a.Update();

// 打印更新后的结果
printBlob(a);

更新后的data(待学习参数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Size: 1 2 3 4 (24)
Position: 0000-> value: -23
Position: 0001-> value: -21
Position: 0002-> value: -19
Position: 0003-> value: -17
Position: 0010-> value: -15
Position: 0011-> value: -13
Position: 0012-> value: -11
Position: 0013-> value: -9
Position: 0020-> value: -7
Position: 0021-> value: -5
Position: 0022-> value: -3
Position: 0023-> value: -1
Position: 0100-> value: 1
Position: 0101-> value: 3
Position: 0102-> value: 5
Position: 0103-> value: 7
Position: 0110-> value: 9
Position: 0111-> value: 11
Position: 0112-> value: 13
Position: 0113-> value: 15
Position: 0120-> value: 17
Position: 0121-> value: 19
Position: 0122-> value: 21
Position: 0123-> value: 23
ASUM = 288
SUMQ = 4600

由结果可看出,其实计算的是data=data-diff模型就是正向传播求diff,反向传播更新data

保存Blob数据到磁盘,或从磁盘再如数据到Blob

从Blob写入protobuff:.ToProto();从protobuff写入Blob:.FromProto

需要添加头文件#include <caffe/util/io.hpp>从而调用函数WriteProtoToBinaryFile()ReadProtoFromBinaryFileOrDie()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//g++编译时加上 -lglog -lboost_system

// 实例化一个BlobProto对象
caffe::BlobProto bp;
// 将a序列化,包括diff,写入protobuff
a.ToProto(&bp, true);
// 将bp对象写入磁盘文件“a.blob”
WriteProtoToBinaryFile(bp, "a.blob");


caffe::BlobProto bp2;
// 将磁盘文件“a.blob”内容读入protobuff对象bp2
ReadProtoFromBinaryFileOrDie("a.blob", &bp2);
caffe::Blob<float> b;
// 将protobuff中内容 写入b
b.FromProto(bp2, true);

printBlob(b);

上述过程从读取Blob,写入磁盘,后从磁盘读取文件,写入里一个Blob。这对于加载,保存模型参数(权值)很实用。


接下来从caffe.proto文件,读关于Blob的描述。

caffe-所使用的数据格式

caffe为什么使用LMDB和levelDB,而不是直接使用原始数据

第一,原始数据的类型,种类很多,如二进制文件,.npy文件,多种图像可是文件等等,不可能使用统一的代码来读取,所以转换成统一的格式可以简化数据读取层的实现。具体说是将不同类型的数据存储为key-value的对应关系。便于caffe DataLayer获取这些数据。

第二,使用LMDB和LEVELDB可以提高磁盘的IO利用率。

较新的caffe,都使用LMDBLEVELDB是caffe早起使用的。

什么是protobuff

ProtoBuff是一种实现内存和其他存储介质数据交换(从磁盘读取数据到内存,从内存保存数据到磁盘)的协议。在caffe中protobuff用于解析.prototxt文件,包括超参数设置,和模型结构的定义。protobuff将.prototxt文件中的配置参数按照caffe.proto的协议解析并加载到内存变量Caffe::SolverParameter对象中。自己可以尝试对一个solver.prototxt文件按照caffe.proto解析出参数.

在caffe中使用一个ProtoBuffer工具完成数据在存储介质间的数据交换。

什么是HDF5

HDF5是一种数据格式,同时还有处理这种数据格式的统一函数库。

HDF5, LMDB, ProtoBuff分别在什么时候使用

caffe 模型结构超参数的定义和训练超参数的定义,使用ProtoBuff(默认),可以看到相应的文件都是以.prototxt为后缀,也可以使用HDF5。而caffe所处理的数据,要转换成LMDB。

在caffe官方文档中,由关于data layer的描述:数据可以是来自高效的LMDB,levelDB数据库,可以使来自内存,或者来自HDF5或通用的图像格式的数据(当效率不是很重要时)。