以前の続きです。ハンガリーにおける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()