ML с нуля: модель линейной регрессии с NumPy

Ваше полное руководство по линейной регрессии

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

Основная цель этого проекта - объяснить, как работает линейная регрессия и как вы можете с нуля кодировать модель линейной регрессии, используя замечательный модуль NumPy.

Конечно, вы можете создать модель линейной регрессии, используя scikit-learn, всего лишь с 3–4 строками кода, но на самом деле кодирование собственной модели с нуля намного круче, чем полагаться на библиотеку, которая делает все за вас, пока вы сидите. и смотри.

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

В этом проекте наша модель будет использоваться для прогнозирования выбросов CO₂ транспортного средства на основе его характеристик, таких как объем двигателя, расход топлива и т. Д.

Приступим к работе над проектом.

Делаем необходимый импорт

Сначала мы импортируем необходимые модули PyData.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

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

df = pd.read_csv("FuelConsumptionCo2.csv")
print(df.head())

Вот ссылка на набор данных. Я также поделюсь ссылкой на репозиторий Github, содержащий записную книжку Jupyter и набор данных в конце этого проекта.



Вот как это выглядит в моем блокноте Jupyter:

Следующие столбцы присутствуют в нашем наборе данных.

  • МОДЕЛЬНЫЙ ГОД например 2014 г.
  • СДЕЛАТЬ например Acura
  • МОДЕЛЬ например ILX
  • КЛАСС АВТОМОБИЛЯ например Внедорожник
  • РАЗМЕР ДВИГАТЕЛЯ например 4,7
  • ЦИЛИНДРЫ, например, 6
  • ПЕРЕДАЧА например A6
  • РАСХОД ТОПЛИВА в ГОРОДЕ (л / 100 км) например. 9.9
  • РАСХОД ТОПЛИВА на HWY (л / 100 км) например 8.9
  • РАСХОД ТОПЛИВА В ГОРНИЧЕСТВЕ (л / 100 км) например. 9.2
  • ВЫБРОСЫ СО2 (г / км) например 182 → низкий → 0

Обработка данных и выбор функций

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

Для нашего проекта первым шагом предварительной обработки будет проверка того, нужно ли нам приводить тип данных для какой-либо функции / целевой переменной.

print(df.dtypes)

Получаем такой вывод:

Как видим, приводить тип какого-либо столбца не нужно.

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

print(df.describe())

Как мы видим, все потенциальные функции имеют одинаковый порядок масштабов, поэтому нам не нужно стандартизировать какую-либо функцию.

Для этого проекта мы выберем следующие функции: РАЗМЕР ДВИГАТЕЛЯ, ЦИЛИНДРЫ и FUELCONSUMPTION_COMB, а целевая переменная - CO2EMISSIONS.

df = df[['ENGINESIZE','CYLINDERS','FUELCONSUMPTION_COMB','CO2EMISSIONS']]
print(df.head())

Наш следующий шаг - проверка количества значений NaN (null) в кадре данных.

for i in df.columns:
    print(df[i].isnull().value_counts()) 

Как мы видим, в нашем фрейме данных нет нулевых значений. Таким образом, данные идеально подходят для обучения модели.

Визуализация и анализ данных

Сначала мы посмотрим на корреляцию функций и целевых переменных.

print(df.corr())

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

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

fig, a =  plt.subplots(1,3, figsize = (18, 5))
a[0].scatter(df['ENGINESIZE'], df['CO2EMISSIONS'], color = 'c')
a[0].set_title('Engine Size vs CO2 Emissions')
a[0].set_xlabel('Engine Size (L)')
a[1].scatter(df['CYLINDERS'], df['CO2EMISSIONS'], color = 'm')
a[1].set_title('No. of Cylinders vs CO2 Emissions')
a[1].set_xlabel('No. of Cylinders')
a[2].scatter(df['FUELCONSUMPTION_COMB'], df['CO2EMISSIONS'], color = 'b')
a[2].set_title('Fuel Consumption vs CO2 Emissions')
a[2].set_xlabel('Fuel Consumption (L/100km)')
fig.text(0.08, 0.5, 'CO2 Emissions', va='center', rotation='vertical')

plt.show()

Как мы видим, функции показывают значительную линейную связь с целью. Следовательно, мы можем использовать их для обучения модели.

Модель линейной регрессии с нуля

Линейная регрессия использует следующую математическую формулу для прогнозирования зависимой переменной с использованием независимой переменной.

y = wx + b

Здесь,

  • y - зависимые переменные
  • x - зависимые переменные
  • w - Вес (ы), связанный с независимой (ыми) переменной (ами)
  • b - смещения для данного линейного уравнения.

Ниже приведен процесс разработки модели линейной регрессии.

  1. Разделение набора данных на обучающий и тестовый наборы. Однако для простоты мы пропустим этот шаг в нашей пользовательской модели.
  2. Присвоение модели случайных весов и смещений с последующим вычислением зависимой переменной ŷ на основе случайных весов и смещений.
  3. Использование функции потерь для расчета общей потери информации, то есть общей неточности в нашей модели. В наших примерах мы будем использовать среднеквадратичную ошибку (MSE) функцию потерь.
  4. Наш следующий шаг - уменьшить общую MSE нашей модели. Для этого мы будем использовать функцию стохастического градиентного спуска (SGD), которая является одним из самых популярных алгоритмов оптимизатора, используемых в регрессионных моделях. Мы обсудим функцию SGD. подробно при кодировании функции оптимизатора.
  5. Мы обновим веса и смещения модели на основе нашего алгоритма оптимизатора, а затем повторно обучим модель. Это повторяющийся процесс, который будет повторяться до тех пор, пока мы не достигнем оптимальной модели с низкой потерей информации.

Во-первых, давайте преобразуем функции в массив NumPy, features.

features = df[['ENGINESIZE','CYLINDERS','FUELCONSUMPTION_COMB']].to_numpy() #Converts the dataframe to numpy array
print(features)

Теперь давайте приведем целевой столбец к массиву NumPy, target.

target = df[‘CO2EMISSIONS’].to_numpy() #Converts the dataframe to numpy array
print(target)

Поскольку у нас есть 3 зависимые переменные, у нас будет 3 веса. Давайте сгенерируем веса массива из 3 маленьких случайных весов.

weights = np.random.rand(3) #Generates a numpy array with two small random floats
print(weights)

Поскольку у нас есть одна целевая переменная, у нас будет только одно смещение, b. Мы также создадим массив смещения, равный длине массива функции, имеющий смещение b для каждого элемента.

b = np.random.rand(1) #Generates a numpy array with a small random float
bias = np.array([b[0] for i in range(len(features))])
print(bias)

Теперь мы определим нашу модельную функцию, которая использует веса, смещения и зависимые переменные для вычисления ŷ.

def linearRegr(features, weights, bias):
    """Calculates the y_hat predicted values using the given parameters of weights, dependent variables, and biases.
    Args:
        -dependant_var: Matrix of dependant variable values
        -weights: Matrix/array of weights associated with each dependant variable
        -biases: Biases for the model
    Returns:
        -Array/matrix of predicted values
    """
    y_hat = weights.dot(features.transpose()) + np.array([bias[0] for i in range(len(features))]) # Takes the value stored in the bias array and makes an array of length of feature matrix for addition
    return y_hat

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

y_hat = linearRegr(features, weights, b)
print(y_hat)

Теперь мы определим функцию MSE для расчета общих потерь нашей модели.

def meanSqrError(y, y_hat):
    """Calculates the total mean squared error.
    
    Args- 
        y: Array of actual target values
        y_hat: Array of predicted target values
        
    Returns-
        total mean squared error
    """
    MSE = np.sum((y - y_hat) ** 2) / len(y)
    return MSE

Давайте теперь посчитаем потерю информации на основе значений y_hat, которые мы получили ранее.

print('Total error- {}'.format(meanSqrError(target, y_hat)))

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

Оптимизация модели

Теперь наступает самый важный шаг в линейной регрессии. Формулировка функции SGD. Это немного продвинутая тема по сравнению со всеми основными функциями, которые мы рассмотрели до этого момента. Это требует некоторых знаний дифференциального исчисления; частичная дифференциация, чтобы быть конкретным. Я попытался объяснить это на изображении ниже, однако, если вы не поняли, я настоятельно рекомендую вам ознакомиться с математической частью машинного обучения (исчисление, статистика и вероятность, линейная алгебра), прежде чем продолжить .

Источник изображения - Adarsh ​​Menon-Medium.com

После расчета градиентов мы обновим параметры следующим образом.

  • m = m - α Dm
  • c = c - α Dc

Здесь,

  • E - общая среднеквадратичная ошибка
  • m - веса, связанные с функциями
  • c - предвзятость модели
  • y - массив фактических целевых значений
  • ŷ - прогнозируемые целевые значения
  • D m - частная производная от E по отношению к вес м
  • D c - частная производная от E по отношению к предвзятость c
  • α - скорость обучения, т. е. размер шага, который выполняет функция оптимизатора.

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

def gradient(target, features, weights, bias):
    """Returns the gradient(slopes) for weights and biases
    """  
    m = len(features)
    target_pred = linearRegr(features, weights, bias)
    loss = target - target_pred # y-y_hat
    # Gradient calculation for model bias
    grad_bias = np.array([-2/m * np.sum(loss)])
    
    grad_weights = np.ones(3)
    # Gradient calculation for first feature
    feature_0 = np.array([feature[0] for feature in features])
    grad_weights[0] = -2/m * np.sum(loss * feature_0)
    # Gradient calculation for second feature
    feature_1 = np.array([feature[1] for feature in features])
    grad_weights[1] = -2/m * np.sum(loss * feature_1)
    # Gradient calculation for third feature
    feature_2 = np.array([feature[1] for feature in features])
    grad_weights[2] = -2/m * np.sum(loss * feature_2)
    
    return grad_bias, grad_weights

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

def stochGradDesMODIFIED(learning_rate, epochs, target, features, weights, bias):
    """Performs stochastic gradient descent optimization on the model.
    
    Args-
        learning_rate- Size of the step the function will take during optimization
        epochs- No. of iterations the function will run for on the model
        target- Actual emission values
        features- Matrix of dependent variables
        weights- Weights associated with each feature
        bias- Model bias
    
    Returns-
        return_dict = {'weights': weights, 'bias': bias[0], 'MSE': total_MSE_new, 'MSE_list': MSE_list}
        
    """
MSE_list = []
    for i in range(epochs):
        grad_bias, grad_weights = gradient(target, features, weights, bias)
        weights -= grad_weights * learning_rate
        bias -= grad_bias * learning_rate
        new_pred = linearRegr(features, weights, bias)
        total_MSE_new = meanSqrError(target, new_pred)
        MSE_list.append(total_MSE_new)
    return_dict = {'weights': weights, 'bias': bias[0], 'MSE': total_MSE_new, 'MSE_list': MSE_list}
    return return_dict

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

model_val = stochGradDesMODIFIED(0.001, 2000, target, features, weights, bias)
print("Weights- {}\nBias- {}\nMSE- {}".format(model_val['weights'], model_val['bias'], model_val['MSE']))

Первоначальная MSE составляла около 65 000, в то время как текущая MSE составляет около 680. По результатам мы видим, что наша модель значительно улучшилась.

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

def LinearRegressionModel(model_val, feature_list):
    """Predicts the CO2 emission values of the vehicle
    
    Args-
        model_val- This is the dictionary returned by the stockGradDesMODIFIED function. Contains model weights and biases
        feature_list- An array of the dependent variables
    Returns-
        co2_emission- Emission predictions for the given set of features
    """
    co2_emission = np.sum(model_val['weights'] * feature_list) + model_val['bias']
    return co2_emission

Тестирование и оценка

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

feature_list = [2.0, 4, 8.5]

Фактическое целевое значение для данных - 196. Посмотрим, как работает наша модель.

target_price = 196
feature_list = [2.0, 4, 8.5]
predicted_price = LinearRegressionModel(model_val, feature_list)
print(predicted_price)

Исходное целевое значение для данной модели составляло 196. Как мы видим, наша модель неплохо справилась с прогнозированием, учитывая, что это реализация модели с нуля. Тем не менее, вы можете еще больше улучшить модель, настроив несколько вещей или, возможно, запустив больше эпох оптимизации. Однако слишком большая оптимизация может привести к переобучению модели, что одинаково плохо для модели, поскольку переобучение делает модель практически непригодной для использования с данными реального мира.

Теперь, чтобы проверить точность нашей модели, мы рассчитаем ее оценку r-квадрат. Ниже приводится формула для оценки r2.

def r2_score(target, prediction):
    """Calculates the r2 score of the model
    
    Args-
        target- Actual values of the target variable
        prediction- Predicted values, calculated using the model
        
    Returns- 
        r2- r-squared score of the model
    """
    r2 = 1- np.sum((target-prediction)**2)/np.sum((target-target.mean())**2)
    return r2

Как мы видим, наша модель объясняет около 83% изменчивости данных отклика относительно среднего значения, что довольно хорошо. Однако в модели машинного обучения всегда есть возможности для улучшения!

На этом мы подошли к концу нашего проекта.

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

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

Нажмите это, следите за новостями и следите за новостями о машинном обучении!

Ссылка на репозиторий GitHub для набора данных и записной книжки Jupyter-