convolutional_layer是用来计算卷积的。
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];
}
这个数据结构只是记录图像尺寸,即宽-高-深度,实际上并不持有数据
template 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用来计算卷积,先看一下其成员变量:
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 in_;//输入数据大小
index3d in_padded_;//填充大小,用来内存对齐
index3d out_;//输出大小
index3d 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,计算最终结果