1 第一个方法:Reshape()
template <typename Dtype>
void Blob<Dtype>::Reshape(const int num, const int channels, const int height,
const int width) {
CHECK_GE(num, 0);
CHECK_GE(channels, 0);
CHECK_GE(height, 0);
CHECK_GE(width, 0);
num_ = num;
channels_ = channels;
height_ = height;
width_ = width;
count_ = num_ * channels_ * height_ * width_;
if (count_ > capacity_) {
capacity_ = count_;
data_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
diff_.reset(new SyncedMemory(capacity_ * sizeof(Dtype)));
}
}
从源码中,可以看到,这个函数感觉不能随便乱用啊,直接根据给定的输入参数,直接修改了元数据的大小,并且如果count_超过了capacity_就重新开辟空间,注意,这里是重新开辟空间!!并没有将原来的数据放进去。就算count_没有超过capacity_,原来的数据不是就已经改变了吗?所以,这个函数不要轻易使用。
至于推荐使用的ReshapeLike(),其实也调用的Reshape(),如果对数据变换以及数据存储结构不是特别清楚,最要不要随便使用这两个函数!
然后再来看看构造函数:
在hpp中的声明是:
Blob()
: data_(), diff_(), num_(0), channels_(0), height_(0), width_(0),
count_(0), capacity_(0) {}
explicit Blob(const int num, const int channels, const int height,
const int width);
第一个构造函数应该说比较好理解,依次调用各种方法。第二个构造函数在cpp中有具体的实现:
template <typename Dtype>
Blob<Dtype>::Blob(const int num, const int channels, const int height,
const int width)
// capacity_ must be initialized before calling Reshape
: capacity_(0) {
Reshape(num, channels, height, width);
}
还是调用那个 Reshape(),不过这里相当于是初始化的过程,所以可以理解。
2 还记得在blob.hpp中定义了各种关于data的方法吗:
const Dtype* cpu_data() const;
void set_cpu_data(Dtype* data);
const Dtype* gpu_data() const;
const Dtype* cpu_diff() const;
const Dtype* gpu_diff() const;
Dtype* mutable_cpu_data();
Dtype* mutable_gpu_data();
Dtype* mutable_cpu_diff();
Dtype* mutable_gpu_diff();
根据上一篇博客( caffe源码阅读2)的介绍,现在再来看这些就应该很好理解了,除了 set_cpu_data()外,这里的方法都是返回数据,而且都是调用syncedmem里面的方法。
例如cpu_diff()的源码如下:
template <typename Dtype>
const Dtype* Blob<Dtype>::cpu_diff() const {
CHECK(data_);
return (const Dtype*)diff_->cpu_data();
}
3 在blob.cpp中接着的方法是 ShareData()和 ShareDiff(),在( caffe源码阅读1)中说得很不详细。这里再具体说一下,以 ShareDiff()的源码来进行说明:
template <typename Dtype>
void Blob<Dtype>::ShareDiff(const Blob& other) {
CHECK_EQ(count_, other.count());
diff_ = other.diff();
}
从源码中可以看到,也就是将参数(const Blob& other)中的数据共享(赋值)给自己。
4 呃,然后Updata(),源码如下:
// The "update" method is used for parameter blobs in a Net, which are stored
// as Blob<float> or Blob<double> -- hence we do not define it for
// Blob<int> or Blob<unsigned int>.
template <> void Blob<unsigned int>::Update() { NOT_IMPLEMENTED; }
template <> void Blob<int>::Update() { NOT_IMPLEMENTED; }
template <typename Dtype>
void Blob<Dtype>::Update() {
// We will perform update based on where the data is located.
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
// perform computation on 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
// perform computation on GPU
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.";
}
}
这里懵的原因如下:函数这样子重载真的可以吗?尽管注释得很详细,但是caffe_axpy()是什么啊?好吧,这个函数就先放放,后面读到了,再继续解读。
解释这里的问题,caffe_axpy()可以在caffe源码阅读-插曲-math_function.cpp中可以看到,用在updata中表达的意思就是:data = -1*diff + data;也就是权重更新的过程。
5 继续源码中的asum_data():
template <typename Dtype>
Dtype Blob<Dtype>::asum_data() const {
if (!data_) { return 0; }
switch (data_->head()) {
case SyncedMemory::HEAD_AT_CPU:
return caffe_cpu_asum(count_, cpu_data());
case SyncedMemory::HEAD_AT_GPU:
case SyncedMemory::SYNCED:
#ifndef CPU_ONLY
{
Dtype asum;
caffe_gpu_asum(count_, gpu_data(), &asum);
return asum;
}
#else
NO_GPU;
#endif
case SyncedMemory::UNINITIALIZED:
return 0;
default:
LOG(FATAL) << "Unknown SyncedMemory head state: " << data_->head();
}
return 0;
}
尽管这里也涉及到一个不认识的函数 caffe_cpu_asum(),不过在hpp中其实介绍的很清楚, asum_data()就是计算数据(data)的绝对值之和,或者说一阶范数。所以我想 caffe_cpu_asum()才是 一阶范数计算的具体实现吧。那么 asum_diff()也是一样的道理。
6 CopyFrom():
函数原型:void Blob<Dtype>::CopyFrom(const Blob& source, bool copy_diff, bool reshape)
应该可以知道,是从数据source中将数据拷贝到自己。
后面的两个参数:
copy_diff如果为true,则从source中拷贝diff数据,否则拷贝data数据;
reshape如果为true,则强制拷贝,否则当source的数据与自己的数据尺寸不同时,报错。
源码如下:
template <typename Dtype>
void Blob<Dtype>::CopyFrom(const Blob& source, bool copy_diff, bool reshape) {
if (num_ != source.num() || channels_ != source.channels() ||
height_ != source.height() || width_ != source.width()) {
if (reshape) {
Reshape(source.num(), source.channels(), source.height(), source.width());
} else {
LOG(FATAL) << "Trying to copy blobs of different sizes.";
}
}
switch (Caffe::mode()) {
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;
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.";
}
}
虽然这里涉及到函数caffe_copy,以及caffe::mode(),不过不影响阅读,使用过caffe的人应该都知道caffe::GPU和caffe::CPU是什么意思。
7 FromProto() 和 ToProto():
源码如下:
template <typename Dtype>
void Blob<Dtype>::FromProto(const BlobProto& proto) {
Reshape(proto.num(), proto.channels(), proto.height(), proto.width());
// copy data
Dtype* data_vec = mutable_cpu_data();
for (int i = 0; i < count_; ++i) {
data_vec[i] = proto.data(i);
}
if (proto.diff_size() > 0) {
Dtype* diff_vec = mutable_cpu_diff();
for (int i = 0; i < count_; ++i) {
diff_vec[i] = proto.diff(i);
}
}
}
template <typename Dtype>
void Blob<Dtype>::ToProto(BlobProto* proto, bool write_diff) const {
proto->set_num(num_);
proto->set_channels(channels_);
proto->set_height(height_);
proto->set_width(width_);
proto->clear_data();
proto->clear_diff();
const Dtype* data_vec = cpu_data();
for (int i = 0; i < count_; ++i) {
proto->add_data(data_vec[i]);
}
if (write_diff) {
const Dtype* diff_vec = cpu_diff();
for (int i = 0; i < count_; ++i) {
proto->add_diff(diff_vec[i]);
}
}
}
这两个方法阅读起来,理解上应该不会有什么问题, FromProto()就是从proto中拷贝数据给自己, ToProto()就是将自己的数据拷贝出去给proto。但是对我产生了几个疑问:
·BlobProto长什么样呢?当然估计和Blob差不多的。但是注意它的下标访问是用()而不是[]。
回答这个问题:BlobProto定义在caffe.proto中,源码为:
message BlobProto { optional int32 num = 1 [default = 0]; optional int32 channels = 2 [default = 0]; optional int32 height = 3 [default = 0]; optional int32 width = 4 [default = 0]; repeated float data = 5 [packed = true]; repeated float diff = 6 [packed = true]; }
至于为什么可以使用()来访问数据,应该与google protocol buffer的协议有关系吧。
·在FromProto()中为什么需要调用mutable_cpu_data()来获得数据,而不是直接存入自己的私有变量data_中呢?难道没有访问权限吗?不能啊,自己类的方法应该是可以访问自己的数据的吧?在ToProto()中也是同样的问题,不过这里调用的是cpu_data(),而不是mutable_cpu_data(),可能都还好理解一点,因为数据刚拷贝过来,当然知道数据的位置,所以调用mutable_cpu_data()也就同时设置数据头的位置。