C++实现长短期记忆人工神经网络LSTM(附带源码)
一、项目背景详细介绍
长短期记忆网络(Long Short-Term Memory,LSTM)是循环神经网络(RNN)的一个变体,专门为解决普通 RNN 在长序列训练中遭遇的梯度消失与梯度爆炸问题而提出。LSTM 在时间序列建模、语音识别、机器翻译、文本生成、时间序列预测等领域有广泛应用。
LSTM 的核心是引入了门控结构(输入门、遗忘门、输出门)和细胞状态(cell state),它们允许网络在长时间跨度内学习并保留信息。对于想深入理解神经网络底层实现的人,手写一个从零实现(仅使用标准 C++、不依赖深度学习库)的 LSTM 是非常有价值的练习:你会弄清每一步前向计算、状态维护、反向传播(BPTT)的细节与参数更新。
本项目目标:用纯 C++(标准库)实现一个可以训练的 LSTM 单元与简单序列模型,包含前向传播、反向传播(含门控梯度计算)、参数更新(SGD),并给出一个小型 demo(如学习简单序列的下一个值预测或字符级序列),代码清晰注释,适合教学与博客使用。
二、项目需求详细介绍
核心需求
-
使用纯 C++(只用 STL,不依赖 Eigen、OpenBLAS、PyTorch 等外部库)。
-
实现基础线性代数工具(矩阵/向量操作)。
-
实现 LSTM 单元(single-layer )的前向与反向传播:
-
支持批次(batch)大小 = 1(教学版以简化实现),但代码结构易扩展到批处理。
-
支持序列长度可变。
-
-
实现损失函数(均方误差 MSE 或交叉熵,示例使用 MSE 做序列回归)。
-
实现反向传播 Through Time(BPTT)以计算梯度,并用 SGD 更新参数。
-
提供训练 demo:比如用 LSTM 学习正弦序列的下一个值预测(小样本演示)。
-
代码封装良好,所有代码放在一个代码块中(不同“文件”用注释区分),详细注释。
-
文中包含方法作用解读(只解释方法作用,不重复代码)。
工程增强(可选但已实现部分)
-
梯度裁剪(gradient clipping)以避免爆炸。
-
可配置学习率、隐藏单元数、序列长度、迭代次数等超参。
-
简单随机初始化与种子控制,便于复现实验。
三、相关技术详细介绍

四、实现思路详细介绍
总体设计分两层:
-
基础矩阵/向量工具层:实现
Matrix与Vector的简易操作函数:乘法、加法、点乘、逐元素操作、转置、随机初始化等。为教学明确,使用std::vector<double>与 2Dstd::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;
}
更多推荐
所有评论(0)