JavaRush /Java блог /Random UA /Машинне навчання для Java-розробників, частина 2

Машинне навчання для Java-розробників, частина 2

Стаття з групи Random UA
Машинне навчання для Java-розробників, частина 1
Машинне навчання для Java-розробників, частина 2 - 1

Оцінка цільової функції

Нагадаємо, цільова функція , вона ж – функція передбачень, є результатом процесу підготовки чи тренування. Математично складність полягає в тому, щоб знайти функцію, яка отримує на вхід змінну хта повертає передбачене значення у.
Машинне навчання для Java-розробників, частина 2 - 2
У машинному навчанні функція вартості (J(θ))використовується для обчислення помилки значення або вартості заданої цільової функції.
Машинне навчання для Java-розробників, частина 2 - 3
Функція вартості показує, наскільки точно модель відповідає тренувальним даним. Для визначення вартості цільової функції, наведеної вище, необхідно розрахувати квадратичну помилку кожного прикладу будинку (i). Помилка – відстань між розрахунковим значенням уі реальним значенням yбудинку з прикладу i.
Машинне навчання для Java-розробників, частина 2 - 4
Наприклад, реальна ціна будинку площею 1330 = 6,500,000 € . А відмінність передбаченої ціни будинку навченою цільовою функцією становить 7,032,478 € : різниця (або помилка) дорівнює 532,478 € . Ви також можете побачити цю різницю на графіці вище. Різниця (або помилка) показана у вигляді вертикальних пунктирних червоних ліній для кожної тренувальної пари ціна – площа. Вирахувавши вартість навченої цільової функції, потрібно підсумувати квадрати помилки для кожного будинку в прикладі і розрахувати основне значення. Чим менше значення ціни (J(θ)), тим точніше будуть передбачення нашої цільової функції. У лістингу-3, наведена проста реалізація Java функції вартості, що приймає на вхід цільову функцію, список тренувальних даних, і мітки пов'язані з ними. Значення передбачень обчислюватимуться в циклі, і помилка обчислюватиметься відніманням реального значення ціни (взятого з мітки). Пізніше квадрат помилок буде підсумовано і значення помилки буде розраховано. Вартість буде повернена як значення типу double:

Лістинг-3

public static double cost(Function<Double[], Double> targetFunction,
 List<Double[]> dataset,
 List<Double> 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;
}
Цікаво читати про Java? Вступайте до групи Java Developer !

Навчання цільової функції

Незважаючи на те, що функція вартості допомагає оцінити якість цільової функції і тета-параметрів, ви все ж таки треба знайти найвідповідніші параметри тета. Ви можете використовувати алгоритм градієнтного спуску.

Градієнтний спуск

Градієнтний спуск мінімізує функцію вартості. Це означає, що він використовується для пошуку параметрів тіта, які мають мінімальну вартість (J(θ))на основі тренувальних даних. Ось спрощений алгоритм обчислення нових, більш відповідних значень:
Машинне навчання для Java-розробників, частина 2 - 5
Так ось, параметри вектора тіта покращуватимуться з кожною ітерацією алгоритму. Коефіцієнт навчання задає кількість обчислень на кожній ітерації. Ці обчислення можна проводити, доки знайдено «хороші» значення тета. Наприклад, функція лінійної регресії нижче має три параметри тета:
Машинне навчання для Java-розробників, частина 2 - 6
На кожній ітерації буде обчислено нове значення для кожного з параметрів: , , і . Після кожної ітерації, можна створити нову, більш відповідну реалізацію використовуючи новий вектор вектор 0 , θ 1 , θ 2 } . У лістингу-4 наведено Java-код алгоритму градієнтного спаду. Тета для функції регресії буде навчено з використанням тренувальних даних, даних маркерів, коефіцієнта навчання . Результатом буде покращена цільова функція, яка використовує параметри тіта. Методθ0θ1θ2LinearRegressionFunction(α)train()буде викликатись знову і знову, і передавати нову цільову функцію та нові параметри тіта з попередніх обчислень. І ці виклики повторюватимуться, поки налаштована цільова функція не досягне плато мінімуму:

Лістинг-4

public static LinearRegressionFunction train(LinearRegressionFunction targetFunction,
 List<Double[]> dataset,
 List<Double> 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 повторень тета-параметри більше не змінюються, і ціна досягає стабільного «плато». Після цього точність цільової функції поліпшити в такий спосіб не вдасться.
Машинне навчання для Java-розробників, частина 2 - 7
У такому випадку, незважаючи на те, що вартість значно не зменшується після 500-600 ітерацій, цільова функція все ще не оптимальна. Це свідчить про невідповідність. У машинному навчанні термін "невідповідність" використовується для позначення того, що алгоритм навчання не знаходить основні тенденції даних. Якщо звернутися до реального досвіду, можна очікувати зменшення ціни за квадратний метр для більших володінь. Звідси ми можемо зробити висновок, що модель, використана для навчання цільової функції, не відповідає даним достатньою мірою. Невідповідність часто пов'язана із надмірним спрощенням моделі. Так сталося і в нашому випадку, цільова функція надто проста, і для аналізу використовує єдиний параметр – площу будинку. Тільки цієї інформації недостатньо для точного передбачення ціни будинку.

Додавання ознак та їх масштабування

Якщо ви виявабо, що ваша цільова функція не відповідає проблемі, яку ви намагаєтеся вирішити, її потрібно підкоригувати. Поширений спосіб коригування невідповідності - додавання додаткових ознак вектор ознак. У прикладі з ціною будинку можна додати такі характеристики, як кількість кімнат або вік будинку. Тобто замість використання вектора з одним значенням ознаки {size}для опису будинку можна використовувати вектор з кількома значеннями, наприклад,{size, number-of-rooms, age}. У деяких випадках кількості ознак доступних тренувальних даних не вистачає. Тоді варто спробувати застосувати поліноміальні ознаки, які обчислюються за допомогою існуючих. Наприклад, ви маєте можливість розширити цільову функцію визначення ціни будинку таким чином, щоб вона включала обчислювану ознаку квадратних метрів (x2):
Машинне навчання для Java-розробників, частина 2 - 8
Використання кількох ознак вимагає масштабування ознак , яке використовується для стандартизації діапазону різних ознак. Так, діапазон значень ознаки size 2 значно більше від діапазону значень ознаки size. Без масштабування ознак, size 2 надмірно впливатиме на функцію вартості. Помилка, що вноситься ознакою size 2 буде значно більше помилки, що вноситься ознакою size. Простий алгоритм масштабування ознак наведено нижче:
Машинне навчання для Java-розробників, частина 2 - 9
Цей алгоритм реалізований у класі FeaturesScalingу коді прикладу нижче. Клас FeaturesScalingпредставляє промисловий метод для створення функції масштабування, що підлаштовується на тренувальних даних. Усередині екземпляри тренувальних даних використовуються для обчислення середнього, мінімального та максимального значень. Результуюча функція використовує вектор ознак та виробляє новий з відмасштабованими ознаками. Масштабування ознак необхідне як процесу навчання, так процесу передбачення, як показано ниже:
// создание массива данных
List<Double[]> dataset = new ArrayList<>();
dataset.add(new Double[] { 1.0, 90.0, 8100.0 }); // feature vector of house#1
dataset.add(new Double[] { 1.0, 101.0, 10201.0 }); // feature vector of house#2
dataset.add(new Double[] { 1.0, 103.0, 10609.0 }); // ...
//...

// создание меток
List<Double> labels = new ArrayList<>();
labels.add(249.0); // price label of house#1
labels.add(338.0); // price label of house#2
labels.add(304.0); // ...
//...

// создание расширенного списка признаков
Function<Double[], Double[]> scalingFunc = FeaturesScaling.createFunction(dataset);
List<Double[]> scaledDataset = dataset.stream().map(scalingFunc).collect(Collectors.toList());

// создаем функцию которая инициализирует теты и осуществляет обучение //используя коэффициент обучения 0.1

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);
}

// делаем предсказание стоимости дома с площадью 600 m2
Double[] scaledFeatureVector = scalingFunc.apply(new Double[] { 1.0, 600.0, 360000.0 });
double predictedPrice = targetFunction.apply(scaledFeatureVector);
З додаванням все більшої кількості ознак, стає помітним зростання відповідності цільової функції, проте будьте обережні. Якщо ви зайдете надто далеко і додаєте надто багато ознак, ви можете в результаті повчити цільову функцію, яка надто відповідає.

Надвідповідність та перехресні перевірки

Надвідповідність виникає тоді, коли цільова функція або модель відповідає тренувальним даним занадто добре, настільки, що захоплює шум або випадкові відхилення в тренувальних даних. Приклад надвідповідності наведено на крайньому з права графіку нижче:
Машинне навчання для Java-розробників, частина 2 - 10
Як би там не було, надвідповідна модель дуже добре показує себе на тренувальних даних, але при цьому показуватиме погані результати на реальних невідомих даних. Існує кілька шляхів уникнути надвідповідності.
  • Використовуйте більший масив даних для тренування.
  • Використовувати менше ознак, як показано на графіках вище.
  • Використовувати покращений алгоритм машинного навчання, що бере до уваги регуляризацію.
Якщо алгоритм передбачення надвідповідає тренувальним даним, необхідно виключити ознаки, які приносять користі щодо його точності. Складність становить пошук ознак, які суттєвіше за інших впливають на точність передбачення. Як показано на графіках, надвідповідність можна визначити візуально за допомогою графіків. Це добре працює для графіків з 2 або 3 координатами, стає важко побудувати та оцінити графік якщо ви використовуєте більше ніж 2 ознаки. У перехресній перевірці ви перевіряєте ще раз моделі після навчання з використанням даних не відомих алгоритму після закінчення процесу навчання. Доступні марковані дані мають бути розбиті на 3 набори:
  • тренувальні дані;
  • перевірочні дані;
  • тестові дані.
У такому разі 60 відсотків промаркованих записів, що характеризують будинки, мають бути використані у процесі навчання варіантів цільового алгоритму. Після процесу навчання, половину даних, що залишабося (не використаних раніше), потрібно використовувати для перевірки того факту, що навчений цільовий алгоритм працює добре з невідомими даними. Як правило, алгоритм, який показує найкращі результати в порівнянні з іншими, і вибирається для використання. Дані використовуються для обчислення величини помилки для остаточно обраної моделі. Існують інші техніки перехресної перевірки, наприклад, як k-fold . Однак у цій статті я не описуватиму їх.

Інструменти машинного навчання та фреймворк Weka

Більшість фреймворків і бібліотек є великою колекцією алгоритмів машинного навчання. Крім цього вони надають зручний високорівневий інтерфейс для навчання, перевірки та обробки моделей даних. Weka один із найпопулярніших фреймворків для JVM. Weka – це Java-бібліотека для практичного застосування, що містить графічні тести для перевірки моделей. У прикладі нижче бібліотека Weka використовується для створення набору тренувальних даних, що містить ознаки та мітки. Метод setClassIndex()– для маркування. У Weka мітка визначена як клас:
// определяем атрибуты для признаков и меток
ArrayList<Attribute> 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);


// создаем и заполняем список признаков 5000 примеров
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 (Attribute Relation File Format), який підтримується графічними тестами 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:
// использование атрибута маркера topseller замість атрибута маркера цена
ArrayList 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);

Висновок

Хоча машинне навчання тісно пов'язане зі статистикою та використовує багато математичних концепцій, інструментарій машинного навчання дозволяє почати інтеграцію машинного навчання у ваші програми без глибоких знань математики. Тим не менш, чим краще ви зрозумієте основні алгоритми машинного навчання такі як, наприклад, алгоритм лінійної регресії, який ми досліджували в цій статті, тим більше можливостей вибрати правильний алгоритм і налаштувати його на оптимальну продуктивність. Переклад з англійської. Автор - Грегор Рот (Gregor Roth), Software Architect, JavaWorld.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ