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_
用來保存:
權重保存的W_
中,輸入數據爲函數參數in_raw
。
卷積時,遍歷輸出的每個像素點:
//計算輸出的值
for (cnn_size_t y = 0; y < out_.height_; y++) {
for (cnn_size_t x = 0; x < out_.width_; x++) {
之後遍歷卷積核,卷積某個區域,計算
for (cnn_size_t wy = 0; wy < weight_.height_; wy++) {
for (cnn_size_t wx = 0; wx < weight_.width_; wx++) {
隨後在加上偏置bias,計算最終結果