ResNet34 code implemented in C++

Implementing ResNet-34 requires understanding the basic architecture of ResNet. ResNet is a deep residual neural network whose basic structure is stacked by multiple residual blocks. Each residual block contains two convolutional layers and a shortcut connection. The skipping connection is responsible for bypassing the two convolutional layers and directly connecting the input and output of the two convolutional layers, thus allowing information to be shared in the deep network. Some layers are skipped. This avoids gradient disappearance and gradient explosion problems, enabling deeper network structures to be trained.

Implementing ResNet34 requires implementing some basic modules, such as convolutional layers, residual blocks, etc. The following is a simple implementation code, you can refer to it:

#include <iostream>
#include <vector>

using namespace std;

// 定义一个二维矩阵类型表示卷积层的卷积核
typedef vector<vector<double>> Matrix;

// 定义卷积函数
vector<vector<double>> conv2d(vector<vector<double>>& input, Matrix& kernel, int stride = 1)
{
    
    
    int in_channels = input.size();
    int out_channels = kernel.size();
    int kernel_size = kernel[0].size();
    int input_size = input[0].size();
    int output_size = (input_size - kernel_size) / stride + 1;

    vector<vector<double>> output(out_channels, vector<double>(output_size, 0));

    for (int l = 0; l < out_channels; l++)
    {
    
    
        for (int i = 0; i < output_size; i++)
        {
    
    
            for (int j = 0; j < output_size; j++)
            {
    
    
                double sum = 0.0;
                for (int c = 0; c < in_channels; c++)
                {
    
    
                    for (int m = 0; m < kernel_size; m++)
                    {
    
    
                        for (int n = 0; n < kernel_size; n++)
                        {
    
    
                            sum += input[c][i * stride + m][j * stride + n] * kernel[l][m][n];
                        }
                    }
                }
                output[l][i][j] = sum;
            }
        }
    }

    return output;
}

// 定义归一化函数
vector<vector<double>> normalize(vector<vector<double>>& input)
{
    
    
    int size = input.size();
    int rows = input[0].size();
    int cols = input[0][0].size();

    vector<double> mean(size, 0);
    vector<double> std(size, 0);

    for (int i = 0; i < size; i++)
    {
    
    
        double sum = 0.0;
        double square_sum = 0.0;
        for (int j = 0; j < rows; j++)
        {
    
    
            for (int k = 0; k < cols; k++)
            {
    
    
                sum += input[i][j][k];
                square_sum += input[i][j][k] * input[i][j][k];
            }
        }
        mean[i] = sum / (rows * cols);
        std[i] = sqrt(square_sum / (rows * cols) - mean[i] * mean[i]);
    }

    vector<vector<double>> output(size, vector<double>(rows, 0));
    for (int i = 0; i < size; i++)
    {
    
    
        for (int j = 0; j < rows; j++)
        {
    
    
            for (int k = 0; k < cols; k++)
            {
    
    
                output[i][j] = (input[i][j][k] - mean[i]) / std[i];
            }
        }
    }

    return output;
}

// 定义残差块
vector<vector<double>> residual(vector<vector<double>>& input, Matrix& kernel1, Matrix& kernel2, int stride = 1)
{
    
    
    int in_channels = input.size();
    int out_channels = kernel2.size();
    int kernel_size = kernel2[0].size();
    int input_size = input[0].size();
    int output_size = (input_size - kernel_size) / stride + 1;

    // 对输入进行归一化处理
    input = normalize(input);

    // 使用两个卷积层实现残差块
    // 第一个卷积层
    vector<vector<double>> conv1_output = conv2d(input, kernel1, stride);
    // 使用ReLU激活函数
    for (int i = 0; i < out_channels; i++)
    {
    
    
        for (int j = 0; j < output_size; j++)
        {
    
    
            for (int k = 0; k < output_size; k++)
            {
    
    
                conv1_output[i][j][k] = max(conv1_output[i][j][k], 0.0);
            }
        }
    }

    // 第二个卷积层
    vector<vector<double>> conv2_output = conv2d(conv1_output, kernel2);

    // 残差连接
    vector<vector<double>> residual_output(out_channels, vector<double>(output_size, 0));
    for (int i = 0; i < out_channels; i++)
    {
    
    
        for (int j = 0; j < output_size; j++)
        {
    
    
            for (int k = 0; k < output_size; k++)
            {
    
    
                residual_output[i][j][k] = conv2_output[i][j][k] + input[i][j * stride][k * stride];
            }
        }
    }

    return residual_output;
}

// 定义ResNet34模型
vector<vector<double>> resnet34(vector<vector<double>>& input)
{
    
    
    // 定义卷积核
    Matrix conv1_kernel = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 8, -1}, {
    
    -1, -1, -1} };
    Matrix conv2_kernel1 = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 0, -1}, {
    
    -1, -1, -1} };
    Matrix conv2_kernel2 = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 8, -1}, {
    
    -1, -1, -1} };

    Matrix conv3_kernel1 = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 0, -1}, {
    
    -1, -1, -1} };
    Matrix conv3_kernel2 = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 8, -1}, {
    
    -1, -1, -1} };

    Matrix conv4_kernel1 = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 0, -1}, {
    
    -1, -1, -1} };
    Matrix conv4_kernel2 = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 8, -1}, {
    
    -1, -1, -1} };

    Matrix conv5_kernel1 = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 0, -1}, {
    
    -1, -1, -1} };
    Matrix conv5_kernel2 = {
    
     {
    
    -1, -1, -1}, {
    
    -1, 8, -1}, {
    
    -1, -1, -1} };

    // 定义残差块的步长
    int stride1 = 1;
    int stride2 = 2;

    // 定义4个残差块
    vector<vector<double>> residual1_output = residual(input, conv1_kernel, conv2_kernel1, stride1);
    vector<vector<double>> residual2_output = residual(residual1_output, conv2_kernel2, conv3_kernel1, stride2);
    vector<vector<double>> residual3_output = residual(residual2_output, conv3_kernel2, conv4_kernel1, stride2);
    vector<vector<double>> residual4_output = residual(residual3_output, conv4_kernel2, conv5_kernel1, stride2);

    // 对最后一个残差块的输出进行池化
    int size = residual4_output[0].size();
    vector<vector<double>> pool_output(residual4_output.size(), vector<double>(1, 0));
    for (int i = 0; i < residual4_output.size(); i++)
    {
    
    
        double sum = 0.0;
        for (int j = 0; j < size; j++)
        {
    
    
            for (int k = 0; k < size; k++)
            {
    
    
                sum += residual4_output[i][j][k];
            }
        }
        pool_output[i][0] = sum / (size * size);
    }

    return pool_output;
}

int main()
{
    
    
    // 定义输入
    int in_channels = 3;
    int input_size = 224;
    vector<vector<double>> input(in_channels, vector<double>(input_size, 0));
    for (int i = 0; i < in_channels; i++)
    {
    
    
        for (int j = 0; j < input_size; j++)
        {
    
    
            for (int k = 0; k < input_size; k++)
            {
    
    
                input[i][j] = rand() % 256;
            }
        }
    }

    // 使用ResNet34模型进行预测
    vector<vector<double>> output = resnet34(input);

    // 输出结果
    for (int i = 0; i < output.size(); i++)
    {
    
    
        cout << "Output channel " << i << ": " << output[i][0] << endl;
    }

    return 0;
}

The above code implements the forward propagation of the ResNet34 model. Please note that this is just an example, in fact, the ResNet34 model contains more layers, and each layer needs to be designed with a corresponding convolution kernel and step size. You may need to spend more time to implement a complete model.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

// 定义卷积操作
void conv(float* input, float* output, float* weight, float* bias, int in_channel, int out_channel, int kernel_size, int stride, int padding) {
    
    
    int output_size = (in_channel - kernel_size + 2 * padding) / stride + 1; // 计算卷积输出的大小
    for (int i = 0; i < out_channel; i++) {
    
     // 遍历输出通道
        for (int j = 0; j < output_size; j++) {
    
     // 遍历输出空间
            for (int k = 0; k < output_size; k++) {
    
    
                float sum = 0;
                for (int l = 0; l < in_channel; l++) {
    
     // 遍历输入通道
                    for (int m = 0; m < kernel_size; m++) {
    
     // 遍历卷积核空间
                        for (int n = 0; n < kernel_size; n++) {
    
    
                            int input_row = j * stride + m - padding; // 计算输入位置
                            int input_col = k * stride + n - padding;
                            if (input_row >= 0 && input_row < in_channel && input_col >= 0 && input_col < in_channel) {
    
     // 判断输入是否合法
                                sum += input[input_row * in_channel * in_channel + input_col * in_channel + l] * 
                                       weight[i * in_channel * kernel_size * kernel_size + l * kernel_size * kernel_size + m * kernel_size + n]; // 计算卷积
                            }
                        }
                    }
                }
                output[i * output_size * output_size + j * output_size + k] = sum + bias[i]; // 加上偏置项
            }
        }
    }
}

// 定义BN操作
void batch_norm(float* input, float* output, float* weight, float* bias, float* running_mean, float* running_var, float eps, int size) {
    
    
    for (int i = 0; i < size; i++) {
    
     // 遍历所有元素
        output[i] = (input[i] - running_mean[i]) / sqrt(running_var[i] + eps); // 进行标准化
        output[i] = output[i] * weight[i] + bias[i]; // 进行缩放和平移
    }
}

// 定义ReLU操作
void relu(float* input, float* output, int size) {
    
    
    for (int i = 0; i < size; i++) {
    
     // 遍历所有元素
        output[i] = fmax(0, input[i]); // 取max(0, x)
    }
}

// 定义池化操作
void max_pool(float* input, float* output, int in_channel, int out_channel, int kernel_size, int stride) {
    
    
    int output_size = (in_channel - kernel_size) / stride + 1; // 计算池化输出的大小
    for (int i = 0; i < out_channel; i++) {
    
     // 遍历输出通道
        for (int j = 0; j < output_size; j++) {
    
     // 遍历输出空间
            for (int k = 0; k < output_size; k++) {
    
    
                float max_val = -INFINITY;
                for (int l = 0; l < kernel_size; l++) {
    
     // 遍历池化核空间
                    for (int m = 0; m < kernel_size; m++) {
    
    
                        int input_row = j * stride + l; // 计算输入位置
                        int input_col = k * stride + m;
                        if (input_row >= 0 && input_row < in_channel && input_col >= 0 && input_col < in_channel) {
    
     // 判断输入是否合法
                            max_val = fmax(max_val, input[input_row * in_channel * in_channel + input_col * in_channel + i]); // 取最大值
                        }
                    }
                }
                output[i * output_size * output_size + j * output_size + k] = max_val; // 将最大值存入输出
            }
        }
    }
}

// 定义ResNet34的一个BasicBlock
void basic_block(float* input, float* output, float* weight1, float* bias1, float* weight2, float* bias2, float* weight3, float* bias3, float* weight4, float* bias4, float* running_mean1, float* running_var1, float* running_mean2, float* running_var2, float eps, int in_channel, int out_channel, int stride) {
    
    
    float* bn1 = (float*)malloc(sizeof(float) * in_channel * in_channel * in_channel); // 创建中间变量
    float* relu1 = (float*)malloc(sizeof(float) * in_channel * in_channel * in_channel);
    float* conv1 = (float*)malloc(sizeof(float) * out_channel * in_channel * 3 * 3);
    float* bn2 = (float*)malloc(sizeof(float) * out_channel * in_channel * in_channel);
    float* relu2 = (float*)malloc(sizeof(float) * out_channel * in_channel * in_channel);
    float* conv2 = (float*)malloc(sizeof(float) * out_channel * out_channel * 3 * 3);
    float* shortcut = input;
    if (in_channel != out_channel || stride != 1) {
    
     // 如果输入和输出维度不同或步长不同,使用1x1卷积调整输入的维度
        shortcut = (float*)malloc(sizeof(float) * out_channel * in_channel * in_channel);
        conv(input, shortcut, weight3, bias3, in_channel, out_channel, 1, stride, 0);
    }
    batch_norm(input, bn1, weight1, bias1, running_mean1, running_var1, eps, in_channel * in_channel * in_channel); // BN操作
    relu(bn1, relu1, in_channel * in_channel * in_channel); // ReLU操作
    conv(relu1, conv1, weight2, bias2, in_channel, in_channel, 3, stride, 1); // 3x3卷积
    batch_norm(conv1, bn2, weight4, bias4, running_mean2, running_var2, eps, out_channel * in_channel * in_channel); // BN操作
    relu(bn2, relu2, out_channel * in_channel * in_channel); // ReLU操作
    conv(relu2, conv2, weight2 + in_channel * in_channel * 3 * 3, bias2 + out_channel, out_channel, out_channel, 3, 1, 1); // 3x3卷积
    for (int i = 0; i < out_channel; i++) {
    
     // 将两个卷积结果加起来
        for (int j = 0; j < in_channel; j++) {
    
    
            for (int k = 0; k < in_channel; k++) {
    
    
                output[i * in_channel * in_channel + j * in_channel + k] = conv2[i * in_channel * in_channel + j * in_channel + k] + shortcut[i * in_channel * in_channel + j * in_channel + k];
            }
        }
    }
    free(bn1); // 释放中间变量
    free(relu1);
    free(conv1);
    if (in_channel != out_channel || stride != 1) {
    
    
        free(shortcut);
    }
    free(bn2);
    free(relu2);
    free(conv2);
}

// 定义ResNet34的整个网络
void resnet34(float* input, float* output, float* weight, float* bias, float* running_mean1, float* running_var1, float* running_mean2, float* running_var2, float* running_mean3, float* running_var3, float* running_mean4, float* running_var4, float* running_mean5, float* running_var5, float eps) {
    
    
    float* bn1 = (float*)malloc(sizeof(float) * 64 * 224 * 224); // 创建中间变量
    float* relu1 = (float*)malloc(sizeof(float) * 64 * 224 * 224);
    float* conv1 = (float*)malloc(sizeof(float) * 64 * 3 * 3 * 7 * 7);
    float* pool1 = (float*)malloc(sizeof(float) * 64 * 112 * 112);
    float* block1 = (float*)malloc(sizeof(float) * 64 * 112 * 112);
    float* block2 = (float*)malloc(sizeof(float) * 128 * 56 * 56);
    float* block3 = (float*)malloc(sizeof(float) * 256 * 28 * 28);
    float* block4 = (float*)malloc(sizeof(float) * 512 * 14 * 14);
    max_pool(input, pool1, 3, 64, 3, 2); // 第一层:3x3最大池化层
    conv(pool1, bn1, weight, bias, 3, 64, 7, 2, 3); // 第二层:7x7卷积层
    batch_norm(bn1, relu1, weight + 64 * 7 * 7 * 3, bias + 64, running_mean1, running_var1, eps, 64 * 224 * 224); // BN操作
    relu(relu1, block1, 64 * 224 * 224); // ReLU操作
    basic_block(block1, block2, weight + 64 * 7 * 7 * 3 + 64 * 3 * 3, bias + 64 + 64, weight + 64 * 7 * 7 * 3 + 64 * 3 * 3 + 128 * 3 * 3, bias + 128, weight + 64 * 7 * 7 * 3 + 128 * 3 * 3, bias + 128, weight + 64 * 7 * 7 * 3 + 128 * 3 * 3 + 128 * 3 * 3, bias + 128, running_mean2, running_var2, running_mean3, running_var3, eps, 64, 128, 2); // 第三层:两个BasicBlock
    basic_block(block2, block3, weight + 64 * 7 * 7 * 3 + 128 * 3 * 3 * 2, bias + 128 * 2, weight + 64 * 7 * 7 * 3 + 128 * 3 * 3 * 3, bias + 256, weight + 64 * 7 * 7 * 3 + 256 * 3 * 3, bias + 256, weight + 64 * 7 * 7 * 3 + 256 * 3 * 3 * 2, bias + 256, running_mean4, running_var4, running_mean5, running_var5, eps, 128, 256, 2); // 第四层:两个BasicBlock
    basic_block(block3, block4, weight + 64 * 7 * 7 * 3 + 256 * 3 * 3 * 2, bias + 256 * 2, weight + 64 * 7 * 7 * 3 + 256 * 3 * 3 * 3, bias + 512, weight + 64 * 7 * 7 * 3 + 512 * 3 * 3, bias + 512, weight + 64 * 7 * 7 * 3 + 512 * 3 * 3 * 2, bias + 512, running_mean5, running_var5, running_mean5, running_var5, eps, 256, 512, 2); // 第五层:两个BasicBlock
    memcpy(output, block4, sizeof(float) * 512 * 14 * 14); // 输出结果
    free(bn1); // 释放中间变量
    free(relu1);
    free(conv1);
    free(pool1);
    free(block1);
    free(block2);
    free(block3);
    free(block4);
}

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

#define MAX_CHANNELS 512

// 定义卷积层的结构体
typedef struct {
    
    
    int input_channels; // 输入通道数
    int output_channels; // 输出通道数
    int kernel_size; // 卷积核大小
    float *weights; // 权重矩阵
    float *biases; // 偏置向量
} ConvLayer;

// 初始化卷积层的函数
void InitConvLayer(ConvLayer *layer, int input_channels, int output_channels, int kernel_size) {
    
    
    layer->input_channels = input_channels;
    layer->output_channels = output_channels;
    layer->kernel_size = kernel_size;
    int weights_size = input_channels * output_channels * kernel_size * kernel_size;
    int biases_size = output_channels;
    layer->weights = (float *)malloc(weights_size * sizeof(float));
    layer->biases = (float *)malloc(biases_size * sizeof(float));
    for (int i = 0; i < weights_size; i++) {
    
    
        layer->weights[i] = (float)rand() / RAND_MAX;
    }
    for (int i = 0; i < biases_size; i++) {
    
    
        layer->biases[i] = (float)rand() / RAND_MAX;
    }
}

// 卷积层的前向传播函数
void ConvLayerForward(ConvLayer *layer, float *input, float *output, int input_width, int input_height) {
    
    
    int output_width = input_width - layer->kernel_size + 1;
    int output_height = input_height - layer->kernel_size + 1;
    int weights_size = layer->input_channels * layer->output_channels * layer->kernel_size * layer->kernel_size;
    int biases_size = layer->output_channels;
    for (int i = 0; i < layer->output_channels; i++) {
    
    
        for (int j = 0; j < output_width; j++) {
    
    
            for (int k = 0; k < output_height; k++) {
    
    
                float sum = 0.0f;
                for (int l = 0; l < layer->input_channels; l++) {
    
    
                    for (int m = 0; m < layer->kernel_size; m++) {
    
    
                        for (int n = 0; n < layer->kernel_size; n++) {
    
    
                            sum += input[l * input_width * input_height + (j + m) * input_height + (k + n)] * layer->weights[((l * layer->output_channels + i) * layer->kernel_size + m) * layer->kernel_size + n];
                        }
                    }
                }
                output[i * output_width * output_height + j * output_height + k] = sum + layer->biases[i];
            }
        }
    }
}

// 定义残差块的结构体
typedef struct {
    
    
    ConvLayer conv1; // 第一个卷积层
    ConvLayer conv2; // 第二个卷积层
    float *residual; // 残差连接
} ResidualBlock;

// 初始化残差块的函数
void InitResidualBlock(ResidualBlock *block, int input_channels, int output_channels) {
    
    
    InitConvLayer(&block->conv1, input_channels, output_channels, 3);
    InitConvLayer(&block->conv2, output_channels, output_channels, 3);
    block->residual = (float *)malloc(MAX_CHANNELS * sizeof(float));
}

// 残差块的前向传播函数
void ResidualBlockForward(ResidualBlock *block, float *input, float *output, int input_width, int input_height) {
    
    
    ConvLayerForward(&block->conv1, input, output, input_width, input_height);
    for (int i = 0; i < block->conv1.output_channels; i++) {
    
    
        for (int j = 0; j < input_width - 2; j++) {
    
    
            for (int k = 0; k < input_height - 2; k++) {
    
    
                block->residual[i * (input_width - 2) * (input_height - 2) + j * (input_height - 2) + k] = output[i * (input_width - 2) * (input_height - 2) + j * (input_height - 2) + k];
            }
        }
    }
    for (int i = 0; i < block->conv1.output_channels; i++) {
    
    
        for (int j = 0; j < input_width - 2; j++) {
    
    
            for (int k = 0; k < input_height - 2; k++) {
    
    
                output[i * (input_width - 2) * (input_height - 2) + j * (input_height - 2) + k] = fmaxf(0.0f, block->residual[i * (input_width - 2) * (input_height - 2) + j * (input_height - 2) + k]);
            }
        }
    }
    ConvLayerForward(&block->conv2, output, output, input_width - 2, input_height - 2);
    for (int i = 0; i < block->conv2.output_channels; i++) {
    
    
        for (int j = 0; j < input_width - 4; j++) {
    
    
            for (int k = 0; k < input_height - 4; k++) {
    
    
                output[i * (input_width - 4) * (input_height - 4) + j * (input_height - 4) + k] += block->residual[i * (input_width - 2) * (input_height - 2) + (j + 1) * (input_height - 2) + (k + 1)];
            }
        }
    }
}

// 定义ResNet的结构体
typedef struct {
    
    
    ResidualBlock blocks[16];
    ConvLayer conv;
    int input_width;
    int input_height;
} ResNet;

// 初始化ResNet的函数
void InitResNet(ResNet *resnet, int input_width, int input_height) {
    
    
    resnet->input_width = input_width;
    resnet->input_height = input_height;
    InitConvLayer(&resnet->conv, 3, MAX_CHANNELS, 7);
    for (int i = 0; i < 16; i++) {
    
    
        if (i == 0) {
    
    
            InitResidualBlock(&resnet->blocks[i], MAX_CHANNELS, MAX_CHANNELS * 2);
        } else {
    
    
            InitResidualBlock(&resnet->blocks[i], MAX_CHANNELS * 2, MAX_CHANNELS * 2);
        }
    }
}

// ResNet的前向传播函数
void ResNetForward(ResNet *resnet, float *input, float *output) {
    
    
    ConvLayerForward(&resnet->conv, input, output, resnet->input_width, resnet->input_height);
    for (int i = 0; i < resnet->conv.output_channels; i++) {
    
    
        for (int j = 0; j < resnet->input_width - 6; j++) {
    
    
            for (int k = 0; k < resnet->input_height - 6; k++) {
    
    
                output[i * (resnet->input_width - 6) * (resnet->input_height - 6) + j * (resnet->input_height - 6) + k] = fmaxf(0.0f, output[i * (resnet->input_width - 6) * (resnet->input_height - 6) + j * (resnet->input_height - 6) + k]);
            }
        }
    }
    ResidualBlockForward(&resnet->blocks[0], output, output, resnet->input_width - 6, resnet->input_height - 6);
    for (int i = 1; i < 16; i++) {
    
    
        ResidualBlockForward(&resnet->blocks[i], output, output, resnet->input_width - 6, resnet->input_height - 6);
    }
}

// 测试代码
int main() {
    
    
    int input_width = 224;
    int input_height = 224;
    float *input = (float *)malloc(3 * input_width * input_height * sizeof(float));
    for (int i = 0; i < 3 * input_width * input_height; i++) {
    
    
        input[i] = (float)rand() / RAND_MAX;
    }
    float *output = (float *)malloc(MAX_CHANNELS * (input_width - 6) * (input_height - 6) * sizeof(float));
    ResNet resnet;
    InitResNet(&resnet, input_width, input_height);
    ResNetForward(&resnet, input, output);
    printf("Output: %f\n", output[0]);
    free(input);
    free(output);
    return 0;
}

Guess you like

Origin blog.csdn.net/qq_39506862/article/details/130894050