<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Tal Perry</title><description>Shouting into the void , now with AI.</description><link>https://talperry.com/</link><language>en</language><copyright>Copyright 2026, Calvin Tran</copyright><lastBuildDate>Tue, 13 Jan 2026 00:00:00 +0100</lastBuildDate><generator>Hugo - gohugo.io</generator><docs>http://cyber.harvard.edu/rss/rss.html</docs><atom:link href="https://talperry.com//atom.xml" rel="self" type="application/atom+xml"/><item><title>חמש תובנות מעשיות להגשת מודלים עם Triton Inference Server</title><link>https://talperry.com/he/posts/genai/triton-inference-server/</link><description>&lt;p>Triton Inference Server הפך לבחירה פופולרית להגשת מודלים בסביבת פרודקשן, ומסיבה טובה: הוא מהיר, גמיש וחזק. עם זאת, שימוש אפקטיבי ב‑Triton דורש להבין איפה הוא מצטיין — ואיפה הוא ממש לא. הפוסט הזה אוסף חמש תובנות מעשיות מהפעלת Triton בפרודקשן שהלוואי והייתי מפנים מוקדם יותר.&lt;/p>
&lt;h2 id="בחרו-את-שכבת-ההגשה-הנכונה">בחרו את שכבת ההגשה הנכונה&lt;/h2>
&lt;p>לא כל המודלים שייכים ל‑Triton. &lt;strong>השתמשו ב‑vLLM עבור מודלים גנרטיביים; השתמשו ב‑Triton עבור עומסי היסק מסורתיים יותר.&lt;/strong>&lt;/p>
&lt;p>LLMs נמצאים בכל מקום כרגע, ו‑Triton מציע אינטגרציות גם עם TensorRT-LLM וגם עם vLLM. במבט ראשון זה גורם ל‑Triton להיראות כמו one-stop shop להגשת כל דבר, ממסווגי תמונות ועד מודלי שפה גדולים.&lt;/p>
&lt;p>בפועל, גיליתי ש‑Triton מוסיף מעט מאוד מעל פריסה “גולמית” של vLLM. זו לא ביקורת על Triton — זו פשוט השתקפות של עד כמה עומסים גנרטיביים שונים מהיסק קלאסי. רבות מהיכולות הטובות ביותר של Triton פשוט לא מתאימות בצורה נקייה לאופן שבו מגישים LLMs.&lt;/p>
&lt;p>כמה דוגמאות קונקרטיות מבהירות זאת:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>אצווה דינמית → אצווה רציפה&lt;/strong>
ה‑dynamic batcher של Triton ממתין לזמן קצר כדי לקבץ בקשות שלמות ואז מבצע אותן יחד. זה עובד מצוין עבור היסק עם צורות קלט קבועות. הגשה של LLMs, לעומת זאת, מרוויחה מ‑continuous batching, שבו בקשות חדשות מוכנסות לתוך אצווה פעילה בעוד אחרות מסיימות לייצר טוקנים. אמנם זה אפשרי טכנית דרך ה‑vLLM backend של Triton, אבל התפעול אינו פשוט ואינו ברור מאליו.&lt;/li>
&lt;/ul>
&lt;picture>
&lt;source srcset="https://talperry.com/en/posts/genai/triton-inference-server/dynamic-vs-continuous-batching_hu5809796310521240948.webp" type="image/webp">
&lt;source srcset="https://talperry.com/en/posts/genai/triton-inference-server/dynamic-vs-continuous-batching_hu6424849289932300575.png" type="image/png">
&lt;img src="https://talperry.com/en/posts/genai/triton-inference-server/dynamic-vs-continuous-batching_hu6424849289932300575.png" alt="אצווה דינמית מול אצווה רציפה" class="article-image" loading="lazy">
&lt;/picture>
&lt;ul>
&lt;li>&lt;strong>אריזת מודלים → שארדינג של מודלים&lt;/strong>
Triton מקל על אריזת כמה מודלים על GPU יחיד כדי לשפר ניצול. LLMs כמעט אף פעם לא מתאימים למודל הזה. אפילו מודלים צנועים נוטים לצרוך GPU שלם, וגדולים יותר דורשים שארדינג על פני GPUs או אפילו נודים. Triton לא מונע זאת, אבל גם לא ממש עוזר באופן משמעותי.&lt;/li>
&lt;/ul>
&lt;picture>
&lt;source srcset="https://talperry.com/en/posts/genai/triton-inference-server/model-sharding-vs-packing_hu16157470664919221625.webp" type="image/webp">
&lt;source srcset="https://talperry.com/en/posts/genai/triton-inference-server/model-sharding-vs-packing_hu11555892844646172857.png" type="image/png">
&lt;img src="https://talperry.com/en/posts/genai/triton-inference-server/model-sharding-vs-packing_hu11555892844646172857.png" alt="שארדינג של מודלים מול אריזת מודלים" class="article-image" loading="lazy">
&lt;/picture>
&lt;ul>
&lt;li>&lt;strong>מטמון בקשות → מטמון פרפיקס&lt;/strong>
ה‑cache המובנה של Triton עובד באמצעות שמירת זוגות בקשה–תגובה, דבר שהוא יעיל מאוד עבור עומסים דטרמיניסטיים. מודלים גנרטיביים, במקום זאת, מרוויחים ממטמון של מצב ביניים, כגון KV caches שממופתחים לפי פרפיקסים משותפים של פרומפט. זו בעיה שונה מהותית, כזו שמערכות הגשה “ילידיות LLM” מטפלות בה בצורה טבעית בהרבה.&lt;/li>
&lt;/ul>
&lt;p>בקיצור, בעקביות מצאתי שזה פשוט בהרבה לפרוס vLLM ישירות ולקבל מיד יתרונות של אצווה רציפה, שארדינג ומטמון פרפיקס, מאשר להוסיף שכבה של Triton מעל ולריב עם קונפיגורציה כדי להשיג התנהגות דומה.&lt;/p>
&lt;h2 id="הגנו-על-לטנטיות-באמצעות-timeouts-בצד-השרת">הגנו על לטנטיות באמצעות timeouts בצד השרת&lt;/h2>
&lt;p>אצווה דינמית היא יכולת הדגל של Triton. באמצעות אגירה של בקשות לחלון קצר שניתן להגדרה והרצה שלהן כאצווה, Triton משפר ניצול חומרה ומפחית כמות גדולה של מורכבות בצד הלקוח.&lt;/p>
&lt;p>עם זאת, יש כאן מוקש חשוב: כברירת מחדל, Triton לא יפנה (evict) בקשות שממתינות בתור.&lt;/p>
&lt;p>בעומס, לגמרי אפשרי ש‑Triton יצבור תור אחורי בזמן שלקוחות יגיעו ל‑timeout וימשיכו הלאה. אם &lt;code>max_queue_delay_microseconds&lt;/code> לא מוגדר, אותן בקשות שננטשו יכולות להישאר בתור ובסופו של דבר לרוץ, לצרוך משאבים בזמן שבקשות חדשות יותר ממתינות לתורן.&lt;/p>
&lt;p>התוצאה מעוותת אבל נפוצה:&lt;/p>
&lt;ul>
&lt;li>Triton מבזבז זמן על עיבוד בקשות שהלקוח כבר ויתר עליהן.&lt;/li>
&lt;li>הלטנטיות עולה בזמן שהתור מתרוקן מעבודה מיושנת.&lt;/li>
&lt;/ul>
&lt;p>הבעיה הזו חריפה במיוחד כשמשתמשים ב‑Python backend. בעוד שחלק מה‑backends הנייטיביים יכולים לזהות ביטול מצד הלקוח, ה‑Python backend משאיר את האחריות הזו ברובה לקוד המשתמש. ברגע שבקשה מגיעה לשיטת &lt;code>execute()&lt;/code> שלכם, היא לרוב תרוץ עד סיום אלא אם תבדקו במפורש אם בוטלה.&lt;/p>
&lt;p>אם אכפת לכם מלטנטיות — וכמעט בוודאות אכפת לכם — timeouts לתור בצד השרת אינם אופציונליים.&lt;/p>
&lt;h2 id="שמרו-על-ספריות-לקוח-מינימליות">שמרו על ספריות לקוח מינימליות&lt;/h2>
&lt;p>Triton דורש שלקוחות ידעו שמות מודלים, שמות טנסורים, צורות וסוגי נתונים. לחשוף את זה ישירות למפתחים אפליקטיביים זה לא נעים, ולכן לרוב משתלם לספק מעטפת לקוח קטנה.&lt;/p>
&lt;p>הבעיה מתחילה כשהמעטפת הזאת מפתחת שאיפות.&lt;/p>
&lt;p>ראיתי (וגם בניתי) ספריות לקוח שמנסות לעזור באמצעות הוספת retries, backoff או תכונות עמידות אחרות. בפועל, זה לעיתים קרובות מתהפך לרעה. ניסיון חוזר לבקשות שנכשלו בגלל עומס או קלט לא תקין יכול להגביר תעבורה בדיוק כשהמערכת כבר מתקשה, ולהפוך האטה זמנית ל‑denial-of-service שנגרם מעצמנו.&lt;/p>
&lt;p>אין הכוונה שלא להשתמש ב‑retries, אלא שלא להפוך אותם לבלתי נראים, ולאפשר לקוראים לזהות ולהיות מזוהים כאשר יש צורך לחזור ולבחון את לוגיקת ה‑retry.&lt;/p>
&lt;p>ההמלצה שלי פשוטה: שמרו על ספריות הלקוח משעממות. תנו להן לטפל בבניית הבקשה ותו לא. מימשו retries וטיפול בשגיאות בנקודת הקריאה, שם לאפליקציה יש את ההקשר ואת יכולות התצפית הנחוצות כדי לעשות את הדבר הנכון.&lt;/p>
&lt;h2 id="נצלו-את-המטמון-המובנה-של-triton">נצלו את המטמון המובנה של Triton&lt;/h2>
&lt;p>מטמון הבקשה–תגובה של Triton קל לפספס, אבל הוא יכול להיות אפקטיבי באופן מפתיע, במיוחד בסביבות ענן. מופעי GPU מגיעים לעיתים קרובות עם הרבה יותר זיכרון מערכת ממה שבאמת מנוצל, והקצאה של עוד כמה ג׳יגה-בייט למטמון יכולה לחסוך ל‑GPU עבודה מיותרת משמעותית.&lt;/p>
&lt;p>זו לא המלצה גורפת — הרבה עומסים לא ירוויחו מזה — אבל בהחלט שווה להתנסות. מעקב אחר שיעורי פגיעת מטמון (cache hit rates) לצד עומק התור יכול לומר מהר מאוד אם המטמון עוזר והאם לקוח מסוים מייצר תעבורה כפולה מיותרת.&lt;/p>
&lt;h2 id="העדיפו-threadpoolexecutor-לפרלליות-בצד-הלקוח">העדיפו ThreadPoolExecutor לפרלליות בצד הלקוח&lt;/h2>
&lt;p>בצד הלקוח, מצאתי שהדרך הפשוטה ביותר להוציא בקשות היסק במקביל היא גם הטובה ביותר: להשתמש ב‑thread pool.&lt;/p>
&lt;p>ב‑CPython, I/O של sockets משחרר את ה‑GIL. מכיוון שלקוח ה‑HTTP של Triton הוא בעיקר I/O-bound, זה הופך את &lt;code>ThreadPoolExecutor&lt;/code> לבחירה יעילה וישירה:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">infer&lt;/span>(inputs):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> model_client&lt;span style="color:#f92672">.&lt;/span>infer(inputs&lt;span style="color:#f92672">=&lt;/span>inputs)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> ThreadPoolExecutor(max_workers&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">8&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> pool:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> results &lt;span style="color:#f92672">=&lt;/span> list(pool&lt;span style="color:#f92672">.&lt;/span>map(infer, batch_of_requests))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>לגישה הזו יש כמה תכונות נחמדות:&lt;/p>
&lt;ol>
&lt;li>הלקוח לא צריך לממש לוגיקת אצווה.&lt;/li>
&lt;li>ה‑dynamic batcher של Triton יכול לאגד בקשות בין threads ואפילו בין לקוחות.&lt;/li>
&lt;li>המקביליות מוגבלת באופן טבעי, ומספקת סוג של backpressure.&lt;/li>
&lt;/ol>
&lt;p>כל עבודה ב‑Python בתוך &lt;code>infer&lt;/code> נשארת מסורבלת (serialized), ומה שמסתבר שהוא פיצ׳ר ולא באג: זה מונע מהלקוח להציף את השרת בעוד שהוא עדיין מאפשר I/O מקבילי יעיל.&lt;/p>
&lt;h2 id="סיכום">סיכום&lt;/h2>
&lt;p>Triton היא מערכת הגשה חזקה, אבל היא גם דעתנית. היא עובדת הכי טוב כשהאבסטרקציות שלה מתיישרות עם העומס שאתם מנסים להגיש.&lt;/p>
&lt;p>עבור עומסי היסק קלאסיים, ה‑batching, ה‑scheduling וה‑caching של Triton הם מהטובים שיש. עבור LLMs ומודלים גנרטיביים אחרים, מערכות ייעודיות כמו vLLM לרוב מתאימות יותר. הבנה של ההבחנה הזו — וקונפיגורציה “הגנתית” של Triton כשכן משתמשים בו — עוזרת מאוד בבניית מערכות היסק אמינות ובעלות לטנטיות נמוכה.&lt;/p></description><author/><guid>https://talperry.com/he/posts/genai/triton-inference-server/</guid><pubDate>Mon, 15 Dec 2025 10:00:00 +0200</pubDate></item><item><title>אני לא המייסד שהאפליקציה הזו ראויה לו</title><link>https://talperry.com/he/posts/scripture-app/</link><description>&lt;p>לפני שנצלול לסיבות מאחורי ההחלטה שלי, חשוב לדעת שאני אתאיסט יהודי-ישראלי שחי בברלין. הרקע הזה עשוי לגרום לכם לתהות למה בכלל אשקול לבנות אפליקציה כזו.&lt;/p>
&lt;p>למרות הזהות הבסיסית שלי, לפני שנתיים מכרתי &lt;a href="https://lighttag.io">חברה לכלי מפתחים&lt;/a> ונשבעתי: &amp;ldquo;לעולם לא שוב לבנות חברת כלי מפתחים.&amp;rdquo; במקום זאת, אני רוצה לרדוף משהו עם שוק יעד מוגדר היטב והצעת ערך ברורה, ובאופן אידיאלי כזה שלא דורש הון חיצוני.&lt;/p>
&lt;p>שינון כתבי קודש נוצריים, נישה בתוך שוק ה-Faithtech, נראה בתחילה מבטיח. אבל בסופו של דבר הגעתי למסקנה שזה לא מתאים לי. כאן אשקף איך מצאתי את הרעיון מלכתחילה ואיך הגעתי למסקנה שאני לא מתאים אליו.&lt;/p>
&lt;h2 id="המוטיבציה-הראשונית">המוטיבציה הראשונית&lt;/h2>
&lt;p>כמהגר בגרמניה, לימוד השפה המקומית היה אתגר מתמשך. השתמשתי ב-&lt;a href="https://apps.ankiweb.net/">Anki&lt;/a>, כלי שמשתמש ב&amp;quot;חזרה מרווחת&amp;quot; (“spaced repetition”), כדי להרחיב את אוצר המילים שלי בגרמנית.&lt;/p>
&lt;p>כשהבנתי עד כמה Anki אפקטיבי, נתקלתי ב&lt;a href="https://www.reddit.com/r/Anki/comments/eisra4/update_on_my_daughter_and_anki/">סיפור רדיט&lt;/a> מחמם לב על הורה שמלמד את הילד שלו לקרוא באמצעות הכלי הזה. בהשראת זאת, הצלחתי ללמד את הבן שלי בן החמש לקרוא עם Anki.
&lt;img
src="./giraffe.jpeg"
alt="GenAI יוצר דרך כיפית לקרוא את המילה ג׳ירפה"
loading="lazy"
decoding="async"
class="full-width"
/>
מעבר לכך, גיליתי ש-GenAI מאפשר לי לייצר כמויות גדולות של תוכן איכותי בעלות נמוכה, דבר שלפני כמה שנים היה יקר באופן בלתי אפשרי. באמצעות GenAI יצרתי לבן שלי תוכן חינוכי מהנה, כמו לאיית את המילה &amp;ldquo;Wurst&amp;rdquo; (נקניקייה) באמצעות תמונות של נקניקיות, ולהפיק משפטים בגרמנית מאוירים ומוקראים בסרטוני יוטיוב.&lt;/p>
&lt;p>יש משהו יפה בכך שאנשים רוצים להפנים את המילים שמעצבות אותם. הסתקרנתי מהאפשרות למכור את זה כמוצר להורים אחרים. אבל הבנתי שהשוק של אפליקציות חינוכיות שמלמדות ילדים לקרוא אינו מושך. נקודת המחיר נמוכה, עלויות רכישת לקוחות גבוהות, הרגולציות מורכבות, וקשה להשיג הכנסות ממנוי.&lt;/p>
&lt;p>למרות המכשולים האלה, המשכתי להתעניין בצומת שבין תוכן איכותי וזול ש-GenAI מאפשר לבין אלגוריתמי שינון. אבל לאחר שבעבר עשיתי את הטעות של לבנות משהו ואז לבדוק אם מישהו בכלל רוצה אותו, הפעם חיפשתי בעיה לפתור לפני שאפתח פתרון.&lt;/p>
&lt;p>יום אחד, מתוך סקרנות, יצאתי למאמץ לשנן כמה פרקים מהתנ&amp;quot;ך בעברית באמצעות Anki. למרות שזה יכול להיות מוצר, המיקוד היהודי שלו מגביל את השוק הפוטנציאלי בגלל האוכלוסייה היהודית העולמית הקטנה יחסית.&lt;/p>
&lt;p>לעומת זאת, יש הרבה נוצרים בארה״ב עם סמארטפונים והכנסה פנויה יחסית גבוהה. זה יכול להיות שוק בר-קיימא, ולכן התחלתי לחקור שינון כתבי קודש עבור נוצרים.&lt;/p>
&lt;p>הייתי צריך גם להודות שבעוד שהשוק גדול וברור, זה לא הסיפור שלי לספר, ולא קהל שאני יכול להבין באינטואיציה מתוך ניסיון חיים.&lt;/p>
&lt;h2 id="הצלילה-לעומק">הצלילה לעומק&lt;/h2>
&lt;p>כמה חיפושי גוגל מהירים גילו שיש בערך 200 מיליון נוצרים בארה״ב, כש-140 מיליון מזהים את עצמם כאוונגליסטים. אמנם לא הבנתי לגמרי את המשמעות של זה, אבל ידעתי מהרשתות החברתיות שאוונגליסטים הם אדוקים ומוכנים להשקיע ברוחניות שלהם.&lt;/p>
&lt;p>הרעיון נעשה עוד יותר מושך כשגיליתי את העושר של הנתונים הזמינים על השוק הפוטנציאלי. בניגוד לניסיון שלי עם כלי מפתחים, שבו פילוח שוק היה אתגר, כאן מצאתי נתונים מפורטים של &lt;a href="https://www.pewresearch.org/religion/2023/06/02/use-of-apps-and-websites-in-religious-life/">Pew Research&lt;/a> על שימוש באפליקציות בקרב זרמים שונים, הכנסה פנויה והתפלגות גאוגרפית.&lt;/p>
&lt;p>עם הנתונים האלה יכולתי למקד פלחי שוק ספציפיים בצורה אפקטיבית, להתאים שפה, דימויים ואסטרטגיות שיווק בהתאם. השתכנעתי שאם אנשים מוכנים לשלם על הפתרון הזה, אוכל לתכנן ניסויי שיווק יעילים כדי להגדיל מכונת מכירות בקנה מידה.&lt;/p>
&lt;h2 id="המכשול-בפיתוח-המוצר">המכשול בפיתוח המוצר&lt;/h2>
&lt;p>בעוד ששיווק סקיילבילי נשמע מבטיח, קמפיין שיווקי צריך מוצר עובד כדי להביא לשוק. מה פירוש &amp;ldquo;עובד&amp;rdquo; בהקשר הזה? עבור המשתמשים זה אומר שהאפליקציה עוזרת להם לשנן כתבי קודש.&lt;/p>
&lt;p>אבל עבורי, האדם שישקיע זמן וכסף בבנייה, אפליקציה &amp;ldquo;עובדת&amp;rdquo; פירושה אפליקציה שממירה משתמשים ללקוחות משלמים ומשמרת אותם.&lt;/p>
&lt;p>לראות מוצר כמכונה שמייצרת הכנסה מסבך את ההיקף של MVP. זה כולל מיקרו-קופי מתאים, תמחור נכון, לספק רגע &amp;ldquo;וואו!&amp;rdquo; מהיר, ולהבטיח שימור משתמשים.&lt;/p>
&lt;p>למרות שזה אפשרי, זה נשמע מאתגר, יקר וגוזל זמן. שאלתי את עצמי כמה שאלות: האם אני יכול להשיג זאת בלי מימון מקרנות הון סיכון? כנראה שלא. האם יש לי מומחיות ביצירת אפליקציות צרכניות שממירות? לא. האם יש לי תובנות לגבי איך להפוך את האפליקציה לויראלית? לא.&lt;/p>
&lt;p>ההתלהבות שלי דעכה, ותובנה שעלתה בשיחה עם אשתי חתמה את ההחלטה.&lt;/p>
&lt;h2 id="אתגר-השיווק">אתגר השיווק&lt;/h2>
&lt;p>בזמן שדיברנו על הרעיון בפקקים, מריה שרה יחד עם &lt;a href="https://genius.com/Carrie-underwood-before-he-cheats-lyrics">“Before He Cheats” של קארי אנדרווד&lt;/a>, שבה היא תופסת יקום שלם עם:&lt;/p>
&lt;blockquote>
&lt;p>&amp;ldquo;Right now, he&amp;rsquo;s probably buying her some fruity little drink &amp;lsquo;Cause she can&amp;rsquo;t shoot a whiskey,&amp;rdquo;&lt;/p>
&lt;/blockquote>
&lt;picture>
&lt;source srcset="https://talperry.com/en/posts/scripture-app/before-he-cheats_hu17757550046027709510.webp" type="image/webp">
&lt;source srcset="https://talperry.com/en/posts/scripture-app/before-he-cheats_hu17757550046027709510.webp" type="image/webp">
&lt;img src="https://talperry.com/en/posts/scripture-app/before-he-cheats_hu17757550046027709510.webp" alt="קארי אנדרווד — Before He Cheats" class="article-image" loading="lazy">
&lt;/picture>
&lt;p>זה הדגיש את ההבנה העמוקה של כותבי השיר את הקהל שלהם. &amp;ldquo;לירות וויסקי&amp;rdquo; הוא ביטוי מעורר-דמיון עבור הקהל שלהם, אבל עבורי הוא כמעט חסר משמעות (ישראלי בברלין שבה וויסקי אינו עוגן תרבותי). כותבי השיר הכירו את הקהל שלהם כל כך טוב שהם יכלו לאלתר ביטויים מעוררי-דמיון כאלה.&lt;/p>
&lt;p>אם הייתי מוכר תוכנת שינון כתבי קודש לנוצרים אמריקאים, מה הייתי יכול להבין עליהם באינטואיציה? איזו רלוונטיות או יתרון יש לי ביצירת מוצר שנוגע בזהות שאינני חולק?&lt;/p>
&lt;p>הבנתי שלא רק חסרה לי שפת השיווק—חסר לי ההקשר החי שמסביר למה שינון כתבי קודש חשוב מלכתחילה.&lt;/p>
&lt;p>אפשר לפתור את הבעיה הזו בכסף. הייתי יכול לשכור סוכנות שמתמחה בסגמנט הנוצרי. אבל בלי מוצר מוכן לשוק, למה להשקיע בשיווק? ובלי אסטרטגיית שיווק ברורה, למה לבנות את המוצר?&lt;/p>
&lt;h2 id="התאמה-אישית-והבנת-השוק">התאמה אישית והבנת השוק&lt;/h2>
&lt;p>גם אתגרי המוצר וגם אתגרי השיווק אפשר לפתור עם זמן וכסף. אבל הייתי צריך לשאול את עצמי: כמה זמן? כמה מהחיים שלי אני מוכן להקדיש לבניית תוכנה לשינון כתבי קודש ולמכירה שלה?&lt;/p>
&lt;p>כן, הייתי רוצה לעזור לאנשים להעמיק את הרוחניות שלהם. כן, זה יהיה מגרה אינטלקטואלית. כן, זה יכול להיות רווחי. אבל אין לי קשר אישי למוצר או לקהילה. האם כך אני רוצה להעביר את 5–10 השנים הבאות של חיי?&lt;/p>
&lt;p>לא, זה לא.&lt;/p>
&lt;p>השאלה לא הייתה אם אני יכול לבנות את זה—אלא למה שאעשה זאת. יש הרבה בעיות טובות, אבל לא כולן שלי.&lt;/p>
&lt;h2 id="סיכום">סיכום&lt;/h2>
&lt;p>בהתחלה התלהבתי מההזדמנות הזו כי היא נשענה על טכנולוגיה מוכרת, הייתה לה שוק גדול ומוגדר היטב, והיא נראתה פוטנציאלית רווחית. אבל הבנתי שבלי יתרון אישי בתחום הזה, העלות (בזמן ובכסף) לפתח אפילו MVP גבוהה יותר ממה שהייתי מוכן להשקיע.&lt;/p>
&lt;p>חקר שוק הפך לחקר זהות.&lt;/p></description><author/><guid>https://talperry.com/he/posts/scripture-app/</guid><pubDate>Tue, 14 May 2024 10:07:22 +0200</pubDate></item><item><title>שיטות קונבולוציוניות לטקסט</title><link>https://talperry.com/he/posts/classics/cmft/</link><description>&lt;h3 id="tldr">tl;dr&lt;/h3>
&lt;ul>
&lt;li>RNN-ים עובדים מצוין לטקסט, אבל קונבולוציות יכולות לעשות את זה מהר יותר&lt;/li>
&lt;li>כל חלק במשפט יכול להשפיע על הסמנטיקה של מילה. לכן אנחנו רוצים שהרשת שלנו תראה את כל הקלט בבת אחת&lt;/li>
&lt;li>יצירת שדה קליטה גדול כל כך יכולה לגרום לגרדיאנטים להיעלם ולרשתות שלנו להיכשל&lt;/li>
&lt;li>אפשר לפתור את בעיית היעלמות הגרדיאנט בעזרת DenseNets או קונבולוציות מדוללות&lt;/li>
&lt;li>לפעמים צריך לייצר טקסט. אפשר להשתמש ב״דקונבולוציות״ כדי לייצר פלטים באורך שרירותי.&lt;/li>
&lt;/ul>
&lt;h3 id="מבוא">מבוא&lt;/h3>
&lt;p>בשלוש השנים האחרונות תחום ה‑NLP עבר מהפכה עצומה בזכות למידה עמוקה. מובילת המהפכה הזו הייתה הרשת הנוירונית החוזרת, ובמיוחד המימוש שלה כ‑LSTM. במקביל, תחום הראייה הממוחשבת עוצב מחדש על ידי רשתות נוירונים קונבולוציוניות. הפוסט הזה בוחן מה אנחנו, ״אנשי הטקסט״, יכולים ללמוד מהחברים שלנו שעושים ראייה.&lt;/p>
&lt;h3 id="משימות-nlp-נפוצות">משימות NLP נפוצות&lt;/h3>
&lt;p>כדי להציב את הבמה ולהסכים על אוצר מילים, הייתי רוצה להציג כמה מהמשימות הנפוצות יותר ב‑NLP. לשם עקביות, אניח שכל קלטי המודל שלנו הם תווים, ושה״יחידה הנצפית״ שלנו היא משפט. שתי ההנחות הללו הן רק מטעמי נוחות, ואתם יכולים להחליף תווים במילים ומשפטים במסמכים אם תרצו.&lt;/p>
&lt;h4 id="סיווג">סיווג&lt;/h4>
&lt;p>אולי הטריק הוותיק ביותר בספר: לעיתים קרובות אנחנו רוצים לסווג משפט. למשל, נרצה לסווג נושא של אימייל כמרמז על ספאם, לנחש את הסנטימנט של ביקורת מוצר, או לשייך נושא למסמך.&lt;/p>
&lt;p>הדרך הישירה להתמודד עם משימה כזו בעזרת RNN היא להזין לתוכו את כל המשפט, תו אחר תו, ואז להתבונן במצב החבוי הסופי של ה‑RNN.&lt;/p>
&lt;h4 id="תיוג-רצפים">תיוג רצפים&lt;/h4>
&lt;p>משימות תיוג רצפים הן משימות שמחזירות פלט עבור כל קלט. דוגמאות כוללות תיוג חלקי דיבר או משימות זיהוי ישויות. אף שמודל ה‑LSTM הבסיסי רחוק מלהיות מצב‑האמנות, הוא קל למימוש ומציע תוצאות משכנעות. ראו את &lt;a href="https://arxiv.org/pdf/1508.01991.pdf">המאמר הזה&lt;/a> לארכיטקטורה מפורטת יותר&lt;/p>
&lt;p>&lt;img
src="./bilstm-ner-sequence-labeling.webp"
alt="ארכיטקטורת תיוג רצפים עם LSTM דו-כיווני"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h4 id="יצירת-רצפים">יצירת רצפים&lt;/h4>
&lt;p>אפשר לטעון שהתוצאות המרשימות ביותר ב‑NLP בזמן האחרון היו בתרגום. תרגום הוא מיפוי מרצף אחד לאחר, בלי הבטחה לגבי אורך משפט הפלט. למשל, תרגום המילים הראשונות של התנ״ך מעברית לאנגלית הוא בראשית = “In the Beginning”.&lt;/p>
&lt;p>בלב ההצלחה הזו נמצא מסגרת Sequence to Sequence (המכונה גם encoder decoder), מתודולוגיה ל״דחוס״ רצף לקוד ואז לפענח אותו לרצף אחר. דוגמאות בולטות כוללות תרגום (מקודדים עברית ומפענחים לאנגלית), תיאור תמונות (מקודדים תמונה ומפענחים תיאור טקסטואלי של התוכן שלה)&lt;/p>
&lt;p>&lt;img
src="./cnn-attention-image-captioning.webp"
alt="צינור תיאור תמונות עם מאפייני CNN שמזינים מפענח LSTM עם קשב"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>שלב ה‑Encoder הבסיסי דומה לסכמה שתיארנו עבור סיווג. מה שמדהים הוא שאפשר לבנות מפענח שלומד לייצר פלטים באורך שרירותי.&lt;/p>
&lt;p>שתי הדוגמאות למעלה הן בעצם תרגום, אבל יצירת רצפים היא קצת רחבה יותר מזה. OpenAI לאחרונה &lt;a href="https://blog.openai.com/unsupervised-sentiment-neuron/">פרסמה מאמר&lt;/a> שבו הם לומדים לייצר ״ביקורות אמזון״ תוך שליטה בסנטימנט של הפלט&lt;/p>
&lt;p>&lt;img
src="./sentiment-controlled-examples.webp"
alt="ביקורות אמזון שנוצרו עם סנטימנט שמוגבל לחיובי או שלילי"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>עוד אהובה אישית היא המאמר &lt;a href="https://arxiv.org/pdf/1511.06349.pdf">Generating Sentences from a Continuous Space&lt;/a>. במאמר הזה הם אימנו אוטואנקודר וריאציוני על טקסט, מה שהוביל ליכולת לאינטרפולציה בין שני משפטים ולקבל תוצאות קוהרנטיות.&lt;/p>
&lt;p>&lt;img
src="./sentence-interpolation-samples.webp"
alt="דוגמאות לאינטרפולציה בין משפטים מאוטואנקודר וריאציוני"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h3 id="דרישות-מארכיטקטורת-nlp">דרישות מארכיטקטורת NLP&lt;/h3>
&lt;p>מה שמשותף לכל המימושים שבדקנו הוא שהם משתמשים בארכיטקטורה חוזרת, בדרך כלל LSTM (אם אתם לא בטוחים מה זה, &lt;a href="http://karpathy.github.io/2015/05/21/rnn-effectiveness/">כאן&lt;/a> יש מבוא מצוין). ראוי לציין שלאף אחת מהמשימות אין ״חוזר״ בשם שלה, ושאף אחת לא הזכירה LSTM‑ים. עם זה בראש, בואו נעצור רגע לחשוב מה RNN‑ים ובמיוחד LSTM‑ים מספקים שהופך אותם לכל כך נפוצים ב‑NLP.&lt;/p>
&lt;h4 id="גודל-קלט-שרירותי">גודל קלט שרירותי&lt;/h4>
&lt;p>רשת נוירונים feed forward סטנדרטית כוללת פרמטר עבור כל קלט. זה נעשה בעייתי כשעובדים עם טקסט או תמונות מכמה סיבות.&lt;/p>
&lt;ol>
&lt;li>זה מגביל את גודל הקלט שאפשר לטפל בו. לרשת שלנו יהיה מספר סופי של נודים קלט, והיא לא תוכל לגדול מעבר לכך.&lt;/li>
&lt;li>אנחנו מאבדים הרבה מידע משותף. קחו למשל את המשפטים “I like to drink beer a lot” ו‑“I like to drink a lot of beer”. רשת feed forward הייתה צריכה ללמוד על המושג “a lot” פעמיים, כי הוא מופיע בכל פעם בנוד קלט אחר.&lt;/li>
&lt;/ol>
&lt;p>רשתות נוירונים חוזרות פותרות את הבעיה הזו. במקום שיהיה נוד לכל קלט, יש לנו ״קופסה״ גדולה של נודים שאנחנו מפעילים על הקלט שוב ושוב. ה״קופסה״ לומדת סוג של פונקציית מעבר, מה שאומר שהפלטים מקיימים יחס רקורסיה כלשהו, ומכאן השם.&lt;/p>
&lt;p>זכרו ש״אנשי הראייה״ קיבלו הרבה מאותו אפקט עבור תמונות בעזרת קונבולוציות. כלומר, במקום שיהיה נוד קלט לכל פיקסל, קונבולוציות אפשרו שימוש חוזר באותה קבוצה קטנה של פרמטרים לאורך כל התמונה.&lt;/p>
&lt;h4 id="תלות-ארוכת-טווח">תלות ארוכת טווח&lt;/h4>
&lt;p>ההבטחה של RNN‑ים היא היכולת שלהם למדל תלות ארוכת טווח באופן מובלע. התמונה למטה לקוחה מ‑OpenAI. הם אימנו מודל שבסופו של דבר זיהה סנטימנט וצבע את הטקסט, תו אחר תו, לפי פלט המודל. שימו לב איך המודל רואה את המילה “best” ומפעיל סנטימנט חיובי שהוא שומר עליו לאורך יותר מ‑100 תווים. זה לכידת תלות ארוכת טווח.&lt;/p>
&lt;p>&lt;img
src="./sentiment-heatmap.webp"
alt="מפת חום של הפעלת נוירון סנטימנט על טקסט ביקורת"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>התיאוריה של RNN‑ים מבטיחה לנו תלות ארוכת טווח out of the box. בפועל זה קצת יותר קשה. כשאנחנו לומדים באמצעות backpropagation, אנחנו צריכים להפיץ את האות לאורך כל יחס הרקורסיה. העניין הוא שבכל צעד אנחנו בסוף מכפילים במספר. אם המספרים האלה בדרך כלל קטנים מ‑1, האות שלנו יגיע מהר מאוד ל‑0. אם הם גדולים מ‑1, האות יתפוצץ.&lt;/p>
&lt;p>הבעיות הללו נקראות היעלמות והתפוצצות גרדיאנט, והן בדרך כלל נפתרות על ידי LSTM‑ים ועוד כמה טריקים חכמים. אני מזכיר אותן עכשיו כי אנחנו נפגוש את הבעיות האלה שוב עם קונבולוציות ונצטרך דרך אחרת להתמודד איתן.&lt;/p>
&lt;h3 id="יתרונות-של-קונבולוציות">יתרונות של קונבולוציות&lt;/h3>
&lt;p>עד עכשיו ראינו כמה LSTM‑ים נהדרים, אבל הפוסט הזה עוסק בקונבולוציות. ברוח של &lt;em>אל תתקן מה שלא שבור&lt;/em>, אנחנו צריכים לשאול את עצמנו למה בכלל נרצה להשתמש בקונבולוציות.&lt;/p>
&lt;p>תשובה אחת היא: “Because we can”.&lt;/p>
&lt;p>אבל יש עוד שתי סיבות משכנעות להשתמש בקונבולוציות: מהירות והקשר.&lt;/p>
&lt;h4 id="הקבלה">הקבלה&lt;/h4>
&lt;p>RNN‑ים פועלים סדרתית; הפלט עבור הקלט השני תלוי בראשון ולכן אי אפשר להקביל RNN. לקונבולוציות אין בעיה כזו: כל ״טלאי״ שעליו פועל קרנל קונבולוציה הוא בלתי תלוי באחרים, כלומר אפשר לעבור על כל שכבת הקלט במקביל.&lt;/p>
&lt;p>יש לזה מחיר: כפי שנראה, צריך לערום קונבולוציות לשכבות עמוקות כדי לראות את כל הקלט, וכל אחת מהשכבות הללו מחושבת סדרתית. אבל החישובים בכל שכבה מתרחשים במקביל וכל חישוב בודד קטן (בהשוואה ל‑LSTM), כך שבפועל מקבלים האצה גדולה.&lt;/p>
&lt;p>כשיצאתי לכתוב את זה היו לי רק הניסיון האישי שלי ו‑ByteNet של גוגל כדי לגבות את הטענה הזו. רק השבוע, פייסבוק פרסמה את מודל התרגום הקונבולוציוני לחלוטין שלה ודיווחה על האצה פי 9 לעומת מודלים מבוססי LSTM.&lt;/p>
&lt;h4 id="לראות-את-כל-הקלט-בבת-אחת">לראות את כל הקלט בבת אחת&lt;/h4>
&lt;p>LSTM‑ים קוראים את הקלט משמאל לימין (או מימין לשמאל), אבל לפעמים נרצה שההקשר מסוף המשפט ישפיע על מחשבות הרשת לגבי תחילתו. למשל, יכול להיות לנו משפט כמו “I’d love to buy your product. Not!” ונרצה שהשלילה בסוף תשפיע על כל המשפט.&lt;/p>
&lt;p>עם LSTM‑ים אנחנו משיגים זאת על ידי הרצה של שני LSTM‑ים: אחד משמאל לימין והשני מימין לשמאל, ואז מצרפים את הפלטים שלהם. זה עובד טוב בפועל אבל מכפיל את עומס החישוב.&lt;/p>
&lt;p>קונבולוציות, לעומת זאת, מגדילות ״שדה קליטה״ (receptive field) כשאנחנו מערימים עוד ועוד שכבות. המשמעות היא שכברירת מחדל, כל ״צעד״ בייצוג של הקונבולוציה רואה את כל הקלט שבשדה הקליטה שלו — מה שלפניו ומה שאחריו. אני לא מכיר טיעון מכריע שזה בהכרח טוב יותר מ‑LSTM, אבל זה נותן לנו את האפקט הרצוי בצורה נשלטת ובעלות חישובית נמוכה.&lt;/p>
&lt;p>עד עכשיו הגדרנו את תחום הבעיה ודיברנו קצת על היתרונות המושגיים של קונבולוציות ל‑NLP. מכאן והלאה הייתי רוצה לתרגם את המושגים הללו לשיטות מעשיות שנוכל להשתמש בהן כדי לנתח ולבנות את הרשתות שלנו.&lt;/p>
&lt;h3 id="קונבולוציות-מעשיות-לטקסט">קונבולוציות מעשיות לטקסט&lt;/h3>
&lt;p>&lt;img
src="./convolution-animation.webp"
alt="הדמיה מונפשת של קרנל קונבולוציה שמחליק על תמונה"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>כנראה ראיתם אנימציה כמו זו למעלה שממחישה מה עושה קונבולוציה. למטה נמצאת תמונת הקלט, למעלה התוצאה, והצל האפור הוא קרנל הקונבולוציה שמיושם שוב ושוב.&lt;/p>
&lt;p>הכול נראה הגיוני, חוץ מזה שהקלט בתמונה הוא תמונה עם שני ממדים מרחביים (גובה ורוחב). אנחנו מדברים על טקסט, שיש לו רק ממד אחד, והוא זמני ולא מרחבי.&lt;/p>
&lt;p>לכל צורך מעשי, זה לא משנה. אנחנו רק צריכים לחשוב על הטקסט כתמונה ברוחב &lt;em>n&lt;/em> ובגובה 1. Tensorflow מספקת פונקציית conv1d שעושה זאת עבורנו, אבל היא לא חושפת פעולות קונבולוציה אחרות בגרסת ה‑1d שלה.&lt;/p>
&lt;p>כדי להפוך את הרעיון ״טקסט = תמונה בגובה 1״ לקונקרטי, בואו נראה איך היינו משתמשים באופרטור הקונבולוציה הדו‑ממדי של Tensorflow על רצף של טוקנים מוטמעים.&lt;/p>
&lt;p>אז מה שאנחנו עושים כאן הוא לשנות את הצורה של הקלט עם tf.expand_dims כך שהוא יהפוך ל״תמונה בגובה 1״. אחרי הרצת אופרטור הקונבולוציה הדו‑ממדי אנחנו מסירים (squeeze) את הממד העודף.&lt;/p>
&lt;h3 id="היררכיה-ושדות-קליטה">היררכיה ושדות קליטה&lt;/h3>
&lt;p>&lt;img
src="./cnn-receptive-hierarchy.webp"
alt="היררכיה של פילטרים ב-CNN שמתקדמת מקצוות לפרצופים"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>רבים מאיתנו ראו תמונות כמו זו למעלה. היא מראה בערך את היררכיית ההפשטות ש‑CNN לומדת על תמונות. בשכבה הראשונה, הרשת לומדת קצוות בסיסיים. בשכבה הבאה היא משלבת את הקצוות כדי ללמוד מושגים מופשטים יותר כמו עיניים ואפים. לבסוף היא משלבת אותם כדי לזהות פרצופים ספציפיים.&lt;/p>
&lt;p>עם זה בראש, אנחנו צריכים לזכור שכל שכבה לא רק לומדת צירופים מופשטים יותר של השכבה הקודמת. שכבות עוקבות, במובלע או במפורש, רואות יותר מהקלט.&lt;/p>
&lt;p>&lt;img
src="./hierarchical-receptive-field-tree.webp"
alt="עץ שדה קליטה שמראה אגירה שכבתית על פני קלטים"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h4 id="הגדלת-שדה-הקליטה">הגדלת שדה הקליטה&lt;/h4>
&lt;p>בְּראייה, לעיתים קרובות נרצה שהרשת תזהה אובייקט אחד או יותר בתמונה תוך התעלמות מאחרים. כלומר, נתעניין בתופעה מקומית אבל לא בקשר שנפרש על פני כל הקלט.&lt;/p>
&lt;p>&lt;img
src="./hotdog-classifier-example.webp"
alt="אפליקציית מסווג נקניקייה חמה שמשווה תמונות של נקניקייה ונעל"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>טקסט עדין יותר, כי לעיתים קרובות נרצה שלייצוגים ביניים של הנתונים שלנו יהיה כמה שיותר הקשר סביבתי. במילים אחרות, אנחנו רוצים שדה קליטה גדול ככל האפשר. יש כמה דרכים לגשת לזה.&lt;/p>
&lt;h4 id="פילטרים-גדולים-יותר">פילטרים גדולים יותר&lt;/h4>
&lt;p>הדרך הראשונה והברורה ביותר היא להגדיל את גודל הפילטר, כלומר לבצע קונבולוציית [1x5] במקום [1x3]. בעבודה שלי עם טקסט לא קיבלתי תוצאות טובות עם זה, ואציע את ההשערות שלי למה.&lt;/p>
&lt;p>בדומיין שלי אני בעיקר מתמודד עם קלט ברמת תו ועם טקסטים עשירים מאוד מורפולוגית. אני חושב על (לפחות השכבות הראשונות) של קונבולוציה כעל למידת n‑grams, כך שרוחב הפילטר תואם ביגרמים, טריגרמים וכו׳. כשגורמים לרשת ללמוד n‑grams גדולים יותר מוקדם, חושפים אותה לפחות דוגמאות, כי יש יותר מופעים של “ab” בטקסט מאשר של “abb”.&lt;/p>
&lt;p>מעולם לא הוכחתי את הפרשנות הזו, אבל באופן עקבי קיבלתי תוצאות גרועות יותר עם רוחבי פילטר גדולים מ‑3.&lt;/p>
&lt;h4 id="הוספת-שכבות">הוספת שכבות&lt;/h4>
&lt;p>כפי שראינו בתמונה למעלה, הוספת שכבות תגדיל את שדה הקליטה. &lt;a href="https://medium.com/u/b04dc6044cc">Dang Ha The Hien&lt;/a> כתב &lt;a href="https://medium.com/@nikasa1889/a-guide-to-receptive-field-arithmetic-for-convolutional-neural-networks-e0f514068807">מדריך מצוין&lt;/a> לחישוב שדה הקליטה בכל שכבה, ואני ממליץ לכם לקרוא.&lt;/p>
&lt;p>להוספת שכבות יש שני אפקטים מובחנים אבל קשורים. הראשון, שמוזכר הרבה, הוא שהמודל ילמד לבצע הפשטות ברמה גבוהה יותר על הקלטים שהוא מקבל (Pixels =&amp;gt;Edges =&amp;gt; Eyes =&amp;gt; Face). השני הוא ששדה הקליטה גדל בכל צעד.&lt;/p>
&lt;p>זה אומר שעומק מספיק יכול לאפשר לרשת שלנו להביט בכל שכבת הקלט, אולי דרך ערפל של הפשטות. למרבה הצער, כאן בעיית היעלמות הגרדיאנט עשויה להרים את ראשה המכוער.&lt;/p>
&lt;h4 id="הפשרה-בין-גרדיאנט-לשדה-קליטה">הפשרה בין גרדיאנט לשדה קליטה&lt;/h4>
&lt;p>רשתות נוירונים הן רשתות שדרכן מידע זורם. ב‑forward pass הקלט זורם ומשתנה, בתקווה שיהפוך לייצוג שמתאים יותר למשימה שלנו. בשלב ה‑back אנחנו מפיצים אות — הגרדיאנט — חזרה דרך הרשת. בדיוק כמו ב‑RNN‑ים וניליים, האות הזה מוכפל לעיתים תכופות, ואם הוא עובר דרך סדרה של מספרים קטנים מ‑1 הוא ידעך ל‑0. זה אומר שלרשת שלנו יהיה מעט מאוד אות ללמוד ממנו.&lt;/p>
&lt;p>זה משאיר אותנו עם סוג של tradeoff. מצד אחד, נרצה לקלוט כמה שיותר הקשר. מצד שני, אם ננסה להגדיל את שדות הקליטה על ידי ערימת שכבות אנחנו מסתכנים בהיעלמות גרדיאנטים ובכישלון ללמוד משהו.&lt;/p>
&lt;h3 id="שתי-פתרונות-לבעיית-היעלמות-הגרדיאנט">שתי פתרונות לבעיית היעלמות הגרדיאנט&lt;/h3>
&lt;p>למזלנו, הרבה אנשים חכמים חשבו על הבעיות האלה. למזל גדול עוד יותר, אלו לא בעיות ייחודיות לטקסט: גם ״אנשי הראייה״ רוצים שדות קליטה גדולים וגרדיאנטים עשירים במידע. בואו נסתכל על כמה מהרעיונות המטורפים שלהם ונשתמש בהם כדי להעצים את התהילה הטקסטואלית שלנו.&lt;/p>
&lt;h4 id="חיבורים-שיוריים">חיבורים שיוריים&lt;/h4>
&lt;p>2016 הייתה עוד שנה נהדרת עבור ״אנשי הראייה״, עם לפחות שתי ארכיטקטורות פופולריות מאוד שצמחו: &lt;a href="https://arxiv.org/abs/1512.03385">ResNets&lt;/a> ו‑&lt;a href="https://arxiv.org/abs/1608.06993">DenseNets&lt;/a> (המאמר של DenseNet, במיוחד, כתוב באופן יוצא מן הכלל וממש שווה קריאה). שתיהן מתמודדות עם אותה בעיה: ״איך אני עושה את הרשת שלי מאוד עמוקה בלי לאבד את אות הגרדיאנט?״&lt;/p>
&lt;p>&lt;a href="https://medium.com/u/18dfe63fa7f0">Arthur Juliani&lt;/a> כתב סקירה נהדרת של &lt;a href="https://chatbotslife.com/resnets-highwaynets-and-densenets-oh-my-9bb15918ee32">Resnet, DenseNets and Highway networks&lt;/a> עבור מי שמחפש פרטים והשוואה. אני אתעכב בקצרה על DenseNets, שמביאות את הרעיון לליטרליות קיצונית.&lt;/p>
&lt;p>&lt;img
src="./densenet-connections.webp"
alt="בלוק DenseNet עם שכבות קונבולוציה מחוברות בצפיפות"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>הרעיון הכללי הוא להקטין את המרחק בין האות שמגיע מה‑loss של הרשת לבין כל שכבה בנפרד. הדרך לעשות זאת היא להוסיף חיבור שיורי/ישיר בין כל שכבה לבין קודמותיה. כך הגרדיאנט יכול לזרום מכל שכבה לקודמותיה ישירות.&lt;/p>
&lt;p>DenseNets עושות זאת בדרך מעניינת במיוחד. הן מצרפות (concatenate) את הפלט של כל שכבה אל הקלט שלה כך ש:&lt;/p>
&lt;ol>
&lt;li>מתחילים באמבדינג של הקלטים שלנו, נניח בממד 10.&lt;/li>
&lt;li>השכבה הראשונה מחשבת 10 מפות מאפיינים. היא מוציאה את 10 מפות המאפיינים מצורפות לאמבדינג המקורי.&lt;/li>
&lt;li>השכבה השנייה מקבלת כקלט וקטורים בממד 20 (10 מהקלט ו‑10 מהשכבה הקודמת) ומחשבת עוד 10 מפות מאפיינים. כך היא מוציאה וקטורים בממד 30.&lt;/li>
&lt;/ol>
&lt;p>וכך הלאה והלאה, לכמה שכבות שתרצו. המאמר מתאר המון טריקים כדי לשמור על ניהוליות ויעילות, אבל זו ההנחה הבסיסית ובעיית היעלמות הגרדיאנט נפתרת.&lt;/p>
&lt;p>יש עוד שני דברים שהייתי רוצה להדגיש.&lt;/p>
&lt;ol>
&lt;li>קודם הזכרתי ששכבות עליונות רואות את הקלט המקורי אולי דרך שכבות של הפשטה. אחד היתרונות של צירוף הפלטים של כל שכבה הוא שהאות המקורי מגיע לשכבות הבאות ללא פגיעה, כך שלכל השכבות יש ראייה ישירה של מאפיינים ברמה נמוכה — למעשה זה מסיר חלק מה״ערפל״.&lt;/li>
&lt;li>טריק החיבור השיורי דורש שלכל השכבות תהיה אותה צורה. זה אומר שאנחנו צריכים לרפד (pad) כל שכבה כך שלקלט ולפלט יהיו אותם ממדים מרחביים [1Xwidth]. זה אומר שכשלעצמו, סוג כזה של ארכיטקטורה יעבוד למשימות תיוג רצפים (שבהן לקלט ולפלט יש אותם ממדים מרחביים) אבל ידרוש יותר עבודה עבור משימות קידוד וסיווג (שבהן צריך לצמצם את הקלט לווקטור בגודל קבוע או סט וקטורים). המאמר של DenseNet למעשה מטפל בזה כי המטרה שלהם היא סיווג, ונרחיב על הנקודה הזו בהמשך.&lt;/li>
&lt;/ol>
&lt;h4 id="קונבולוציות-מדוללות">קונבולוציות מדוללות&lt;/h4>
&lt;p>קונבולוציות מדוללות, המכונות גם &lt;em>atrous&lt;/em> קונבולוציות, או קונבולוציות עם חורים, הן שיטה נוספת להגדלת שדה הקליטה בלי להרגיז את אלי הגרדיאנט. כשדיברנו עד עכשיו על ערימת שכבות, ראינו ששדה הקליטה גדל לינארית עם העומק. קונבולוציות מדוללות מאפשרות להגדיל את שדה הקליטה אקספוננציאלית עם העומק.&lt;/p>
&lt;p>אפשר למצוא הסבר כמעט נגיש לקונבולוציות מדוללות במאמר &lt;a href="https://arxiv.org/pdf/1511.07122.pdf">Multi scale context aggregation by dilated convolutions&lt;/a>, שמשתמש בהן לראייה. אף שהוא פשוט מושגית, לקח לי זמן להבין בדיוק מה הן עושות, וייתכן שאני עדיין לא לגמרי מדייק.&lt;/p>
&lt;p>הרעיון הבסיסי הוא להכניס ״חורים״ בכל פילטר, כך שהוא לא פועל על חלקים סמוכים של הקלט אלא מדלג עליהם לחלקים רחוקים יותר. שימו לב שזה שונה מלהחיל קונבולוציה עם stride &amp;gt;1. כשאנחנו מבצעים stride לפילטר, אנחנו מדלגים על חלקים בקלט בין יישומים של הקונבולוציה. עם קונבולוציות מדוללות אנחנו מדלגים על חלקים בקלט בתוך יישום יחיד של הקונבולוציה. בעזרת ארגון חכם של דילולים הולכים וגדלים אפשר להשיג את ההבטחה לגידול אקספוננציאלי בשדות הקליטה.&lt;/p>
&lt;p>דיברנו הרבה תיאוריה עד עכשיו, אבל סוף סוף אנחנו בנקודה שבה אפשר לראות את הדברים האלה בפעולה!&lt;/p>
&lt;p>מאמר אהוב במיוחד עליי הוא &lt;a href="https://arxiv.org/pdf/1610.10099.pdf">Neural Machine Translation in Linear Time&lt;/a>. הוא עוקב אחרי מבנה ה‑encoder decoder שעליו דיברנו בתחילת הדרך. עדיין אין לנו את כל הכלים לדבר על המפענח, אבל אפשר לראות את המקודד בפעולה.&lt;/p>
&lt;p>&lt;img
src="./dilated-convolution-receptive-field.webp"
alt="מקודד קונבולוציה מדוללת עם שדות קליטה מתרחבים על פני רצף"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>והנה קלט באנגלית&lt;/p>
&lt;blockquote>
&lt;p>Director Jon Favreau, who is currently working on Disney’s forthcoming Jungle Book film, told the website Hollywood Reporter: “I think times are changing.”&lt;/p>
&lt;/blockquote>
&lt;p>והתרגום שלו, באדיבות קונבולוציות מדוללות&lt;/p>
&lt;blockquote>
&lt;p>Regisseur Jon Favreau, der zur Zeit an Disneys kommendem Jungle Book Film arbeitet, hat der Website Hollywood Reporter gesagt: “Ich denke, die Zeiten andern sich”.&lt;/p>
&lt;/blockquote>
&lt;p>וכבונוס, זכרו שצליל הוא בדיוק כמו טקסט, במובן שיש לו רק ממד מרחבי/זמני אחד. בדקו את &lt;a href="https://deepmind.com/blog/wavenet-generative-model-raw-audio/">Wavenet&lt;/a> של DeepMind, שמשתמש בקונבולוציות מדוללות (והרבה קסם נוסף) כדי לייצר &lt;a href="https://storage.googleapis.com/deepmind-media/pixie/knowing-what-to-say/second-list/speaker-1.wav">דיבור שנשמע אנושי&lt;/a> ו‑&lt;a href="https://storage.googleapis.com/deepmind-media/pixie/making-music/sample_4.wav">מוזיקת פסנתר&lt;/a>.&lt;/p>
&lt;h3 id="להוציא-דברים-מהרשת-שלך">להוציא דברים מהרשת שלך&lt;/h3>
&lt;p>כשדיברנו על DenseNets הזכרתי שהשימוש בחיבורים שיוריים מאלץ אותנו לשמור על אותו אורך לקלט ולפלט של הרצף שלנו, מה שנעשה באמצעות ריפוד. זה נהדר למשימות שבהן צריך לתייג כל פריט ברצף שלנו, למשל:&lt;/p>
&lt;ul>
&lt;li>בתִיוג חלקי דיבר, שבו כל מילה היא חלק דיבר.&lt;/li>
&lt;li>בזיהוי ישויות, שבו אולי נתייג Person, Company, ו‑Other עבור כל השאר&lt;/li>
&lt;/ul>
&lt;p>פעמים אחרות נרצה לצמצם את רצף הקלט לייצוג וקטורי ולהשתמש בו כדי לחזות משהו על כל המשפט&lt;/p>
&lt;ul>
&lt;li>נרצה לתייג אימייל כספאם בהתבסס על התוכן שלו ו/או הנושא&lt;/li>
&lt;li>לחזות אם משפט מסוים סרקסטי או לא&lt;/li>
&lt;/ul>
&lt;p>במקרים האלה אפשר ללכת בעקבות הגישות המסורתיות של ״אנשי הראייה״ ולהוסיף לראש הרשת שכבות קונבולוציה ללא ריפוד ו/או להשתמש בפעולות pooling.&lt;/p>
&lt;p>אבל לפעמים נרצה לעקוב אחרי פרדיגמת Seq2Seq, מה ש‑&lt;a href="https://medium.com/u/42936aed59d2">Matthew Honnibal&lt;/a> קרא בקצרה &lt;a href="https://explosion.ai/blog/deep-learning-formula-nlp">&lt;em>Embed, encode, attend, predict&lt;/em>&lt;/a>&lt;em>.&lt;/em> במקרה הזה אנחנו מצמצמים את הקלט לייצוג וקטורי כלשהו, אבל אז צריכים איכשהו לבצע upsample לוקטור הזה חזרה לרצף באורך הנכון.&lt;/p>
&lt;p>המשימה הזו כוללת שתי בעיות&lt;/p>
&lt;ul>
&lt;li>איך עושים upsampling עם קונבולוציות?&lt;/li>
&lt;li>איך עושים בדיוק את כמות ה‑up sampling הנכונה?&lt;/li>
&lt;/ul>
&lt;p>עדיין לא מצאתי את התשובה לשאלה השנייה, או לפחות עדיין לא הבנתי אותה. בפועל, היה לי מספיק להניח חסם עליון כלשהו לאורך המקסימלי של הפלט ואז לבצע upsample עד לנקודה הזו. אני חושד שהמאמר החדש של פייסבוק על &lt;a href="https://s3.amazonaws.com/fairseq/papers/convolutional-sequence-to-sequence-learning.pdf">תרגום&lt;/a> עשוי לטפל בזה, אבל עדיין לא קראתי אותו לעומק כדי להגיב.&lt;/p>
&lt;h4 id="upsampling-עם-דקונבולוציות">Upsampling עם דקונבולוציות&lt;/h4>
&lt;p>דקונבולוציות הן הכלי שלנו ל‑upsampling. הכי קל (לי) להבין מה הן עושות דרך ויזואליזציות. למזלנו, כמה אנשים חכמים פרסמו &lt;a href="http://distill.pub/2016/deconv-checkerboard/">פוסט נהדר על דקונבולוציות&lt;/a> ב‑Distill וכללו שם כמה ויזואליזרים כיפיים. בואו נתחיל איתם.&lt;/p>
&lt;p>&lt;img
src="./strided-convolution-diagram.webp"
alt="דיאגרמת קונבולוציה עם stride שמראה את הקרנל מכסה את הקלטים"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>שקלו את התמונה למעלה. אם ניקח את השכבה התחתונה כקלט, יש לנו קונבולוציה סטנדרטית עם stride 1 וברוחב 3. &lt;em>אבל,&lt;/em> אנחנו יכולים גם ללכת מלמעלה למטה, כלומר להתייחס לשכבה העליונה כקלט ולקבל את השכבה התחתונה, שהיא מעט גדולה יותר.&lt;/p>
&lt;p>אם תעצרו לחשוב על זה לרגע, פעולת ה״מלמעלה למטה״ הזו כבר מתרחשת ברשתות הקונבולוציה שלכם כשאתם עושים back propagation, כי אותות הגרדיאנט צריכים להתפשט בדיוק בדרך שמוצגת בתמונה. אפילו טוב יותר: מסתבר שהפעולה הזו היא פשוט ה‑transpose של פעולת הקונבולוציה, ומכאן השם הנפוץ האחר (והטכני הנכון) לפעולה הזו: transposed convolution.&lt;/p>
&lt;p>וכאן זה נעשה כיף. אנחנו יכולים לבצע stride לקונבולוציות כדי לכווץ את הקלט. לכן אנחנו יכולים לבצע stride גם ל״דקונבולוציות״ כדי להגדיל את הקלט. אני חושב שהדרך הקלה ביותר להבין איך strides עובדים עם דקונבולוציות היא להסתכל על התמונות הבאות.&lt;/p>
&lt;p>&lt;img
src="./strided-convolution-diagram.webp"
alt="דיאגרמת קונבולוציה עם stride שמראה את הקרנל מכסה את הקלטים"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;img
src="./transposed-convolution-overlap.webp"
alt="קונבולוציה טרנספוזית עם חפיפה בכיסוי הפלטים"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>כבר ראינו את העליונה. שימו לב שכל קלט (השכבה העליונה) מזין שלושה פלטים, ושכל פלט מוזן על ידי שלושה קלטים (מלבד הקצוות).&lt;/p>
&lt;p>&lt;img
src="./dilated-convolution-spacing.webp"
alt="ריווח קונבולוציה מדוללת עם פערים שמרחיבים את שדה הקליטה"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>בתמונה השנייה אנחנו שמים חורים דמיוניים בקלטים שלנו. שימו לב שעכשיו כל פלט מוזן על ידי לכל היותר שני קלטים.&lt;/p>
&lt;p>&lt;img
src="./transposed-convolution-upscaling.webp"
alt="קונבולוציה טרנספוזית שמגדילה את אורך הרצף"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>בתמונה השלישית הוספנו שני חורים דמיוניים לשכבת הקלט שלנו, כך שכל פלט מוזן על ידי בדיוק קלט אחד. זה בסופו של דבר משלש את אורך רצף הפלט שלנו ביחס לאורך רצף הקלט.&lt;/p>
&lt;p>לבסוף, אפשר לערום כמה שכבות דקונבולוציה כדי להגדיל בהדרגה את שכבת הפלט לגודל הרצוי.&lt;/p>
&lt;p>כמה דברים שכדאי לחשוב עליהם&lt;/p>
&lt;ol>
&lt;li>אם מסתכלים על השרטוטים האלה מלמטה למעלה, הם בסוף קונבולוציות עם stride רגיל, שבהן פשוט הוספנו חורים דמיוניים לשכבות הפלט (הבלוקים הלבנים)&lt;/li>
&lt;li>בפועל, כל ״קלט״ אינו מספר יחיד אלא וקטור. בעולם התמונות זה יכול להיות ערך RGB תלת‑ממדי. בטקסט זה יכול להיות אמבדינג מילה בממד 300. אם אתם (de)convolving באמצע הרשת, כל נקודה תהיה וקטור בגודל כלשהו שיצא מהשכבה הקודמת.&lt;/li>
&lt;li>אני מציין זאת כדי לשכנע אתכם שיש מספיק מידע בשכבת הקלט של דקונבולוציה כדי להתפרס על פני כמה נקודות בפלט.&lt;/li>
&lt;li>בפועל, הייתה לי הצלחה בהרצה של כמה קונבולוציות עם padding שמשמר אורך אחרי דקונבולוציה. אני מדמיין (אם כי לא הוכחתי) שזה פועל כמו redistributing מידע. אני חושב על זה כמו לתת לסטייק לנוח אחרי הצלייה כדי לתת למיצים להתפזר מחדש.&lt;/li>
&lt;/ol>
&lt;p>&lt;img
src="./steak-resting-comparison.webp"
alt="השוואה בין סטייק שלא נח לבין סטייק שנח כדי להמחיש הפצה מחדש של מידע"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h3 id="סיכום">סיכום&lt;/h3>
&lt;p>הסיבה המרכזית שבגללה אולי תרצו לשקול קונבולוציות בעבודה שלכם היא שהן מהירות. אני חושב שזה חשוב כדי להפוך מחקר וניסויים למהירים ויעילים יותר. רשתות מהירות מקצרות את מחזורי המשוב שלנו.&lt;/p>
&lt;p>רוב המשימות שפגשתי עם טקסט מגיעות עם אותה דרישה ארכיטקטונית: למקסם את שדה הקליטה תוך שמירה על זרימה מספקת של גרדיאנטים. ראינו שימוש גם ב‑DenseNets וגם בקונבולוציות מדוללות כדי להשיג זאת.&lt;/p>
&lt;p>לבסוף, לפעמים אנחנו רוצים להרחיב רצף או וקטור לרצף גדול יותר. הסתכלנו על דקונבולוציות כדרך לעשות “upsampling” לטקסט, וכבונוס השווינו הוספת קונבולוציה אחר כך ללתת לסטייק לנוח ולהפיץ מחדש את המיצים שלו.&lt;/p>
&lt;p>אשמח לשמוע יותר על המחשבות והניסיון שלכם עם מודלים מהסוג הזה. שתפו בתגובות או שלחו לי פינג בטוויטר &lt;a href="https://twitter.com/thetalperry">@thetalperry&lt;/a>&lt;/p></description><author/><guid>https://talperry.com/he/posts/classics/cmft/</guid><pubDate>Mon, 22 May 2017 00:00:00 +0000</pubDate></item><item><title>Deep Learning ושוק ההון</title><link>https://talperry.com/he/posts/classics/dlsm/</link><description>&lt;p>&lt;em>&lt;strong>עדכון 15.03.2024&lt;/strong> כתבתי את זה לפני יותר משבע שנים. ההבנה שלי התפתחה מאז, ועולם ה־deep learning עבר יותר ממהפכה אחת מאז. זה היה פופולרי בזמנו, ואולי עדיין כיף לקרוא — למרות שסביר שתלמדו מידע מדויק ועדכני יותר במקום אחר&lt;/em>&lt;/p>
&lt;p>&lt;em>&lt;strong>עדכון 25.1.17&lt;/strong> — לקח לי זמן אבל&lt;/em> &lt;a href="https://github.com/talolard/MarketVectors/blob/master/preparedata.ipynb">&lt;em>הנה מחברת ipython&lt;/em>&lt;/a> &lt;em>עם מימוש גס&lt;/em>&lt;/p>
&lt;p>&lt;img
src="./performance-plot-market-returns.webp"
alt="השוואת תשואה מצטברת עבור אותות מסחר שונים"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="למה-nlp-רלוונטי-לחיזוי-מניות">למה NLP רלוונטי לחיזוי מניות&lt;/h2>
&lt;p>בהרבה בעיות NLP אנחנו בסופו של דבר לוקחים רצף ומקודדים אותו לייצוג יחיד בגודל קבוע, ואז מפענחים את הייצוג הזה לרצף אחר. למשל, אנחנו עשויים לתייג ישויות בטקסט, לתרגם מאנגלית לצרפתית או להמיר תדרי אודיו לטקסט. יש שטף עצום של עבודה שיוצא בתחומים האלה והרבה מהתוצאות מגיעות לביצועים מהטובים ביותר.&lt;/p>
&lt;p>בעיניי ההבדל הגדול ביותר בין NLP לניתוח פיננסי הוא שלשפה יש איזושהי הבטחה למבנה, רק שהחוקים של המבנה מעורפלים. שווקים, לעומת זאת, לא מגיעים עם הבטחה למבנה שאפשר ללמוד אותו; ההנחה שמבנה כזה קיים היא מה שהפרויקט הזה אמור היה להוכיח או להפריך (או יותר נכון, אולי להוכיח או להפריך אם אצליח למצוא את המבנה הזה).&lt;/p>
&lt;p>בהנחה שהמבנה שם, הרעיון לסכם את מצב השוק הנוכחי באותה צורה שבה אנחנו מקודדים את הסמנטיקה של פסקה נשמע לי סביר. אם זה עדיין לא נשמע הגיוני, המשיכו לקרוא. זה יתחיל להסתדר.&lt;/p>
&lt;h2 id="תדע-מילה-לפי-החברה-שהיא-שומרת-firth-j-r-195711">תדע מילה לפי החברה שהיא שומרת (Firth, J. R. 1957:11)&lt;/h2>
&lt;p>יש המון ספרות על word embeddings. &lt;a href="https://www.youtube.com/watch?v=xhHOL3TNyJs&amp;index=2&amp;list=PLmImxx8Char9Ig0ZHSyTqGsdhb9weEGam">ההרצאה של Richard Socher&lt;/a> היא מקום מצוין להתחיל. בקצרה, אפשר ליצור גאומטריה לכל המילים בשפה שלנו, והגאומטריה הזאת לוכדת את המשמעות של מילים ואת היחסים ביניהן. אולי ראיתם את הדוגמה של “King-man +woman=Queen” או משהו בסגנון.&lt;/p>
&lt;p>&lt;img
src="./shakespeare-code-sample.webp"
alt="דוגמה לגאומטריית embeddings שמדגישה שכנים הקרובים ביותר למילה frog"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>Embeddings מגניבים כי הם מאפשרים לנו לייצג מידע בצורה דחוסה. הדרך הישנה לייצג מילים הייתה להחזיק וקטור (רשימה גדולה של מספרים) שאורכו כמספר המילים שאנחנו מכירים, ולהציב 1 במקום מסוים אם זו המילה הנוכחית שאנחנו מסתכלים עליה. זו לא גישה יעילה, והיא גם לא לוכדת שום משמעות. עם embeddings, אפשר לייצג את כל המילים במספר קבוע של ממדים (300 נראה יותר ממספיק, 50 עובד מצוין) ואז לנצל את הגאומטריה המממדית הגבוהה שלהן כדי להבין אותן.&lt;/p>
&lt;p>התמונה למטה מראה דוגמה. embedding אומן פחות או יותר על כל האינטרנט. אחרי כמה ימים של חישובים אינטנסיביים, כל מילה הוטמעה במרחב ממדים גבוה. ל“מרחב” הזה יש גאומטריה, מושגים כמו מרחק, ולכן אפשר לשאול אילו מילים קרובות זו לזו. המחברים/הממציאים של השיטה עשו דוגמה. הנה המילים שהכי קרובות ל־Frog.&lt;/p>
&lt;p>&lt;img
src="./word2vec-neighbors-frog.webp"
alt="רשימת שכנים קרובים למילה frog ממודל word2vec"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>אבל אפשר להטמיע יותר מרק מילים. אפשר, למשל, לעשות embeddings לשוק ההון.&lt;/p>
&lt;h2 id="market2vec">Market2Vec&lt;/h2>
&lt;p>אלגוריתם ה־word embedding הראשון ששמעתי עליו היה word2vec. אני רוצה לקבל אפקט דומה לשוק, למרות שאשתמש באלגוריתם אחר. נתוני הקלט שלי הם csv: העמודה הראשונה היא התאריך, ויש 4*1000 עמודות שמתאימות למחירי High Low Open Closing של 1000 מניות. כלומר וקטור הקלט שלי הוא מממד 4000, שזה גדול מדי. אז הדבר הראשון שאני הולך לעשות הוא לדחוס אותו למרחב ממדים נמוך יותר, נגיד 300 כי אהבתי את הסרט.
&lt;img
src="./market-embedding-diagram.webp"
alt="תרשים embedding של Market2Vec שמכווץ מחירים בממד 4000 לממד 300"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>לקחת משהו ב־4000 ממדים ולדחוס אותו למרחב של 300 ממדים אולי נשמע קשה, אבל זה בעצם קל. צריך רק להכפיל מטריצות. מטריצה היא גיליון אקסל גדול שיש בו מספרים בכל תא ואין בו בעיות פורמט. תדמיינו טבלה באקסל עם 4000 עמודות ו־300 שורות, וכשאנחנו בעצם דופקים אותה על הווקטור יוצא וקטור חדש שהוא רק בגודל 300. הלוואי שככה היו מסבירים את זה בקולג׳.&lt;/p>
&lt;p>התחכום מתחיל כאן: אנחנו נקבע את המספרים במטריצה באקראי, וחלק מה“deep learning” הוא לעדכן את המספרים האלה כך שגיליון האקסל ישתנה. בסופו של דבר למטריצת הגיליון (מעכשיו אקרא לה פשוט מטריצה) יהיו מספרים שמדחסים את הווקטור המקורי בממד 4000 לסיכום תמציתי בממד 300.&lt;/p>
&lt;p>אנחנו נהיה קצת יותר מתוחכמים וניישם מה שנקרא פונקציית אקטיבציה. אנחנו ניקח פונקציה וניישם אותה על כל מספר בווקטור בנפרד כך שכולם ייצאו בין 0 ל־1 (או בין 0 לאינסוף — תלוי). למה? זה הופך את הווקטור ליותר “מיוחד”, ומאפשר לתהליך הלמידה שלנו להבין דברים מורכבים יותר. &lt;a href="https://lmgtfy.com/?q=why+does+deep+learning+use+non+linearities">איך&lt;/a>?&lt;/p>
&lt;p>אז מה? מה שאני מצפה למצוא הוא שההטמעה החדשה של מחירי השוק (הווקטור) למרחב קטן יותר תתפוס את כל המידע החיוני למשימה, בלי לבזבז זמן על דברים אחרים. אז הייתי מצפה שהיא תלכוד קורלציות בין מניות אחרות, אולי תזהה מתי סקטור מסוים נחלש או מתי השוק מאוד “חם”. אני לא יודע אילו תכונות היא תמצא, אבל אני מניח שהן יהיו שימושיות.&lt;/p>
&lt;h2 id="אז-מה-עכשיו">אז מה עכשיו&lt;/h2>
&lt;p>בואו נשים רגע בצד את וקטורי השוק שלנו ונדבר על מודלי שפה. &lt;a href="https://medium.com/u/ac9d9a35533e">Andrej Karpathy&lt;/a> כתב את הפוסט האפי “&lt;a href="http://karpathy.github.io/2015/05/21/rnn-effectiveness/">The Unreasonable effectiveness of Recurrent Neural Networks&lt;/a>”. אם אסכם בצורה הכי ליברלית, הפוסט מצטמצם ל:&lt;/p>
&lt;ol>
&lt;li>אם נסתכל על הכתבים של שייקספיר ונעבור עליהם תו־תו, אפשר להשתמש ב“deep learning” כדי ללמוד מודל שפה.&lt;/li>
&lt;li>מודל שפה (במקרה הזה) &lt;strong>הוא קופסה קסומה&lt;/strong>. נותנים לו את כמה התווים הראשונים והוא אומר מה יהיה התו הבא.&lt;/li>
&lt;li>אם ניקח את התו שמודל השפה חזה ונאכיל אותו חזרה פנימה, נוכל להמשיך לנצח.&lt;/li>
&lt;/ol>
&lt;p>ואז כפואנטה, הוא יצר המון טקסט שנראה כמו שייקספיר. ואז הוא עשה את זה שוב עם קוד המקור של לינוקס. ואז שוב עם ספר לימוד על גאומטריה אלגברית.&lt;/p>
&lt;p>אז עוד רגע אחזור למכניקה של הקופסה הקסומה הזו, אבל אזכיר שאנחנו רוצים לחזות את השוק העתידי על בסיס העבר בדיוק כמו שהוא חזה את המילה הבאה על בסיס הקודמת. איפה ש־Karpathy השתמש בתווים, אנחנו נשתמש בוקטורי השוק שלנו ונאכיל אותם לקופסה השחורה הקסומה. עדיין לא החלטנו מה אנחנו רוצים שהיא תחזה, אבל זה בסדר — גם לא נאכיל את הפלט שלה חזרה פנימה.&lt;/p>
&lt;h2 id="להעמיק">להעמיק&lt;/h2>
&lt;p>אני רוצה להדגיש שזה המקום שבו אנחנו מתחילים להיכנס ל־deep של deep learning. עד עכשיו יש לנו רק שכבת למידה אחת — גיליון האקסל שמדחס את השוק. עכשיו נוסיף עוד כמה שכבות ונערום אותן כדי לעשות משהו “עמוק”. זה ה־deep ב־deep learning.&lt;/p>
&lt;p>אז Karpathy מראה לנו פלט לדוגמה מקוד המקור של לינוקס — זה משהו שהקופסה השחורה שלו כתבה.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">action_new_function&lt;/span>(&lt;span style="color:#66d9ef">struct&lt;/span> &lt;span style="color:#a6e22e">s_stat_info&lt;/span> &lt;span style="color:#f92672">*&lt;/span>wb)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span> flags;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> lel_idx_bit &lt;span style="color:#f92672">=&lt;/span> e&lt;span style="color:#f92672">-&amp;gt;&lt;/span>edd, &lt;span style="color:#f92672">*&lt;/span>sys &lt;span style="color:#f92672">&amp;amp;&lt;/span> &lt;span style="color:#f92672">~&lt;/span>((&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span>) &lt;span style="color:#f92672">*&lt;/span>FIRST_COMPAT);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> buf[&lt;span style="color:#ae81ff">0&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0xFFFFFFFF&lt;/span> &lt;span style="color:#f92672">&amp;amp;&lt;/span> (bit &lt;span style="color:#f92672">&amp;lt;&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> min(inc, slist&lt;span style="color:#f92672">-&amp;gt;&lt;/span>bytes);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printk(KERN_WARNING &lt;span style="color:#e6db74">&amp;#34;Memory allocated %02x/%02x, &amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;original MLL instead&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> min(min(multi_run &lt;span style="color:#f92672">-&lt;/span> s&lt;span style="color:#f92672">-&amp;gt;&lt;/span>len, max) &lt;span style="color:#f92672">*&lt;/span> num_data_in),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> frame_pos, sz &lt;span style="color:#f92672">+&lt;/span> first_seg);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> div_u64_w(val, inb_p);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> spin_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>disk&lt;span style="color:#f92672">-&amp;gt;&lt;/span>queue_lock);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mutex_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>s&lt;span style="color:#f92672">-&amp;gt;&lt;/span>sock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>mutex);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mutex_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>func&lt;span style="color:#f92672">-&amp;gt;&lt;/span>mutex);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> disassemble(info&lt;span style="color:#f92672">-&amp;gt;&lt;/span>pending_bh);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>שימו לב שהוא יודע לפתוח ולסגור סוגריים, ומכבד מוסכמות הזחה; תוכן הפונקציה מוזח נכון והמשפט הרב־שורי &lt;em>printk&lt;/em> מכיל הזחה פנימית. זה אומר שהקופסה הקסומה הזו מבינה תלות לטווח ארוך. כשהיא מזיחה בתוך משפט print היא יודעת שהיא בתוך משפט print וגם זוכרת שהיא בתוך פונקציה (או לפחות בתוך scope מוזח אחר). &lt;strong>זה מטורף.&lt;/strong> קל לדלג מעל זה, אבל אלגוריתם שיש לו יכולת ללכוד ולזכור תלות ארוכת טווח הוא מאוד שימושי כי… אנחנו רוצים למצוא תלות ארוכת טווח בשוק.&lt;/p>
&lt;h2 id="בתוך-הקופסה-השחורה-הקסומה">בתוך הקופסה השחורה הקסומה&lt;/h2>
&lt;p>מה יש בתוך הקופסה השחורה הקסומה הזו? זה סוג של Recurrent Neural Network (RNN) שנקרא LSTM. RNN הוא אלגוריתם deep learning שפועל על רצפים (כמו רצפים של תווים). בכל צעד, הוא לוקח ייצוג של התו הבא (כמו ה־embeddings שדיברנו עליהם קודם) ומפעיל על הייצוג מטריצה, כמו שראינו קודם. העניין הוא של־RNN יש סוג של זיכרון פנימי, כך שהוא זוכר מה הוא ראה בעבר. הוא משתמש בזיכרון הזה כדי להחליט איך בדיוק לפעול על הקלט הבא. בעזרת הזיכרון הזה, ה־RNN יכול “לזכור” שהוא בתוך scope מוזח — וככה אנחנו מקבלים טקסט פלט מקונן נכון.&lt;/p>
&lt;p>&lt;img
src="./nested-scope-code-structure.webp"
alt="LSTM פרוש לאורך הזמן שמראה כיצד המצב החבוי נושא את הקשר ההזחה"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>גרסה “מפונפנת” של RNN נקראת Long Short Term Memory (LSTM). ל־LSTM יש זיכרון שתוכנן בחוכמה כך שהוא מאפשר לו:&lt;/p>
&lt;ol>
&lt;li>לבחור באופן סלקטיבי מה לזכור&lt;/li>
&lt;li>להחליט לשכוח&lt;/li>
&lt;li>לבחור כמה מהזיכרון שלו להוציא כפלט.&lt;/li>
&lt;/ol>
&lt;p>&lt;img
src="./lstm-memory-gates.webp"
alt="תרשים של שערי LSTM שמנהלים את פעולות הקלט פלט והשכחה של הזיכרון"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>אז LSTM יכול לראות “{“ ולהגיד לעצמו “אה כן, זה חשוב — אני צריך לזכור את זה”, וכשהוא עושה זאת הוא בעצם שומר אינדיקציה לכך שהוא בתוך scope מקונן. אחרי שהוא רואה את ה־“}” המתאים, הוא יכול להחליט לשכוח את הסוגר המסולסל הפותח המקורי ובכך לשכוח שהוא בתוך scope מקונן.&lt;/p>
&lt;p>אנחנו יכולים לגרום ל־LSTM ללמוד מושגים מופשטים יותר על ידי ערימה של כמה LSTM אחד מעל השני, וזה יחזיר אותנו להיות “Deep” שוב. עכשיו כל פלט של ה־LSTM הקודם הופך לקלט של הבא, וכל אחד ממשיך ללמוד הפשטות גבוהות יותר של הנתונים הנכנסים. בדוגמה למעלה (וזו רק ספקולציה להמחשה), שכבת ה־LSTM הראשונה אולי תלמד שתווים שמופרדים ברווח הם “מילים”. השכבה הבאה אולי תלמד טיפוסי מילים כמו (&lt;code>**static** **void** **action_new_function).**&lt;/code>השכבה הבאה אולי תלמד את המושג של פונקציה והארגומנטים שלה וכן הלאה. קשה לדעת בדיוק מה כל שכבה עושה, למרות שלבלוג של Karpathy יש דוגמה ממש יפה איך הוא המחיש את זה.&lt;/p>
&lt;h2 id="חיבור-בין-market2vec-לlstms">חיבור בין Market2Vec ל־LSTMs&lt;/h2>
&lt;p>הקורא החרוץ ישים לב ש־Karpathy השתמש בתווים כקלטים שלו, לא ב־embeddings (טכנית, one-hot encoding של תווים). אבל Lars Eidnes למעשה השתמש ב־word embeddings כשכתב &lt;a href="https://larseidnes.com/2015/10/13/auto-generating-clickbait-with-recurrent-neural-networks/">Auto-Generating Clickbait With Recurrent Neural Network&lt;/a>&lt;/p>
&lt;p>&lt;img
src="./stacked-lstm-architecture.webp"
alt="ארכיטקטורת LSTM מוערמת שצורכת word vectors ומעבירה פלט כלפי מעלה"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;p>האיור למעלה מראה את הרשת שהוא השתמש בה. התעלמו מחלק ה־SoftMax (נגיע לזה אחר כך). לעת עתה, שימו לב איך בתחתית הוא מכניס רצף של word vectors וכל אחד מהם. (זכרו: “word vector” הוא ייצוג של מילה בצורת אוסף מספרים, כמו שראינו בתחילת הפוסט). Lars מכניס רצף של Word Vectors וכל אחד מהם:&lt;/p>
&lt;ol>
&lt;li>משפיע על ה־LSTM הראשון&lt;/li>
&lt;li>גורם ל־LSTM שלו להוציא משהו ל־LSTM שמעליו&lt;/li>
&lt;li>גורם ל־LSTM שלו להוציא משהו ל־LSTM של המילה הבאה&lt;/li>
&lt;/ol>
&lt;p>אנחנו נעשה אותו דבר עם הבדל אחד: במקום word vectors נכניס “MarketVectors”, אותם וקטורי שוק שתיארנו קודם. כדי לסכם, ה־MarketVectors אמורים להכיל סיכום של מה שקורה בשוק בנקודת זמן נתונה. על ידי העברת רצף שלהם דרך LSTMs אני מקווה ללכוד את הדינמיקה ארוכת הטווח שהתרחשה בשוק. על ידי ערימה של כמה שכבות LSTM אני מקווה ללכוד הפשטות ברמה גבוהה יותר של התנהגות השוק.&lt;/p>
&lt;h2 id="מה-יוצא-החוצה">מה יוצא החוצה&lt;/h2>
&lt;p>&lt;em>עד כה לא דיברנו בכלל על איך האלגוריתם באמת לומד משהו; רק דיברנו על כל הטרנספורמציות החכמות שנעשה על הנתונים. נדחה את השיחה הזאת לכמה פסקאות למטה, אבל בבקשה זכרו את החלק הזה כי הוא ההכנה לפאנץ׳־ליין שהופך את כל השאר לכדאי.&lt;/em>&lt;/p>
&lt;p>בדוגמה של Karpathy, הפלט של ה־LSTMs הוא וקטור שמייצג את התו הבא באיזשהו ייצוג מופשט. בדוגמה של Eidnes, הפלט של ה־LSTMs הוא וקטור שמייצג מה תהיה המילה הבאה במרחב מופשט. השלב הבא בשני המקרים הוא להפוך את הייצוג המופשט הזה לווקטור הסתברויות — רשימה שאומרת עד כמה סביר שכל תו או מילה בהתאמה יופיעו הבאים. זו העבודה של פונקציית SoftMax. ברגע שיש לנו רשימת הסתברויות אנחנו בוחרים את התו או המילה שהכי סביר שיופיעו הבאים.&lt;/p>
&lt;p>במקרה שלנו, של “חיזוי השוק”, אנחנו צריכים לשאול את עצמנו מה בדיוק אנחנו רוצים שהשוק יחזה? כמה אפשרויות שחשבתי עליהן היו:&lt;/p>
&lt;ol>
&lt;li>לחזות את המחיר הבא לכל אחת מ־1000 המניות&lt;/li>
&lt;li>לחזות את הערך של איזה אינדקס (S&amp;amp;P, VIX וכו׳) בעוד &lt;em>n&lt;/em> דקות.&lt;/li>
&lt;li>לחזות אילו מניות יעלו ביותר מ־&lt;em>x%&lt;/em> בעוד &lt;em>n&lt;/em> דקות&lt;/li>
&lt;li>(האהוב עליי אישית) לחזות אילו מניות יעלו/ירדו ב־&lt;em>2x%&lt;/em> בעוד &lt;em>n&lt;/em> דקות, תוך שהן לא יורדות &lt;em>down/up&lt;/em> ביותר מ־&lt;em>x%&lt;/em> בזמן הזה.&lt;/li>
&lt;li>(זה שנמשיך איתו לאורך שאר המאמר). לחזות מתי ה־VIX יעלה/ירד ב־&lt;em>2x%&lt;/em> בעוד &lt;em>n&lt;/em> דקות, תוך שהוא לא יורד &lt;em>down/up&lt;/em> ביותר מ־&lt;em>x%&lt;/em> בזמן הזה.&lt;/li>
&lt;/ol>
&lt;p>1 ו־2 הן בעיות רגרסיה, שבהן צריך לחזות מספר ממשי ולא הסתברות לאירוע ספציפי (כמו הופעת האות n או עליית השוק). זה בסדר, אבל לא מה שאני רוצה לעשות.&lt;/p>
&lt;p>3 ו־4 די דומות; שתיהן מבקשות לחזות אירוע (בז׳רגון טכני — תווית מחלקה). אירוע יכול להיות הופעת האות &lt;em>n&lt;/em> הבאה או &lt;em>עלה 5% תוך שלא ירד יותר מ־3% בעשר הדקות האחרונות.&lt;/em> ההחלפה בין 3 ל־4 היא ש־3 הרבה יותר נפוץ ולכן קל יותר ללמוד עליו, בעוד 4 יותר בעל ערך כי הוא לא רק אינדיקטור לרווח אלא גם כולל אילוץ על סיכון.&lt;/p>
&lt;p>5 הוא זה שנמשיך איתו במאמר הזה כי הוא דומה ל־3 ול־4 אבל עם מכניקה שקל יותר לעקוב אחריה. ה־&lt;a href="https://en.wikipedia.org/wiki/VIX">VIX&lt;/a> נקרא לפעמים מדד הפחד והוא מייצג עד כמה המניות ב־S&amp;amp;P500 תנודתיות. הוא נגזר מהתבוננות ב־&lt;a href="https://en.wikipedia.org/wiki/Implied_volatility">implied volatility&lt;/a> עבור אופציות ספציפיות על כל אחת מהמניות במדד.&lt;/p>
&lt;h3 id="הערת-אגב--למה-לחזות-את-הvix">הערת אגב — למה לחזות את ה־VIX&lt;/h3>
&lt;p>מה שהופך את ה־VIX ליעד מעניין הוא ש:&lt;/p>
&lt;ol>
&lt;li>זה מספר אחד בלבד, בניגוד לאלפים של מניות. זה מקל על המעקב ברמה המושגית ומפחית עלויות חישוב.&lt;/li>
&lt;li>זה סיכום של הרבה מניות, אז רוב אם לא כל הקלטים שלנו רלוונטיים.&lt;/li>
&lt;li>זו לא קומבינציה ליניארית של הקלטים שלנו. implied volatility מופק מנוסחה מסובכת ולא־ליניארית מניה־מניה. ה־VIX נגזר מעל זה מנוסחה מורכבת נוספת. אם נוכל לחזות את זה — זה די מגניב.&lt;/li>
&lt;li>אפשר לסחור בו, אז אם זה באמת עובד נוכל להשתמש בזה.&lt;/li>
&lt;/ol>
&lt;h2 id="חזרה-לפלטים-של-הlstm-ולsoftmax">חזרה לפלטים של ה־LSTM ול־SoftMax&lt;/h2>
&lt;p>איך משתמשים בניסוחים שראינו קודם כדי לחזות שינויים ב־VIX כמה דקות קדימה? עבור כל נקודה בדאטה־סט שלנו, נבדוק מה קרה ל־VIX 5 דקות אחר כך. אם הוא עלה ביותר מ־1% בלי לרדת ביותר מ־0.5% במהלך הזמן הזה נוציא 1, אחרת 0. ואז נקבל רצף שנראה כך:&lt;/p>
&lt;blockquote>
&lt;p>0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,1,1,1,0,0,0,0,0 ….&lt;/p>
&lt;/blockquote>
&lt;p>אנחנו רוצים לקחת את הווקטור שה־LSTMs מוציאים ולדחוס אותו כך שייתן לנו את ההסתברות שהפריט הבא ברצף שלנו יהיה 1. הדחיסה מתרחשת בחלק ה־SoftMax בתרשים למעלה. (טכנית, מכיוון שיש לנו עכשיו רק מחלקה אחת, אנחנו משתמשים ב־sigmoid).&lt;/p>
&lt;p>אז לפני שניכנס לאיך הדבר הזה לומד, בואו נסכם מה עשינו עד עכשיו:&lt;/p>
&lt;ol>
&lt;li>אנחנו מקבלים כקלט רצף של נתוני מחיר עבור 1000 מניות&lt;/li>
&lt;li>כל נקודת זמן ברצף היא צילום־מצב של השוק. הקלט שלנו הוא רשימה של 4000 מספרים. אנחנו משתמשים בשכבת embedding כדי לייצג את המידע המרכזי ב־300 מספרים בלבד.&lt;/li>
&lt;li>עכשיו יש לנו רצף של embeddings של השוק. אנחנו מכניסים אותם לערימה של LSTMs, צעד־זמן אחרי צעד־זמן. ה־LSTMs זוכרים דברים מהצעדים הקודמים וזה משפיע על איך הם מעבדים את הנוכחי.&lt;/li>
&lt;li>אנחנו מעבירים את הפלט של השכבה הראשונה של ה־LSTMs לשכבה נוספת. החבר׳ה האלה גם זוכרים, והם לומדים הפשטות ברמה גבוהה יותר של המידע שהכנסנו.&lt;/li>
&lt;li>לבסוף, אנחנו לוקחים את הפלט מכל ה־LSTMs ו“דוחסים” אותו כך שרצף מידע השוק יהפוך לרצף הסתברויות. ההסתברות המדוברת היא: “מה הסיכוי שה־VIX יעלה 1% בחמש הדקות הבאות בלי לרדת 0.5%?”&lt;/li>
&lt;/ol>
&lt;h2 id="איך-הדבר-הזה-לומד">איך הדבר הזה לומד?&lt;/h2>
&lt;p>עכשיו החלק הכיפי. כל מה שעשינו עד עכשיו נקרא forward pass; היינו עושים את כל הצעדים האלה גם בזמן אימון האלגוריתם וגם כשמשתמשים בו בפרודקשן. כאן נדבר על ה־backward pass — החלק שעושים רק בזמן האימון, והוא זה שגורם לאלגוריתם ללמוד.&lt;/p>
&lt;p>אז בזמן אימון, לא רק שהכנו שנים של נתונים היסטוריים, אלא גם הכנו רצף של יעדי חיזוי — אותה רשימת 0 ו־1 שמראה אם ה־VIX זז בצורה שרצינו או לא אחרי כל תצפית בנתונים שלנו.&lt;/p>
&lt;p>כדי ללמוד, נאכיל את נתוני השוק לרשת ונשווה את הפלט שלה למה שחישבנו. ההשוואה אצלנו תהיה פשוט חיסור — כלומר נגיד שהשגיאה של המודל שלנו היא:&lt;/p>
&lt;blockquote>
&lt;p>ERROR = (((precomputed)— (predicted probability))² )^(1/2)&lt;/p>
&lt;/blockquote>
&lt;p>או באנגלית, השורש הריבועי של ריבוע ההפרש בין מה שקרה בפועל לבין מה שחזינו.&lt;/p>
&lt;p>הנה היופי: זו פונקציה דיפרנציאלית, כלומר אפשר לומר בכמה השגיאה הייתה משתנה אם התחזית שלנו הייתה משתנה קצת. התחזית שלנו היא התוצאה של פונקציה דיפרנציאלית — ה־SoftMax. הקלטים ל־softmax, ה־LSTMs, כולם פונקציות מתמטיות שניתנות לגזירה. עכשיו כל הפונקציות האלה מלאות בפרמטרים — גיליונות האקסל הגדולים שדיברתי עליהם לפני עידן ועידנים. אז בשלב הזה אנחנו לוקחים את הנגזרת של השגיאה ביחס לכל אחד ממיליוני הפרמטרים בכל גיליונות האקסל האלה במודל. כשעושים את זה רואים איך השגיאה תשתנה כשנשנה כל פרמטר, ולכן נשנה כל פרמטר בצורה שתקטין את השגיאה.&lt;/p>
&lt;p>התהליך הזה מתפשט עד להתחלה של המודל. הוא משנה את הדרך שבה אנחנו מטמיעים את הקלטים ל־MarketVectors כך שה־MarketVectors ייצגו את המידע המשמעותי ביותר למשימה שלנו.&lt;/p>
&lt;p>הוא משנה מתי ומה כל LSTM בוחר לזכור כך שהפלטים שלהם יהיו הרלוונטיים ביותר למשימה.&lt;/p>
&lt;p>הוא משנה את ההפשטות שה־LSTMs לומדים כך שהם ילמדו את ההפשטות החשובות ביותר למשימה.&lt;/p>
&lt;p>וזה בעיניי מדהים כי יש לנו פה כל כך הרבה מורכבות והפשטה שמעולם לא היינו צריכים לפרט בשום מקום. הכול מוסק “MathaMagically” מההגדרה של מה אנחנו מחשיבים לשגיאה.&lt;/p>
&lt;p>&lt;img
src="./stochastic-gradient-plot.webp"
alt="עקומת loss באימון שממחישה התנהגות של stochastic gradient descent"
loading="lazy"
decoding="async"
class="full-width"
/>
&lt;/p>
&lt;h2 id="מה-הלאה">מה הלאה&lt;/h2>
&lt;p>עכשיו, אחרי שכתבתי את זה וזה עדיין נשמע לי הגיוני, אני רוצה:&lt;/p>
&lt;ol>
&lt;li>לראות אם מישהו בכלל טורח לקרוא את זה.&lt;/li>
&lt;li>לתקן את כל הטעויות שהקוראים היקרים שלי מצביעים עליהן&lt;/li>
&lt;li>לשקול אם זה עדיין אפשרי&lt;/li>
&lt;li>ולבנות את זה&lt;/li>
&lt;/ol>
&lt;p>אז אם הגעתם עד כאן, בבקשה הצביעו על השגיאות שלי ושתפו את התובנות שלכם.&lt;/p>
&lt;h2 id="מחשבות-נוספות">מחשבות נוספות&lt;/h2>
&lt;p>הנה כמה מחשבות (בעיקר מתקדמות יותר) על הפרויקט הזה — מה עוד אולי אנסה ולמה זה נשמע לי הגיוני שזה באמת עשוי לעבוד.&lt;/p>
&lt;h3 id="נזילות-ושימוש-יעיל-בהון">נזילות ושימוש יעיל בהון&lt;/h3>
&lt;p>באופן כללי, ככל ששוק מסוים יותר נזיל, כך הוא יעיל יותר. אני חושב שזה נובע ממעגל של ביצה ותרנגולת: ככל ששוק נהיה נזיל יותר, הוא מסוגל לספוג יותר הון שנכנס ויוצא בלי שההון הזה “יפגע בעצמו”. ככל ששוק נהיה נזיל יותר וניתן להשתמש בו ביותר הון, תמצאו יותר שחקנים מתוחכמים שנכנסים. זה כי להיות מתוחכם זה יקר, ולכן צריך להפיק תשואות על נתח גדול של הון כדי להצדיק את עלויות התפעול.&lt;/p>
&lt;p>מסקנה משנית מהירה היא שבשווקים פחות נזילים התחרות לא ממש מתוחכמת באותה מידה, ולכן ההזדמנויות שמערכת כזו יכולה להביא אולי עדיין לא נסגרו. כלומר, אם הייתי מנסה לסחור בזה, הייתי מנסה לסחור במקטעים פחות נזילים של השוק — אולי TASE 100 במקום S&amp;amp;P 500.&lt;/p>
&lt;h3 id="הדבר-הזה-חדש">הדבר הזה חדש&lt;/h3>
&lt;p>הידע על האלגוריתמים האלה, המסגרות להריץ אותם וכוח החישוב כדי לאמן אותם — כולם חדשים, לפחות במובן שהם זמינים לג׳ו הממוצע כמוני. אני מניח ששחקנים גדולים פתרו את זה לפני שנים ויכלו להריץ את זה כבר זמן רב, אבל כפי שציינתי בפסקה למעלה, הם כנראה פועלים בשווקים נזילים שיכולים לתמוך בגודל שלהם. הדרג הבא של משתתפי השוק, אני מניח, בעל קצב איטי יותר של אימוץ טכנולוגי, ובמובן הזה יש או בקרוב תהיה תחרות ליישם את זה בשווקים שעדיין לא נוגעים בהם.&lt;/p>
&lt;h3 id="מסגרות-זמן-מרובות">מסגרות זמן מרובות&lt;/h3>
&lt;p>למרות שציינתי זרם יחיד של קלטים למעלה, אני מדמיין שדרך יעילה יותר לאמן תהיה לאמן וקטורי שוק (לפחות) על פני מסגרות זמן מרובות ולהזין אותם בשלב ה־inference. כלומר, מסגרת הזמן הנמוכה ביותר שלי תהיה דגימה כל 30 שניות, והייתי מצפה שהרשת תלמד תלות שמגיעה לכל היותר למתיחות של שעות.&lt;/p>
&lt;p>אני לא יודע אם זה רלוונטי או לא, אבל אני חושב שיש תבניות במסגרות זמן מרובות, ואם אפשר להוריד את עלות החישוב מספיק אז כדאי לשלב אותן במודל. אני עדיין מתחבט איך הכי טוב לייצג את זה על הגרף החישובי, ואולי זה לא חובה כדי להתחיל.&lt;/p>
&lt;h3 id="marketvectors">MarketVectors&lt;/h3>
&lt;p>כשמשתמשים ב־word vectors ב־NLP לרוב מתחילים עם מודל מאומן מראש וממשיכים לכוונן את ה־embeddings במהלך אימון המודל שלנו. במקרה שלי אין market vector מאומן מראש זמין, וגם אין אלגוריתם ברור לאימון שלהם.&lt;/p>
&lt;p>השיקול המקורי שלי היה להשתמש ב־auto-encoder כמו ב־&lt;a href="http://cs229.stanford.edu/proj2013/TakeuchiLee-ApplyingDeepLearningToEnhanceMomentumTradingStrategiesInStocks.pdf">המאמר הזה&lt;/a>, אבל אימון מקצה לקצה יותר מגניב.&lt;/p>
&lt;p>שיקול רציני יותר הוא ההצלחה של מודלי sequence to sequence בתרגום ובהכרה בדיבור, שבהם רצף מקודד בסופו של דבר לווקטור יחיד ואז מפוענח לייצוג אחר (כמו מדיבור לטקסט או מאנגלית לצרפתית). במבט הזה, כל הארכיטקטורה שתיארתי היא בעצם ה־encoder, ולא ממש פירטתי decoder.&lt;/p>
&lt;p>אבל אני רוצה להשיג משהו ספציפי עם השכבה הראשונה — זו שלוקחת כקלט את הווקטור בממד 4000 ומוציאה וקטור בממד 300. אני רוצה שהיא תמצא קורלציות או יחסים בין מניות שונות ותיצור מאפיינים מהן.&lt;/p>
&lt;p>האלטרנטיבה היא להעביר כל קלט דרך LSTM, אולי לשרשר (concatenate) את כל וקטורי הפלט ולהחשיב את זה כפלט של שלב ה־encoder. אני חושב שזה יהיה לא יעיל כי האינטראקציות והקורלציות בין מכשירים לבין המאפיינים שלהם יאבדו, ויידרש פי־10 יותר חישוב. מצד שני, ארכיטקטורה כזו יכולה במובן נאיבי להיות מקבילית על פני כמה GPUs ומכונות, שזה יתרון.&lt;/p>
&lt;h3 id="cnns">CNNs&lt;/h3>
&lt;p>לאחרונה היה גל של מאמרים על תרגום מכונה ברמת התו. &lt;a href="https://arxiv.org/pdf/1610.03017v2.pdf">המאמר הזה&lt;/a> תפס לי את העין, כי הם מצליחים ללכוד תלות ארוכת טווח בעזרת שכבה קונבולוציונית במקום RNN. לא קראתי אותו יותר מקריאה מהירה, אבל אני חושב ששינוי שבו אתייחס לכל מניה כערוץ ואבצע קונבולוציה על פני ערוצים תחילה (כמו בתמונות RGB) יכול להיות דרך נוספת ללכוד את דינמיקת השוק — באותה צורה שבה הם בעצם מקודדים משמעות סמנטית מתווים.&lt;/p></description><author/><guid>https://talperry.com/he/posts/classics/dlsm/</guid><pubDate>Sat, 03 Dec 2016 00:00:00 +0000</pubDate></item></channel></rss>