מה מסתתר מאחורי נוסחת ה-Quantization?
מה מסתתר מאחורי נוסחת ה-Quantization?
output = clamp(round(input / scale) + offset)
אם יצא לך לעבוד עם quantization (כימות) במודלים של למידת מכונה - זו כנראה אחת הנוסחאות שראית שוב ושוב. אבל מה באמת קורה כאן?
למה בכלל צריך את זה?
מודלים נוירוניים עובדים בדרך כלל עם ערכים “רציפים” (float32). אבל בחומרה, זה יקר - גם באחסון וגם בזמן ריצה. לכן מבצעים Quantization: מתרגמים את הערכים הרציפים למספרים שלמים (int8, למשל) בצורה ששומרת על המשמעות של המספרים, גם אם מאבדים קצת דיוק.
נפרק את הנוסחה
input - הערך הרציף המקורי (למשל 2.8 או -0.47). המודל “חושב” במספרים עשרוניים, אבל החומרה צריכה ערכים שלמים - וזה בדיוק מה שאנחנו הולכים לחשב.
scale - זהו מקדם ההמרה בין העולם הרציף לעולם השלמים. הוא קובע בכמה מחלקים את הערך המקורי כדי להביא אותו לטווח החדש.
offset (גם נקרא zero-point) - זהו הזזה של נקודת האפס. אם רוצים שכל הערכים יהיו לא-שליליים (0 עד 255 במקום -128 עד 127), מוסיפים offset מתאים כך שהאפס “נודד” למקום הנכון בטווח החדש. במילים פשוטות: offset מזיז את המספרים כדי שיתאימו למבנה הזיכרון הרצוי של החומרה.
round() - לאחר הכיווץ וההזזה, אנחנו עדיין עם מספר עשרוני. בשלב זה מעגלים אותו למספר השלם הקרוב ביותר, כי המעבד לא עובד עם חלקים של מספרים כשמדובר ב-int8 או int16.
clamp() - זהו השומר בשער. הוא מוודא שהתוצאה לא חורגת מהטווח המותר - למשל -128 עד 127. אם קיבלנו ערך 150, הוא נחתך ל-127; אם קיבלנו -200, הוא הופך ל—128. כך מונעים טעויות overflow שעלולות לשבש את תוצאות המודל.
דוגמה מוחשית
נניח שהמודל החזיר input = 2.8, ויש לנו scale = 0.1, offset = 0.
output = clamp(round(2.8 / 0.1) + 0)
= clamp(round(28))
= clamp(28)
התוצאה היא 28 - מספר שלם שמייצג את הערך הרציף “בצורה דחוסה”, בלי לחרוג מהטווח המותר של int8 (-128 עד 127).
נקודה חשובה לגבי בחירת ה-scale
חשוב לבחור scale כך שכל הטווח האפשרי של הקלט “יתאים” בפועל לטווח השלמים - כולל שני הקצוות.
לדוגמה: אם רוצים למפות טווח של [-1.0, 1.0] ל-int8 חתום (-128 עד 127), scale = 1/127 מתאים יותר מ-scale = 1/128 - כי עם 1/128, הקצה העליון (1.0) יתעגל ל-128, שהוא כבר מעבר לטווח, ויתחתך על ידי ה-clamp() ל-127. זה לא שובר את התוצאה (clamp מטפל בזה), אבל פוגע קצת בדיוק בקצוות הטווח - בחירת scale מדויקת יותר פותרת את זה מראש.
ומה קורה בכיוון ההפוך?
כשרוצים לשחזר את הערך המקורי (De-quantization), משתמשים בנוסחה ההפוכה:
real_value = (output - offset) * scale
וכך חוזרים לעולם ה-float - עם אובדן מידע קטן וצפוי, אך לא הרס של המשמעות המספרית.