За последние несколько месяцев я прошел довольно много курсов по машинному обучению (самых популярных) — «Машинное обучение» Эндрю Нг и первые три курса «Специализация глубокого обучения». Пока я думал, стоит ли переходить на сверточные нейронные сети или нет, я подумал, что было бы лучше дать себе шанс реализовать то, что я узнал до сих пор. Это помогло бы мне укрепить мои основы, и это определенно помогло бы мне в моем карьерном росте.

Теперь я успешно реализовал две модели машинного обучения с нуля — и, как следует из названия, избегая встроенных инструментов, таких как sklearn, tensorflow и т. д. Хотя я не мог отказаться от использования pandas и numpy, я все же убедился, что реализовал функции стоимости и шаги градиентного спуска с нуля. Я задокументировал уроки, полученные в процессе.

Я начну с линейной регрессии в этом блоге.

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

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

Затем я открыл блокнот с этим набором данных в своей учетной записи Kaggle и начал программировать! (Вы также можете загрузить набор данных и кодировать в своих локальных системах с помощью блокнотов Jupyter).

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

После того, как я визуализировал данные, я начал кодировать формулы линейной регрессии на Python. Из моего курса ML я узнал следующее:

Чтобы вычислить стоимость между прогнозируемыми значениями и фактическими значениями (функция стоимости линейной регрессии):

где
m —количество обучающих примеров
J(𝜃₀, 𝜃₁) — функция, вычисляющая потери по всей обучающей выборке
𝜃₀ — Постоянный член в нашем линейном уравнении
𝜃₁ — Коэффициент «x» в линейном уравнении
h₀(x )Функция, которая предсказывает значение y при заданном x
y — Фактический результат для заданного x

Здесь 𝜃₀ и 𝜃₁ — это параметры, которые мы должны изучить в нашей модели линейной регрессии.

h₀(x) определяется следующим образом:

Постоянный член 𝜃₀ также называется смещением, а коэффициент при x 𝜃₁ называется весом.

Вот код, который я сделал для того же самого:

#Computes h0(x)
#Inputs: x - input data, parameters - θ₀ and θ#Output: h0(x)
def compute_hx(x, parameters):
   m = len(x)
   # print(parameters[0])
   hx = []
   # print(“m = “,m)
   for i in range(m):
      # print(“i=”,i)
      prediction = parameters[0]
      prediction+= parameters[1]*x.iloc[i]
      hx.append(prediction)
   return hx
#Function to compute Linear Regression Cost
#Inputs: hx - The predicted values, y - The actual values
#Output: cost, as per the formula
def compute_cost(hx, y):
   m = len(hx)
   sum1 = 0
   for i in range(m):
      sum1 += (hx[i] — y.iloc[i])**2
   cost = sum1/(2*m)
   return cost

Целью этой задачи линейной регрессии является получение 𝜃₀ и 𝜃₁таких, что h₀(x)иy почти равны (т. е. разница между ними сведена к минимуму). Алгоритм, который помогает минимизировать эту разницу, называется стандартным градиентным спуском.

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

Здесь 𝜶 относится к скорости обучения.

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

# This performs one step of the gradient descent loop
def gradient_descent_one_step(hx,x,y, parameters, alpha):
    m = len(hx)
    sum1 = 0
    sum2 = 0
    for i in range(m):
        sum1 += (hx[i] - y.iloc[i])
        sum2 += (hx[i] - y.iloc[i])*(x.iloc[i])
    cost1 = (sum1 * alpha)/m
    cost2 = (sum2 * alpha)/m
    parameters[0] -= cost1
    parameters[1] -= cost2
    return parameters

#Function to perform batch gradient descent, over many epochs
def batch_gradient_descent(x, y, parameters, alpha, epochs):
    cost_list={}
    parameter_list={}
    param_cost_list_1={}
    param_cost_list_0={}
    for i in range(epochs):
        hx = compute_hx(x, parameters)
        cost = compute_cost(hx, y)
        parameters = gradient_descent_one_step(hx,x,y,parameters,alpha)
        # The following if block stores data for graph_plotting
        if i%10==0:
            # Saving cost and params here, which'll help to plot graphs
            cost_list[i] = cost
            parameter_list[i] = parameters
            param_cost_list_1[parameters[1]] = cost
            param_cost_list_0[parameters[0]] = cost
    return parameters,cost,cost_list,parameter_list, param_cost_list_1, param_cost_list_0

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

Мой набор данных хранится в формате CSV. Но в то время мне было очень любопытно узнать о форматах данных pickle, и поэтому я сделал обходной путь — сохранил набор данных в файл pickle и прочитал его снова! Однако этот шаг необязателен.

#Code to read data from csv file
import pandas as pd
dataset = pd.read_pickle('lr_dataset.pkl')
print(dataset.shape)

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

#Split the dataset into 70% training, 10% Validation and 20% testing
#70% of 300 = 210
#10% of 300 = 30
#20% of 300 = 60
#Before splitting, shuffle the dataset first
dataset = dataset.sample(frac = 1).reset_index(drop=True)
#Splitting the dataset
train_set = dataset[:210].copy()
validation_set = dataset[211:241].copy()
test_set = dataset[241:].copy()
# print(len(train_set))
#Split each of the sets further into x and y
x_train = train_set['X']
y_train = train_set['Y']
x_valid = validation_set['X']
y_valid = validation_set['Y']
x_test = test_set['X']
y_test = test_set['Y']

Я не хотел использовать метод sklearn train_test_split, поэтому разделил его таким образом.

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

  1. параметры = я выбрал [0,1, -0,1] в начале.
  2. Скорость обучения = 0,01
  3. Количество эпох = 100

Я обучил модель следующим кодом:

parameters,cost,cost_list,parameter_list, param_cost_list_1, param_cost_list_0 = batch_gradient_descent(x_train, y_train, parameters, alpha, epoch_max)

После обучения модели я построил график зависимости функции стоимости от номера эпохи.

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

После этого я пробовал с разными значениями скорости обучения, начальными значениями параметров и количеством эпох. Я наконец наткнулся на правильный, спустя почти 2 недели:

  1. параметры = [-0,00001,0,00005]
  2. Скорость обучения (альфа) = 0,000001
  3. Количество эпох = 200

С этими значениями я получил лучшую кривую стоимости по сравнению с эпохой:

Я также построил графики, чтобы наблюдать, как веса (𝜃₁ или w) и смещение (𝜃₀ или b) меняются в зависимости от функции стоимости.

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

На графике кривой стоимости и эпохи я выбрал веса соответствующей эпохи, при которой стоимость была наименьшей. Из графика я выбрал значение весов в эпоху 80: [0,0036074754087815596, 0,6810518933205848]

Я использовал эти параметры для вычисления прогнозов для набора для разработки (проверки) и набора тестов. Результаты визуализируются следующим образом:

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

Итак, мы наконец-то изучили модель набора данных!

Некоторые будущие улучшения, о которых я думаю:

  1. Используйте нормальное уравнение вместо градиентного спуска; и посмотрите, насколько различаются конечные параметры.
  2. Я мог бы автоматизировать ту часть, где я выбираю параметры с наименьшими затратами. Вместо того, чтобы выбирать его вручную, глядя на графики, я мог бы внедрить этот шаг в сам код.
  3. Используйте ML Frameworks и сравните производительность полученной модели с той, которую я только что построил.

Весь код можно посмотреть здесь.

Спасибо за чтение!