Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41,408 changes: 21,051 additions & 20,357 deletions 01_Analyse_exploratoire+Features_engineering.ipynb

Large diffs are not rendered by default.

1,240 changes: 816 additions & 424 deletions 02_modelistation_supervisee.ipynb

Large diffs are not rendered by default.

3,224 changes: 1,596 additions & 1,628 deletions 2016_Building_Energy_Benchmarking_nonresidential.csv

Large diffs are not rendered by default.

3,224 changes: 1,596 additions & 1,628 deletions ML_modele.csv

Large diffs are not rendered by default.

2,904 changes: 1,437 additions & 1,467 deletions ML_modele_iqr_cat.csv

Large diffs are not rendered by default.

7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
<p style="text-align: center;">
<img src="logo_OCR.jpg" alt="Logo Academy" width="100">
</p>
#Anticipez_les_besoins_en_consommation_de_batiments
Projet ML pour prédire la consommation d’énergie des bâtiments. Parcours complet : exploration et nettoyage des données, préparation des variables, entraînement et comparaison de modèles, sélection du meilleur, sauvegarde, API de prédiction, tests et exemples.


14 changes: 14 additions & 0 deletions bentofile.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
service: "service:EnergyAPI"

include:
- "service.py"

python:
packages:
- numpy
- pandas
- scikit-learn==1.5.2 # même version que le training
- bentoml==1.4.25

models:
- "energy_model:latest"
8 changes: 8 additions & 0 deletions check_keys.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import json, bentoml
ref = bentoml.models.get("energy_model:latest")
expected = list(ref.custom_objects["feature_names"])
with open("request.json","r",encoding="utf-8") as f:
got = list(json.load(f).keys())
print("== same length? ", len(expected)==len(got), len(expected))
print("Missing:", sorted(set(expected)-set(got)))
print("Extra :", sorted(set(got)-set(expected)))
51 changes: 51 additions & 0 deletions make.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
param([Parameter(Position=0)][string]$Task="help")

function Train { python .\train_and_save.py; bentoml models list }
function Build { bentoml build; bentoml containerize energy_api:latest -t energy_api:latest }
function Run {
docker ps -q --filter "publish=3000" | ForEach-Object { docker rm -f $_ } | Out-Null
docker rm -f energy_api 2>$null | Out-Null
docker run -d --name energy_api -p 3000:3000 energy_api:latest
docker ps --filter "name=energy_api"
}

function Logs { docker logs -f energy_api }
function Stop { docker rm -f energy_api }
function Inspect {
$payload = Get-Content .\request.json -Raw | ConvertFrom-Json
$json = (@{ payload = $payload } | ConvertTo-Json -Depth 10 -Compress)
$enc = New-Object System.Text.UTF8Encoding($false) # UTF-8 SANS BOM
$bytes = $enc.GetBytes($json)
Invoke-RestMethod -Uri "http://localhost:3000/inspect" -Method Post -ContentType "application/json; charset=utf-8" -Body $bytes
}
function Predict {
$payload = Get-Content .\request.json -Raw | ConvertFrom-Json
$json = (@{ payload = $payload } | ConvertTo-Json -Depth 10 -Compress)
$enc = New-Object System.Text.UTF8Encoding($false) # UTF-8 SANS BOM
$bytes = $enc.GetBytes($json)
Invoke-RestMethod -Uri "http://localhost:3000/predict" -Method Post -ContentType "application/json; charset=utf-8" -Body $bytes
}function Help {
@"
Usage: .\make.ps1 <task>

Tasks:
train — entraîne + sauvegarde le modèle
build — bento build + docker image
run — lance le conteneur en arrière-plan (port 3000)
logs — suit les logs du conteneur
stop — stoppe & supprime le conteneur
inspect — envoie request.json à /inspect (enveloppe auto sans BOM)
predict — envoie request.json à /predict (enveloppe auto sans BOM)
"@
}

switch ($Task) {
"train" { Train }
"build" { Build }
"run" { Run }
"logs" { Logs }
"stop" { Stop }
"inspect" { Inspect }
"predict" { Predict }
default { Help }
}
53 changes: 53 additions & 0 deletions make_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import json, bentoml

ref = bentoml.models.get("energy_model:latest")
features = list(ref.custom_objects.get("feature_names", []))

payload = {}
for f in features:
lf = f.lower()

# one-hot PropertyCategory_*
if f.startswith("PropertyCategory_"):
payload[f] = 1 if f == "PropertyCategory_Office" else 0
continue

# ratios
if "ratio" in lf or "fraction" in lf or "share" in lf:
payload[f] = 0.7
continue

# surfaces / GFA
if any(k in lf for k in ["gfa","area","surface","size","sqft","sf"]):
if "parking" in lf: payload[f] = 30000
elif "second" in lf: payload[f] = 40000
elif "largest" in lf: payload[f] = 180000
elif "building(s" in lf or "building_s" in lf: payload[f] = 220000
else: payload[f] = 250000
continue

# étages
if "floor" in lf or "floors" in lf or "story" in lf:
payload[f] = 5
continue

# âge / années
if "âge" in f.lower() or "age" in lf or "year" in lf or "years" in lf:
payload[f] = 40
continue

# nombre de bâtiments
if "numberofbuildings" in lf or "nbuildings" in lf or "buildings" in lf:
payload[f] = 1
continue

# défaut
payload[f] = 100.0

# ASSURE la présence explicite de la clé accentuée
payload.setdefault("Surface moyenne par étage", 250000)

with open("request.json","w",encoding="utf-8") as f:
json.dump(payload, f, ensure_ascii=False, indent=2)

print("OK -> request.json (", len(payload), "keys )")
107 changes: 107 additions & 0 deletions raccourci_cmd_cleansheet.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
1 venv311 : .\.venv311\Scripts\Activate.ps1

2 lancer le serveur dans le venv311 : python -m bentoml serve service:svc --reload

3 ouvrir le swagger : http://127.0.0.1:3000

4 Regénère un request.json propre (0/1 pour les dummies)

Exécute dans la racine du projet (venv activé) :

@'
import json, bentoml, unicodedata as ud

ref = bentoml.models.get("energy_model:latest")
features = list(ref.custom_objects.get("feature_names", []))

def default_value(name: str):
n = name.lower()
# dummies
if name.startswith("PropertyCategory_"):
return 1 if name == "PropertyCategory_Office" else 0
# ratios
if "ratio" in n or "fraction" in n or "share" in n:
return 0.7
# surfaces / gfa
if any(k in n for k in ["gfa","area","surface","size","sqft","sf"]):
if "parking" in n: return 30000
if "second" in n: return 40000
if "largest" in n: return 180000
if "building(s" in n or "building_s" in n: return 220000
return 250000
# étages
if "floor" in n or "floors" in n or "story" in n:
return 5
# âge / années
if "age" in n or "année" in n or "an " in n or "year" in n or "years" in n:
return 40
# nombre de bâtiments
if "numberofbuildings" in n or "nbuildings" in n or "buildings" in n:
return 1
# défaut
return 100.0

# construit le payload EN UTILISANT EXACTEMENT les clés du modèle
payload = { name: default_value(name) for name in features }

with open("request.json","w",encoding="utf-8") as f:
json.dump(payload, f, ensure_ascii=False, indent=2)

print("OK -> request.json aligné, nb clés:", len(payload))
'@ | Out-File -Encoding utf8 rebuild_payload.py

python .\rebuild_payload.py
type .\request.json


DOCKER



Tag Size Model Size Creation Time
energy_api:2l7mkre5qo7n3l7m 42.29 KiB 0.00 B 2025-09-30 00:29:56



Successfully built Bento(tag="energy_api:72idth45rc3hhl7m").







2) Lance l’inspect & la prédiction
.\make.ps1 inspect
.\make.ps1 predict

Option rapide (sans éditer de fichier)

Tu peux envoyer un bâtiment “à la volée” :

$payload = @{
BuildingAge = 40; NumberofBuildings = 1; NumberofFloors = 5
PropertyGFATotal = 250000; PropertyGFAParking = 30000; "PropertyGFABuilding(s)" = 220000
LargestPropertyUseTypeGFA = 180000; SecondLargestPropertyUseTypeGFA = 40000
PropertyCategory = "Office"
}
$json = (@{ payload = $payload } | ConvertTo-Json -Depth 10 -Compress)
$enc = New-Object System.Text.UTF8Encoding($false)
$bytes = $enc.GetBytes($json)
Invoke-RestMethod -Uri "http://localhost:3000/predict" -Method Post -ContentType "application/json; charset=utf-8" -Body $bytes




1) Pré-prod locale (checklist rapide)

Rebuild si tu changes service.py ou réentraînes :

.\make.ps1 train # si tu réentraînes
.\make.ps1 build
.\make.ps1 run
.\make.ps1 health
.\make.ps1 predict


Garde un request.json “type” (celui qui marche) dans le repo.
39 changes: 39 additions & 0 deletions rebuild_payload.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import json, bentoml, unicodedata as ud

ref = bentoml.models.get("energy_model:latest")
features = list(ref.custom_objects.get("feature_names", []))

def default_value(name: str):
n = name.lower()
# dummies
if name.startswith("PropertyCategory_"):
return 1 if name == "PropertyCategory_Office" else 0
# ratios
if "ratio" in n or "fraction" in n or "share" in n:
return 0.7
# surfaces / gfa
if any(k in n for k in ["gfa","area","surface","size","sqft","sf"]):
if "parking" in n: return 30000
if "second" in n: return 40000
if "largest" in n: return 180000
if "building(s" in n or "building_s" in n: return 220000
return 250000
# étages
if "floor" in n or "floors" in n or "story" in n:
return 5
# âge / années
if "age" in n or "année" in n or "an " in n or "year" in n or "years" in n:
return 40
# nombre de bâtiments
if "numberofbuildings" in n or "nbuildings" in n or "buildings" in n:
return 1
# défaut
return 100.0

# construit le payload EN UTILISANT EXACTEMENT les clés du modèle
payload = { name: default_value(name) for name in features }

with open("request.json","w",encoding="utf-8") as f:
json.dump(payload, f, ensure_ascii=False, indent=2)

print("OK -> request.json aligné, nb clés:", len(payload))
13 changes: 13 additions & 0 deletions request.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"BuildingAge": 40,
"Surface moyenne par étage": 44000,
"Ratio Largest Use / Total": 0.81818,
"NumberofBuildings": 1,
"NumberofFloors": 5,
"PropertyGFATotal": 250000,
"PropertyGFAParking": 30000,
"PropertyGFABuilding(s)": 220000,
"LargestPropertyUseTypeGFA": 180000,
"SecondLargestPropertyUseTypeGFA": 40000,
"PropertyCategory": "Office"
}
20 changes: 20 additions & 0 deletions request_wrapped.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{"payload": {
"BuildingAge": 40,
"Surface moyenne par étage": 250000,
"Ratio Largest Use / Total": 0.7,
"NumberofBuildings": 1,
"NumberofFloors": 5,
"PropertyGFATotal": 250000,
"PropertyGFAParking": 30000,
"PropertyGFABuilding(s)": 220000,
"LargestPropertyUseTypeGFA": 180000,
"SecondLargestPropertyUseTypeGFA": 40000,
"PropertyCategory_Healthcare": 0,
"PropertyCategory_Hospitality": 0,
"PropertyCategory_Industrial & Storage": 0,
"PropertyCategory_Mixed Use": 0,
"PropertyCategory_Office": 1,
"PropertyCategory_Other": 0,
"PropertyCategory_Public / Institutional": 0,
"PropertyCategory_Retail & Food": 0
} }
41 changes: 41 additions & 0 deletions sanity_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# sanity_check.py
import json, pandas as pd, numpy as np, bentoml

CSV_PATH = "ML_modele_iqr_cat.csv"
TARGET = "SiteEnergyUse(kBtu)"
FEATURES = [
"BuildingAge","Surface moyenne par étage","Ratio Largest Use / Total",
"NumberofBuildings","NumberofFloors","PropertyGFATotal","PropertyGFAParking",
"PropertyGFABuilding(s)","LargestPropertyUseTypeGFA","SecondLargestPropertyUseTypeGFA",
"PropertyCategory",
]

# 1) repères
df = pd.read_csv(CSV_PATH)
q = df[TARGET].quantile([0.05,0.25,0.5,0.75,0.95]).to_dict()
print("[Target quantiles kBtu]", {int(k*100): float(v) for k,v in q.items()})

# 2) modèle
ref = bentoml.models.get("energy_model:latest")
model = bentoml.sklearn.load_model(ref)
md = ref.info.metadata or {}
use_log = bool(md.get("use_log_target", False))

# 3) payload
with open("request.json","r",encoding="utf-8") as f:
p = json.load(f)

# reconstruire la catégorie si one-hot
if "PropertyCategory" not in p:
cat_cols = [c for c in p if c.startswith("PropertyCategory_")]
if not cat_cols:
raise SystemExit("Aucune colonne PropertyCategory_*.")
best = max(cat_cols, key=lambda c: float(p.get(c,0) or 0))
p["PropertyCategory"] = best.split("PropertyCategory_",1)[1]

X = pd.DataFrame([p], columns=FEATURES)
y = model.predict(X)
if use_log: y = np.expm1(y)

print("[Request used]", X.iloc[0].to_dict())
print("[Prediction kBtu]", float(np.ravel(y)[0]))
Loading