Интуитивный вывод формулы KDE

Введение

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

К сожалению, распределение данных иногда бывает слишком неравномерным и не похоже ни на один из обычных PDF-файлов. В таких случаях Kernel Density Estimator (KDE) обеспечивает рациональное и визуально приятное представление распределения данных.

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

Функция ядра

Ключом к пониманию KDE является представление о нем как о функции, состоящей из строительных блоков, подобно тому, как различные объекты состоят из кубиков Lego. Отличительной особенностью KDE является то, что он использует только один тип кирпича, известный как ядроодин кирпич, чтобы управлять всеми»). Ключевое свойство этого кирпича – способность смещаться и растягиваться/сжиматься. Каждой точке данных присваивается кирпич, а KDE — это сумма всех кирпичей.

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

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

Первый шаг на пути к KDE — сосредоточиться только на одной точке данных. Что бы вы сделали, если бы вас попросили создать PDF-файл для одной точки данных? Для начала возьмем x =0. Самый логичный подход — использовать PDF-файл, который достигает максимума точно в этой точке и затухает по мере удаления от нее. Функция

сделал бы свое дело.

Однако, поскольку предполагается, что PDF имеет единичную площадь под кривой, мы должны изменить масштаб результата. Следовательно, функцию необходимо разделить на квадратный корень из 2π и растянуть на коэффициент √2 (3Blue1Brown обеспечивает отличный вывод этих коэффициентов):

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

Это ядро ​​эквивалентно распределению Гаусса с нулевым средним значением и единичной дисперсией.

Давайте поиграем с этим некоторое время. Начнем с того, что научимся сдвигать его по оси x.

Возьмите одну точку данных xᵢ - i-ю точку, принадлежащую нашему набору данных X. Сдвиг можно осуществить вычитанием аргумента:

Чтобы сделать кривую шире или уже, мы можем просто добавить в аргумент константу h (так называемую полосу пропускания ядра). Обычно его представляют в виде знаменателя:

Однако в результате площадь под функцией ядра умножается на h. Поэтому нам придется вернуть его обратно к единице площади, разделив на h:

Вы можете выбрать любое значение h. Вот пример того, как это работает.

Чем выше h, тем шире PDF-файл. Чем меньше h, тем уже PDF-файл.

Оценщик плотности ядра

Рассмотрим некоторые фиктивные данные, чтобы увидеть, как мы можем расширить метод до нескольких точек.

# dataset
x = [1.33, 0.3, 0.97, 1.1, 0.1, 1.4, 0.4]

# bandwidth
h = 0.3

Для первой точки данных мы просто используем:

Мы можем сделать то же самое со второй точкой данных:

Чтобы получить единый PDF-файл по первым двум пунктам, мы должны объединить эти два отдельных PDF-файла:

Поскольку мы добавили два PDF-файла с единичной площадью, площадь под кривой становится равной 2. Чтобы вернуть ее обратно к единице, мы делим ее на два:

Хотя для точности можно использовать полную подпись функции f:

мы просто будем использовать f(x), чтобы не загромождать обозначение.

Вот как это работает для двух точек данных:

И последний шаг на пути к KDE — принять во внимание n точек данных.

Оценщик плотности ядра:

Давайте немного повеселимся с нашим вновь открытым KDE.

import numpy as np
import matplotlib as plt

# the Kernel function
def K(x):
    return np.exp(-x**2/2)/np.sqrt(2*np.pi)

# dummy dataset
dataset = np.array([1.33, 0.3, 0.97, 1.1, 0.1, 1.4, 0.4])

# x-value range for plotting KDEs
x_range = np.linspace(dataset.min()-0.3, dataset.max()+0.3, num=600)

# bandwith values for experimentation
H = [0.3, 0.1, 0.03]
n_samples = dataset.size

# line properties for different bandwith values
color_list = ['goldenrod', 'black', 'maroon']
alpha_list = [0.8, 1, 0.8]
width_list = [1.7,2.5,1.7]

plt.figure(figsize=(10,4))
# iterate over bandwith values
for h, color, alpha, width in zip(H, color_list, alpha_list, width_list):
    total_sum = 0
    # iterate over datapoints
    for i, xi in enumerate(dataset):
        total_sum += K((x_range - xi) / h)
        plt.annotate(r'$x_{}$'.format(i+1),
                     xy=[xi, 0.13],
                     horizontalalignment='center',
                     fontsize=18,
                    )
    y_range = total_sum/(h*n_samples)
    plt.plot(x_range, y_range, 
             color=color, alpha=alpha, linewidth=width, 
             label=f'{h}')

    plt.plot(dataset, np.zeros_like(dataset) , 's', 
             markersize=8, color='black')
    
plt.xlabel('$x$', fontsize=22)
plt.ylabel('$f(x)$', fontsize=22, rotation='horizontal', labelpad=20)
plt.legend(fontsize=14, shadow=True, title='$h$', title_fontsize=16)
plt.show()

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

KDE с библиотеками Python

Морская библиотека использует KDE, чтобы обеспечить красивую визуализацию распределения данных.

import seaborn as sns
sns.set()

fig, ax = plt.subplots(figsize=(10,4))

sns.kdeplot(ax=ax, data=dataset, 
            bw_adjust=0.3,
            linewidth=2.5, fill=True)

# plot datapoints
ax.plot(dataset, np.zeros_like(dataset) + 0.05, 's', 
        markersize=8, color='black')
for i, xi in enumerate(dataset):
    plt.annotate(r'$x_{}$'.format(i+1),
                 xy=[xi, 0.1],
                 horizontalalignment='center',
                 fontsize=18,
                )
plt.show()

Scikit Learn предлагает функцию KernelDensity для выполнения аналогичной работы.

from sklearn.neighbors import KernelDensity

dataset = np.array([1.33, 0.3, 0.97, 1.1, 0.1, 1.4, 0.4])

# KernelDensity requires 2D array
dataset = dataset[:, np.newaxis]

# fit KDE to the dataset
kde = KernelDensity(kernel='gaussian', bandwidth=0.1).fit(dataset)

# x-value range for plotting KDE
x_range = np.linspace(dataset.min()-0.3, dataset.max()+0.3, num=600)

# compute the log-likelihood of each sample
log_density = kde.score_samples(x_range[:, np.newaxis])

plt.figure(figsize=(10,4))
# put labels over datapoints
for i, xi in enumerate(dataset):
    plt.annotate(r'$x_{}$'.format(i+1),
                 xy=[xi, 0.07],
                 horizontalalignment='center',
                 fontsize=18)

# draw KDE curve
plt.plot(x_range, np.exp(log_density), 
         color='gray', linewidth=2.5)

# draw boxes representing datapoints
plt.plot(dataset, np.zeros_like(dataset) , 's', 
         markersize=8, color='black')
    
plt.xlabel('$x$', fontsize=22)
plt.ylabel('$f(x)$', fontsize=22, rotation='horizontal', labelpad=24)
plt.show()

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

# Generate random samples from the model
synthetic_data = kde.sample(100)

plt.figure(figsize=(10,4))

# draw KDE curve
plt.plot(x_range, np.exp(log_density), 
         color='gray', linewidth=2.5)

# draw boxes representing datapoints
plt.plot(synthetic_data, np.zeros_like(synthetic_data) , 's', 
         markersize=6, color='black', alpha=0.5)
    
plt.xlabel('$x$', fontsize=22)
plt.ylabel('$f(x)$', fontsize=22, rotation='horizontal', labelpad=24)
plt.show()

Выводы

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

Отличительные особенности KDE:

  • это функция, состоящая из одного типа строительных блоков, называемого функция ядра;
  • это непараметрическая оценка, что означает, что ее функциональная форма определяется точками данных;
  • на форму создаваемого PDF-файла сильно влияет значение пропускной способности ядра h;
  • чтобы соответствовать набору данных, никакая оптимизация не требуется.

Применение KDE к многомерным данным очень просто. Но это тема для другого рассказа.

Если не указано иное, все изображения принадлежат автору.

Рекомендации

[1] С. Венгларчик, Оценка плотности ядра и ее применение (2018), Сеть конференций ITM, том. 23, EDP Sciences.

[2] Ю. Со, Ю. Хэ, А. Мехмуд, Р. Х. Ашраф, И. Ким: Производительность Оценка различных функций для оценки плотности ядра (2013), Открытый журнал прикладных наук, том. 3, стр. 58–64.