Дилемма смещения-дисперсии. Продолжение

Полиномиальная регрессия. Демонстрация

Здравствуйте и вновь добро пожаловать на занятия по теме «Машинное обучение с учителем на языке Python: ансамблевые методы».

В этой дополнительной статье мы с помощью полиномиальной регрессии продемонстрируем уменьшение смещения по мере увеличения степени многочлена с увеличением дисперсии, рассмотрим метод k-ближайших соседей и дерево решений, и в заключение пройдем еще один метод – перекрёстную проверку.

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

Итак,импортируем необходимые библиотеки и функции:

import numpy as np

import matplotlib.pyplot as plt

from sklearn.linear_model import LinearRegression

from sklearn.metrics import mean_squared_error as mse

Крометого, нам понадобятся ряд констант. Количество наборов данных у нас будет равно50, дисперсия шума – 0,5, максимальная степень многочлена – 12, количествопримеров – 25 и количество учебных примеров – 90% от общего их количества:

NUM_DATASETS = 50

NOISE_VARIANCE = 0.5

MAX_POLY = 12

N= 25

Ntrain= int(0.9*N)

Установимтакже начальное число для генерации случайных чисел, чтобы у вас получился тотже результат, что и у меня:

np.random.seed(2)

И напишем функцию make_poly для создания многочлена от x:

def make_poly(x, D):

  N = len(x)

  X =np.empty((N, D+1))

  for d in xrange(D+1):

    X[:,d] =x**d

    if d > 1:

      X[:,d] =(X[:,d] – X[:,d].mean()) / X[:,d].std()

  return X

Следующее– зададим функцию f(x) и зададим данные для вывода графика наэкран:

def f(X):

  returnnp.sin(X)

x_axis = np.linspace(-np.pi, np.pi, 100)

y_axis = f(x_axis)

Теперь определим x:

X = np.linspace(-np.pi, np.pi, N)

np.random.shuffle(X)

f_X = f(X)

Затем создадим многочлен:

Xpoly = make_poly(X, MAX_POLY)

Теперьсоздадим ряд пустых массивов для хранения результатов нашего эксперимента:

train_scores = np.zeros((NUM_DATASETS, MAX_POLY))

test_scores = np.zeros((NUM_DATASETS, MAX_POLY))

train_predictions = np.zeros((Ntrain, NUM_DATASETS,MAX_POLY))

prediction_curves = np.zeros((100, NUM_DATASETS,MAX_POLY))

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

Итак, создадим модель:

model = LinearRegression()

for k in xrange(NUM_DATASETS):

  Y = f_X +np.random.randn(N)*NOISE_VARIANCE

  Xtrain =Xpoly[:Ntrain]

  Ytrain =Y[:Ntrain]

  Xtest =Xpoly[Ntrain:]

  Ytest =Y[Ntrain:]

Теперьцикл по всем степеням многочлена:

  for d in xrange(MAX_POLY):

   model.fit(Xtrain[:,:d+2], Ytrain)

    predictions= model.predict(Xpoly[:,:d+2])

    x_axis_poly = make_poly(x_axis, d+1)

   prediction_axis = model.predict(x_axis_poly)

   prediction_curves[:,k,d] = prediction_axis

   train_prediction = predictions[:Ntrain]

   test_prediction = predictions[Ntrain:]

   train_predictions[:,k,d] = train_prediction

    train_score= mse(train_prediction, Ytrain)

    test_score =mse(test_prediction, Ytest)

   train_scores[k,d] = train_score

   test_scores[k,d] = test_score

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

for d in xrange(MAX_POLY):

  for k in xrange(NUM_DATASETS):

   plt.plot(x_axis, prediction_curves[:,k,d], color=’green’, alpha=0.5)

  plt.plot(x_axis,prediction_curves[:,:,d].mean(axis=1),color=’blue’, linewidth=2.0)

 plt.title(“All curves for degree = %d” % (d+1))

  plt.show()

Следующее– вычисление квадрата смещения и дисперсии, чтобы впоследствии их тоже вывестина экран. Сначала смещение:

avg_train_prediction = np.zeros((Ntrain, MAX_POLY))

squared_bias = np.zeros(MAX_POLY)

f_Xtrain = f_X[:Ntrain]

for d in xrange(MAX_POLY):

  for i in xrange(Ntrain):

   avg_train_prediction[i,d] = train_predictions[i,:,d].mean()

 squared_bias[d] = ((avg_train_prediction[:,d] – f_Xtrain)**2).mean()

Теперь вычисляем дисперсию.

variances = np.zeros((Ntrain, MAX_POLY))

for d in xrange(MAX_POLY):

  for i in xrange(Ntrain):

    delta =train_predictions[i,:,d] – avg_train_prediction[i,d]

   variances[i,d] = delta.dot(delta) / N

variance = variances.mean(axis=0)

Имеясмещение и дисперсию, можем построить их графики.

degrees = np.arange(MAX_POLY) + 1

best_degree = np.argmin(test_scores.mean(axis=0)) + 1

plt.plot(degrees, squared_bias, label=’squared bias’)

plt.plot(degrees, variance, label=’variance’)

plt.plot(degrees, test_scores.mean(axis=0), label=’test scores’)

plt.plot(degrees, squared_bias + variance, label=’squared bias + variance’)

plt.axvline(x=best_degree,linestyle=’–‘, label=’best complexity’)

plt.legend()

plt.show()

plt.plot(degrees, train_scores.mean(axis=0), label=’train scores’)

plt.plot(degrees, test_scores.mean(axis=0), label=’test scores’)

plt.axvline(x=best_degree,linestyle=’–‘, label=’bestcomplexity’)

plt.legend()

plt.show()

Итак,запустим и посмотрим, что у нас получится.

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

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

И последнее – график с результатами учебного и проверочного наборов. На нём также хорошо видна лучшая сложность.

Метод k-ближайших соседей и дерево решений

На этой лекции мы проведём небольшую демонстрацию того, как выглядят высокое смещение с малой дисперсией и малое смещение с высокой дисперсией на примере двух наших любимых моделей – методе k-ближайших соседей и дереве решений. Мы рассмотрим две разные ситуации: сначала регрессию, а потом классификацию. Если вы не хотите писать код сами, то соответствующий файл в репозитарии называется knn_dt_demo.py.

Дляэтой демонстрации я просто вставлю код в окно IPython,поскольку значительная его часть уже написана.

Итак,вставляем все необходимые импорты.

import numpy as np

import matplotlib.pyplot as plt

from sklearn.tree import DecisionTreeRegressor,DecisionTreeClassifier

from sklearn.neighbors import KNeighborsRegressor,KNeighborsClassifier

from sklearn.utils import shuffle

Вставляем значения: N = 20, Ntrain = 12:

N = 20

Ntrain = 12

Изатем создаём и перетасовываем данные. Skikit-learn всегда принимает данные в формате NxD, а исходные данные у нас одномерные, поэтомунеобходимо ещё преобразовать их в двухчерную форму размерности Nx1. Y у нас равно просто sin 3x:

X = np.linspace(0, 2*np.pi, N).reshape(N,1)

Y = np.sin(3*X)

X, Y = shuffle(X, Y)

Далееполучаем Xtrain и Ytrain.

Xtrain = X[:Ntrain]

Ytrain = Y[:Ntrain]

Теперьсоздадим дерево решений. По умолчанию параметр max_depth равен None, что значит, что дерево будет иметь максимальновозможную глубину.

model = DecisionTreeRegressor()

model.fit(Xtrain, Ytrain)

Крометого, несколько сгладим график.

T = 50

Xaxis = np.linspace(0, 2*np.pi, T)

Yaxis = np.sin(3*Xaxis)

Ивыведем получившееся на экран.

plt.scatter(Xtrain, Ytrain, s=50, alpha=0.7, c=’blue’)

plt.scatter(Xtrain,model.predict(Xtrain.reshape(Ntrain, 1)), s=50,alpha=0.7, c=’green’)

plt.show()

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

Теперьпопробуем дерево решений с высоким смещением и малой дисперсией. Установимпараметр max_depth равным 1 и выведем график ещё раз.

model = DecisionTreeRegressor(max_depth=1)

model.fit(Xtrain, Ytrain)

Можемвидеть, что все прогнозы крайне неточны – они представлены зелёными точками;синие же представляют учебные данные. Следовательно, высокое смещение означаеточень простую модель.

Следующиму нас будет метод k-ближайших соседей с малым смещением и высокойдисперсией. В случае метода k-ближайших соседей это значит, что k = 1.

model = KNeighborsRegressor(n_neighbors=1)

model.fit(Xtrain, Ytrain)

plt.scatter(Xtrain, Ytrain, s=50, alpha=0.7, c=’blue’)

plt.scatter(Xtrain,model.predict(Xtrain.reshape(Ntrain, 1)), s=50,alpha=0.7, c=’green’)

plt.plot(Xaxis, Yaxis)

plt.plot(Xaxis, model.predict(Xaxis.reshape(T, 1)))

plt.show()

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

Теперьпопробуем метод k-ближайшихсоседей с высоким смещением и малой дисперсией. Это когда количество соседейдостаточно велико.

model = KNeighborsRegressor(n_neighbors=10)

model.fit(Xtrain, Ytrain)

plt.scatter(Xtrain, Ytrain, s=50, alpha=0.7, c=’blue’)

plt.scatter(Xtrain,model.predict(Xtrain.reshape(Ntrain, 1)), s=50,alpha=0.7, c=’green’)

plt.plot(Xaxis, Yaxis)

plt.plot(Xaxis, model.predict(Xaxis.reshape(T, 1)))

plt.show()

Иполучаем ситуацию, аналогичную случаю с деревом решений, когда прогноз весьмапохож на прямую. В обоих случаях – и методе k-ближайшихсоседей, и дереве решений – оказывается, что большая сложность лучше подходитдля данного конкретного набора данных.

Перейдёмтеперь к классификации. Сгенерируем наши данные – два гауссова облака сдисперсией 1 и центрами в (1, 1) и (-1, -1):

N = 100

D = 2

X = np.random.randn(N, D)

X[:N/2] += np.array([1, 1]) # center it at (1,1)

X[N/2:] += np.array([-1, -1]) # center it at (-1, -1)

Y = np.array([0]*(N/2) + [1]*(N/2))

Отобразимнаши данные:

plt.scatter(X[:,0], X[:,1], s=50, c=Y, alpha=0.7)

plt.show()

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

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

def plot_decision_boundary(X, model):

  h = .02  # step size in the mesh

  # create amesh to plot in

  x_min, x_max =X[:, 0].min() – 1, X[:, 0].max() + 1

  y_min, y_max =X[:, 1].min() – 1, X[:, 1].max() + 1

  xx, yy = np.meshgrid(np.arange(x_min,x_max, h),

                      np.arange(y_min, y_max, h))

  # Plot thedecision boundary. For that, we will assign a color to each

  # point in themesh [x_min, m_max]x[y_min, y_max].

  Z =model.predict(np.c_[xx.ravel(), yy.ravel()])

  # Put theresult into a color plot

  Z =Z.reshape(xx.shape)

 plt.contour(xx, yy, Z, cmap=plt.cm.Paired)

model = DecisionTreeClassifier()

model.fit(X, Y)

plt.scatter(X[:,0], X[:,1], s=50, c=Y, alpha=0.7)

plot_decision_boundary(X, model)

plt.show()

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

Наочереди дерево решений с высоким смещением и малой дисперсией. Установиммаксимальную глубину дерева равной 2.

model = DecisionTreeClassifier(max_depth=2)

model.fit(X, Y)

plt.scatter(X[:,0], X[:,1], s=50, c=Y, alpha=0.7)

plot_decision_boundary(X, model)

plt.show()

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

Теперьметод k-ближайшихсоседей с малым смещением и высокой дисперсией.

model = KNeighborsClassifier(n_neighbors=1)

model.fit(X, Y)

plt.scatter(X[:,0], X[:,1], s=50, c=Y, alpha=0.7)

plot_decision_boundary(X, model)

plt.show()

Ивновь-таки разграничительная линия кажется чересчур сложной.

Инаконец, метод k-ближайшихсоседей с малым смещением и высокой дисперсией, когда k велико.

model = KNeighborsClassifier(n_neighbors=20)

model.fit(X, Y)

plt.scatter(X[:,0], X[:,1], s=50, c=Y, alpha=0.7)

plot_decision_boundary(X, model)

plt.show()

Это даёт нам куда лучшее разграничение.

Перекрёстная проверка как метод оптимизации сложности модели

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

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

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

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

Разберёмся,как работает перекрёстная проверка. Суть K-блочной перекрёстной проверкипроста. Мы просто разбиваем наши учебные данные на K частей и делаем цикл,повторяющийся K раз. При этом каждый раз берётся одна из частей и используетсяв качестве проверочного набора, тогда как остальные части данных используются вкачестве учебного. Так, при первом проходе цикла мы проверяем результат напервой части, а обучаемся на оставшихся, на втором проходе – проверяемрезультат на второй части, а на остальных обучаемся. Надеюсь, суть очевидна.Вот псевдокод, который это делает:

scores= []

sz= N / K

for i in xrange(K):

    Xvalid,Yvalid = X[ i*sz: (i+1)*sz ], Y[ i*sz: (i+1)*sz ]

    Xtrain,Ytrain = concat(X[0 : i*sz], X[(i+1)*sz:N]), concat(Y[0 : i*sz], Y[(i+1)*sz:N])

   model.fit(Xtrain, Ytrain)

   scores.append(model.score(Xvalid, Yvalid))

returnscores

Мыинициируем оценки для пустого списка, находим размер каждый части путём деленияN на K и выполняем цикл for K раз, получая проверочные блоки X и Y, а такжеучебные X и Y, которые состоят из всего, что не входит в проверочные X и Y.Таким образом, этот алгоритм возвращает K разных оценок, и можно использоватьпросто их среднее значение, чтобы оценить, насколько хорошим является данноеконкретное значение гиперпараметра.

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

Обратите внимание, что библиотека Sci-Kit Learn включает в себя реализацию K-блочной перекрёстной проверки, которую следует использовать при написании реального кода. Правда, реализация в версии Sci-Kit Learn требует соответствия определённым тонкостям API этой библиотеки, о которых вы можете и не знать, а потому это может не сработать, если вставить её прямо в код. Так, например, необходимо обеспечить класс со списком из трёх методов – fit, predict и score, но есть и другие необходимые вещи. Поэтому создавая собственную модель, вам, вероятно, будет проще сделать и собственную версию перекрёстной проверки.

Одинспециальный случай K-блочнойперекрёстной проверки встретится нам в этом курсе чуть позже. Это такназываемая перекрёстная проверка с исключением, состоящая в том, чтобыустановить K = N. Другимисловами, мы запускаем цикл N раз, причёмкаждый раз обучаясь на всех примерах, кроме одного, а проверяемся на этом одномисключении. Если же не считать перекрёстной проверки с исключением, то, какправило, значение K устанавливаетсяравным 5, 8 или 10.

Понравилась статья? Поделить с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: