caffe源码阅读(1)——math_functions

不定期在知乎发布个人对于caffe源码阅读的心得。既是学习笔记,也是一个对于自己的督促。

1. 本文涉及到的文件:

math_functions是深度学习开源框架Caffe底层的数学运算函数。它包装了各种CBLAS(Basic Linear Algebra Subprograms)底层库,并给出了统一的接口。

了解math_functions对于我们后面分析各个Layer非常重要。

math_functions的声明和定义在如下路径中:

${CAFFE_ROOT}/include/util/math_functions.hpp
${CAFFE_ROOT}/src/util/math_functions.cpp

2. 主要函数及其说明

(注:在Caffe源代码中,特化了float和double两种函数模板,此处仅以float定义为例)

1. caffe_cpu_gemm

template <>
void caffe_cpu_gemm<float>(const CBLAS_TRANSPOSE TransA,
    const CBLAS_TRANSPOSE TransB, const int M, const int N, const int K,
    const float alpha, const float* A, const float* B, const float beta,
    float* C) {
  int lda = (TransA == CblasNoTrans) ? K : M;
  int ldb = (TransB == CblasNoTrans) ? N : K;
  cblas_sgemm(CblasRowMajor, TransA, TransB, M, N, K, alpha, A, lda, B,
      ldb, beta, C, N);
}

功能:

接收三个矩阵A, B, CA \in \mathbb{R}^{M \times K}, B \in \mathbb{R}^{K \times N}, C \in \mathbb{R}^{M \times N})并计算:

C := \alpha \cdot A \cdot B + \beta \cdot C (完成简单的矩阵乘法/加法运算)

参数:

(1)TransA / TransB:对于矩阵A / B是否做转置(CBlasTrans, CBlasNoTrans);

(2)M:矩阵A, C的行数;

(3)N:矩阵B, C的列数;

(4)K:矩阵 A 的列数,矩阵 B 的函数;

( 2,3,4的意思就是:A \in \mathbb{R}^{M \times K}, B \in \mathbb{R}^{K \times N}, C \in \mathbb{R}^{M \times N}

(5)alpha,beta:系数;

(6)A, B, C:3个矩阵



2. caffe_cpu_gemv

template <>
void caffe_cpu_gemv<float>(const CBLAS_TRANSPOSE TransA, const int M,
    const int N, const float alpha, const float* A, const float* x,
    const float beta, float* y) {
  cblas_sgemv(CblasRowMajor, TransA, M, N, alpha, A, N, x, 1, beta, y, 1);
}

功能:

接收一个矩阵 AA \in \mathbb{R}^{M \times N}),以及两个向量xyx \in \mathbb{R}^{N \times 1}y \in \mathbb{R}^{M \times 1})。进行如下计算:


y := \alpha \cdot A \cdot x + \beta \cdot y

参数:

(1)TransA:对于矩阵A 是否做转置(CBlasTrans, CBlasNoTrans);

(2)M:矩阵A的行数;

(3)N:矩阵A的列数;

(4)alpha,beta:系数;

(6)A, x, y:其中A为矩阵,x和y为向量。

(注:cblas_sgemv中的1代表Stride With X/Y,例如若 incX = incY = 7,那么每7个数据才会被使用,一般设置为1就好)



3. caffe_axpy

template <>
void caffe_axpy<float>(const int N, const float alpha, const float* X,
    float* Y) { cblas_saxpy(N, alpha, X, 1, Y, 1); }

功能:

接收两个向量 xyx \in \mathbb{R}^{N \times 1}y \in \mathbb{R}^{M \times 1})。进行如下计算:

y := \alpha \cdot x +  y (在 y 的基础上,自加 alpha · x)

参数:

(1)N:向量x和y的元素数量;

(2)alpha:系数;

(3)x, y:向量(运算完成后,X不变,Y自加)



4. caffe_set

template <typename Dtype>
void caffe_set(const int N, const Dtype alpha, Dtype* Y) {
  if (alpha == 0) {
    memset(Y, 0, sizeof(Dtype) * N);  // NOLINT(caffe/alt_fn)
    return;
  }
  for (int i = 0; i < N; ++i) {
    Y[i] = alpha;
  }
}

template void caffe_set<int>(const int N, const int alpha, int* Y);
template void caffe_set<float>(const int N, const float alpha, float* Y);
template void caffe_set<double>(const int N, const double alpha, double* Y);

功能:

用常数\alphaY 进行初始化,在caffe源代码中,声明了模板函数,并特化了int,float,double三种类型;

(注1:memset函数常用作,将s当前位置后面n个字节(typedef unsigned int size_t)用ch替换并返回s的指针);

void *memset(void *s, int ch, size_t n);

(注2:由于向量、矩阵在内存中都是连续排列,因此,本函数实际上也可对矩阵进行初始化)。



5. caffe_add_scalar

template <>
void caffe_add_scalar(const int N, const float alpha, float* Y) {
  for (int i = 0; i < N; ++i) {
    Y[i] += alpha;
  }
}

功能:

实现了简单的向量和标量相加的运算:

y[i] := y[i] + \alpha; \ \ \   \mathrm{for} \ i=1,2,...,N


6. caffe_copy

template <typename Dtype>
void caffe_copy(const int N, const Dtype* X, Dtype* Y) {
  if (X != Y) {
    if (Caffe::mode() == Caffe::GPU) {
#ifndef CPU_ONLY
      // NOLINT_NEXT_LINE(caffe/alt_fn)
      CUDA_CHECK(cudaMemcpy(Y, X, sizeof(Dtype) * N, cudaMemcpyDefault));
#else
      NO_GPU;
#endif
    } else {
      memcpy(Y, X, sizeof(Dtype) * N);  // NOLINT(caffe/alt_fn)
    }
  }
}

template void caffe_copy<int>(const int N, const int* X, int* Y);
template void caffe_copy<unsigned int>(const int N, const unsigned int* X,
    unsigned int* Y);
template void caffe_copy<float>(const int N, const float* X, float* Y);
template void caffe_copy<double>(const int N, const double* X, double* Y);

功能:

从源向量X中,拷贝N个数据给目标向量Y。

y[i] = x[i]; \ \ \   \mathrm{for} \ i=1,2,...,N

(注:C++中的内存拷贝函数,从源src所指的内存地址的起始位置开始拷贝n个字节到目标dest所指的内存地址的起始位置中)。

void *memcpy(void *dest, const void *src, size_t n);

(小技巧:在形参列表中,X是const Dtype*类型的,而Y是Dtype*类型的,观察到这一点,可以帮助我们快速确定谁是源向量,谁是目标向量)



7. caffe_scal

template <>
void caffe_scal<float>(const int N, const float alpha, float *X) {
  cblas_sscal(N, alpha, X, 1);
}
功能:

将原向量与标量\alpha相乘,并赋给自身:

x[i]:=\alpha \cdot x[i]; \ \ \ \mathrm{for}\ i=1,2,...,N

(注:cblas库中最后一个“1”代表:Stride with x,举个例子,如果incX=7的话,那么向量x,每7个数中,才有一个会乘以\alpha。一般我们设置成为1就好)。


8. caffe_cpu_axpby

template <>
void caffe_cpu_axpby<float>(const int N, const float alpha, const float* X,
                            const float beta, float* Y) {
  cblas_saxpby(N, alpha, X, 1, beta, Y, 1);
}

功能:

接收两个向量x和y(x \in \mathbb{R}^{N \times 1} \ \ y \in \mathbb{R}^{N \times 1}),计算\alpha \cdot x + \beta \cdot y并将结果赋给y

y:=\alpha \cdot x + \beta \cdot y

参数:

(1)N:向量x和y的长度;

(2)alpha,beta:系数;

(3)x, y:数据向量,注意它们的类型分别为:const float* 和float*


9. 一系列caffe四则运算函数

template <>
void caffe_add<float>(const int n, const float* a, const float* b,
    float* y) {
  vsAdd(n, a, b, y);
}

/*********************************************/

template <>
void caffe_sub<float>(const int n, const float* a, const float* b,
    float* y) {
  vsSub(n, a, b, y);
}

/*********************************************/

template <>
void caffe_mul<float>(const int n, const float* a, const float* b,
    float* y) {
  vsMul(n, a, b, y);
}

/*********************************************/

template <>
void caffe_div<float>(const int n, const float* a, const float* b,
    float* y) {
  vsDiv(n, a, b, y);
}

/*********************************************/

template <>
void caffe_powx<float>(const int n, const float* a, const float b,
    float* y) {
  vsPowx(n, a, b, y);
}

/*********************************************/

template <>
void caffe_sqr<float>(const int n, const float* a, float* y) {
  vsSqr(n, a, y);
}

/*********************************************/

template <>
void caffe_exp<float>(const int n, const float* a, float* y) {
  vsExp(n, a, y);
}

/*********************************************/

template <>
void caffe_log<float>(const int n, const float* a, float* y) {
  vsLn(n, a, y);
}

/*********************************************/

template <>
void caffe_abs<float>(const int n, const float* a, float* y) {
    vsAbs(n, a, y);
}

功能:

这一系列函数虽然多,但都属于“见名知意”。实现了加减乘除、指数、开方、对数、绝对值、exp等操作。这其中有单操作数的,也有两个操作数的。但无一例外地,运算结果都赋给了向量y。

另一个值得注意的点,它们都是element-wise的。


10. caffe_cpu_strided_dot / caffe_cpu_dot

template <>
double caffe_cpu_strided_dot<double>(const int n, const double* x,
    const int incx, const double* y, const int incy) {
  return cblas_ddot(n, x, incx, y, incy);
}

/*********************************************/

template <typename Dtype>
Dtype caffe_cpu_dot(const int n, const Dtype* x, const Dtype* y) {
  return caffe_cpu_strided_dot(n, x, 1, y, 1);
}

功能:

首先,之所以将这两个函数放在一起,是因为他们很相像。

caffe_cpu_strided_dot中,返回了向量x和y的内积,但还需指定两个参数incxincy。而caffe_cpu_dot中则默认了incx = incy = 1;

之所以做这样的设定,是因为在卷积中,设置stride是非常正常且必要的部分;

因此,为方便程序的可读性,笔者在此建议,当我们只是想计算两个向量内积的时候,直接调用“caffe_cpu_dot”,而当确实需要用到stride这个参量的时候,才调用完整版本“caffe_cpu_strided_dot"。


11. caffe_cpu_asum

template <>
float caffe_cpu_asum<float>(const int n, const float* x) {
  return cblas_sasum(n, x, 1);
}

功能:

计算向量x中所有元素的绝对值之和。(同样的,inc被设置为1)


12. caffe_cpu_scale

template <>
void caffe_cpu_scale<float>(const int n, const float alpha, const float *x,
                            float* y) {
  cblas_scopy(n, x, 1, y, 1);
  cblas_sscal(n, alpha, y, 1);
}

功能:

将一个向量x与标量相乘后,赋给另一向量y:

y := \alpha \cdot x

从源代码中也可以清晰地看到,实际上是分别调用了两个函数cblas_scopy和cblas_sscal。先完成赋值:y:=x;再完成系数相乘:y:=\alpha \cdot y


参考文献:

1. blog.csdn.net/seven_fir

2. Mac Developer Library —— BLAS Reference

编辑于 2016-08-10