Peramalan Deret Waktu dengan Prophet, NeuralProphet, dan LSTM
Daftar Isi
Pendahuluan
Peramalan deret waktu (time series forecasting) merupakan salah satu penerapan ilmu data yang paling berdampak di dunia industri. Mulai dari memprediksi harga saham, konsumsi energi, hingga meramalkan permintaan produk dan beban server, kemampuan untuk mengantisipasi nilai masa depan berdasarkan pola historis sangatlah berharga.
Tutorial ini membahas tiga pendekatan andal untuk peramalan deret waktu: Facebook Prophet untuk dekomposisi aditif yang kokoh, NeuralProphet untuk peramalan berbasis jaringan saraf dengan autoregresi, dan LSTM (Long Short-Term Memory) yang dibangun dengan PyTorch untuk menangkap dependensi sekuensial yang kompleks.
Di akhir tutorial ini, Anda akan mampu membangun, mengevaluasi, dan membandingkan model peramalan berkualitas produksi menggunakan ketiga framework tersebut.
Prasyarat
- Python 3.9+
- Pemahaman dasar statistika (rata-rata, variansi, korelasi)
- Familiaritas dengan pandas dan matplotlib
Instalasi paket yang diperlukan:
pip install prophet neuralprophet torch pandas numpy matplotlib scikit-learn
Dasar-Dasar Deret Waktu
Deret waktu adalah urutan titik data yang diindeks berdasarkan urutan waktu. Sebelum membangun model apa pun, Anda harus memahami komponen-komponen utama yang menyusun sinyal deret waktu.
Komponen Deret Waktu
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
Membuat deret waktu sintetis dengan komponen yang diketahui
np.random.seed(42)
dates = pd.daterange(start="2020-01-01", periods=730, freq="D")
tren = np.linspace(50, 150, 730)
musiman = 20 np.sin(2 np.pi np.arange(730) / 365.25)
noise = np.random.normal(0, 5, 730)
y = tren + musiman + noise
df = pd.DataFrame({"ds": dates, "y": y})
fig, axes = plt.subplots(4, 1, figsize=(14, 10), sharex=True)
axes[0].plot(dates, y, color="steelblue")
axes[0].settitle("Deret Waktu Teramati")
axes[1].plot(dates, tren, color="darkorange")
axes[1].settitle("Komponen Tren")
axes[2].plot(dates, musiman, color="green")
axes[2].settitle("Komponen Musiman")
axes[3].plot(dates, noise, color="red", alpha=0.6)
axes[3].settitle("Noise / Residu")
plt.tightlayout()
plt.savefig("dekomposisideretwaktu.png", dpi=150)
plt.show()
Stasioneritas
Deret waktu yang stasioner memiliki properti statistik yang konstan sepanjang waktu. Banyak metode peramalan yang mengasumsikan atau mendapat manfaat dari stasioneritas. Gunakan uji Augmented Dickey-Fuller (ADF) untuk memeriksa:
from statsmodels.tsa.stattools import adfuller
hasil = adfuller(df["y"].values)
print(f"Statistik ADF: {hasil[0]:.4f}")
print(f"p-value: {hasil[1]:.4f}")
if hasil[1] < 0.05:
print("Deret bersifat stasioner (tolak hipotesis nol).")
else:
print("Deret bersifat non-stasioner. Pertimbangkan differencing.")
Pembagian Data Latih-Uji untuk Deret Waktu
Jangan pernah menggunakan pembagian acak untuk deret waktu. Selalu bagi secara kronologis:
tanggalbatas = "2021-07-01"
data
latih = df[df["ds"] < tanggalbatas].copy()
data
uji = df[df["ds"] >= tanggalbatas].copy()
print(f"Sampel pelatihan: {len(data
latih)}")
print(f"Sampel pengujian: {len(datauji)}")
Peramalan dengan Prophet
Facebook Prophet dirancang untuk peramalan bisnis. Model ini mendekomposisi deret waktu menjadi tren, musiman, dan hari libur menggunakan model aditif (atau multiplikatif): y(t) = g(t) + s(t) + h(t) + e(t).
Model Prophet Dasar
from prophet import Prophet
model = Prophet(
growth="linear",
changepointpriorscale=0.05, # fleksibilitas perubahan tren
seasonalitypriorscale=10.0, # kekuatan musiman
yearlyseasonality=True,
weeklyseasonality=False,
dailyseasonality=False,
)
model.fit(datalatih)
Membuat dataframe masa depan untuk prediksi
masadepan = model.makefuturedataframe(periods=len(datauji), freq="D")
ramalan = model.predict(masadepan)
Visualisasi ramalan
fig = model.plot(ramalan)
plt.title("Ramalan Prophet")
plt.savefig("ramalanprophet.png", dpi=150)
plt.show()
Menambahkan Efek Hari Libur
harilibur = pd.DataFrame({
"holiday": "haribesar",
"ds": pd.todatetime([
"2020-12-25", "2021-12-25", # Natal
"2020-01-01", "2021-01-01", # Tahun Baru
"2020-08-17", "2021-08-17", # Hari Kemerdekaan RI
]),
"lowerwindow": -1,
"upperwindow": 1,
})
modellibur = Prophet(holidays=harilibur)
modellibur.fit(datalatih)
ramalanlibur = modellibur.predict(masadepan)
fig = modellibur.plotcomponents(ramalanlibur)
plt.savefig("komponenprophet.png", dpi=150)
plt.show()
Menambahkan Musiman Kustom dan Regresor
# Menambahkan musiman bulanan
modelkustom = Prophet()
modelkustom.addseasonality(name="bulanan", period=30.5, fourierorder=5)
Menambahkan regresor eksternal (misal: suhu)
datalatih["suhu"] = np.random.normal(25, 5, len(datalatih))
datauji["suhu"] = np.random.normal(25, 5, len(datauji))
modelkustom.addregressor("suhu")
modelkustom.fit(datalatih)
masadepankustom = pd.concat([datalatih, datauji])[["ds", "suhu"]]
ramalankustom = modelkustom.predict(masadepankustom)
Validasi Silang dengan Prophet
from prophet.diagnostics import crossvalidation, performancemetrics
df
cv = crossvalidation(
model,
initial="365 days",
period="90 days",
horizon="90 days",
)
df
metrik = performancemetrics(dfcv)
print(dfmetrik[["horizon", "mae", "rmse", "mape"]].tail(10))
NeuralProphet: Peramalan Berbasis Jaringan Saraf
NeuralProphet memperluas Prophet dengan komponen jaringan saraf autoregresif, regresor tertunda (lagged regressors), dan backend PyTorch. Framework ini menjembatani kesenjangan antara dekomposisi klasik dan deep learning.
Model NeuralProphet Dasar
from neuralprophet import NeuralProphet
modelnp = NeuralProphet(
growth="linear",
nchangepoints=10,
yearlyseasonality=True,
weeklyseasonality=False,
dailyseasonality=False,
nforecasts=1,
nlags=0, # belum ada autoregresi
learningrate=0.1,
epochs=100,
batchsize=64,
)
metrik = modelnp.fit(datalatih, freq="D")
ramalannp = modelnp.predict(datauji)
modelnp.plot(ramalannp)
plt.title("Ramalan NeuralProphet")
plt.savefig("ramalanneuralprophet.png", dpi=150)
plt.show()
NeuralProphet Autoregresif (AR-Net)
Komponen autoregresif memungkinkan NeuralProphet menggunakan nilai-nilai masa lalu sebagai fitur masukan, mirip dengan model AR namun didukung oleh jaringan saraf:
modelnpar = NeuralProphet(
growth="linear",
nchangepoints=10,
yearlyseasonality=True,
nforecasts=30, # prediksi 30 langkah ke depan
nlags=60, # gunakan 60 nilai masa lalu sebagai masukan
learningrate=0.01,
epochs=200,
batchsize=64,
arlayers=[32, 16], # lapisan tersembunyi untuk AR-Net
)
metrikar = modelnpar.fit(datalatih, freq="D", validationdf=datauji)
ramalanar = modelnpar.predict(datauji)
modelnpar.plot(ramalanar)
plt.title("Ramalan AR NeuralProphet (30-langkah)")
plt.savefig("ramalanneuralprophetar.png", dpi=150)
plt.show()
Regresor Tertunda (Lagged Regressors)
datalatih["suhu"] = np.random.normal(25, 5, len(datalatih))
datauji["suhu"] = np.random.normal(25, 5, len(datauji))
modelnpreg = NeuralProphet(
nforecasts=7,
nlags=30,
yearlyseasonality=True,
epochs=150,
)
modelnpreg.addlaggedregressor("suhu", nlags=14)
metrikreg = modelnpreg.fit(datalatih, freq="D", validationdf=datauji)
ramalanreg = modelnpreg.predict(datauji)
LSTM dengan PyTorch
Jaringan Long Short-Term Memory unggul dalam mempelajari dependensi jangka panjang pada data sekuensial. LSTM lebih fleksibel dibandingkan model bergaya Prophet, namun memerlukan rekayasa yang lebih cermat.
Persiapan Data untuk LSTM
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler(featurerange=(0, 1))
nilaiterskalakan = scaler.fittransform(df["y"].values.reshape(-1, 1))
def buatsekuens(data, panjangsekuens):
xs, ys = [], []
for i in range(len(data) - panjangsekuens):
xs.append(data[i : i + panjangsekuens])
ys.append(data[i + panjangsekuens])
return np.array(xs), np.array(ys)
PANJANGSEKUENS = 60
X, y = buatsekuens(nilaiterskalakan, PANJANGSEKUENS)
idxbatas = int(len(X) 0.8)
Xlatih, Xuji = X[:idxbatas], X[idxbatas:]
ylatih, yuji = y[:idxbatas], y[idxbatas:]
Xlatiht = torch.FloatTensor(Xlatih)
ylatiht = torch.FloatTensor(ylatih)
Xujit = torch.FloatTensor(Xuji)
yujit = torch.FloatTensor(yuji)
class DatasetDeretWaktu(Dataset):
def init(self, X, y):
self.X = X
self.y = y
def len(self):
return len(self.X)
def getitem(self, idx):
return self.X[idx], self.y[idx]
datasetlatih = DatasetDeretWaktu(Xlatiht, ylatiht)
loaderlatih = DataLoader(datasetlatih, batchsize=32, shuffle=True)
Arsitektur Model LSTM
class PeramalLSTM(nn.Module):
def init(self, ukuranmasukan=1, ukurantersembunyi=64, jumlahlapisan=2, dropout=0.2):
super(PeramalLSTM, self).init()
self.ukurantersembunyi = ukurantersembunyi
self.jumlahlapisan = jumlahlapisan
self.lstm = nn.LSTM(
inputsize=ukuranmasukan,
hiddensize=ukurantersembunyi,
numlayers=jumlahlapisan,
batchfirst=True,
dropout=dropout,
)
self.fc = nn.Sequential(
nn.Linear(ukurantersembunyi, 32),
nn.ReLU(),
nn.Linear(32, 1),
)
def forward(self, x):
# x bentuk: (batch, panjangsekuens, ukuranmasukan)
keluaranlstm, (hn, cn) = self.lstm(x)
# Gunakan hidden state terakhir
tersembunyiterakhir = keluaranlstm[:, -1, :]
output = self.fc(tersembunyiterakhir)
return output
device = torch.device("cuda" if torch.cuda.isavailable() else "cpu")
model = PeramalLSTM(ukuranmasukan=1, ukurantersembunyi=64, jumlahlapisan=2, dropout=0.2).to(device)
print(model)
Melatih LSTM
kriteria = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
scheduler = torch.optim.lrscheduler.ReduceLROnPlateau(optimizer, patience=5, factor=0.5)
jumlahepoch = 100
lossvalterbaik = float("inf")
losslatihlist, lossvallist = [], []
for epoch in range(jumlahepoch):
model.train()
lossepoch = 0.0
for batchX, batchy in loaderlatih:
batchX, batchy = batchX.to(device), batchy.to(device)
optimizer.zerograd()
prediksi = model(batchX)
loss = kriteria(prediksi, batchy)
loss.backward()
torch.nn.utils.clipgradnorm(model.parameters(), maxnorm=1.0)
optimizer.step()
lossepoch += loss.item()
ratalosslatih = lossepoch / len(loaderlatih)
losslatihlist.append(ratalosslatih)
# Validasi
model.eval()
with torch.nograd():
predval = model(Xujit.to(device))
lossval = kriteria(predval, yujit.to(device)).item()
lossvallist.append(lossval)
scheduler.step(lossval)
if lossval < lossvalterbaik:
lossvalterbaik = lossval
torch.save(model.statedict(), "modellstmterbaik.pt")
if (epoch + 1) % 10 == 0:
print(f"Epoch [{epoch+1}/{jumlahepoch}] Loss Latih: {ratalosslatih:.6f} | Loss Val: {lossval:.6f}")
Plot kurva pembelajaran
plt.figure(figsize=(10, 5))
plt.plot(losslatihlist, label="Loss Pelatihan")
plt.plot(lossvallist, label="Loss Validasi")
plt.xlabel("Epoch")
plt.ylabel("Loss MSE")
plt.legend()
plt.title("Kurva Pelatihan LSTM")
plt.savefig("kurvapelatihanlstm.png", dpi=150)
plt.show()
Menghasilkan Prediksi
model.loadstatedict(torch.load("modellstmterbaik.pt"))
model.eval()
with torch.nograd():
prediksiuji = model(Xujit.to(device)).cpu().numpy()
Transformasi balik ke skala asli
prediksiujiasli = scaler.inversetransform(prediksiuji)
yujiasli = scaler.inversetransform(yuji)
plt.figure(figsize=(14, 5))
plt.plot(yujiasli, label="Aktual", color="steelblue")
plt.plot(prediksiujiasli, label="Prediksi LSTM", color="darkorange")
plt.title("Ramalan LSTM vs Aktual")
plt.legend()
plt.savefig("ramalanlstm.png", dpi=150)
plt.show()
Deteksi Anomali pada Deret Waktu
Model peramalan dapat berfungsi ganda sebagai detektor anomali. Titik data yang menyimpang secara signifikan dari interval kepercayaan ramalan ditandai sebagai anomali.
Deteksi Anomali Berbasis Prophet
ramalanpenuh = model.predict(model.makefuturedataframe(periods=0))
dfgabung = df.merge(ramalanpenuh[["ds", "yhat", "yhatlower", "yhatupper"]], on="ds")
dfgabung["anomali"] = (dfgabung["y"] < dfgabung["yhatlower"]) | (dfgabung["y"] > dfgabung["yhatupper"])
anomali = dfgabung[dfgabung["anomali"]]
print(f"Terdeteksi {len(anomali)} anomali dari {len(dfgabung)} titik data.")
plt.figure(figsize=(14, 5))
plt.plot(dfgabung["ds"], dfgabung["y"], label="Aktual", alpha=0.7)
plt.fillbetween(dfgabung["ds"], dfgabung["yhatlower"], dfgabung["yhatupper"], alpha=0.2, color="blue")
plt.scatter(anomali["ds"], anomali["y"], color="red", s=50, label="Anomali", zorder=5)
plt.legend()
plt.title("Deteksi Anomali dengan Prophet")
plt.savefig("deteksianomali.png", dpi=150)
plt.show()
Metode Z-Score Residu
residu = dfgabung["y"] - dfgabung["yhat"]
z
scores = (residu - residu.mean()) / residu.std()
ambangbatas = 2.5
dfgabung["anomalizscore"] = np.abs(zscores) > ambangbatas
print(f"Anomali Z-score: {dfgabung['anomalizscore'].sum()}")
Metrik Evaluasi
Selalu evaluasi model peramalan Anda dengan beberapa metrik untuk mendapatkan gambaran lengkap.
from sklearn.metrics import meanabsoluteerror, meansquarederror
def evaluasi
ramalan(aktual, prediksi, namamodel="Model"):
mae = mean
absoluteerror(aktual, prediksi)
rmse = np.sqrt(mean
squarederror(aktual, prediksi))
# Hindari pembagian nol pada MAPE
mask = aktual != 0
mape = np.mean(np.abs((aktual[mask] - prediksi[mask]) / aktual[mask])) 100
print(f"--- {nama
model} ---")
print(f" MAE: {mae:.4f}")
print(f" RMSE: {rmse:.4f}")
print(f" MAPE: {mape:.2f}%")
return {"mae": mae, "rmse": rmse, "mape": mape}
Contoh penggunaan
nilaiaktual = datauji["y"].values
predprophet = ramalan[ramalan["ds"].isin(datauji["ds"])]["yhat"].values
metrikprophet = evaluasiramalan(nilaiaktual, predprophet, "Prophet")
| Metrik | Rumus | Interpretasi |
|--------|-------|-------------|
| MAE | mean(\|aktual - prediksi\|) | Rata-rata kesalahan absolut dalam satuan asli |
| RMSE | sqrt(mean((aktual - prediksi)^2)) | Memberikan penalti lebih besar pada kesalahan besar |
| MAPE | mean(\|aktual - prediksi\| / \|aktual\|) 100 | Kesalahan persentase, tidak bergantung skala |
Peramalan Multi-Langkah
Multi-Langkah Langsung dengan LSTM
class LSTMMultiLangkah(nn.Module):
def init(self, ukuranmasukan=1, ukurantersembunyi=64, jumlahlapisan=2, horisonramalan=7):
super(LSTMMultiLangkah, self).init()
self.lstm = nn.LSTM(ukuranmasukan, ukurantersembunyi, jumlahlapisan, batchfirst=True, dropout=0.2)
self.fc = nn.Linear(ukurantersembunyi, horisonramalan)
def forward(self, x):
keluaranlstm, = self.lstm(x)
tersembunyiterakhir = keluaranlstm[:, -1, :]
return self.fc(tersembunyiterakhir)
Model ini menghasilkan 7 nilai sekaligus
modelmulti = LSTMMultiLangkah(horisonramalan=7).to(device)
Multi-Langkah Rekursif
def ramalanrekursif(model, sekuensawal, langkah, scaler, device):
model.eval()
prediksi
list = []
sekuenssaatini = sekuensawal.clone()
for in range(langkah):
with torch.nograd():
pred = model(sekuenssaatini.unsqueeze(0).to(device))
prediksilist.append(pred.cpu().item())
# Geser sekuens dan tambahkan prediksi baru
nilaibaru = torch.FloatTensor([[pred.cpu().item()]])
sekuenssaatini = torch.cat([sekuenssaatini[1:], nilaibaru], dim=0)
prediksiarray = np.array(prediksilist).reshape(-1, 1)
return scaler.inversetransform(prediksiarray)
Ramalkan 30 langkah ke depan
sekuensawal = Xujit[0]
ramalanpanjang = ramalanrekursif(model, sekuensawal, langkah=30, scaler=scaler, device=device)
Perbandingan Model
hasil = {}
Prophet
predprophet = ramalan[ramalan["ds"].isin(datauji["ds"])]["yhat"].values
hasil["Prophet"] = evaluasiramalan(datauji["y"].values, predprophet, "Prophet")
NeuralProphet
prednp = ramalannp["yhat1"].values[:len(datauji)]
hasil["NeuralProphet"] = evaluasiramalan(datauji["y"].values, prednp, "NeuralProphet")
LSTM
hasil["LSTM"] = evaluasiramalan(yujiasli.flatten(), prediksiujiasli.flatten(), "LSTM")
Tabel ringkasan
dfperbandingan = pd.DataFrame(hasil).T
dfperbandingan.columns = ["MAE", "RMSE", "MAPE"]
print("\n=== Perbandingan Model ===")
print(dfperbandingan.tostring())
Visualisasi
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
for i, metrik in enumerate(["MAE", "RMSE", "MAPE"]):
axes[i].bar(dfperbandingan.index, dfperbandingan[metrik], color=["#4C72B0", "#55A868", "#C44E52"])
axes[i].settitle(metrik)
axes[i].setylabel(metrik)
plt.tightlayout()
plt.savefig("perbandinganmodel.png", dpi=150)
plt.show()
Praktik Terbaik
Kesimpulan
Dalam tutorial ini, kita telah menjelajahi tiga pendekatan komplementer untuk peramalan deret waktu:
- Prophet unggul dalam peramalan berbasis dekomposisi yang dapat diinterpretasi dengan penyetelan minimal. Sangat ideal untuk deret waktu bisnis dengan pola tren dan musiman yang jelas.
- NeuralProphet memperluas Prophet dengan komponen neural autoregresif, menjadikannya cocok untuk deret di mana nilai-nilai masa lalu yang baru merupakan prediktor kuat.
- LSTM memberikan fleksibilitas maksimum untuk pola nonlinier yang kompleks, namun memerlukan persiapan data dan penyetelan hyperparameter yang lebih banyak.
Pendekatan terbaik bergantung pada karakteristik data Anda, persyaratan akurasi, dan batasan operasional. Untuk sistem produksi, pertimbangkan untuk memulai dengan Prophet sebagai baseline yang kuat, kemudian secara bertahap menambahkan kompleksitas dengan NeuralProphet atau LSTM jika diperlukan. Selalu validasi dengan validasi silang berbasis waktu yang tepat dan beberapa metrik evaluasi.