基于Caffe的DeepID2实现(下)

小喵的唠叨话:这次的博客,真心累伤了小喵的心。但考虑到知识需要巩固和分享,小喵决定这次把剩下的内容都写完。

四、数据的重整,简单的划分

前面的Data层用于生成成对的输入数据,Normalization层,用于将feature归一化,那么之后是不是就可以使用ContrastiveLoss层进行训练了呢?

且慢,还差一步。

ContrastiveLoss层要求有3个bottom:feature1、feature2以及表示对位的feature是否为同一个identity的label。

我们现在得到的feature却是所有的都在一起,data层直接得到的label也和这里要求的label不同。因此务必要对数据进行一次重整。

一个简单的规则就是按照奇偶,将feature划分成两部分。这样得到的两部分正好就是相同位置为一对。对于label的重整,也可以用类似的方法。小喵这里只对feature进行重整,而label的处理则是通过改ContrastiveLoss层来实现。

feature的重整本质上就是一个切片的操作,这里命名为id2_slice_layer,实现方法就是按照奇偶把bottom的数据复制到top。后馈的时候,也就是将两部分的feature的diff都直接复制到对应位置的bottom_diff中,具体实现如下:

// created by miao
#ifndef CAFFE_ID2_SLICE_LAYER_HPP_
#define CAFFE_ID2_SLICE_LAYER_HPP_

#include <vector>

#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"

namespace caffe {

/**
 * @brief Takes a Blob and slices it along either the num or channel dimension,
 *        outputting multiple sliced Blob results.
 *
 * TODO(dox): thorough documentation for Forward, Backward, and proto params.
 */
template <typename Dtype>
class Id2SliceLayer : public Layer<Dtype> {
 public:
  explicit Id2SliceLayer(const LayerParameter& param)
      : Layer<Dtype>(param) {}
  virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);

  virtual inline const char* type() const { return "Id2Slice"; }
  virtual inline int ExactNumBottomBlobs() const { return 1; }
  virtual inline int MinTopBlobs() const { return 1; }

 protected:
  virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
  virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
};

}  // namespace caffe

#endif  // CAFFE_ID2_SLICE_LAYER_HPP_

头文件,巨简单。。。

Cpp的代码,也非常简单,要注意id2_slice层的top有两个,每个的形状都是bottom的一半。

// created by miao
#include <algorithm>
#include <vector>

#include "caffe/layers/id2_slice_layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {

template <typename Dtype>
void Id2SliceLayer<Dtype>::LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
}

template <typename Dtype>
void Id2SliceLayer<Dtype>::Reshape(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
    vector<int> top_shape = bottom[0]->shape();
    top_shape[0] /= 2;
    top[0]->Reshape(top_shape);
    top[1]->Reshape(top_shape);
}

template <typename Dtype>
void Id2SliceLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
    const int feature_size = bottom[0]->count(1);
    for (int n = 0; n < bottom[0]->num(); ++ n) {
        caffe_copy(
                feature_size, 
                bottom[0]->cpu_data() + n * feature_size, 
                top[n & 1]->mutable_cpu_data() + (n / 2) * feature_size
                );
    }
}

template <typename Dtype>
void Id2SliceLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
    const int feature_size = bottom[0]->count(1);
    for (int n = 0; n < bottom[0]->num(); ++ n) {
        caffe_copy(
                feature_size,
                top[n & 1]->cpu_diff() + (n / 2) * feature_size,
                bottom[0]->mutable_cpu_diff() + n * feature_size
                );
    }
}

#ifdef CPU_ONLY
STUB_GPU(Id2SliceLayer);
#endif

INSTANTIATE_CLASS(Id2SliceLayer);
REGISTER_LAYER_CLASS(Id2Slice);

}  // namespace caffe

GPU上的实现,为了简单起见,也是直接调用了CPU的前馈函数。

// created by miao
#include <vector>

#include "caffe/layers/id2_slice_layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {
template <typename Dtype>
void Id2SliceLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top) {
    this->Forward_cpu(bottom, top);
}

template <typename Dtype>
void Id2SliceLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
    this->Backward_cpu(top, propagate_down, bottom);
}

INSTANTIATE_LAYER_GPU_FUNCS(Id2SliceLayer);

}  // namespace caffe

这样就完成了feature的重整。由于没有用到新的参数,因此也不需要修改caffe.proto。

亲可以仿照这个方法对label来做类似的操作。鉴于小喵比较懒。。。这里就只是简单的改ContrastiveLoss层的代码了。

第一步,在ContrastiveLossLayer中新增一个用于记录feature pair是否是同一个identity的成员变量,取代原本的第三个bottom的功能。这样只需要在前馈的时候提前算好,就可以代替之前的第三个bottom来使用,而不需要再修改别的地方的代码。

为了大家使用的方便,小喵直接把修改之后的头文件粘贴出来(删掉注释)。新增的行,用“added by miao”这个注释标注出来。头文件只加了一行。

#ifndef CAFFE_CONTRASTIVE_LOSS_LAYER_HPP_
#define CAFFE_CONTRASTIVE_LOSS_LAYER_HPP_

#include <vector>

#include "caffe/blob.hpp"
#include "caffe/layer.hpp"
#include "caffe/proto/caffe.pb.h"

#include "caffe/layers/loss_layer.hpp"

namespace caffe {
template <typename Dtype>
class ContrastiveLossLayer : public LossLayer<Dtype> {
 public:
  explicit ContrastiveLossLayer(const LayerParameter& param)
      : LossLayer<Dtype>(param), diff_() {}
  virtual void LayerSetUp(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);

  virtual inline int ExactNumBottomBlobs() const { return 3; }
  virtual inline const char* type() const { return "ContrastiveLoss"; }
  virtual inline bool AllowForceBackward(const int bottom_index) const {
    return bottom_index != 2;
  }
 protected:
  /// @copydoc ContrastiveLossLayer
  virtual void Forward_cpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Forward_gpu(const vector<Blob<Dtype>*>& bottom,
      const vector<Blob<Dtype>*>& top);
  virtual void Backward_cpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);
  virtual void Backward_gpu(const vector<Blob<Dtype>*>& top,
      const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom);

  Blob<Dtype> diff_;  // cached for backward pass
  Blob<Dtype> dist_sq_;  // cached for backward pass
  Blob<Dtype> diff_sq_;  // tmp storage for gpu forward pass
  Blob<Dtype> summer_vec_;  // tmp storage for gpu forward pass
  Blob<Dtype> is_same_; // added by miao
};
}  // namespace caffe

#endif  // CAFFE_CONTRASTIVE_LOSS_LAYER_HPP_

源文件的修改也十分简单,这里只贴出来Cuda的部分。源文件,修改了与原来的bottom3相关的地方。

#include <algorithm>
#include <vector>
#include <iostream>
#include "caffe/layers/contrastive_loss_layer.hpp"
#include "caffe/util/math_functions.hpp"

namespace caffe {

template <typename Dtype>
void ContrastiveLossLayer<Dtype>::Forward_gpu(
    const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
  const int count = bottom[0]->count();
  caffe_gpu_sub(
      count,
      bottom[0]->gpu_data(),  // a
      bottom[1]->gpu_data(),  // b
      diff_.mutable_gpu_data());  // a_i-b_i
  caffe_gpu_powx(
      count,
      diff_.mutable_gpu_data(),  // a_i-b_i
      Dtype(2),
      diff_sq_.mutable_gpu_data());  // (a_i-b_i)^2
  caffe_gpu_gemv(
      CblasNoTrans,
      bottom[0]->num(),
      bottom[0]->channels(),
      Dtype(1.0),
      diff_sq_.gpu_data(),  // (a_i-b_i)^2
      summer_vec_.gpu_data(),
      Dtype(0.0),
      dist_sq_.mutable_gpu_data());  // Sum (a_i-b_i)^2
  Dtype margin = this->layer_param_.contrastive_loss_param().margin();
  bool legacy_version =
      this->layer_param_.contrastive_loss_param().legacy_version();
  Dtype loss(0.0);
  for (int i = 0; i < bottom[0]->num(); ++i) {
    // added by miao
    is_same_.mutable_cpu_data()[i] = (bottom[2]->cpu_data()[2 * i] == bottom[2]->cpu_data()[2 * i + 1])? 1:0;
    if (is_same_.cpu_data()[i] == 1) {  // similar pairs
      loss += dist_sq_.cpu_data()[i];
    } else {  // dissimilar pairs
      if (legacy_version) {
        loss += std::max(margin - dist_sq_.cpu_data()[i], Dtype(0.0));
      } else {
        Dtype dist = std::max(margin - sqrt(dist_sq_.cpu_data()[i]),
                              Dtype(0.0));
        loss += dist*dist;
      }
    }
  }
  loss = loss / static_cast<Dtype>(bottom[0]->num()) / Dtype(2);
  top[0]->mutable_cpu_data()[0] = loss;
}

template <typename Dtype>
__global__ void CLLBackward(const int count, const int channels,
    const Dtype margin, const bool legacy_version, const Dtype alpha,
    const Dtype* y, const Dtype* diff, const Dtype* dist_sq,
    Dtype *bottom_diff) {
  CUDA_KERNEL_LOOP(i, count) {
    int n = i / channels;  // the num index, to access y and dist_sq
    if (static_cast<int>(y[n])) {  // similar pairs
      bottom_diff[i] = alpha * diff[i];
    } else {  // dissimilar pairs
      Dtype mdist(0.0);
      Dtype beta(0.0);
      if (legacy_version) {
        mdist = (margin - dist_sq[n]);
        beta = -alpha;
      } else {
        Dtype dist = sqrt(dist_sq[n]);
        mdist = (margin - dist);
        beta = -alpha * mdist / (dist + Dtype(1e-4)) * diff[i];
      }
      if (mdist > 0.0) {
        bottom_diff[i] = beta;
      } else {
        bottom_diff[i] = 0;
      }
    }
  }
}

template <typename Dtype>
void ContrastiveLossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top,
    const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) {
  for (int i = 0; i < 2; ++i) {
    if (propagate_down[i]) {
      const int count = bottom[0]->count();
      const int channels = bottom[0]->channels();
      Dtype margin = this->layer_param_.contrastive_loss_param().margin();
      const bool legacy_version =
          this->layer_param_.contrastive_loss_param().legacy_version();
      const Dtype sign = (i == 0) ? 1 : -1;
      const Dtype alpha = sign * top[0]->cpu_diff()[0] /
          static_cast<Dtype>(bottom[0]->num());
      // NOLINT_NEXT_LINE(whitespace/operators)
      CLLBackward<Dtype><<<CAFFE_GET_BLOCKS(count), CAFFE_CUDA_NUM_THREADS>>>(
          count, channels, margin, legacy_version, alpha,
          is_same_.gpu_data(),  // pair similarity 0 or 1  added by miao
          diff_.gpu_data(),  // the cached eltwise difference between a and b
          dist_sq_.gpu_data(),  // the cached square distance between a and b
          bottom[i]->mutable_gpu_diff());
      CUDA_POST_KERNEL_CHECK;
    }
  }
}

INSTANTIATE_LAYER_GPU_FUNCS(ContrastiveLossLayer);

}  // namespace caffe

需要注意的时候,前馈和后馈都需要做一点代码上的修改,虽说十分的简单,但也要小心。 至此,基于Caffe的DeepID2的修改全部完成。

最后,十分感谢小喵的师弟,胖喵的审查,每一篇博客都由胖喵第一手的阅读并提出意见。  

转载请注明出处~