tiny_cnn源碼閱讀(4)-convolutional_layer

convolutional_layer是用來計算卷積的。

connection_table

connecction_table是一個二維數組,(x,y)的值(true/false)表示x和y是否關聯,即是用來判斷和那幾個feature_map連接的。在其實現中,內部數據結構爲一個bool類型隊列,函數is_connected判斷是否關聯

bool is_connected(cnn_size_t x, cnn_size_t y) const {//判斷x,y位置的值即可。如果connection table爲空,那麼一直爲真
        return is_empty() ? true : connected_[y * cols_ + x];
    }

index3d

這個數據結構只是記錄圖像尺寸,即寬-高-深度,實際上並不持有數據

template <typename T>
struct index3d {//用來保存圖像的數據結構,實際上並不保存數據,寬-高-深度,共三個參數
    index3d(T width, T height, T depth) {
        reshape(width, height, depth);
    }

    index3d() : width_(0), height_(0), depth_(0) {}

    void reshape(T width, T height, T depth) {
        width_ = width;
        height_ = height;
        depth_ = depth;

        if ((long long) width * height * depth > std::numeric_limits<T>::max())
            throw nn_error(
            format_str("error while constructing layer: layer size too large for tiny-cnn\nWidthxHeightxChannels=%dx%dx%d >= max size of [%s](=%d)",
            width, height, depth, typeid(T).name(), std::numeric_limits<T>::max()));
    }

    T get_index(T x, T y, T channel) const {
        assert(x >= 0 && x < width_);
        assert(y >= 0 && y < height_);
        assert(channel >= 0 && channel < depth_);
        return (height_ * channel + y) * width_ + x; 
    }

    T area() const {
        return width_ * height_;
    }

    T size() const {
        return width_ * height_ * depth_;
    }

    T width_;
    T height_;
    T depth_;
};

convolutional_layer

convolutional_layer用來計算卷積,先看一下其成員變量:

const vec_t* prev_out_padded_[CNN_TASK_SIZE];//指針,pad_type_ == padding::same時,填充用
    vec_t* prev_out_buf_[CNN_TASK_SIZE];
    vec_t  prev_delta_padded_[CNN_TASK_SIZE];//用來填充prev_delta_
    vec_t  prev_delta2_padded_;//用來填充prev_delta2_

    connection_table tbl_;//connection_table
    index3d<cnn_size_t> in_;//輸入數據大小
    index3d<cnn_size_t> in_padded_;//填充大小,用來內存對齊
    index3d<cnn_size_t> out_;//輸出大小
    index3d<cnn_size_t> weight_;//權重大小
    padding pad_type_;//枚舉變量,輸出大小是原圖像大小還是卷積後:原圖像-卷積核+1
    size_t w_stride_;//stride表示窗口移動的大小。卷積時窗口每次滑動1,但是pooling時每次滑動的大小爲卷積核大小
    size_t h_stride_;

pad_type_表示填充類型。卷積時,如果如果不填充,卷積核每次滑動距離w_stride_h_stride_都爲1,那麼卷積後圖像:高=原高度-卷積核高+1;寬=原寬度-卷積核寬度+1。

卷積層重點是卷積,看一下卷積函數

 virtual const vec_t& forward_propagation(const vec_t& in_raw, size_t worker_index) override
    {
        copy_and_pad_input(in_raw, static_cast<int>(worker_index));

        vec_t &a = a_[worker_index]; // w*x,輸出
        vec_t &out = output_[worker_index]; // output
        const vec_t &in = *(prev_out_padded_[worker_index]); // input

        std::fill(a.begin(), a.end(), float_t(0));

        for_i(parallelize_, out_.depth_, [&](int o) {
            for (cnn_size_t inc = 0; inc < in_.depth_; inc++) {
                if (!tbl_.is_connected(o, inc)) continue;//通過connection_table判斷是否有關聯,connection_table爲空則全部關聯

                const float_t *pw = &this->W_[weight_.get_index(0, 0, in_.depth_ * o + inc)];//權重
                const float_t *pi = &in[in_padded_.get_index(0, 0, inc)];//輸入
                float_t *pa = &a[out_.get_index(0, 0, o)];//pa用來保存計算結果

                //計算輸出的值
                for (cnn_size_t y = 0; y < out_.height_; y++) {
                    for (cnn_size_t x = 0; x < out_.width_; x++) {
                        const float_t * ppw = pw;//指向權重,下面ppi指向輸入數據
                        const float_t * ppi = pi + (y * h_stride_) * in_padded_.width_ + x * w_stride_;
                        float_t sum = float_t(0);

                        //下面是計算卷積
                        // should be optimized for small kernel(3x3,5x5)
                        for (cnn_size_t wy = 0; wy < weight_.height_; wy++) {
                            for (cnn_size_t wx = 0; wx < weight_.width_; wx++) {
                                sum += *ppw++ * ppi[wy * in_padded_.width_ + wx];//卷積爲:sum(權重x某一像素)
                            }
                        }
                        pa[y * out_.width_ + x] += sum;//結果保存到輸出
                    }
                }
            }
            //bias不爲空時還要加上bias
            if (!this->b_.empty()) {
                float_t *pa = &a[out_.get_index(0, 0, o)];
                float_t b = this->b_[o];
                std::for_each(pa, pa + out_.width_ * out_.height_, [&](float_t& f) { f += b; });
            }
        });

        for_i(parallelize_, out_size_, [&](int i) {
            out[i] = h_.f(a, i);
        });

        CNN_LOG_VECTOR(in_raw, "[pc]in");
        CNN_LOG_VECTOR(W_, "[pc]w");
        CNN_LOG_VECTOR(a, "[pc]a");
        CNN_LOG_VECTOR(out, "[pc]forward");

        return next_ ? next_->forward_propagation(out, worker_index) : out;//後面有網絡,就再計算下一層
    }

上面變量中a_用來保存:wx+b ;out用來保存:f(wx+b)
權重保存的W_中,輸入數據爲函數參數in_raw
卷積時,遍歷輸出的每個像素點:

//計算輸出的值
                for (cnn_size_t y = 0; y < out_.height_; y++) {
                    for (cnn_size_t x = 0; x < out_.width_; x++) {

之後遍歷卷積核,卷積某個區域,計算wx :

for (cnn_size_t wy = 0; wy < weight_.height_; wy++) {
                            for (cnn_size_t wx = 0; wx < weight_.width_; wx++) {

隨後在加上偏置bias,計算最終結果

發佈了280 篇原創文章 · 獲贊 326 · 訪問量 125萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章