1. 程式人生 > >caffe程式碼閱讀6:Filler的實現細節-2016.3.18

caffe程式碼閱讀6:Filler的實現細節-2016.3.18

一、Filler的作用簡介

Filler層的作用實際上就是根據proto中給出的引數對權重進行初始化,初始化的方式有很多種,分別為常量初始化(constant)、高斯分佈初始化(gaussian)、positive_unitball初始化、均勻分佈初始化(uniform)、xavier初始化、msra初始化、雙線性初始化(bilinear)這麼幾種。

二、Filler類的詳細介紹

首先了解一下Filler類的第一個函式:該函式把整個Filler類一下子就看明白了
template <typename Dtype>
Filler<Dtype>* GetFiller(const FillerParameter& param) {
  const std::string& type = param.type();
  if (type == "constant") {
    return new ConstantFiller<Dtype>(param);
  } else if (type == "gaussian") {
    return new GaussianFiller<Dtype>(param);
  } else if (type == "positive_unitball") {
    return new PositiveUnitballFiller<Dtype>(param);
  } else if (type == "uniform") {
    return new UniformFiller<Dtype>(param);
  } else if (type == "xavier") {
    return new XavierFiller<Dtype>(param);
  } else if (type == "msra") {
    return new MSRAFiller<Dtype>(param);
  } else if (type == "bilinear") {
    return new BilinearFiller<Dtype>(param);
  } else {
    CHECK(false) << "Unknown filler name: " << param.type();
  }
  return (Filler<Dtype>*)(NULL);

根據給定的引數獲取對應的Filler,由該段程式碼可以看出proto檔案裡面對於權重可以有哪些指定的初始化方式。


1)基類Filler

template <typename Dtype>
class Filler {
 public:
 // 建構函式
  explicit Filler(const FillerParameter& param) : filler_param_(param) {}
  // 解構函式,並且是虛擬函式
  virtual ~Filler() {}
  // 純虛擬函式,繼承的子類必須要實現
  virtual void Fill(Blob<Dtype>* blob) = 0;
 protected:
  FillerParameter filler_param_;
};  // class Filler

2)繼承Filler的類

2-1常量初始化類

template <typename Dtype>
class ConstantFiller : public Filler<Dtype> {
 public:
  explicit ConstantFiller(const FillerParameter& param)
      : Filler<Dtype>(param) {}
  virtual void Fill(Blob<Dtype>* blob) {
    // 獲取資料指標
    Dtype* data = blob->mutable_cpu_data();
    // 獲取資料長度
    const int count = blob->count();
    // 獲取常量初始化的常數值
    const Dtype value = this->filler_param_.value();
    CHECK(count);
    for (int i = 0; i < count; ++i) {
      data[i] = value;//對於每一個元素都初始化為常數值
    }
    CHECK_EQ(this->filler_param_.sparse(), -1)
         << "Sparsity not supported by this Filler.";
  }
};

2-2均勻分佈初始化類

template <typename Dtype>
class UniformFiller : public Filler<Dtype> {
 public:
  explicit UniformFiller(const FillerParameter& param)
      : Filler<Dtype>(param) {}
  virtual void Fill(Blob<Dtype>* blob) {
    // 檢查blob中的元素是否為0
    CHECK(blob->count());
    // 呼叫caffe_rng_uniform進行初始化
    caffe_rng_uniform<Dtype>(blob->count(), Dtype(this->filler_param_.min()),
        Dtype(this->filler_param_.max()), blob->mutable_cpu_data());
    // 均勻分佈初始化是不支援稀疏特性的
    CHECK_EQ(this->filler_param_.sparse(), -1)
         << "Sparsity not supported by this Filler.";
  }
};

2-3高斯分佈初始化類(支援稀疏特性)

template <typename Dtype>
class GaussianFiller : public Filler<Dtype> {
 public:
  explicit GaussianFiller(const FillerParameter& param)
      : Filler<Dtype>(param) {}
  virtual void Fill(Blob<Dtype>* blob) {
    Dtype* data = blob->mutable_cpu_data();
    CHECK(blob->count());
    // 呼叫caffe_rng_gaussian初始化、其中輸入了高斯分佈的均值和標準差
    caffe_rng_gaussian<Dtype>(blob->count(), Dtype(this->filler_param_.mean()),
        Dtype(this->filler_param_.std()), blob->mutable_cpu_data());
    int sparse = this->filler_param_.sparse();
    // 檢查sparse > -1
    CHECK_GE(sparse, -1);
    if (sparse >= 0) {//  如果啟用稀疏的話
      // Sparse initialization is implemented for "weight" blobs; i.e. matrices.
      // These have num == channels == 1; width is number of inputs; height is
      // number of outputs.  The 'sparse' variable specifies the mean number
      // of non-zero input weights for a given output.
      CHECK_GE(blob->num_axes(), 1);
      // 假設權重的形狀是 輸出單元個數 X輸入單元個數
      // blob->shape(0) = 輸出單元的個數
      const int num_outputs = blob->shape(0);
      // 不為0的概率 = 1/輸出單元個數
      // 那麼為0的概率= 1 - 1/輸出單元個數
      Dtype non_zero_probability = Dtype(sparse) / Dtype(num_outputs);
      // 新建一個rand_vec,使用者存放伯努利分佈(二項分佈)所生成的值
      rand_vec_.reset(new SyncedMemory(blob->count() * sizeof(int)));
      int* mask = reinterpret_cast<int*>(rand_vec_->mutable_cpu_data());
      caffe_rng_bernoulli(blob->count(), non_zero_probability, mask);
      for (int i = 0; i < blob->count(); ++i) {
        data[i] *= mask[i];// 每一個數據元素都與生成的二項分佈的樣本值相乘
      }
    }
  }

 protected:
  shared_ptr<SyncedMemory> rand_vec_;
};

2-4PositiveUnitballFiller初始化

相當於是一個單位球
// PositiveUnitballFiller首先用均勻分佈填充W
// 然後將W中的元素按行求和,然後該行每一個的元素都除以該行的和
template <typename Dtype>
class PositiveUnitballFiller : public Filler<Dtype> {
 public:
  explicit PositiveUnitballFiller(const FillerParameter& param)
      : Filler<Dtype>(param) {}
  virtual void Fill(Blob<Dtype>* blob) {
    Dtype* data = blob->mutable_cpu_data();
    DCHECK(blob->count());// 我很奇怪為啥這裡用DCHECK
    // 先填充均勻分佈到權重
    caffe_rng_uniform<Dtype>(blob->count(), 0, 1, blob->mutable_cpu_data());
    // We expect the filler to not be called very frequently, so we will
    // just use a simple implementation
    // count / num = 輸入的維度
    int dim = blob->count() / blob->num();
    CHECK(dim);// 檢查輸入維度是否小於0
    for (int i = 0; i < blob->num(); ++i) {// 遍歷隱藏單元的個數(或者是輸出單元的個數)
      Dtype sum = 0;
      for (int j = 0; j < dim; ++j) {
        sum += data[i * dim + j];//sum += data[i][j] 也就是說要按行求和
      }
      for (int j = 0; j < dim; ++j) {
        data[i * dim + j] /= sum;// 每一行都除以該行的和
      }
    }
    CHECK_EQ(this->filler_param_.sparse(), -1)
         << "Sparsity not supported by this Filler.";
  }
};

2-5 XavierFiller初始化(用於卷積核)

 
// 這裡不明白的就是shape (num, a, b, c) where a * b * c = fan_in and num * b * c = fan_out
 // 扇入和扇出的定義了
// 感謝王峰,後來才知道b*c=kernel size
// a是輸入的channel
// num是輸出的channel
template <typename Dtype>
class XavierFiller : public Filler<Dtype> {
 public:
  explicit XavierFiller(const FillerParameter& param)
      : Filler<Dtype>(param) {}
  virtual void Fill(Blob<Dtype>* blob) {
    CHECK(blob->count());
    int fan_in = blob->count() / blob->num();
    int fan_out = blob->count() / blob->channels();
    Dtype n = fan_in;  // default to fan_in
    if (this->filler_param_.variance_norm() ==// 如果引數裡面定義了方差歸一化則n = 扇入+扇出
        FillerParameter_VarianceNorm_AVERAGE) {
      n = (fan_in + fan_out) / Dtype(2);
    } else if (this->filler_param_.variance_norm() ==
        FillerParameter_VarianceNorm_FAN_OUT) {
      n = fan_out;
    }
    Dtype scale = sqrt(Dtype(3) / n);// scale = \frac{sqrt{3}}{n}
    // 然後用[-scale,scale]的均勻分佈初始化
    caffe_rng_uniform<Dtype>(blob->count(), -scale, scale,
        blob->mutable_cpu_data());
    CHECK_EQ(this->filler_param_.sparse(), -1)
         << "Sparsity not supported by this Filler.";
  }
};

2-6 MSRAFiller初始化方式(用於卷積核)

template <typename Dtype>
class MSRAFiller : public Filler<Dtype> {
 public:
  explicit MSRAFiller(const FillerParameter& param)
      : Filler<Dtype>(param) {}
  virtual void Fill(Blob<Dtype>* blob) {
    CHECK(blob->count());
    int fan_in = blob->count() / blob->num();
    int fan_out = blob->count() / blob->channels();
    Dtype n = fan_in;  // default to fan_in
    if (this->filler_param_.variance_norm() ==
        FillerParameter_VarianceNorm_AVERAGE) {
      n = (fan_in + fan_out) / Dtype(2);
    } else if (this->filler_param_.variance_norm() ==
        FillerParameter_VarianceNorm_FAN_OUT) {
      n = fan_out;
    }
    // 標準差是\sqrt{\frac{2}{n}}
    Dtype std = sqrt(Dtype(2) / n);
    caffe_rng_gaussian<Dtype>(blob->count(), Dtype(0), std,
        blob->mutable_cpu_data());
    CHECK_EQ(this->filler_param_.sparse(), -1)
         << "Sparsity not supported by this Filler.";
  }
};

2-7 BilinearFiller初始化(使用者反捲積核)

 
// 反捲積所用的初始化,不支援稀疏特性
 // 沒研究過。。。也不知道
template <typename Dtype>
class BilinearFiller : public Filler<Dtype> {
 public:
  explicit BilinearFiller(const FillerParameter& param)
      : Filler<Dtype>(param) {}
  virtual void Fill(Blob<Dtype>* blob) {
    CHECK_EQ(blob->num_axes(), 4) << "Blob must be 4 dim.";
    CHECK_EQ(blob->width(), blob->height()) << "Filter must be square";
    Dtype* data = blob->mutable_cpu_data();
    // f是寬度除以2
    int f = ceil(blob->width() / 2.);
    // c的含義不明白
    float c = (2 * f - 1 - f % 2) / (2. * f);
    for (int i = 0; i < blob->count(); ++i) {
      float x = i % blob->width();// x表示列的索引
      float y = (i / blob->width()) % blob->height();// 行的索引%寬度
      data[i] = (1 - fabs(x / f - c)) * (1 - fabs(y / f - c));
    }
    CHECK_EQ(this->filler_param_.sparse(), -1)
         << "Sparsity not supported by this Filler.";
  }
};

三、與Filler類相關類的介紹

因為Filler用到了關於隨機數生成的一些方法,下面來看下math_function的相關實現:

(1)高斯分佈隨機數的生成:

CPU上的實現(直接呼叫Boost的庫了)
template <typename Dtype>
void caffe_rng_gaussian(const int n, const Dtype a,
                        const Dtype sigma, Dtype* r) {
  CHECK_GE(n, 0);
  CHECK(r);
  CHECK_GT(sigma, 0);
  // 直接呼叫boost中的正太分佈了。
  boost::normal_distribution<Dtype> random_distribution(a, sigma);
  boost::variate_generator<caffe::rng_t*, boost::normal_distribution<Dtype> >
      variate_generator(caffe_rng(), random_distribution);
  for (int i = 0; i < n; ++i) {
    r[i] = variate_generator();
  }
}

GPU的實現(直接呼叫CUDA的庫了)
template <>
void caffe_gpu_rng_gaussian(const int n, const float mu, const float sigma,
                            float* r) {
  CURAND_CHECK(
      curandGenerateNormal(Caffe::curand_generator(), r, n, mu, sigma));
}

template <>
void caffe_gpu_rng_gaussian(const int n, const double mu, const double sigma,
                            double* r) {
  CURAND_CHECK(
      curandGenerateNormalDouble(Caffe::curand_generator(), r, n, mu, sigma));
}

(2)均勻分佈隨機數的生成:

CPU:
template <typename Dtype>
void caffe_rng_uniform(const int n, const Dtype a, const Dtype b, Dtype* r) {
  CHECK_GE(n, 0);
  CHECK(r);
  CHECK_LE(a, b);
  // 呼叫Boost的庫
  boost::uniform_real<Dtype> random_distribution(a, caffe_nextafter<Dtype>(b));
  boost::variate_generator<caffe::rng_t*, boost::uniform_real<Dtype> >
      variate_generator(caffe_rng(), random_distribution);
  for (int i = 0; i < n; ++i) {
    r[i] = variate_generator();
  }
}

GPU:
void caffe_gpu_rng_uniform(const int n, unsigned int* r) {
  CURAND_CHECK(curandGenerate(Caffe::curand_generator(), r, n));
}

template <>
void caffe_gpu_rng_uniform<float>(const int n, const float a, const float b,
                                  float* r) {
  CURAND_CHECK(curandGenerateUniform(Caffe::curand_generator(), r, n));
  const float range = b - a;
  if (range != static_cast<float>(1)) {
    caffe_gpu_scal(n, range, r);
  }
  if (a != static_cast<float>(0)) {
    caffe_gpu_add_scalar(n, a, r);
  }
}

template <>
void caffe_gpu_rng_uniform<double>(const int n, const double a, const double b,
                                   double* r) {
  CURAND_CHECK(curandGenerateUniformDouble(Caffe::curand_generator(), r, n));
  const double range = b - a;
  if (range != static_cast<double>(1)) {
    caffe_gpu_scal(n, range, r);
  }
  if (a != static_cast<double>(0)) {
    caffe_gpu_add_scalar(n, a, r);
  }
}

(3)伯努利分佈(二項分佈)隨機數的生成(竟然沒有GPU上的程式碼。。。)

template <typename Dtype>
void caffe_rng_bernoulli(const int n, const Dtype p, int* r) {
  CHECK_GE(n, 0);
  CHECK(r);
  CHECK_GE(p, 0);
  CHECK_LE(p, 1);
  boost::bernoulli_distribution<Dtype> random_distribution(p);
  boost::variate_generator<caffe::rng_t*, boost::bernoulli_distribution<Dtype> >
      variate_generator(caffe_rng(), random_distribution);
  for (int i = 0; i < n; ++i) {
    r[i] = variate_generator();
  }
}
void caffe_rng_bernoulli(const int n, const Dtype p, unsigned int* r) {
  CHECK_GE(n, 0);
  CHECK(r);
  CHECK_GE(p, 0);
  CHECK_LE(p, 1);
  boost::bernoulli_distribution<Dtype> random_distribution(p);
  boost::variate_generator<caffe::rng_t*, boost::bernoulli_distribution<Dtype> >
      variate_generator(caffe_rng(), random_distribution);
  for (int i = 0; i < n; ++i) {
    r[i] = static_cast<unsigned int>(variate_generator());
  }
}

四、總結

主要介紹了Filler中初始化權重各個演算法的具體的實現,具體原理可以參考相關的論文。關於Filler其實沒啥可以深挖的。已經被挖得差不多了。