深度学习:Neural Network Layers Understanding-36大数据
Inner Product Layer
Inner Product Layer即全连接层,对于IP层的理解,可以简单的将其视为矩阵1*N和矩阵N*M相乘后得到1*M的维度向量。
举个简单的例子,比如输入全连接层的是一个3*56*56维度的数据,假设未知的权重维度为N*M,假设全连接层的输出为num_ouput = 4096,为了计算全连接层的输出,全连接层会将输入的数据3*56*56 reshape 成为1*N的形式,即1x(56x56x3) = 1×9408,所以:
N = 9408 M = num_ouput = 4096
由此,我们做了一个(1×9408)矩阵和(9408×4096)矩阵的乘法。如果num_output的值改变成为100,则做的是一个(1×9408)矩阵和(9408×100)矩阵的乘法。Inner Product layer(常被称为全连接层)将输入视为一个vector,输出也是一个vector(height和width被设为1)。下面是IP层的示意图
图片摘自 Why GEMM is at the heart of deep learning
增大num_output会使得模型需要学习的权重参数增加。IP层一个典型的例子:
layer { name: "ip1" type: "InnerProduct" bottom: "pool2" top: "ip1" # learning rate and decay multipliers for the weights param { lr_mult: 1 } # learning rate and decay multipliers for the biases param { lr_mult: 2 } inner_product_param { num_output: 500 weight_filler { type: "xavier" } bias_filler { type: "constant" } } }
有了上面对IP层的理解,对 caffe inner_product_layer.cpp 中Forward的理解就比较自然了。下面是Caffe的IP层在CPU上的实现:
template <typename Dtype> void InnerProductLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { const Dtype* bottom_data = bottom[0]->cpu_data(); Dtype* top_data = top[0]->mutable_cpu_data(); const Dtype* weight = this->blobs_[0]->cpu_data(); caffe_cpu_gemm<Dtype>(CblasNoTrans, transpose_ ? CblasNoTrans : CblasTrans, M_, N_, K_, (Dtype)1., bottom_data, weight, (Dtype)0., top_data); if (bias_term_) { caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, N_, 1, (Dtype)1., bias_multiplier_.cpu_data(), this->blobs_[1]->cpu_data(), (Dtype)1., top_data); } }
上面完成矩阵与矩阵相乘的函数是caffe_cpu_gemm<Dtype>(见 math_functions.cpp ),caffe_cpu_gemm函数矩阵相乘的具体数学表示形式为:
上式中TransX是对X做的一种矩阵变换,比如转置、共轭等,具体是cblas.h中定义的为枚举类型。在 math_functions.cpp 中,除了定义矩阵与矩阵相乘的caffe_cpu_gemm外,还定义了矩阵与向量的相乘,具体的函数为caffe_cpu_gemv,其数学表示形式为:
上面表达式中,y是向量,不是标量。
参考
- Why GEMM is at the heart of deep learning
- What is the output of fully connected layer in CNN?
- caffe_cpu_gemm函数
- Caffe学习:Layers
- Caffe Layers
GEMM
在上面的IP层中,我们已经涉及到了GEMM的知识,在这一小节里面,不妨对该知识点做一个延伸。
GEMM是BLAS (Basic Linear Algebra Subprograms)库的一部分,该库在1979年首次创建。为什么GEMM在深度学习中如此重要呢?我们可以先来看一个图:
图片摘自Yangqing Jia的thesis
上图是采用AlexNet对CNN网络中不同layer GPU和CPU的时间消耗,从更底层的实现可以看到CNN网络的主要时间消耗用在了FC (for fully-connected)和Conv (for convolution),而FC和Conv在实现上都将其转为了矩阵相乘的形式。举个例子:
图片摘自 cuDNN: Efficient Primitives for Deep Learning
上面Conv在Caffe中具体实现的时候,会将每一个小的patch拉成一个向量,很多patch构成的向量会构成一个大的矩阵,同样的对于多个卷积核展成一个矩阵形式,从而将图像的卷积转成了矩阵与矩阵的相乘(更形象化的解释参阅 在 Caffe 中如何计算卷积? )。上面可以看到在FC和Conv上消耗的时间GPU占95%,CPU上占89%。因而GEMM的实现高效与否对于整个网络的效率有很大的影响。
那么什么是GEMM呢?GEMM的全称是GEneral Matrix to Matrix Multiplication,正如其字面意思所表达的,GEMM即表示两个输入矩阵进行相乘,得到一个输出的矩阵。两个矩阵在进行相乘的时候,通常会进行百万次的浮点运算。对于一个典型网络中的某一层,比如一个256 row1152 column的矩阵和一个1152 row192 column的矩阵,二者相乘57 million (256 x 1152 x 192)的浮点运算。因而,通常我们看到的情形是,一个网络在处理一帧的时候,需要几十亿的FLOPs(Floating-point operations per second,每秒浮点计算)。
既然知道了GEMM是限制整个网络时间消耗的主要部分,那么我们是不是可以对GEMM做优化调整呢?答案是否定的,GEMM采用Fortran编程语言实现,经过了科学计算编程人员几十年的优化,性能已经极致,所以很难再去进一步的优化,在Nvidia的论文 cuDNN: Efficient Primitives for Deep Learning 中指出了还存在着其他的一些方法,但是他们最后采用的还是改进的GEMM版本实现。GEMM可匹敌的对手是傅里叶变换,将卷积转为频域的相乘,但是由于在图像的卷积中存在strides,使得傅里叶变换方式很难保持高效。
End.
转载请注明来自36大数据(36dsj.com): 36大数据 » 深度学习:Neural Network Layers Understanding