Commit 683e53a7 authored by Christos Anastasiou's avatar Christos Anastasiou
Browse files

Add core ML energy forecasting files

parents
Loading
Loading
Loading
Loading
+0 −0

File added.

Preview size limit exceeded, changes collapsed.

+0 −0

File added.

Preview size limit exceeded, changes collapsed.

+0 −0

File added.

Preview size limit exceeded, changes collapsed.

report_batch.py

0 → 100644
+215 −0
Original line number Diff line number Diff line
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
report_batch.py - v2 (Advanced)
---------------------------------
Δημιουργεί μια προηγμένη, διαδραστική αναφορά HTML από τα αποτελέσματα των πειραμάτων.
- Σαρώνει έναν κατάλογο για να βρει όλα τα αρχεία 'summary_overall.csv'.
- Συγκεντρώνει όλα τα δεδομένα σε ένα ενιαίο DataFrame.
- Δημιουργεί αναλύσεις για πολλαπλές μετρικές (wMAPE, RMSE, R2).
- Προσθέτει εξειδικευμένες ενότητες:
  - Ανάλυση ανά scope/αισθητήρα.
  - Λεπτομερής ανάλυση για συγκεκριμένους ορίζοντες.
- Εξάγει μια αυτόνομη (self-contained) HTML σελίδα.

Απαιτήσεις: pandas, plotly
"""

import argparse
from pathlib import Path
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from collections import defaultdict

# Οι βασικές μετρικές για τις οποίες θα δημιουργηθεί η αναφορά.
KEY_METRICS = ['wMAPE', 'RMSE', 'R2']
# Κρίσιμοι ορίζοντες για λεπτομερή ανάλυση.
KEY_HORIZONS_FOR_DEEP_DIVE = [10, 288, 2016] # ~50 λεπτά, 1 ημέρα, 1 εβδομάδα (σε βήματα των 5 λεπτών)

def find_summary_files(root_dir: Path) -> list[Path]:
    """Βρίσκει όλα τα αρχεία summary_overall.csv αναδρομικά."""
    print(f"🔍 Σάρωση του καταλόγου '{root_dir}' για αρχεία αποτελεσμάτων...")
    files = list(root_dir.glob("**/summary_overall.csv"))
    print(f"✅ Βρέθηκαν {len(files)} αρχεία summary.")
    return files

def load_and_combine_data(files: list[Path]) -> pd.DataFrame:
    """
    Φορτώνει όλα τα αρχεία summary, προσθέτει metadata από τη διαδρομή του αρχείου,
    και τα συνδυάζει σε ένα ενιαίο DataFrame.
    """
    all_dfs = []
    for file in files:
        try:
            df = pd.read_csv(file)
            parts = file.parts
            df['model'] = parts[-2]
            df['policy'] = parts[-3]
            df['scope'] = parts[-4]
            # Βεβαιωνόμαστε ότι οι βασικές μετρικές υπάρχουν
            for metric in KEY_METRICS:
                if metric not in df.columns:
                    df[metric] = float('nan')
            all_dfs.append(df)
        except Exception as e:
            print(f"⚠️ Προειδοποίηση: Αδυναμία φόρτωσης του αρχείου {file}. Σφάλμα: {e}")

    if not all_dfs:
        return pd.DataFrame()

    combined_df = pd.concat(all_dfs, ignore_index=True)
    print(f"📊 Συγχωνεύθηκαν δεδομένα από {len(all_dfs)} πειράματα. Σύνολο γραμμών: {len(combined_df)}")
    return combined_df

def create_overall_analysis_section(df: pd.DataFrame, metric: str) -> str:
    """Δημιουργεί την ενότητα της γενικής ανάλυσης για μια συγκεκριμένη μετρική."""
    html_parts = [f"<h2>Συνολική Ανάλυση Απόδοσης (Μετρική: {metric})</h2>"]

    # 1. Bar Chart - Μέση Απόδοση
    is_ascending = metric != 'R2' # Για το R2, μεγαλύτερο είναι καλύτερο
    avg_performance = df.groupby('model')[metric].mean().sort_values(ascending=is_ascending).reset_index()
    fig1 = px.bar(
        avg_performance, x='model', y=metric,
        title=f'Μέση Απόδοση Μοντέλων (Βάσει {metric})',
        labels={'model': 'Μοντέλο', metric: f'Μέση Τιμή {metric}'}, text=metric
    )
    fig1.update_traces(texttemplate='%{text:.3f}', textposition='outside')
    html_parts.append(fig1.to_html(full_html=False, include_plotlyjs=False))

    # 2. Heatmap - Απόδοση ανά Ορίζοντα
    heatmap_data = df.pivot_table(index='model', columns='horizon', values=metric, aggfunc='mean')
    color_scale = 'RdYlGn' if is_ascending is False else 'RdYlGn_r'
    fig2 = px.imshow(
        heatmap_data, text_auto=".2f", aspect="auto", color_continuous_scale=color_scale,
        title=f'Απόδοση ({metric}) ανά Μοντέλο και Ορίζοντα Πρόβλεψης'
    )
    html_parts.append(fig2.to_html(full_html=False, include_plotlyjs=False))

    # 3. Table - Καλύτερο Μοντέλο ανά Ορίζοντα
    best_models = df.loc[df.groupby('horizon')[metric].idxmin() if is_ascending else df.groupby('horizon')[metric].idxmax()]
    best_models = best_models[['horizon', 'model', 'policy', 'scope', metric]].sort_values('horizon')
    fig3 = go.Figure(data=[go.Table(
        header=dict(values=['Ορίζοντας', 'Καλύτερο Μοντέλο', 'Πολιτική', 'Scope', f'Score ({metric})']),
        cells=dict(values=[best_models.horizon, best_models.model, best_models.policy, best_models.scope, best_models[metric].round(3)])
    )])
    fig3.update_layout(title_text=f'Καλύτερο Μοντέλο ανά Ορίζοντα (Βάσει {metric})', title_x=0.5)
    html_parts.append(fig3.to_html(full_html=False, include_plotlyjs=False))

    return "".join(html_parts)

def create_per_scope_analysis_section(df: pd.DataFrame, metric: str = 'wMAPE') -> str:
    """Δημιουργεί την ενότητα ανάλυσης για κάθε scope/αισθητήρα."""
    html_parts = [f"<hr><h2>Ανάλυση Απόδοσης ανά Scope/Αισθητήρα (Μετρική: {metric})</h2>"]
    is_ascending = metric != 'R2'

    unique_scopes = sorted(df['scope'].unique())
    for scope in unique_scopes:
        html_parts.append(f"<h3>Scope: {scope}</h3>")
        scope_df = df[df['scope'] == scope].copy()

        if scope_df.empty:
            html_parts.append("<p>Δεν υπάρχουν δεδομένα για αυτό το scope.</p>")
            continue

        avg_perf = scope_df.groupby('model')[metric].mean().sort_values(ascending=is_ascending).reset_index()
        fig = px.bar(
            avg_perf, x='model', y=metric,
            title=f'Κατάταξη Μοντέλων για το Scope "{scope}"',
            labels={'model': 'Μοντέλο', metric: f'Μέση Τιμή {metric}'}, text=metric
        )
        fig.update_traces(texttemplate='%{text:.3f}', textposition='outside')
        html_parts.append(fig.to_html(full_html=False, include_plotlyjs=False))

    return "".join(html_parts)

def create_per_horizon_analysis_section(df: pd.DataFrame, metric: str = 'wMAPE') -> str:
    """Δημιουργεί την ενότητα λεπτομερούς ανάλυσης για κρίσιμους ορίζοντες."""
    html_parts = [f"<hr><h2>Λεπτομερής Ανάλυση ανά Ορίζοντα (Μετρική: {metric})</h2>"]
    is_ascending = metric != 'R2'

    available_horizons = df['horizon'].unique()

    for horizon in KEY_HORIZONS_FOR_DEEP_DIVE:
        if horizon not in available_horizons:
            continue

        html_parts.append(f"<h3>Ορίζοντας Πρόβλεψης: {horizon} βήματα</h3>")
        horizon_df = df[df['horizon'] == horizon].copy()

        perf = horizon_df.groupby(['model', 'scope'])[metric].mean().reset_index().sort_values(by=metric, ascending=is_ascending)
        fig = px.bar(
            perf, x='model', y=metric, color='scope', barmode='group',
            title=f'Σύγκριση Μοντέλων για Ορίζοντα {horizon}',
            labels={'model': 'Μοντέλο', metric: f'Τιμή {metric}', 'scope': 'Scope'}, text=metric
        )
        fig.update_traces(texttemplate='%{text:.3f}', textposition='outside')
        html_parts.append(fig.to_html(full_html=False, include_plotlyjs=False))

    return "".join(html_parts)

def generate_html_report(html_sections: dict, output_path: Path):
    """Συνθέτει τις ενότητες HTML σε ένα ενιαίο αρχείο."""
    print(f"📝 Δημιουργία αναφοράς HTML στο αρχείο: {output_path}")

    with open(output_path, 'w', encoding='utf-8') as f:
        f.write('<html><head><title>Αναφορά Αποτελεσμάτων Πρόβλεψης</title>')
        f.write('<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>')
        f.write('<style>body { font-family: sans-serif; margin: 2em; background-color: #f9f9f9; } h1, h2, h3 { color: #333; text-align: center; } hr { border: 1px solid #ddd; margin: 4em 0; }</style>')
        f.write('</head><body>')
        f.write('<h1>Αναφορά Αποτελεσμάτων Πρόβλεψης Ενέργειας</h1>')

        # Προσθήκη των ενοτήτων με τη σωστή σειρά
        if 'overall_analysis' in html_sections:
            f.write(html_sections['overall_analysis'])
        if 'per_scope_analysis' in html_sections:
            f.write(html_sections['per_scope_analysis'])
        if 'per_horizon_analysis' in html_sections:
            f.write(html_sections['per_horizon_analysis'])

        f.write('</body></html>')
    print("✅ Η αναφορά ολοκληρώθηκε!")

def main():
    parser = argparse.ArgumentParser(description="Δημιουργία προηγμένης, διαδραστικής αναφοράς HTML από τα αποτελέσματα πειραμάτων.")
    parser.add_argument("--root", type=Path, required=True, help="Ο ριζικός κατάλογος που περιέχει τα αποτελέσματα (π.χ., ./results).")
    parser.add_argument("--output", type=Path, required=True, help="Η πλήρης διαδρομή για την αποθήκευση της αναφοράς HTML (π.χ., ./report_v2.html).")
    args = parser.parse_args()

    if not args.root.is_dir():
        print(f"❌ Σφάλμα: Ο κατάλογος '{args.root}' δεν υπάρχει.")
        return

    summary_files = find_summary_files(args.root)
    if not summary_files:
        print("❌ Δεν βρέθηκαν αρχεία 'summary_overall.csv'. Τερματισμός.")
        return

    combined_df = load_and_combine_data(summary_files)
    if combined_df.empty:
        print("❌ Δεν φορτώθηκαν δεδομένα. Τερματισμός.")
        return

    # Δημιουργία των ενοτήτων HTML
    html_sections = defaultdict(str)

    # 1. Συνολική ανάλυση για κάθε μετρική
    for metric in KEY_METRICS:
        if metric in combined_df.columns:
            html_sections['overall_analysis'] += create_overall_analysis_section(combined_df, metric)

    # 2. Ανάλυση ανά scope (χρησιμοποιώντας την κύρια μετρική wMAPE)
    if 'wMAPE' in combined_df.columns:
         html_sections['per_scope_analysis'] = create_per_scope_analysis_section(combined_df, metric='wMAPE')

    # 3. Ανάλυση ανά ορίζοντα (χρησιμοποιώντας την κύρια μετρική wMAPE)
    if 'wMAPE' in combined_df.columns:
        html_sections['per_horizon_analysis'] = create_per_horizon_analysis_section(combined_df, metric='wMAPE')

    # Δημιουργία του τελικού αρχείου HTML
    generate_html_report(html_sections, args.output)

if __name__ == "__main__":
    main()
 No newline at end of file

watts_experiments.yaml

0 → 100644
+114 −0
Original line number Diff line number Diff line
meta:
  project: "ZWave watts experiments - Final Self-Documented Version"
  seed: 42

# =========================================================================
# 1. ΚΕΝΤΡΙΚΟΣ ΟΡΙΣΜΟΣ ΟΡΙΖΟΝΤΩΝ ΠΡΟΒΛΕΨΗΣ
# Κάθε ορίζοντας ορίζεται με το όνομά του, τη διάρκεια σε δευτερόλεπτα
# και μια ανάλυση του επιχειρησιακού του σκοπού.
# =========================================================================
horizons:
  short_term:
    - {name: "5min", seconds: 300, description: "Βελτιστοποίηση επόμενων λεπτών"}
    - {name: "30min", seconds: 1800, description: "Εξισορρόπηση φορτίου (Load balancing)"}
  medium_term:
    - {name: "1h", seconds: 3600, description: "Ωριαίος προγραμματισμός λειτουργιών"}
    - {name: "3h", seconds: 10800, description: "Προετοιμασία για αλλαγές βάρδιας"}
    - {name: "12h", seconds: 43200, description: "Σχεδιασμός μισής ημέρας (π.χ. νυχτερινό τιμολόγιο)"}
  long_term:
    - {name: "1d", seconds: 86400, description: "Ημερήσιες επιχειρησιακές ανάγκες"}
    - {name: "1w", seconds: 604800, description: "Εβδομαδιαίος προγραμματισμός"}
    - {name: "1M", seconds: 2592000, description: "Μηνιαίος σχεδιασμός χωρητικότητας"}

# =========================================================================
# 2. ΣΥΝΟΛΑ ΟΡΙΖΟΝΤΩΝ (PACKAGES ΓΙΑ ΕΥΚΟΛΗ ΧΡΗΣΗ)
# Ορίζουμε "πακέτα" που περιέχουν συνδυασμούς από τους παραπάνω ορίζοντες.
# =========================================================================
horizon_sets:
  quick_test: ["5min", "1h", "1d"]
  daily_operational: ["30min", "1h", "3h", "12h", "1d"]
  strategic_planning: ["1d", "1w", "1M"]
  full_analysis: ["5min", "30min", "1h", "3h", "12h", "1d", "1w", "1M"]

# =========================================================================
# 3. ΠΟΛΙΤΙΚΕΣ ΠΡΟΕΠΕΞΕΡΓΑΣΙΑΣ ΔΕΔΟΜΕΝΩΝ
# Κάθε πολιτική είναι μια "συνταγή" για το πώς προετοιμάζουμε τα δεδομένα
# πριν τα δώσουμε στο μοντέλο. Δοκιμάζοντας διαφορετικές πολιτικές,
# βλέπουμε ποιος τρόπος προετοιμασίας δίνει τα καλύτερα αποτελέσματα.
# =========================================================================
policies:
  # MINIMAL: Η πιο γρήγορη, "γυμνή" προσέγγιση.
  # Φτιάχνει μόνο τα απολύτως απαραίτητα χαρακτηριστικά.
  # Χρήση: Για να έχουμε ένα γρήγορο baseline και να δούμε την απόδοση
  # των μοντέλων χωρίς πολλή βοήθεια από feature engineering.
  minimal_300s:
    resample: "300s" # 5 λεπτά
    clip_quantiles: [0.001, 0.999]
    hampel: { enabled: false }
    fourier: { daily_K: 0, weekly_K: 0 } # Δεν "βλέπει" καθόλου εποχικότητα.
    lags: [1, 5, 10, 30, 60] # "Βλέπει" μόνο μέχρι 5 ώρες πίσω (60 steps * 5 min).

  # STANDARD: Η ισορροπημένη, "πλήρης" προσέγγιση.
  # Περιλαμβάνει όλες τις βασικές τεχνικές feature engineering.
  # Χρήση: Η κύρια πολιτική μας για τα περισσότερα πειράματα.
  standard_300s:
    resample: "300s" # 5 λεπτά
    clip_quantiles: [0.01, 0.99]
    hampel: { enabled: true, window: 25, sigma: 3 }
    fourier: { daily_K: 5, weekly_K: 1 } # "Βλέπει" την ημερήσια και εβδομαδιαία εποχικότητα.
    lags: [1, 5, 10, 30, 60, 288, 2016] # "Βλέπει" μέχρι και 1 εβδομάδα πίσω (2016 steps * 5 min).

  # RICH FEATURES: Η "all-in" προσέγγιση για δενδρο-βασισμένα μοντέλα.
  # Προσθέτει και κυλιόμενες στατιστικές (rolling stats), που είναι πολύ ισχυρά χαρακτηριστικά.
  # Χρήση: Για να δούμε αν τα μοντέλα όπως LGBM/XGB μπορούν να εκμεταλλευτούν
  # ακόμα περισσότερη πληροφορία για να βελτιώσουν την ακρίβειά τους.
  rich_features_300s:
    resample: "300s" # 5 λεπτά
    clip_quantiles: [0.01, 0.99]
    hampel: { enabled: true, window: 25, sigma: 3 }
    fourier: { daily_K: 5, weekly_K: 1 }
    lags: [1, 5, 10, 30, 60, 288, 2016]
    rolls: [12, 24, 288] # ΝΕΟ: "Βλέπει" τον μέσο όρο, std, min, max της τελευταίας ώρας (12*5m), 2 ωρών (24*5m) και 1 ημέρας (288*5m).

  # LINEAR FRIENDLY: Ειδική πολιτική για γραμμικά μοντέλα (Ridge, ElasticNet).
  # Αποφεύγει χαρακτηριστικά που μπορεί να προκαλέσουν προβλήματα σε αυτά τα μοντέλα.
  # Χρήση: Για να δώσουμε στα γραμμικά μοντέλα το καλύτερο δυνατό περιβάλλον για να αποδώσουν.
  linear_friendly_300s:
    resample: "300s" # 5 λεπτά
    clip_quantiles: [0.01, 0.99]
    hampel: { enabled: true, window: 25, sigma: 3 }
    fourier: { daily_K: 0, weekly_K: 0 } # Αποφεύγουμε τα Fourier terms που μπορεί να προκαλέσουν πολυσυγγραμμικότητα.
    lags: [1, 5, 10, 288] # Χρησιμοποιούμε μόνο λίγα, σημαντικά lags.

  # STANDARD 60s: Η standard προσέγγιση σε δεδομένα υψηλότερης ανάλυσης.
  # Ίδια λογική με το standard_300s, αλλά για δεδομένα ανά 1 λεπτό.
  # Χρήση: Για να εξετάσουμε πώς η αύξηση της συχνότητας δειγματοληψίας επηρεάζει την πρόβλεψη.
  standard_60s:
    resample: "60s" # 1 λεπτό
    clip_quantiles: [0.01, 0.99]
    hampel: { enabled: true, window: 50, sigma: 3 } # Μεγαλύτερο window λόγω περισσότερων σημείων
    fourier: { daily_K: 5, weekly_K: 1 }
    lags: [1, 5, 10, 30, 60, 1440] # "Βλέπει" μέχρι και 1 ημέρα πίσω (1440 steps * 1 min).

# =========================================================================
# 4. ΡΥΘΜΙΣΕΙΣ ΜΟΝΤΕΛΩΝ
# =========================================================================
models:
  lgbm:
    num_leaves: 64
    learning_rate: 0.05
    n_estimators: 2000
  xgb:
    n_estimators: 800
    learning_rate: 0.05
    max_depth: 8
  # ... (κτλ. για τα υπόλοιπα μοντέλα)

# =========================================================================
# 5. ΡΥΘΜΙΣΕΙΣ BACKTESTING
# =========================================================================
backtest:
  horizon_set: "full_analysis"
  folds: 3
  ratios: [0.7, 0.1, 0.2]
 No newline at end of file