7.8. קלט/פלט של טנזורים

המנוע מקבל טנזור יחיד בצד הקלט ומייצר אחד או יותר בצד הפלט. הטנזורים הם אובייקטי ndarray בעלי הצורה, ה-dtype, ואוצר המילים של המתאר (descriptor) שהפרק על numpy הציג. הצורות וה-dtype שלהם מגיעים מקובץ המודל ומדווחים דרך input_shape / output_shape ו-input_dtype / output_dtype.

7.8.1. קוונטיזציה

רוב הרשתות שהמצלמה מריצה פועלות על טנזורים שלמים מקוונטזים – int8 או uint8 – כדי להיכנס לתקציב ה-RAM והחישוב של המצלמה. טנזור מקוונטז נושא ערכים שלמים המייצגים מספרים בעלי ערך ממשי דרך קנה מידה ונקודת אפס לכל טנזור:

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

קנה המידה ונקודת האפס מגיעים מהכיול של זמן האימון של המודל ונשמרים בקובץ המודל. הם נחשפים כ-input_scale, input_zero_point, output_scale, ו-output_zero_point – כל אחד רשימה עם רשומה אחת לכל טנזור קלט או פלט.

ml.utils.quantize() ו-ml.utils.dequantize() מיישמים את הנוסחאות מול אינדקס פלט מצוין:

import ml.utils

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

שתי הפונקציות מחזירות את הערך ללא שינוי כאשר ה-dtype של הפלט באינדקס הנתון הוא כבר float, כך שהקריאה בטוחה בלי קשר למצב הקוונטיזציה של המודל.

7.8.2. מה הסקריפט רואה בצד הפלט

מה ש-predict() מחזירה תלוי בשאלה האם רשום פוסט-מעבד.

ללא פוסט-מעבד, הפלטים השלמים הגולמיים של המנוע מבוטלי-קוונטיזציה אוטומטית אל float ומוחזרים כרשימה של אובייקטי ndarray מסוג float. הסקריפט מקבל מספרים בעלי ערך ממשי המוכנים לקריאה. זהו המסלול הנכון עבור רשתות סיווג, שטנזור הפלט היחיד שלהן הוא כבר רשימה של ציוני ביטחון לכל מחלקה שהיישום עובר עליהם – ללא צורך בשלב פענוח. זהו גם המסלול הקל להפעלה מהירה של מודל לא ידוע או לבחינה אד-הוק מתוך ה-REPL.

כשרשום פוסט-מעבד (דרך postprocess= בבנאי או callback= בקריאת predict), הטנזורים המקוונטזים הגולמיים נמסרים ישירות לישות בת-הקריאה (callable) של הפוסט-מעבד. הפוסט-מעבד מקבל את הטנזורים המקוונטזים הגולמיים ואחראי על כל ביטול קוונטיזציה שהוא צריך.

הפיצול הוא בחירת ביצועים. ביטול קוונטיזציה אוטומטי מקצה טנזור float חדש עבור כל פלט ועובר על כל איבר. פוסט-מעבד הזקוק רק לכמה ערכים מכל טנזור – מסנן את ציוני הביטחון לפי סף, ואז מפענח תיבות עבור השורדים – חוסך את עלות ביטול הקוונטיזציה של השאר. מפענחי התיבות שמסופקים תחת ml.postprocessing כולם נוקטים במסלול הזה, ו-ml.utils.threshold() בנויה בדיוק עבור המקרה הזה: היא מקבלת טנזור ציונים מקוונטז ומחזירה את האינדקסים שערכיהם המבוטלי-קוונטיזציה עוברים סף בעל ערך ממשי, מבלי לבטל את הקוונטיזציה של הטנזור כולו.