以前の続きです。ハンガリーにおけるchikenpox(水疱瘡)のデータを使って、BUTAPESTにおける患者数の3か月分の予想を行っています。今まではAutoML、LSTMを使って予測してきました。今回は、CNN(Convolutional Neural Network)を使って予測したいと思います。

Convolutional Neural Network

1次の畳み込み層を使って特徴を捉える試みです。tensorflowのConv1Dを使います。Conv1Dに読み込ませるデータを作成するまでが大変です。

feature engineering

feature engineeringは、前回と同様にlag変数を作成し、standardizationを行います。以下のコードの場合は現在、1つ前、2つ前のデータ(X)を使って1つ先のデータ(Y)を予想します。windowgenerator(dataframe,target,shift)のshiftを3に設定することでXとYを作成できます。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('hungary_chickenpox.csv')
df["Date"] = pd.to_datetime(df["Date"],format='%d/%m/%Y')

def windowgenerator(dataframe,target,shift):
    Y = np.array(df[target])  
    Y = np.expand_dims(Y,axis=1)
    for i in reversed(range(1,shift+1)):   
        if i == (shift):
            X = Y[(shift-i):(len(Y)-i), :] 
        else:
            X2 = Y[(shift-i):(len(Y)-i), :]
            X = np.concatenate([X,X2],axis=1)      
    Y = Y[shift:len(dataframe), :]
    return X,Y

X,Y = windowgenerator(df,"BUDAPEST",3)

# Standardization ( value - mean ) / SD
from sklearn.preprocessing import StandardScaler
stand_x = StandardScaler().fit(X)  
X = stand_x.transform(X)  
stand_y = StandardScaler().fit(Y)  
Y = stand_y.transform(Y) 

'''
#Normalization 0 ~ 1
from sklearn.preprocessing import MinMaxScaler
norm_x = MinMaxScaler().fit(X)  
X = norm_x.transform(X)  
norm_y = MinMaxScaler().fit(Y)  
Y = norm_y.transform(Y)  
'''

standardizationする前のX[0:5]をみます。

array([[168, 157, 96],
[157, 96, 163],
[ 96, 163, 122],
[163, 122, 174],
[122, 174, 153]], dtype=int64)

standardizationする前のY[0:5]を見ます。

array([[163],
[122],
[174],
[153],
[115]], dtype=int64)

わかりやすいようにラグ(shift)を3にしましたが、ラグ(shift)を30にして作成したデータを使って予測します。

X,Y = windowgenerator(df,”BUDAPEST”,30)

CNNの設計

from tensorflow.compat.v1 import ConfigProto
from tensorflow.compat.v1 import InteractiveSession
config = ConfigProto()
config.gpu_options.allow_growth = True
session = InteractiveSession(config=config)

from tensorflow import keras
from tensorflow.keras.layers import Input,Dense
from tensorflow.keras.layers import Dropout,Conv1D,MaxPool1D,Flatten
from tensorflow.keras.optimizers import Adam

X = np.expand_dims(X,axis=2) # ( , ) -> ( , ,1)

inputs = Input(shape=(X.shape[1],X.shape[2]))
conv1th = Conv1D(filters=30,kernel_size=3)(inputs)
drop1 = Dropout(0.2)(conv1th)
maxpool = MaxPool1D(pool_size=2)(drop1)
flat = Flatten()(maxpool)
dense1 = Dense(64,activation="relu")(flat)
outputs = Dense(1)(dense1)
model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(optimizer=Adam(learning_rate=0.01),
              loss= "mse",
              metrics = "mae")
model.summary()

以下のような構造になります。

それでは学習・予想させます。1未来ずつ、3か月分(13レコード分)を予想します。

for loopでkerasを学習させるには、重みのリセットを行わないと過学習を起こしてしまいます。リセットしないと前の重みを学習したまま次の学習に突入します。for loopに入る前(学習前)に重みを保存して、for loop毎にその重みをロードして学習したweightをリセットします。

weights = model.get_weights()
early = keras.callbacks.EarlyStopping(monitor='val_loss',
                                      min_delta=0,
                                      patience=5,
                                      verbose=0,
                                      mode='auto')

# forecast each 1 step (in total 13 records) 
result = pd.DataFrame(0,
                  index=np.arange(13),
                  columns=["pred", "true"])

last13 = len(X)-13
last = len(X)

for i in range(last13,last): 
    X_train = X[:i, :, :]
    Y_train = Y[:i, :]
    X_test = X[i:i+1, :, :]
    Y_test = Y[i:i+1, :]
           
    model.set_weights(weights)
        
    history = model.fit(X_train,
              Y_train,
              validation_split=0.1,
              epochs=100, 
              verbose=2,
              callbacks=[early])
    
    pred = model.predict(X_test)

    ##### visualization #####
    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('model loss')
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train', 'validation'], loc='upper left')
    plt.show()
    ##### visualization #####

    result.iloc[i-(last13),0] = float(pred[0,0])
    result.iloc[i-(last),1] = Y_test[0,0]

学習過程をグラフ化しました。もっと学習させるべきであったかもしれません。

MAEを計算します。逆standardizeをして元のスケールに戻してからMAEを計算します。

result["pred"] = stand_y.inverse_transform(np.expand_dims(result["pred"],axis=1))
result["true"] = stand_y.inverse_transform(np.expand_dims(result["true"],axis=1))
result["mae"] = abs(result["true"]-result["pred"])
result["mae"].mean()

34.21

AutokerasがMAE31を出したので、それには負けてしまいました。LSTMとはだいたい同じくらいです。

予想値のグラフです。

import seaborn as sns
import matplotlib.pyplot as plt
from dfply import *

fig, ax = plt.subplots()
sns.lineplot(x=df["Date"] >> tail(-3)  >> tail(50) >> head(-13),
             y=df["BUDAPEST"]>> tail(-3) >> tail(50) >> head(-13),
             color="blue",legend='auto')

sns.lineplot(x=df["Date"]>>tail(-3)  >> tail(13),
             y=df["BUDAPEST"]>>tail(-3) >>tail(13),
             color="skyblue",legend='auto')

sns.lineplot(x = (df["Date"]>>tail(-3) >> tail(13)).reset_index(drop=True),
             y=result["pred"],
             color="red",legend='auto')

ax.legend(["True value", "True value", "Predicted value"])
sns.set_theme(style="darkgrid")
plt.show()

Categories:

category