Машинное обучение 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 с полным пониманием таких гиперпараметров, как альфа, лямбда и т. д...

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

Увидимся в следующем блоге.

«Что хорошего в идее, если она остается идеей? Пытаться. Эксперимент. Повторить. Неудача. Попробуйте еще раз. Изменить мир."

Саймон Синек