ELAI S.r.l.

Quando il sensore inganna l’AI: perché una vibrazione veloce sembra lenta

Un esempio riproducibile mostra perché 700 Hz possono sembrare 300 Hz: campionamento, perdita di informazione e filtri prima della decimazione nei sistemi embedded.

Quando il sensore inganna l’AI: perché una vibrazione veloce sembra lenta

Il problema viene prima del modello

Un motore vibra e un piccolo dispositivo cerca di riconoscere un’anomalia. Il sistema registra un picco a 300 hertz, cioè trecento oscillazioni al secondo. Possiamo concludere che il motore contenga davvero quella vibrazione? Non necessariamente: con un campionamento inadeguato, una componente a 700 hertz può lasciare esattamente la stessa traccia digitale. Un modello AI molto accurato non può distinguere due cause che gli vengono presentate con gli stessi numeri, se non dispone di altre informazioni.

La domanda di questo articolo è come conservare l’informazione utile quando portiamo un segnale fisico dentro un sistema embedded, cioè un calcolatore integrato in un dispositivo. Partiremo da due onde semplici, dimostreremo perché diventano indistinguibili e costruiremo un filtro prima di ridurre i campioni. Vedremo anche il prezzo di quella scelta: ritardo, memoria e operazioni. Tutti i segnali e i risultati numerici sono sintetici e riproducibili; non abbiamo misurato un motore, una scheda o un prodotto EL-AI.

Campionare significa guardare a intervalli

Un accelerometro può produrre un segnale che cambia nel tempo. Il convertitore analogico-digitale, spesso abbreviato ADC, lo rappresenta con numeri acquisiti a istanti discreti. Se la frequenza di campionamento f_s è 1.000 campioni al secondo, osserviamo il segnale ogni millisecondo. Non conserviamo direttamente ciò che accade fra due osservazioni. La questione non è soltanto quanti bit usiamo per ciascun numero: anche numeri infinitamente precisi possono descrivere in modo ambiguo una dinamica osservata troppo raramente.

Per isolare il fenomeno usiamo il coseno: un’oscillazione regolare con ampiezza unitaria e fase iniziale zero. La frequenza f conta i cicli al secondo, t è il tempo in secondi, n è l’indice intero del campione. Sostituire t = n/f_s significa valutare l’onda soltanto agli istanti disponibili. Le parentesi tonde indicano qui il tempo continuo, quelle quadre la sequenza digitale; non cambiano la natura fisica del sensore, rendono esplicito cosa stiamo osservando.

x(t) = cos(2π f t) x[n] = cos(2π f n / f_s)

Due frequenze, la stessa sequenza

Mettiamo f_s = 1.000 Hz e confrontiamo f = 700 Hz con f = 300 Hz. In un millisecondo la prima onda compie 0,7 giri, la seconda 0,3. Il coseno non distingue un angolo negativo dal suo opposto positivo; inoltre aggiungere un numero intero di giri non cambia il valore. Sono queste due proprietà, non un difetto del software, a rendere identici i campioni. La dimostrazione seguente vale per ogni intero n, non soltanto per i punti che abbiamo disegnato.

cos(2π · 700 n / 1000) = cos(2π n − 2π · 300 n / 1000) = cos(2π · 300 n / 1000)

Questo fenomeno si chiama aliasing: frequenze fisiche diverse assumono la stessa identità nella sequenza campionata. Nel nostro controllo numerico su 1.000 campioni la massima differenza è circa 1,27 × 10⁻¹², dovuta all’aritmetica finita del computer; matematicamente è zero. Cambiare fase può cambiare il segno o la fase dell’alias, ma non elimina l’ambiguità generale. Se aggiungiamo un’ipotesi esterna, per esempio sapere già che non esistono frequenze sopra 400 Hz, possiamo escludere alcune spiegazioni. È informazione aggiuntiva, non qualcosa recuperato dai campioni.

La frequenza f_s/2 è detta frequenza di Nyquist. Per rappresentare senza aliasing un segnale arbitrario limitato in banda, la sua frequenza massima deve stare sotto quella soglia; al bordo esatto alcune fasi diventano problematiche. «Il segnale utile è a 300 Hz» non equivale però a «tutto il segnale è sotto 500 Hz». Disturbi e armoniche sopra soglia continuano a entrare nel convertitore. Per un sistema reale serve quindi conoscere e limitare anche ciò che non vorremmo misurare.

Perché filtrare dopo può essere troppo tardi

Supponiamo di aver già acquisito a 1 kHz e di applicare un filtro digitale che conserva 300 Hz. Quel filtro conserverà anche il contributo proveniente da 700 Hz, ormai sovrapposto agli stessi campioni. Se elimina 300 Hz, elimina entrambe le spiegazioni. Un filtro non può decidere quale componente fosse vera quando la distinzione è stata persa. La protezione deve agire prima del passaggio che rende indistinguibili le frequenze: davanti all’ADC per il primo campionamento, e prima di ogni successiva riduzione della frequenza digitale.

Nel nostro esperimento partiamo invece da 4.000 campioni al secondo: 300 e 700 Hz sono entrambe sotto 2.000 Hz e restano distinguibili. Vogliamo poi consegnare al modello soltanto 1.000 campioni al secondo. Tenere un campione ogni quattro è una decimazione di fattore M = 4. Farlo senza filtrare riporta esattamente il problema precedente. Il filtro intermedio deve attenuare le componenti che diventerebbero ambigue alla nuova frequenza di campionamento, conservando la banda che ci interessa.

Un filtro costruito e verificabile

Usiamo un filtro FIR, a risposta impulsiva finita: ogni uscita è una somma pesata di un numero finito di campioni recenti. I pesi h[k] descrivono quanto ciascun campione contribuisce. Non è una rete neurale addestrata: scegliamo esplicitamente una risposta passa-basso, che lascia passare le oscillazioni lente e attenua quelle più rapide. La formula della somma è utile perché mostra sia il calcolo sia il suo costo. Con L = 129 coefficienti, una valutazione diretta richiede 129 prodotti e la loro somma.

y[n] = Σ(k=0…128) h[k] x[n−k] z[m] = y[4m]

La prima riga filtra; la seconda prende un’uscita ogni quattro. m conta i campioni della nuova sequenza. Per definire i coefficienti partiamo da una sinc, la forma del passa-basso ideale, la limitiamo a 129 valori e ne addolciamo i bordi con una finestra di Hann. La finestra riduce alcune ondulazioni della risposta, al prezzo di una transizione non istantanea. Infine dividiamo tutti i pesi per la loro somma, così un ingresso costante mantiene lo stesso livello dopo il transitorio iniziale.

sinc(u) = sin(πu)/(πu), sinc(0) = 1 w[k] = 0.5 − 0.5 cos(2πk/128) g[k] = (2f_c/f_s) sinc((2f_c/f_s)(k−64)) w[k] h[k] = g[k] / Σ(j=0…128) g[j] f_c = 400 Hz, f_s = 4000 Hz

Il numero 64 centra la risposta: è metà dei 128 intervalli fra il primo e l’ultimo coefficiente. f_c è il parametro di taglio del progetto, non una garanzia che tutto passi intatto fino a 400 Hz e scompaia subito dopo. Un filtro finito ha una zona di transizione. Perciò controlliamo la risposta effettiva alle frequenze dell’esempio e sull’intero grafico. Il codice in fondo usa la stessa definizione di sinc normalizzata di NumPy; cambiare convenzione senza cambiare formula produrrebbe un filtro diverso.

Il risultato: un picco falso smette di dominare

Costruiamo l’ingresso come somma di una componente utile a 300 Hz con ampiezza 0,2 e una componente indesiderata a 700 Hz con ampiezza 1. Le ampiezze sono normalizzate, senza attribuire accelerazioni fisiche. Senza filtro, dopo la decimazione le due componenti si sovrappongono: il picco a 300 Hz ha ampiezza 1,2. Il software potrebbe scambiarlo per una vibrazione utile sei volte maggiore del previsto. Questa conclusione dipende dalle fasi scelte; con altre fasi le componenti potrebbero anche cancellarsi parzialmente.

x[n] = 0.2 cos(2π · 300 n / 4000) + 1.0 cos(2π · 700 n / 4000)

Con il filtro, il guadagno a 300 Hz è 0,998531: quasi tutta la componente utile passa. A 700 Hz vale circa 0,0000131684. Espressa in decibel, la risposta a quella specifica frequenza è −97,61 dB; il decibel usa qui 20 log₁₀ del rapporto fra ampiezze. Non significa che tutta la banda eliminata abbia quella attenuazione: il grafico mostra minimi e massimi diversi. Dopo filtraggio e decimazione, misuriamo nel segnale sintetico un’ampiezza a 300 Hz di circa 0,199693, vicina allo 0,2 originario.

QuantitàValore calcolato
f_s → f_out4000 → 1000 Hz
|H(300 Hz)|0.998531446
|H(700 Hz)|0.0000131684
A300: x[4m]1.200000000
A300: y[4m]0.199693121
(L−1)/(2f_s)16 ms
Risposta calcolata del FIR: quasi 0 dB a 300 Hz e forte attenuazione a 700 Hz. La linea a 500 Hz è la nuova frequenza di Nyquist. Le ondulazioni ricordano che −97,6 dB vale a 700 Hz, non per tutta la banda.
Risposta calcolata del FIR: quasi 0 dB a 300 Hz e forte attenuazione a 700 Hz. La linea a 500 Hz è la nuova frequenza di Nyquist. Le ondulazioni ricordano che −97,6 dB vale a 700 Hz, non per tutta la banda.

Per rendere controllabile il confronto generiamo 5.000 campioni a 4 kHz, applichiamo la convoluzione causale e selezioniamo gli indici da 256 a 4252 a passi di quattro: esattamente 1.000 uscite. Partire da 256 evita il transitorio del filtro, lungo al massimo 128 campioni di ingresso per questa sequenza. Su quel secondo di dati stimiamo l’ampiezza con la proiezione sinusoidale a 300 Hz, equivalente al corrispondente coefficiente della trasformata discreta di Fourier. La finestra contiene un numero intero di cicli: non stiamo studiando la dispersione spettrale dovuta a frequenze non allineate.

Il costo nascosto: aspettare e conservare campioni

I coefficienti sono simmetrici: il filtro ha fase lineare e introduce un ritardo di gruppo di (L−1)/2 campioni nella banda utile. Per L = 129 e f_s = 4 kHz sono 64 campioni, cioè 16 ms. Un riconoscitore che deve reagire rapidamente deve includere questo tempo nel bilancio complessivo. Il filtro non impiega necessariamente 16 ms di CPU: è il ritardo temporale del segnale, distinto dal tempo necessario al processore per calcolare le somme.

Se elaboriamo blocchi da 128 ingressi, occorrono 32 ms per raccogliere un blocco completo. Un campione all’inizio del blocco aspetta più di uno vicino alla fine; non dobbiamo confondere questa attesa con il ritardo di gruppo. Si aggiungono trasferimenti, calcolo del modello ed eventuali code. Qui non abbiamo misurato né latenza media né percentili su un dispositivo: abbiamo identificato due contributi deterministici alla progettazione. Ridurre il blocco diminuisce l’attesa di raccolta, ma può aumentare il costo delle chiamate e dei trasferimenti.

La documentazione del decimatore FIR CMSIS-DSP indica uno stato di L + B − 1 valori per blocchi di B ingressi. Nel nostro esempio, con float32 da quattro byte, lo stato occuperebbe 1.024 byte, i coefficienti 516, l’ingresso 512 e l’uscita di 32 campioni 128. Sono 2.180 byte complessivi se tutti i quattro array fossero in RAM; se i coefficienti restassero in memoria di sola lettura, i tre buffer espliciti richiederebbero 1.664 byte. Stack, strutture, allineamento, DMA e modello AI non sono inclusi: non è una misura del picco RAM dell’applicazione.

Calcolare soltanto le uscite conservate richiede, con la somma diretta, circa 129.000 operazioni moltiplica-accumula al secondo per canale: 129 coefficienti per 1.000 uscite. Calcolare prima tutte le 4.000 uscite e poi scartarne tre su quattro ne richiederebbe quattro volte tante. L’aritmetica descrive il lavoro, non la durata: istruzioni vettoriali, simmetria, cache e implementazione possono cambiarne il costo reale. Nel nostro esperimento NumPy calcola la convoluzione completa per chiarezza; non abbiamo eseguito il kernel CMSIS su Cortex-M.

Che cosa cambierebbe su una scheda reale

Il primo controllo sarebbe il percorso analogico. Campionare a 4 kHz non protegge da qualunque frequenza: componenti sopra 2 kHz possono già ripiegarsi nell’ingresso digitale. Il sensore, il suo filtro interno e l’eventuale filtro esterno devono essere considerati insieme. Non assumiamo che un accelerometro integri automaticamente il filtro adatto. Inoltre saturazione dell’ADC e fissaggio meccanico scorretto introducono problemi diversi dall’aliasing; il filtro digitale studiato qui non li risolve.

Seguirebbe una verifica numerica sulla precisione realmente usata. I risultati pubblicati sono calcolati con float64; l’attenuazione di −97,61 dB a 700 Hz non è garantita dopo conversione dei coefficienti a float32 o a interi a virgola fissa. Occorre ricalcolare la risposta, controllare overflow e saturazione e verificare il comportamento ai confini dei blocchi, mantenendo lo stato del filtro. Azzerarlo a ogni blocco introdurrebbe transitori ripetuti: cambierebbe il segnale proprio mentre vogliamo renderlo più affidabile.

Infine va valutata la decisione del modello con la stessa catena di acquisizione usata durante l’addestramento e in esercizio. Un cambio di filtro modifica ampiezze, fase e ritardo; un modello può aver imparato accidentalmente proprio un artefatto. Le prove dovrebbero registrare frequenze di ingresso note, configurazione del sensore, clock, versioni e codice, poi misurare latenza e consumo sulla piattaforma. Questo è un protocollo proposto: non riportiamo potenza, energia per inferenza o accuratezza diagnostica non misurate.

La conclusione: proteggere l’informazione prima di interpretarla

Il picco a 300 Hz dell’apertura non bastava a identificare la vibrazione reale. Abbiamo visto perché: la scelta degli istanti di osservazione può cancellare una distinzione fisica, e l’AI a valle non la ricrea senza altre ipotesi. Nel caso costruito, filtrare a 4 kHz prima di scendere a 1 kHz conserva la componente utile e riduce quella che la contaminava. Il prezzo è misurabile in ritardo, buffer e calcolo, ma le prestazioni su hardware restano da verificare. Progettare un sistema embedded AI significa quindi progettare anche il modo in cui il mondo diventa dato.

Fonti, codice e condizioni di riproduzione

MIT 6.300 — Sampling and Aliasing, Spring 2026.

Arm CMSIS-DSP — Finite Impulse Response Decimator, main documentation.

Analog Devices — Filter Basics: Anti-Aliasing.

Le fonti documentano campionamento, protezione dall’aliasing e interfaccia del decimatore. La pagina CMSIS su main è una documentazione mutabile, consultata il 25 settembre 2026, non una versione del kernel da noi misurata. Il filtro, i segnali e il confronto sono costruiti nell’esperimento allegato. Il frammento seguente calcola guadagno e ritardo; l’archivio aggiunge convoluzione, stima delle ampiezze e figura. Usati Python 3.14.0, NumPy 2.5.3 e Matplotlib 3.11.2, senza numeri casuali. Questa analisi editoriale non implica che EL-AI disponga già di prodotti o installazioni embedded.

import numpy as np
fs, fc, taps = 4000, 400, 129
k = np.arange(taps)
h = (2*fc/fs) * np.sinc((2*fc/fs)*(k-64)) * np.hanning(taps)
h /= h.sum()
for f in (300, 700):
    gain = abs(np.sum(h*np.exp(-2j*np.pi*f*k/fs)))
    print(f, round(float(gain), 9))
print("delay_ms", 1000*64/fs)

Codice, dati e istruzioni · JSON. Calcoli didattici eseguiti con Python 3.14.0; figure con Matplotlib 3.11.2. Analisi con assistenza AI, senza dichiarare peer review o revisione umana. Copertina originale ImageGen, illustrativa: non documenta persone, sedi o installazioni EL-AI. Fonti consultate il 25 settembre 2026.