時間序列是對某一個或者一組變量 x(t) 進行觀察測量,將在一系列時刻 t1,t2,⋯,tn 所獲得的離散數字組成的序列集合。javascript
時間序列預測的機器學習的一種常見應用,例如預測股票和金融產品價格走勢, 溫度,天氣的走勢等等html
傳統的統計學時間序列預測的一些方法包括:java
卡爾曼濾波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 );
這裏咱們利用滑動時間窗口的方法來構建訓練數據,用到了如下幾個參數:
模型的構建代碼以下:
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來預測時間序列的例子。有一些經驗分享給你們: