מה מסתתר מאחורי נוסחת ה-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 - עם אובדן מידע קטן וצפוי, אך לא הרס של המשמעות המספרית.

תגובות