למידת מכונה למפתחי Java, חלק 1
הערכת פונקציה אובייקטיבית
הבה נזכיר שפונקציית המטרה
hθ
, הידועה גם כפונקציית החיזוי, היא תוצאה של תהליך ההכנה או האימון. מבחינה מתמטית, האתגר הוא למצוא פונקציה שלוקחת משתנה כקלט
х
ומחזירה את הערך החזוי
у
.
בלמידת מכונה, פונקציית עלות
(J(θ))
משמשת לחישוב ערך השגיאה או ה"עלות" של פונקציית מטרה נתונה.
פונקציית העלות מראה עד כמה המודל מתאים לנתוני האימון. כדי לקבוע את העלות של פונקציית המטרה המוצגת לעיל, יש צורך לחשב את השגיאה בריבוע של כל בית לדוגמה
(i)
. שגיאה היא המרחק בין הערך המחושב
у
לערך האמיתי
y
של הבית מהדוגמה
i
.
לדוגמה, המחיר האמיתי של בית עם שטח של
1330 = 6,500,000 € . וההבדל בין מחיר הבית החזוי על ידי פונקציית המטרה המאומנת הוא
€7,032,478 : ההפרש (או השגיאה) הוא
€532,478 . אתה יכול גם לראות את ההבדל הזה בגרף למעלה. ההבדל (או השגיאה) מוצג כקווים אדומים מקווקו אנכיים עבור כל זוג אימונים של מחיר-אזור. לאחר חישוב העלות של פונקציית המטרה המאומנת, עליך לסכם את השגיאה בריבוע עבור כל בית בדוגמה ולחשב את הערך העיקרי. ככל שערך המחיר קטן יותר
(J(θ))
, כך תחזיות הפונקציה האובייקטיבית שלנו יהיו מדויקות יותר. רשימה
3 מציגה יישום פשוט של Java של פונקציית עלות שמקבלת כקלט פונקציה אובייקטיבית, רשימה של נתוני אימון ותוויות הקשורות אליהם. ערכי החיזוי יחושבו בלולאה והשגיאה תחושב על ידי הפחתת ערך המחיר האמיתי (נלקח מהתווית). בהמשך, ריבוע השגיאות יסוכם וערך השגיאה יחושב. העלות תוחזר כערך מסוג
double
:
רישום-3
public static double cost(Function<ltDouble[], Double> targetFunction,
List<ltDouble[]> dataset,
List<ltDouble> labels) {
int m = dataset.size();
double sumSquaredErrors = 0;
for (int i = 0; i < m; i++) {
Double[] featureVector = dataset.get(i);
double predicted = targetFunction.apply(featureVector);
double label = labels.get(i);
double gap = predicted - label;
sumSquaredErrors += Math.pow(gap, 2);
}
return (1.0 / (2 * m)) * sumSquaredErrors;
}
לימוד פונקציית המטרה
למרות שפונקציית העלות עוזרת להעריך את האיכות של פונקציית היעד ופרמטרי תטא, עדיין עליך למצוא את פרמטרי התטא המתאימים ביותר. אתה יכול להשתמש באלגוריתם הירידה בשיפוע בשביל זה.
ירידה בשיפוע
ירידה בשיפוע ממזערת את פונקציית העלות. המשמעות היא שהוא משמש כדי למצוא את פרמטרי התטא בעלי העלות המינימלית
(J(θ))
בהתבסס על נתוני ההדרכה. להלן אלגוריתם פשוט לחישוב ערכי תטא חדשים ומתאימים יותר:
לכן, הפרמטרים של וקטור התטא ישתפרו עם כל איטרציה של האלגוריתם. מקדם הלמידה α מציין את מספר החישובים בכל איטרציה. ניתן לבצע חישובים אלה עד למציאת ערכי תטא "טובים". לדוגמה, לפונקציית הרגרסיה הלינארית למטה יש שלושה פרמטרים של תטא:
בכל איטרציה, יחושב ערך חדש עבור כל אחד מפרמטרי תטא: , , ו . לאחר כל איטרציה, ניתן ליצור מימוש חדש ומתאים יותר באמצעות וקטור התטא החדש
{θ 0 , θ 1 , θ 2 } . רישום
-4 מציג את קוד ה-Java עבור אלגוריתם הדעיכה של גרדיאנט. תטא עבור פונקציית הרגרסיה יאומן באמצעות נתוני אימון, נתוני סמן, קצב למידה . התוצאה תהיה פונקציה אובייקטיבית משופרת באמצעות פרמטרי תטא. השיטה תיקרא שוב ושוב, ותעביר את פונקציית המטרה החדשה ואת פרמטרי התטא החדשים מחישובים קודמים. והשיחות הללו יחזרו על עצמן עד שפונקציית המטרה המוגדרת תגיע לרמה מינימלית:
θ0
θ1
θ2
LinearRegressionFunction
(α)
train()
רישום-4
public static LinearRegressionFunction train(LinearRegressionFunction targetFunction,
List<ltDouble[]> dataset,
List<ltDouble> labels,
double alpha) {
int m = dataset.size();
double[] thetaVector = targetFunction.getThetas();
double[] newThetaVector = new double[thetaVector.length];
for (int j = 0; j < thetaVector.length; j++) {
double sumErrors = 0;
for (int i = 0; i < m; i++) {
Double[] featureVector = dataset.get(i);
double error = targetFunction.apply(featureVector) - labels.get(i);
sumErrors += error * featureVector[j];
}
double gradient = (1.0 / m) * sumErrors;
newThetaVector[j] = thetaVector[j] - alpha * gradient;
}
return new LinearRegressionFunction(newThetaVector);
}
כדי להבטיח שהעלות יורדת ללא הרף, תוכל להפעיל את פונקציית העלות
J(θ)
לאחר כל שלב אימון. לאחר כל איטרציה, העלות אמורה לרדת. אם זה לא קורה, זה אומר שהערך של מקדם הלמידה גדול מדי והאלגוריתם פשוט החמיץ את הערך המינימלי. במקרה כזה, אלגוריתם דעיכת השיפוע נכשל. התרשים שלהלן מציגים את פונקציית המטרה תוך שימוש בפרמטרים החדשים והמחושבים של תטא, החל בוקטור התטא ההתחלתי
{1.0, 1.0}
. העמודה השמאלית מציגה את העלילה של פונקציית החיזוי לאחר 50 איטרציות; עמודה אמצעית לאחר 200 חזרות; והעמודה הימנית לאחר 1000 חזרות. מתוך אלו אנו יכולים לראות שהמחיר יורד לאחר כל איטרציה, ופונקציית האובייקטיב החדשה מתאימה יותר ויותר. לאחר 500-600 חזרות, פרמטרי התטא כבר לא משתנים משמעותית, והמחיר מגיע לרמה יציבה. לאחר מכן, לא ניתן לשפר את הדיוק של פונקציית המטרה בדרך זו.
במקרה זה, למרות שהעלות כבר לא יורדת משמעותית לאחר 500-600 איטרציות, הפונקציה האובייקטיבית עדיין לא אופטימלית. זה מעיד על
אי התאמה . בלמידת מכונה, המונח "חוסר עקביות" משמש כדי לציין שאלגוריתם הלמידה אינו מוצא מגמות בסיסיות בנתונים. בהתבסס על ניסיון בחיים האמיתיים, סביר להניח שצפוי להוזלה במחיר למ"ר עבור נכסים גדולים יותר. מכאן נוכל להסיק שהמודל המשמש לתהליך למידת פונקציית המטרה אינו מתאים מספיק לנתונים. הפער נובע לרוב מפישוט יתר של המודל. זה קרה במקרה שלנו, הפונקציה האובייקטיבית פשוטה מדי, ולניתוח היא משתמשת בפרמטר בודד - שטח הבית. אבל המידע הזה לא מספיק כדי לחזות במדויק את מחיר הבית.
הוספת תכונות והגדלתן
אם אתה מגלה שפונקציית המטרה שלך אינה מתאימה לבעיה שאתה מנסה לפתור, יש להתאים אותה. דרך נפוצה לתקן חוסר עקביות היא הוספת תכונות נוספות לווקטור התכונה. בדוגמה של מחיר בית ניתן להוסיף מאפיינים כמו מספר חדרים או גיל הבית. כלומר, במקום להשתמש בוקטור בעל ערך תכונה אחד
{size}
לתיאור בית, ניתן להשתמש בווקטור עם מספר ערכים, לדוגמה,
{size, number-of-rooms, age}.
במקרים מסוימים, מספר התכונות בנתוני האימון הזמינים אינו מספיק. אז כדאי לנסות להשתמש בתכונות פולינומיות שמחושבות באמצעות הקיימות. לדוגמה, יש לך הזדמנות להרחיב את הפונקציה האובייקטיבית לקביעת מחיר הבית כך שתכלול תכונה מחושבת של מטרים רבועים (x2):
שימוש במספר תכונות דורש
קנה מידה של תכונות , המשמש לסטנדרטיזציה של הטווח על פני תכונות שונות. לפיכך, טווח הערכים של תכונת
הגודל 2 גדול משמעותית מטווח הערכים של תכונת הגודל. ללא קנה מידה של תכונה,
גודל 2 ישפיע בצורה בלתי הולמת על פונקציית העלות. השגיאה שמוצגת על ידי תכונת
size 2 תהיה גדולה משמעותית מהשגיאה שמוצגת על ידי תכונת size. אלגוריתם קנה מידה פשוט של תכונה ניתן להלן:
אלגוריתם זה מיושם במחלקה
FeaturesScaling
בקוד לדוגמה למטה. הכיתה
FeaturesScaling
מציגה שיטה מסחרית ליצירת פונקציית קנה מידה המכווננת לנתוני אימון. באופן פנימי, מופעי נתוני האימון משמשים לחישוב הערכים הממוצעים, המינימום והמקסימום. הפונקציה המתקבלת לוקחת את וקטור התכונה ומייצרת אחד חדש עם התכונות המותאמות. קנה המידה של תכונות הכרחי הן לתהליך הלמידה והן לתהליך החיזוי, כפי שמוצג להלן:
List<ltDouble[]> dataset = new ArrayList<>();
dataset.add(new Double[] { 1.0, 90.0, 8100.0 });
dataset.add(new Double[] { 1.0, 101.0, 10201.0 });
dataset.add(new Double[] { 1.0, 103.0, 10609.0 });
List<ltDouble> labels = new ArrayList<>();
labels.add(249.0);
labels.add(338.0);
labels.add(304.0);
Function<ltDouble[], Double[]> scalingFunc = FeaturesScaling.createFunction(dataset);
List<ltDouble[]> scaledDataset = dataset.stream().map(scalingFunc).collect(Collectors.toList());
LinearRegressionFunction targetFunction = new LinearRegressionFunction(new double[] { 1.0, 1.0, 1.0 });
for (int i = 0; i < 10000; i++) {
targetFunction = Learner.train(targetFunction, scaledDataset, labels, 0.1);
}
Double[] scaledFeatureVector = scalingFunc.apply(new Double[] { 1.0, 600.0, 360000.0 });
double predictedPrice = targetFunction.apply(scaledFeatureVector);
ככל שמתווספים יותר ויותר תכונות, ההתאמה לפונקציה האובייקטיבית גדלה, אך היזהר. אם תלך רחוק מדי ותוסיף יותר מדי תכונות, אתה עלול בסופו של דבר ללמוד פונקציה אובייקטיבית שהיא כושר יתר.
התאמה יתר ואימות צולב
התאמת יתר מתרחשת כאשר הפונקציה או המודל האובייקטיבי מתאימים יותר מדי לנתוני האימון, עד כדי כך שהם קולטים רעש או שינויים אקראיים בנתוני האימון. דוגמה להתאמת יתר מוצגת בגרף הימני ביותר למטה:
עם זאת, מודל התאמה יתר מתפקד טוב מאוד בנתוני אימון, אך יבצע ביצועים גרועים בנתונים לא ידועים אמיתיים. ישנן מספר דרכים להימנע מהתאמה יתר.
- השתמש במערך נתונים גדול יותר לאימון.
- השתמש בפחות תכונות כפי שמוצג בתרשימים למעלה.
- השתמש באלגוריתם למידת מכונה משופר שלוקח בחשבון רגולציה.
אם אלגוריתם חיזוי מתאים יותר מדי לנתוני האימון, יש צורך לבטל תכונות שאינן מועילות לדיוק שלה. הקושי הוא למצוא תכונות שיש להן השפעה משמעותית יותר על דיוק החיזוי מאחרות. כפי שמוצג בגרפים, ניתן לקבוע התאמה חזותית באמצעות גרפים. זה עובד היטב עבור גרפים עם 2 או 3 קואורדינטות, זה הופך להיות קשה לשרטט ולהעריך את הגרף אם אתה משתמש ביותר מ-2 תכונות. באימות צולב, אתה בודק מחדש מודלים לאחר אימון תוך שימוש בנתונים שאינם ידועים לאלגוריתם לאחר השלמת תהליך האימון. יש לחלק את הנתונים המסומנים הזמינים ל-3 קבוצות:
- נתוני אימון;
- נתוני אימות;
- נתוני בדיקה.
במקרה זה, יש להשתמש ב-60 אחוז מהרשומות המסומנות המאפיינות את הבתים בתהליך של אימון גרסאות של אלגוריתם היעד. לאחר תהליך האימון, יש להשתמש במחצית מהנתונים הנותרים (שלא נעשה בהם שימוש קודם) כדי לוודא שאלגוריתם היעד המאומן מתפקד היטב על הנתונים הלא ידועים. בדרך כלל, האלגוריתם שפועל טוב יותר מאחרים נבחר לשימוש. שאר הנתונים משמשים לחישוב ערך השגיאה עבור המודל הסופי שנבחר. ישנן טכניקות אימות צולבות אחרות, כגון
k-fold . עם זאת, לא אתאר אותם במאמר זה.
כלי למידת מכונה ומסגרת Weka
רוב המסגרות והספריות מספקות אוסף נרחב של אלגוריתמים של למידת מכונה. בנוסף, הם מספקים ממשק נוח ברמה גבוהה להדרכה, בדיקה ועיבוד מודלים של נתונים. Weka היא אחת המסגרות הפופולריות ביותר עבור ה-JVM. Weka היא ספריית Java מעשית המכילה מבחנים גרפיים לאימות מודלים. הדוגמה שלהלן משתמשת בספריית Weka כדי ליצור מערך הדרכה המכיל תכונות ותוויות. שיטה
setClassIndex()
- לסימון. ב-Weka, תווית מוגדרת כמחלקה:
ArrayList<ltAttribute> attributes = new ArrayList<>();
Attribute sizeAttribute = new Attribute("sizeFeature");
attributes.add(sizeAttribute);
Attribute squaredSizeAttribute = new Attribute("squaredSizeFeature");
attributes.add(squaredSizeAttribute);
Attribute priceAttribute = new Attribute("priceLabel");
attributes.add(priceAttribute);
Instances trainingDataset = new Instances("trainData", attributes, 5000);
trainingDataset.setClassIndex(trainingSet.numAttributes() - 1);
Instance instance = new DenseInstance(3);
instance.setValue(sizeAttribute, 90.0);
instance.setValue(squaredSizeAttribute, Math.pow(90.0, 2));
instance.setValue(priceAttribute, 249.0);
trainingDataset.add(instance);
Instance instance = new DenseInstance(3);
instance.setValue(sizeAttribute, 101.0);
...
ניתן לשמור ולטעון את ערכת הנתונים ואת אובייקט הדוגמה מקובץ. Weka משתמשת
ב-ARFF (פורמט קובץ יחסי תכונה) הנתמך על ידי המדדים הגרפיים של Weka. מערך נתונים זה משמש לאימון פונקציה אובייקטיבית המכונה מסווג ב-Weka. קודם כל, עליך להגדיר את פונקציית המטרה. הקוד שלהלן
LinearRegression
יצור מופע של המסווג. מסווג זה יקבל הכשרה באמצעות
buildClassifier()
. השיטה
buildClassifier()
בוחרת פרמטרי תטא על סמך נתוני אימון בחיפוש אחר מודל היעד הטוב ביותר. עם Weka, אתה לא צריך לדאוג לגבי הגדרת קצב הלמידה או מספר האיטרציות. Weka גם מבצעת שינוי קנה מידה עצמאי.
Classifier targetFunction = new LinearRegression();
targetFunction.buildClassifier(trainingDataset);
לאחר ביצוע הגדרות אלה, ניתן להשתמש בפונקציה האובייקטיבית כדי לחזות את מחיר הבית, כפי שמוצג להלן:
Instances unlabeledInstances = new Instances("predictionset", attributes, 1);
unlabeledInstances.setClassIndex(trainingSet.numAttributes() - 1);
Instance unlabeled = new DenseInstance(3);
unlabeled.setValue(sizeAttribute, 1330.0);
unlabeled.setValue(squaredSizeAttribute, Math.pow(1330.0, 2));
unlabeledInstances.add(unlabeled);
double prediction = targetFunction.classifyInstance(unlabeledInstances.get(0));
Weka מספקת שיעור
Evaluation
לבדיקת מסווג או דגם מיומן. בקוד שלהלן, נעשה שימוש במערך נבחר של נתוני אימות כדי למנוע תוצאות שגויות. תוצאות המדידה (עלות השגיאה) יוצגו בקונסולה. בדרך כלל, תוצאות הערכה משמשות להשוואת מודלים שהוכשרו באמצעות אלגוריתמים שונים של למידת מכונה, או וריאציות של אלה:
Evaluation evaluation = new Evaluation(trainingDataset);
evaluation.evaluateModel(targetFunction, validationDataset);
System.out.println(evaluation.toSummaryString("Results", false));
הדוגמה שלמעלה משתמשת ברגרסיה ליניארית, אשר מנבאת ערכים מספריים, כגון מחיר בית, על סמך ערכי קלט. רגרסיה לינארית תומכת בחיזוי של ערכים מספריים מתמשכים. כדי לחזות ערכים בינאריים ("כן" ו"לא"), עליך להשתמש באלגוריתמים אחרים של למידת מכונה. למשל, עץ החלטות, רשתות עצביות או רגרסיה לוגיסטית.
Classifier targetFunction = new Logistic();
targetFunction.buildClassifier(trainingSet);
אתה יכול להשתמש באחד מאלגוריתמים אלה, למשל, כדי לחזות אם הודעת דואר אלקטרוני היא ספאם, או לחזות את מזג האוויר, או לחזות אם בית יימכר היטב. אם אתה רוצה ללמד את האלגוריתם שלך לחזות את מזג האוויר או כמה מהר בית יימכר, אתה צריך מערך נתונים אחר, למשל.
topseller:
ArrayList<string> classVal = new ArrayList<>();
classVal.add("true");
classVal.add("false");
Attribute topsellerAttribute = new Attribute("topsellerLabel", classVal);
attributes.add(topsellerAttribute);
מערך נתונים זה ישמש לאימון מסווג חדש
topseller
. לאחר הכשרה, קריאת החיזוי אמורה להחזיר אינדקס מחלקה אסימון שניתן להשתמש בו כדי לקבל את הערך החזוי.
int idx = (int) targetFunction.classifyInstance(unlabeledInstances.get(0));
String prediction = classVal.get(idx);
סיכום
למרות שלמידת מכונה קשורה קשר הדוק לסטטיסטיקה ומשתמשת במושגים מתמטיים רבים, ערכת הכלים למידת מכונה מאפשרת לך להתחיל לשלב למידת מכונה בתוכניות שלך ללא ידע מעמיק במתמטיקה. עם זאת, ככל שתבינו טוב יותר את האלגוריתמים הבסיסיים של למידת מכונה, כמו אלגוריתם הרגרסיה הליניארית שבדקנו במאמר זה, כך תוכלו לבחור את האלגוריתם הנכון ולכוון אותו לביצועים מיטביים.
תרגום מאנגלית. מחבר: גרגור רוט, אדריכל תוכנה, JavaWorld.
GO TO FULL VERSION