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:
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.