Статья Начало работы с Java Machine Learning

Java

Это 14-й день моего участия в августовском испытании обновлений.Подробности о событии:Испытание августовского обновления

Введение

Сейчас машинное обучение — очень модное слово, и многие вещи в жизни будут связаны с машинным обучением. Итак, вы знаете, что такое машинное обучение? На самом деле, многие читатели уже ознакомились с некоторым содержанием машинного обучения, но они не используют слово машинное обучение.

Сегодня я познакомлю вас со знаниями, связанными с машинным обучением, и использую Java для реализации очень распространенного алгоритма машинного обучения — линейной регрессии.

2. Машинное обучение

Машинное обучение — это часть искусственного интеллекта. Целью машинного обучения является создание модели, которая постоянно учится оптимизировать параметры на основе имеющегося опыта. Используйте эту оптимизированную модель, чтобы предсказать, что еще не произошло. Здесь задействованы четыре вещи:

  1. Модель
  2. Опыт
  3. учиться
  4. предсказывать

Модель — это алгоритм машинного обучения, о котором мы часто говорим, и обычно мы выбираем некоторые существующие модели. Такие как линейная регрессия, логистическая регрессия, машины опорных векторов и т. д. Нам нужно выбрать модель в соответствии с типом проблемы, в этой статье представлена ​​модель линейной регрессии.

На самом деле опыт — это данные, которые мы часто говорим.Если мы сталкиваемся с проблемой прогнозирования погоды, наш опыт — это данные о погоде за предыдущие часы или дни.

Обучение — очень важный шаг в машинном обучении. Но сама машина не учится сама по себе, ей нужен человек, чтобы сказать машине, как учиться. Поэтому нам нужно определить специальную функцию (функцию потерь), чтобы помочь параметрам машинного обучения.

Прогнозирование — это процесс, в котором мы используем модель, и это самый простой шаг.

Теперь, когда мы понимаем некоторые основы, давайте подробнее рассмотрим некоторые детали линейной регрессии.

3. Линейная регрессия

3.1 Найдите правила

Я полагаю, что все делали такую ​​задачу.Учитывая следующие числа, давайте угадаем, какой будет следующий номер:

4
7
10
13
16

Если мы будем угадывать вслепую, мы вряд ли сможем угадать результат, теперь мы предполагаем, что приведенные выше числа удовлетворяют уравнению:

y=kx+by = kx + b

Я беру порядковый номер числа как x и полученное число как y. Тогда мы можем получить следующие координаты:

(1, 4)
(2, 7)
(3, 10)
(4, 13)
(5, 16)

Затем выберите две координаты для включения в уравнение, например (1, 4) и (3, 10), мы можем получить следующие уравнения:

{k+b=43k+b=10\begin{cases} k + b = 4 \\ 3k + b = 10 \end{cases}

мы можем решить

{k=3b=1\begin{cases} k = 3 \\ b = 1 \end{cases}

Затем подставьте другие координаты, чтобы проверить точность уравнения. Тогда мы можем угадать, какое будет следующее число, x следующего числа будет 6, тогда следующее число должно быть 19.

3.2 Линейная регрессия

Фактически вышеизложенное является линейной регрессией, наша цель — найти набор оптимальных k и b. Но мы обычно пишем уравнение линейной регрессии следующим образом:

y=wx+by = wx + b

Между этим уравнением и предыдущим нет никакой разницы, за исключением того, что оно представлено другой буквой. Где w означает вес (вес), b означает смещение (bias).

Теперь способ решения параметров отличается от предыдущего: вместо того, чтобы напрямую вводить координаты и решать уравнение, мы корректируем параметры с помощью алгоритма, называемого градиентным спуском. Мы поговорим об этом подробно в следующем разделе.

В машинном обучении мы называем данные, используемые для настройки (обучения) параметров, обучающими данными. где x называется собственным значением, а y называется целевым значением.

3. Функция потерь

Перед обучением мы обычно даем модели параметр инициализации. Но этот начальный параметр обычно не очень хорош, с таким набором параметров мы получим плохой результат. Насколько плохой результат? Это требует определения функции потерь, чтобы оценить, насколько плох этот набор параметров.

Функция потерь — это функция, используемая для оценки качества параметров, и обычно у нас есть много готовых функций на выбор. Здесь мы выбираем среднеквадратичную ошибку в качестве нашей функции потерь:

L=[y(wx+b)]2L = [y - (wx + b)]^2

где y и x — наши известные данные, а w и b — переменные этой функции. мы используемwx+bВычислите результат прогноза, а затем вычтите прогнозируемый y из фактического y — это ошибка нашего прогноза. Чтобы гарантировать, что это значение является положительным числом, добавляется квадрат.

Здесь следует отметить, что мы можем выбрать возведение корня в квадрат или не открывать корень. Когда мы берем квадратный корень, это называется среднеквадратической ошибкой.

Выше мы просто посчитали проигрыш для одного из чисел, но обычно у нас много обучающих данных, поэтому нужно усреднять все ошибки. Таким образом, полная стоимость потерь рассчитывается следующим образом:

L=1ni=0n[y(wx+b)]2L = \frac{1}{n}\sum_{i=0}^n[y - (wx + b)]^2

Вот обычная операция.

4. Градиентный спуск

4.1 Градиентный спуск

Сначала мы можем наблюдать график функции между параметром и значением потерь.Для удобства просмотра мы рассматриваем только параметр w.Изображение выглядит следующим образом:

在这里插入图片描述

где ось x — это параметр w, а ось y — значение потерь. Из рисунка видно, что точка В является точкой с наименьшим значением потерь (независимо от площади вне рисунка).

Слева и справа от точки B есть две точки A и C. Разберем эти две точки по отдельности.

Точка А находится левее точки Б. Для того, чтобы значение убытка было меньше, нам нужно обновить параметры вправо, то есть увеличить параметры. Из рисунка видно, что наклон касательной в точке А меньше 0, то есть производная в точке А меньше 0. Это очень важно, и мы подробно обсудим это после анализа пункта C.

Точка C находится правее точки B, поэтому нам нужно обновить параметры влево, то есть уменьшить параметры. Из рисунка видно, что наклон точки С больше 0, то есть производная в точке С больше 0.

Проанализировав две точки A и C, мы обнаружили, что. Когда производная больше 0, нам нужно уменьшить параметр, а когда производная меньше 0, нам нужно увеличить параметр. Поэтому мы можем обновить параметры по следующей формуле:

w_=wdLdww\_ = w - \frac{dL}{dw}

Обновленные параметры равны исходным параметрам за вычетом производной функции потерь по параметру w.

4.2 Скорость обучения

Обновление параметров вышеуказанным способом на самом деле проблематично, потому что получаемая нами производная часто является относительно большим значением.Если мы вычтем производную напрямую, появится ситуация слева:

在这里插入图片描述

Видно, что параметры в каньоне колеблются, и обновление оптимальных параметров занимает много времени, либо оптимальные параметры вообще не могут быть обновлены.

Поэтому мы можем умножить производную на небольшое число, которое является скоростью обучения, Когда мы используем скорость обучения, наше обновление параметра может быть ближе к правильному изображению.

4.3. Множественные обновления параметров

Мы уже знаем, как обновить параметр, как обновить несколько параметров? В нашем предыдущем уравнении линейной регрессии с двумя параметрами w и b обновление будет иметь значение?

На самом деле особой разницы нет, нам нужно только заменить производную на частную производную, а затем обновить w и b соответственно.

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

Lw=2x[y(wx+b)]\frac{\partial{L}}{\mathrm{\partial{w}}} = -2x[y - (wx + b)]

Частная производная функции потерь по b выглядит следующим образом:

Lb=2[y(wx+b)]\frac{\partial{L}}{\mathrm{\partial{b}}} = -2[y - (wx + b)]

Наша функция обновления параметров выглядит следующим образом:

w_=wηLwb_=bηLbw\_ = w - η\frac{\partial{L}}{\mathrm{\partial{w}}} \\ b\_ = b - η\frac{\partial{L}}{\mathrm{\partial{b}}}

Если вы не помните формулу вывода, вы можете напрямую применить приведенную выше формулу для обновления параметров.

5. Реализация линейной регрессии

Ниже мы используем Java для реализации модели линейной регрессии.

5.1, создание класса и параметры инициализации

Сначала мы создаем класс LinearRegression со следующим кодом:

package com.zack.lr;

import java.util.ArrayList;

public class LinearRegression {
    //权重
    private double weight;
    //偏置值
    private double bias;
    //特征值
    private ArrayList<Double> features;
    //目标值
    private ArrayList<Double> targets;

    /**
     * 构造线性回归模型
     * @param features 训练数据的特征值
     * @param targets 训练数据的目标值
     */
    public LinearRegression(ArrayList<Double> features, ArrayList<Double> targets){
        this.features = features;
        this.targets = targets;
        initParameter();
    }

    /**
     * 初始化权重和偏置值
     */
    public void initParameter(){
        this.weight = Math.random();
        this.bias = Math.random();
    }
}

В классе мы определяем четыре переменных-члена, которые являются двумя параметрами модели, а также собственными значениями и целевыми значениями обучающих данных.

Затем нам нужно передать значения признаков и целевые значения обучающих данных при создании модели и вызвать функцию initParameter для рандомизации параметров исходной модели.

5.2 Градиентный спуск

Далее пишем алгоритм градиентного спуска, код такой:

package com.zack.lr;

import java.util.ArrayList;

public class LinearRegression {

	......
        
    /**
     * 梯度下降更新参数
     * @param learning_rate 学习率
     * @return 损失值
     */
    public double gradientDecent(double learning_rate){
        double w_ = 0;
        double b_ = 0;
        double totalLoss = 0;
        int n = this.features.size();
        double n = this.features.size();
        for (int i = 0; i < this.features.size(); i++) {
            double yPredict = this.features.get(i) * this.weight + this.bias;
            // loss对w的偏导
            w_ += -2 * learning_rate * this.features.get(i) * (this.targets.get(i) - yPredict) / n;
            // loss对b的偏导
            b_ += -2 * learning_rate * (this.targets.get(i) - (yPredict)) / n;

            // 计算loss用于输出
            totalLoss += Math.pow(this.targets.get(i) - yPredict, 2) / n;
        }
        //更新参数
        this.weight -= w_;
        this.bias -= b_;
        return totalLoss;
    }
    
}

В этой функции мы передаем скорость обучения, а затем обновляем параметры.

Во-первых, w_ и b_ представляют здесь изменение двух параметров, а totalLoss представляет значение потери параметра. Давайте сосредоточимся на следующем коде:

double yPredict = this.features.get(i) * this.weight + this.bias;
// loss对w的偏导
w_ += -2 * learning_rate * this.features.get(i) * (this.targets.get(i) - yPredict) / n;
// loss对b的偏导
b_ += -2 * learning_rate * (this.targets.get(i) - (yPredict)) / n;

// 计算loss用于输出
totalLoss += Math.pow(this.targets.get(i) - yPredict, 2) / n;

Видно, что мы просто подставляем формулу для частных производных. Поскольку мы зацикливаемся n (количество обучающих данных) раз, нам нужно разделить на n.

Затем мы можем обновить параметры.

5.3 Результаты прогнозирования

Цель нашей модели обучения — предсказать результат.Этот шаг очень прост, нам нужно только ввести x и найти значение y. код показывает, как показано ниже:

package com.zack.lr;

import java.util.ArrayList;

public class LinearRegression {

	......
     
    /**
     * 预测结果
     * @param features 特征值
     * @return 预测结果
     */
    public ArrayList<Double> predict(ArrayList<Double> features){
        //用于装预测结果
        ArrayList<Double> yPredict = new ArrayList<>();
        for (Double feature : features) {
            //对每个x进行预测
            yPredict.add(feature * this.weight + this.bias);
        }
        return yPredict;
    }

}

Эта часть кода очень проста: мы напрямую используем обученные параметры для вычисления значения y, затем добавляем его в набор предсказаний и возвращаем результат.

5.4 Процедура испытаний

Давайте напишем тестовую программу, чтобы увидеть, насколько хорошо работает эта модель. код показывает, как показано ниже:

package com.zack.lr;

import java.util.ArrayList;

public class LrDemo {

    public static void main(String[] args) {
        ArrayList<Double> features = new ArrayList<>();
        ArrayList<Double> targets = new ArrayList<>();
        //准备特征值
        for (int i = 0; i < 200; i++) {
            features.add((double)i);
        }
        //用y = 3x + 1生成目标值
        for (Double feature : features) {
            //生成目标值,并加上一个随机数
            double target = feature * 3 + 1 + Math.random() * 3;
            targets.add(target);
        }

        //创建线性回归模型
        LinearRegression linearRegression = new LinearRegression(features, targets);

        for (long i = 1; i <= 300; i++){
            double loss = linearRegression.gradientDecent(1e-6);
            if(i % 100 == 0){
                System.out.println("第" + i + "次更新后");
                System.out.println("weight = " + linearRegression.getWeight());
                System.out.println("bias = " + linearRegression.getBias());
                System.out.println("loss = " + loss);
            }
        }

        //准备数据用于测试
        ArrayList<Double> testList = new ArrayList<>();
        testList.add(100.0);
        testList.add(27.0);
        ArrayList<Double> testPredict = linearRegression.predict(testList);
        System.out.println("真实结果");
        for (Double testX : testList) {
            System.out.println(testX * 3 + 1);
        }
        System.out.println("预测结果");
        for (Double predict : testPredict) {
            System.out.println(predict);
        }

    }

}

Мы генерируем некоторые данные для обучения. Затем установите относительно небольшую скорость обучения для градиентного спуска, что также является нашей обычной практикой.

Мы получили хороший результат после обновления параметров 300 раз Вот результат:

第100次更新后
weight = 2.824277848777937
bias = 0.5474555396904982
loss = 511.60209782851484
第200次更新后
weight = 3.0023165757117525
bias = 0.5488865404002248
loss = 3.9653518084868575
第300次更新后
weight = 3.0144922328217816
bias = 0.5490704219725514
loss = 1.5908606096719522
真实结果
301.0
82.0
预测结果
301.9982937041507
81.94036070816065

Видно, что предсказанные результаты очень близки к реальным результатам. На данный момент мы завершили простой алгоритм линейной регрессии.

В этом случае рассматривается только одномерная линейная регрессия с одной переменной, и заинтересованные читатели могут рассмотреть возможность расширения до множественной линейной регрессии. Спасибо за чтение~