close menu

[מדריך] טרנספורמרים לסדרת זמן (TIME2VEC)

איך להשמיד סדרות זמן עם טרנספורמרים?

אמ;לק: המחברת: https://www.kaggle.com/…/tutorial-time-series…

התגלגלה אלי שמועה שאנשי הקבוצה לא אוהבים רשתות עמוקות לסדרות זמן, השמועה אומרת שעצים עובדים יותר טוב וגם יציבים הרבה יותר.

להלן מחברת, במחברת תוכלו למצוא טרנספורמר שמכסח את כל העצים בתחרות אחרי עשרה אפוקים בלבד. 

[ואני מאמן אחד ל1000 אפוקים ברגעים אלה ממש]

רגע לפני שאתם עם צלב בוער: אני בעקרון מסכים אתכם לגבי העצים, קראו עוד למטה.

הסבר ומדריך קצר:

הסט-אפ: חלון מתגלגל

  • הנתונים שלנו מגיעים בטבלה.
  • אנחנו רוצים ללמוד סדרה. (והיא לא טבלה)
  • יש לנו בעיה.

על מנת לפתור את הבעיה שלנו נהפוך את הטבלה לסדרה בצורה הבאה:

כל אחד מהוקטורי הקלט שלנו יהיה slice של הדאטהסט המייצג חלון מתגלגל של N צעדים אחורה, עבור כל אחד מהווקטורים הללו נפרוס גם וקטור מטרה המתאים לאינדקסים של החלון.

בקיצור, אנו יוצרים את הקלט והפלט הבאים:

– צורת X: (דוגמאות, זמן, פיצ'רים)

– צורת y: (דוגמאות, 1)

טרנספורמרים לסדרות זמן

ולאירוע המרכזי של הערב:

כפי שכולנו יודעים, טרנספורמרים משתלטים על כל תחום שהם נכנסים אליו. היה רק ​​עניין של זמן עד שנקבל את העבודות הראשונות שמיישמות אותם לסדרות זמן.

המחברת המצורפת מממשת מודל טרנספורמר ללימוד ייצוג של סדרת זמן.

הוא לומד להתייחס הן לקטעים קודמים והן לקטעים הבאים בפיצ'רים בודדים, כמו גם לתלות ההדדית בין הפיצ'רים.

התבססתי על כמה מאמרים וכמה מימושים שונים כדי להרכיב את הטרנספורמר הכי חזק שיש. [חיפשתי בברוטפורס את כל הרעיונות מכל המאמרים והשארתי רק את מה שעבד]

הבדלים בין טרנספורמרים לטרנספורמרים לסדרת זמן:

  1. במאמר, הם משתמשים ב-batchnorm ולא layernorm, הסיבה לכך היא שהבעיה ב-batchnorm מלכתחילה הייתה שונות גדולה באורכי הקלט עבור NLP. זו הסיבה לביצועים הנמוכים של backnorm ב-NLP (כלומר, דאטה המורכב ממילים) (Shen et al., 2020).
  2. במאמר, הם משתמשים ב-positioal encoding נלמד ולא positional encoding קבוע (כפי שה-BERT הקלאסי משתמש בו). ניתן לראות שהם מתפקדים טוב יותר עבור כל הדאטהסטים המוצגים במאמר.
  3. במאמר, יש להם אימון ללא פיקוח Masked MLM (mask רציף משמאל). לאחר מכן המודל מנסה לחזות את כל וקטור הקלט, אבל הloss ניתן רק מהפיצ'רים הmasked. הלוס הוא mse.
  4. במחברת זו אני משתמש בskip connections כפי שזה שנעשה באחד המקורות עליהם התבססתי: הוספתי קבוע "SKIP_CONNECTION_STRENGTH" ואני מכפיל בו את הskip connection. זה משפר את הביצועים בפועל. [הקבוע הוא היפרפרמטר]
  5. במחברת זו אני מחליף את הpositional encoding ומשלב במקומו Time2Vec (מאמר:https://arxiv.org/abs/1907.05321). שיפר את הביצועים [בהרבה].

ארכיטקטורה ולוס

המודל מאומן עם קרס במשך כ-1,000 אפוקים. פונקציית הלוס היא MAE והמודל מאומן end2end לtarget (בלי unsupervised). אתם יכולים (ומוזמנים) לשחק עם ההיפרפרמטרים וליצור לעצמכם מודל גדול יותר או להשתמש בפיצ'רים מיוחדים יותר כקלט. (כבר הזכרתי שהטרנספורמר כמעט ללא פיצ'רים ומנצח גם את כל המודלים שיש להם פיצ'רים מתוחכמים?)

הקוד של הבלוק:

class TransformerBlock(layers.Layer):

def __init__(self, embed_dim, feat_dim, num_heads, ff_dim, rate = 0.1):

super(TransformerBlock, self).__init__()

self.att = layers.MultiHeadAttention(num_heads = num_heads, key_dim = embed_dim)

self.ffn = keras.Sequential( [layers.Dense(ff_dim, activation = "gelu"), layers.Dense(feat_dim),] )

self.layernorm1 = layers.BatchNormalization()

self.layernorm2 = layers.BatchNormalization()

self.dropout1 = layers.Dropout(rate)

self.dropout2 = layers.Dropout(rate)

def call(self, inputs, training):

attn_output = self.att(inputs, inputs)

attn_output = self.dropout1(attn_output, training = training)

out1 = self.layernorm1(inputs + attn_output)

ffn_output = self.ffn(out1)

ffn_output = self.dropout2(ffn_output, training = training)

return self.layernorm2(out1 + ffn_output)

אמבדינגס Time2Vec

הרעיון של Time2Vec הוא די קל, נלמד פרמטרים של פונ' אקטיבציה המכילה חלק מחזורי וחלק "לינארי" בצורה אינווריאנטית לפיצ'רים – על מנת ללמוד את העונתיות ואת הtrend של הזמן עצמו [ללא קשר לפיצ'רים].

בפועל, שיפור גדול בביצועים הגיע מהחלפת הקידוד בתחילת המודל לTime2Vec. (גם על פני Positional Encoding)

class Time2Vec(tf.keras.layers.Layer):

def __init__(self, kernel_size = 1):

super(Time2Vec, self).__init__(trainable = True, name = 'Time2VecLayer')

self.k = kernel_size

def build(self, input_shape):

# trend

self.wb = self.add_weight(name = 'wb', shape = (input_shape[1],), initializer = 'uniform', trainable = True)

self.bb = self.add_weight(name = 'bb', shape = (input_shape[1],), initializer = 'uniform', trainable = True)

# periodic

self.wa = self.add_weight(name = 'wa', shape = (1, input_shape[1], self.k), initializer = 'uniform', trainable = True)

self.ba = self.add_weight(name = 'ba', shape = (1, input_shape[1], self.k), initializer = 'uniform', trainable = True)

super(Time2Vec, self).build(input_shape)

def call(self, inputs, **kwargs):

bias = self.wb * inputs + self.bb

dp = K.dot(inputs, self.wa) + self.ba

wgts = K.sin(dp) # or K.cos(.)

ret = K.concatenate([K.expand_dims(bias, -1), wgts], -1)

ret = K.reshape(ret, (-1, inputs.shape[1] * (self.k + 1)))

return ret

def compute_output_shape(self, input_shape):

return (input_shape[0], input_shape[1] * (self.k + 1))

ביצועים

חשוב לאמר: התחרות התחילה אתמול, הביצועים בטבלה ובמחברות האחרות לא מייצגים שום דבר.

אני כמעט בטוח שבשעות הקרובות שיא הטבלה ישבר מספר רב של פעמים.

למרות שכך עולה מהטקסט, אני לא טוען שרשתות הן פתרון קסם לסדרות זמן. [חוץ מwavenet. רשת משוגעת.]

אני טוען שרשתות הן פתרון לא פחות טוב מעצים (מבחינת ביצועים ויציבות).

כרגע "אימנתי במהירות" והגשתי מודל כזה לטבלה של TPS-22-01.

הוא כבר עקף רבים מה-GBM שבמחברות האחרות. כרגע אני מאמן עוד אחד באופן מלא, הוא יוגש ויהיה לכולם ברגע שהוא יהיה מוכן.

אני בהחלט מסכים שיותר קשה לאמן ולתחזק רשתות מעצים.

אבל אנחנו חזקים ונעשה את זה בכל זאת!

עוד בנושא: