Машинное обучение 101 Все алгоритмы в Python (линейная регрессия)
Создание популярного курса машинного обучения профессора Эндрю на Coursera. Все задания на Python.
В этом блоге я расскажу вам о выполнении самых популярных заданий курса машинного обучения, доступных в Интернете и представленных профессором Эндрю Нг. Начнем с линейной регрессии.
Первое задание — построить алгоритм линейной регрессии
Обратите внимание, что исходный pdf-файл, который используется для описания задания, будет использоваться здесь, чтобы сделать то же самое и объяснить алгоритм. Прежде чем приступить к этому упражнению по программированию, мы настоятельно рекомендуем посмотреть видеолекции и ответить на контрольные вопросы по соответствующим темам.
Импорт пакетов Python
# used for manipulating directory paths import os # Scientific and vector computation for python import numpy as np # Plotting library from matplotlib import pyplot # tells matplotlib to embed plots within the notebook %matplotlib inline
Линейная регрессия с одной переменной
Первым шагом перед работой над любым проектом машинного обучения является определение проблемы, которую мы собираемся решить с помощью ML.
Предположим, вы генеральный директор ресторанной франшизы и рассматриваете возможность открытия новой точки в разных городах. У сети уже есть грузовики в разных городах, и у вас есть данные о прибыли и населении городов. Вы хотели бы использовать эти данные, чтобы помочь вам выбрать, какой город расширить до следующего.
Файл Data/ex1data1.txt
содержит набор данных для нашей задачи линейной регрессии. Первый столбец — это население города (в 10 000), а второй столбец — прибыль фудтрака в этом городе (в 10 000 долларов). Отрицательное значение прибыли указывает на убыток.
Загрузка набора данных
# Read comma separated data data = np.loadtxt(os.path.join('Data', 'ex1data1.txt'), delimiter=',') X, y = data[:, 0], data[:, 1] m = y.size # number of training examples
Отображение данных
Прежде чем приступить к какой-либо задаче, часто бывает полезно понять данные, визуализировав их. Для этого набора данных мы можем использовать точечный график для визуализации данных, поскольку он имеет только два свойства для построения (прибыль и население). Многие другие проблемы, с которыми вы столкнетесь в реальной жизни, являются многомерными и не могут быть нанесены на двумерный график. В python есть много библиотек для построения графиков.
В этом курсе мы будем использовать исключительно matplotlib
для всех наших графиков. matplotlib
— одна из самых популярных научных библиотек для построения графиков на Python, в ней есть обширные инструменты и функции для создания красивых графиков. pyplot
— это модуль внутри matplotlib
, который предоставляет упрощенный интерфейс для наиболее распространенных задач построения графиков в matplotlib
, имитируя интерфейс построения графиков MATLAB.
def plotData(x, y): """ Plots the data points x and y into a new figure. Plots the data points and gives the figure axes labels of population and profit. Parameters ---------- x : array_like Data point values for x-axis. y : array_like Data point values for y-axis. Note x and y should have the same size. Instructions ------------ Plot the training data into a figure using the "figure" and "plot" functions. Set the axes labels using the "xlabel" and "ylabel" functions. Assume the population and revenue data have been passed in as the x and y arguments of this function. Hint ---- You can use the 'ro' option with plot to have the markers appear as red circles. Furthermore, you can make the markers larger by using plot(..., 'ro', ms=10), where `ms` refers to marker size. You can also set the marker edge color using the `mec` property. """ fig = pyplot.figure() # open a new figure pyplot.plot(x, y, 'ro', ms=10, mec='k') pyplot.ylabel('Profit in $10,000') pyplot.xlabel('Population of City in 10,000s') plotData(X, y)
Градиентный спуск
В этой части мы подгоним тета параметров линейной регрессии к нашему набору данных, используя градиентный спуск.
Обновленные уравнения
Цель линейной регрессии – минимизировать функцию затрат.
где гипотеза ho(x) задается линейной
Напомним, что параметры вашей модели — это тета-значения. Это значения, которые вы будете корректировать, чтобы минимизировать стоимость J(тета). Один из способов сделать это — использовать алгоритм пакетного градиентного спуска. В пакетном градиентном спуске каждая итерация выполняет обновление.
С каждым шагом градиентного спуска параметры тета приближаются к оптимальным значениям, обеспечивающим наименьшую стоимость J(тета).
Реализация
# Add a column of ones to X. The numpy function stack joins arrays along a given axis. # The first axis (axis=0) refers to rows (training examples) # and second axis (axis=1) refers to columns (features). X = np.stack([np.ones(m), X], axis=1)
Вычисление стоимости (тета)
Поскольку мы выполняем градиентный спуск, чтобы научиться минимизировать функцию стоимости J (тета), полезно отслеживать сходимость, вычисляя стоимость. В этом разделе мы реализуем функцию для вычисления J(theta), чтобы мы могли проверить сходимость нашей реализации градиентного спуска.
def computeCost(X, y, theta): """ Compute cost for linear regression. Computes the cost of using theta as the parameter for linear regression to fit the data points in X and y. Parameters ---------- X : array_like The input dataset of shape (m x n+1), where m is the number of examples, and n is the number of features. We assume a vector of one's already appended to the features so we have n+1 columns. y : array_like The values of the function at each data point. This is a vector of shape (m, ). theta : array_like The parameters for the regression function. This is a vector of shape (n+1, ). Returns ------- J : float The value of the regression cost function. Instructions ------------ Compute the cost of a particular choice of theta. """ # initialize some useful values m = y.size # number of training examples J = 0 J=1/(2*m)*np.sum(((X[:,0]*theta[0]+X[:,1]*theta[1])-y)**2) return J
На следующем шаге мы запустим computeCost
два раза, используя две разные инициализации тета. мы увидим стоимость, напечатанную на экране.
J = computeCost(X, y, theta=np.array([0.0, 0.0])) print('With theta = [0, 0] \nCost computed = %.2f' % J) print('Expected cost value (approximately) 32.07\n') # further testing of the cost function J = computeCost(X, y, theta=np.array([-1, 2])) print('With theta = [-1, 2]\nCost computed = %.2f' % J) print('Expected cost value (approximately) 54.24')
Градиентный спуск
Далее мы завершим функцию, реализующую градиентный спуск. Имейте в виду, что стоимость J(theta) параметризуется вектором theta, а не X и y. То есть мы минимизируем значение J(theta), изменяя значения вектора theta, а не изменяя X или y. Хороший способ убедиться, что градиентный спуск работает правильно, — посмотреть на значение J(theta) и убедиться, что оно уменьшается с каждым шагом.
Стартовый код для функции gradientDescent
вызывает computeCost
на каждой итерации и сохраняет стоимость в списке python
.
def gradientDescent(X, y, theta, alpha, num_iters): """ Performs gradient descent to learn `theta`. Updates theta by taking `num_iters` gradient steps with learning rate `alpha`. Parameters ---------- X : array_like The input dataset of shape (m x n+1). y : array_like Value at given features. A vector of shape (m, ). theta : array_like Initial values for the linear regression parameters. A vector of shape (n+1, ). alpha : float The learning rate. num_iters : int The number of iterations for gradient descent. Returns ------- theta : array_like The learned linear regression parameters. A vector of shape (n+1, ). J_history : list A python list for the values of the cost function after each iteration. Instructions ------------ Peform a single gradient step on the parameter vector theta. While debugging, it can be useful to print out the values of the cost function (computeCost) and gradient here. """ # Initialize some useful values m = y.shape[0] # number of training examples # make a copy of theta, to avoid changing the original array, since numpy arrays # are passed by reference to functions theta = theta.copy() J_history = [] # Use a python list to save cost in every iteration for i in range(num_iters): temp_0=theta[0]-alpha*(1/m)*np.sum(((X[:,0]*theta[0]+X[:,1]*theta[1])-y)*X[:,0]) temp_1=theta[1]-alpha*(1/m)*np.sum(((X[:,0]*theta[0]+X[:,1]*theta[1])-y)*X[:,1]) theta[0]=temp_0 theta[1]=temp_1 # save the cost J in every iteration J_history.append(computeCost(X, y, theta)) return theta, J_history # initialize fitting parameters theta = np.zeros(2) # some gradient descent settings iterations = 1500 alpha = 0.01 theta, J_history = gradientDescent(X ,y, theta, alpha, iterations) print('Theta found by gradient descent: {:.4f}, {:.4f}'.format(*theta)) print('Expected theta values (approximately): [-3.6303, 1.1664]') Theta found by gradient descent: -3.6303, 1.1664 Expected theta values (approximately): [-3.6303, 1.1664] # plot the linear fit plotData(X[:, 1], y) pyplot.plot(X[:, 1], np.dot(X, theta), '-') pyplot.legend(['Training data', 'Linear regression']);
Использование модели для прогнозирования
# Predict values for population sizes of 35,000 and 70,000 predict1 = np.dot([1, 3.5], theta) print('For population = 35,000, we predict a profit of {:.2f}\n'.format(predict1*10000)) predict2 = np.dot([1, 7], theta) print('For population = 70,000, we predict a profit of {:.2f}\n'.format(predict2*10000))
Результат
For population = 35,000, we predict a profit of 4519.77 For population = 70,000, we predict a profit of 45342.45
Линейная регрессия с несколькими переменными
В этой части мы реализуем линейную регрессию с несколькими переменными для прогнозирования цен на дома. Предположим, вы продаете свой дом и хотите знать, какой будет хорошая рыночная цена. Один из способов сделать это — сначала собрать информацию о недавно проданных домах и составить модель цен на жилье.
Файл Data/ex1data2.txt
содержит обучающий набор цен на жилье в Портленде, штат Орегон. Первый столбец — это размер дома (в квадратных футах), второй столбец — количество спален, а третий столбец — цена дома.
Нормализация функций
Мы начинаем с загрузки и отображения некоторых значений из этого набора данных. Глядя на значения, обратите внимание, что размеры дома примерно в 1000 раз превышают количество спален. Когда функции различаются на порядки, первое масштабирование функций может ускорить сходимость градиентного спуска. Подробнее об этом читайте в лекциях профессора Эндрю.
# Load data data = np.loadtxt(os.path.join('Data', 'ex1data2.txt'), delimiter=',') X = data[:, :2] y = data[:, 2] m = y.size # print out some data points print('{:>8s}{:>8s}{:>10s}'.format('X[:,0]', 'X[:, 1]', 'y')) print('-'*26) for i in range(10): print('{:8.0f}{:8.0f}{:10.0f}'.format(X[i, 0], X[i, 1], y[i]))
Здесь задача состоит в том, чтобы завершить код в функции featureNormalize
:
- Вычтите среднее значение каждой функции из набора данных.
- После вычитания среднего дополнительно масштабируйте (разделите) значения признаков на их соответствующие «стандартные отклонения».
Стандартное отклонение — это способ измерения степени вариации в диапазоне значений определенного признака (большинство точек данных будет лежать в пределах ±2 стандартных отклонений от среднего значения); это альтернатива диапазону значений (max-min). В numpy
вы можете использовать функцию std
для вычисления стандартного отклонения.
Например, количество X[:, 0]
содержит все значения x_1 (размеры домов) в обучающем наборе, поэтому np.std(X[:, 0])
вычисляет стандартное отклонение размеров домов. Во время вызова функции featureNormalize
дополнительный столбец единиц, соответствующий x_0 = 1, еще не был добавлен к X.
Вы сделаете это для всех функций, и ваш код должен работать с наборами данных всех размеров (любое количество функций/примеров). Обратите внимание, что каждый столбец матрицы X соответствует одному признаку.
def featureNormalize(X): """ Normalizes the features in X. returns a normalized version of X where the mean value of each feature is 0 and the standard deviation is 1. This is often a good preprocessing step to do when working with learning algorithms. Parameters ---------- X : array_like The dataset of shape (m x n). Returns ------- X_norm : array_like The normalized dataset of shape (m x n). Instructions ------------ First, for each feature dimension, compute the mean of the feature and subtract it from the dataset, storing the mean value in mu. Next, compute the standard deviation of each feature and divide each feature by it's standard deviation, storing the standard deviation in sigma. Note that X is a matrix where each column is a feature and each row is an example. You needto perform the normalization separately for each feature. Hint ---- You might find the 'np.mean' and 'np.std' functions useful. """ # You need to set these values correctly X_norm = X.copy() mu = np.zeros(X.shape[1]) sigma = np.zeros(X.shape[1]) # =========================== YOUR CODE HERE ===================== mu=np.mean(X,axis=0) sigma=np.std(X,axis=0) X_norm=X-mu/sigma # ================================================================ return X_norm, mu, sigma
Выполните следующую ячейку, чтобы запустить реализованную функцию featureNormalize
.
# call featureNormalize on the loaded data X_norm, mu, sigma = featureNormalize(X) print('Computed mean:', mu) print('Computed standard deviation:', sigma) Computed mean: [2000.68085106 3.17021277] Computed standard deviation: [7.86202619e+02 7.52842809e-01] # Add intercept term to X X = np.concatenate([np.ones((m, 1)), X_norm], axis=1)
Градиентный спуск
Ранее мы реализовали градиентный спуск для задачи одномерной регрессии. Единственное отличие теперь состоит в том, что в матрице X есть еще одна функция. Функция гипотезы и правило обновления пакетного градиентного спуска остаются без изменений.
def computeCostMulti(X, y, theta): """ Compute cost for linear regression with multiple variables. Computes the cost of using theta as the parameter for linear regression to fit the data points in X and y. Parameters ---------- X : array_like The dataset of shape (m x n+1). y : array_like A vector of shape (m, ) for the values at a given data point. theta : array_like The linear regression parameters. A vector of shape (n+1, ) Returns ------- J : float The value of the cost function. Instructions ------------ Compute the cost of a particular choice of theta. You should set J to the cost. """ # Initialize some useful values m = y.shape[0] # number of training examples J = 0 J=1/(2*m)*np.sum(((X[:,0]*theta[0]+X[:,1]*theta[1]+X[:,2]*theta[2])-y)**2) return J def gradientDescentMulti(X, y, theta, alpha, num_iters): """ Performs gradient descent to learn theta. Updates theta by taking num_iters gradient steps with learning rate alpha. Parameters ---------- X : array_like The dataset of shape (m x n+1). y : array_like A vector of shape (m, ) for the values at a given data point. theta : array_like The linear regression parameters. A vector of shape (n+1, ) alpha : float The learning rate for gradient descent. num_iters : int The number of iterations to run gradient descent. Returns ------- theta : array_like The learned linear regression parameters. A vector of shape (n+1, ). J_history : list A python list for the values of the cost function after each iteration. Instructions ------------ Peform a single gradient step on the parameter vector theta. While debugging, it can be useful to print out the values of the cost function (computeCost) and gradient here. """ # Initialize some useful values m = y.shape[0] # number of training examples # make a copy of theta, which will be updated by gradient descent theta = theta.copy() J_history = [] for i in range(num_iters): temp_0=theta[0]-alpha*(1/m)*np.sum(((X[:,0]*theta[0]+X[:,1]*theta[1]+X[:,2]*theta[2])-y)*X[:,0]) temp_1=theta[1]-alpha*(1/m)*np.sum(((X[:,0]*theta[0]+X[:,1]*theta[1]+X[:,2]*theta[2])-y)*X[:,1]) temp_2=theta[2]-alpha*(1/m)*np.sum(((X[:,0]*theta[0]+X[:,1]*theta[1]+X[:,2]*theta[2])-y)*X[:,2]) theta[0]=temp_0 theta[1]=temp_1 theta[2]=temp_2 # save the cost J in every iteration J_history.append(computeCostMulti(X, y, theta)) return theta, J_history
Если вы подписались на меня и написали код выше, поздравляю!
Вы построили модели одномерной и многомерной линейной регрессии с нуля. Это указывает на то, что теперь вы можете использовать scikit-learn с полным пониманием таких гиперпараметров, как альфа, лямбда и т. д...
Чтобы вас заинтересовать, мы построим модель логистической регрессии с нуля.
Увидимся в следующем блоге.
«Что хорошего в идее, если она остается идеей? Пытаться. Эксперимент. Повторить. Неудача. Попробуйте еще раз. Изменить мир."