Это третья статья в этой серии, в которой я пытаюсь перекодировать упражнения из (старого) курса машинного обучения Эндрю Нг (где упражнения по программированию выполняются с использованием Octave). Моя цель при написании этих статей — помочь слушателям этого курса использовать Python в качестве альтернативы при выполнении упражнений. Пожалуйста, также не стесняйтесь исследовать Часть 1 и Часть 2.

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

1. Знакомство с данными

Приведенный набор данных содержит 100 записей с двумя функциями (exam1_score и exam2_score) и 1 целевой столбец (результат приема). Как обычно, я загружу данные и попытаюсь визуализировать данные, чтобы получить представление о том, как они выглядят.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
data = pd.read_table('ex2data1.txt', header = None, sep=',')
data.columns =['exam1', 'exam2', 'admission']
data.head()

Ниже приведен фрагмент набора данных:

В приведенном ниже коде функция «plotdata()» написана для построения диаграммы рассеяния наших данных. Из графика видно, что допущенные студенты имеют общий балл (сумма баллов за экзамен 1 и экзамен 2) более 100. Те, у кого общий балл ниже 100, не допускаются.

def plotdata(data):
    one_index = data.index[data['admission'] ==1].tolist()
    zero_index = data.index[data['admission'] ==0].tolist()
    plt.plot(data.iloc[one_index, 0], data.iloc[one_index, 1], 'k+', linewidth = 2, markersize = 7)
    plt.plot(data.iloc[zero_index, 0], data.iloc[zero_index, 1], 'ko', markersize = 7)
    plt.xlabel('Exam 1 score')
    plt.ylabel('Exam 2 score')
    plt.legend(['Admitted', 'Not admitted'])
    plt.show()
plotdata(data)

2. Сигмовидная функция

Гипотеза логистической регрессии определяется как:

где функция g — сигмовидная функция. Он определяется как:

Ниже приведена сигмовидная функция, написанная на питоне:

# the function 'sigmoid' can compute the sigmoid of each value of z (z can be a matrix vector or a scalar)
def sigmoid(z):
    g = 1/(1 + np.exp(-z))
    return g

3. Функция стоимости и градиент

Функция стоимости в логистической регрессии определяется следующим образом:

, где m — количество обучающих примеров. А градиент стоимости определяется как:

, для j = 0,1,2,…,n, где n — количество признаков.

Теперь давайте определим x и y из нашего набора данных. Для x мы удалим первые два столбца из нашего фрейма данных «данные» и добавим столбец из единиц для термина смещения.

# getting total number of training examples
m = data.shape[0]
# define x and y
y = data.iloc[:, 2].to_numpy().reshape(m,1)
x = np.concatenate((np.ones((m,1)), data.iloc[:, :2]), axis = 1)
# initialize theta
theta_start = np.zeros((x.shape[1], 1))

Мы определим две отдельные функции для расчета стоимости и градиента.

# this function will compute cost of using theta as the parameter for logistic regression
def J(theta, x, y):
    m = len(y)
    y_prime = np.transpose(y)
    x_prime = np.transpose(x)
    h_theta = sigmoid(np.dot(x,theta))
    
    J = (-np.dot(y_prime, np.log(h_theta)) -  np.dot(np.transpose(1-y), np.log(1-h_theta)))/ m
    J = np.sum(J)
    
    return J
# Gradient of the cost
def Gradient(theta, x, y):
    m = len(y)
    x_prime = np.transpose(x)
    
    error = sigmoid(np.dot(x,theta)) - y

    grad = (np.dot(x_prime, error))/ m
    grad = grad.flatten()
    return grad

4. Параметры обучения с использованием Newton-Conjugate-Gradient

Вместо выполнения шагов градиентного спуска для ряда итераций мы будем использовать функцию fmin_ncg() (неограниченная минимизация с использованием метода Newton-CG) из scipy.optimize. Примечание: для логистической регрессии нет ограничений на значения тета (т. е. тета может принимать любое реальное значение). Подробнее о функции можно прочитать здесь.

Необходимые параметры для этой функции:

  1. Целевая функция, которую необходимо минимизировать. (это наша функция стоимости J(ϴ)).
  2. Начальное предположение оптимизируемых параметров, т. е. наши инициализированные значения ϴ.
  3. Градиент J(ϴ)
  4. maxiter: максимальное количество итераций для выполнения

Функция вернет оптимизированные значения ϴ (вместе с другой информацией).

import scipy.optimize as opt
theta_start = np.zeros((x.shape[1], 1))
result = opt.fmin_ncg(J, x0=theta_start, fprime = Gradient, args = (x,y.flatten()), maxiter = 400)
print("Cost at theta found by fmin_ncg() : {}".format(J(result,x,y)))
print('Theta : {}'.format(result))

Ниже приведен вывод:

5. Построение граничной линии

Я напишу еще одну функцию, чтобы вызвать нашу предыдущую функцию «plotdata()» и добавить на график граничную линию.

def plotDB(data, theta, x, y):
    plotdata(data)
    
    # we take out two values for x1
    x_values = [np.min(x[:, 1]), np.max(x[:, 1])]
    # use these x1 values to calculate x2 values
    y_values = - (theta[0] + np.dot(theta[1], x_values)) / theta[2]
    
    plt.plot(x_values, y_values, label='Decision Boundary')
plotDB(data, result, x, y)

6. Оценка модели

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

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

# Function for predicting
# this will predict whether the label is 1 or 0 using the learned logistic regression parameters theta
def predict(theta, x):
    m = len(x)
    p = np.zeros((m,1))
    for i in range(m):
        sig = sigmoid(np.dot(x[i],theta))
        if sig > 0.5:
            p[i] = 1
        else:
            p[i] = 0
    return p
p = predict(result, x)
print("Train Accuracy : {}%".format(int(sum(p==y))))

Исходя из этого, точность нашей модели, основанной на обучающем наборе, составляет 89%.

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

Продолжай учиться. Наслаждайся путешествием!