Машинное обучение с нуля: часть 1
В этой статье мы построим самую базовую модель машинного обучения, называемую линейной регрессией, и реализуем ее, используя только python NumPy. Сначала мы рассмотрим наш набор данных, а затем поговорим об общем процессе контролируемого обучения, за которым следует представление гипотез, функция потерь и алгоритм градиентного спуска.
После этого мы напишем LinReg
класс и протестируем его на наших данных.
Серия "Машинное обучение с нуля" -
- Часть 1: Линейная регрессия с нуля в Python
- Часть 2: Локально взвешенная линейная регрессия в Python
- Часть 3: Нормальное уравнение с использованием Python: решение в закрытой форме для линейной регрессии
- Часть 4: Полиномиальная регрессия с нуля в Python
Линейная регрессия
Проще говоря, у нас есть набор данных с X
(функции) и y
(метки), и мы хотим провести к нему прямую линию. Давайте возьмем следующие данные в качестве мотивирующего примера:
Предположим, вы изучаете физику и хотите знать, сколько вы можете набрать на тесте (из 100), учитывая количество часов, которые вы занимаетесь.
# Imoprting required libraries. import numpy as np import matplotlib.pyplot as plt import pandas as pd # Reading the csv file. df = pd.read_csv('data.csv') # Displayinng the first five elements of the dataframe. df.head(10)
В этих данных Часы - это X
(функция) (помните, что у нас есть только одна функция в этих данных, то есть часы, хотя может быть несколько функций, например, сколько проблем вы решили для практики и т. Д.). Оценки равны y
(метки / цель) для каждого имеющегося у нас примера. Вот как выглядят данные, когда мы их строим.
# Taking the Hours and Scores column of the dataframe as X and y # respectively and coverting them to numpy arrays. X = np.array(df['Hours']).reshape(-1,1) y = np.array(df['Scores']) # Plotting the data X(Hours) on x-axis and y(Scores) on y-axis plt.figure(figsize=(8,6)) # figure size plt.scatter(X, y) plt.title('Hours vs Scores') plt.xlabel('X (Input) : Hours') plt.ylabel('y (Target) : Scores')
В линейной регрессии мы хотим провести прямую линию к таким данным. После этого мы сможем прогнозировать результаты на любое количество часов, которое захотим.
Стандартный процесс для алгоритма контролируемого обучения выглядит следующим образом -
Во-первых, у нас есть обучающий набор (данные), такой как в таблице 1. Мы передаем этот обучающий набор в алгоритм обучения. Задача алгоритма обучения состоит в том, чтобы вывести функцию h(x)
или функцию гипотезы, которая затем используется для прогнозирования (y
: оценка) для заданных входных данных X
(количество часов).
Первый вопрос: как представить гипотезу h (x)?
Итак, поскольку нам нужна прямая линия, мы скажем, что наша гипотеза будет:
h(x) = wX + b
w
→ веса, b
→ смещение
В коде я буду называть h(x)
как y_hat
.
y_hat = np.dot(X, weights) + bias
В приведенном выше примере мы можем представить данные в двух измерениях, поэтому мы можем сказать, что weights
будет наклоном линии, а bias
будет точкой пересечения по оси Y. Но если бы у нас было две функции в наших данных, а не только одна, то мы бы представляли данные в 3-х измерениях, и нам потребовалась бы плоскость для размещения данных в 3-х мерном пространстве. Итак, наша гипотеза будет представлять собой плоскость, а не прямую линию. По мере увеличения количества функций размеры наших weights
и bias
увеличиваются.
weights
и bias
- векторы, а размеры w и b равны количеству объектов. weights
и bias
также называются параметрами алгоритма обучения.
Наша цель - найти такие значения weights
и bias
, чтобы h(x)
было как можно ближе к y
.
Обозначения -
n
→ количество функций (1 в нашем примере, это часы)
m
→ количество обучающих примеров (в нашем примере 25)
X
→ Возможности
y
→ метки / цель
(X(i), y(i))
→ i-й пример в Обучающем наборе
Представление X, y, w и b
X
- матрица размера (m, n); строки представляют собой обучающие примеры, а столбцы представляют функции.
y
- матрица размера (m, 1); каждая строка является меткой для соответствующего набора функций в X
.
w
- это вектор размера (n, 1), а параметр b - это скаляр, который может транслироваться. Ознакомьтесь с Трансляция в NumPy
Если бы у нас было 3 функции и 3 обучающих примера, то представление выглядело бы так (m = 3, n = 3) -
Нижний индекс - это i-й обучающий пример, а верхний индекс - это j-я функция.
Следующий вопрос: как выбрать параметры w и b?
Итак, давайте выберем w и b так, чтобы h(x)
было как можно ближе к y
. Итак, мы хотим выбрать такие параметры, чтобы, по крайней мере, для часов, оценка которых нам известна, алгоритм обучения выдавал оценки, близкие к тем, которые мы знаем в обучающем наборе.
Функция потерь
Итак, мы определяем функцию потерь как среднеквадратичную ошибку -
loss = np.mean((y_hat - y)**2)
И мы хотим найти значения w и b, которые минимизируют функцию потерь. h(x)
или y_hat
- это значение, предсказываемое алгоритмом, а y
- истинное значение. Функция потерь - это мера того, насколько мы близки к истинному / целевому значению или, в целом, это мера того, насколько хорошо работает алгоритм. Чем меньше убыток, тем лучше.
Снова цель состоит в том, чтобы найти weights
и bias
так, чтобы минимизировать функцию потерь.
Буква J
или L
может использоваться для обозначения функции потерь. Функция потерь также называется функцией затрат, эти термины могут использоваться как синонимы.
Последний вопрос: как минимизировать функцию потерь?
Это делается с помощью алгоритма, называемого градиентным спуском.
Интуиция для градиентного спуска
Здесь theta0
то же самое, что bias
, и theta1
то же самое, что weights
.
Допустим, у нас есть функция затрат / потерь J(theta0, theta1)
, и мы хотим найти значения для theta0
и theta1
, которые минимизируют J
. Итак, при градиентном спуске мы начинаем в какой-то момент с инициализации theta0
и theta1
случайным образом или значением всех нулей, и что мы делаем, перед каждым шагом мы оглядываемся на все 360 градусов вокруг нас и смотрим, должны ли мы сделать небольшой шаг. , в каком направлении мы должны сделать этот шаг, чтобы спуститься как можно быстрее, потому что это самая низкая точка функции, где мы найдем минимум J
и, таким образом, получим оптимальные значения для theta0
и theta1
. На каждом этапе градиентный спуск будет искать самый крутой спуск.
Алгоритм градиентного спуска
Сначала мы инициализируем weights
и bias
случайным образом или вектором всех нулей.
# Initializing weights as a matrix of zeros of size: (number of # features: n, 1) and bias as 0 weights = np.zeros((n,1)) bias = 0
Затем мы продолжим изменять / обновлять weights
и bias
, чтобы уменьшить функцию потерь L
.
Правило обновления для градиентного спуска
# Updating the parameters: parameter := parameter - lr*(derivative # of loss/cost w.r.t parameter) weights -= lr*dw bias -= lr*db
lr or alpha
→ Скорость обучения. Он определяет, насколько большой или маленький шаг вы сделаете при спуске. Более низкая скорость обучения означает меньшие шаги и наоборот.
На приведенном выше рисунке показан параметр w
, который спускается вниз, чтобы достичь минимального значения J
. w
, где J
минимально, является оптимальным w
.
Градиент / производная weights
w.r.t Loss
Ограничение «y» в приведенной ниже формуле такое же, как y_hat
или h(x)
.
dw = (1/m)*np.dot(X.T, (y_hat - y))
Градиент / производная bias
w.r.t Loss
db = (1/m)*np.sum((y_hat - y))
Если вы знаете достаточно вычислений, вы можете взять частную производную от убытка (заменить y_hat
в убытке) с параметрами weights
и bias
, вы получите тот же результат.
Резюме линейной регрессии
Сначала получите данные и разделите их на X
(функции) и y
(метки). Затем инициализируйте параметры случайным образом или все нули, как это -
weights = np.zeros((n,1)) # n: number of features bias = 0
После этого,
1. Вычислите y_hat
или h (x).
y_hat = np.dot(X, weights) + bias
2. Рассчитайте градиенты потерь по параметрам (weights
и bias
)
dw = (1/m)*np.dot(X.T, (y_hat - y)) db = (1/m)*np.sum((y_hat - y))
3. Обновите параметры weights
и bias
.
weights -= lr*dw bias -= lr*db
4. Повторите вышеуказанные шаги. Количество повторений - это количество шагов, которые вы делаете при спуске. В машинном обучении это также называется epochs
(или количество итераций).
Класс линейной регрессии
Объясняется комментариями (#).
# Linear Regression class class LinReg: # Initializing lr: learning rate, epochs: no. of iterations, # weights & bias: parameters as None # default lr: 0.01, epochs: 800 def __init__(self, lr=0.01, epochs=800): self.lr = lr self.epochs = epochs self.weights = None self.bias = None # Training function: fit def fit(self, X, y): # shape of X: (number of training examples: m, number of # features: n) m, n = X.shape # Initializing weights as a matrix of zeros of size: (number # of features: n, 1) and bias as 0 self.weights = np.zeros((n,1)) self.bias = 0 # reshaping y as (m,1) in case your dataset initialized as # (m,) which can cause problems y = y.reshape(m,1) # empty lsit to store losses so we can plot them later # against epochs losses = [] # Gradient Descent loop/ Training loop for epoch in range(self.epochs): # Calculating prediction: y_hat or h(x) y_hat = np.dot(X, self.weights) + self.bias # Calculting loss loss = np.mean((y_hat - y)**2) # Appending loss in list: losses losses.append(loss) # Calculating derivatives of parameters(weights, and # bias) dw = (1/m)*np.dot(X.T, (y_hat - y)) db = (1/m)*np.sum((y_hat - y)) # Updating the parameters: parameter := parameter - lr*derivative # of loss/cost w.r.t parameter) self.weights -= self.lr*dw self.bias -= self.lr*db # returning the parameter so we can look at them later return self.weights, self.bias, losses # Predicting(calculating y_hat with our updated weights) for the # testing/validation def predict(self, X): return np.dot(X, self.weights) + self.bias
Теперь давайте протестируем класс LinReg
в нашем наборе данных.
Обучение
X_train, X_test, y_train, y_test = X[:20], X[20:], y[:20], y[20:] model = LinReg(epochs=100) w, b, l = model.fit(X_train,y_train)
Построение результатов
# Plotting our predictions. fig = plt.figure(figsize=(8,6)) plt.scatter(X, y) plt.plot(X, model.predict(X)) # X and predictions. plt.title('Hours vs Percentage') plt.xlabel('X (Input) : Hours') plt.ylabel('y (Target) : Scores')
# Predicting on the test set. X_test_preds = model.predict(X_test) X_test_preds >> array([[28.05459243], [48.44728266], [38.73647779], [68.83997288], [77.57969726]])
Сравнение истинных значений с прогнозами модели.
# Comparing True values to our predictions. Compare_df = pd.DataFrame({'Actual':y_test,Predicted':X_test_preds}) Compare_df
Потери по сравнению с эпохами (количество итераций).
fig = plt.figure(figsize=(8,6)) plt.plot([i for i in range(100)], l, 'r-') plt.xlabel('Number of iterations') plt.ylabel('Loss / Cost')
Мы видим, что по мере того, как мы делаем каждый шаг, потери уменьшаются и уменьшаются, что означает, что мы делаем все хорошо и идем в правильном направлении (то есть вниз) в ландшафте функции потерь.
Это все, что касается линейной регрессии. Немного поиграйте с кодом и попробуйте его с более крупными наборами данных. Спасибо за прочтение.
Скоро появятся новые статьи о машинном обучении с нуля.
По вопросам, комментариям, проблемам обращайтесь ко мне в разделе ответов.
Серия "Машинное обучение с нуля" -
- Часть 1: Линейная регрессия с нуля в Python
- Часть 2: Локально взвешенная линейная регрессия в Python
- Часть 3: Нормальное уравнение с использованием Python: решение в закрытой форме для линейной регрессии
- Часть 4: Полиномиальная регрессия с нуля в Python