Перейти к содержанию

Введение в машинное обучение

Автор:

Категория
учебники и пособия для вузов
Год издания
Издательство
Автор
Возрастное ограничение
16+
Обновлено
25.09.2026

Аннотация

Учебник поможет студентам различных специальностей освоить современные технологии машинного обучения и практически использовать их в работе и научных проектах. В настоящем пособии даются весьма краткие теоретические и относительно подробные практические сведения о применении отдельных алгоритмов классификации и регрессии. Для практического освоения материала достаточно базовых навыков работы с языком Python. При этом освоение возможностей основных библиотек, таких как matplotlib, numpy, pandas, sklearn происходит в процессе решения задач. Используя полученные знания и навыки, студенты смогут решать широкий круг задач классификации, регрессии, анализировать влияние отдельных признаков на работу классификаторов и регрессионных моделей, снижать размерность данных, визуализировать результаты и оценивать качество моделей машинного обучения. Издание рекомендовано УМО РУМС.

Читать «Введение в машинное обучение» — Равиль Ильгизович Мухамедиев онлайн

Страница 2 из 5

on=np.ones([m,1])

#и сформируем матрицу X, объединив колонки

X=np.concatenate((on,x),axis=1)


Это матрица, в первой колонке которой стоят единицы, а во второй – значения x(1), x(2),…, x(m). Затем зададим абсолютно произвольно начальные значения коэффициентов регрессии:


theta=np.matrix('0.1;1.3')

#и рассчитаем значения функции гипотезы

h=np.dot(X,theta)

#дополним предыдущий график регрессионной прямой

plt.plot(x,h)


Получим график вида:


Рисунок 2.3. Начальное положение прямой регрессии


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


t0=time.time()

alpha=0.05

iterations=500

for i in range(iterations):

theta=theta-alpha*(1/m)*np.sum(np.multiply((h-y),x))

h=np.dot(X,theta)

t1=time.time()

#Построим графики

plt.figure(figsize=(9,9))

plt.plot(x,y,'.')

plt.plot(x,h,label='regressionByIteration')

leg=plt.legend(loc='upper right',shadow=True,fontsize='x-small')

leg.get_frame().set_facecolor('#0055DD')

leg.get_frame().set_facecolor('#eeeeee')

leg.get_frame().set_alpha(0.5)

plt.show()


Получим следующий график регрессионной прямой:


Рисунок 2.4. Результат выполнения алгоритма градиентного спуска


#рассчитаем среднеквадратическую ошибку

mse=np.sum(np.power((h-y),2))/m

print('regressionByIteration mse= ', mse)

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

print('regressionByIterations takes ',(t1-t0))


Получим примерно следующий вывод:

regressionByIterations mse = 63.270782365456206

regressionByIterations takes 0.027503490447998047

В качестве небольшого дополнения рассчитаем показатели точности регрессии с применением библиотеки метрик sklearn.

from sklearn.metrics import mean_squared_error, r2_score

y_predict = h

y_test=y

print("Mean squared error: {:.2f}".format(mean_squared_error(y_test,y_predict)))

print("r2_score: {:.2f}".format(r2_score(y_test, y_predict)))


Получим следующие результаты:

Mean squared error: 63.27

r2_score: 0.93


Подробнее о метриках точности классификации и регрессии см. в разделе «3. Оценка качества методов ML».

2.3. Полиномиальная регрессия

В отличие от линейной регрессии, полиномиальная регрессия оперирует нелинейной функцией гипотезы вида , что позволяет строить экстраполирующие кривые (гиперповерхности) сложной формы. Однако с увеличением числа параметров существенно возрастает вычислительная сложность алгоритма. Кроме этого, существует опасность «переобучения», когда форма кривой или гиперповерхности становится слишком сложной, практически полностью подстроившись под обучающее множество, но дает большую ошибку на тестовом множестве. Это свидетельствует о том, что алгоритм потерял способность к обобщению и предсказанию. В случае переобучения, когда классификатор теряет способность к обобщению, применяют регуляризацию, снижающую влияние величин высокого порядка:



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

Вычисление параметров регрессии методом градиентного спуска выполняется так же, как и ранее, за исключением небольшого дополнения в виде регуляризационного коэффициента, так что для j-го параметра на каждом шаге цикла вычисляется значение:



Рассмотрим пример.

В качестве исходных данных синтезируем набор данных в соответствии с выражением:



Добавив некоторый случайный коэффициент с помощью np.array([np.random.rand(x.size)]).T/50, получим примерно следующее (рисунок 2.5):




Рисунок 2.5. Исходные данные и результаты выполнения алгоритма полиномиальной регрессии


Введем переменную degree, означающую коэффициент регрессии. Например, при degree = 1 получим обычную линейную регрессию (r2_score = 0.27). Увеличивая степень регрессии, можем добится значительно лучших результатов. Например, при degree = 19 r2_score = 0.90. Использование коэффициента регуляризации lambda_reg позволяет «сглаживать» регрессионную кривую. Фрагмент программы, обеспечивающей расчет параметров полиномиальной регрессии, приведен ниже:

xr=np.array([np.linspace(0,1,180)])

x=xr.T

print(x.size)

y=f(x)

(x,y)=plusRandomValues(x,y) #добавление случайных величин

plt.figure(figsize=(9,9))

plt.plot(x,y,'.')

m=x.size

degree=19 #коэффициент регрессии

lambda_reg=0.00001

on=np.ones([m,1])

X=on

#расчет степеней свободной переменной в соответствии со степенью регрессии degree

for i in range(degree):

xx=np.power(x, i+1)

X=np.concatenate((X,xx),axis=1)

theta=np.array([np.random.rand(degree+1)])

h=np.dot(X,theta.T)

t0=time.time()

alpha=0.5

iterations=100000

for i in range(iterations):

theta=theta-alpha*(1/m)*np.dot((h-y).T,X) -(lambda_reg/m)*theta

h=np.dot(X,theta.T)

t1=time.time()

plt.plot(x,y,'.')

plt.plot(x,h, label='Regression degree = {:0.2f})'.format(degree))

leg=plt.legend(loc='upper left',shadow=True,fontsize=16)

leg.get_frame().set_facecolor('#0055DD')

leg.get_frame().set_facecolor('#')

leg.get_frame().set_alpha(0.9)

plt.show()

2.4. Классификаторы. Логистическая регрессия

Несмотря на присутствующее в названии данного метода слово «регрессия», цель данного алгоритма не восстановление значений или предсказание. Алгоритм применяется в случае, если необходимо решить задачу классификации. В случае логистической регрессии речь идет о задаче бинарной классификации, то есть отнесении объектов к классу «негативных» или «позитивных», вследствие чего набор обучающих примеров построен таким образом, что y ∈ {0,1}.

В этом случае от функции гипотезы требуется выполнение условия 0 ≤hθ(x) ≤1, что достигается применением сигмоидальной (логистической) функции:



Где θ – вектор параметров.

Можно записать также



где n – число параметров (свойств или признаков) объектов; g(z) – сигмоидальная или логистическая функция.

В сокращенном виде hθ(x) = g(θTx).

Отметим, что сигмоидальная функция широко применяется и в нейронных сетях в качестве активационной функции нейронов, поскольку является непрерывно дифференцируемой и тем самым гарантирует сходимость алгоритмов обучения нейронной сети. Примерный вид сигмоиды показан в разделе «Активационные функции».

Функция hθ(x) может рассматриваться как вероятность того, что объект является «позитивным» (hθ(x)≥0.5) или «негативным» (hθ(x)<0.5). В сложных случаях, требующих нелинейной границы разделения, например, в виде окружности (рисунок 2.6), необходимо добавить дополнительные параметры, например, квадратные степени исходных параметров:



или их произведения и т.п.


Рисунок 2.6. Объекты, для которых необходима нелинейная граница разделения


Подбор параметров θ после выбора функции гипотезы выполняется так, чтобы минимизировать функцию стоимости вида:



Из двух частей функции стоимости, объединенных знаком +, вычисляется фактически только одна, так как в задаче классификации y может принимать только два значения: 1 и 0.

То есть в случае, если y = 0, стоимость для i-го примера принимает вид:



Таким образом, при минимальном значении функции стоимости в обоих случаях достигается максимизация вероятности принадлежности объекта к положительному классу для «положительных» объектов и минимизация вероятности для «отрицательных» объектов. По-другому логистический классификатор называется классификатором максимизации энтропии (maximum-entropy classification – MaxEnt).

Как и в случае с линейной регрессией, минимизация функции стоимости достигается с помощью алгоритма градиентного спуска (gradient descent), но также применяются Conjugate gradient [[36]], BFGS, L-BFGS или lbfgs [[37]].

Логистический классификатор может быть применен и в отношении нескольких классов. В этом случае для каждого класса классификатор настраивается отдельно. Класс, к которому принадлежит новый объект, вычисляется расчетом значений всех функций гипотез и выбором из них максимального значения miaxhθ(i)(x), где i – номер класса. Другими словами, объект принадлежит к тому классу, функция гипотезы которого максимальна.

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



Интересно, что производная функции стоимости логистической регрессии ровно такая же, как и производная функции стоимости линейной регрессии (вывод см., например, в [[38]]). Следовательно, алгоритм градиентного спуска будет работать так же, как и для линейной регрессии (формула 1.5), с тем отличием, что значение функции гипотезы будет вычисляться по формуле 2.8.

Пример. Построим линейный классификатор на основе логистической регрессии. Вначале сгенерируем набор данных и разделим его на тренировочное и тестовое множества:


from sklearn.datasets import make_moons, make_circles, make_classification

from sklearn.model_selection import train_test_split

dataset = make_circles(noise=0.2, factor=0.5, random_state=1)

X_D2, y_D2 = dataset

plt.figure(figsize=(9,9))

plt.scatter(X_D2[:,0],X_D2[:,1],c=y_D2,marker='o',

s=50,cmap=ListedColormap(['#FF0000','#00FF00']))

X_train, X_test, y_train, y_test = train_test_split(X_D2, y_D2, test_size=.4, random_state=42)


В результате получим распределение данных, показанное на рисунке 1.3.

Вызовем необходимые библиотеки и методы:


import matplotlib.pyplot as plt

import numpy as np

from sklearn.metrics import confusion_matrix, classification_report

from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score


Последние две строки необходимы для оценки точности работы классификатора (см. раздел «Оценка качества методов ML»).

Разработаем логистическую функцию logisticFunction(X,theta) и функцию, обеспечивающую оценку объекта на основе предсказанного значения гипотезы, – logRegPredictMatrix(h,threshold). Как показано выше, функция гипотезы принимает значение от 0 до 1. Для того чтобы получить оценку принадлежности объекта к классу (1 – «положительный», 0 – «отрицательный»), необходимо для каждого значения гипотезы вычислить номер класса («предсказать») по правилу predicted = 0 If h <threshold и predicted = 1 If h >= threshold. Обычное значение порога threshold=0.5.

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


def logisticRegressionByNumpy(X,y):

m=y.size

X=np.concatenate((np.ones([m,1]),X), axis=1)

theta=np.array(np.random.rand(X.shape[1]))

h=logisticFunction(X,theta)

alpha=0.05

iterations=1500

lambda_reg=0.01

for i in range(iterations):

theta=theta – alpha*(1/m) *np.dot(X.T,(h-y))-(lambda_reg/m)*theta

h=logisticFunction(X,theta)

return theta,h


Вызов функции и вывод показателей качества можно выполнить:


theta,h=logisticRegressionByNumpy(X_train,y_train)

predicted_train=logRegPredictMatrix(h,threshold=0.50)

matrix_train = confusion_matrix(y_train, predicted_train)#,labels)

print('Logistic regression')

print('Results on train set')

print('Accuracy on train set: {:.2f}'.format(accuracy_score(y_train, predicted_train)))

print('Conf. matrix on the train \n', matrix_train)

print('Classification report at train set\n',

classification_report(y_train, predicted_train, target_names = ['not 1', '1']))


В результате получим на тренировочном множестве значение accuracy = 0.57, а на тестовом 0.4. Другими словами, точность предсказания нашей функции хуже, чем при случайном выборе классов! Подобный результат вполне предсказуем, поскольку мы попытались использовать прямую там, где требуется как минимум окружность.

Исправить положение можно, используя регрессию второго порядка в соответствии с выражением (2.10). В предыдущей функции достаточно изменить одного оператора:

X=np.concatenate((np.ones([m,1]),X,X**2), axis=1)

После этого мы получим значение accuracy на тренировочном и тестовом множествах, равное 0.9, что вполне приемлемо для нашей задачи.

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

Вторым недостатком данного метода является то, что он предназначен для решения задач бинарной классификации.

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

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