7.8. Entrées/sorties de tenseurs¶
Le moteur accepte un seul tenseur côté entrée et en produit un ou plusieurs côté sortie. Les tenseurs sont des objets ndarray dotés de la forme, du dtype et du vocabulaire de descripteurs présentés au chapitre numpy. Leurs formes et leurs dtypes proviennent du fichier de modèle et sont rapportés via input_shape / output_shape et input_dtype / output_dtype.
7.8.1. Quantification¶
La plupart des réseaux que la caméra exécute opèrent sur des tenseurs entiers quantifiés – int8 ou uint8 – afin de tenir dans la RAM et le budget de calcul de la caméra. Un tenseur quantifié porte des valeurs entières qui représentent des nombres à valeurs réelles via une échelle et un point zéro par tenseur :
L’échelle et le point zéro proviennent de la calibration au temps d’entraînement du modèle et sont stockés dans le fichier de modèle. Ils sont exposés via input_scale, input_zero_point, output_scale et output_zero_point – chacun étant une liste avec une entrée par tenseur d’entrée ou de sortie.
ml.utils.quantize() et ml.utils.dequantize() appliquent les formules sur un indice de sortie spécifié:
import ml.utils
real_tensor = ml.utils.dequantize(model, q_tensor, index=0)
q_tensor = ml.utils.quantize(model, real_tensor, index=0)
Les deux fonctions renvoient la valeur inchangée lorsque le dtype de sortie à l’indice donné est déjà flottant, de sorte que l’appel est sûr quel que soit l’état de quantification du modèle.
7.8.2. Ce que le script voit côté sortie¶
Ce que renvoie predict() dépend de la présence ou non d’un post-processeur enregistré.
Sans post-processeur, les sorties entières brutes du moteur sont automatiquement déquantifiées en flottant et renvoyées sous forme de liste d’objets ndarray flottants. Le script reçoit des nombres à valeurs réelles prêts à être lus. C’est le bon chemin pour les réseaux de classification, dont l’unique tenseur de sortie est déjà une liste de scores de confiance par classe que l’application parcourt – aucune étape de décodage nécessaire. C’est aussi le chemin facile pour faire fonctionner rapidement un modèle inconnu ou pour une inspection ad hoc depuis le REPL.
Avec un post-processeur enregistré (via postprocess= sur le constructeur ou callback= sur l’appel à predict), les tenseurs quantifiés bruts sont remis directement à l’appelable du post-processeur. Le post-processeur reçoit les tenseurs quantifiés bruts et est responsable de toute déquantification dont il a besoin.
Le partage est un choix de performance. La déquantification automatique alloue un nouveau tenseur flottant pour chaque sortie et parcourt chaque élément. Un post-processeur qui n’a besoin que de quelques valeurs de chaque tenseur – seuiller les scores de confiance, puis décoder les boîtes des survivants – évite le coût de la déquantification du reste. Les décodeurs de boîtes livrés sous ml.postprocessing empruntent tous cette voie, et ml.utils.threshold() est conçu exactement pour ce cas : il prend un tenseur de scores quantifié et renvoie les indices dont les valeurs déquantifiées passent un seuil à valeur réelle, sans déquantifier le tenseur entier.