7.8. Wejście/wyjście tensorów#

Silnik przyjmuje pojedynczy tensor po stronie wejściowej i produkuje jeden lub więcej po stronie wyjściowej. Tensory to obiekty ndarray o kształcie, dtype i słownictwie deskryptorów wprowadzonych w rozdziale o numpy. Ich kształty i dtype pochodzą z pliku modelu i są raportowane przez input_shape / output_shape oraz input_dtype / output_dtype.

7.8.1. Kwantyzacja#

Większość sieci uruchamianych przez kamerę operuje na skwantyzowanych tensorach całkowitoliczbowych – int8 lub uint8 – aby zmieścić się w budżecie pamięci RAM i obliczeń kamery. Skwantyzowany tensor przenosi wartości całkowite, które reprezentują liczby o wartościach rzeczywistych poprzez skalę i punkt zerowy na każdy tensor:

\[\text{real} = \text{scale} \times (q - \text{zero_point})\]
\[q = \mathrm{round}(\text{real} / \text{scale}) + \text{zero_point}\]

Skala i punkt zerowy pochodzą z kalibracji modelu z czasu treningu i są przechowywane w pliku modelu. Są udostępniane jako input_scale, input_zero_point, output_scale oraz output_zero_point – każda będąca listą z jednym wpisem na każdy tensor wejściowy lub wyjściowy.

ml.utils.quantize() i ml.utils.dequantize() stosują te wzory względem określonego indeksu wyjścia:

import ml.utils

real_tensor = ml.utils.dequantize(model, q_tensor, index=0)
q_tensor    = ml.utils.quantize(model, real_tensor, index=0)

Obie funkcje zwracają wartość bez zmian, gdy dtype wyjścia o danym indeksie jest już zmiennoprzecinkowy, więc wywołanie jest bezpieczne niezależnie od statusu kwantyzacji modelu.

7.8.2. Co skrypt widzi po stronie wyjścia#

To, co zwraca predict(), zależy od tego, czy zarejestrowano post-procesor.

Bez post-procesora surowe wyjścia całkowitoliczbowe silnika są automatycznie dekwantyzowane do liczby zmiennoprzecinkowej i zwracane jako lista obiektów ndarray typu float. Skrypt otrzymuje liczby o wartościach rzeczywistych gotowe do odczytu. To właściwa ścieżka dla sieci klasyfikujących, których pojedynczy tensor wyjściowy jest już listą wyników pewności na każdą klasę, po której aplikacja iteruje – bez potrzeby kroku dekodowania. Jest to także łatwa ścieżka do szybkiego uruchomienia nieznanego modelu lub do doraźnej inspekcji z REPL.

Gdy post-procesor jest zarejestrowany (poprzez postprocess= w konstruktorze lub callback= w wywołaniu predict), surowe skwantyzowane tensory są przekazywane bezpośrednio do obiektu wywoływalnego post-procesora. Post-procesor otrzymuje surowe skwantyzowane tensory i odpowiada za dowolną dekwantyzację, jakiej potrzebuje.

Ten podział jest wyborem wydajnościowym. Automatyczna dekwantyzacja alokuje nowy tensor zmiennoprzecinkowy dla każdego wyjścia i przechodzi przez każdy element. Post-procesor, który potrzebuje tylko kilku wartości z każdego tensora – progowanie wyników pewności, a następnie dekodowanie ramek dla tych, które przetrwały – pomija koszt dekwantyzacji reszty. Dekodery ramek dostarczane pod ml.postprocessing wszystkie obierają tę trasę, a ml.utils.threshold() jest zbudowana dokładnie pod ten przypadek: przyjmuje skwantyzowany tensor wyników i zwraca indeksy, których zdekwantyzowane wartości przekraczają próg o wartości rzeczywistej, bez dekwantyzowania całego tensora.