Вы подписаны на Машинное обучение доступно
Отлично! завершите оплату для полного доступа к Машинное обучение доступно
Снова приветствуем Вас! Вы успешно авторизовались
Успех! Аккаунт активирован, и Вы имеете полный доступ к контенту.
Линейная регрессия (Linear Regression)

Линейная регрессия (Linear Regression)

in

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

Что такое линейная регрессия?

Линейная регрессия в Машинном обучении – это подход к моделированию отношений между Целевой переменной (Target Variable) и одной или несколькими "предсказывающими" переменными (Predictor Variable). Проще говоря, это «линия наилучшего соответствия», которая помогает спрогнозировать положение других точек в будущем. Ниже приведен пример линии, которая наилучшим образом соответствует точкам данных. Создавая прямую наилучшего соответствия, Вы сможете также выявить выбросы (Outlier). Предположим, что этот график представляет цену бриллиантов в зависимости от веса. Если мы посмотрим на голубую точку, мы увидим, что этот конкретный бриллиант переоценен.

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

Как работает простая линейная регрессия

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

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

Мы строим еще одну линию и повторяем вышеописанные шаги. Как только возведенные в квадрат расстояния суммированы, мы сравниваем это значение с предыдущей суммой и выбираем наименьшее. Это сравнение еще называют минимизацией квадратичного расстояния. Этот регламент оставляет немало вопросов: насколько удалять от предыдущей следующую линию? Сколько линий стоит перебрать, прежде чем выбрать наилучшую? В качестве примера выше показаны две «линии наилучшего соответствия»: красная и зеленая. Обратите внимание: суммарная длина тонких зеленых линий гораздо больше таких же красных, что говорит о большей Ошибке (Error) в предсказании положения точки в будущем. Если Вы хотите использовать простую линейную регрессию, используйте класс LinearRegression библиотеки Scikit-learn. Импортируем его и NumPy:

from sklearn.linear_model import LinearRegression
import numpy as np

Полиномиальная линейная регрессия

Простая линейная регрессия полезна, когда вы хотите найти уравнение, которое представляет переменную-предиктор (x) и целевую переменную (y). Но что если у Вас много предикторов? Цена автомобиля, определенно, зависит от множества факторов, таких как мощность, размер автомобиля и стоимость бренда. Тогда на сцену выходит полиномиальная (множественная) регрессия. Она используется для объяснения взаимосвязи между зависимой переменной и более чем одной независимой переменной. Вы наверняка уже встречали красочные графики с плоскостями, теперь давайте разберемся, как это работает:

import matplotlib
from matplotlib import cm # Для окрашивания плоскости
import matplotlib.pyplot as plt

import numpy as np
import scipy, scipy.optimize
from mpl_toolkits.mplot3d import Axes3D # Для трехмерного графика

Итак, мы импортировали все необходимое для работы с трехмерными графиками, теперь определим их внешний вид:

# Параметры графика
graphWidth = 8
graphHeight = 6
numberOfContourLines = 16

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

# Сгенерированные игрушечные данные
xData = np.array([971.0, 691.0, 841.0, 970.0, 755.0, 684.0, 938.0, 956.0, 658.0, 838.0, 879.0, 752.0, 690.0, 970.0, 964.0, 966.0, 901.0, 671.0, 660.0, 666.0, 765.0, 831.0, 899.0, 668.0, 969.0, 967.0, 651.0, 929.0, 805.0, 812.0, 936.0, 650.0, 964.0, 719.0, 654.0, 646.0, 932.0, 827.0, 917.0, 945.0, 724.0, 956.0, 966.0, 969.0, 968.0, 967.0, 718.0, 966.0, 812.0, 649.0, 645.0, 675.0, 959.0, 966.0, 962.0, 967.0, 956.0, 757.0, 964.0, 817.0, 666.0, 812.0, 902.0, 969.0, 661.0, 962.0, 752.0, 802.0, 670.0, 663.0, 966.0, 967.0, 773.0, 663.0, 818.0, 917.0, 952.0, 834.0, 516.0, 547.0, 846.0, 458.0, 490.0, 835.0, 579.0, 472.0, 557.0, 652.0, 471.0, 455.0, 837.0, 842.0, 832.0, 675.0, 529.0, 509.0, 533.0, 493.0, 572.0, 695.0, 464.0, 846.0, 845.0, 505.0, 833.0, 544.0, 550.0, 594.0, 486.0, 847.0, 471.0, 533.0, 497.0, 838.0, 832.0, 830.0, 847.0, 844.0, 837.0, 831.0, 671.0, 844.0, 824.0, 841.0, 532.0, 576.0, 852.0, 471.0, 496.0, 839.0, 587.0, 478.0, 565.0, 657.0, 481.0, 463.0, 841.0, 842.0, 832.0, 682.0, 532.0, 509.0, 539.0, 497.0, 574.0, 704.0, 472.0, 850.0, 849.0, 512.0, 834.0, 540.0, 542.0, 603.0, 481.0, 847.0, 472.0, 529.0, 496.0, 836.0, 570.0, 588.0, 837.0, 474.0, 781.0, 842.0, 855.0, 846.0, 845.0, 518.0, 854.0, 585.0, 531.0, 539.0, 536.0])

yData = np.array([956.0, 825.0, 963.0, 731.0, 939.0, 879.0, 523.0, 962.0, 880.0, 962.0, 536.0, 942.0, 902.0, 954.0, 662.0, 959.0, 550.0, 798.0, 836.0, 778.0, 945.0, 959.0, 532.0, 880.0, 783.0, 733.0, 833.0, 526.0, 955.0, 956.0, 959.0, 863.0, 714.0, 924.0, 778.0, 849.0, 523.0, 957.0, 960.0, 559.0, 925.0, 959.0, 955.0, 760.0, 953.0, 952.0, 921.0, 713.0, 955.0, 838.0, 819.0, 781.0, 956.0, 950.0, 714.0, 937.0, 955.0, 947.0, 739.0, 957.0, 864.0, 957.0, 531.0, 896.0, 796.0, 954.0, 945.0, 955.0, 762.0, 878.0, 951.0, 953.0, 951.0, 877.0, 959.0, 958.0, 609.0, 791.0, 496.0, 786.0, 597.0, 615.0, 574.0, 432.0, 805.0, 599.0, 793.0, 344.0, 617.0, 615.0, 792.0, 456.0, 807.0, 328.0, 504.0, 543.0, 494.0, 644.0, 803.0, 319.0, 611.0, 690.0, 471.0, 543.0, 392.0, 774.0, 783.0, 812.0, 597.0, 478.0, 627.0, 508.0, 576.0, 799.0, 803.0, 421.0, 534.0, 645.0, 791.0, 422.0, 321.0, 790.0, 384.0, 803.0, 520.0, 797.0, 563.0, 629.0, 581.0, 441.0, 809.0, 602.0, 797.0, 354.0, 625.0, 621.0, 796.0, 463.0, 806.0, 333.0, 511.0, 543.0, 501.0, 648.0, 804.0, 323.0, 620.0, 689.0, 483.0, 554.0, 396.0, 767.0, 777.0, 806.0, 596.0, 479.0, 625.0, 506.0, 574.0, 411.0, 801.0, 811.0, 426.0, 626.0, 811.0, 809.0, 515.0, 805.0, 804.0, 651.0, 564.0, 795.0, 589.0, 576.0, 495.0])

zData = np.array([-2.0, -105.0, -26.0, 44.0, -69.0, -65.0, 60.0, -22.0, -77.0, -24.0, 58.0, -36.0, -66.0, -3.0, 34.0, -8.0, 57.0, -82.0, -98.0, -90.0, -55.0, -23.0, 60.0, -61.0, 29.0, 36.0, -72.0, 61.0, -44.0, -47.0, -27.0, -73.0, 40.0, -37.0, -107.0, -89.0, 68.0, -32.0, -38.0, 63.0, -54.0, -33.0, 16.0, 34.0, 3.0, 15.0, -61.0, 54.0, -39.0, -72.0, -77.0, -97.0, -16.0, 0.0, 45.0, 11.0, -9.0, -57.0, 47.0, -37.0, -82.0, -15.0, 63.0, 21.0, -73.0, 4.0, -55.0, -23.0, -87.0, -74.0, 24.0, -1.0, -46.0, -59.0, -47.0, -18.0, 41.0, 18.0, -104.0, -25.0, 18.0, -55.0, -64.0, 55.0, -35.0, -56.0, -25.0, 63.0, -46.0, -70.0, 16.0, 59.0, -17.0, 78.0, -86.0, -102.0, -113.0, -41.0, -53.0, 68.0, -56.0, 28.0, 24.0, -88.0, 42.0, -59.0, -35.0, -38.0, -79.0, 48.0, -65.0, -113.0, -73.0, 4.0, -8.0, 63.0, 28.0, 23.0, 25.0, 48.0, 74.0, 7.0, 45.0, 11.0, -92.0, -38.0, 29.0, -69.0, -87.0, 56.0, -31.0, -60.0, -29.0, 59.0, -43.0, -53.0, -4.0, 50.0, -5.0, 74.0, -89.0, -84.0, -116.0, -53.0, -42.0, 46.0, -69.0, 32.0, 36.0, -83.0, 57.0, -64.0, -36.0, -18.0, -94.0, 52.0, -72.0, -87.0, -77.0, 44.0, -57.0, -33.0, 53.0, -76.0, -33.0, -12.0, 15.0, 9.0, -6.0, -70.0, 43.0, -58.0, -100.0, -78.0, -97.0])

# Инициализируем специальный список data, чтобы передавать его целиком или по частям
data = [xData, yData, zData]

Теперь определим функцию-построитель:

def SurfacePlot(func, data, fittedParameters):
    # Зададим размер графика
    f = plt.figure(figsize = (graphWidth, graphHeight), dpi = 100)

    # Отобразим сетку
    matplotlib.pyplot.grid(True)

    # Зададим трехмерное пространство
    axes = Axes3D(f)

    # Снова раздробим данные натрое для комбинирования моделей x- и yModel 
    x_data = data[0]
    y_data = data[1]
    z_data = data[2]

    # min() и max() найдут для каждого ряда наименьшее 
    # и наибольшее значения, а linspace() возвратит список 
    # чисел с равным интервалом (20).
    xModel = np.linspace(min(x_data), max(x_data), 20)
    yModel = np.linspace(min(y_data), max(y_data), 20)

    # meshgrid() вернет матрицу координат.
    X, Y = np.meshgrid(xModel, yModel)

    Z = func(np.array([X, Y]), *fittedParameters)

    axes.plot_surface(X, Y, Z, 
                      # Аргументы rstride и cstride устанавливают 
                      # шаг, используемый для выборки входных данных 
                      # при создания графика.
                      rstride = 1, cstride = 1, 
                      cmap = cm.coolwarm, # Градиентное окрашивание 
                      linewidth = 1, antialiased = True) # Толщина линий

	# Вспомогательная точечная диаграмма
	axes.scatter(x_data, y_data, z_data)
    
    # Название графика и осей координат
    axes.set_title('График плоскости') 
    axes.set_xlabel('X')
    axes.set_ylabel('Y')
    axes.set_zlabel('Z')

    plt.show()

И наконец построим плоскостный график полиномиальной линейной регрессии:

fittedParameters, pcov = scipy.optimize.curve_fit(func, [xData, yData], zData, p0 = initialParameters)
SurfacePlot(func, data, fittedParameters)

Мы получили такую прелестную графику:

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

Ноутбук, не требующий дополнительной настройки на момент написания статьи, можно скачать здесь.