時間序列

使用神經網路的時間序列預測中的一致偏移/滯後(提供所有程式碼)

  • August 24, 2020

我正在使用神經網路(keras 包)提前 48 小時預測比特幣價格。問題是由於某種原因,我的預測是“正確的”,但它們落後於真實值。我已經為此苦苦掙扎了好幾個星期。這是一個圖表,向您展示我的意思(紅色為真實值,藍色為模型預測): 在此處輸入圖像描述

我想你可以很清楚地看到兩條線的整體“形狀”非常匹配,但藍色的線條始終與紅色相抵消。我知道你可能在想什麼:它是神經網路拾取過去(自回歸)值並將它們複製到未來,因為它找不到更好的模式。我幾乎可以保證這不是解釋。

  1. 我的變數都不是比特幣價格的過去/滯後值。如在,所有變數都是外生的。
  2. 我已經嘗試手動(並通過設置 shuffle=TRUE)隨機化訓練數據的順序以消除任何可能的時間序列效應,但問題仍然存在。我在神經網路方面沒有太多經驗,但除非有其他方法可以讓神經網路將過去的值複製到未來,否則我真的不認為這是問題所在。

我試圖通過我的程式碼找到一個我錯誤地設置表格的地方,但我還沒有發現問題。下面,請找到我所有的程式碼和解釋。任何幫助將不勝感激,我一生都無法弄清楚問題所在。

在導入我的所有數據並對其進行修剪後,它們都按時間匹配(BTC 價格向量的第一個值是 2019 年 1 月 1 日 2:00,雜湊率向量的第一個值是 2019 年 1 月 1 日 2:00 等.),這就是我所做的:

# Actually offsetting the predictors and outcomes
lagg <- 48
# Cutting off first "lagg" outcome entries, so that the "ahead" entries are matched with past predictor entries
bitcoinpricecut <- bitcoinpriceprelag[(lagg):(length(bitcoinpriceprelag))]

“滯後”變數只是控制我試圖預測提前多少小時的變數。有趣的是,改變這個變數是唯一影響偏移的事情。如果我讓 lagg=0,偏移量就會消失。如果我增加它,偏移量就會增加。我切斷了 BTC 價格的第一個“滯後”條目,以使該向量中的第一個條目成為“未來”值。接下來,我切斷了每個預測變數的最後一個“滯後”條目,以便它們與 BTC 價格向量的長度相匹配

hashrate <- hashrate[1:(length(hashrate)-lagg)]
activeaddresses <- activeaddresses[1:(length(activeaddresses)-lagg)]
difficulty <- difficulty[1:(length(difficulty)-lagg)]
sopr <- sopr[1:(length(sopr)-lagg)]
tethertradingvol <- tethertradingvol[1:(length(tethertradingvol)-lagg)]
tradingvol <- tradingvol[1:(length(tradingvol)-lagg)]
bigaddresseshourly <- bigaddresseshourly[1:(length(bigaddresseshourly)-lagg)]
coindaysdestroyedhourly <- coindaysdestroyedhourly[1:(length(coindaysdestroyedhourly)-lagg)]
exchangeflowhourly <- exchangeflowhourly[1:(length(exchangeflowhourly)-lagg)]
minerrevenuehourly <- minerrevenuehourly[1:(length(minerrevenuehourly)-lagg)]
unrealizedprofitlosshourly <- unrealizedprofitlosshourly[1:(length(unrealizedprofitlosshourly)-lagg)]
tetherrichlisthourly <- tetherrichlisthourly[1:(length(tetherrichlisthourly)-lagg)]
tethersmartcontracthourly <- tethersmartcontracthourly[1:(length(tethersmartcontracthourly)-lagg)]

然後我將所有這些向量放在一個數據框中:

supervised <- data.frame('BitcoinPrice' = bitcoinpricecut)
supervised['HashRate'] <- hashrate 
supervised['ActiveAddresses'] <- activeaddresses 
supervised['Difficulty'] <- difficulty 
supervised['SOPR'] <- sopr
supervised['TetherTradingVol'] <- tethertradingvol 
supervised['TradingVol'] <- tradingvol 
supervised['AddressesOver10BTC'] <- bigaddresseshourly 
supervised['CDD'] <- coindaysdestroyedhourly 
supervised['ExchangeNetFlow'] <- exchangeflowhourly 
supervised['MinerRevenue'] <- minerrevenuehourly 
supervised['UnrealizedProfitLoss'] <- unrealizedprofitlosshourly 
supervised['TetherRichList'] <- tetherrichlisthourly 
supervised['TetherSmartContracts'] <- tethersmartcontracthourly 

接下來,我將該數據框分成兩部分,一部分用於訓練,另一部分用於測試:

# Splitting into training and testing
N = nrow(supervised)
n = round(N *0.8, digits = 0)
pretrain = supervised[1:(n), ]
pretest  = supervised[(n+1):N,  ]

然後我繼續規範化訓練數據集中的所有值:

recipe_obj <- recipe(BitcoinPrice ~ 
                     HashRate 
                    + ActiveAddresses 
                    + Difficulty 
                    + SOPR 
                    + TetherTradingVol 
                    + TradingVol 
                    + AddressesOver10BTC 
                    + CDD 
                    + ExchangeNetFlow 
                    + MinerRevenue 
                    + UnrealizedProfitLoss 
                    + TetherRichList 
                    + TetherSmartContracts, 
                    data=pretrain) %>%
             step_normalize(all_predictors()) %>%
             step_normalize(all_outcomes()) %>%
             prep()
df_processed_tbl <- bake(recipe_obj, pretrain)

接下來,我創建一個與目前測試數據幀(“pretest”)具有相同尺寸的數據幀,並用“pretest”中的值填充它,但已標準化(為了標準化這些值,我使用訓練數據集的平均值和標準差):

for (testsamp in 1:length(pretest$BitcoinPrice)){
 testingdatanorm[testsamp, 'BitcoinPrice'] <- (pretest$BitcoinPrice[testsamp] - recipe_obj$steps[[2]]$means['BitcoinPrice'])/(recipe_obj$steps[[2]]$sds['BitcoinPrice'])
 testingdatanorm[testsamp, 'HashRate'] <- (pretest$HashRate[testsamp] - recipe_obj$steps[[1]]$means['HashRate'])/(recipe_obj$steps[[1]]$sds['HashRate'])
 testingdatanorm[testsamp, 'ActiveAddresses'] <- (pretest$ActiveAddresses[testsamp] - recipe_obj$steps[[1]]$means['ActiveAddresses'])/(recipe_obj$steps[[1]]$sds['ActiveAddresses'])
 testingdatanorm[testsamp, 'Difficulty'] <- (pretest$Difficulty[testsamp] - recipe_obj$steps[[1]]$means['Difficulty'])/(recipe_obj$steps[[1]]$sds['Difficulty'])
 testingdatanorm[testsamp, 'SOPR'] <- (pretest$SOPR[testsamp] - recipe_obj$steps[[1]]$means['SOPR'])/(recipe_obj$steps[[1]]$sds['SOPR'])
 testingdatanorm[testsamp, 'TetherTradingVol'] <- (pretest$TetherTradingVol[testsamp] - recipe_obj$steps[[1]]$means['TetherTradingVol'])/(recipe_obj$steps[[1]]$sds['TetherTradingVol'])
 testingdatanorm[testsamp, 'TradingVol'] <- (pretest$TradingVol[testsamp] - recipe_obj$steps[[1]]$means['TradingVol'])/(recipe_obj$steps[[1]]$sds['TradingVol'])
 testingdatanorm[testsamp, 'AddressesOver10BTC'] <- (pretest$AddressesOver10BTC[testsamp] - recipe_obj$steps[[1]]$means['AddressesOver10BTC'])/(recipe_obj$steps[[1]]$sds['AddressesOver10BTC'])
 testingdatanorm[testsamp, 'CDD'] <- (pretest$CDD[testsamp] - recipe_obj$steps[[1]]$means['CDD'])/(recipe_obj$steps[[1]]$sds['CDD'])
 testingdatanorm[testsamp, 'ExchangeNetFlow'] <- (pretest$ExchangeNetFlow[testsamp] - recipe_obj$steps[[1]]$means['ExchangeNetFlow'])/(recipe_obj$steps[[1]]$sds['ExchangeNetFlow'])
 testingdatanorm[testsamp, 'MinerRevenue'] <- (pretest$MinerRevenue[testsamp] - recipe_obj$steps[[1]]$means['MinerRevenue'])/(recipe_obj$steps[[1]]$sds['MinerRevenue'])
 testingdatanorm[testsamp, 'UnrealizedProfitLoss'] <- (pretest$UnrealizedProfitLoss[testsamp] - recipe_obj$steps[[1]]$means['UnrealizedProfitLoss'])/(recipe_obj$steps[[1]]$sds['UnrealizedProfitLoss'])
 testingdatanorm[testsamp, 'TetherRichList'] <- (pretest$TetherRichList[testsamp] - recipe_obj$steps[[1]]$means['TetherRichList'])/(recipe_obj$steps[[1]]$sds['TetherRichList'])
 testingdatanorm[testsamp, 'TetherSmartContracts'] <- (pretest$TetherSmartContracts[testsamp] - recipe_obj$steps[[1]]$means['TetherSmartContracts'])/(recipe_obj$steps[[1]]$sds['TetherSmartContracts'])
 }

然後,我從預訓練/預測試數據幀的列(13 個預測變數)創建矩陣,以便用作我的神經網路的輸入。老實說,我並不完全理解這裡的矩陣轉換,我是從一個線上教程/NN 實現的演練中得到的。

x_train <- df_processed_tbl %>% select(1:13)
x_train <- as.matrix(x_train)
y_train <- df_processed_tbl %>% select(14)
y_train <- as.matrix(y_train)
x_test <- testingdatanorm %>% select(2:14)
x_test <- as.matrix(x_test)
y_test <- testingdatanorm %>% select(1)
y_test <- as.matrix(y_test)
dim(x_train) <- c((length(x_train))/13,1,13)
dim(x_test) <- c((length(x_test))/13,1,13)
length(x_test)
X_shape1 = dim(x_train)[2]
X_shape2 = dim(x_train)[3]

我的神經網路的設計(我之前嘗試過 LSTM 層,但它不能解決預測中的滯後/偏移問題)。無論如何,我懷疑這裡有問題,但是:

batch_size = 2          

model <- keras_model_sequential()
model%>%
 layer_dense(units=13, 
            batch_input_shape = c(batch_size, 1, 13), use_bias = TRUE) %>%
 layer_dense(units=75, batch_input_shape = c(batch_size, 1, 13)) %>%
 layer_dense(units=1)
model %>% compile(
 loss = 'mean_absolute_error',
 optimizer = optimizer_adam(lr= 0.00005, decay = 0.00000035),  
 metrics = c('mean_absolute_error')
)

在這裡,我訓練模型並創建數組以便以後生成預測。同樣,這不是我完全理解的東西,但我從另一個 NN 指南中得到它,它似乎有效。你還會注意到我只做了 5 個 Epochs - 這是因為出於某種原因,損失在 5 個 Epochs 之後停止減少:

Epochs <- 5
for (i in 1:Epochs){
 print(i)
 model %>% fit(x_train, y_train, epochs=1, batch_size=batch_size, verbose=1, shuffle=FALSE)
}

x_train_arr <- array(data = x_train, dim = c(length(x_train), 1, 1))
y_train_arr <- array(data = y_train, dim = c(length(y_train), 1))
x_test_arr <- array(data = x_test, dim=c(length(x_test),1 ,1))

最後,在訓練模型之後,我生成預測並反轉最初完成的正規化:

pred_out <- model %>% predict(x_test, batch_size = batch_size) 
pred_out <- as.matrix(pred_out)
norm_history_y <- recipe_obj$steps[[2]]$means['BitcoinPrice']
norm2_history_y <- recipe_obj$steps[[2]]$sds['BitcoinPrice']
nnpredictions <- c()
for (i in 1:length(pred_out)){
 nnpredictions <- c(nnpredictions, pred_out[i]*norm2_history_y + norm_history_y)
}

另一個維度變換:

dim(nnpredictions) <- c(length(pred_out),1)

最後,我將反向正規化應用於測試數據集(“y_test”)的“真實”值,並為 ggplot 準備一切:

y_nntest <- y_test*norm2_history_y
y_nntest <- y_nntest+norm_history_y
y_nntest <- as.data.frame(y_nntest)
nnpredictions <- as.data.frame(nnpredictions)

使用下面的程式碼,我生成了您之前看到的圖表:

p = ggplot() + 
 geom_line(data = nnpredictions, aes(x = seq(1, (length(nnpredictions$V1))), y = nnpredictions$V1), color = "blue") +
 geom_line(data = y_nntest, aes(x = seq(1, (length(y_nntest$BitcoinPrice))), y = y_nntest$BitcoinPrice), color = "red") +
 xlab('Dates') +
 ylab('Price')
p

這裡又是: 在此處輸入圖像描述

任何幫助將不勝感激。

首先,當你嘗試非線性建模時,你應該從線性模型開始。你試過一個嗎?結果是什麼?

看來你一天的非線性模型 $ d $ (藍色曲線)接近前一天的值(紅色曲線)。您的型號可能接近 $$ \hat Y(d)=Y(d-1)+f(Y(d-1),Y(d-2),\ldots;X(d-1),X(d),\ldots), $$ 其中非線性部分 $ f(\cdot) $ 是小。線性模型可能更好(或至少不是最差的)。

此外(在您問題評論中的討論之後),使用固定變數預測固定變數總是更好。回報比價格更穩定;這可能就是為什麼試圖預測價格最終會受到昨天價格的支配。

我可能會漏掉一點,但如果我正確理解了您的圖表,兩條曲線之間的平行性似乎表明您的 NN 大致預測了 2 天內的目前價格(因此存在時間滯後)。如果您根本沒有算法,而只是預測 Xt+2 = Xt,您將得到一條藍色曲線,該曲線與紅色曲線完全相同,具有相同的滯後。

此外,如果您保持損失函式不變,那麼試圖預測未來價格是一個糟糕的交易模型。事實上,如果你的錯誤很小,但預見的價格高於預期的價格,並且你做多 BTC,你就不會抱怨這個錯誤。相反,如果 2 天內的有效價格低於預測值,並且您做多 BTC 做空美元,您可能會損失現金……因此,假設只做多策略,您應該修改損失函式並懲罰低於預測值的預測培訓期間的有效價值。

希望這可以幫助。最好的,

引用自:https://quant.stackexchange.com/questions/57264