--- license: mit library_name: sklearn pipeline_tag: tabular-classification tags: - ml-lab - scikit-learn - predictive-maintenance - time-series-classification - iot - synthetic-data datasets: [] metrics: - f1 - precision - recall - pr_auc ml_lab: title: Elevator Predictive Maintenance order: 15 summary: Predicts from the last 60 minutes of 11 elevator sensors whether the elevator will be failing 10 minutes later (trained on synthetic data). framework: scikit-learn architecture: RandomForest (300 trees) on 63 window features incl. FFT dataset: Synthetic elevator IoT month (seeded generator) space: shalev396/elevator-maintenance runtime: cpu-basic ui_kind: custom colab: https://colab.research.google.com/github/shalev396/ml-lab/blob/main/elevator-maintenance/training/notebook.ipynb github: https://github.com/shalev396/ml-lab/tree/main/elevator-maintenance metric: name: f1 value: 0.988235 split: test params: 10408 params_human: 10.41K trained_at: '2026-09-25' model-index: - name: elevator-maintenance results: - task: type: tabular-classification dataset: name: Synthetic elevator IoT month (seeded generator) type: synthetic-elevator-iot-month-seeded-generator split: test metrics: - type: f1 value: 0.988235 name: f1 - type: precision value: 0.988235 name: precision - type: recall value: 0.988235 name: recall - type: accuracy value: 0.996983 name: accuracy - type: pr_auc value: 0.988996 name: pr_auc - type: roc_auc value: 0.992683 name: roc_auc --- # Elevator Predictive Maintenance > ⚠️ **Synthetic data.** This model was trained and tested on a **seeded synthetic month** of elevator > sensor readings, not on data from a real elevator. The public Kaggle set usually linked to this task > (`shivamb/elevator-predictive-maintenance-dataset`) has no failure label, so the training code generates a > month that matches the brief. The numbers below say how well the model learns that generator's degradation > pattern. They say nothing about real elevators. From the **last 60 minutes** of 11 elevator sensors (one reading per minute), the model predicts whether the elevator will be in a **degradation/failure state 10 minutes later**. It is a RandomForest (300 trees) on 63 features computed from the window. On the held-out test days of the synthetic month it scores F1 = 0.988 (168 of 170 failure windows caught, 2 false alarms among 1,156 healthy windows) at its tuned threshold of 0.38 (run of 2026-09-25, `metrics.json`). It beat a Keras 1D-CNN and a Keras LSTM trained on the raw windows. ## Model - **Input**: at least 60 rows (one per minute) with the columns `temperature, humidity, vibration_rms, vibration_peak, motor_current, motor_rpm, door_cycles, load_kg, acoustic_db, oil_level, power_kw`, as a CSV path, CSV text, a DataFrame or a list of row dicts. The last 60 rows are used. An optional `timestamp` column gives the time of the window end; without it a weekday noon is assumed. - **Features** (`model.window_features`, 63 in total): mean, std, min, max and least-squares slope of each sensor over the window (55), the top-5 FFT magnitudes of `vibration_rms` (DC dropped) and its dominant frequency bin (6), and the hour and day of week of the window end (2). - **Classifier** (`rf.joblib`): scikit-learn `RandomForestClassifier`, 300 trees, `class_weight="balanced"`, default depth, 10,408 tree nodes in total. P(failure) is the trees' vote share. - **Decision**: flag the elevator for maintenance when P(failure) >= 0.377, the threshold that maximised F1 on the validation days (`config.json` → `threshold`). - **Output** of `predict(data)`: `{"failure": p, "healthy": 1 - p}`. - `config.json` holds the sensors, window length, label lag, feature names, threshold and library versions. `model.py` reads it all. No scaler is needed: trees are insensitive to feature scale. ## Usage ```python from huggingface_hub import hf_hub_download, snapshot_download import sys path = snapshot_download("shalev396/elevator-maintenance") sys.path.insert(0, path) import model predictor = model.load(path, device="cpu") # CPU only (scikit-learn) csv = hf_hub_download("shalev396/elevator-maintenance", "examples/degrading.csv", repo_type="space") p = predictor.predict(csv) # {"failure": 0.92, "healthy": 0.08} print(p, "maintenance" if p["failure"] >= predictor.threshold else "healthy") ``` Or call the hosted API: see the [Space](https://huggingface.co/spaces/shalev396/elevator-maintenance) (`POST /gradio_api/call/predict` with the CSV file). **Inference Endpoint**: `handler.py` makes this repo deployable from its page (Deploy → Inference Endpoints). Send `{"inputs": "= 60 rows>"}` (or a list of row dicts, or `{column: [values]}`); the response is `{"failure": p, "healthy": 1 - p}`. ## Training - **Data**: a seeded synthetic month (`training/src/data_setup.py`, seed 42): 31 days x 1,440 minutes = 44,640 rows. A daily usage profile (morning, lunch and evening peaks, quiet nights, weekends at 35 %) drives all sensors, plus noise. Five degradation episodes add 6-14 h of linear drift (vibration +1.8, peak vibration +4.5, motor current +5 A, temperature +7 °C, noise +9 dB, power +2 kW, rpm -90, faster oil loss), then a 45-120 min failure, then a maintenance reset. `Status = 1` during degradation + failure: 8.5 % of minutes. The anchors put three episodes in train and one each in validation and test. - **Split**: by time, 70/15/15 of the rows (train to 22 Jan 16:48, validation to 27 Jan 08:24, test to the end). Windows: 60 min, label = `Status` 10 min after the window, stride 5 min. A window and its label must lie in the same segment, so nothing crosses a boundary. This gives 6,236 / 1,326 / 1,326 windows with 413 / 178 / 170 failure windows. - **Experiments**: the RandomForest on the 63 features, and two Keras nets on the raw windows (standardised with a scaler fit on train rows): a 1D-CNN (Conv1D 64 → MaxPool → Conv1D 128 → GlobalMaxPool → Dense 64 → Dropout 0.3 → sigmoid) and an LSTM (64 units → Dense 64 → Dropout 0.3 → sigmoid). The nets use Adam (lr 1e-3), batch 128, class weights (neg/pos), up to 8 epochs and early stopping on validation PR-AUC. - **Selection**: each model's threshold is tuned for max F1 on validation (searched in [0.05, 0.95]). The model with the highest **validation F1** is deployed. The test segment is scored once, afterwards. - **Hardware**: local desktop CPU; the whole notebook ran in 248 s (forest fit 2.9 s). Code: [training/](https://github.com/shalev396/ml-lab/tree/main/elevator-maintenance/training) · [Colab](https://colab.research.google.com/github/shalev396/ml-lab/blob/main/elevator-maintenance/training/notebook.ipynb) ## Evaluation Deployed RandomForest on the 1,326 test windows (the last 4.65 days, one unseen episode), threshold 0.377: | metric (test) | value | |---|---| | f1 | 0.9882 | | precision | 0.9882 | | recall | 0.9882 | | accuracy | 0.9970 | | pr_auc | 0.9890 | | roc_auc | 0.9927 | Confusion (test): 168 true alarms, 2 missed failure windows, 2 false alarms, 1,154 correct "healthy". ## Experiments Every variant, ranked in training order; bold = deployed. Size is weights for the Keras nets and tree nodes for the forest. | experiment | size | val F1 | threshold | test F1 | test precision | test recall | test PR-AUC | train time | |---|---|---|---|---|---|---|---|---| | **RandomForest** (deployed) | 10,408 tree nodes | **0.9888** | **0.377** | **0.9882** | **0.9882** | **0.9882** | **0.9890** | 2.9 s | | CNN-1D | 52,993 weights | 0.9117 | 0.189 | 0.9581 | 0.9756 | 0.9412 | 0.9380 | 64.5 s | | LSTM | 23,681 weights | 0.9375 | 0.166 | 0.9329 | 0.9684 | 0.9000 | 0.9200 | 146.1 s | - The forest wins on validation (F1 0.989 vs 0.938 for the LSTM and 0.912 for the CNN) and on test. Rolling means, slopes and maxima capture a slow drift almost directly, which suits the synthetic generator's linear degradation ramps. - Both nets reached their best validation PR-AUC in epoch 1 and then overfit; early stopping restored the epoch-1 weights. - The Keras nets are not exported: serving the forest keeps the Space free of TensorFlow. - The same metrics came out of the earlier version of this project (F1 0.988 / 0.958 / 0.933), which used the same seed and data. ![Test metrics of every experiment](assets/model_comparison.png) ![Keras training curves](assets/training_curves.png) ![Precision-recall curves on the test segment](assets/pr_curves.png) ![Confusion matrix of the deployed model](assets/confusion_matrix.png) ![Predicted failure probability over the whole month](assets/timeline.png) ![RandomForest feature importance](assets/feature_importance.png) The timeline shows the train episodes at a flat 1.0: those windows were in the training set, so the forest fits them perfectly. Only the orange (validation) and green (test) parts are out of sample. ## Limitations - **Synthetic data.** The generator, not a real elevator, defines what "degradation" looks like: smooth linear drifts in several sensors at once. Real failures are noisier, rarer and more varied. Do not use this model to make maintenance decisions. - **One episode per evaluation split.** Validation and test each hold a single degradation episode, so the scores rest on 178 and 170 overlapping failure windows (stride 5 on a 60-min window). One different episode could move them by several points. - The label covers the whole 6-14 h drift, so "failure in 10 minutes" in practice means "inside a degradation episode"; the model sees the drift itself, not an early warning hours ahead. - Hour and day-of-week features encode the generator's usage schedule. Uploads without a `timestamp` column are scored as weekday noon. - The window must be sampled once per minute, in the same units as the training data.