Написание алгоритма линейной регрессии исключительно в TensorFlow 2.0

Линейная регрессия - один из самых основных и, возможно, один из наиболее часто используемых алгоритмов машинного обучения, который новички и эксперты должны знать наизусть. В этой статье я расскажу, как реализовать линейную регрессию, используя только Tensorflow. Обсуждение будет разделено на две части: первая часть объясняет концепцию линейной регрессии, вторая часть представляет собой пошаговое руководство по реализации линейной регрессии в Tensorflow.

I. Концепция

Линейная регрессия пытается смоделировать отношения зависимых и независимых переменных путем подбора линейного уравнения. Предположим, у нас есть данные о результатах тестов и продолжительности учебных часов 100 студентов.

Визуально изучая диаграмму рассеяния, мы можем легко нарисовать линию с помощью уравнения y=mx+b, где m и b - наклон и точка пересечения по оси Y соответственно. На рисунке это видно как примерно 40 и 0 для m и b соответственно.

Проведем линию где m=40 и b=0.

Линия y=40x выглядит хорошо! Затем мы можем подсчитать, что балл студента равен всего 40, умноженному на количество часов, которые он изучал.

Линейная регрессия работает точно так же, за исключением того, что она не может визуально проверить наклон и точку пересечения по оси Y на графике рассеяния. Вместо этого он сначала угадывает наклон и точку пересечения по оси Y, а затем измеряет, насколько хорошо это предположение. Если он недостаточно хорош, он корректирует наклон и точку пересечения по оси Y до тех пор, пока линия не будет хорошо соответствовать данным.

Линейная регрессия - это трехэтапный алгоритм:

  1. Инициализируйте параметры линейного уравнения (первое предположение об уклоне и точке пересечения по оси Y).
  2. Измерьте степень соответствия на основе некоторой функции.
  3. Отрегулируйте параметры до тех пор, пока результат шага 2 не будет выглядеть хорошо.

1. Линейное уравнение и инициализация

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

Первым шагом в модели линейной регрессии является инициализация линейного уравнения. Да, мы будем использовать y=mx+b, но мы должны обобщить наш подход. Причина в том, что мы можем столкнуться с данными с несколькими независимыми переменными. Думайте об этом как о добавлении еще одной переменной в наши данные о результатах теста, например, о количестве кофе, потребляемом во время учебы. Имея это coffee измерение, линейное уравнение будет выглядеть так: y=m1x1+m2x2+b, где m1 и m2 - наклоны для часов обучения и измерений кофе, соответственно, а x1 и x2 - часы обучения и переменные кофе.

Мы будем использовать точечное произведение для представления произведения матриц m и x, вместо того, чтобы писать более длинное уравнение для каждой новой переменной. Обратите внимание, что также можно использовать термин тензор, поскольку он является обобщением матрицы. Для обозначения матриц используются жирные буквы, поэтому линейное уравнение должно быть записано как y = mx + b.

Есть много способов инициализировать параметры уравнения, наиболее распространенными из которых являются использование случайных значений, нулей или единиц. Вы можете использовать любой тип инициализации, от этого выбора будет зависеть скорость завершения вашего алгоритма обучения. На следующей итерации алгоритма эти параметры будут обновлены на основе некоторой функции, обсуждаемой на шаге 2.

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

Теперь предположим, что начальные значения, которые вы установили для m и b, равны единице, так что ваше уравнение y=1x+1. Первоначальные прогнозы будут выглядеть как оранжевые точки на рисунке ниже. Очевидно, что это очень плохой прогноз, нам нужно число, чтобы количественно оценить, насколько плохи или хороши эти прогнозы.

Есть много способов измерить точность нашего прогноза, мы будем использовать один из них, называемый среднеквадратичной ошибкой (MSE). В этом контексте ошибка означает разницу, поэтому MSE буквально означает взятие квадрата разница между фактическими и прогнозируемыми значениями, затем возьмите среднее значение. Математически это записано как

Такие функции, как MSE, называются функцией потерь или целевой функцией. Это функции, которые алгоритм хочет минимизировать. Если наша модель линейной регрессии идеально предсказывает оценки викторины, ее MSE будет равна 0. Таким образом, на каждой итерации алгоритма она должна обновлять параметры так, чтобы MSE приближалась к 0 без переобучения. Переоснащение - это целая тема, но по сути это означает, что мы не хотим, чтобы наш алгоритм обучения так хорошо работал с обучающими данными и терпел неудачу на тестовом наборе.

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

Конечно, мы можем продолжать угадывать параметры, пока не подойдем достаточно близко к нулю MSE, но это потребует времени и усилий - градиентный спуск сделает это за нас. Если вы не знакомы с этим термином, есть масса статей и видео, объясняющих его концепцию.

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

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

На каждой новой итерации обновленные параметры будут иметь вид p_new = p_old- (l * dL / dp), где p - параметр, который может быть наклоном m или точкой пересечения оси y b. Новые переменные l и dL / dp, - это скорость обучения и частная производная функции потерь по параметру.

При достаточном количестве итераций наклон и точка пересечения по оси Y приблизятся к 40 и 0, значениям, которые мы считаем «достаточно близкими», чтобы соответствовать нашим данным. Как вы, возможно, заметили, если вам случится инициализировать параметры, близкие к 40 и 0, скажем, 35 ​​и 0,5, то алгоритм потребует меньше итераций.

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

Критерии остановки

Вот некоторые из возможных способов завершить работу алгоритма:

  1. Завершите алгоритм, как только будет выполнено заданное количество итераций.
  2. Завершите алгоритм, как только указанная MSE будет удовлетворена.
  3. Завершите алгоритм, если MSE не улучшится на следующей итерации. Вы можете указать точность, например 0,001, если разница между двумя последовательными MSE меньше этой точности, остановите алгоритм.

II Реализация TensorFlow2

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

Импортировать библиотеки

Это единственные библиотеки, которые нам понадобятся для этой демонстрации. TensorFlow для построения алгоритма, pyplot для визуализации и boston_housing в качестве набора данных нашей игрушки.

import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import boston_housing

1. Инициализировать линейное уравнение.

Начнем с создания класса aSimpleLinearRegression с параметрами инициализации.

Я указал три варианта инициализации: ones, zeros и random (по умолчанию). tf.random.uniform будет генерировать тензор со случайными значениями из равномерного распределения в диапазоне minval и maxval с формой shape. Я определил m как переменную без определенной формы, поэтому она может быть достаточно гибкой, чтобы принимать любое количество независимых переменных, это можно сделать, установив shape=tf.TensorShape(None).

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

Далее следует реализовать нашу функцию потерь, MSE. Напомним, математически MSE записывается как:

Вот моя реализация функции в TensorFlow:

Это довольно легко написать в Tensorflow. Сначала вы берете разницу значений true и predicted, используйте tf.square, чтобы возвести разницы в квадрат, а затем получать среднее значение квадратов разностей, используя tf.reduce_mean.

Эта функция принимает значения true и predicted, первое берется из самих данных, а второе должно быть вычислено.

Метод predict осуществляется путем упрощения линейного уравнения. Сначала мы берем скалярное произведение m (тензор наклона) и x (тензор признаков) и добавляем точку пересечения по оси y b. Мне пришлось указать ось, по которой будет вычисляться уменьшение reduction_sum до 1, иначе тензор уменьшится до единственной суммы.

3. Обновление параметров

Нам нужен градиентный спуск, чтобы обновлять наши параметры на каждой итерации. Нет необходимости создавать этот алгоритм с нуля, поскольку в Tensorflow есть встроенная функция tf.GradientTape. По умолчанию для GradientTape persistent установлено значение False, что означает, что к методу gradient() в этом объекте может быть выполнено не более одного вызова. Поскольку мы используем это для вычисления двух градиентов (один для m, а другой для b) для каждой итерации, мы должны установить это значение True. Затем мы указываем функцию потерь, для которой будут вычисляться градиенты, в данном случае mse с параметрами y и self.predict(X), которые представляют значения true и predicted соответственно.

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

p_new = p_old - (l*dL/dp)

Обучение - это гиперпараметр, и его следует указывать при обучении линейного регрессора. Градиент dL/dp вычисляется с использованием метода gradient(), который принимает функцию потерь и параметр. Эта операция просто решает частную производную функции потерь по параметру. Мы должны вычислять два градиента на каждой итерации, по одному для m и b.

Чтобы обновить параметр новым значением, которое представляет собой просто старое значение минус l*dL/dp, мы просто используем assign_sub() метод tf.Variable.

Метод обучения

Давайте соберем все вместе train методом.

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

self.m.assign([self.var]*X.shape[-1]) инициализирует m начальные значения, которые мы установили во время инициализации, с формой, соответствующей количеству независимых переменных, которые имеют данные.

Критерием остановки нашего алгоритма является количество итераций, определяемое epoch. Для каждой итерации он будет вызывать метод update.

Вот весь код линейного регрессора.

Тестируем наш алгоритм

Пришло время протестировать наш алгоритм с использованием набора данных Boston Housing.

Загрузите набор данных, используя keras.datasets:

(x_train, y_train), (x_test, y_test) = boston_housing.load_data()

Стандартизируйте данные:

mean_label = y_train.mean(axis=0)
std_label = y_train.std(axis=0)
mean_feat = x_train.mean(axis=0)
std_feat = x_train.std(axis=0)
x_train = (x_train-mean_feat)/std_feat
y_train = (y_train-mean_label)/std_label

Создайте и обучите объект SimpleLinearRegression.

linear_model = SimpleLinearRegression('zeros')
linear_model.train(x_train_norm, y_train_norm, learning_rate=0.1, epochs=50)

Вот потери последних пяти итераций:

Давайте предскажем, используя набор тестов:

# standardize
x_test = (x_test-mean_feat)/std_feat
# reverse standardization
pred = linear_model.predict(x_test)
pred *= std_label
pred += mean_label

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

Вывод

Несмотря на свою простоту, линейная регрессия является одним из наиболее часто используемых алгоритмов машинного обучения в отрасли, и некоторые компании проверяют, насколько хорошо вы его понимаете. Хотя есть гораздо более простые способы реализации этого алгоритма, такие как использование scikit-learn и даже LinearRegressor от TensorFlow, мы реализовали весь алгоритм с нуля с намерением испытать функции TensorFlow. Когда вы входите в нейронные сети, есть библиотеки высокого уровня, такие как Keras, которые упрощают процесс построения нейронных сетей с использованием TensorFlow в качестве бэкэнда. Но в конечном итоге, особенно если вы исследователь, вы захотите настроить свои модели, как хотите, и именно здесь низкоуровневые функции TensorFlow очень полезны.