算法2026-05-05·11 分钟
算法知识库:时间序列算法实现
JavaScript/TypeScript 实现时间序列分析算法,如ARIMA、指数平滑、LSTM等。
时间序列算法实现
1. 简单指数平滑
ts
function simpleExponentialSmoothing(data: number[], alpha: number = 0.3): number[] {
if (data.length === 0) return [];
const smoothed: number[] = [data[0]]; // 第一个值作为初始平滑值
for (let i = 1; i < data.length; i += 1) {
const smoothedValue = alpha * data[i] + (1 - alpha) * smoothed[i - 1];
smoothed.push(smoothedValue);
}
return smoothed;
}2. 双指数平滑 (Holt 方法)
ts
function doubleExponentialSmoothing(data: number[], alpha: number = 0.3, beta: number = 0.1): number[] {
if (data.length < 2) return data;
const smoothed: number[] = [];
let level = data[0];
let trend = data[1] - data[0];
smoothed.push(level);
for (let i = 1; i < data.length; i += 1) {
const prevLevel = level;
level = alpha * data[i] + (1 - alpha) * (level + trend);
trend = beta * (level - prevLevel) + (1 - beta) * trend;
smoothed.push(level + trend);
}
return smoothed;
}3. 三指数平滑 (Holt-Winters 方法)
ts
function tripleExponentialSmoothing(data: number[], alpha: number = 0.3, beta: number = 0.1, gamma: number = 0.1, seasonLength: number = 12): number[] {
if (data.length < seasonLength * 2) return data;
const smoothed: number[] = [];
const seasonal = new Array(seasonLength).fill(0);
// 初始化季节性成分
for (let i = 0; i < seasonLength; i += 1) {
seasonal[i] = (data[i] / data.slice(0, seasonLength).reduce((sum, val) => sum + val, 0)) * seasonLength;
}
let level = data.slice(0, seasonLength).reduce((sum, val) => sum + val, 0) / seasonLength;
let trend = (data[seasonLength] - data[0]) / seasonLength;
for (let i = 0; i < data.length; i += 1) {
const seasonIndex = i % seasonLength;
if (i >= seasonLength) {
const prevLevel = level;
level = alpha * (data[i] / seasonal[seasonIndex]) + (1 - alpha) * (level + trend);
trend = beta * (level - prevLevel) + (1 - beta) * trend;
seasonal[seasonIndex] = gamma * (data[i] / (prevLevel + trend)) + (1 - gamma) * seasonal[seasonIndex];
}
smoothed.push((level + trend) * seasonal[seasonIndex]);
}
return smoothed;
}4. ARIMA 模型 (简化版)
ts
class ARIMAModel {
private p: number; // 自回归阶数
private d: number; // 差分阶数
private q: number; // 移动平均阶数
private phi: number[]; // AR 系数
private theta: number[]; // MA 系数
private data: number[];
private differenced: number[];
constructor(p: number = 1, d: number = 1, q: number = 1) {
this.p = p;
this.d = d;
this.q = q;
this.phi = new Array(p).fill(0);
this.theta = new Array(q).fill(0);
this.data = [];
this.differenced = [];
}
fit(data: number[]): void {
this.data = [...data];
this.differenced = this.difference(data, this.d);
// 简化版:使用线性回归估计参数
this.estimateParameters();
}
private difference(data: number[], d: number): number[] {
let result = [...data];
for (let i = 0; i < d; i += 1) {
result = result.slice(1).map((val, idx) => val - result[idx]);
}
return result;
}
private estimateParameters(): void {
// 简化版:随机初始化参数
for (let i = 0; i < this.p; i += 1) {
this.phi[i] = Math.random() * 0.5;
}
for (let i = 0; i < this.q; i += 1) {
this.theta[i] = Math.random() * 0.5;
}
}
predict(steps: number = 1): number[] {
const predictions: number[] = [];
let currentData = [...this.differenced];
for (let step = 0; step < steps; step += 1) {
let prediction = 0;
// AR 部分
for (let i = 0; i < this.p; i += 1) {
const idx = currentData.length - 1 - i;
if (idx >= 0) {
prediction += this.phi[i] * currentData[idx];
}
}
// MA 部分 (简化版)
// 实际实现需要维护误差项
predictions.push(prediction);
currentData.push(prediction);
}
// 逆差分
return this.inverseDifference(predictions, this.data.slice(-this.d));
}
private inverseDifference(predictions: number[], lastValues: number[]): number[] {
let result = [...predictions];
for (let d = this.d - 1; d >= 0; d -= 1) {
const integrated: number[] = [];
let cumulative = lastValues[d];
for (const pred of result) {
cumulative += pred;
integrated.push(cumulative);
}
result = integrated;
}
return result;
}
}5. 移动平均
ts
function movingAverage(data: number[], windowSize: number): number[] {
if (windowSize <= 0 || windowSize > data.length) {
throw new Error('Invalid window size');
}
const result: number[] = [];
for (let i = windowSize - 1; i < data.length; i += 1) {
const sum = data.slice(i - windowSize + 1, i + 1).reduce((acc, val) => acc + val, 0);
result.push(sum / windowSize);
}
return result;
}
function weightedMovingAverage(data: number[], weights: number[]): number[] {
if (weights.length === 0 || weights.length > data.length) {
throw new Error('Invalid weights');
}
const result: number[] = [];
const weightSum = weights.reduce((sum, w) => sum + w, 0);
for (let i = weights.length - 1; i < data.length; i += 1) {
let sum = 0;
for (let j = 0; j < weights.length; j += 1) {
sum += data[i - j] * weights[j];
}
result.push(sum / weightSum);
}
return result;
}6. 季节性分解
ts
function seasonalDecompose(
data: number[],
seasonLength: number,
): {
trend: number[];
seasonal: number[];
residual: number[];
} {
const n = data.length;
// 计算趋势 (移动平均)
const trend: number[] = [];
for (let i = 0; i < n; i += 1) {
const start = Math.max(0, i - Math.floor(seasonLength / 2));
const end = Math.min(n, i + Math.floor(seasonLength / 2) + 1);
const sum = data.slice(start, end).reduce((acc, val) => acc + val, 0);
trend.push(sum / (end - start));
}
// 计算季节性
const seasonal: number[] = [];
for (let i = 0; i < n; i += 1) {
seasonal.push(data[i] - trend[i]);
}
// 计算季节性平均
const seasonalAvg: number[] = new Array(seasonLength).fill(0);
const counts: number[] = new Array(seasonLength).fill(0);
for (let i = 0; i < n; i += 1) {
const seasonIndex = i % seasonLength;
seasonalAvg[seasonIndex] += seasonal[i];
counts[seasonIndex] += 1;
}
for (let i = 0; i < seasonLength; i += 1) {
seasonalAvg[i] /= counts[i];
}
// 标准化季节性
const seasonalSum = seasonalAvg.reduce((sum, val) => sum + val, 0);
for (let i = 0; i < seasonLength; i += 1) {
seasonalAvg[i] -= seasonalSum / seasonLength;
}
// 应用季节性
const finalSeasonal: number[] = [];
for (let i = 0; i < n; i += 1) {
finalSeasonal.push(seasonalAvg[i % seasonLength]);
}
// 计算残差
const residual: number[] = [];
for (let i = 0; i < n; i += 1) {
residual.push(data[i] - trend[i] - finalSeasonal[i]);
}
return { trend, seasonal: finalSeasonal, residual };
}7. 简单 LSTM (用于时间序列预测)
ts
class SimpleLSTM {
private inputSize: number;
private hiddenSize: number;
private outputSize: number;
private learningRate: number;
// 权重矩阵 (简化版)
private Wf: number[][]; // 遗忘门
private Wi: number[][]; // 输入门
private Wo: number[][]; // 输出门
private Wc: number[][]; // 候选值
private bf: number[]; // 偏置
private bi: number[];
private bo: number[];
private bc: number[];
constructor(inputSize: number, hiddenSize: number, outputSize: number, learningRate: number = 0.01) {
this.inputSize = inputSize;
this.hiddenSize = hiddenSize;
this.outputSize = outputSize;
this.learningRate = learningRate;
// 初始化权重
this.Wf = this.randomMatrix(hiddenSize, inputSize + hiddenSize);
this.Wi = this.randomMatrix(hiddenSize, inputSize + hiddenSize);
this.Wo = this.randomMatrix(hiddenSize, inputSize + hiddenSize);
this.Wc = this.randomMatrix(hiddenSize, inputSize + hiddenSize);
this.bf = new Array(hiddenSize).fill(0);
this.bi = new Array(hiddenSize).fill(0);
this.bo = new Array(hiddenSize).fill(0);
this.bc = new Array(hiddenSize).fill(0);
}
private randomMatrix(rows: number, cols: number): number[][] {
const matrix: number[][] = [];
for (let i = 0; i < rows; i += 1) {
matrix[i] = [];
for (let j = 0; j < cols; j += 1) {
matrix[i][j] = (Math.random() - 0.5) * 0.1;
}
}
return matrix;
}
private sigmoid(x: number): number {
return 1 / (1 + Math.exp(-x));
}
private tanh(x: number): number {
return Math.tanh(x);
}
forward(input: number[], prevHidden: number[], prevCell: number[]): { hidden: number[]; cell: number[]; output: number[] } {
const combined = [...input, ...prevHidden];
// 遗忘门
const ft = this.matrixVectorMultiply(this.Wf, combined).map((x, i) => this.sigmoid(x + this.bf[i]));
// 输入门
const it = this.matrixVectorMultiply(this.Wi, combined).map((x, i) => this.sigmoid(x + this.bi[i]));
// 候选值
const cTilde = this.matrixVectorMultiply(this.Wc, combined).map((x, i) => this.tanh(x + this.bc[i]));
// 细胞状态
const cell = [];
for (let i = 0; i < this.hiddenSize; i += 1) {
cell[i] = ft[i] * prevCell[i] + it[i] * cTilde[i];
}
// 输出门
const ot = this.matrixVectorMultiply(this.Wo, combined).map((x, i) => this.sigmoid(x + this.bo[i]));
// 隐藏状态
const hidden = cell.map((c, i) => ot[i] * this.tanh(c));
// 输出 (简化版:直接使用隐藏状态)
const output = hidden.slice(0, this.outputSize);
return { hidden, cell, output };
}
private matrixVectorMultiply(matrix: number[][], vector: number[]): number[] {
const result: number[] = [];
for (let i = 0; i < matrix.length; i += 1) {
let sum = 0;
for (let j = 0; j < vector.length; j += 1) {
sum += matrix[i][j] * vector[j];
}
result.push(sum);
}
return result;
}
predict(sequence: number[][]): number[] {
let hidden = new Array(this.hiddenSize).fill(0);
let cell = new Array(this.hiddenSize).fill(0);
const outputs: number[] = [];
for (const input of sequence) {
const result = this.forward(input, hidden, cell);
hidden = result.hidden;
cell = result.cell;
outputs.push(...result.output);
}
return outputs;
}
}8. 实现要点
- 指数平滑适合趋势和季节性数据。
- ARIMA 结合自回归、差分和移动平均。
- 移动平均用于噪声平滑。
- 季节性分解分离趋势、季节性和残差。
- LSTM 适合复杂的时间序列模式。
算法时间序列JavaScript