Abstract

     本次的內容是利用Yahoo股市裡的股價資料並針對A公司 (AAPL)來進行資料分析並且利用Long Short Term Memory Network(LSTM)進行預測,此篇主旨為分享資料分析方法與資料探勘課程作為參考案例。


Framework

此次的分析架構如上圖,因此次為連續值的預測,所以最後利用視覺化的方式來呈現結果。


資料收集

時間序列的資料與一般結構型資料最大的不同為有前後順序相關資訊,例如天氣預報中每月每日每小時不同的天氣狀況。
因此利用公開資料裡的A公司股價作為此次分析案例。


資料來源:
Source:YAHOO!FINANCE
Reference:Time Series Forecasting using LSTM in R

library(dwapi)
## Warning: package 'dwapi' was built under R version 3.5.3
library(keras)
## Warning: package 'keras' was built under R version 3.5.3
library(dplyr)
## Warning: package 'dplyr' was built under R version 3.5.3
## 
## Attaching package: 'dplyr'
## The following object is masked from 'package:dwapi':
## 
##     sql
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 3.5.3
library(ggthemes)
## Warning: package 'ggthemes' was built under R version 3.5.3
library(lubridate)
## Warning: package 'lubridate' was built under R version 3.5.3
## 
## Attaching package: 'lubridate'
## The following object is masked from 'package:base':
## 
##     date
library(tensorflow)
## Warning: package 'tensorflow' was built under R version 3.5.3
data <- read.csv('E:/AAPL.csv')       # read Data
summary(data)                         # summary
##          Date          Open            High            Low       
##  2018-03-14:  1   Min.   :144.0   Min.   :145.7   Min.   :142.0  
##  2018-03-15:  1   1st Qu.:171.3   1st Qu.:173.0   1st Qu.:169.7  
##  2018-03-16:  1   Median :186.3   Median :187.4   Median :184.9  
##  2018-03-19:  1   Mean   :187.6   Mean   :189.5   Mean   :185.8  
##  2018-03-20:  1   3rd Qu.:207.3   3rd Qu.:209.4   3rd Qu.:205.8  
##  2018-03-21:  1   Max.   :230.8   Max.   :233.5   Max.   :229.8  
##  (Other)   :245                                                  
##      Close         Adj.Close         Volume        
##  Min.   :142.2   Min.   :141.6   Min.   :12513900  
##  1st Qu.:171.0   1st Qu.:170.2   1st Qu.:22831100  
##  Median :186.1   Median :184.2   Median :29184000  
##  Mean   :187.7   Mean   :186.1   Mean   :32662699  
##  3rd Qu.:207.8   3rd Qu.:205.9   3rd Qu.:39070400  
##  Max.   :232.1   Max.   :230.3   Max.   :96246700  
## 
ggplot(data, aes(x=Date, y=Close)) +  # scatter polt
  geom_point(color='#56B4E9') +
  theme_grey(base_size = 16)

基本的先觀察此次的分析資料,總共有7個欄位分別為時間(Data), 開盤價(Open), 最高價(High), 最低價(Low), 收盤價(Close), 調整收盤價(Adj.Close), 與交易量(Volume)等。


先簡化模型難度,所以針對收盤價(Close)作為分析重點,並且利用散布圖(scatter polt)來檢視資料分布。


敘述統計

除了最基本的summary函式可以觀察出最大值(Max.),最小值(Min.),第一與第三四分位數(1st Qu. & 3rd Qu.),中位數(Median)與平均數(Mean),可以利用更多函式來檢視資料。

# mode function
getmode <- function(v) {
  uniqv <- unique(v)
  uniqv[which.max(tabulate(match(v, uniqv)))]
}
getmode(data$Close)
## [1] 172.8
# standard deviation
sd(data$Close)
## [1] 22.17417
# variance 
var(data$Close)
## [1] 491.6937

眾數(mode)可以觀察出股價最常出現的價位,標準差(standard deviation)與變異數(variance)用來觀察股價資料的離散程度。


資料預處理

因LSTM模型對於資料投入也需要時間序列的架構,在預處理上主要分為兩項步驟
1.擷取斜率(get slope)
2.正規化(normalization)


擷取斜率是為了觀察前後資料之間,有正負兩種斜率(方向),可以萃取資料特性,也是一種資料科學中特徵工程(Feature Enginnering)處理方式。
正規化有很多利於資料分析的原因,但比較大的目的是希望標準化資料之間的殘差,詳細的內容可以參考關於normalization相關文獻。

Series = data$Close  # target

# create shift dataset, e.g. t-1, t

lag_transform <- function(x, k= 1){
  
  lagged =  c(rep(NA, k), x[1:(length(x)-k)])
  DF = as.data.frame(cbind(lagged, x))
  colnames(DF) <- c( paste0('x-', k), 'x')
  DF[is.na(DF)] <- 0
  return(DF)
}
supervised = lag_transform(Series, 1)


# split transfer dataset and get Slope
diffed = diff(Series, differences = 1)

supervised = lag_transform(diffed, 1)

N = nrow(supervised)
n = round(N *0.8, digits = 0)
train = supervised[1:n, ]
test  = supervised[(n+1):N,  ]

## normalize function
normalize <- function(train, test, feature_range = c(0, 1)) {
  x = train
  fr_min = feature_range[1]
  fr_max = feature_range[2]
  std_train = ((x - min(x) ) / (max(x) - min(x)  ))
  std_test  = ((test - min(x) ) / (max(x) - min(x)  ))
  
  scaled_train = std_train *(fr_max -fr_min) + fr_min
  scaled_test = std_test *(fr_max -fr_min) + fr_min
  
  return( list(scaled_train = as.vector(scaled_train), scaled_test = as.vector(scaled_test) ,scaler= c(min =min(x), max = max(x))) )
  
}

## inverse-normalize
inverter = function(scaled, scaler, feature_range = c(0, 1)){
  min = scaler[1]
  max = scaler[2]
  n = length(scaled)
  mins = feature_range[1]
  maxs = feature_range[2]
  inverted_dfs = numeric(n)
  
  for( i in 1:n){
    X = (scaled[i]- mins)/(maxs - mins)
    rawValues = X *(max - min) + min
    inverted_dfs[i] <- rawValues
  }
  return(inverted_dfs)
}

# normalization

Scaled = normalize(train, test, c(-1, 1))


y_train = Scaled$scaled_train[, 2]
x_train = Scaled$scaled_train[, 1]

y_test = Scaled$scaled_test[, 2]
x_test = Scaled$scaled_test[, 1]

模型建立

在模型建立前,先簡單介紹關於LSTM此方法,此方法基礎為遞歸神經網路(Recurrent Neural Network, RNN),藉由循環的網路設計,使得某些預保留的訊息能夠傳遞到下一個神經元,但當需要保留的訊息太過冗長,在資料與資料間連結上會產生無法順利訓練,但在使用LSTM模型便能克服這個問題。


在設計上以input gate, output gate, memory cell與forget gate等設計將必要的透入資料做轉換與結合,詳細介紹藉由此下方連結進行學習。


Reference:Understanding LSTM Networks


了解基本的模型架構後,將剛處理好的資料投入到模型中,進行訓練。

dim(x_train) <- c(length(x_train), 1, 1) # 取得training的資料長度與維度
X_shape2 = dim(x_train)[2]               
X_shape3 = dim(x_train)[3]            
batch_size = 1
units = 10                               # 神經元數目

# 建立模型
model <- keras_model_sequential() 
model%>%
  layer_lstm(units, batch_input_shape = c(batch_size, X_shape2, X_shape3),return_sequences = TRUE, stateful= TRUE)%>%
  layer_lstm(units, stateful= TRUE,return_sequences = FALSE)%>%
  layer_dense(units = 1)



model %>% compile(
  loss = 'mean_squared_error',
  optimizer = optimizer_adam( lr= 0.02 , decay = 1e-6 ),  
  metrics = c('accuracy')
)


此次的模型設計layer為兩層,最後一層是output,每一層的神經元數目皆為10,其設計可以依照樣本的數量進行調整。

summary(model)
## ___________________________________________________________________________
## Layer (type)                     Output Shape                  Param #     
## ===========================================================================
## lstm_1 (LSTM)                    (1, 1, 10)                    480         
## ___________________________________________________________________________
## lstm_2 (LSTM)                    (1, 10)                       840         
## ___________________________________________________________________________
## dense_1 (Dense)                  (1, 1)                        11          
## ===========================================================================
## Total params: 1,331
## Trainable params: 1,331
## Non-trainable params: 0
## ___________________________________________________________________________


設計完模型的層數與節點數與損失函數等等參數後,可以利用summary確認建構出來的模型結構。最後設定好Epoch便可進行模型訓練。

Epochs = 50
nb_epoch = Epochs   
for(i in 1:nb_epoch ){
  model %>% fit(x_train, y_train, epochs=1, batch_size=batch_size, verbose=1, shuffle=FALSE)
  model %>% reset_states()
}

預測

完成了模型訓練,便要進行預測,在程式撰寫上必須要取得預測的資料長度,完成預測值後會進行逆正規化,將數值恢復到原本的單位,最後利用迴圈的設計,將值順序性的存取。

L = length(x_test)
scaler = Scaled$scaler
predictions = numeric(L)

for(i in 1:L){
     X = x_test[i]
     dim(X) = c(1,1,1)
     yhat = model %>% predict(X, batch_size=batch_size)
     # invert scaling
     yhat = inverter(yhat, scaler,  c(-1, 1))
     # invert differencing
     yhat  = yhat + Series[(n+i)]
     # store
     predictions[i] <- yhat
}

資料視覺化

最後利用ggplot將結果以線圖的方式呈現,如下圖,藍色的為實際股價,紅色為預測股價。

list1 <- rep(NA,201)
list2 <- c(list1,predictions)
data_plot1 <- as.data.frame(Series)
data_plot2 <- as.data.frame(list2)
ggplot(data_plot1,aes(x=seq_along(Series), y=Series)) + 
  geom_line(color='#56B4E9') +
  geom_line(data = data_plot2,aes(x=seq_along(list2), y=list2),color='red') +
  theme_grey(base_size = 16) +
  ggtitle("Prediction") +
  labs(x = "time index", y = "price")
## Warning: Removed 201 rows containing missing values (geom_path).


結論

此分享筆記希望能藉由資料科學架構運用在LSTM模型上,並且以股價資料作為分析對像,完成由資料收集,敘述統計…到最後的視覺化呈現結果,希望能對於讀者在資料科學上有更好的學習範例。