SorryToPerson logo
返回
算法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