Классификация методом k ближайших соседей

Две основные разновидности машинного обучения:

  • машинное обучение с учителем, которое работает с помеченными данными;
  • машинное обучение без учителя, которое работает с непомеченными данными.

alt text

источник

Машинное обучение с учителем

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

Классификация

Для анализа набора данных Digits, включенного в поставку scikit-learn, будет применен один из простейших классификационных алгоритмов — метод k ближайших соседей.

Классификационные алгоритмы прогнозируют дискретные классы (категории), к которым относятся точки данных. При бинарной классификации используются два класса: например, «спам» или «не спам» в приложении классификации электронной почты. В задачах множественной классификации используется более двух классов — например, 10 классов (от 0 до 9) в наборе данных Digits. Схема классификации для описаний фильмов может пытаться классифицировать их по жанру: «приключения», «фэнтези», «романтика», «исторический» и т. д.

Регрессия

Регрессионные модели прогнозируют непрерывный вывод — например, прогнозируемую температуру в анализе временных рядов. Далее рассмотрим пример простой линейной регрессии, но реализуем его с использованием оценщика LinearRegression из scikit-learn.

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

Машинное обучение без учителя

Воспользуемся методом снижения размерности признакового пространства (с применением оценщика scikit-learn TSNE) для сжатия 64 признаков набора данных Digits до двух в целях визуализации. Это позволит увидеть, как хорошо «группируются» данные Digits — наборы данных с рукописными цифрами наподобие тех, что должны распознаваться компьютерами в почтовых отделениях для отправки писем по указанным почтовым индексам. Речь идет о сложной задаче из области распознавания образов, если учесть неповторимость человеческого почерка.

Кластеризация методом k средних и набор данных Iris

Представим простейший алгоритм машинного обучения без учителя — кластеризацию методом k средних и воспользуемся им для набора данных Iris, также включенного в поставку scikit-learn. Снижение размерности признакового пространства (с оценщиком PCA из scikit-learn) сжимает четыре признака набора данных Iris до двух с целью визуализации. Будет продемонстрирована кластеризация трех образцов Iris по набору данных и графическое представление центроида каждого кластера (то есть центральной точки кластера). Применим несколько оценщиков кластеризации для сравнения их эффективности по разбиению точек набора данных Iris на три кластера.

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

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

Наборы данных, включенные в поставку scikit-learn

«Игрушечные» наборы данных - Цены на дома в Бостоне - Ирисы - Диабет - Оптическое распознавание рукописных цифр - Linnerrud - Распознавание вин - Диагностика рака груди (Висконсин)

«Реальные» наборы данных - Лица Оливетти - Тексты 20 новостных групп - Помеченные лица для распознавания - Типы лесопосадок - RCV1 - Kidcup 99 - California Housing

Подробнее: - https://www.openml.org

Последовательность действий в типичном исследовании data science

  • загрузка набора данных;
  • исследование данных с использованием pandas и визуализаций;
  • преобразование данных (нечисловых данных в числовые, потому что scikit-learn требуются числовые данные);
  • разбиение данных для обучения и тестирования;
  • создание модели;
  • обучение и тестирование модели;
  • настройка параметров модели и оценка ее точности;
  • формирование прогнозов на основании «живых» данных, которые еще неизвестны модели.

alt text

Практический пример: классификация методом k ближайших соседей и набор данных Digits, часть 1

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

Задачи классификации

Рассмотрим задачу классификации в области машинного обучения с учителем, где требуется спрогнозировать класс, к которому относится образец. Например, если у вас имеются изображения собак и кошек, то каждое изображение должно классифицироваться как «собака» или «кошка».

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

Воспользуемся набором данных Digits, входящим в поставку scikit-learn.

Набор состоит из изображений 8 × 8 пикселов и представляет 1797 рукописных цифр (от 0 до 9). Требуется определить, какую цифру представляет изображение.

Так как существует 10 возможных цифр (классов), данная задача является задачей множественной классификации. Для обучения модели используются помеченные данные — класс каждой цифры известен заранее. В этом примере для распознавания рукописных цифр будет применен один из простейших алгоритмов классификации — метод k ближайших соседей (k-NN).

Следующая визуализация цифры 5 в низком разрешении была получена в результате вывода одной цифры в виде матрицы 8 × 8:

alt text

Начнем с основных этапов реализации задач машинного обучения: * Выбор данных для обучения модели. * Загрузка и анализ данных. * Разбиение данных для обучения и тестирования. * Выбор и построение модели. * Обучение модели. * Формирование прогнозов.

Далее: * проведем оценку результатов; * настроим параметры модели; * обработаем несколько классификационных моделей для выбора наилучшей модели(-ей).

Для визуализации данных будут использоваться библиотеки Matplotlib и Seaborn.

Алгоритм k ближайших соседей

Scikit-learn поддерживает много алгоритмов классификации, включая простейший алгоритм k ближайших соседей (k-NN). Этот алгоритм пытается спрогнозировать класс тестового образца, анализируя k обучающих образцов, расположенных ближе всего (по расстоянию) к тестовому образцу.

Для примера возьмем следующую диаграмму, на которой заполненные точки представляют четыре класса — A, B, C и D. В контексте нашего обсуждения эти буквы будут использоваться как имена классов:

alt text

Требуется спрогнозировать классы, к которым принадлежат новые образцы X, Y и Z. Будем считать, что прогнозы должны формироваться по трем ближайшим соседям каждого образца — k равно 3 в алгоритме k ближайших соседей: * Все три ближайших соседа образца X являются точками класса D, поэтому модель прогнозирует, что X относится к классу D. * Все три ближайших соседа образца Y являются точками класса B, поэтому модель прогнозирует, что Y относится к классу B. * Для Z выбор не очевиден, потому что образец находится между точками B и C. Из трех ближайших соседей один принадлежит классу B, а два — классу C. В алгоритме k ближайших соседей побеждает класс с большинством «голосов». Из-за двух голосов C против одного голоса B мы прогнозируем, что Z относится к классу C. Выбор нечетного значения k в алгоритме k-NN предотвращает «ничьи» и гарантирует, что количество голосов никогда не будет равным.

Гиперпараметры и настройка гиперпараметров

В области машинного обучения модель реализует алгоритм машинного обучения. В терминологии scikit-learn модели называются оценщиками. Существуют два типа параметров машинного обучения: * вычисляемые оценщиком в ходе своего обучения на основании предоставленных вами данных; * задаваемые заранее при создании объекта оценщика scikit-learn, представляющего модель.

Параметры, задаваемые заранее, называются гиперпараметрами.

В алгоритме k ближайших соседей k является гиперпараметром. Для простоты мы используем значения гиперпараметров по умолчанию для scikit-learn. В реальном исследовании из области машинного обучения желательно поэкспериментировать с разными значениями k для получения наилучших возможных моделей для ваших исследований. Этот процесс называется настройкой гиперпараметров. Позднее мы используем настройку гиперпараметров для выбора значения k, которое позволяет алгоритму k ближайших соседей выдать лучшие прогнозы для набора данных Digits. Scikit-learn также содержит средства автоматической настройки гиперпараметров.

Загрузка набора данных

Функция load_digits из модуля sklearn.datasets возвращает объект scikit-learn Bunch, содержащий данные цифр и информацию о наборе данных Digits (так называемые метаданные):

from sklearn.datasets import load_digits
digits = load_digits()

Bunch представляет собой подкласс dict, содержащий дополнительные атрибуты для взаимодействия с набором данных.

Вывод описания

Набор данных Digits, входящий в поставку scikit-learn, является подмножеством набора данных рукописных цифр UCI (Калифорнийский университет в Ирвайне) ML.

Исходный набор данных UCI содержит 5620 образцов — 3823 для обучения и 1797 для тестирования. Версия набора данных, поставляемая со scikit-learn, содержит только 1797 тестовых образцов. Атрибут DESCR объекта Bunch содержит описание набора данных. Согласно описанию набора данных Digits, каждый образец содержит 64 признака (Number of Attributes), представляющие изображение 8 × 8 со значениями пикселов в диапазоне 0–16 (Attribute Information). Набор данных не содержит отсутствующих значений (Missing Attribute Values). Создается впечатление, что 64 признака — это много, но необходимо иметь в виду, что реальные наборы данных иногда содержат сотни, тысячи и даже миллионы признаков.

print(digits.DESCR)
.. _digits_dataset:

Optical recognition of handwritten digits dataset
--------------------------------------------------

**Data Set Characteristics:**

    :Number of Instances: 5620
    :Number of Attributes: 64
    :Attribute Information: 8x8 image of integer pixels in the range 0..16.
    :Missing Attribute Values: None
    :Creator: E. Alpaydin (alpaydin '@' boun.edu.tr)
    :Date: July; 1998

This is a copy of the test set of the UCI ML hand-written digits datasets
https://archive.ics.uci.edu/ml/datasets/Optical+Recognition+of+Handwritten+Digits

The data set contains images of hand-written digits: 10 classes where
each class refers to a digit.

Preprocessing programs made available by NIST were used to extract
normalized bitmaps of handwritten digits from a preprinted form. From a
total of 43 people, 30 contributed to the training set and different 13
to the test set. 32x32 bitmaps are divided into nonoverlapping blocks of
4x4 and the number of on pixels are counted in each block. This generates
an input matrix of 8x8 where each element is an integer in the range
0..16. This reduces dimensionality and gives invariance to small
distortions.

For info on NIST preprocessing routines, see M. D. Garris, J. L. Blue, G.
T. Candela, D. L. Dimmick, J. Geist, P. J. Grother, S. A. Janet, and C.
L. Wilson, NIST Form-Based Handprint Recognition System, NISTIR 5469,
1994.

.. topic:: References

  - C. Kaynak (1995) Methods of Combining Multiple Classifiers and Their
    Applications to Handwritten Digit Recognition, MSc Thesis, Institute of
    Graduate Studies in Science and Engineering, Bogazici University.
  - E. Alpaydin, C. Kaynak (1998) Cascading Classifiers, Kybernetika.
  - Ken Tang and Ponnuthurai N. Suganthan and Xi Yao and A. Kai Qin.
    Linear dimensionalityreduction using relevance weighted LDA. School of
    Electrical and Electronic Engineering Nanyang Technological University.
    2005.
  - Claudio Gentile. A New Approximate Maximal Margin Classification
    Algorithm. NIPS. 2000.

Проверка атрибутов data и target

Атрибуты data и target объекта Bunch представляют собой массивы NumPy: * Массив data содержит 1797 образца (изображения цифр), каждый из которых несет 64 признака со значениями в диапазоне 0–16, представляющие интенсивности пикселов. С Matplotlib можно визуализировать интенсивности в оттенках серого от белого (0) до черного (16):

alt text
  • Массив target содержит метки изображений, то есть классы, указывающие, какую цифру представляет каждое изображение. Массив называется target, потому что при прогнозировании вы стремитесь «попасть в цель» с выбором значений. Чтобы увидеть метки образцов в наборе данных, выведем значения target каждого 100-го образца:
digits.target[::100]
array([0, 4, 1, 7, 4, 8, 2, 2, 4, 4, 1, 9, 7, 3, 2, 1, 2, 5])

Количество образцов и признаков (на один образец) подтверждается при помощи атрибута shape массива data, который показывает, что набор данных состоит из 1797 строк (образцов) и 64 столбцов (признаков):

digits.data.shape
(1797, 64)

Размеры массива target подтверждают, что количество целевых значений соответствует количеству образцов:

digits.target.shape
(1797,)

Пример изображения цифры

Все изображения двумерны — они обладают шириной и высотой в пикселах.

Объект Bunch, возвращаемый load_digits, содержит атрибут images — массив, каждый элемент которого представляет собой двумерный массив 8 × 8 с интенсивностями пикселов изображения цифры. Хотя в исходном наборе данных каждый пиксел представлен целочисленным значением в диапазоне 0–16, scikit-learn хранит эти значения в виде значений с плавающей точкой (тип NumPy float64). Например, двумерный массив, представляющий изображение образца с индексом 13, выглядит так:

digits.images[13]
array([[ 0.,  2.,  9., 15., 14.,  9.,  3.,  0.],
       [ 0.,  4., 13.,  8.,  9., 16.,  8.,  0.],
       [ 0.,  0.,  0.,  6., 14., 15.,  3.,  0.],
       [ 0.,  0.,  0., 11., 14.,  2.,  0.,  0.],
       [ 0.,  0.,  0.,  2., 15., 11.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  2., 15.,  4.,  0.],
       [ 0.,  1.,  5.,  6., 13., 16.,  6.,  0.],
       [ 0.,  2., 12., 12., 13., 11.,  0.,  0.]])

Ниже показано изображение, представленное этим двумерным массивом:

alt text

Подготовка данных для использования в scikit-learn

Алгоритмы машинного обучения scikit-learn требуют, чтобы образцы хранились в двумерном массиве значений с плавающей точкой (или коллекции, сходной с двумерным массивом, например списком списков или коллекцией pandas.DataFrame): * каждая строка представляет один образец; * каждый столбец заданной строки представляет один признак этого образца.

Для представления каждого образца в виде одной строки данных многомерные данные должны быть преобразованы в одномерный массив.

Если вы работаете с данными, содержащими категорийные признаки (обычно представленные в виде строк — скажем, ‘spam’ и ‘not-spam’), то вам также придется провести предварительную обработку этих признаков и преобразовать их в числовые значения (так называемое прямое унитарное кодирование). Модуль sklearn.preprocessing библиотеки Scikit-learn предоставляет функциональность для преобразования категорийных данных в числовые. Набор данных Digits не содержит категорийных признаков.

Функция load_digits возвращает предварительно обработанные данные, готовые к машинному обучению. Набор данных Digits является числовым, поэтому load_digits просто сглаживает двумерный массив в одномерный массив. Например, массив 8 × 8 digits.images[13] соответствует массиву 1 × 64 digits.data[13] следующего вида:

digits.data[13]
array([ 0.,  2.,  9., 15., 14.,  9.,  3.,  0.,  0.,  4., 13.,  8.,  9.,
       16.,  8.,  0.,  0.,  0.,  0.,  6., 14., 15.,  3.,  0.,  0.,  0.,
        0., 11., 14.,  2.,  0.,  0.,  0.,  0.,  0.,  2., 15., 11.,  0.,
        0.,  0.,  0.,  0.,  0.,  2., 15.,  4.,  0.,  0.,  1.,  5.,  6.,
       13., 16.,  6.,  0.,  0.,  2., 12., 12., 13., 11.,  0.,  0.])

В этом одномерном массиве первые восемь элементов содержат элементы строки 0 двумерного массива, следующие восемь элементов — элементы строки 1 двумерного массива, и т. д.

Визуализация данных

Всегда старайтесь поближе познакомиться со своими данными. Этот процесс называется исследованием данных. Например, изображения цифр (с тем чтобы составить представление об их внешнем виде) можно просто вывести функцией implot библиотеки Matplotlib.

Чтобы понять, насколько трудна задача распознавания рукописных цифр, посмотрите, как сильно различаются изображения цифры 3 в первой, третьей и четвертой строке, и взгляните на изображения цифры 2 в первой, третьей и четвертой строке.

alt text

Следующий вызов функции subplots создает эту диаграмму 6 × 4 (размер задается ключевым аргументом figsize(6, 4)), которая состоит из 24 поддиаграмм, расположенных в 4 строки (nrows=4) и 6 столбцов (ncols=6). Каждая поддиаграмма имеет собственный объект Axes, который используется для вывода одного изображения цифры:

import matplotlib.pyplot as plt
figure, axes = plt.subplots(nrows=4,
                            ncols=6,
                            figsize=(6, 4))

Функция subplots возвращает объекты Axes в двумерном массиве NumPy.

Затем цикл for в сочетании со встроенной функцией zip будет использоваться для параллельного перебора всех 24 объектов Axes, первых 24 изображений в digits.images и первых 24 значений в digits.target:

figure, axes = plt.subplots(nrows=4,
                            ncols=6,
                            figsize=(6, 4))

for item in zip(axes.ravel(), digits.images, digits.target):
    axes, image, target = item
    axes.imshow(image, cmap=plt.cm.gray_r)
    axes.set_xticks([]) # Удаление делений на оси x
    axes.set_yticks([]) # Удаление делений на оси y
    axes.set_title(target)
plt.tight_layout()

Напомним, метод массивов NumPy ravel создает одномерное представление многомерного массива, а функция zip — кортежи, содержащие элементы всех аргументов zip с одинаковыми индексами. Аргумент с наименьшим количеством элементов определяет количество возвращаемых кортежей. Каждая итерация цикла: * распаковывает один кортеж на три переменные, представляющие объект Axes , изображение и целевое значение; * вызывает метод imshow объекта Axes для вывода одного изображения. Ключевой аргумент cmap=plt.cm.gray_r определяет цвета, выводимые в изображении. Значение plt.cm.gray_r представляет собой цветовую карту — группу часто выбираемых цветов, хорошо сочетающихся друг с другом. С этой конкретной цветовой картой пикселы изображения выводятся в оттенках серого: 0 соответствует белому цвету, 16 — черному, а промежуточные значения — оттенкам серого с возрастанием темного. Названия цветовых карт Matplotlib приведены на странице. К ним можно обращаться через объект plt.cm bkb или в строковом виде gray_r; * вызывает методы set_xticks и set_yticks объекта Axes с пустыми списками, чтобы указать, что оси x и y должны выводиться без делений; * вызывает метод set_title объекта Axes для вывода целевого значения над изображением, то есть фактического значения, представляемого изображением.

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

Разбиение данных для обучения и тестирования

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

Сначала данные разбиваются на два поднабора: обучающий и тестовый.

Функция train_test_split из модуля sklearn.model_selection осуществляет случайную перестановку данных, а затем разбивает образцы в массиве data и целевые значения в массиве target на обучающий и тестовый набор. Это гарантирует, что обучающий и тестовый наборы обладают сходными характеристиками. Случайная перестановка и разбиение выполняются объектом ShuffleSplit из модуля sklearn.model_selection. Функция train_test_split возвращает кортеж из четырех элементов, в котором два первых элемента содержат образцы, разделенные на обучающий и тестовый набор, а два последних — соответствующие целевые значения, также разделенные на обучающий и тестовый набор.

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

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(digits.data, digits.target, random_state=11)

Предполагается, что классы данных сбалансированы, то есть образцы равномерно распределены между классами. К слову, все классификационные наборы, входящие в поставку scikit-learn, обладают этим свойством. Несбалансированность классов может привести к ошибочным результатам.

Функция train_test_split предоставляет ключевой аргумент random_state для воспроизводимости результатов. Если в будущем тот же код будет выполняться с тем же значением инициализации, train_test_split выберет те же данные для обучающего и тестового наборов. Значение инициализации в нашем примере (11) было выбрано произвольно.

Размеры обучающего и тестового наборов

Взглянув на размеры наборов X_train и X_test, мы видим, что по умолчанию train_test_split резервирует 75% данных для обучения и 25% для тестирования:

X_train.shape
(1347, 64)
X_test.shape
(450, 64)

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

Используйте значения с плавающей точкой в диапазоне от 0.0 до 1.0 для определения процентной доли каждого набора в данных. Целочисленные значения задают точное количество образцов. Если один из этих ключевых аргументов задается при вызове, то второй вычисляется автоматически. Например, команда

X_train, X_test, y_train, y_test = train_test_split(
digits.data, digits.target, random_state=11, test_size=0.20)

сообщает, что 20% данных предназначены для тестирования, поэтому значение train_size вычисляется равным 0.80.

Создание модели

Оценщик KNeighborsClassifier (модуль sklearn.neighbors) реализует алгоритм k ближайших соседей.

Сначала создается объект оценщика KNeighborsClassifier:

from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()

Чтобы создать оценщика, достаточно создать объект.

Обучение модели

Затем вызывается метод fit объекта KNeighborsClassifier, который загружает обучающий набор образцов (X_train) и обучающий набор целевых значений (y_train) в оценщике:

knn.fit(X=X_train, y=y_train)
KNeighborsClassifier()

Для большинства оценщиков scikit-learn метод fit загружает данные в оценщика, а затем использует эти данные для выполнения «за кулисами» сложных вычислений, в ходе которых происходит извлечение информации и обучение модели. Метод fit объекта KNeighborsClassifier просто загружает данные в оценщике, потому что алгоритм k-NN не имеет исходного процесса обучения.

Данный оценщик называется отложенным, потому что он выполняет свою работу только тогда, когда он используется для построения прогнозов. В реальных приложениях обучение моделей может занимать минуты, часы, дни и даже месяцы, но специализированное высокопроизводительное оборудование — графические процессоры (GPU) и тензорные процессоры (TPU) — могут значительно сократить время обучения модели.

Как видно из вывода фрагмента, метод fit возвращает оценщика, поэтому выводится его строковое представление, включающее настройки по умолчанию. Значение n_neighbors соответствует k в алгоритме k ближайших соседей. По умолчанию KNeighborsClassifier ищет пятерых ближайших соседей для построения своих прогнозов. Для простоты мы используем оценки оценщика по умолчанию. Для KNeighborsClassifier они описаны по адресу.

Прогнозирование классов для рукописных цифр

Итак, после загрузки данных в KNeighborsClassifier эти данные могут использоваться с тестовыми образцами для построения прогнозов. При вызове метода predict оценщика с передачей X_test в аргументе возвращает массив, содержащий прогнозируемый класс каждого тестового изображения:

predicted = knn.predict(X=X_test)
expected = y_test

Сравним прогнозируемые цифры с ожидаемыми для первых 20 тестовых образцов:

predicted[:20]
array([0, 4, 9, 9, 3, 1, 4, 1, 5, 0, 4, 9, 4, 1, 5, 3, 3, 8, 5, 6])
expected[:20]
array([0, 4, 9, 9, 3, 1, 4, 1, 5, 0, 4, 9, 4, 1, 5, 3, 3, 8, 3, 6])

Как видим, среди первых 20 элементов массивов predicted и expected не совпадают только значения с индексом 18. Здесь ожидалась цифра 3, но модель предсказала 5.

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

wrong = [(p, e) for (p, e) in zip(predicted, expected) if p != e]
wrong
[(5, 3),
 (8, 9),
 (4, 9),
 (7, 3),
 (7, 4),
 (2, 8),
 (9, 8),
 (3, 8),
 (3, 8),
 (1, 8)]

Трансформация списка использует zip для создания кортежей, содержащих соответствующие элементы predicted и expected. Кортеж включается в результат только в том случае, если его значение p (прогнозируемое значение) и e (ожидаемое значение) различны, то есть спрогнозированное значение было неправильным. В этом примере оценщик неправильно спрогнозировал только 10 из 450 тестовых образцов. Таким образом, точность прогнозирования для этого оценщика составила впечатляющую величину 97,78% даже при том, что мы использовали только параметры оценщика по умолчанию.

Практический пример: классификация методом k ближайших соседей и набор данных Digits, часть 2

Сделаем следующее: * оценим точность оценщика для классификации методом k-NN; * выполним несколько оценщиков и сравним их результаты для выбора наилучшего варианта(-ов); * продемонстрируем настройку гиперпараметра k метода k-NN для достижения оптимальной эффективности KNeighborsClassifier.

Метод score оценщика

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

print(f'{knn.score(X_test, y_test):.2%}')
97.78%

Оценщик kNeighborsClassifier со своим значением k по умолчанию (то есть n_neighbors=5) достигает точности прогнозирования 97,78%. Вскоре мы проведем настройку гиперпараметра, чтобы попытаться определить оптимальное значение k и добиться еще более высокой точности.

Матрица несоответствий

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

Вызовите функцию confusion_matrix из модуля sklearn.metrics и передайте в аргументах классы expected и predicted:

from sklearn.metrics import confusion_matrix
confusion = confusion_matrix(y_true=expected, y_pred=predicted)

Ключевой аргумент y_true задает фактические классы тестовых образцов. Люди просмотрели изображения в наборе данных и пометили их конкретными классами (цифры). Ключевой аргумент y_pred определяет прогнозируемые цифры для этих тестовых изображений.

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

confusion
array([[45,  0,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0, 45,  0,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0, 54,  0,  0,  0,  0,  0,  0,  0],
       [ 0,  0,  0, 42,  0,  1,  0,  1,  0,  0],
       [ 0,  0,  0,  0, 49,  0,  0,  1,  0,  0],
       [ 0,  0,  0,  0,  0, 38,  0,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0, 42,  0,  0,  0],
       [ 0,  0,  0,  0,  0,  0,  0, 45,  0,  0],
       [ 0,  1,  1,  2,  0,  0,  0,  0, 39,  1],
       [ 0,  0,  0,  0,  1,  0,  0,  0,  1, 41]])

Каждая строка представляет один класс, то есть одну из цифр от 0 до 9.

Столбцы обозначают количество тестовых образцов, классифицированных в соответствующий класс. Например, строка 0 представляет класс цифры 0. Столбцы представляют 10 возможных целевых классов 0–9. Так как мы работаем с цифрами, классы (0–9) и индексы строк и столбцов (0–9) совпадают. По данным строки, 0, 45 тестового образца были классифицированы как цифра 0, но ни один из тестовых образцов не был ошибочно классифицирован как одна из цифр 1–9. Таким образом, все 100% цифр 0 были спрогнозированы правильно.

Теперь возьмем строку 8, представляющую результат для цифры 8:

[0, 1, 1, 2, 0, 0, 0, 0, 39, 1]
  • 1 в столбце с индексом 1 означает, что одна цифра 8 была неправильно классифицирована как 1.
  • 1 в столбце с индексом 2 означает, что одна цифра 8 была неправильно классифицирована как 2.
  • 2 в столбце с индексом 3 означает, что две цифры 8 были неправильно классифицированы как 3.
  • 39 в столбце с индексом 8 означает, что 39 цифр 8 были правильно классифицированы как 8.
  • 1 в столбце с индексом 9 означает, что одна цифра 8 была неправильно классифицирована как 9.

Таким образом, алгоритм правильно спрогнозировал 88,63% (39 из 44) всех цифр 8. Позднее было показано, что общая точность прогнозирования этого оценщика составляла 97,78%. Более низкая точность прогнозирования для цифры 8 означает, что она из-за своей формы труднее распознается, чем другие цифры.

Отчет по классификации

Модуль sklearn.metrics также предоставляет функцию classification_report, которая выводит таблицу метрик классификации, основанных на ожидаемых и прогнозируемых значениях:

from sklearn.metrics import classification_report
names = [str(digit) for digit in digits.target_names]
print(classification_report(expected, predicted, target_names=names))
              precision    recall  f1-score   support

           0       1.00      1.00      1.00        45
           1       0.98      1.00      0.99        45
           2       0.98      1.00      0.99        54
           3       0.95      0.95      0.95        44
           4       0.98      0.98      0.98        50
           5       0.97      1.00      0.99        38
           6       1.00      1.00      1.00        42
           7       0.96      1.00      0.98        45
           8       0.97      0.89      0.93        44
           9       0.98      0.95      0.96        43

    accuracy                           0.98       450
   macro avg       0.98      0.98      0.98       450
weighted avg       0.98      0.98      0.98       450

В этом отчете:

  • precision — точность, то есть общее количество точных прогнозов для заданной цифры, разделенное на общее количество прогнозов для этой цифры. Точность можно проверить по столбцам матрицы несоответствий. Например, взглянув на столбец с индексом 7, вы увидите значение 1 в строках 3 и 4: это означает, что одна цифра 3 и одна цифра 4 были ошибочно классифицированы как 7. Значение 45 в строке 7 показывает, что 45 изображений были правильно классифицированы как 7. Таким образом, точность для цифры 7 составляет 45/47, или 0,96;
  • recall — отклик, то есть общее количество правильных прогнозов для заданной цифры, разделенное на общее количество образцов, которые должны были прогнозироваться как эта цифра. Отклик можно проверить по строкам матрицы несоответствий. Например, в строке с индексом 8 встречаются три значения 1 и значение 2; это означает, что некоторые цифры 8 были ошибочно классифицированы как другие цифры, а также значение 39, которое показывает, что 39 изображений были классифицированы правильно. Таким образом, отклик для цифры 8 составляет 39/44, или 0,89;
  • f1-score — среднее значение точности и отклика;
  • support — количество образцов с заданным ожидаемым значением. Например, 50 образцов были снабжены меткой 4, а 38 образцов — меткой 5.

Подробности о средних значениях в нижней части отчета можно найти здесь.

Визуализация матрицы несоответствий

На тепловой карте значения представлены цветами; обычно более высоким значениям соответствуют более интенсивные цвета. Функции построения диаграмм Seaborn работают с двумерными данными. При использовании DataFrame в качестве источника данных Seaborn автоматически помечает свои визуализации по именам столбцов и индексам строк. Преобразуем матрицу несоответствий в коллекцию DataFrame, а затем построим ее визуальное представление:

import pandas as pd
confusion_df = pd.DataFrame(confusion,
                            index=range(10),
                            columns=range(10))
import seaborn as sns
axes = sns.heatmap(confusion_df,
                   annot=True,
                   cmap='nipy_spectral_r')

Функция heatmap библиотеки Seaborn строит тепловую карту по заданной коллекции DataFrame. Ключевой аргумент annot=True (сокращение от «annotation») выводит справа от диаграммы цветную полосу, которая обозначает соответствие между значениями и цветами цветовой карты. Ключевой аргумент cmap='nipy_spectral_r' определяет используемую цветовую карту.

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

K-проходная перекрестная проверка

K-проходная перекрестная проверка позволяет использовать все данные как для обучения, так и для тестирования. Повторное обучение и тестирование модели с разными частями набора данных помогают лучше понять, как модель справляется с прогнозированием для новых данных. Набор данных разбивается на k частей равного размера (параметр k в данном случае никак не связан с k из алгоритма k ближайших соседей). После этого модель повторно обучается на k–1 частях и тестируется на оставшейся части. Для примера возьмем k=10 с нумерацией частей от 1 до 10. Со всеми частями будут выполнены 10 последовательных циклов обучения и тестирования: * Сначала выполняется обучение на частях 1–9, а затем тестирование с частью 10. * Затем выполняется обучение на частях 1–8 и 10, а затем тестирование с частью 9. * Затем выполняется обучение на частях 1–7 и 9–10, а затем тестирование с частью 8.

Цикл обучения и тестирования продолжается до тех пор, пока каждая часть не будет использована для тестирования модели.

Класс KFold

Библиотека scikit-learn предоставляет класс KFold и функцию cross_val_score (из модуля sklearn.model_selection) для выполнения описанных выше циклов обучения и тестирования. Выполним k-проходную перекрестную проверку с набором данных Digits и оценщиком KNeighborsClassifier, созданным ранее. Начнем с создания объекта KFold:

from sklearn.model_selection import KFold
kfold = KFold(n_splits=10, random_state=11, shuffle=True)

Ключевые аргументы: * n_splits=10 — количество частей; * random_state=11 — значение инициализации генератора случайных чисел для обеспечения воспроизводимости результатов; * shuffle=True — объект KFold выполняет случайную перестановку данных перед разбиением их на части. Этот шаг особенно важен, если образцы могут быть сгруппированы или упорядочены. Например, набор данных Iris, который будет использован позднее в этой главе, содержит 150 образцов трех разновидностей ирисов: первые пятьдесят относятся к Iris setosa, следующие пятьдесят — к Iris versicolor, а последние 5 пятьдесят 0 — к Iris virginica. Если не переставить образцы, то может оказаться, что в обучающих данных нет ни одного образца конкретного вида ирисов, а тестовые данные состоят из данных одного вида.

Использование объекта KFold с функцией cross_val_score. Затем воспользуемся функцией cross_val_score для обучения и тестирования модели:

from sklearn.model_selection import cross_val_score
scores = cross_val_score(estimator=knn, X=digits.data, y=digits.target, cv=kfold)

Ключевые аргументы: * estimator=knn — оценщик, который вы хотите проверить; * X=digits.data — образцы, используемые для обучения и тестирования; * y=digits.target — прогнозы целевых значений для образцов; * cv=kfold — генератор перекрестной проверки, определяющий способ разбиения образцов и целевых значений для обучения и тестирования.

Функция cross_val_score возвращает массив показателей точности — по одной для каждой части. Как видно из следующего вывода, модель была достаточно точной. Наименьший показатель точности составил 0,97777778 (97,78%), а в одном случае при прогнозировании всей части была достигнута 100-процентная точность:

scores
array([0.97777778, 0.99444444, 0.98888889, 0.97777778, 0.98888889,
       0.99444444, 0.97777778, 0.98882682, 1.        , 0.98324022])

Располагая частичными показателями точности, можно получить общее представление о точности модели. Для этого можно вычислить средний показатель точности и стандартное отклонение по 10 показателям точности (или другому выбранному вами количеству частей):

print(f'Mean accuracy: {scores.mean():.2%}')
Mean accuracy: 98.72%
print(f'Accuracy standard deviation: {scores.std():.2%}')
Accuracy standard deviation: 0.75%

В среднем модель обеспечивала точность 98,72%, то есть даже больше, чем в предыдущем варианте, когда 75% данных использовалось для обучения, а 25% — для тестирования.

Выполнение нескольких моделей для поиска наилучшей

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

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

alt text

Источник картинки

Воспользуемся методами из предыдущего раздела для сравнения нескольких классификационных оценщиков — KNeighborsClassifier, SVC и GaussianNB (существуют и другие). И хотя оценщики SVC и GaussianNB ранее не описывались, scikit-learn позволяет легко опробовать их с настройками по умолчанию.

Импортируем двух других оценщиков:

from sklearn.svm import SVC
from sklearn.naive_bayes import GaussianNB

Теперь необходимо создать оценщиков. Следующий словарь содержит пары «ключ-значение» для существующего оценщика KNeighborsClassifier, созданного ранее, а также новых оценщиков SVC и GaussianNB:

estimators = {
    'KNeighborsClassifier': knn,
    'SVC': SVC(gamma='scale'),
    'GaussianNB': GaussianNB()}

После этого можно переходить к выполнению модели:

for estimator_name, estimator_object in estimators.items():
    kfold = KFold(n_splits=10, random_state=11, shuffle=True)
    scores = cross_val_score(estimator=estimator_object,
                             X=digits.data, y=digits.target, cv=kfold)
    print(f'{estimator_name:>20}: ' +
          f'mean accuracy={scores.mean():.2%}; ' +
          f'standard deviation={scores.std():.2%}')
KNeighborsClassifier: mean accuracy=98.72%; standard deviation=0.75%
                 SVC: mean accuracy=98.72%; standard deviation=0.79%
          GaussianNB: mean accuracy=84.48%; standard deviation=3.47%

Цикл перебирает элементы словаря estimators и для каждой пары «ключ-значение» выполняет следующие операции:

  • распаковывает ключ в estimator_name, а значение — в estimator_object;
  • создает объект KFold, осуществляющий случайную перестановку данных и формирующий 10 частей. В данном случае ключевой аргумент random_state особенно важен — он гарантирует, что все оценщики будут работать с идентичными частями (чтобы эффективность сравнивалась по одним исходным данным);
  • оценивает текущий объект estimator_object с использованием cross_val_score;
  • выводит имя оценщика, за которым следует математическое ожидание и стандартное отклонение для оценок точности, вычисленных для всех 10 частей.

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

Точности оценщиков KNeighborsClassifier и SVC почти идентичны, поэтому стоит провести настройку гиперпараметров каждого оценщика для выбора лучшего варианта.

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

Настройка гиперпараметров

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

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

Следующий цикл создает объект KNeighborsClassifiers с нечетными значениями k от 1 до 19 (как упоминалось ранее, нечетные значения k в k-NN предотвращают неоднозначные ситуации с «ничейными» результатами) и выполняет k-проходную перекрестную проверку для каждого варианта.

Как видно из оценок точности и стандартных отклонений, при значении k=1 достигается наибольшая точность прогнозирования для набора данных Digits.

Рост значений k ведет к снижению точности:

for k in range(1, 20, 2):
    kfold = KFold(n_splits=10, random_state=11, shuffle=True)
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(estimator=knn,
                             X=digits.data, y=digits.target, cv=kfold)
    print(f'k={k:<2}; mean accuracy={scores.mean():.2%}; ' +
          f'standard deviation={scores.std():.2%}')
k=1 ; mean accuracy=98.83%; standard deviation=0.58%
k=3 ; mean accuracy=98.78%; standard deviation=0.78%
k=5 ; mean accuracy=98.72%; standard deviation=0.75%
k=7 ; mean accuracy=98.44%; standard deviation=0.96%
k=9 ; mean accuracy=98.39%; standard deviation=0.80%
k=11; mean accuracy=98.39%; standard deviation=0.80%
k=13; mean accuracy=97.89%; standard deviation=0.89%
k=15; mean accuracy=97.89%; standard deviation=1.02%
k=17; mean accuracy=97.50%; standard deviation=1.00%
k=19; mean accuracy=97.66%; standard deviation=0.96%

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