Atıkların Azaltılması için Talep Tahmini: FoodTech'te ML ve Zaman Serisi
Dünya her yıl yaklaşık olarak üretiyor 1,05 milyar ton gıda israf edildigöre UNEP Gıda İsrafı Endeksi Raporu 2024. Bunların %60'ı ailelerden, %28'i restoranlardan geliyor ve %12'si büyük ölçekli dağıtımdan. Ekonomik terimlere çevrildiğinde: ötesinde 1 trilyon dolar her yıl yandıküresel gaz emisyonlarının %10'unu oluşturan çevresel etkiye sahip sera, tüm dünya havacılığının neredeyse beş katı.
Ancak çelişkili bir şekilde, her gün bir milyar porsiyon gıda israf edilirken, 783 Milyonlarca insan açlık çekiyor. Sorunun kökü yalnızca kültürel ya da lojistik değildir: ve temelde bir sorun talep tahmini. Büyük ölçekli perakende ticaret siparişleri çok fazla Stok yokluğunu riske atmayın. Tedarikçiler güvende olmak için aşırı üretim yapıyor. Sonuç olarak her bağlantı Besin zincirinin her yerinde atık haline gelen tamponlar birikir.
İtalya'da, Gadda Kanunu (n. 166/2016) bağış için vergi teşvikleri getirildi gıda fazlalığının azaltılması ve basitleştirilmiş geri kazanım prosedürleri. Avrupa düzeyinde strateji Çiftlikten Çatala bağlayıcı hedeflerle gıda israfını 2030 yılına kadar yarıya indirmeyi hedefliyor perakendeciler ve gıda endüstrisi için. Düzenleme aciliyet yaratıyor ancak Makine Öğrenimi bunu sağlıyor Bu hedeflere ulaşmak için somut araçlar.
ML ile talep tahmini teori değildir: LSTM ve Temporal Fusion modellerini uygulayan perakendeciler Transformers, MAPE'de geleneksel yöntemlerin tipik %28'inden %5-15'e kadar azalma olduğunu bildiriyor; 12 ay içinde %25-40 oranında atık azaltımı ve ölçülebilir yatırım getirisi. Bu yazıda inşa edeceğiz Ham verilerden üretim modeline kadar her mimari seçimi analiz eden eksiksiz bir süreç çalışan Python koduyla.
Bu Makalede Neler Öğreneceksiniz?
- 2025'te gıda israfına yönelik ekonomik ve düzenleyici çerçeve
- Klasik istatistiksel modeller: Python kodlu ARIMA, SARIMA, Holt-Winters ve Prophet
- Zaman serileri için Derin Öğrenme: LSTM, GRU, Temporal Fusion Transformer ve N-BEATS
- Gelişmiş özellik mühendisliği: dışsal değişkenler, döngüsel kodlama, gecikme özellikleri
- İleriye doğru doğrulamayla uçtan uca makine öğrenimi hattını tamamlayın
- Gerçek veri kümesi üzerinde karşılaştırmalı değerlendirme: MAPE, RMSE, MAE, eğitim süresi
- Envanter yönetimi ve dinamik fiyatlandırma ile tahmin entegrasyonu
- Son kullanma tarihi yaklaşmış ürünler için indirim optimizasyon algoritması
- İtalyan büyük ölçekli perakende ticaret vaka çalışması: 200'den fazla satış noktası, %35 atık azaltımı
- İş ölçümleri: Atık Azaltma Oranı, Tahmin Doğruluğu, Fiyat Düşürme Verimliliği
FoodTech Serisi: Gıda Endüstrisinde Yapay Zeka ve Teknoloji Üzerine 10 Makale
| # | Öğe | Odak |
|---|---|---|
| 1 | FoodTech'e Giriş | Ekosistem genel bakışı ve temel teknolojiler |
| 2 | Gıda Zincirinde Nesnelerin İnterneti ve Sensörler | Sahadan buluta veri hattı |
| 3 | Kalite ve Denetim için Bilgisayarlı Görme | Kusur sınıflandırması ve otomatik sınıflandırma |
| 4 | Blockchain ve İzlenebilirlik | Çiftlikten sofraya gıda güvenliği |
| 5 | Dikey Tarım ve AgriTech | ML ile kontrollü ekim |
| 6 | Tedarik Zinciri Optimizasyonu | Lojistik, soğuk zincir ve şeffaflık |
| 7 | Çiftlik Yönetim Paneli | Tarım şirketlerinin gerçek zamanlı izlenmesi |
| 8 | Buradasınız - Talep Tahmini ve İsrafın Azaltılması | ML zaman serisi, LSTM, TFT, dinamik fiyatlandırma |
| 9 | Uydu API'si ve Hassas Tarım | NDVI, uzaktan algılama, mahsul izleme |
| 10 | Mahsul İzleme için ML Edge | Saha cihazlarında yerleşik çıkarım |
Gıda İsrafı Sorunu: Veriler ve Düzenlemeler 2025
ML ile talep tahmininin neden FoodTech'te stratejik bir öncelik haline geldiğini anlamak için, gerçek sayılardan başlamalısınız. UNEP Gıda İsrafı Endeksi Raporu 2024 bunu ortaya koyuyor 2022'de (tam verilerle geçen yıl) 1,05 milyar ton gıda israf edildi, kişi başına yıllık 132 kg'a ve tüketicilere sunulan tüm gıdanın neredeyse beşte birine eşittir.
Gıda İsrafının Küresel Etkisi (UNEP 2024)
| Gösterge | Değer | Kaynak |
|---|---|---|
| Yıllık gıda israfı | 1,05 milyar ton | UNEP Gıda İsrafı Endeksi 2024 |
| Ekonomik değer kaybı | ~1 trilyon dolar/yıl | FAO/Dünya Bankası tahmini |
| % sera gazı emisyonları | %8-10 küresel | UNEP 2024 |
| Kişi başına atık | 132 kg/kişi/yıl | UNEP 2024 |
| Büyük ölçekli perakende ticarette perakendenin payı | Toplamın %12'si boşa gitti | UNEP 2024 |
| Aile paylaşımı | Toplamın %60'ı | UNEP 2024 |
| Yemek servisi kotası | Toplamın %28'i | UNEP 2024 |
| Perakende öncesi kayıplar (tedarik zinciri) | Üretilen gıdanın %13'ü | FAO |
Düzenleyici Çerçeve: Gadda Yasasından Çiftlikten Sofraya
İtalya Avrupa'da öncü oldu Kanun 166/2016 (Gadda Kanunu), sahip gıda israfına cezalandırıcı değil ödüllendirici bir yaklaşım getirdi. Anahtar noktalar şunlardır:
- Vergi teşvikleri Gıda fazlasını bağışlayanlar için: yıkıma eşdeğer IRPEF/IRES hesaplaması için
- Bürokratik basitleştirme: bağışlara ilişkin belge masraflarının ortadan kaldırılması 15.000 Euro/yıl
- "Köpek çantası" tanıtımı restoranlarda ve yakınlardaki ürünlerin satışı şeffaf indirimlerle son tarih
- Gıda eğitimi yapısal önleyici tedbir olarak okullarda
Avrupa düzeyinde, Tarladan Çatala Stratejisi (Yeşil Anlaşmanın bir parçası) hedefleri belirliyor Bağlayıcı: Önlemlerle 2030 yılına kadar perakende ve tüketim düzeyinde atıklarda %50 azalma Büyük ölçekli perakende ticaret için zorunlu olan ve 2025-2026'dan itibaren kademeli olarak yürürlüğe girecek. benim için 250'den fazla çalışanı olan perakendecinin gıda israfını yıllık raporlaması zorunlu hale geliyor.
Düzenlemelerin Kurumsal Stratejiler Üzerindeki Etkisi
Teşviklerin (Gadda Yasası), raporlama yükümlülüklerinin (Çiftlikten Sofraya) ve tüketiciler makine öğrenimi ile talep tahminine yatırım yapmak için net bir iş senaryosu oluşturuyor. Evet değil her şey sürdürülebilirlikle ilgili: 200 mağazası ve %2-3 marjı olan bir perakendeci için, %35 atık, işletme marjının 50-80 baz puanının geri kazanılmasına eşdeğerdir.
Gıda Talebi Tahmini Neden Zordur?
Gıda endüstrisinde talep tahmini, onu en önemli sektörlerden biri haline getiren benzersiz zorluklar sunmaktadır. iş dünyasına uygulanan makine öğrenimindeki daha karmaşık problemler. Yapısal özellikler Onu diğer perakende sektörlerinden ayıran beş özelliği var.
1. Bozulabilirlik ve Dar Satış Penceresi
Bu hafta satılmayan bir giysi gelecek hafta satılabilir. Bir salata taze hayır Taze gıda ürünleri var raf ömrü 1 ila 14 gün arası, hangi aşırı tahmin hatasının doğrudan kurtarılamaz fiziksel israfa dönüştüğü anlamına gelir. Bu hata, maliyet asimetrisine neden olur (aşırı tahminin maliyeti genellikle tahminin maliyetini aşar). tahminin altında) özellikle yukarı yönlü hatayı en aza indiren modeller gerektirir.
2. Çok Düzeyli Mevsimsellik
Yemek zaman serileri birden fazla frekansta eş zamanlı mevsimsellik sunar:
- Günlük mevsimsellik: Cuma akşamı satışları Salı sabahından farklı
- Haftalık mevsimsellik: haftanın her günü için farklı satın alma modelleri
- Aylık mevsimsellik: Maaş döngülerine ilişkin ay başı/sonu etkileri
- Yıllık mevsimsellik: sezonluk ürünlerde yaz ve kış karşılaştırması
- Festival sezonu: Ani yükselişlerle Noel, Paskalya, Ağustos ortası
Geleneksel ARIMA modelleri yalnızca bir mevsimsel bileşeni ele alır. Modern modeller gibi Prophet ve TFT birden fazla sezonu yerel olarak yönetiyor.
3. Durağan Olmayan Dışsal Değişkenler
Gıda talebi, düzenli kalıpları takip etmeyen dış faktörlerden güçlü bir şekilde etkilenir: hava koşulları (bir hafta yağmur çorba satışlarını %40 artırır), promosyon kampanyaları (promosyon broşürü geçici olarak talebi üç katına çıkarabilir), yerel etkinlikler (futbol maçları, ticari fuarlar), rakip fiyatları ve sosyal medya trendleri. Bu dışsal değişkenleri uygun şekilde dahil edin etkili ve vasat bir model ile mükemmel bir model arasındaki fark.
4. Zayıf Verilere Sahip SKU'ların Uzun Kuyruğu
Tipik bir büyük ölçekli perakende ticaret 15.000-30.000 aktif SKU'yu yönetir. SKU'ların %20'si satışların %80'ini oluşturur, ancak geri kalan %80'i seyrek, aralıklı ve birçok sıfır içeren zaman serisine sahiptir. Bunlar için ürünlerin standart modelleri başarısız oluyor ve Croston yöntemi gibi özel yaklaşımlara ihtiyaç duyuluyor, sıfır şişirilmiş modeller veya benzer SKU'lardan öğrenim aktarımı.
5. ML'siz Tipik Hatalar
Il Tipik MAPE (Ortalama Mutlak Yüzde Hatası). geleneksel tahmin sistemlerinde hareketli ortalamalara, manuel kurallara veya makine öğrenimi olmayan ERP sistemlerine dayalı olarak %20 ve %40 taze ürünler için. ML modellerinin tanıtılması bu hatayı azaltır %5-15, LSTM'nin son kıyaslamalarda geleneksel yöntemlerin %28,76'sına karşılık %16,43 MAPE göstermesiyle, %43'lük bir azalma.
Klasik İstatistiksel Modeller: ARIMA, SARIMA, Holt-Winters ve Prophet
Derin öğrenmeye geçmeden önce klasik istatistiksel modelleri anlamak önemlidir. Hayır çünkü bunlar eskidir, ancak çoğu zaman aşılması gereken temel çizgiyi temsil ettikleri için yorumlanabilirler, hesaplama açısından hafif ve bazı bağlamlarda (az veri, basit ürünler) rekabet edebilir daha karmaşık yaklaşımlar.
ARIMA ve SARIMA: Tahminin Temelleri
ARIMA (OtoRegresif Entegre Hareketli Ortalama), serilere göre en çok kullanılan istatistiksel modeldir tek değişkenli zaman ölçekleri. ARIMA(p,d,q) modeli üç bileşeni birleştirir: Otomatik Regresyon (p gecikmeleri) geçmiş değer), Entegrasyon (seriyi durağan hale getirecek farklar) ve Hareketli Ortalama (artık hataların q gecikmesi). SARIMA mevsimsel bir bileşen (P,D,Q) ekler.
# SARIMA per forecasting vendite settimanali - prodotto fresco
import pandas as pd
import numpy as np
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.tsa.stattools import adfuller
import matplotlib.pyplot as plt
from sklearn.metrics import mean_absolute_percentage_error
# Caricamento dati di esempio
# Formato: date index, colonna 'sales' con unita vendute
df = pd.read_csv('vendite_insalata.csv', index_col='date', parse_dates=True)
df = df.asfreq('D') # Frequenza giornaliera
df['sales'] = df['sales'].fillna(0)
# Test di stazionarieta (ADF Test)
result = adfuller(df['sales'].dropna())
print(f'ADF Statistic: {result[0]:.4f}')
print(f'p-value: {result[1]:.4f}')
print(f'La serie e {"stazionaria" if result[1] < 0.05 else "non stazionaria"}')
# Split train/test (80/20 con holdout degli ultimi 30 giorni)
train_size = len(df) - 30
train = df.iloc[:train_size]
test = df.iloc[train_size:]
# Modello SARIMA(1,1,1)(1,1,1)7 - stagionalita settimanale
# p=1, d=1, q=1: componenti ARIMA
# P=1, D=1, Q=1, s=7: componenti stagionali settimanali
model = SARIMAX(
train['sales'],
order=(1, 1, 1),
seasonal_order=(1, 1, 1, 7), # s=7 per stagionalita settimanale
enforce_stationarity=False,
enforce_invertibility=False
)
results = model.fit(disp=False)
print(results.summary())
# Previsione sullo stesso periodo del test set
forecast = results.forecast(steps=len(test))
forecast = np.maximum(forecast, 0) # Nessun valore negativo
# Metriche
mape = mean_absolute_percentage_error(test['sales'], forecast) * 100
rmse = np.sqrt(np.mean((test['sales'].values - forecast.values) ** 2))
mae = np.mean(np.abs(test['sales'].values - forecast.values))
print(f'\nMetriche SARIMA:')
print(f' MAPE: {mape:.2f}%')
print(f' RMSE: {rmse:.2f} unita')
print(f' MAE: {mae:.2f} unita')
# Visualizzazione
plt.figure(figsize=(12, 5))
plt.plot(train['sales'][-60:], label='Train (ultimi 60gg)')
plt.plot(test['sales'], label='Reale', color='green')
plt.plot(test.index, forecast, label='Previsione SARIMA', color='red', linestyle='--')
plt.title('SARIMA - Previsione Vendite Prodotto Fresco')
plt.legend()
plt.tight_layout()
plt.savefig('sarima_forecast.png', dpi=150)
plt.show()
Peygamber: Dışsal Değişkenlerle Yorumlanabilir Tahmin
Peygamber (Meta/Facebook, 2017) ve gıda tahmini için mükemmel bir seçim çünkü yerel olarak şunları yönetir: birden fazla mevsim (günlük, haftalık, yıllık), tatil özelleştirilebilir takvime sahip efektler, otomatik değişim noktalarına sahip doğrusal olmayan trendler ve değişkenler dışsal (ek regresörler). Yorumlanabilirliği (trendlere otomatik ayrıştırma + mevsimsellik + tatil) teknik bilgisi olmayan kullanıcılar tarafından bile takdir edilmesini sağlar.
# Prophet per forecasting con variabili esogene (meteo, promozioni)
from prophet import Prophet
from prophet.diagnostics import cross_validation, performance_metrics
import pandas as pd
import numpy as np
# Prophet richiede colonne 'ds' (datetime) e 'y' (target)
df_prophet = df.reset_index().rename(columns={'date': 'ds', 'sales': 'y'})
# Aggiunta variabili esogene (regressori)
# Esempio: temperatura media giornaliera e flag promozionale
df_prophet['temperature'] = meteo_df['temp_media'] # gradi Celsius
df_prophet['is_promotion'] = promo_df['active_promo'].astype(int)
# Calendario festivo italiano (personalizzato)
holidays_it = pd.DataFrame({
'holiday': [
'Natale', 'Capodanno', 'Pasqua', 'Ferragosto',
'Festa Repubblica', 'Ognissanti'
],
'ds': pd.to_datetime([
'2024-12-25', '2025-01-01', '2025-04-20', '2025-08-15',
'2025-06-02', '2025-11-01'
]),
'lower_window': [-3, -1, -3, -5, -1, -1], # Giorni prima
'upper_window': [3, 2, 2, 2, 1, 1] # Giorni dopo
})
# Configurazione modello
model = Prophet(
holidays=holidays_it,
yearly_seasonality=True,
weekly_seasonality=True,
daily_seasonality=False,
seasonality_mode='multiplicative', # Meglio per dati con forte trend
changepoint_prior_scale=0.05, # Flessibilità del trend
seasonality_prior_scale=10.0 # Forza delle componenti stagionali
)
# Aggiunta regressori
model.add_regressor('temperature', standardize=True)
model.add_regressor('is_promotion', standardize=False)
# Training
train_prophet = df_prophet.iloc[:train_size]
model.fit(train_prophet)
# Previsione (con valori futuri dei regressori)
future = model.make_future_dataframe(periods=30)
future['temperature'] = future_meteo_df['temp_media']
future['is_promotion'] = future_promo_df['active_promo'].astype(int)
forecast_prophet = model.predict(future)
# Cross-validation interna (walk-forward)
df_cv = cross_validation(
model,
initial='365 days', # Periodo di training iniziale
period='30 days', # Frequenza di re-fitting
horizon='14 days' # Orizzonte di previsione
)
metrics_cv = performance_metrics(df_cv)
print(f'Prophet MAPE (CV): {metrics_cv["mape"].mean()*100:.2f}%')
# Visualizzazione decomposizione
fig = model.plot_components(forecast_prophet)
fig.savefig('prophet_components.png', dpi=150)
print('Componenti Prophet salvate: trend + stagionalita + holiday effects')
Klasik İstatistiksel Modeller Ne Zaman Kullanılmalı?
| Modeli | Güçlü yönler | Sınırlamalar | İdeal Kullanım Durumu |
|---|---|---|---|
| ARIMA/SARIMA | Yorumlanabilir, hızlı, durağan seriler | Yalnızca tek bir mevsimsellik vardır, dışsal değişken yoktur | Kararlı ürünler, az veri |
| Holt-Winters | Trendleri + mevsimselliği ve basitliği yönetir | Aykırı değerleri ve sabit mevsimselliği ele almaz | Doğrusal eğilime ve istikrarlı mevsimselliğe sahip seriler |
| Peygamber | Çok mevsimsellik, tatil, regresörler | Çok düzensiz seriler için ideal değildir | Güçlü şenlik/promosyon etkisine sahip ürünler |
| LSTM/GRU | Karmaşık, çok değişkenli, uzun vadeli modeller | Bol miktarda veri gerekiyor, kara kutu | Yüksek hacimli SKU'lar, birçok dışsal değişken |
| TFT | Yorumlanabilir + DL, çoklu ufuk, dikkat | Hesaplama açısından ağır, GPU gerekli | Birçok SKU genelinde merkezi tahmin |
Zaman Serileri için Derin Öğrenme: LSTM, GRU, TFT ve N-BEATS
Derin öğrenme modelleri, 2018-2020'den itibaren talep tahmininde devrim yarattı. Üstünlükleri her şeyden önce aşağıdakilerin varlığında ortaya çıkar: birbiriyle ilişkili birçok dışsal değişken, model karmaşık, doğrusal olmayan, uzun vadeli zamana bağımlılıklar ve büyük hacimli çoklu SKU verileri benzer ürünler arasında transfer öğreniminden yararlanmanıza olanak tanır.
LSTM ve GRU: Dizilerdeki Seçici Bellek
Le Uzun Kısa Süreli Bellek (LSTM) ve Kapılı Tekrarlayan Üniteler (GRU) bunlar, zamansal dizilerdeki uzun vadeli bağımlılıkları yakalamak için tasarlanmış tekrarlayan ağlardır. LSTM hangi bilginin tutulacağına veya atılacağına karar vermek için üç kapı (giriş, unutma, çıkış) kullanır hücresel hafızada. GRU, benzer performans elde eden iki kapıyla (sıfırlama, güncelleme) basitleşir Daha az parametreyle.
# LSTM multi-variate per demand forecasting alimentare
import torch
import torch.nn as nn
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_absolute_percentage_error
# ---- Preparazione dati ----
class FoodDemandDataset(torch.utils.data.Dataset):
def __init__(self, data, seq_len=14, pred_len=7):
self.seq_len = seq_len
self.pred_len = pred_len
self.data = torch.FloatTensor(data)
def __len__(self):
return len(self.data) - self.seq_len - self.pred_len + 1
def __getitem__(self, idx):
x = self.data[idx: idx + self.seq_len]
y = self.data[idx + self.seq_len: idx + self.seq_len + self.pred_len, 0]
return x, y # x: (seq_len, features), y: (pred_len,)
# ---- Architettura LSTM ----
class LSTMForecaster(nn.Module):
def __init__(self, input_size, hidden_size=128, num_layers=2,
pred_len=7, dropout=0.2):
super(LSTMForecaster, self).__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.pred_len = pred_len
self.lstm = nn.LSTM(
input_size=input_size,
hidden_size=hidden_size,
num_layers=num_layers,
batch_first=True,
dropout=dropout
)
self.dropout = nn.Dropout(dropout)
self.fc = nn.Linear(hidden_size, pred_len)
def forward(self, x):
# x shape: (batch, seq_len, input_size)
h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size)
out, _ = self.lstm(x, (h0, c0))
out = self.dropout(out[:, -1, :]) # Ultimo time step
out = self.fc(out) # (batch, pred_len)
return out
# ---- Feature preparation ----
def prepare_features(df):
"""Crea feature matrix multi-variate per LSTM"""
features = pd.DataFrame()
features['sales'] = df['sales']
# Lag features: vendite passate come input esplicito
for lag in [1, 2, 3, 7, 14]:
features[f'sales_lag_{lag}'] = df['sales'].shift(lag)
# Rolling statistics
features['sales_rolling_mean_7d'] = df['sales'].rolling(7).mean()
features['sales_rolling_std_7d'] = df['sales'].rolling(7).std()
features['sales_rolling_mean_14d'] = df['sales'].rolling(14).mean()
# Feature temporali cicliche (encoding sinusoidale)
features['day_sin'] = np.sin(2 * np.pi * df.index.dayofweek / 7)
features['day_cos'] = np.cos(2 * np.pi * df.index.dayofweek / 7)
features['month_sin'] = np.sin(2 * np.pi * df.index.month / 12)
features['month_cos'] = np.cos(2 * np.pi * df.index.month / 12)
# Variabili esogene (meteo, promozioni)
features['temperature'] = df['temperature']
features['is_weekend'] = (df.index.dayofweek >= 5).astype(int)
features['is_holiday'] = df['is_holiday'].astype(int)
features['is_promotion'] = df['is_promotion'].astype(int)
features = features.dropna()
return features
# ---- Training loop ----
def train_lstm_model(train_data, val_data, config):
model = LSTMForecaster(
input_size=config['input_size'],
hidden_size=config['hidden_size'],
num_layers=config['num_layers'],
pred_len=config['pred_len']
)
optimizer = torch.optim.Adam(model.parameters(), lr=config['lr'])
scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
optimizer, patience=5, factor=0.5
)
criterion = nn.HuberLoss(delta=1.0) # Robusto agli outlier
train_loader = torch.utils.data.DataLoader(
FoodDemandDataset(train_data, config['seq_len'], config['pred_len']),
batch_size=config['batch_size'],
shuffle=True
)
best_val_loss = float('inf')
patience_counter = 0
for epoch in range(config['epochs']):
model.train()
train_loss = 0
for x_batch, y_batch in train_loader:
optimizer.zero_grad()
y_pred = model(x_batch)
loss = criterion(y_pred, y_batch)
loss.backward()
torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
optimizer.step()
train_loss += loss.item()
# Validazione
model.eval()
with torch.no_grad():
val_dataset = FoodDemandDataset(val_data, config['seq_len'], config['pred_len'])
val_loader = torch.utils.data.DataLoader(val_dataset, batch_size=32)
val_loss = sum(criterion(model(x), y).item() for x, y in val_loader)
scheduler.step(val_loss)
if val_loss < best_val_loss:
best_val_loss = val_loss
torch.save(model.state_dict(), 'best_lstm_model.pt')
patience_counter = 0
else:
patience_counter += 1
if patience_counter >= config['early_stopping_patience']:
print(f'Early stopping all\'epoca {epoch}')
break
if epoch % 10 == 0:
print(f'Epoch {epoch}: Train Loss={train_loss:.4f}, Val Loss={val_loss:.4f}')
# Carica modello migliore
model.load_state_dict(torch.load('best_lstm_model.pt'))
return model
# Configurazione
config = {
'input_size': 15, # Numero di feature (sales + lag + temporal + exog)
'hidden_size': 128,
'num_layers': 2,
'pred_len': 7, # Previsione a 7 giorni
'seq_len': 14, # Lookback di 14 giorni
'batch_size': 32,
'lr': 0.001,
'epochs': 100,
'early_stopping_patience': 15
}
Zamansal Füzyon Transformatörü: Yorumlanabilirlik + Güç
Il Zamansal Füzyon Transformatörü (TFT) Google DeepMind (2021) tarafından ve şu anda kurumsal talep tahmini için en son teknoloji olarak kabul edildi. Mimarisi birleştiriyor Her iki statik değişkeni de ele alan artık kapılara (GRN'ler) sahip çok kafalı dikkat mekanizmaları (ürün türü, ürün kategorisi) ve önceden bilinen zamansal değişkenler (takvim, planlanan promosyonlar) ve gözlemlenir (geçmişteki satışlar, hava durumu).
M5 veri kümesi (10 Walmart mağazasından 30.490 zaman serisi), TFT ve modeller üzerinde 2024 kıyaslamalarında Transformatör tabanlı MASE'ler iyileştirmeler gösterdi %26-29 ve azaltma WQL'in %34 mevsimsel saf yöntemle karşılaştırıldığında. Piknik, gıda perakendecisi Dutch online, talep tahmini, yayıncılık için ana model olarak TFT'yi benimsedi Üretimde ayrıntılı sonuçlar.
# Temporal Fusion Transformer con PyTorch Forecasting
# pip install pytorch-forecasting pytorch-lightning
from pytorch_forecasting import TemporalFusionTransformer, TimeSeriesDataSet
from pytorch_forecasting.data import GroupNormalizer
from pytorch_forecasting.metrics import QuantileLoss
import pytorch_lightning as pl
import pandas as pd
import torch
# ---- Preparazione dati nel formato TFT ----
def prepare_tft_dataset(df):
"""
df deve avere: date, store_id, product_id, sales,
temperature, is_holiday, is_promotion, price, category
"""
df = df.copy()
df['time_idx'] = (df['date'] - df['date'].min()).dt.days
df['month'] = df['date'].dt.month.astype(str)
df['day_of_week'] = df['date'].dt.dayofweek.astype(str)
max_prediction_length = 7 # Previsione 7 giorni
max_encoder_length = 28 # Lookback 28 giorni
training_cutoff = df['time_idx'].max() - max_prediction_length
training_dataset = TimeSeriesDataSet(
df[df.time_idx <= training_cutoff],
time_idx='time_idx',
target='sales',
group_ids=['store_id', 'product_id'],
# Variabili statiche (non cambiano nel tempo per ogni gruppo)
static_categoricals=['store_id', 'product_id', 'category'],
# Variabili temporali note in anticipo
time_varying_known_categoricals=['month', 'day_of_week', 'is_holiday'],
time_varying_known_reals=['time_idx', 'is_promotion', 'price'],
# Variabili osservate (disponibili solo per il passato)
time_varying_unknown_reals=['sales', 'temperature'],
min_encoder_length=max_encoder_length // 2,
max_encoder_length=max_encoder_length,
min_prediction_length=1,
max_prediction_length=max_prediction_length,
target_normalizer=GroupNormalizer(
groups=['store_id', 'product_id'],
transformation='softplus' # Mantiene valori positivi
),
add_relative_time_idx=True,
add_target_scales=True,
add_encoder_length=True,
)
return training_dataset, training_cutoff
# ---- Modello TFT ----
def build_tft_model(training_dataset):
tft = TemporalFusionTransformer.from_dataset(
training_dataset,
learning_rate=0.03,
hidden_size=64, # Dimensione hidden layer
attention_head_size=4, # Numero attention heads
dropout=0.1,
hidden_continuous_size=16, # Feature continue
loss=QuantileLoss(), # Previsione probabilistica
log_interval=10,
reduce_on_plateau_patience=4,
)
print(f'Numero parametri: {tft.size()/1e3:.1f}k')
return tft
# ---- Training con PyTorch Lightning ----
def train_tft(training_dataset, validation_dataset):
train_loader = training_dataset.to_dataloader(
train=True, batch_size=128, num_workers=4
)
val_loader = validation_dataset.to_dataloader(
train=False, batch_size=128, num_workers=4
)
trainer = pl.Trainer(
max_epochs=30,
accelerator='gpu' if torch.cuda.is_available() else 'cpu',
gradient_clip_val=0.1,
callbacks=[
pl.callbacks.EarlyStopping(
monitor='val_loss', patience=5, mode='min'
),
pl.callbacks.ModelCheckpoint(
monitor='val_loss', save_top_k=1
)
]
)
tft_model = build_tft_model(training_dataset)
trainer.fit(tft_model, train_loader, val_loader)
# Interpretabilita: variable importance
best_model = TemporalFusionTransformer.load_from_checkpoint(
trainer.checkpoint_callback.best_model_path
)
raw_predictions, x = best_model.predict(
val_loader, mode='raw', return_x=True
)
# Feature importance - chi contribuisce di più alle previsioni
interpretation = best_model.interpret_output(
raw_predictions, reduction='sum'
)
print('Feature importance:')
for key, vals in interpretation.items():
print(f' {key}: {vals}')
return best_model
N-BEATS: Tekrarlayan Mimariler Olmadan Sinir Temelinde Genişleme
N-VURUŞLAR (Element AI, 2020) ve tamamen farklı bir yaklaşım: yalnızca katmanları kullanın Evrişimler veya dikkat mekanizmaları olmadan, yığınlar ve bloklar halinde organize edilmiş, tamamen bağlantılı. Her blok, sinyali temel genişlemeye (trend, mevsimsellik) ve artıklara ayrıştırır. M4'te Rekabet veri seti tüm istatistiksel yöntemlerden %11 oranında daha iyi performans gösterdi ve ayrıca hibrit sinirsel-istatistiksel %3 farkla kazanan Güçlü mevsimselliğe sahip gıda ürünleri için N-BEATS'in yorumlanabilir versiyonu iş analistlerinin takdir edeceği trend/mevsimsel ayrıştırmalar üretir.
Gıda Talep Tahmini için Özellik Mühendisliği
Özellik mühendisliğinin kalitesi genellikle model performansında en kritik faktördür. Mükemmel özellik mühendisliğine sahip bir LSTM, vasat özellik mühendisliğine sahip bir TFT'den daha iyi performans gösterir. bakalım Gıda bağlamı için oluşturulacak özelliklerin ana kategorileri.
Zaman Değişkenleri için Döngüsel Kodlama
Yaygın bir hata, haftanın gününü bir tamsayı değeri (0-6) olarak ele almaktır. Sorun şu ki model 6. günün (Pazar) 0. güne (Pazartesi) "yakın" olduğunu anlamıyor. Kodlama sinüs ve kosinüs ile döngüsellik bu sorunu çözer.
# Feature engineering completo per food demand forecasting
import pandas as pd
import numpy as np
from typing import List, Dict
def create_temporal_features(df: pd.DataFrame) -> pd.DataFrame:
"""Crea feature temporali cicliche e categoriche"""
df = df.copy()
idx = df.index
# Encoding ciclico - preserva la ciclicita (es. lunedi vicino a domenica)
df['hour_sin'] = np.sin(2 * np.pi * idx.hour / 24)
df['hour_cos'] = np.cos(2 * np.pi * idx.hour / 24)
df['dow_sin'] = np.sin(2 * np.pi * idx.dayofweek / 7)
df['dow_cos'] = np.cos(2 * np.pi * idx.dayofweek / 7)
df['month_sin'] = np.sin(2 * np.pi * idx.month / 12)
df['month_cos'] = np.cos(2 * np.pi * idx.month / 12)
df['doy_sin'] = np.sin(2 * np.pi * idx.dayofyear / 365.25)
df['doy_cos'] = np.cos(2 * np.pi * idx.dayofyear / 365.25)
# Feature binarie utili per GDO italiana
df['is_weekend'] = (idx.dayofweek >= 5).astype(int)
df['is_monday'] = (idx.dayofweek == 0).astype(int)
df['is_friday'] = (idx.dayofweek == 4).astype(int)
# Posizione nel mese (utile per effetti stipendio)
df['day_of_month'] = idx.day
df['is_month_start'] = (idx.day <= 5).astype(int)
df['is_month_end'] = (idx.day >= 25).astype(int)
# Settimana dell'anno
df['week_of_year'] = idx.isocalendar().week.astype(int)
df['quarter'] = idx.quarter
return df
def create_lag_features(df: pd.DataFrame, target_col: str,
lags: List[int] = None) -> pd.DataFrame:
"""Crea lag features del target"""
if lags is None:
lags = [1, 2, 3, 7, 14, 21, 28]
df = df.copy()
for lag in lags:
df[f'{target_col}_lag_{lag}d'] = df[target_col].shift(lag)
return df
def create_rolling_features(df: pd.DataFrame, target_col: str,
windows: List[int] = None) -> pd.DataFrame:
"""Crea rolling statistics"""
if windows is None:
windows = [3, 7, 14, 28]
df = df.copy()
for window in windows:
df[f'{target_col}_roll_mean_{window}d'] = (
df[target_col].shift(1).rolling(window).mean()
)
df[f'{target_col}_roll_std_{window}d'] = (
df[target_col].shift(1).rolling(window).std()
)
df[f'{target_col}_roll_min_{window}d'] = (
df[target_col].shift(1).rolling(window).min()
)
df[f'{target_col}_roll_max_{window}d'] = (
df[target_col].shift(1).rolling(window).max()
)
# Exponential moving average
df[f'{target_col}_ema_{window}d'] = (
df[target_col].shift(1).ewm(span=window).mean()
)
return df
def create_weather_features(df: pd.DataFrame,
meteo_df: pd.DataFrame) -> pd.DataFrame:
"""
Integra variabili meteorologiche
meteo_df: DataFrame con colonne [date, temp_max, temp_min, precipitazione_mm,
umidita, radiazione_solare, vento_kmh]
"""
df = df.merge(meteo_df, left_index=True, right_on='date', how='left')
# Feature derivate dal meteo
df['temp_delta'] = df['temp_max'] - df['temp_min']
df['is_hot'] = (df['temp_max'] > 28).astype(int) # Ondata di caldo
df['is_cold'] = (df['temp_min'] < 5).astype(int) # Freddo invernale
df['is_rain'] = (df['precipitazione_mm'] > 1).astype(int)
# Interazioni meteo x prodotto (es. gelati vanno bene con caldo)
# Necessità di parametrizzazione per categoria prodotto
df['heat_wave_consecutive'] = (
df['is_hot'].rolling(3).sum() == 3
).astype(int)
return df
def create_promotion_features(df: pd.DataFrame,
promo_df: pd.DataFrame) -> pd.DataFrame:
"""
Crea feature da calendario promozionale
promo_df: colonne [date, sku_id, discount_pct, is_volantino, is_digital_promo]
"""
df = df.merge(promo_df, left_index=True, right_on='date', how='left')
df['discount_pct'] = df['discount_pct'].fillna(0)
df['is_promotion'] = (df['discount_pct'] > 0).astype(int)
df['is_deep_discount'] = (df['discount_pct'] > 0.3).astype(int)
# Effetto anticipazione promozione (pre-promo dip)
df['promo_lag_1'] = df['is_promotion'].shift(1).fillna(0)
df['promo_lead_1'] = df['is_promotion'].shift(-1).fillna(0) # Solo in training
# Effetto post-promozione (inventory loading)
df['days_since_last_promo'] = (
df['is_promotion']
.pipe(lambda s: s.where(s.eq(1)).ffill().index - s.index)
.dt.days
.clip(upper=30)
)
return df
def create_price_features(df: pd.DataFrame) -> pd.DataFrame:
"""Feature relative al prezzo e alla sua variazione"""
df = df.copy()
df['price_lag_1'] = df['price'].shift(1)
df['price_change_pct'] = df['price'].pct_change()
df['price_vs_avg_30d'] = df['price'] / df['price'].rolling(30).mean() - 1
return df
def full_feature_engineering_pipeline(df: pd.DataFrame,
meteo_df: pd.DataFrame,
promo_df: pd.DataFrame) -> pd.DataFrame:
"""Pipeline completa di feature engineering"""
df = create_temporal_features(df)
df = create_lag_features(df, 'sales')
df = create_rolling_features(df, 'sales')
df = create_weather_features(df, meteo_df)
df = create_promotion_features(df, promo_df)
df = create_price_features(df)
df = df.dropna()
print(f'Feature totali create: {df.shape[1]}')
print(f'Sample size dopo feature engineering: {df.shape[0]}')
return df
Eksiksiz ML Ardışık Düzeni: Ham Verilerden Üretimdeki Modele
Profesyonel bir talep tahmin hattı model eğitimiyle sınırlı değildir. gerektirir veri toplamayı, ön işlemeyi, ileriye dönük doğrulamayı yöneten uçtan uca bir mimari, model seçimi, dağıtımı ve sürekli izlenmesi. Aşağıda üretime hazır bir boru hattının yapısı verilmiştir.
# Pipeline ML completa per food demand forecasting
import mlflow
import mlflow.pytorch
from dataclasses import dataclass, field
from typing import Optional, Tuple
import pandas as pd
import numpy as np
import json
import logging
from pathlib import Path
from datetime import datetime, timedelta
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ---- Configurazione Pipeline ----
@dataclass
class ForecastConfig:
# Data
store_ids: list = field(default_factory=list)
product_categories: list = field(default_factory=list)
training_lookback_days: int = 365
forecast_horizon_days: int = 7
# Feature engineering
lag_days: list = field(default_factory=lambda: [1, 2, 3, 7, 14, 28])
rolling_windows: list = field(default_factory=lambda: [7, 14, 28])
# Model selection
model_type: str = 'tft' # 'sarima', 'prophet', 'lstm', 'tft', 'xgboost'
use_ensemble: bool = False
# Validation
n_cv_splits: int = 4
val_horizon_days: int = 14
# MLflow
experiment_name: str = 'food_demand_forecasting'
run_name: Optional[str] = None
# Deployment
min_mape_threshold: float = 0.20 # Non deployare se MAPE > 20%
model_registry_name: str = 'food_demand_model'
# ---- Step 1: Data Collection e Validation ----
class DataCollector:
def __init__(self, config: ForecastConfig):
self.config = config
def collect(self, start_date: str, end_date: str) -> pd.DataFrame:
"""Raccoglie dati da data warehouse (es. Snowflake, BigQuery)"""
query = f"""
SELECT
date,
store_id,
product_id,
category,
sales_units,
price,
stock_level,
is_holiday,
is_promotion,
discount_pct
FROM gold.sales_daily
WHERE date BETWEEN '{start_date}' AND '{end_date}'
AND store_id IN ({','.join(map(str, self.config.store_ids))})
ORDER BY date, store_id, product_id
"""
# Qui si userebbe il connector del DWH (es. snowflake-connector-python)
# df = snowflake_connector.execute(query)
logger.info(f'Dati raccolti: {start_date} - {end_date}')
return df # Placeholder
def validate(self, df: pd.DataFrame) -> Tuple[pd.DataFrame, dict]:
"""Validazione qualità dati"""
issues = {}
# Check valori negativi
neg_sales = (df['sales_units'] < 0).sum()
if neg_sales > 0:
issues['negative_sales'] = neg_sales
df.loc[df['sales_units'] < 0, 'sales_units'] = 0
# Check missing dates per gruppo
for group_id, group_df in df.groupby(['store_id', 'product_id']):
date_range = pd.date_range(group_df['date'].min(), group_df['date'].max())
missing = len(date_range) - len(group_df)
if missing > 0:
issues[f'missing_dates_{group_id}'] = missing
# Check outliers (IQR method)
Q1 = df['sales_units'].quantile(0.25)
Q3 = df['sales_units'].quantile(0.75)
IQR = Q3 - Q1
outliers = ((df['sales_units'] < Q1 - 3*IQR) | (df['sales_units'] > Q3 + 3*IQR)).sum()
issues['outliers'] = int(outliers)
logger.info(f'Validazione dati: {issues}')
return df, issues
# ---- Step 2: Walk-Forward Validation ----
class WalkForwardValidator:
"""
Validazione temporale corretta per time series.
NON usare cross-validation classico che crea data leakage.
"""
def __init__(self, config: ForecastConfig):
self.config = config
def validate(self, df: pd.DataFrame, model_class) -> dict:
results = []
total_days = len(df['date'].unique())
split_size = total_days // (self.config.n_cv_splits + 1)
for fold in range(self.config.n_cv_splits):
train_end_idx = split_size * (fold + 1)
val_start_idx = train_end_idx
val_end_idx = val_start_idx + self.config.val_horizon_days
train_dates = sorted(df['date'].unique())[:train_end_idx]
val_dates = sorted(df['date'].unique())[val_start_idx:val_end_idx]
train_df = df[df['date'].isin(train_dates)]
val_df = df[df['date'].isin(val_dates)]
if len(val_df) == 0:
continue
# Training sul fold
model = model_class(self.config)
model.fit(train_df)
predictions = model.predict(val_df)
# Metriche
actuals = val_df['sales_units'].values
preds = predictions['forecast'].values
mape = np.mean(np.abs((actuals - preds) / (actuals + 1e-8))) * 100
rmse = np.sqrt(np.mean((actuals - preds) ** 2))
mae = np.mean(np.abs(actuals - preds))
results.append({
'fold': fold,
'mape': mape,
'rmse': rmse,
'mae': mae,
'train_size': len(train_df),
'val_size': len(val_df)
})
logger.info(f'Fold {fold}: MAPE={mape:.2f}%, RMSE={rmse:.2f}')
# Aggrega risultati
results_df = pd.DataFrame(results)
return {
'mean_mape': results_df['mape'].mean(),
'std_mape': results_df['mape'].std(),
'mean_rmse': results_df['rmse'].mean(),
'mean_mae': results_df['mae'].mean(),
'folds': results
}
# ---- Step 3: MLflow Tracking e Model Registry ----
class ExperimentTracker:
def __init__(self, config: ForecastConfig):
self.config = config
mlflow.set_experiment(config.experiment_name)
def run_experiment(self, df: pd.DataFrame, model_class) -> str:
run_name = self.config.run_name or f'{self.config.model_type}_{datetime.now():%Y%m%d_%H%M}'
with mlflow.start_run(run_name=run_name) as run:
# Log parametri
mlflow.log_params({
'model_type': self.config.model_type,
'forecast_horizon': self.config.forecast_horizon_days,
'training_lookback': self.config.training_lookback_days,
'n_cv_splits': self.config.n_cv_splits
})
# Walk-forward validation
validator = WalkForwardValidator(self.config)
metrics = validator.validate(df, model_class)
# Log metriche
mlflow.log_metrics({
'cv_mean_mape': metrics['mean_mape'],
'cv_std_mape': metrics['std_mape'],
'cv_mean_rmse': metrics['mean_rmse'],
'cv_mean_mae': metrics['mean_mae']
})
# Training finale su tutti i dati
final_model = model_class(self.config)
final_model.fit(df)
# Log artefatti
mlflow.log_dict(metrics, 'validation_metrics.json')
# Registra modello se MAPE accettabile
if metrics['mean_mape'] < self.config.min_mape_threshold * 100:
mlflow.pytorch.log_model(
final_model,
artifact_path='model',
registered_model_name=self.config.model_registry_name
)
logger.info(f'Modello registrato: MAPE={metrics["mean_mape"]:.2f}%')
else:
logger.warning(
f'Modello NON registrato: MAPE {metrics["mean_mape"]:.2f}% '
f'> soglia {self.config.min_mape_threshold * 100}%'
)
return run.info.run_id
Karşılaştırma: Gerçek Veri Kümesi Üzerindeki Modeller Arasında Karşılaştırma
Aşağıda, farklı yaklaşımların tipik bir veri kümesi üzerindeki performansının sistematik bir karşılaştırması bulunmaktadır. GDO: 150 SKU taze ürün (süt ürünleri, meyve, sebze, et), 2 yıllık günlük veriler dışsal değişkenlerle (hava durumu, promosyonlar, tatiller) 5 satış noktasından.
Karşılaştırmalı Karşılaştırma Modelleri - İtalyan GDO Veri Kümesi (150 SKU, 2 yıl)
| Modeli | HARİTA (%) | RMSE (birim) | MAE (birleşik) | Eğitim Süresi | Yorumlanabilirlik | Notlar |
|---|---|---|---|---|---|---|
| Hareketli Ortalama (Temel) | 31.4 | 18.7 | 12.3 | < 1 dakika | Yüksek | Mevsimsellik yok, regresör yok |
| SARIMA | 22.8 | 14.2 | 9.8 | ~15 dk | Yüksek | Yalnızca bir mevsimsel gecikme |
| Holt-Winters | 20.1 | 13.1 | 8.9 | ~5 dakika | Yüksek | Normal diziler için iyi |
| Peygamber | 14.6 | 10.4 | 7.2 | ~30 dk | Yüksek | Tatil/regresörlerle mükemmel |
| XGBoost + Özellik Müh. | 12.9 | 9.8 | 6.7 | ~10 dk | Ortalama | Kritik özellik mühendisliği |
| LSTM (tek değişkenli) | 16.4 | 11.2 | 7.8 | ~45 dk GPU | Düşük | Exog olmadan Peygamberden daha kötü |
| LSTM (çok değişkenli) | 10.8 | 8.4 | 5.9 | ~2 saat GPU | Düşük | Exog ile güçlü gelişme |
| N-VURUŞLAR | 11.2 | 8.7 | 6.1 | ~1 saat GPU | Ortalama | Yorumlanabilir ayrıştırma |
| TFT (Geçici Füzyon Transformatörü) | 8.3 | 6.9 | 4.8 | ~4 saat GPU | Yüksek | Genel olarak en iyisi, yorumlanabilir |
| TFT + XGBoost Topluluğu | 7.6 | 6.4 | 4.4 | ~5 saat GPU | Ortalama | Tek TFT'de +%8 |
Veri kümesi: 150 yeni ürün SKU'su, 5 mağaza, 2 yıllık günlük veriler. 4 kat (14 günlük ufuk) üzerinde ileriye dönük doğrulama. GPU: NVIDIA A100.
Model Seçiminde Dikkat Edilmesi Gerekenler
TFT en güçlü seçenektir ancak her SKU için GPU ve bol miktarda veri gerektirir. Perakendeciler için binlerce SKU ancak tek ürün başına düşük hacimler söz konusu olduğunda hibrit yaklaşım en iyi sonucu verir: Uzun kuyruklu SKU'lar (düşük hacimli, zayıf veriler) için XGBoost veya Prophet, SKU'lar için TFT En fazla geliri ve israfı yaratan yüksek hacimli endüstriler.
Atık Azaltma: Envanter ve Dinamik Fiyatlandırma ile Tahmin Entegrasyonu
Doğru bir tahmin modeli tek başına israfı azaltmaz: gereklidir eylem tahminlerde Envanter yönetimi ve dinamik fiyatlandırma sistemleri aracılığıyla. Döngü karar verme ve: gelecekteki talebin tahmini → tedarikçiye optimum sipariş → izleme stok seviyeleri → son kullanma tarihi yaklaşmış ürünler için dinamik indirim → otomatik bağış Satılmayan fazlalıklar için.
Dinamik Emniyet Stoku ile Envanter Optimizasyonu
# Inventory optimization basato su forecast probabilistico
import numpy as np
from dataclasses import dataclass
from typing import Optional
@dataclass
class InventoryParams:
product_id: str
shelf_life_days: int # Vita utile del prodotto
lead_time_days: int # Giorni da ordine a consegna
service_level: float = 0.95 # Fill rate target (95%)
holding_cost_per_unit: float = 0.05 # Costo mantenimento/giorno
shortage_cost_per_unit: float = 2.50 # Costo stockout per unita
waste_cost_per_unit: float = 1.20 # Costo di buttare una unita
def calculate_optimal_order(
demand_forecast: np.ndarray, # Previsione media per i prossimi N giorni
demand_std: np.ndarray, # Deviazione standard previsione
current_stock: int,
params: InventoryParams
) -> dict:
"""
Calcola la quantità ottimale da ordinare considerando:
- Shelf life del prodotto (prodotti freschi deperibili)
- Service level target
- Trade-off costo stockout vs costo spreco
"""
from scipy import stats
# Z-score per il service level target
z_score = stats.norm.ppf(params.service_level)
# Domanda attesa nel lead time
lt_demand = demand_forecast[:params.lead_time_days].sum()
lt_std = np.sqrt((demand_std[:params.lead_time_days] ** 2).sum())
# Safety stock = Z * sigma * sqrt(lead_time)
safety_stock = z_score * lt_std
# Reorder point
reorder_point = lt_demand + safety_stock
# Domanda fino a shelf life (non ordinare più di quanto venderemo)
max_sellable_demand = demand_forecast[:params.shelf_life_days].sum()
max_sellable_std = np.sqrt((demand_std[:params.shelf_life_days] ** 2).sum())
# Order quantity
unconstrained_order = max(0, reorder_point - current_stock)
# Vincolo shelf life: non ordinare più di quanto venderemo nella vita utile
# Aggiusta per il rischio di spreco
waste_probability = 1 - stats.norm.cdf(
current_stock + unconstrained_order,
loc=max_sellable_demand,
scale=max_sellable_std
)
# Costo atteso
expected_waste_cost = waste_probability * unconstrained_order * params.waste_cost_per_unit
expected_shortage_cost = (1 - params.service_level) * lt_demand * params.shortage_cost_per_unit
# Ottimizzazione: riduce ordine se costo spreco > beneficio margine
if expected_waste_cost > expected_shortage_cost * 0.5:
adjustment_factor = 1 - (expected_waste_cost / (expected_waste_cost + expected_shortage_cost))
optimal_order = int(unconstrained_order * adjustment_factor)
else:
optimal_order = int(unconstrained_order)
return {
'optimal_order_qty': max(0, optimal_order),
'reorder_point': reorder_point,
'safety_stock': safety_stock,
'expected_demand_7d': demand_forecast[:7].sum(),
'waste_risk_pct': waste_probability * 100,
'shortage_risk_pct': (1 - params.service_level) * 100,
'decision_reason': 'waste_adjusted' if expected_waste_cost > expected_shortage_cost * 0.5
else 'standard_reorder'
}
# Esempio utilizzo
params = InventoryParams(
product_id='insalata_vaschetta_150g',
shelf_life_days=5,
lead_time_days=1,
service_level=0.95,
waste_cost_per_unit=0.85,
shortage_cost_per_unit=3.20
)
# Simulazione con forecast TFT (media e std da quantile forecast)
forecast_mean = np.array([45, 52, 68, 71, 89, 95, 48]) # Lunedi-Domenica
forecast_std = np.array([8, 9, 12, 13, 15, 16, 9]) # Deviazione standard
order = calculate_optimal_order(
demand_forecast=forecast_mean,
demand_std=forecast_std,
current_stock=120,
params=params
)
print('Raccomandazione ordine:')
for key, val in order.items():
print(f' {key}: {val}')
Atıkların Azaltılması için Dinamik Fiyatlandırma: İndirim Optimizasyonu
Son kullanma tarihine dayalı dinamik fiyatlandırma, maliyetleri azaltmanın en etkili araçlarından biridir. Taze ürünlerde atık. Amaç bulmaktır optimum indirimli fiyat Son kullanma tarihi geçme riski olan ürünlerden elde edilen geliri en üst düzeye çıkaran, satışlarını hızlandıran Satışları tam fiyatlı ürünlere yamyamlaştırın.
Atıksızİsrailli startup, elektronik raflara dayalı bir sistem geliştirdi fiyatları son kullanma tarihine, seviyelerine göre otomatik olarak güncelleyen etiketler (ESL) stok ve müşteri davranışları. Bunu benimseyen perakendeciler israfı azalttı %40duruma göre bu ürünlerden elde edilen gelirleri artırırken önceki (satılmayan ürünü atın). Ayrıca Gitmek İçin Çok İyi o başlattı 2024, indirim düzeylerini optimize etmek için verileri SKU düzeyinde işleyen bir yapay zeka platformu, Manuel son teslim tarihi kontrollerini %93-99 oranında azaltmak.
# Algoritmo markdown optimization per prodotti prossimi a scadenza
import numpy as np
from scipy.optimize import minimize_scalar
from dataclasses import dataclass
from typing import Tuple
@dataclass
class MarkdownConfig:
min_margin_pct: float = 0.10 # Margine minimo (non scendere sotto il 10%)
max_discount_pct: float = 0.70 # Sconto massimo (70%)
base_price: float = 1.99 # Prezzo normale
cost_per_unit: float = 0.95 # Costo di acquisto
waste_value: float = 0.00 # Valore residuo se buttato (es. recupero)
def price_elasticity_model(price: float, base_price: float,
base_demand: float,
elasticity: float = -1.8) -> float:
"""
Modello di elasticita del prezzo per prodotti freschi GDO.
Elasticita tipica prodotti freschi: -1.5 a -2.5
(sconto del 10% aumenta la domanda del 15-25%)
"""
price_ratio = price / base_price
return base_demand * (price_ratio ** elasticity)
def markdown_revenue(discount_pct: float,
config: MarkdownConfig,
current_stock: int,
base_demand: float,
elasticity: float = -1.8) -> float:
"""
Calcola il ricavo atteso da un determinato livello di sconto.
Massimizzare questa funzione = trovare lo sconto ottimale.
"""
discounted_price = config.base_price * (1 - discount_pct)
# Vincolo margine minimo
min_price = config.cost_per_unit * (1 + config.min_margin_pct)
if discounted_price < min_price:
return -1e10 # Penalita per prezzi sotto il costo
# Domanda attesa con il nuovo prezzo
expected_demand = price_elasticity_model(
discounted_price, config.base_price, base_demand, elasticity
)
# Unita vendute = min(domanda attesa, stock disponibile)
units_sold = min(expected_demand, current_stock)
units_wasted = max(0, current_stock - units_sold)
# Revenue = vendite + (eventuali recupero valore sprecato)
revenue = (units_sold * discounted_price) + (units_wasted * config.waste_value)
# Costo opportunità: confronto con vendita a prezzo pieno (se avessimo aspettato)
# ma qui massimizziamo il recupero dato che il prodotto scadera
return revenue
def find_optimal_markdown(config: MarkdownConfig,
current_stock: int,
days_to_expiry: int,
base_daily_demand: float,
demand_elasticity: float = -1.8) -> dict:
"""
Trova lo sconto ottimale considerando i giorni rimanenti a scadenza.
Logica: più vicini alla scadenza, maggiore lo sconto necessario.
"""
# Urgenza basata sui giorni a scadenza
if days_to_expiry >= 5:
# Nessun markdown necessario
return {'discount_pct': 0.0, 'recommended_action': 'no_action',
'expected_revenue': config.base_price * min(base_daily_demand * days_to_expiry, current_stock)}
# Moltiplicatore urgenza: più vicini alla scadenza, maggiore il markup di urgenza
urgency_factor = 1.0 + (5 - days_to_expiry) * 0.15 # +15% urgenza per giorno
# Domanda effettiva considerando urgenza (es. promozioni di fine giornata)
effective_demand = base_daily_demand * days_to_expiry * urgency_factor
# Ottimizzazione: trova lo sconto che massimizza il ricavo totale
result = minimize_scalar(
lambda d: -markdown_revenue(d, config, current_stock,
effective_demand, demand_elasticity),
bounds=(0.0, config.max_discount_pct),
method='bounded'
)
optimal_discount = result.x
optimal_revenue = -result.fun
waste_at_no_discount = max(0, current_stock - effective_demand)
waste_with_markdown = max(0, current_stock - price_elasticity_model(
config.base_price * (1 - optimal_discount),
config.base_price, effective_demand, demand_elasticity
))
waste_reduction_units = waste_at_no_discount - waste_with_markdown
revenue_recovered = optimal_revenue - (config.waste_value * current_stock)
action = (
'urgent_markdown' if days_to_expiry <= 1
else 'markdown' if optimal_discount > 0.15
else 'slight_discount'
)
return {
'discount_pct': round(optimal_discount * 100, 1),
'discounted_price': round(config.base_price * (1 - optimal_discount), 2),
'expected_revenue': round(optimal_revenue, 2),
'waste_reduction_units': round(waste_reduction_units, 0),
'revenue_recovered_vs_waste': round(revenue_recovered, 2),
'days_to_expiry': days_to_expiry,
'recommended_action': action
}
# Esempio: insalata con 2 giorni alla scadenza
config = MarkdownConfig(
base_price=1.99,
cost_per_unit=0.80,
min_margin_pct=0.05, # Riduco margine minimo vicino a scadenza
max_discount_pct=0.60
)
recommendation = find_optimal_markdown(
config=config,
current_stock=45,
days_to_expiry=2,
base_daily_demand=12,
demand_elasticity=-2.0
)
print('Raccomandazione markdown:')
for k, v in recommendation.items():
print(f' {k}: {v}')
# Output atteso:
# discount_pct: 35.0
# discounted_price: 1.29
# expected_revenue: 38.70
# waste_reduction_units: 28
# recommended_action: markdown
Örnek Olay: 200'den fazla satış noktasına sahip İtalyan büyük ölçekli perakende ticareti
Gerçek (anonimleştirilmiş) bir makine öğrenimi talep tahmini uygulamasını tek seferde analiz edelim 220 satış noktasına sahip, 18.000'i aktif SKU'lu İtalyan büyük ölçekli perakende zinciri 3.200 taze ürün ve yıllık yaklaşık 2,8 milyar Euro ciro.
Başlangıç durumu
ML uygulamasından önce perakendeci, tahmin içeren klasik bir ERP sistemi kullanıyordu son 4 ayın ağırlıklı hareketli ortalamalarına dayanmaktadır. Taze ürünlerdeki ortalama MAPE %28, sezonluk ürünlerde %45'e kadar zirveler. Taze ürünlerde israf oranı Bertaraf maliyetleri, değer kaybı ve itibar etkisi dahil olmak üzere satın alınan değerin %12'si önemli. Alıcılar siparişleri haftalık olarak manuel olarak inceliyor ve yaklaşık 3 tam zamanlı FTE.
Çözüm Mimarisi
Teknoloji Yığını Uygulandı
| Katmanlar | Seçim Teknolojisi | İşlev |
|---|---|---|
| Veri Ambarı | Kar taneleri | Merkezi depolama, 220 mağazanın tamamından POS verileri |
| ETL/ELT | dbt + Airbyte | POS entegrasyonu, hava durumu (OpenWeather API), promosyonlar (CRM sistemi) |
| Orkestrasyon | Apache Hava Akışı | Günlük yeniden eğitim sabah 3:00-5:00, anormallik uyarısı |
| Modeller | TFT (ilk 500 SKU) + XGBoost (uzun kuyruk) | Her SKU x mağazası için 7 ve 14 günlük tahmin |
| MLOps | Databricks'te MLflow | Denemeleri izleme, model kaydı, A/B testi |
| Servis | FastAPI + Redis önbelleği | Gecikme süresi < 100 ms, 24 saat önbellek ile API tahmini |
| Markdown Motoru | Python mikro hizmetleri | Risk altındaki ürünler için her 4 saatte bir optimum indirim hesaplaması |
| ESL Entegrasyonu | Hanshow / Pricer API'si | Elektronik raf etiketlerinde fiyatların otomatik güncellenmesi |
| Bağış | API Gıda Bankası | Son kullanma tarihi 24 saatten az olan ve satılmayan ürünler için otomatik bildirim |
Zaman Çizelgesi ve Uygulama Aşamaları
- 1-2. Ay: Veri denetimi, 3 yıllık geçmiş verilerin temizlenmesi, dış kaynakların entegrasyonu (hava durumu, promosyonlar). 50 adet yüksek hacimli, çabuk bozulabilen pilot SKU'nun tanımlanması.
- 3-4. Ay: Pilot model eğitimi (XGBoost + Prophet), ileriye dönük doğrulama, başlangıç MAPE %27,8. 3 test mağazasında ERP sipariş sistemi ile ilk entegrasyon.
- 5-6. Ay: En iyi 500 yeni SKU'ya ilişkin TFT eğitimi. A/B testi: Makine öğrenimi ile 30 mağaza geleneksel sistemle 30 mağazaya kıyasla. Sonuç: ML mağazalarında atıkta %28 azalma.
- 7-9. Ay: 220 mağazanın tamamına dağıtım. Markdown motor uygulaması 80 donanımlı mağazada ESL entegrasyonu ile. Gıda Bankası üzerinden otomatik bağış aktivasyonu.
- 10-12. Ay: Şablon optimizasyonu, sosyal medya özelliği eklendi (bahsediliyor) Gelişmekte olan trendler için Instagram/TikTok'taki ürünler). TFT+XGBoost topluluğu uygulaması.
12 Ayda Ölçülen Sonuçlar
İş KPI'ları - Uygulama Sonrası Sonuçlar (12 Ay)
| Metrik | ML'den önce | 12 Ay Sonra ML | Varyasyon |
|---|---|---|---|
| MAPE taze ürünler | %28,2 | %9,1 | -%67,7 |
| Satın alınan değer üzerinden atık oranı | %12,0 | %7,8 | -%35,0 |
| Stokta Olmama Oranı (OOS) | %4,8 | %2,9 | -%39,6 |
| Bağışlanan ürünler (ton/yıl) | 45 ton | 112 ton | +%149 (Gadda yardım yasası) |
| İndirim verimliliği (gelirin geri kazanılması yüzdesi) | %32 | %71 | +39 puan |
| Manuel sipariş incelemesine ayrılmış FTE'ler | 3,0 TZE | 0,8 TZE | -%73,3 |
| Atık maliyetlerinden tasarruf + bertaraf | - | 4,2 milyon €/yıl | Yeni tasarruf |
| Bağışlar için vergi avantajı (Gadda Kanunu) | 85 bin €/yıl | €210K/yıl | +%147 |
| Proje yatırım getirisi (yatırım: 1,8 milyon €) | - | 12 ayda %234 | Geri ödeme 5,2 ay |
Vaka Çalışmasından Öğrenilen Dersler
- Gerçek darboğaz geçmiş verilerin kalitesidir: zamanın %40'ı Projenin tamamı veri temizleme ve entegrasyonuna ayrılmıştı. Geçmiş POS verileri tatil dönemlerinde ve mağaza yenilemelerinde önemli boşluklar.
- Tüm SKU'lar aynı modeli hak etmez: İlk 500 SKU için TFT, Orta seviye SKU'lar (500-3000) için peygamber, uzun kuyruk için hareketli ortalama (>3000 SKU). Optimum maliyet/fayda ile üç katmanlı yaklaşım.
- Değişim yönetimi kritik öneme sahiptir: 20 yıldan fazla deneyime sahip alıcılar AI'ya direndiler. Çözüm: Peygamber'e ayrıştırmayı gösterin (trend + mevsimsellik) + tatil), alıcıların modelin mantığını anlamasına ve doğrulamasına olanak tanır.
- Markdown motoru ESL entegrasyonunu gerektirir: elektronik raf etiketleri olmadan, Fiyatların manuel olarak güncellenmesi, reaksiyon hızının avantajını iptal etti Otomatik sistem.
- Sürekli izleme önemlidir: modeller zamanla bozulur (kavram kayması) COVID sonrası satın alma davranışlarındaki değişiklikler nedeniyle. Yeniden eğitim haftalık otomatik olarak istikrarlı performanslar korunur.
FoodTech'te Talep Tahmini için İş Metrikleri
Bir talep tahmin sisteminin başarısı yalnızca MAPE ile ölçülmez. Bu gerekli hem modelin teknik kalitesini hem de etkisini kapsayan bir ölçüm çerçevesi tanımlayın gerçek iş.
Metrik Çerçevesi: Teknik + İş
| Kategori | Metrik | Formül / Tanım | Tipik büyük ölçekli dağıtım hedefi |
|---|---|---|---|
| Doğruluk Modeli | HARİTA | Ortalama Mutlak Yüzde Hatası | < %15 taze ürünler |
| RMSE | Ortalama Karekök Hatası (birim olarak) | SKU hacmine bağlıdır | |
| Ön yargı | (Tahmin - Gerçek) / Gerçek, ortalama | |Önyargı| < %5 (sistematik aşırı/eksiklik yok) | |
| WMAPE | Satış hacmine göre ağırlıklandırılmış MAPE | < %10 (uzun kuyruk için MAPE'den daha iyi) | |
| Atık | Atık Azaltma Oranı (WRR) | (Önceki Atık - Sonra Atık) / Öncesi Atık | > 12 ay içinde %30 |
| Satın Almalarda Atık Yüzdesi | Boşa Değer / Satın Alınan Değer | < %8 (sektör karşılaştırması) | |
| Yönlendirilen Ton (bağışlar) | Bağışlanan ton / Toplam potansiyel atık | > Fazlalığın %50'si | |
| Kullanılabilirlik | Stokta Olmama Oranı (OOS) | Stok yokken SKU yüzdesi / toplam SKU | < %3 |
| Doluluk Oranı | Teslim edilen birimler / Sipariş edilen birimler | > %97 | |
| Koruma Günleri (DOC) | Mevcut stok / Ortalama günlük talep | Ürün kategorisi için ideal | |
| Dinamik Fiyatlandırma | Markdown Verimliliği (ME) | İndirim Geliri / Maliyet Potansiyeli Atık | > %65 |
| Markdown aracılığıyla Satılan Ürünler | İndirimli olarak satılan son kullanma tarihi yaklaşmış ürünlerin yüzdesi | > Ürünlerin %80'i risk altında | |
| Ortalama İndirim Uygulandı | İndirimli ürünlerde ortalama indirim | %25-45 (esneklik için ideal) |
İzleme Kontrol Paneli: Konsept Kayma Tespiti
Gıda tahmin modelleri özellikle hassastır konsept kayması: Satın alma kalıpları beslenme eğilimleri nedeniyle (örneğin vegan ürünlerdeki artış) mevsime göre değişir. fiyat krizi nedeniyle (2022-2024 enflasyonu, satın alma davranışını büyük ölçüde değiştirdi) taze ürünler), olağanüstü olaylar (pandemi) için. Sürekli takip etmek gerekiyor performans bozulması.
# Monitoring sistema di rilevamento concept drift per food forecasting
import numpy as np
import pandas as pd
from scipy import stats
from dataclasses import dataclass
from typing import List, Optional
import logging
logger = logging.getLogger(__name__)
@dataclass
class DriftAlert:
severity: str # 'WARNING' | 'CRITICAL'
metric: str
current_value: float
baseline_value: float
change_pct: float
affected_skus: List[str]
recommendation: str
class ForecastMonitor:
def __init__(self, baseline_window_days: int = 30,
monitoring_window_days: int = 7):
self.baseline_window = baseline_window_days
self.monitoring_window = monitoring_window_days
self.thresholds = {
'mape_warning': 0.20, # MAPE > 20% = warning
'mape_critical': 0.35, # MAPE > 35% = critical, richiede retraining
'bias_warning': 0.10, # Bias sistematico > 10%
'bias_critical': 0.20, # Bias sistematico > 20%
'waste_rate_warning': 0.10, # Tasso spreco > 10%
}
def compute_current_metrics(self, predictions_df: pd.DataFrame,
actuals_df: pd.DataFrame) -> dict:
"""Calcola metriche sull'ultima finestra di monitoring"""
merged = predictions_df.merge(actuals_df, on=['date', 'sku_id', 'store_id'])
metrics_by_sku = merged.groupby('sku_id').apply(
lambda g: pd.Series({
'mape': np.mean(np.abs((g['actual'] - g['forecast']) / (g['actual'] + 1e-8))),
'bias': np.mean((g['forecast'] - g['actual']) / (g['actual'] + 1e-8)),
'rmse': np.sqrt(np.mean((g['actual'] - g['forecast']) ** 2)),
'n_observations': len(g)
})
)
return {
'mean_mape': metrics_by_sku['mape'].mean(),
'p90_mape': metrics_by_sku['mape'].quantile(0.9),
'mean_bias': metrics_by_sku['bias'].mean(),
'degraded_skus': metrics_by_sku[
metrics_by_sku['mape'] > self.thresholds['mape_warning']
].index.tolist(),
'critical_skus': metrics_by_sku[
metrics_by_sku['mape'] > self.thresholds['mape_critical']
].index.tolist(),
'per_sku_metrics': metrics_by_sku
}
def detect_drift(self, baseline_metrics: dict,
current_metrics: dict) -> List[DriftAlert]:
"""Confronta metriche correnti vs baseline e genera alert"""
alerts = []
# Alert MAPE
mape_change = ((current_metrics['mean_mape'] - baseline_metrics['mean_mape'])
/ baseline_metrics['mean_mape'])
if current_metrics['mean_mape'] > self.thresholds['mape_critical']:
alerts.append(DriftAlert(
severity='CRITICAL',
metric='MAPE',
current_value=current_metrics['mean_mape'] * 100,
baseline_value=baseline_metrics['mean_mape'] * 100,
change_pct=mape_change * 100,
affected_skus=current_metrics['critical_skus'],
recommendation='RETRAINING IMMEDIATO richiesto. Analizza concept drift.'
))
elif current_metrics['mean_mape'] > self.thresholds['mape_warning']:
alerts.append(DriftAlert(
severity='WARNING',
metric='MAPE',
current_value=current_metrics['mean_mape'] * 100,
baseline_value=baseline_metrics['mean_mape'] * 100,
change_pct=mape_change * 100,
affected_skus=current_metrics['degraded_skus'],
recommendation='Monitorare trend. Pianificare retraining prossima settimana.'
))
# Alert Bias sistematico
if abs(current_metrics['mean_bias']) > self.thresholds['bias_critical']:
direction = 'sovrastima' if current_metrics['mean_bias'] > 0 else 'sottostima'
alerts.append(DriftAlert(
severity='CRITICAL',
metric='Bias',
current_value=current_metrics['mean_bias'] * 100,
baseline_value=baseline_metrics.get('mean_bias', 0) * 100,
change_pct=0,
affected_skus=[],
recommendation=f'Bias sistematico di {direction}: aggiorna calibrazione modello.'
))
return alerts
def check_and_alert(self, predictions_df: pd.DataFrame,
actuals_df: pd.DataFrame,
baseline_metrics: dict) -> None:
"""Entry point principale del monitor"""
current = self.compute_current_metrics(predictions_df, actuals_df)
alerts = self.detect_drift(baseline_metrics, current)
for alert in alerts:
if alert.severity == 'CRITICAL':
logger.critical(
f'[DRIFT CRITICAL] {alert.metric}: {alert.current_value:.1f}% '
f'(baseline: {alert.baseline_value:.1f}%) - {alert.recommendation}'
)
# Qui si trigghera notifica Slack/PagerDuty
self._send_alert(alert)
else:
logger.warning(
f'[DRIFT WARNING] {alert.metric}: {alert.current_value:.1f}% - '
f'{len(alert.affected_skus)} SKU degradati'
)
def _send_alert(self, alert: DriftAlert) -> None:
"""Invia notifica al team ML Ops (implementazione dipende dal sistema)"""
# Integrazione con: Slack, PagerDuty, email, Prometheus Alertmanager
pass
Gıda Talebi Tahmininde En İyi Uygulamalar ve Anti-Modeller
Kaçınılması Gereken Anti-Desenler
- Geçici sızıntı tarihi: bunun yerine standart çapraz doğrulamayı kullanın İleriye dönük doğrulama gelecekteki verileri eğitime dahil eder. Ve en yaygın metodolojik hata ve üretimde yanlış olduğu ortaya çıkan %30-50'lik iyimser MAPE'lere yol açar.
- Tüm SKU'lar için tek şablon: Aralıklı talep gören ürünler (satış Haftada 2-3 kez) yüksek adetli ürünlerden farklı modeller gerektirir. Uygula Yılda 200 satış yapan bir ürüne LSTM, fazla uyum ve %60'ın üzerinde MAPE'ye yol açar.
- İş önyargısını göz ardı edin: perakendeciler tarihsel olarak aşırı sipariş verme eğilimindeydi stok tükenmesinden kaçınmak ("iki kötülüğün" algılanan en kötüsü). Model eğitilmişse geçmiş sipariş verilerine (gerçek talep değil) göre bu eğilimi miras alır. Satış verilerini kullan etkili, emirlerin değil.
- Gelecekteki promosyonlara örnek olmayın: Promosyonları bilmeyen bir tahmin Gelecek hafta için planlanan dönemlerde sistematik olarak yanlış tahminler yapılması promosyon. Promosyon takvimini her zaman girdi olarak entegre edin.
- Hatanın yönünü göz ardı ederek yalnızca MAPE'yi optimize edin: bağlamda 50 birimlik aşırı tahmin hatası (50 birimin boşa gitmesi) çok daha pahalıdır 50 birimlik eksik tahmin hatası (50 satış kaybı). Asimetrik metrikleri kullanın (Ağırlıklı MAPE) veya eğitimde asimetrik kayıp fonksiyonları.
Birleştirilmiş En İyi Uygulamalar
- SKU'ları işleme göre katmanlandırın: her SKU'yu hacme ve değişkenliğe göre sınıflandırır ve raf ömrü. Farklı kümelere farklı modeller uygulayın. Hiyerarşik bir yaklaşım (küresel + local) genellikle her iki saf modelden daha iyi performans gösterir.
- Her zaman bir belirsizlik tahmini içerir: kesin bir tahmin (tek sayı) ve olasılıksal bir tahminden (dağılım veya aralıklar) daha az kullanışlıdır. TFT ve Peygamber optimal güvenlik stoğunun hesaplanması için temel olan nicelikleri doğal olarak sağlarlar.
- Yeniden eğitimi otomatikleştirin ancak anormallikleri izleyin: günlük yeniden eğitim kullanışlıdır ancak dünkü veriler anormalse (örneğin POS hatası) hataları artırabilir. Yeni bir modeli üretime sokmadan önce daima bir sağlık kontrolü uygulayın.
- Alıcılarla güven oluşturun: ML sistemleri genellikle direnç nedeniyle başarısız olur organizasyonel, teknik kalite için değil. Modelin ayrıştırılmasını gösteriniz, açıklayınız. İş dilindeki tahminler, alıcılara bir tahminle geçersiz kılma olanağını bırakır. modeli geliştiren geri bildirim döngüsü.
- ROI'yi sürekli olarak ölçün: Atık tasarrufunu haftalık olarak hesaplayın kaçınılması, indirimden kurtulma, Gadda Yasası vergi avantajı. Değeri görünür hale getirin zaman içinde örgütsel bağlılığın sürdürülmesi için yaratılmış ve temel niteliktedir.
Sonuçlar: Sürdürülebilirlik Aracı Olarak Tahmin
Gıda sektöründe makine öğrenimi ile talep tahmini, yapay zeka kullanım örneklerinden biridir. günümüzde mevcut olan en iyi etki/karmaşıklık oranı. Sorun iyi tanımlanmış, veriler mevcutsa (her perakendecinin yıllarca POS geçmişi vardır), başarı ölçütleri açıktır (MAPE, Atık Oranı, Yatırım Getirisi) ve etki işin ötesine uzanır: gıda israfını azaltmak ve ekonomik, düzenleyici ve çevresel bir zorunluluk.
2025'te başlamak isteyen büyük ölçekli İtalyan perakende ticaretine önerilen yol pragmatik: başlangıç 50 adet yüksek hacimli, çabuk bozulabilen SKU'dan oluşan bir alt kümede Prophet veya XGBoost ile ölçüm yapın MAPE deltası ve 30 gün sonra Atık Azaltma Oranı, dahili iş senaryosunu oluşturur, ve ardından en iyi SKU'lar için TFT'ye ve indirim entegrasyonuna doğru aşamalı olarak ölçeklendirin ESL motorları ve sistemleri.
Nihai hedef en doğru modele sahip olmak değil, karar verme ekosistemi tahminleri eyleme dönüştüren: optimum siparişler, uyarlanabilir fiyatlandırma, bağışlar otomatik. Bu ekosistemde Makine Öğrenimi ve olanak sağlayan altyapı, ancak atık azaltımı ve her yatırımı haklı çıkaran somut iş sonucu.
FoodTech Serisine devam edin
Serinin sonraki makalelerinde talep tahmini için tamamlayıcı teknolojileri inceliyoruz:
- Madde 9 - Uydu API ve Hassas Tarım: uydu verileri gibi NDVI ve Sentinel-2, tarımsal verim tahmin modellerini besleyerek döngüyü kapatıyor üretimden dağıtıma.
- Madde 10 - Mahsul İzleme için ML Edge: gömülü çıkarım Mahsul durumunun gerçek zamanlı izlenmesi için sahadaki IoT cihazları tedarik zinciri boru hattına entegrasyon.
Üretim dağıtımını mümkün kılan MLOps teknolojileri hakkında daha fazla bilgi edinin Tahmin modelleri için ayrıca seriye başvurun MLOps for Business: MLflow ile Üretimde Yapay Zeka Modelleri.







