
הרפתקת קוד טעימה
October 21, 2022 posted by Aviad Shalom
בשנתיים האחרונות למדתי מהקורסים של קורסרה - Andrew NG האגדי בזמני הפנוי. הצד היותר סובייטי שלי מאד מלקה את עצמו על זה שהתקדמתי עם ה-5 קורסים הללו לאט אבל כל התהליך הזה קרה בזמן שעבדתי בסטארטאפים מאד דרשניים (שגם שם לומדים המון, בהחלט שווה פוסט נפרד).
אבל כמו ישראלי טוב, ברגע שהיה לי מינימום מידע לפתירת בעיית הml הראשונה שלי - התחלתי.
אז מה הבעיה?
הבעיה שהוצעה לי לפתור ע״י ידידתי, מתכנתת אחרת שחובבת UI ובישולים, היא כזאת:
בהינתן URL של אתר מתכונים, תחזיר לי
JSON
עם מצרכים בלבד והוראות הכנה בלבד.
למה? כי ידידתי בנתה אפליקציית ״ספר מתכונים״ מאוד יפה, אבל, היה ממש נחמד אם הייתה לה יכולת לעשות לייבא (import) בהינתן URL. לכן היא רצתה שאחשוף לה API שיעזור לה עם זה.
אז על מה אני אכתוב פה?
- קצת על הכלים ויכולות שהיו לי בזמן כתיבת הפרוייקט
- איך שילבתי כמה כלים שלמדתי בcoursera בשביל לפתור את הבעיה
- תהליך הוקטוריזציה, איך הופכים מפסקה לוקטור - הקלט של הרשת נוירונים
- כמה אתגרים שקפצו בדרך ואיך ניגשתי אליהם
- קצת סדר עם דיאגרמה פשוטה
- אפילוג - סוף טוב הכל טוב(?)
רשת נוירונים יודעת לעבוד עם וקטורים בלבד,
בין אם במהלך האימון ושיפור המשקלים שלה וכמובן בזמן "המבצעי״. פה אנסה לתאר את המסע הארוך של
תהליך הוקטוריזציה שמהווה את רוב הלוגיקה. פתחתי פרויקט פייתון והתחלתי לעבוד על וקטוריזציה.
הטבלה נראת ככה:
זכרתי את *רוב* החלקים החשובים של להפוך טקסט לוקטור והם מהקורס Intro To Machine Learning: אבל מאיפה המילים הללו מגיעות בכלל?
המילים שיקבלו ייצוג בינארי בוקטור הם למעשה k המילים הכי נפוצות בדוגמאות (עשיתי ספירה נפרדת ל״הוראות הכנה״ וספירה נפרדת ל״מרכיבים״, כיוון שזה שני מודלים שונים)
אז כתבתי קוד נחמד שעובר על כל ה600 דוגמאות, ודוגם אילו מילים הן הכי נפוצות, לצערי התוצאות היו ממש פזורות ולא באמת היה נצחון מוחלט לקבוצה קטנה של מילים. ואז נזכרתי במשהו שהיה חסר במרק הזה עד כה...
מה שמעניין אותנו זה השורשים,
כי שורשים מייצגים הרבה יותר מילים, וזה אינטרס שלנו כאשר אנחנו עושים וקטוריזציה שהוקטור ייצג וייכנס כמה שיותר מקרים!
פה אני אציין ואודה לHebrew NLP שחושפים REST API נחמד שמאפשר להמיר מילה לשורש שלה ועוד הרבה דברים אחרים בתחום הNLP שאני עדיין לא יודע. ״להיזהר לא לשים יותר מדי מלח״ ייתן לנו ״זהר״, ״מלח״
אחרי שהחלטנו על סדר השורשים לפי תדירות, נוכל לקחת את השורשים בפסקה ו״להדליק״ את הקורדינטות הרלוונטיות כפי שמוצג למטה וזה ייתן לנו וקטור לתפארת
אחרי כחודש וחצי, היה לי כ-600 דוגמאות להתחיל לעבוד איתן ולאמן את המודל-צעצוע שלקחתי מהקורס ~ יאפ חודש וחצי, יעילות לא בשמיים - ככה זה שאתה מבקש מהחברים שלך לעבוד בחינם~
הבדיקה הראשונית שלי גרמה לי
להיות סופר אופטימי, כשבדקתי
את ה-classifier קיבלתי דיוק לא
רע של סביב ה80% על test set,
ויחסית ל-600 דוגמאות הרגשתי שאני בכיוון טוב
- אז אספנו עוד דוגמאות, ולאט לאט הגענו לדיוק בtest set של 97%!
בשלב הזה, הוספתי כבר את מנגנון "החלון הרץ" שם פחות או יותר המציאות הכואבת חבטה בפני - זה שהclassfier עבור פסקה טוב לא אומר כלום על שאר ההיוריסטיקה...
ציפיתי שהוא ייתן לי רצף תשובות באופן הבא(ירוק- אזור רלווטי לפי המודל, אדום - אזור - לא רלוונטי לפי המודל):
לבעיה מעלה מצאתי שתי סיבות עיקריות:
1- קלאסיפייר לא היה מדויק מספיק, בהיבט של ״מה כן מתכון״ הוא היה ״נדיב״ בכך שסיווג פסקאות כמתכון מתי שהם לא. במילים אחרות היו לי הרבה false positives
2- באתרי מתכון, באופן טבעי, יש הרבה חלקים שהם מאד ״מתכוניים״ אך הם אינם ה״תכל׳ס״ של המתכון. גם אנחנו בתור בני אדם אם היינו מקבלים רק חלק מההקשר היינו יכולים לטעות בין תגובה לבין מתכון.
ניסיתי להיות אופטימי ולכן ניסיתי לתקוף קודם את הבעיה הראשונה בצפייה שהפתרון יהיה מספיק טוב שימצא רק את החלקים הרלוונטים.
איך? החלטתי שהוקטור יהיה קצת יותר מורכב מוקטור בינארי שמעיד על העדרות או הימצאות של מילים. החלטתי שצריך שהוקטור יכיל מידע נוסף שירמוז על מבנה הפסקה.
איך? חשבתי, בתור בן אדם, שמרפרף על אתר מתכונים בין הפרסומות וסיפורי הרקע של המחברים, מה גורם לי להגיד לעצמי ״וואלה זה חלק מהמתכון״. אז היה לי תהליך של להפוך אינטואיציה לדברים קונקרטים כמו:
אחרי שנתתי לפיצ׳רים הללו להיות חלק מהוקטור שמייצג את הפסקה הדיוק נעשה הרבה יותר טוב וגם החלון הרץ השתפר פלאים והחזיר לי פחות זבל.
אבל עדיין יש זבל! והפתרון הנוכחי לא היה מספיק טוב אם אני רוצה לתת חוויה טובה למשתמש.
פה היו לי כמה כיוונים:
איך זה יעבוד?
במקום שהחלון שלנו יסרוק את כל הטקסט, הוא קודם כל ינסה למצוא שורה שמתחילה עם אחת מהמילים הנפוצות עבור התחלה של מתכון.
משם החלון יבדוק אם יש רצף ״מתכוני״ עם וודאות גדולה. במידה וכבר מהפסקה הראשונה קיבלנו סבירות לא טובה, נעבור למקום הבא.
איך זה מומש?
אספנו ידנית כמות גדולה של התחלות של הוראות הכנה או מרכיבים, לדוגמא: ״מרכיבים״, ״מצרכים״, ״מה צריך?״ וכו׳
הגדרתי את כל השורות שמכילות לפחות אחת מן המילים הללו כפונטציליות לנקודת התחלה, ושיניתי שהחלון רץ יעבור *רק* עילהם. רק במידה ולא נמצא אף ״התחלת מתכון״ עשיתי fallback לסריקת כל הטקסט.
אני אוסיף פה גם רפרומה נוספת שהוביל אחד מחבריי שהצטרף לפרוייקט, רועי ניצן, שלקח על עצמו לבדוק קונפיגורציות שונות של רשת הנוירונים (כמות שכבות ונוירונים בכל שכבה) והשוואת מדדי הדיוק שלהם אחד כגנד השני.
אחרי הרפורמות שהזכרתי למעלה 90 אחוז מהאתרים ״תוכלסו״ בהצלחה.
בניתי סביב הAPI הזה אתר פשוט ואפליקציה.
האתר הפשוט, הוא פשוט UI עם שדה טקסט שאפשר להעתיק-להדביק לשם URLים.
אפליקציה לאנרואיד מבוססת webview, גם די פשוט, מאפשר לשתף אתר עם האפליקציה ולקבל תיכלוס מהיר (זו הייתה דרישה מאחת הלקוחות הראשונות של האפליקציה)
מה לגביי הידידה עם האפליקציה? היא החליטה לחכות עד שהמודל שלי יהיה מושלם. אם הצלחתם להבין את הקונספט של ה״חלון הרץ״, האלגוריתם לא בהכרח יחזיר **רק** את המרכיבים או הוראות ההכנה אלא יהיה קצת זנב לא רלוונטי(קצת משמע לכל היותר 5 שורות)
יותר ממוזמנים להכנס ולשחק עם זה בגרסת ה
web
או אם יש לכם
android
כלשהו תוכלו ישר לשתף את מתכון חופר עם האפליקציה וישר לקבל את התכלוס
1- להשתמש בספריית NN אמיתית ולא שיעורי בית שיש לי מקורסרה(למרות שזה עדיין מדהים אותי שכל הדבר הזה עובד!)
2- לראות שיש לי גרסה יציבה בארץ עם 100-200 משתמשים מרוצים.
3- לעבור לאנגלית בשביל לתפוס קהל גדול יותר.
4- לתת ערך נוסף לאפליקציה ע״י שינוי המרכיבים (ישרת, צמחונים, טבעונים, אנשים עם אלרגיות וכל דייטה מסויימת)
4- לנהל משתמשים.
לכל האנשים שלקחו חלק בפרוייקט הזה:
לליאור
ורותם
שהטריגו
את הכל בעצם,
לשיר ניצן
על התרומת קוד בצד שרת וסיעורי מוחות ששלחו אותנו מחוץ לקופסא,
לרועי ניצן
על האופטימזיציה של הרשת נוירונים
ולעומר שומרת
על
שיפור הפריסה בשרת. וכל זה בזמן שגרמתם לפרוייקט הזה להעשות תחת אווירה ממש נעימה וזורמת!
הכלים והיכולות שהיו לי באותו זמן:
בהתחלה החלטתי לעזוב את המחשב ופשוט
לקשקש איזשהו רעיון אבסטרקטי
(כפי שכתבתי בפוסט הראשון, היה צריך להתחיל איפשהו)
- וגם באותו זמן בדיוק עברתי לדירה עם המון חלונות אז…
טקסט ל-וקטור
במקביל עשיתי צעד ״ירייה מהמותן״ ופתחתי גוגל שיט בשביל לאגור דוגמאות
לאימון המודל ואותו שיתפתי עם כמה מחבריי עם גישה של היי
יש מצב שאתם עושים לי טובה ממש מוזרה?
בשלב הזה הייתי כל כך סקפטי שהכלים והעקרונות של הקורס ייקחו אותי רחוק מספיק רחוק ושזה אשכרה יעבוד, אבל מה אני בעצם ביקום הזה אם אני לא אנסה לפחות(?)
בהנחה והוקטור הוא בגודל k כלשהו, כל ערך בו מייצג מילה. 1 אם המילה נמצאת בטקסט או 0 אם היא לא (ראו דוגמא עוד כמה פסקאות)
שורשים!
אז ״להמליח את השניצל ולחמם״ יתן לנו את ״מלח״, ״חמם״
כמה אתגרים שקפצו ואיך הם נפתרו
אבל! קיבלתי משהו
כזה:
פה אפשר לראות שהמודל תייג
יותר מידי דברים כחלק מהמתכון, ואני לא מאשים אותו, הפיסות טקסט שהכניס היו בהחלט מאוד
״מתכוניות״
אני כמובן איפשרתי למשתמשים לדווח על
לינקים למתכונים שלא סוכמו בהצלחה.
המשתמש פשוט מדווח על הלינק השבור ובעמוד פרטי שלי אני
מתחקר אותם ובעיקר מוסיף אותם training set שלי.
דיאגרמה קטנה שעושה קצת סדר
בדיאגרמה אפשר לראות את הקומפננטות העיקריות והתקשורת בינהם.
כמו כל דיאגרמה, היא כמובן מפשטת מאוד את המציאות, חסרים פה אמצעי הcaching שהוספתי וכל צד
frontend
שאולי אדבר עליו בשבלב אחר.
אפילוג:
מה הלאה?
רציתי גם להגיד תודה