You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否仿照数据结构节点实现方式编写Autoencoder节点代码?

用类链表节点方式实现简易Autoencoder的可行性与实现指导

可行性结论

完全可以用类似链表节点的思路实现Autoencoder,这种方式能绕开复杂AI框架,直观理解层级间的数据传递与参数更新逻辑,非常适合学习阶段的手动实现。

核心修正与实现步骤

1. 重新定义层节点(Node)

Autoencoder的每个层(编码/隐藏/解码)需要的核心要素远不止数据和指针,需扩展节点结构:

  • 输入/输出维度:确定权重矩阵的尺寸
  • 权重矩阵:层与层之间的连接参数
  • 偏置向量:每个神经元的偏移量
  • 激活函数:层的非线性变换逻辑
  • 指向下一层的指针

2. 重构Autoencoder类

不再模拟链表的“插入节点”逻辑,而是按编码→隐藏→解码的固定顺序构建层级链,同时实现前向传播、反向传播、训练等核心方法。

3. 实现核心功能函数

  • 激活函数(如Sigmoid、ReLU)
  • 前向传播(从输入层到输出层计算重建结果)
  • 损失计算(均方误差MSE,衡量重建与原图的差异)
  • 反向传播(根据损失更新各层的权重与偏置)

完整示例代码

#include <iostream>
#include <cmath>
#include <vector>

// 定义激活函数类型
typedef double (*ActivationFunc)(double);

// 层节点结构:代表Autoencoder的一层
struct LayerNode {
    int input_dim;       // 输入维度
    int output_dim;      // 输出维度
    std::vector<std::vector<double>> weights; // 权重矩阵 [output_dim][input_dim]
    std::vector<double> bias;                // 偏置向量 [output_dim]
    std::vector<double> output;              // 当前层的输出
    ActivationFunc activate;                 // 激活函数指针
    LayerNode* next;                         // 下一层指针

    // 构造函数:初始化层参数
    LayerNode(int in_dim, int out_dim, ActivationFunc func) 
        : input_dim(in_dim), output_dim(out_dim), activate(func), next(nullptr) {
        // 随机初始化权重(简单起见用[-0.1, 0.1]范围)
        weights.resize(out_dim, std::vector<double>(in_dim));
        bias.resize(out_dim);
        for (int i = 0; i < out_dim; i++) {
            bias[i] = (rand() % 200 - 100) / 1000.0;
            for (int j = 0; j < in_dim; j++) {
                weights[i][j] = (rand() % 200 - 100) / 1000.0;
            }
        }
    }
};

class Autoencoder {
private:
    LayerNode* input_layer;  // 输入层(编码层起点)
    LayerNode* output_layer; // 输出层(解码层终点)
    double learning_rate;    // 学习率

    // Sigmoid激活函数
    static double sigmoid(double x) {
        return 1.0 / (1.0 + exp(-x));
    }

    // Sigmoid的导数(用于反向传播)
    static double sigmoid_deriv(double x) {
        return x * (1.0 - x);
    }

public:
    Autoencoder(double lr = 0.1) : learning_rate(lr), input_layer(nullptr), output_layer(nullptr) {}

    // 添加层到Autoencoder链
    void add_layer(int in_dim, int out_dim, ActivationFunc func = sigmoid) {
        LayerNode* new_layer = new LayerNode(in_dim, out_dim, func);
        if (!input_layer) {
            input_layer = new_layer;
            output_layer = new_layer;
        } else {
            output_layer->next = new_layer;
            output_layer = new_layer;
        }
    }

    // 前向传播:计算输入的重建结果
    std::vector<double> forward(const std::vector<double>& input) {
        LayerNode* current = input_layer;
        std::vector<double> current_input = input;

        while (current) {
            // 计算加权和:output = weights * input + bias
            std::vector<double> weighted_sum(current->output_dim, 0.0);
            for (int i = 0; i < current->output_dim; i++) {
                for (int j = 0; j < current->input_dim; j++) {
                    weighted_sum[i] += current->weights[i][j] * current_input[j];
                }
                weighted_sum[i] += current->bias[i];
            }
            // 应用激活函数
            current->output.resize(current->output_dim);
            for (int i = 0; i < current->output_dim; i++) {
                current->output[i] = current->activate(weighted_sum[i]);
            }
            // 传递到下一层
            current_input = current->output;
            current = current->next;
        }
        return current_input;
    }

    // 训练一次:输入数据,计算损失并更新参数
    double train(const std::vector<double>& input) {
        // 1. 前向传播得到重建结果
        std::vector<double> reconstruction = forward(input);
        // 2. 计算损失(均方误差)
        double loss = 0.0;
        for (int i = 0; i < input.size(); i++) {
            loss += pow(input[i] - reconstruction[i], 2);
        }
        loss /= input.size();

        // 3. 反向传播:从输出层往输入层更新参数
        LayerNode* current = output_layer;
        std::vector<double> error;

        // 先处理输出层的误差
        error.resize(current->output_dim);
        for (int i = 0; i < current->output_dim; i++) {
            error[i] = (input[i] - current->output[i]) * sigmoid_deriv(current->output[i]);
        }

        // 反向遍历各层,更新权重和偏置
        while (current) {
            LayerNode* prev_layer = get_prev_layer(current);
            std::vector<double> prev_output = prev_layer ? prev_layer->output : input;

            // 更新权重
            for (int i = 0; i < current->output_dim; i++) {
                for (int j = 0; j < current->input_dim; j++) {
                    current->weights[i][j] += learning_rate * error[i] * prev_output[j];
                }
                // 更新偏置
                current->bias[i] += learning_rate * error[i];
            }

            // 计算上一层的误差(如果不是输入层)
            if (prev_layer) {
                std::vector<double> prev_error(prev_layer->output_dim, 0.0);
                for (int i = 0; i < prev_layer->output_dim; i++) {
                    for (int j = 0; j < current->output_dim; j++) {
                        prev_error[i] += current->weights[j][i] * error[j];
                    }
                    prev_error[i] *= sigmoid_deriv(prev_layer->output[i]);
                }
                error = prev_error;
            }

            current = prev_layer;
        }

        return loss;
    }

    // 辅助函数:获取当前层的前一层
    LayerNode* get_prev_layer(LayerNode* current) {
        if (!input_layer || current == input_layer) return nullptr;
        LayerNode* temp = input_layer;
        while (temp->next != current) {
            temp = temp->next;
        }
        return temp;
    }

    // 析构函数:释放内存
    ~Autoencoder() {
        LayerNode* current = input_layer;
        while (current) {
            LayerNode* next = current->next;
            delete current;
            current = next;
        }
    }
};

int main() {
    // 示例:构建一个简单的Autoencoder(输入784→隐藏32→输出784,模拟MNIST图像)
    Autoencoder ae(0.1);
    ae.add_layer(784, 32);    // 编码层:784维输入→32维隐藏特征
    ae.add_layer(32, 784);    // 解码层:32维特征→784维重建图像

    // 模拟训练数据(这里用随机数据代替真实图像)
    std::vector<double> input(784);
    for (int i = 0; i < 784; i++) {
        input[i] = rand() / (double)RAND_MAX;
    }

    // 训练1000次,打印损失变化
    for (int epoch = 0; epoch < 1000; epoch++) {
        double loss = ae.train(input);
        if (epoch % 100 == 0) {
            std::cout << "Epoch " << epoch << ", Loss: " << loss << std::endl;
        }
    }

    // 测试前向传播,得到重建结果
    std::vector<double> recon = ae.forward(input);
    std::cout << "First pixel input: " << input[0] << ", Reconstructed: " << recon[0] << std::endl;

    return 0;
}

关键说明

  1. 节点设计:每个LayerNode对应Autoencoder的一层,包含了该层所有必要的参数和计算逻辑,通过next指针串联成编码-隐藏-解码的链状结构,和你最初设想的链表节点思路一致。
  2. 核心逻辑:手动实现了前向传播和反向传播,没有依赖任何AI库,所有矩阵运算、参数更新都是手写的,能清晰看到每一步的细节。
  3. 局限性:这种手动实现只适合学习和小型实验,实际项目中还是建议用PyTorch、TensorFlow等框架(它们优化了性能、并行计算和内存管理),但学习阶段的手动实现能帮你彻底理解Autoencoder的工作原理。

内容的提问来源于stack exchange,提问作者Ibrar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 06:20:23