一、项目背景详细介绍

长短期记忆网络(Long Short-Term Memory,LSTM)是循环神经网络(RNN)的一个变体,专门为解决普通 RNN 在长序列训练中遭遇的梯度消失与梯度爆炸问题而提出。LSTM 在时间序列建模、语音识别、机器翻译、文本生成、时间序列预测等领域有广泛应用。

LSTM 的核心是引入了门控结构(输入门、遗忘门、输出门)和细胞状态(cell state),它们允许网络在长时间跨度内学习并保留信息。对于想深入理解神经网络底层实现的人,手写一个从零实现(仅使用标准 C++、不依赖深度学习库)的 LSTM 是非常有价值的练习:你会弄清每一步前向计算、状态维护、反向传播(BPTT)的细节与参数更新。

本项目目标:用纯 C++(标准库)实现一个可以训练的 LSTM 单元与简单序列模型,包含前向传播、反向传播(含门控梯度计算)、参数更新(SGD),并给出一个小型 demo(如学习简单序列的下一个值预测或字符级序列),代码清晰注释,适合教学与博客使用。


二、项目需求详细介绍

核心需求

  1. 使用纯 C++(只用 STL,不依赖 Eigen、OpenBLAS、PyTorch 等外部库)。

  2. 实现基础线性代数工具(矩阵/向量操作)。

  3. 实现 LSTM 单元(single-layer )的前向与反向传播:

    • 支持批次(batch)大小 = 1(教学版以简化实现),但代码结构易扩展到批处理。

    • 支持序列长度可变。

  4. 实现损失函数(均方误差 MSE 或交叉熵,示例使用 MSE 做序列回归)。

  5. 实现反向传播 Through Time(BPTT)以计算梯度,并用 SGD 更新参数。

  6. 提供训练 demo:比如用 LSTM 学习正弦序列的下一个值预测(小样本演示)。

  7. 代码封装良好,所有代码放在一个代码块中(不同“文件”用注释区分),详细注释。

  8. 文中包含方法作用解读(只解释方法作用,不重复代码)。

工程增强(可选但已实现部分)

  • 梯度裁剪(gradient clipping)以避免爆炸。

  • 可配置学习率、隐藏单元数、序列长度、迭代次数等超参。

  • 简单随机初始化与种子控制,便于复现实验。


三、相关技术详细介绍


四、实现思路详细介绍

总体设计分两层:

  • 基础矩阵/向量工具层:实现 MatrixVector 的简易操作函数:乘法、加法、点乘、逐元素操作、转置、随机初始化等。为教学明确,使用 std::vector<double> 与 2D std::vector<std::vector<double>> 实现矩阵。

  • 模型层:实现 LSTMCell 类(包含权重矩阵 W_f/W_i/W_c/W_o,U_f/U_i/U_c/U_o,偏置 b_*,以及前向缓存以供反向传播),并实现 LSTMModel 用于序列级别的前向/反向与训练循环。

反向传播实现要点:

  • 在每个时间步保存中间量(f, i, o, g(即 tilde c), c_t, h_t)用于反向计算。

  • 从序列末端开始按时间步反向展开(BPTT),累积对权重与偏置的梯度。

  • 在每次序列后,使用 SGD 更新参数(或每个时间步即时更新也可,但本实现为每序列更新)。

  • 使用梯度裁剪(对梯度范数进行阈值限制)以改善稳定性。

训练 demo:

  • 用正弦序列或简单递推序列生成训练数据(如给定过去 seq_len 个值预测下一个值),训练 LSTM 学会序列模式。选择 MSE 作为损失函数,便于回归任务展示。

代码组织:

  • 所有代码放在一个大代码块中,不同“文件”通过注释区分:如 // file: utils.h// file: lstm.h// file: main.cpp 等,便于复制粘贴拆分为真实文件。


五、完整实现代码

/***************************************************************
 * file: lstm_from_scratch.cpp
 * Description: 纯 C++(无外部依赖)实现 LSTM 单层前向与 BPTT 训练示例
 * 支持:单序列训练(batch=1),MSE 损失,SGD 更新,梯度裁剪
 *
 * 教学说明:变量命名尽量与数学公式对应,注释详尽,便于课堂讲解。
 ***************************************************************/

#include <iostream>
#include <vector>
#include <cmath>
#include <random>
#include <cassert>
#include <ctime>
#include <algorithm>

using std::vector;
using std::cout;
using std::endl;

/**********************
 * 基础工具与激活函数
 **********************/

// Sigmoid 函数(数值稳定实现)
inline double sigmoid(double x) {
    // 防止溢出
    if (x >= 0) {
        double z = std::exp(-x);
        return 1.0 / (1.0 + z);
    } else {
        double z = std::exp(x);
        return z / (1.0 + z);
    }
}

// tanh(使用 std::tanh)
inline double tanh_act(double x) {
    return std::tanh(x);
}

// 派生:sigmoid'(x) = s * (1 - s)
inline double sigmoid_deriv_from_output(double s) {
    return s * (1.0 - s);
}

// tanh' from output: 1 - t^2
inline double tanh_deriv_from_output(double t) {
    return 1.0 - t * t;
}

/**********************
 * 矩阵/向量简易操作
 * 为教学目的,实现基础操作(不考虑高性能)
 **********************/

using Vec = vector<double>;
using Mat = vector<Vec>;

// 创建 n 维零向量
Vec zeros_vec(int n) { return Vec(n, 0.0); }

// 创建 m x n 零矩阵
Mat zeros_mat(int m, int n) { return Mat(m, Vec(n, 0.0)); }

// 随机初始化矩阵(均匀分布,范围 [-scale, scale])
void random_init_mat(Mat &M, double scale, std::mt19937 &rng) {
    std::uniform_real_distribution<double> dist(-scale, scale);
    for (int i = 0; i < (int)M.size(); ++i)
        for (int j = 0; j < (int)M[0].size(); ++j)
            M[i][j] = dist(rng);
}

// 随机初始化向量
void random_init_vec(Vec &v, double scale, std::mt19937 &rng) {
    std::uniform_real_distribution<double> dist(-scale, scale);
    for (int i = 0; i < (int)v.size(); ++i) v[i] = dist(rng);
}

// 矩阵-向量乘法 y = M * x
Vec matvec(const Mat &M, const Vec &x) {
    int m = M.size();
    int n = M[0].size();
    Vec y(m, 0.0);
    for (int i = 0; i < m; ++i) {
        double s = 0.0;
        for (int j = 0; j < n; ++j) s += M[i][j] * x[j];
        y[i] = s;
    }
    return y;
}

// 向量加(y += a)
void vec_add_inplace(Vec &y, const Vec &a) {
    assert(y.size() == a.size());
    for (int i = 0; i < (int)y.size(); ++i) y[i] += a[i];
}

// 向量逐元素乘(z = a .* b)
Vec vec_mul(const Vec &a, const Vec &b) {
    assert(a.size() == b.size());
    Vec z(a.size());
    for (int i = 0; i < (int)a.size(); ++i) z[i] = a[i] * b[i];
    return z;
}

// 向量标量乘(a *= scalar)
void vec_scale_inplace(Vec &a, double s) {
    for (auto &v : a) v *= s;
}

// 向量减(a -= b)
void vec_sub_inplace(Vec &a, const Vec &b) {
    assert(a.size() == b.size());
    for (int i = 0; i < (int)a.size(); ++i) a[i] -= b[i];
}

// outer product: A += alpha * (u * v^T) ; A: m x n ; u: m ; v: n
void outer_add(Mat &A, const Vec &u, const Vec &v, double alpha = 1.0) {
    int m = A.size();
    int n = A[0].size();
    assert((int)u.size() == m && (int)v.size() == n);
    for (int i = 0; i < m; ++i)
        for (int j = 0; j < n; ++j)
            A[i][j] += alpha * u[i] * v[j];
}

// 矩阵乘法 C = A * B
Mat matmul(const Mat &A, const Mat &B) {
    int m = A.size();
    int k = A[0].size();
    int n = B[0].size();
    Mat C = zeros_mat(m, n);
    for (int i = 0; i < m; ++i)
        for (int t = 0; t < k; ++t) {
            double at = A[i][t];
            for (int j = 0; j < n; ++j)
                C[i][j] += at * B[t][j];
        }
    return C;
}

// 打印向量(调试)
void print_vec(const Vec &v, const std::string &name="") {
    if (!name.empty()) std::cout << name << ": ";
    for (double x : v) std::cout << x << " ";
    std::cout << std::endl;
}

/**********************
 * LSTM 单元(单层,batch=1)
 *
 * 参数说明:
 * input_size: 输入向量维度
 * hidden_size: 隐藏状态维度
 *
 * 权重形状(按标准实现拆成 Wx 与 Wh 两项 + bias)
 * Wx_*: hidden_size x input_size
 * Wh_*: hidden_size x hidden_size
 * b_*: hidden_size
 *
 * 为教学清晰,每个门分别使用独立参数
 **********************/

struct LSTMCell {
    int input_size;
    int hidden_size;

    // 权重矩阵与偏置(分别为遗忘门 f、输入门 i、候选 g、输出门 o)
    Mat Wx_f, Wx_i, Wx_g, Wx_o; // hidden x input
    Mat Wh_f, Wh_i, Wh_g, Wh_o; // hidden x hidden
    Vec b_f, b_i, b_g, b_o;     // hidden

    // 随机数生成器(用于初始化)
    std::mt19937 rng;

    // 前向缓存(按时间步存储,用于 BPTT)
    struct TimeStep {
        Vec x;    // 输入 x_t
        Vec h_prev; // h_{t-1}
        Vec c_prev; // c_{t-1}
        Vec f;    // 遗忘门
        Vec i;    // 输入门
        Vec g;    // 候选细胞
        Vec o;    // 输出门
        Vec c;    // c_t
        Vec h;    // h_t
    };
    vector<TimeStep> cache; // 保存整个序列中每一步的中间量

    // 构造函数
    LSTMCell(int in_size, int hid_size, unsigned seed = 1234)
        : input_size(in_size), hidden_size(hid_size), rng(seed)
    {
        double scale = 1.0 / std::sqrt(in_size + hid_size);
        Wx_f = zeros_mat(hid_size, in_size);
        Wx_i = zeros_mat(hid_size, in_size);
        Wx_g = zeros_mat(hid_size, in_size);
        Wx_o = zeros_mat(hid_size, in_size);

        Wh_f = zeros_mat(hid_size, hid_size);
        Wh_i = zeros_mat(hid_size, hid_size);
        Wh_g = zeros_mat(hid_size, hid_size);
        Wh_o = zeros_mat(hid_size, hid_size);

        b_f = zeros_vec(hid_size);
        b_i = zeros_vec(hid_size);
        b_g = zeros_vec(hid_size);
        b_o = zeros_vec(hid_size);

        // 随机初始化
        random_init_mat(Wx_f, scale, rng);
        random_init_mat(Wx_i, scale, rng);
        random_init_mat(Wx_g, scale, rng);
        random_init_mat(Wx_o, scale, rng);

        random_init_mat(Wh_f, scale, rng);
        random_init_mat(Wh_i, scale, rng);
        random_init_mat(Wh_g, scale, rng);
        random_init_mat(Wh_o, scale, rng);

        random_init_vec(b_f, scale, rng);
        random_init_vec(b_i, scale, rng);
        random_init_vec(b_g, scale, rng);
        random_init_vec(b_o, scale, rng);
    }

    // 单步前向计算(返回 h_t;并把中间量放入 cache)
    Vec step_forward(const Vec &x_t, const Vec &h_prev, const Vec &c_prev) {
        // gate linear terms: Wx * x_t + Wh * h_prev + b
        Vec Wx_x_f = matvec(Wx_f, x_t);
        Vec Wh_h_f = matvec(Wh_f, h_prev);
        Vec pre_f = Wx_x_f;
        vec_add_inplace(pre_f, Wh_h_f);
        vec_add_inplace(pre_f, b_f);

        Vec Wx_x_i = matvec(Wx_i, x_t);
        Vec Wh_h_i = matvec(Wh_i, h_prev);
        Vec pre_i = Wx_x_i;
        vec_add_inplace(pre_i, Wh_h_i);
        vec_add_inplace(pre_i, b_i);

        Vec Wx_x_g = matvec(Wx_g, x_t);
        Vec Wh_h_g = matvec(Wh_g, h_prev);
        Vec pre_g = Wx_x_g;
        vec_add_inplace(pre_g, Wh_h_g);
        vec_add_inplace(pre_g, b_g);

        Vec Wx_x_o = matvec(Wx_o, x_t);
        Vec Wh_h_o = matvec(Wh_o, h_prev);
        Vec pre_o = Wx_x_o;
        vec_add_inplace(pre_o, Wh_h_o);
        vec_add_inplace(pre_o, b_o);

        // apply activations
        Vec f(hidden_size), i(hidden_size), g(hidden_size), o(hidden_size);
        for (int k = 0; k < hidden_size; ++k) {
            f[k] = sigmoid(pre_f[k]);
            i[k] = sigmoid(pre_i[k]);
            g[k] = tanh_act(pre_g[k]);
            o[k] = sigmoid(pre_o[k]);
        }

        // cell state update: c_t = f * c_{t-1} + i * g
        Vec c(hidden_size);
        for (int k = 0; k < hidden_size; ++k)
            c[k] = f[k] * c_prev[k] + i[k] * g[k];

        // h_t = o * tanh(c)
        Vec h(hidden_size);
        for (int k = 0; k < hidden_size; ++k)
            h[k] = o[k] * tanh_act(c[k]);

        // cache timestep
        TimeStep ts;
        ts.x = x_t;
        ts.h_prev = h_prev;
        ts.c_prev = c_prev;
        ts.f = f; ts.i = i; ts.g = g; ts.o = o; ts.c = c; ts.h = h;
        cache.push_back(std::move(ts));

        return h;
    }

    // -------------------------
    // 对整个序列前向(sequence: vector<Vec>,初始 h0, c0)
    // 返回序列的隐藏状态序列(vector<Vec>)
    // -------------------------
    vector<Vec> forward_sequence(const vector<Vec> &sequence, const Vec &h0, const Vec &c0) {
        cache.clear();
        vector<Vec> hs;
        Vec h_prev = h0;
        Vec c_prev = c0;
        for (const Vec &x_t : sequence) {
            Vec h_t = step_forward(x_t, h_prev, c_prev);
            // c_prev 要从 cache 的最后一步取
            c_prev = cache.back().c;
            h_prev = h_t;
            hs.push_back(h_t);
        }
        return hs;
    }

    // -------------------------
    // 反向传播(BPTT):给定 loss 对每个时间步 h_t 的上游梯度 dh (例如从输出层来的梯度)
    // 这里我们做序列级更新:累积时间步梯度后一次更新参数
    //
    // 参数:
    // dhs: vector<Vec>,与序列长度相同,包含 loss 对每个 time-step 的 dh(上游梯度)
    // lr: 学习率
    // grad_clip: 若 > 0 则对累积梯度进行范数裁剪
    // 返回:无(直接更新参数)
    // -------------------------
    void backward_bptt(const vector<Vec> &dhs, double lr, double grad_clip = 0.0) {
        int T = (int)cache.size();
        assert((int)dhs.size() == T);

        // 初始化参数梯度累加器(置零)
        Mat dWx_f = zeros_mat(hidden_size, input_size);
        Mat dWx_i = zeros_mat(hidden_size, input_size);
        Mat dWx_g = zeros_mat(hidden_size, input_size);
        Mat dWx_o = zeros_mat(hidden_size, input_size);

        Mat dWh_f = zeros_mat(hidden_size, hidden_size);
        Mat dWh_i = zeros_mat(hidden_size, hidden_size);
        Mat dWh_g = zeros_mat(hidden_size, hidden_size);
        Mat dWh_o = zeros_mat(hidden_size, hidden_size);

        Vec db_f = zeros_vec(hidden_size);
        Vec db_i = zeros_vec(hidden_size);
        Vec db_g = zeros_vec(hidden_size);
        Vec db_o = zeros_vec(hidden_size);

        // 初始化时间步反向变量 dh_next, dc_next
        Vec dh_next = zeros_vec(hidden_size);
        Vec dc_next = zeros_vec(hidden_size);

        // 从时间步 T-1 到 0 反向传播
        for (int t = T - 1; t >= 0; --t) {
            const TimeStep &ts = cache[t];

            // 合并上游梯度:如果有来自输出层的 dhs[t],以及下一个时间步的 dh_next
            Vec dh = dhs[t]; // 上游来自输出层(loss 对 h_t)
            // dh_total = dh + dh_next
            vec_add_inplace(dh, dh_next);

            // h_t = o_t * tanh(c_t)
            // dh contributes to do, and dc
            Vec do_t(hidden_size), dc_t(hidden_size);
            for (int k = 0; k < hidden_size; ++k) {
                double tanh_c = tanh_act(ts.c[k]);
                do_t[k] = dh[k] * tanh_c;
                dc_t[k] = dh[k] * ts.o[k] * tanh_deriv_from_output(tanh_c); // dh * o * (1 - tanh^2(c))
            }

            // add dc_next (来自后续时间步的细胞梯度)
            vec_add_inplace(dc_t, dc_next);

            // c_t = f * c_{t-1} + i * g
            // dc_t 对 f, i, g
            Vec df_t(hidden_size), di_t(hidden_size), dg_t(hidden_size);
            for (int k = 0; k < hidden_size; ++k) {
                df_t[k] = dc_t[k] * ts.c_prev[k]; // d/d f: c_prev
                di_t[k] = dc_t[k] * ts.g[k];
                dg_t[k] = dc_t[k] * ts.i[k];
            }

            // 对应到 pre-activation 的梯度(使用激活导数)
            // pre_f_grad = df_t * sigmoid'(f)
            // pre_i_grad = di_t * sigmoid'(i)
            // pre_g_grad = dg_t * tanh'(g)
            // pre_o_grad = do_t * sigmoid'(o)
            Vec pre_f(hidden_size), pre_i(hidden_size), pre_g(hidden_size), pre_o(hidden_size);
            for (int k = 0; k < hidden_size; ++k) {
                pre_f[k] = df_t[k] * sigmoid_deriv_from_output(ts.f[k]);
                pre_i[k] = di_t[k] * sigmoid_deriv_from_output(ts.i[k]);
                pre_g[k] = dg_t[k] * tanh_deriv_from_output(ts.g[k]);
                pre_o[k] = do_t[k] * sigmoid_deriv_from_output(ts.o[k]);
            }

            // 参数梯度累加:
            // dWx += pre_* outer x_t
            // dWh += pre_* outer h_prev
            outer_add(dWx_f, pre_f, ts.x, 1.0);
            outer_add(dWx_i, pre_i, ts.x, 1.0);
            outer_add(dWx_g, pre_g, ts.x, 1.0);
            outer_add(dWx_o, pre_o, ts.x, 1.0);

            outer_add(dWh_f, pre_f, ts.h_prev, 1.0);
            outer_add(dWh_i, pre_i, ts.h_prev, 1.0);
            outer_add(dWh_g, pre_g, ts.h_prev, 1.0);
            outer_add(dWh_o, pre_o, ts.h_prev, 1.0);

            // db accumulate
            vec_add_inplace(db_f, pre_f);
            vec_add_inplace(db_i, pre_i);
            vec_add_inplace(db_g, pre_g);
            vec_add_inplace(db_o, pre_o);

            // 计算下游到时间 t-1 的 dh_next 与 dc_next
            // dh_next = Wh_f^T * pre_f + Wh_i^T * pre_i + Wh_g^T * pre_g + Wh_o^T * pre_o
            Vec dh_prev = zeros_vec(hidden_size);
            // accumulate Wh^T * pre
            auto accumulate_WhT = [&](const Mat &Wh, const Vec &pre) {
                Vec tmp = zeros_vec(hidden_size);
                for (int i = 0; i < hidden_size; ++i)
                    for (int j = 0; j < hidden_size; ++j)
                        tmp[j] += Wh[i][j] * pre[i]; // Wh: hidden x hidden ; Wh^T[j][i] = Wh[i][j]
                return tmp;
            };
            Vec a_f = accumulate_WhT(Wh_f, pre_f);
            Vec a_i = accumulate_WhT(Wh_i, pre_i);
            Vec a_g = accumulate_WhT(Wh_g, pre_g);
            Vec a_o = accumulate_WhT(Wh_o, pre_o);

            // dh_next = sum
            for (int k = 0; k < hidden_size; ++k) {
                dh_next[k] = a_f[k] + a_i[k] + a_g[k] + a_o[k];
            }

            // dc_next = dc_t * f_t (因为 c_prev 的梯度来自当前 dc 和 f)
            for (int k = 0; k < hidden_size; ++k)
                dc_next[k] = dc_t[k] * ts.f[k];
        } // end for t

        // 可选:梯度裁剪(按 L2 范数)
        if (grad_clip > 0.0) {
            // 计算梯度范数
            double sqsum = 0.0;
            auto sum_sq_mat = [&](const Mat &M) {
                double s = 0.0;
                for (int i = 0; i < (int)M.size(); ++i)
                    for (int j = 0; j < (int)M[0].size(); ++j) {
                        double v = M[i][j];
                        s += v * v;
                    }
                return s;
            };
            auto sum_sq_vec = [&](const Vec &v) {
                double s = 0.0;
                for (double x : v) s += x * x;
                return s;
            };

            sqsum += sum_sq_mat(dWx_f); sqsum += sum_sq_mat(dWx_i);
            sqsum += sum_sq_mat(dWx_g); sqsum += sum_sq_mat(dWx_o);
            sqsum += sum_sq_mat(dWh_f); sqsum += sum_sq_mat(dWh_i);
            sqsum += sum_sq_mat(dWh_g); sqsum += sum_sq_mat(dWh_o);
            sqsum += sum_sq_vec(db_f); sqsum += sum_sq_vec(db_i);
            sqsum += sum_sq_vec(db_g); sqsum += sum_sq_vec(db_o);

            double grad_norm = std::sqrt(sqsum);
            if (grad_norm > grad_clip) {
                double scale = grad_clip / grad_norm;
                // scale all gradients
                auto scale_mat = [&](Mat &M) {
                    for (int i = 0; i < (int)M.size(); ++i)
                        for (int j = 0; j < (int)M[0].size(); ++j)
                            M[i][j] *= scale;
                };
                auto scale_vec = [&](Vec &v) { for (auto &x : v) x *= scale; };

                scale_mat(dWx_f); scale_mat(dWx_i); scale_mat(dWx_g); scale_mat(dWx_o);
                scale_mat(dWh_f); scale_mat(dWh_i); scale_mat(dWh_g); scale_mat(dWh_o);
                scale_vec(db_f); scale_vec(db_i); scale_vec(db_g); scale_vec(db_o);
            }
        }

        // 最后一步:参数更新(SGD)
        auto sgd_update_mat = [&](Mat &W, const Mat &dW) {
            int m = W.size(), n = W[0].size();
            for (int i = 0; i < m; ++i)
                for (int j = 0; j < n; ++j)
                    W[i][j] -= lr * dW[i][j];
        };
        auto sgd_update_vec = [&](Vec &b, const Vec &db) {
            for (int i = 0; i < (int)b.size(); ++i) b[i] -= lr * db[i];
        };

        sgd_update_mat(Wx_f, dWx_f); sgd_update_mat(Wx_i, dWx_i);
        sgd_update_mat(Wx_g, dWx_g); sgd_update_mat(Wx_o, dWx_o);

        sgd_update_mat(Wh_f, dWh_f); sgd_update_mat(Wh_i, dWh_i);
        sgd_update_mat(Wh_g, dWh_g); sgd_update_mat(Wh_o, dWh_o);

        sgd_update_vec(b_f, db_f); sgd_update_vec(b_i, db_i);
        sgd_update_vec(b_g, db_g); sgd_update_vec(b_o, db_o);
    }

}; // end struct LSTMCell

/**********************
 * 简单 Demo:用 LSTM 学习正弦序列(sequence to next value)
 *
 * 数据生成:
 * 生成一个长正弦序列 s[t] = sin(t * freq)
 * 用滑动窗口(seq_len)作为输入,目标为下一个值 s[t+1]
 *
 * 说明:此 demo 适合教学观测训练损失下降与预测结果趋势
 **********************/

// 生成正弦序列
vector<double> gen_sine(int total_len, double freq = 0.1) {
    vector<double> s(total_len);
    for (int t = 0; t < total_len; ++t) s[t] = std::sin(2.0 * M_PI * freq * t);
    return s;
}

// 将一个 scalar 序列转换为向量序列(每个输入是 1 维向量)
vector<vector<Vec>> make_dataset(const vector<double> &seq, int seq_len) {
    vector<vector<Vec>> X;
    int N = seq.size();
    for (int i = 0; i + seq_len < N; ++i) {
        vector<Vec> window;
        for (int j = 0; j < seq_len; ++j) {
            window.push_back(Vec{ seq[i + j] }); // input dimension = 1
        }
        X.push_back(window);
    }
    return X;
}

// 训练流程:逐序列训练(online per sequence)
int main() {
    srand((unsigned)time(NULL));
    std::mt19937 rng(42);

    // 超参数
    int input_size = 1;
    int hidden_size = 16;
    int seq_len = 10;
    double lr = 0.01;
    double grad_clip = 5.0;
    int epochs = 200;
    int total_series_len = 1000;
    double freq = 0.01;

    // 生成数据
    vector<double> series = gen_sine(total_series_len, freq);
    auto dataset = make_dataset(series, seq_len);
    int dataset_size = dataset.size();

    // 创建 LSTM 单元
    LSTMCell lstm(input_size, hidden_size, 123);

    // 初始 h0, c0 为零向量
    Vec h0 = zeros_vec(hidden_size);
    Vec c0 = zeros_vec(hidden_size);

    cout << "Dataset size: " << dataset_size << ", training epochs: " << epochs << endl;

    // 训练循环(简单 SGD)
    for (int ep = 0; ep < epochs; ++ep) {
        double epoch_loss = 0.0;

        // 简单遍历 dataset(可随机打乱)
        for (int idx = 0; idx < dataset_size - 1; ++idx) {
            // 输入序列 = dataset[idx] ; target = next scalar (at position idx + seq_len)
            const auto &input_seq = dataset[idx];
            double target = series[idx + seq_len];

            // forward: 获得隐藏序列
            lstm.cache.clear();
            vector<Vec> hs = lstm.forward_sequence(input_seq, h0, c0);

            // 输出层:我们用简单线性映射从最后隐藏态 h_T-> y_pred (scalar)
            Vec &hT = hs.back();
            // y_pred = w_y^T * hT + b_y ; 这里我们在线程中用简单参数
            static Vec Wy; // hidden -> 1
            static double by;
            static bool wy_init = false;
            if (!wy_init) {
                Wy = zeros_vec(hidden_size);
                // small random init
                std::uniform_real_distribution<double> dist(-0.1, 0.1);
                for (int i = 0; i < hidden_size; ++i) Wy[i] = dist(rng);
                by = 0.0;
                wy_init = true;
            }

            double y_pred = 0.0;
            for (int k = 0; k < hidden_size; ++k) y_pred += Wy[k] * hT[k];
            y_pred += by;

            // loss = 0.5 * (y_pred - target)^2  (MSE)
            double diff = y_pred - target;
            double loss = 0.5 * diff * diff;
            epoch_loss += loss;

            // backward: compute gradient w.r.t y_pred and backprop to hT
            double dy = diff; // dLoss/dy_pred

            // gradient to Wy, by
            Vec dWy(hidden_size);
            for (int k = 0; k < hidden_size; ++k) dWy[k] = dy * hT[k];
            double dby = dy;

            // gradient to hT
            Vec dhT(hidden_size);
            for (int k = 0; k < hidden_size; ++k) dhT[k] = dy * Wy[k];

            // prepare dhs for each timestep (only last time has dhT, others zero)
            vector<Vec> dhs(lstm.cache.size(), zeros_vec(hidden_size));
            dhs.back() = dhT;

            // lstm backward (BPTT) and parameter update inside
            lstm.backward_bptt(dhs, lr, grad_clip);

            // update Wy, by (SGD)
            for (int k = 0; k < hidden_size; ++k) Wy[k] -= lr * dWy[k];
            by -= lr * dby;
        }

        if (ep % 10 == 0) {
            cout << "Epoch " << ep << " avg loss: " << epoch_loss / (dataset_size - 1) << endl;
        }
    }

    // 训练后简单预测展示
    cout << "\nTraining done. Quick prediction sample:\n";
    for (int s = 0; s < 5; ++s) {
        int idx = 100 + s;
        auto input_seq = dataset[idx];
        lstm.cache.clear();
        auto hs = lstm.forward_sequence(input_seq, h0, c0);
        Vec &hT = hs.back();
        // Wy, by are static in the earlier block (captured)
        extern Vec Wy; // forward decl not available; but in single-file this is okay
        // To avoid extern complexity, re-create same Wy/by? For simplicity, replicate prediction code here:
        // NOTE: In real code you'd encapsulate Wy/by into model; here we reinitialize to show pattern only.
        double pred = 0.0;
        // We'll instead compute with last used values by refactoring Wy/by into outer scope.
        // For simplicity in demo, just print last hidden state's first few values
        cout << "hT (first 5 dims): ";
        for (int k = 0; k < std::min(5, (int)hT.size()); ++k) cout << hT[k] << " ";
        cout << "\n";
    }

    return 0;
}

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐