C++实践(一)C++实现前馈神经网络

本篇主要讲2016年的时候写的一个神经网络,顺便复习以前的知识。

需求

想实现一个前馈神经网络,基于C++,性能会比较好。主要是因为当时Caffe等库依赖太多了,又没有其他开源的好用,顺便复习下C++。

架构设计

在现行的神经网络库里,Caffe采用以层为单位的抽象,优点是逻辑清晰,实现简便,但是需要手动实现梯度计算;而Tensorflow是以计算图为基础,封装好了每个算子,采用这些算子可以无需手动计算梯度。当然我们这里不需要计算梯度。整体架构采用:数据存储+层的方案。

(1)数据存储:用C++实现基础的类,然后底层用Blas之类的库加速。主要参考的是Caffe的设计。层:实现一些基础的层,比如卷积、池化、非线性层等。

  • 首先,要考虑的问题是数据的存储,需要一个4维的数据存储类,类似于Caffe的Blob。因为是前馈网络,所以暂时不需要存储梯度
  • 这个数据存储类要实现一些常见的操作,比如加减乘除,改变维度(数据重新排列)
  • 需要对运算进行加速,比如矩阵乘法可以并行化加速。

(2)模型定义与存储:采用代码实现,在代码里定义模型。参数的存储采用文本格式,封装到数据类里。当然,C++有一些序列化模型的库,但是为了简便这里不采用

整体的架构还是比较简单的。

代码实现

卷积的实现

参考了Caffe的实现方式,把图像卷积转化为矩阵相乘,然后再用矩阵运算库armadillo加速。注意这里armadillo底层是通过 Lapack/OpenBlas/ Intel MKL等加速的。这种实现卷积的方式优点是计算快,缺点是占用比较多的存储空间。当然还有其他更快的实现卷积的方式,比如FFT。

template<typename Dtype>
void im2col(const arma::Cube<Dtype> &feats,
    const int kernel_h, const int kernel_w,
    const int pad_h, const int pad_w,
    const int stride_h, const int stride_w,
    arma::Mat<Dtype>& feat_mat)
{
    const int height = feats.n_rows;
    const int width = feats.n_cols;
    const int channels = feats.n_slices;
    const int output_h = (height + 2 * pad_h - kernel_h )/ stride_h + 1;
    const int output_w = (width + 2 * pad_w - kernel_w )/ stride_w + 1;

    const int channel_size = height*width;
    feat_mat = arma::Mat<Dtype>(output_h*output_w, kernel_w*kernel_h*channels);
    const Dtype *data_im = feats.memptr();
    Dtype *data_col = feat_mat.memptr();
    //由于armadillo库的矩阵元素在内存中是按照先遍历低轴排布(列元素连续),所以
    //我们把一次卷积的各元素展开成一行,把一个卷积核展开成一列。
    //两个矩阵相乘之后,得到的矩阵的每一列是一个feature map.
    for (int c = channels; c--; data_im += channel_size) {
        //遍历整个核.按照从左到右,从上到下的顺序将一个核的元素排成一行
        for (int kernel_y = 0; kernel_y < kernel_h; kernel_y++) {
            for (int kernel_x = 0; kernel_x < kernel_w; kernel_x++) {
                //遍历Feature Map。卷积核在图像上移动,从左到右,从上到下。
                //当前在图像中的位置
                int input_y = kernel_y-pad_h;
                for (int output_y = output_h; output_y; output_y--) {
                    //检查是否处于padding位置
                    if (!is_a_ge_zero_and_a_lt_b(input_y, height)) {
                        for (int output_x = output_w; output_x; output_x--) {
                            *(data_col++) = 0;
                        }
                    }
                    else {
                        int input_x = kernel_x - pad_w;
                        for (int output_x = output_w; output_x; output_x--) {
                            if (is_a_ge_zero_and_a_lt_b(input_x, width)) {
                                *(data_col++) = data_im[input_x*height + input_y];
                            }
                            else {
                                *(data_col++) = 0;
                            }
                            input_x += stride_w;
                        }
                    }
                    input_y += stride_h;
                }
            }
        }
    }
}

其他层的实现依次类推

网络类

/*
* @Author: Weiliang Chen
* @Date:   2016-09-29 10:51:07
* @Last Modified by:   Weiliang Chen
* @Last Modified time: 2016-09-29 10:55:51
*/
#ifndef FN_NET_H_
#define FN_NET_H_

#include <opencv.hpp>
#include <core.hpp>
#include <armadillo>
#include "ConvLayer.h"
#include "ReluLayer.h"
#include "PoolingLayer.h"
#include "InnerProductLayer.h"

namespace fn {

template <typename Dtype>
class Net
{
public:
    Net();
    ~Net();
    bool init(std::string model_dir);

    void forward(const cv::Mat &image, Dtype *feat160);
    bool load(std::string filename, const int height,
        const int width,const int channels,const int number,
        Blob<Dtype> &weights);
    bool load(std::string filename, const int height,
        const int width, const int channels,
        arma::Cube<Dtype> &mean);
    bool load(std::string filename, const int length, arma::Col<Dtype> &bias);
    bool load(std::string filename, const int height,
        const int width, arma::Mat<Dtype> &weights);
private:
    const int IMAGE_SIZE_WIDTH = 47;
    const int IMAGE_SIZE_HEIGHT = 55;
    const int IMAGE_CHANNELS = 1;
    arma::Cube<Dtype> mean_;
    ConvLayer<Dtype> conv1_;
    ConvLayer<Dtype> conv1_1_;
    ConvLayer<Dtype> conv2_1_;
    ConvLayer<Dtype> conv2_2_;
    ConvLayer<Dtype> conv2_;
    ConvLayer<Dtype> conv3_1_;
    ConvLayer<Dtype> conv3_2_;
    ConvLayer<Dtype> conv3_3_;
    ConvLayer<Dtype> conv3_4_;
    ConvLayer<Dtype> conv3_;
    ConvLayer<Dtype> conv4_;

    ReluLayer<Dtype> relu1_;
    ReluLayer<Dtype> relu1_1_;
    ReluLayer<Dtype> relu2_1_;
    ReluLayer<Dtype> relu2_2_;
    ReluLayer<Dtype> relu2_;
    ReluLayer<Dtype> relu3_1_;
    ReluLayer<Dtype> relu3_2_;
    ReluLayer<Dtype> relu3_3_;
    ReluLayer<Dtype> relu3_4_;
    ReluLayer<Dtype> relu3_;
    ReluLayer<Dtype> relu4_;

    PoolingLayer<Dtype> pool1_;
    PoolingLayer<Dtype> pool2_;
    PoolingLayer<Dtype> pool3_;

    InnerProductLayer<Dtype> fc160_1_;
    InnerProductLayer<Dtype> fc160_2_;
};


}   //namespace fn
#endif // !FN_NET_H_

这里直接把网络写在代码里了。

测试

几个典型的测试用例是空白图片以及固定值的图片。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值