Proof of Concept software — simulazione locale del ciclo IoT sense → think → act, senza hardware reale
Autori: Vellotti Gianmarco, Terrecuso Francesco Corso: Internet of Things — Università degli Studi di Napoli Federico II Modello ML di partenza: TomatoCNN (progetto P14, esame di Neural Networks)
Realizzare un Proof of Concept (PoC) interamente software che simuli una serra domotica intelligente per il pomodoro, capace di:
- "Osservare" le foglie tramite una fotocamera simulata (che pesca immagini reali dal dataset PlantVillage-Tomato già usato per l'addestramento);
- Classificare lo stato di salute della pianta con la CNN già addestrata (10 classi, incluso "healthy"), compressa per l'esecuzione su hardware a risorse limitate (Raspberry Pi);
- Simulare sensori ambientali (temperatura, umidità, luminosità, umidità del suolo) e un agente decisionale che, combinando la diagnosi della CNN con il contesto ambientale, aziona attuatori simulati (irrigazione, ventilazione, allarme);
- Far girare tutto questo su un normale PC, che nel PoC "si finge" un Raspberry Pi (niente hardware reale, niente sensori fisici).
Non si tratta quindi di un deployment reale, ma di una simulazione end-to-end del ciclo sense → think → act tipico di un sistema IoT/Edge AI, usando dati reali del dataset al posto degli stimoli fisici.
Il PoC riusa direttamente il lavoro già fatto nell'esame di Neural Networks, che ha confrontato una FCNN e una CNN sullo stesso task:
| FCNN [2L-512-ReLU] | CNN [64f-k3-3blk] | |
|---|---|---|
| Accuracy (holdout 80/20) | ~84% | ~96% |
| Accuracy media (Stratified 5-Fold, Weighted CE) | — | 94.98% (σ < 3.2%, range 93.32–96.52%) |
| Recall minimo per classe | 0.69 (Target Spot) | 0.90 |
| Punto debole | Non percepisce la struttura spaziale → confonde Early blight/Late blight/Bacterial spot | Classi rare (Tomato mosaic virus, 299 img) restano le più critiche |
La rete da portare in edge è quindi la CNN, nella configurazione migliore individuata in Fase 1 e validata in Fase 2:
# model.py — configurazione da usare per il PoC
model = TomatoCNN(
n_filters=64,
kernel_size=3,
num_blocks=3,
num_classes=10
)
model.load_state_dict(torch.load("best_cnn_64f_k3_3blk.pth"))Pipeline di input da rispettare (identica al training, altrimenti la rete non funziona in inferenza):
- resize a 64×64, RGB (3×64×64 = 12.288 valori)
- normalizzazione ImageNet:
mean=[0.485, 0.456, 0.406],std=[0.229, 0.224, 0.225] - nessuna augmentation in inferenza (flip/rotazione solo in training)
Classi critiche da tenere d'occhio nel PoC (per scegliere immagini di test "interessanti" da mostrare in demo): Early blight (confusa con Late blight) e Tomato mosaic virus (classe più rara, recall più basso).
Riprendendo il modello IoT World Forum visto a lezione, il PoC copre così i livelli:
| Livello IoTWF | Nel PoC reale (target) | Nel PoC implementato (software) |
|---|---|---|
| 1. Physical Devices & Controllers | Fotocamera, sensori DHT22/igrometro, pompa, ventola | Moduli Python mock (VirtualCameraSensor, EnvironmentSensorSimulator, FakeGPIO) |
| 2. Connectivity | Wi-Fi del Raspberry Pi | Loopback locale (comunicazione in-memory tra moduli Python via interfacce strutturate) |
| 3. Edge (Fog) Computing | Inferenza CNN quantizzata sul Raspberry Pi | Inferenza CNN quantizzata su CPU (con vincoli di risorse simulati: 1 core, solo CPU) |
| 4. Data Accumulation | Storage locale su SD card | Log CSV strutturati (logs/run_*.csv) |
| 5. Data Abstraction | Normalizzazione dei dati per il livello applicativo | Classi e dataclass Python (SensorReading, InferenceResult, AgentDecision) |
| 6. Application | Dashboard di monitoraggio | Dashboard interattiva real-time (Streamlit + Plotly in dashboard/app_streamlit.py) |
| 7. Collaboration & Processes | Notifiche all'agricoltore, integrazione gestionale | Alert critici e notifiche loggate ed esposte in UI (NotificationActuator) |
Questo mapping è utile anche per la relazione/presentazione del progetto, perché lega esplicitamente le scelte implementative alla teoria del corso.
┌───────────────────────────────────────────────────────────────────────┐
│ "RASPBERRY PI VIRTUALE" (processo Python) │
│ │
│ ┌────────────────┐ ┌──────────────────┐ ┌───────────────────┐ │
│ │ Sensor Layer │ │ Edge AI Layer │ │ Actuator Layer │ │
│ │ (simulato) │───▶│ (CNN compressa) │───▶│ (simulato) │ │
│ │ │ │ │ │ │ │
│ │ • Virtual Cam │ │ • Preprocessing │ │ • Irrigazione │ │
│ │ • Temp/Umidità │ │ • Inferenza │ │ • Ventilazione │ │
│ │ • Umidità suolo │ │ • Post-proc. │ │ • Allarme/Alert │ │
│ │ • Luminosità │ │ (softmax→label)│ │ • Notifica log │ │
│ └────────────────┘ └──────────────────┘ └───────────────────┘ │
│ │ │ ▲ │
│ └──────────────────────┼───────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ Agente Decisionale │ │
│ │ (regole PEAS) │ │
│ └─────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ Logging / Dashboard │ │
│ └─────────────────────────┘ │
└───────────────────────────────────────────────────────────────────────┘
Virtual Camera (VirtualCameraSensor)
- Ad ogni ciclo, pesca in modo pseudo-casuale un'immagine dalla cartella del dataset PlantVillage-Tomato (riusando lo split di test, per non "barare" con immagini viste in training).
- Restituisce: immagine (tensor), classe reale (per calcolare accuratezza live nel PoC), timestamp.
- Parametro opzionale:
bias_classper forzare la simulazione a pescare più spesso da una classe specifica (utile per demo mirate, es. simulare un'epidemia di Early blight).
Sensori ambientali (EnvironmentSensorSimulator)
- Genera in modo sintetico: temperatura (°C), umidità relativa (%), umidità del suolo (%), luminosità (lux).
- Per rendere la simulazione plausibile e "raccontabile" nella relazione, i valori non sono puramente casuali ma correlati euristicamente alla patologia estratta dalla foto (es. umidità alta → più probabile la patologia fungina Early/Late blight; questo riprende il concetto di Data Fusion visto a lezione: unire dati eterogenei — immagine + ambiente — in un'unica rappresentazione logica per la decisione).
class SensorReading:
timestamp: float
image_path: str
true_label: str # per validazione nel PoC
temperature_c: float
humidity_pct: float
soil_moisture_pct: float
light_lux: float- Carica il modello compresso (vedi §4) all'avvio, una sola volta.
- Applica lo stesso preprocessing del training (resize 64×64 + normalizzazione ImageNet).
- Esegue l'inferenza e restituisce: classe predetta, confidenza (softmax), tempo di inferenza (ms) — utile per le metriche di performance edge.
- Deve poter girare in due modalità, selezionabili da config, per confrontarle
nella relazione:
full_precision→ modello originale.pth(baseline)optimized→ modello quantizzato/pruned (target edge)
Qui si applica direttamente il modello dell'agente razionale visto negli appunti IoT: si definisce esplicitamente il PEAS del sistema.
| Componente PEAS | Definizione nel PoC |
|---|---|
| Performance | Diagnosi corrette, tempo di reazione, risparmio idrico, riduzione falsi allarmi |
| Environment | Serra simulata (parzialmente osservabile, dinamica, continua per i sensori ambientali, discreta per le classi patologiche) |
| Actuators | Pompa irrigazione, ventola, LED di allarme, notifica |
| Sensors | Fotocamera virtuale, sensori ambientali simulati |
L'agente è del tipo reattivo basato su modello + basato su obiettivi: mantiene un piccolo stato interno (es. "N. rilevamenti Early blight consecutivi") e decide le azioni in base a regole condizione→azione, per esempio:
SE predizione == "healthy" → nessuna azione
SE predizione == malattia fungina (Early/Late blight)
E umidità > soglia → attiva ventilazione + alert
SE predizione == malattia virale (es. TYLCV) → alert "possibile vettore insetto"
SE soil_moisture < soglia → attiva irrigazione
SE confidenza_modello < soglia_minima → alert "richiesta ispezione umana"
(human-in-the-loop, come da
Service Layer dello stack IoT)
Ogni attuatore è una semplice classe con metodo activate()/deactivate() che
al posto di pilotare un pin GPIO reale stampa/logga l'azione:
class SimulatedActuator:
def activate(self, reason: str): ...
def deactivate(self): ...Per rendere il PoC più "vicino" a un reale deployment Raspberry Pi, si può
usare una libreria di mock delle GPIO (es. RPi.GPIO sostituito da un modulo
fittizio con la stessa interfaccia), così il codice è già pronto per essere
spostato su hardware reale senza riscritture.
Ciclo sense → think → act → log, eseguito a intervalli regolari (es. ogni
5–10 secondi simulati, accelerabile per demo):
while True:
reading = camera.capture()
env = env_sensors.read()
prediction = edge_model.infer(reading.image)
action = agent.decide(prediction, env)
actuators.apply(action)
logger.log(reading, env, prediction, action)
time.sleep(CYCLE_INTERVAL)- Log strutturato CSV (
logs/run_*.csv): ad ogni ciclo l'orchestratore registra tutte le metriche del sistema (timestamp, ground truth, predizione, confidenza, tempi di inferenza, CPU/RAM, valori ambientali, stato attuatori e ragionamento dell'agente). - Dashboard real-time con Streamlit e Plotly (
dashboard/app_streamlit.py): interfaccia grafica avanzata che esegue e visualizza il ciclo integrato sense-think-act. Include:- Visualizzazione live della foto analizzata e barra delle probabilità softmax (10 classi);
- Gauge chart interattivi per i sensori ambientali e mini-trend nel tempo;
- Indicatori LED di stato per il banco attuatori (
ActuatorBank) e log del ragionamento PEAS; - System Monitor interattivo (CPU %, RAM, latenza ms) per il monitoraggio dei vincoli Edge;
- Grafici di analisi temporale (accuracy cumulativa, distribuzione predizioni, istogramma latenze, radar sensori);
- Tabella storico degli ultimi 30 cicli.
Obiettivo: partire dal miglior checkpoint (.pth, CNN 64f-k3-3blk, ~96%/94.98%)
e produrre una versione più leggera/veloce, misurando il trade-off
accuracy/dimensione/latenza — è il cuore "tecnico" della parte Edge AI.
- Unstructured pruning (
torch.nn.utils.prune.l1_unstructured): azzera i pesi meno significativi (magnitude-based) nei layerConv2d/Linear. Riduce la dimensione "logica" ma non necessariamente quella su disco/i tempi, a meno di usare formati sparsi. - Structured pruning (rimozione di interi filtri/canali convoluzionali,
ln_structuredsudim=0): più efficace su CPU (Raspberry Pi) perché riduce davvero le operazioni, non solo azzera pesi.
- Dynamic quantization (
torch.quantization.quantize_dynamic): la più semplice, converte i pesi dei layerLinearin INT8 a runtime. Buon primo esperimento, ma la CNN ha per lo piùConv2d, meno impattata. - Static quantization (post-training, PTQ): richiede una fase di calibrazione con un piccolo batch di immagini del training set per stimare i range di attivazione; quantizza sia pesi che attivazioni. Più efficace su CNN.
- Quantization-Aware Training (QAT): se si vuole il massimo recupero di accuracy, si simula la quantizzazione già durante un breve re-training.
- Alternativa via ONNX: esportare il modello con
torch.onnx.export, quindi quantizzare cononnxruntime.quantizationed eseguire l'inferenza con ONNX Runtime — spesso più realistico di PyTorch puro come target "simil-Raspberry Pi" perché è il motore di inferenza più diffuso in ambito embedded.
Dalla suite di benchmark implementata (benchmarks/benchmark_compression.py), abbiamo misurato le seguenti performance sulla nostra architettura:
| Variante | Accuracy | Dimensione su disco | Latenza media (CPU, 1 thread) | Parametri totali / non nulli | Sparsità | RAM processo |
|---|---|---|---|---|---|---|
| Baseline (float32) | 99.2% | 33.44 MB | 2.28 ms | 8,765,066 / 8,765,066 | 0.0% | 404 MB |
| Pruned (L1 Unstructured, 30%) | 86.0% | 33.44 MB | 2.28 ms | 8,765,066 / 8,653,961 | 1.3% | 293 MB |
| Pruned + Dynamic Quant (INT8) | 86.2% | 9.43 MB | 2.98 ms | 370,816 / 259,711 | 30.0% | 339 MB |
| ONNX Runtime (float32/INT8) | 99.2% | — | 1.58 ms | — | — | 746 MB |
Nota sui risultati Edge: La quantizzazione dinamica (
Pruned + Dynamic Quant) riduce la dimensione del modello di ~3.5x (da 33.4 MB a 9.4 MB), rendendolo ideale per il caricamento nella RAM limitata di microcontrollori o schede embedded. Per quanto riguarda la velocità di esecuzione, il motore ONNX Runtime si dimostra il più efficiente sul nostro target CPU, abbattendo la latenza a ~1.58 ms per scatto preservando l'accuratezza del modello originale.
PomodorIA/
├── README.md
├── requirements.txt
├── config.yaml # soglie, intervalli, path dataset, modalità modello
├── models/
│ ├── CNN_64f-k3-3blk.pth # checkpoint originale della CNN (float32)
│ ├── model.py # architettura TomatoCNN
│ ├── compress.py # pruning, quantizzazione dinamica, export ONNX
│ └── optimized/ # checkpoint compressi generati (ONNX, ecc.)
├── sensors/
│ ├── virtual_camera.py # fotocamera virtuale che campiona dal dataset
│ └── environment_simulator.py # simulatore sensori T/Hum/Soil/Lux (con Data Fusion)
├── edge/
│ └── inference_engine.py # motore di inferenza (preprocessing, predict, timing)
├── agent/
│ └── decision_agent.py # agente decisionale razionale (regole PEAS)
├── actuators/
│ ├── fake_gpio.py # mock hardware livello basso (interfaccia RPi.GPIO)
│ └── actuators.py # banco attuatori (irrigazione, ventola, allarme, notifiche)
├── orchestrator/
│ └── main_loop.py # orchestratore del ciclo sense-think-act
├── dashboard/
│ └── app_streamlit.py # dashboard real-time interattiva (Streamlit + Plotly)
├── logs/
│ └── run_*.csv # log strutturati generati dai cicli
└── benchmarks/
├── benchmark_compression.py # suite di benchmark di quantizzazione e pruning
└── benchmark_results.csv # risultati di performance misurati
Assicurati di avere Python 3.10+ installato sul tuo sistema. Per configurare l'ambiente isolato e installare tutte le dipendenze:
# 1. Entra nella cartella del progetto
cd PomodorIA
# 2. Crea un virtual environment (raccomandato)
python3 -m venv .venv
# 3. Attiva l'ambiente virtuale
# Su macOS / Linux:
source .venv/bin/activate
# Su Windows:
# .venv\Scripts\activate
# 4. Installa le dipendenze richieste (PyTorch, Streamlit, Plotly, ecc.)
pip install -r requirements.txtPrima di avviare il sistema, verifica che il percorso del dataset nel file config.yaml punti correttamente alla cartella di PlantVillage-Tomato presente sul tuo computer:
paths:
dataset_root: "/percorso/al/tuo/dataset/plantvillage"Per avviare la simulazione interattiva della serra domotica con l'interfaccia grafica real-time (Streamlit):
streamlit run dashboard/app_streamlit.pyIl comando aprirà automaticamente nel tuo browser la dashboard all'indirizzo http://localhost:8501, dalla quale potrai:
- Eseguire e monitorare il ciclo integrato
sense → think → actpasso dopo passo o in automatico; - Osservare le diagnosi della CNN con le barre di probabilità softmax sulle 10 classi del pomodoro;
- Monitorare i sensori ambientali in tempo reale (temperatura, umidità suolo/aria, luminosità);
- Verificare il ragionamento dell'agente PEAS e lo scatto degli attuatori virtuali (irrigazione, ventola, allarmi);
- Consultare il System Monitor per misurare latenza di inferenza, consumo RAM e utilizzo CPU (su 1 thread, come su Raspberry Pi).
| Ambito | Strumenti |
|---|---|
| ML / Edge AI | PyTorch, torch.nn.utils.prune, torch.quantization, ONNX, ONNX Runtime |
| Simulazione risorse | psutil, time, torch.set_num_threads |
| Dashboard & UI | Streamlit, Plotly, PIL |
| Logging / Gestione Dati | pandas, file CSV strutturati |
| Configurazione | pyyaml (config.yaml) |
| Testing | Script integrati e test end-to-end |
- Federated Learning simulato: più "serre virtuali" che addestrano localmente e aggregano i pesi, richiamando il paradigma citato nei tuoi appunti per l'Edge/Federated Learning.
- Modello di degrado nel tempo: simulare l'evoluzione di una malattia su più cicli, non solo singoli scatti indipendenti.
- Dataset: PlantVillage — Tomato (14.529 immagini, 10 classi).
- Modello:
TomatoCNN(n_filters=64, kernel_size=3, num_blocks=3), progetto P14 "FCNN vs CNN", corso di Neural Networks. - Appunti del corso di Internet of Things (capitoli su IoT, architetture, Big Data, Smart Cities/CPS) per il framing teorico.