7.16. Riepilogo

Il capitolo ha esaminato le parti del modulo ml a cui un’applicazione OpenMV ricorre quando una fase di inferenza fa parte della pipeline:

  • Concetti – cos’è una rete neurale in termini aritmetici (uno stack di operatori addestrabili che mappa un tensore in un tensore), cosa ha cambiato il machine learning rispetto all’elaborazione classica delle immagini (l’algoritmo riassuntivo scritto dall’uomo è scomparso, sostituito da pesi appresi da dati etichettati) e la demo hello che ha eseguito un rilevatore di volti in poche righe di Python.

  • Il modulo ml – l’oggetto ml.Model e le sue proprietà per ispezionare i tensori di input e output, i percorsi dei file del modello che accetta e dove risiedono questi file: una partizione ROMFS di sola lettura per l’esecuzione direttamente dalla flash, oppure qualsiasi altro filesystem MicroPython quando il modello può essere copiato in RAM al momento del caricamento.

  • La pipeline di inferenza – le tre fasi che predict() esegue in sequenza (pre-elaborazione, dispatch del motore, post-elaborazione), l’handle Normalization sulla prima fase, l’handle del post-processore sulla terza fase e l’aritmetica di quantizzazione che lega i tensori interi eseguiti dalla cam ai numeri a valori reali rispetto ai quali la rete è stata addestrata.

  • Motori di inferenza – TFLM (l’interprete di operatori eseguito dalla maggior parte delle cam), CMSIS-NN (la libreria di kernel SIMD che vi sta sotto su Cortex-M) e le NPU (l’Ethos-U55 di Arm sull’AE3 abbinato al compilatore offline Vela, la Neural-ART di ST sull’N6 abbinata a STAI e STEdgeAI). Il motore è determinato dalla cam; lo script non lo sceglie.

  • Decodifica dell’output – i post-processori che trasformano i tensori di output grezzi in box, keypoint o liste per classe, la classe NMS che fonde i candidati sovrapposti, il tutorial su YOLOv8 che mostra come mantenere veloce la decodifica applicando la soglia prima della dequantizzazione e il protocollo per scrivere un decodificatore personalizzato quando il catalogo non copre un modello.

7.16.1. Cosa diventa ora accessibile

Tre cose per cui il capitolo prepara:

  • Caricare un modello addestrato ed eseguirlo. Qualsiasi cosa in /rom/ funziona senza ulteriore preparazione; qualsiasi cosa fornita esternamente come file .tflite compatibile funziona dopo che lo strumento offline per la cam di destinazione (Vela per l’AE3, STEdgeAI per l’N6) ha prodotto il layout corretto.

  • Decodificare qualsiasi tensore di output. Quando l’architettura è nel catalogo, il post-processore corretto è meccanico: YoloV8 per un modello YOLOv8, BlazeFace per BlazeFace e così via. Quando non lo è, il protocollo writing-your-own copre il contratto e il tutorial su YOLOv8 è il riferimento più pulito da cui copiare.

  • Ragionare sulle prestazioni. Un modello che gira a 30 FPS su una NPU può girare a 3 FPS su un Cortex-M7; il rapporto dipende da quanta parte della rete la cam riesce a togliere dal carico della CPU. Quantizzazione, posizionamento in ROMFS, compilazione per NPU e copertura degli operatori del motore di destinazione sono le quattro leve, e il capitolo ha trattato ciascuna di esse.

7.16.2. L’ML si combina con il resto della cam

Un’inferenza raramente viene eseguita in isolamento. Il modulo image cattura e pre-elabora il frame, il modulo ml esegue la rete e ulab.numpy svolge qualsiasi lavoro numerico per cui nessuna delle due parti ha una funzione integrata. Un tipico script di rilevamento combina tutti e tre: cattura con csi, eventualmente regola il frame con image, esegue predict(), post-elabora il risultato con il modulo corretto tra quelli di ml.postprocessing e ricorre a ulab.numpy per qualsiasi calcolo personalizzato che l’applicazione voglia applicare sui box restituiti dal post-processore. I tre moduli condividono lo stesso modello di memoria; i confini tra loro sono zero-copy ovunque possibile.