Files
micro_servers/lci/include/nn/PoolingLayer.hpp
T
2026-08-02 21:21:03 +08:00

502 lines
21 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#pragma once
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <limits>
#include <functional>
class PoolingLayer {
private:
// 池化类型枚举
enum class PoolType {
MAX,
AVG
};
// 参数
int pool_height;
int pool_width;
int stride;
int padding;
PoolType pool_type;
// 输入输出尺寸
int input_channels;
int input_height;
int input_width;
int output_height;
int output_width;
// 中间变量缓存(用于反向传播)
std::vector<int> max_idx_cache; // 最大池化时缓存最大值位置
std::vector<float> input_cache; // 缓存的输入
std::vector<float> output_cache; // 缓存的输出
// 辅助函数
//void max_pool_forward(const float* input, float* output, int batch_size);
//void avg_pool_forward(const float* input, float* output, int batch_size);
//void max_pool_backward(const float* grad_output, float* grad_input, int batch_size);
//void avg_pool_backward(const float* grad_output, float* grad_input, int batch_size);
public:
// 构造函数
PoolingLayer(int pool_size, int stride = 2, int padding = 0, bool max_pool = true)
: pool_height(pool_size),
pool_width(pool_size),
stride(stride),
padding(padding),
pool_type(max_pool ? PoolType::MAX : PoolType::AVG) {}
// 带不同高度和宽度的构造函数
PoolingLayer(int pool_height, int pool_width, int stride = 2, int padding = 0, bool max_pool = true)
: pool_height(pool_height),
pool_width(pool_width),
stride(stride),
padding(padding),
pool_type(max_pool ? PoolType::MAX : PoolType::AVG) {}
// 设置输入尺寸
void set_input_shape(int channels, int height, int width) {
input_channels = channels;
input_height = height;
input_width = width;
// 计算输出尺寸
output_height = (height + 2 * padding - pool_height) / stride + 1;
output_width = (width + 2 * padding - pool_width) / stride + 1;
// 清空缓存
max_idx_cache.clear();
input_cache.clear();
output_cache.clear();
}
// 前向传播
std::vector<float> forward(const std::vector<float>& input, int batch_size = 1, bool training = true) {
// 计算输出大小
int output_size = batch_size * input_channels * output_height * output_width;
std::vector<float> output(output_size, 0.0f);
// 如果训练模式,缓存输入
if (training) {
input_cache = input;
}
// 根据池化类型选择前向传播函数
if (pool_type == PoolType::MAX) {
max_pool_forward(input.data(), output.data(), batch_size);
} else {
avg_pool_forward(input.data(), output.data(), batch_size);
}
// 如果训练模式,缓存输出
if (training) {
output_cache = output;
}
return output;
}
// 反向传播
std::vector<float> backward(const std::vector<float>& grad_output, int batch_size = 1) {
// 创建输入梯度
std::vector<float> grad_input(batch_size * input_channels * input_height * input_width, 0.0f);
// 根据池化类型选择反向传播函数
if (pool_type == PoolType::MAX) {
max_pool_backward(grad_output.data(), grad_input.data(), batch_size);
} else {
avg_pool_backward(grad_output.data(), grad_input.data(), batch_size);
}
return grad_input;
}
// 获取输出尺寸
std::tuple<int, int, int> get_output_shape() const {
return std::make_tuple(input_channels, output_height, output_width);
}
// 获取参数信息
void print_info() const {
std::cout << "Pooling Layer Info:" << std::endl;
std::cout << " Type: " << (pool_type == PoolType::MAX ? "Max Pooling" : "Average Pooling") << std::endl;
std::cout << " Pool Size: " << pool_height << "x" << pool_width << std::endl;
std::cout << " Stride: " << stride << std::endl;
std::cout << " Padding: " << padding << std::endl;
std::cout << " Input Shape: " << input_channels << "x" << input_height << "x" << input_width << std::endl;
std::cout << " Output Shape: " << input_channels << "x" << output_height << "x" << output_width << std::endl;
}
private:
// 最大池化前向传播实现
void max_pool_forward(const float* input, float* output, int batch_size) {
// 计算填充后的尺寸
int padded_height = input_height + 2 * padding;
int padded_width = input_width + 2 * padding;
// 清空最大值位置缓存
max_idx_cache.clear();
max_idx_cache.resize(batch_size * input_channels * output_height * output_width, -1);
// 对每个批次、每个通道进行处理
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
// 当前通道的输入和输出指针
const float* input_ptr = input + b * input_channels * input_height * input_width +
c * input_height * input_width;
float* output_ptr = output + b * input_channels * output_height * output_width +
c * output_height * output_width;
int* max_idx_ptr = max_idx_cache.data() + b * input_channels * output_height * output_width +
c * output_height * output_width;
// 对每个输出位置进行计算
for (int oh = 0; oh < output_height; oh++) {
for (int ow = 0; ow < output_width; ow++) {
// 计算池化窗口的起始位置
int start_h = oh * stride - padding;
int start_w = ow * stride - padding;
// 计算池化窗口的结束位置
int end_h = std::min(start_h + pool_height, padded_height);
int end_w = std::min(start_w + pool_width, padded_width);
// 调整起始位置,确保不超出边界
start_h = std::max(start_h, 0);
start_w = std::max(start_w, 0);
// 在池化窗口内寻找最大值
float max_val = -std::numeric_limits<float>::max();
int max_idx = -1;
for (int ph = start_h; ph < end_h; ph++) {
for (int pw = start_w; pw < end_w; pw++) {
// 计算原始输入中的索引(考虑填充)
int orig_ph = ph - padding;
int orig_pw = pw - padding;
// 检查是否在有效范围内
if (orig_ph >= 0 && orig_ph < input_height &&
orig_pw >= 0 && orig_pw < input_width) {
int idx = orig_ph * input_width + orig_pw;
float val = input_ptr[idx];
if (val > max_val) {
max_val = val;
max_idx = idx;
}
}
}
}
// 存储输出和最大值位置
output_ptr[oh * output_width + ow] = max_val;
max_idx_ptr[oh * output_width + ow] = max_idx;
}
}
}
}
}
// 平均池化前向传播实现
void avg_pool_forward(const float* input, float* output, int batch_size) {
// 计算填充后的尺寸
int padded_height = input_height + 2 * padding;
int padded_width = input_width + 2 * padding;
// 对每个批次、每个通道进行处理
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
// 当前通道的输入和输出指针
const float* input_ptr = input + b * input_channels * input_height * input_width +
c * input_height * input_width;
float* output_ptr = output + b * input_channels * output_height * output_width +
c * output_height * output_width;
// 对每个输出位置进行计算
for (int oh = 0; oh < output_height; oh++) {
for (int ow = 0; ow < output_width; ow++) {
// 计算池化窗口的起始位置
int start_h = oh * stride - padding;
int start_w = ow * stride - padding;
// 计算池化窗口的结束位置
int end_h = std::min(start_h + pool_height, padded_height);
int end_w = std::min(start_w + pool_width, padded_width);
// 调整起始位置,确保不超出边界
start_h = std::max(start_h, 0);
start_w = std::max(start_w, 0);
// 在池化窗口内计算平均值
float sum = 0.0f;
int count = 0;
for (int ph = start_h; ph < end_h; ph++) {
for (int pw = start_w; pw < end_w; pw++) {
// 计算原始输入中的索引(考虑填充)
int orig_ph = ph - padding;
int orig_pw = pw - padding;
// 检查是否在有效范围内
if (orig_ph >= 0 && orig_ph < input_height &&
orig_pw >= 0 && orig_pw < input_width) {
int idx = orig_ph * input_width + orig_pw;
sum += input_ptr[idx];
count++;
}
}
}
// 计算平均值
output_ptr[oh * output_width + ow] = (count > 0) ? sum / count : 0.0f;
}
}
}
}
}
// 最大池化反向传播实现
void max_pool_backward(const float* grad_output, float* grad_input, int batch_size) {
// 初始化输入梯度为0
std::memset(grad_input, 0, batch_size * input_channels * input_height * input_width * sizeof(float));
// 对每个批次、每个通道进行处理
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
// 当前通道的梯度指针
const float* grad_output_ptr = grad_output + b * input_channels * output_height * output_width +
c * output_height * output_width;
float* grad_input_ptr = grad_input + b * input_channels * input_height * input_width +
c * input_height * input_width;
const int* max_idx_ptr = max_idx_cache.data() + b * input_channels * output_height * output_width +
c * output_height * output_width;
// 对每个输出位置进行反向传播
for (int oh = 0; oh < output_height; oh++) {
for (int ow = 0; ow < output_width; ow++) {
// 获取最大值位置索引
int max_idx = max_idx_ptr[oh * output_width + ow];
// 如果最大值位置有效,将梯度传递到该位置
if (max_idx >= 0) {
grad_input_ptr[max_idx] += grad_output_ptr[oh * output_width + ow];
}
}
}
}
}
}
// 平均池化反向传播实现
void avg_pool_backward(const float* grad_output, float* grad_input, int batch_size) {
// 初始化输入梯度为0
std::memset(grad_input, 0, batch_size * input_channels * input_height * input_width * sizeof(float));
// 计算填充后的尺寸
int padded_height = input_height + 2 * padding;
int padded_width = input_width + 2 * padding;
// 对每个批次、每个通道进行处理
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
// 当前通道的梯度指针
const float* grad_output_ptr = grad_output + b * input_channels * output_height * output_width +
c * output_height * output_width;
float* grad_input_ptr = grad_input + b * input_channels * input_height * input_width +
c * input_height * input_width;
// 对每个输出位置进行反向传播
for (int oh = 0; oh < output_height; oh++) {
for (int ow = 0; ow < output_width; ow++) {
// 计算池化窗口的起始位置
int start_h = oh * stride - padding;
int start_w = ow * stride - padding;
// 计算池化窗口的结束位置
int end_h = std::min(start_h + pool_height, padded_height);
int end_w = std::min(start_w + pool_width, padded_width);
// 调整起始位置,确保不超出边界
start_h = std::max(start_h, 0);
start_w = std::max(start_w, 0);
// 计算有效池化窗口的大小
int count = 0;
for (int ph = start_h; ph < end_h; ph++) {
for (int pw = start_w; pw < end_w; pw++) {
// 计算原始输入中的索引(考虑填充)
int orig_ph = ph - padding;
int orig_pw = pw - padding;
// 检查是否在有效范围内
if (orig_ph >= 0 && orig_ph < input_height &&
orig_pw >= 0 && orig_pw < input_width) {
count++;
}
}
}
// 如果池化窗口内有有效元素,计算平均梯度
if (count > 0) {
float avg_grad = grad_output_ptr[oh * output_width + ow] / count;
// 将平均梯度分配到池化窗口内的每个位置
for (int ph = start_h; ph < end_h; ph++) {
for (int pw = start_w; pw < end_w; pw++) {
// 计算原始输入中的索引(考虑填充)
int orig_ph = ph - padding;
int orig_pw = pw - padding;
// 检查是否在有效范围内
if (orig_ph >= 0 && orig_ph < input_height &&
orig_pw >= 0 && orig_pw < input_width) {
int idx = orig_ph * input_width + orig_pw;
grad_input_ptr[idx] += avg_grad;
}
}
}
}
}
}
}
}
}
};
// 使用示例函数
inline void pooling_layer_example() {
std::cout << "=== Pooling Layer Example ===" << std::endl;
// 创建最大池化层
PoolingLayer max_pool(2, 2, 0, true); // 2x2 最大池化,步长2,无填充
max_pool.set_input_shape(3, 4, 4); // 3个通道,4x4输入
// 创建输入数据
std::vector<float> input(3 * 4 * 4);
for (int i = 0; i < 3 * 4 * 4; i++) {
input[i] = static_cast<float>(i) / 10.0f;
}
// 前向传播
std::cout << "Forward pass..." << std::endl;
auto output = max_pool.forward(input, 1, true);
// 获取输出形状
auto [channels, height, width] = max_pool.get_output_shape();
std::cout << "Output shape: " << channels << "x" << height << "x" << width << std::endl;
// 打印输出
std::cout << "Output values (first 10): ";
for (int i = 0; i < std::min(10, static_cast<int>(output.size())); i++) {
std::cout << output[i] << " ";
}
std::cout << std::endl;
// 创建梯度(假设全为1)
std::vector<float> grad_output(output.size(), 1.0f);
// 反向传播
std::cout << "Backward pass..." << std::endl;
auto grad_input = max_pool.backward(grad_output, 1);
std::cout << "Gradient input size: " << grad_input.size() << std::endl;
std::cout << "Gradient input values (first 10): ";
for (int i = 0; i < std::min(10, static_cast<int>(grad_input.size())); i++) {
std::cout << grad_input[i] << " ";
}
std::cout << std::endl;
// 打印层信息
max_pool.print_info();
}
// 全局平均池化层(常用于CNN分类网络的最后)
class GlobalAveragePooling {
private:
int input_channels;
int input_height;
int input_width;
std::vector<float> input_cache;
public:
// 设置输入尺寸
void set_input_shape(int channels, int height, int width) {
input_channels = channels;
input_height = height;
input_width = width;
}
// 前向传播
std::vector<float> forward(const std::vector<float>& input, int batch_size = 1, bool training = true) {
// 缓存输入(如果需要)
if (training) {
input_cache = input;
}
// 计算输出大小:每个通道一个值
int output_size = batch_size * input_channels;
std::vector<float> output(output_size, 0.0f);
// 对每个批次、每个通道计算平均值
int spatial_size = input_height * input_width;
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
const float* input_ptr = input.data() + b * input_channels * spatial_size + c * spatial_size;
float sum = 0.0f;
// 计算该通道所有空间位置的平均值
for (int i = 0; i < spatial_size; i++) {
sum += input_ptr[i];
}
output[b * input_channels + c] = sum / spatial_size;
}
}
return output;
}
// 反向传播
std::vector<float> backward(const std::vector<float>& grad_output, int batch_size = 1) {
// 创建输入梯度
int spatial_size = input_height * input_width;
std::vector<float> grad_input(batch_size * input_channels * spatial_size, 0.0f);
// 对每个批次、每个通道分配梯度
for (int b = 0; b < batch_size; b++) {
for (int c = 0; c < input_channels; c++) {
float grad = grad_output[b * input_channels + c] / spatial_size;
float* grad_input_ptr = grad_input.data() + b * input_channels * spatial_size + c * spatial_size;
// 将梯度均匀分配到所有空间位置
for (int i = 0; i < spatial_size; i++) {
grad_input_ptr[i] = grad;
}
}
}
return grad_input;
}
// 获取输出尺寸
std::tuple<int, int> get_output_shape() const {
return std::make_tuple(input_channels, 1);
}
};
/*
// 创建池化层
PoolingLayer pool(2, 2, 0, true); // 2x2最大池化,步长2,无填充
pool.set_input_shape(64, 28, 28); // 64通道,28x28输入
// 前向传播
auto output = pool.forward(input, batch_size, true);
// 反向传播
auto grad_input = pool.backward(grad_output, batch_size);
*/