在瀏覽器中進行深度學習:TensorFlow.js (十一)時間序列預測

時間序列是對某一個或者一組變量 x(t) 進行觀察測量,將在一系列時刻 t1,t2,⋯,tn 所獲得的離散數字組成的序列集合。javascript

時間序列預測的機器學習的一種常見應用,例如預測股票和金融產品價格走勢, 溫度,天氣的走勢等等html

傳統的統計學時間序列預測的一些方法包括:java

  • AR (自迴歸模型 Auto Regression)
  • MA (移動平均 Moving Average )
  • ACF/PACF (自相關和偏自相關)
  • ARIMA (AR和MA模型的結合)
  • 卡爾曼濾波python

可是這些傳統的基於統計的方法用起來並不方便,你可能須要利用ACF/PACF找到一些時序數據的規律而後設置超參數來進行時序數據的分析。git

facebook開源的prophet是另外一個選擇,利用prophet來進行時許數據的分析比較簡單,可是本質上,prophet是基於另外一盒開源工具pystan,仍然是基於統計來進行時序數據的分析。github

利用深度神經網絡,例如LSTM,咱們也能夠很方便的進行時序數據的分析,那麼咱們就看看如可利用TensorflowJS來進行時序數據的分析吧。算法

 

數據導入

咱們要分析的數據集是一組模擬的航空旅客的數據信息,以下圖:api

這組數據能夠當作一個線性模型和一個震盪模型(正餘弦)的疊加,咱們看看LSTM能不能學習出這個規律。瀏覽器

async function loadData(path) {
  return await d3.csv(path);
}

const airPassagnerData = await loadData(
    "https://cdn.jsdelivr.net/gh/gangtao/datasets@master/csv/air_passengers.csv"
  );

數據準備

在數據準備階段,咱們要爲咱們的LSTM模型準備訓練數據。網絡

首先咱們對數據進行作一個標準化操做:

// Normalize data with value change
  let changeData = [];
  for (let i = 1; i < airPassagnerData.length; i++) {
    const item = {};
    item.date = airPassagnerData[i].Date;
    const val = parseInt(airPassagnerData[i].Number);
    const val0 = parseInt(airPassagnerData[i - 1].Number);
    item.value = val / val0 - 1;
    changeData.push(item);
  }

也就是把絕對數據變成漲跌的百分比,標準化後的數據都在 -1 和 +1之間,反映了數據相對於前一個時間點的漲跌。

而後就是就是準備訓練數據了,在準備訓練數據以前,咱們要理解一下LSTM的輸入數據的形狀:

LSTM須要一個三維的數據輸入,分別對應Batch(數據是一批一批進入神經網絡訓練),Input是輸入數據的size和Timestep,時間。因此咱們在構造訓練數據的時候,須要針對時間維度建立訓練數據。

const STEP_SIZE = 3;
const STEP_NUM = 24;
const STEP_OFFSET = 1;
const TARGET_SIZE = 12;

function buildX(data, stepSize, stepNum, stepOffset) {
  let xData = [];
  for (let n = 0; n < stepNum; n += 1) {
    const startIndex = n * stepOffset;
    const endIndex = startIndex + stepSize;
    const item = data.slice(startIndex, endIndex).map(obj => obj.value);
    xData.push(item);
  }
  return xData;
}

function makeTrainData(data, stepSize, stepNum, stepOffset, targetSize) {
  const trainData = { x: [], y: [] };

  const length = data.length;

  const xLength = stepSize + stepOffset * (stepNum - 1);
  const yLength = targetSize;
  const stopIndex = length - xLength - yLength;

  for (let i = 0; i < stopIndex; i += 1) {
    const x = data.slice(i, i + xLength);
    const y = data.slice(i + xLength + 1, i + xLength + 1 + yLength);

    const xData = buildX(x, stepSize, stepNum, stepOffset);
    const yData = y;

    trainData.x.push(xData.map(item => item));
    trainData.y.push(yData.map(item => item.value));
  }
  return trainData;
}

const trainData = makeTrainData(
    changeData,
    STEP_SIZE,
    STEP_NUM,
    STEP_OFFSET,
    TARGET_SIZE
  );

這裏咱們利用滑動時間窗口的方法來構建訓練數據,用到了如下幾個參數:

  • STEP_SIZE
    這個是數據窗口的大小,3表示每個數據包含三個值,數據input維度就是3
  • STEP_NUM 
    這個是一共要走多少步,24表示走24步,那個Time維度就是24
  • STEP_OFFSET
    這個offset決定了時間窗口向前移動的時候,每次走多少個時間單位,我這裏取1,也就是每次走一步,這樣第一個數據和第二個數據其實存在兩個重複值。
  • TARGET_SIZE 
    這個是我要預測的時間長度,12表示預測12個月的數據。

 

模型構建

模型的構建代碼以下:

function buildModel() {
  const model = tf.sequential();

  //lstm input layer
  const hidden1 = tf.layers.lstm({
    units: LSTM_UNITS,
    inputShape: [STEP_NUM, STEP_SIZE],
    returnSequences: true
  });
  model.add(hidden1);

  //2nd lstm layer
  const output = tf.layers.lstm({
    units: TARGET_SIZE,
    returnSequences: false
  });
  model.add(output);

  model.add(
    tf.layers.dense({
      units: TARGET_SIZE
    })
  );

  model.add(tf.layers.activation({ activation: "tanh" }));

  //compile
  const rmsprop = tf.train.rmsprop(0.005);
  model.compile({
    optimizer: rmsprop,
    loss: tf.losses.meanSquaredError
  });

  return model;
}

咱們的網絡一共有兩個LSTM層,一個Dense層和一個激活層。優化器咱們選擇了rmsprop,損失函數是mse,由於咱們其實一個迴歸模型。

利用TensorflowJS提供的模型導出功能,咱們能夠導出訓練好的模型,並利用Netron(一個神經網絡可視化工具,支持各類神經網絡)來查看咱們的模型。

const saveResults = await model.save('downloads://model-name');

我第一次用Netron的時候,TensorflowJS導出的JSON模型有錯誤,我給做者提了一個Issue,當天修復,贊一個!

模型訓練

訓練數據和模型都準備好了,下一步咱們就開始訓練了。

async function trainBatch(data, model) {
  const metrics = ["loss", "val_loss", "acc", "val_acc"];
  const container = {
    name: "show.fitCallbacks",
    tab: "Training",
    styles: {
      height: "1000px"
    }
  };
  const callbacks = tfvis.show.fitCallbacks(container, metrics);

  console.log("training start!");
  tfvis.visor();

  const epochs = config.epochs;
  const results = [];
  const xs = tf.tensor3d(data.x);
  const ys = tf.tensor2d(data.y);

  const history = await model.fit(xs, ys, {
    batchSize: config.batchSize,
    epochs: config.epochs,
    validationSplit: 0.2,
    callbacks: callbacks
  });

  console.log("training complete!");
  return history;
}

const trainData = makeTrainData(
    changeData,
    STEP_SIZE,
    STEP_NUM,
    STEP_OFFSET,
    TARGET_SIZE
  );
const model = buildModel();
model.summary();

爲了監控訓練的過程,咱們使用了tfvis,它在DOM中提供了一個可視化的框架,能夠很方便的監控訓練過程。

咱們發現,從開始到第30的迭代,損失指標有明顯的降低,這正是咱們但願看到的,30個迭代後,損失降低的不明顯。

預測

最後咱們能夠預測了,注意預測的時候,咱們須要把標準化的漲跌數據還原爲實際的值。

async function predict(model, input) {
    const prediction = await model.predict(tf.tensor([input])).data();
    return prediction;
  }

  const inputStart = changeData.length - X_LEN;
  const inputEnd = changeData.length;
  const input = changeData.slice(inputStart, inputEnd);
  const predictInput = buildX(input, STEP_SIZE, STEP_NUM, STEP_OFFSET);
  const prediction = await predict(model, predictInput);
  
  // re-constructe predicted value based on change
  const base = airPassagnerData[airPassagnerData.length-1];
  const baseDate = moment(new Date(base.Date));
  const baseValue = parseInt(base.Number);
  
  let predictionValue = [];
  let val = baseValue;
  for( let i = 0; i < prediction.length; i+=1) {
    const item = {};
    const date = baseDate.add(1, 'months');
    item.time = moment(date).format('YYYY-MM-DD');
    item.value = val + val * prediction[i];
    item.isPrediction = "Yes";
    predictionValue.push(item);
    val = item.value;
  }
  
  console.log(predictionValue);
  let airPassagnerDataWithPrediction = [];
  chartData.forEach(item => {
    item.isPrediction = "No";
    airPassagnerDataWithPrediction.push(item);
  })
  predictionValue.forEach(item => {
    airPassagnerDataWithPrediction.push(item);
  })

上圖是訓練了一個迭代後的結果。

上圖是10個迭代後的結果。

 

上圖是我訓練30個迭代後的結果,感受LSTM很好的學習到了趨勢和震盪。有興趣的同窗能夠去跑我在Codepen上的代碼。或者個人gist

總結

本文提供了一個使用LSTM來預測時間序列的例子。有一些經驗分享給你們:

  • 對於輸入數據,一開始是的size是1,可是訓練的效果不好,爲了有好的訓練效果,把Size增長到3,結果很不錯。
  • LSTM會有梯度消失的問題,若是選擇合適的網絡結構,參數須要有至關的經驗和嘗試。
  • 在本文的問題中,複雜和更深的網絡並不能帶來效果的明顯提高。
  • 咱們利用窗口滑動獲取訓練數據的過程和卷積操做很像,聽說利用CNN也能夠達到不錯的效果,有興趣的同窗能夠試一下。

 

參考

相關文章
相關標籤/搜索