MLOPs

3 Практический мониторинг табличных данных. Серия руководств по ML-OPS

Дрейф концепций, дрейф данных и мониторинг

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

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

Например. Для простоты предположим, что у нас есть модель классификации/регрессии с 20 функциями. Допустим, функция A и функция B являются одними из самых важных функций модели. В этом посте давайте посмотрим, как мы можем попытаться увидеть, происходит ли дрейф данных в функциях.

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

import pandas as pd
import numpy as np
df=pd.read_csv('../input/yahoo-data/MSFT.csv')
df=df[['Date','Close']]

Этот файл содержит данные о цене акций Microsoft за 53 недели. Теперь мы хотим работать с доходностью акций, а не с самой ценой, поэтому нам нужно немного поработать с данными

df['diff'] = pd.Series(np.diff(df['Close']))
df['return'] = df['diff']/df['Close']
df = df[['Date', 'return']].dropna()
print(df.head(3))
Date    return
0  2018-01-01  0.015988
1  2018-01-08  0.004464
2  2018-01-15  0.045111

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

from matplotlib import pyplot as plt

plt.hist(df['return'])
plt.show()

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

Тестирование графика Q-Q

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

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

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

import pylab
import scipy.stats as stats
stats.probplot(df['return'], dist="norm", plot=pylab)
pylab.show()

Глядя на график выше, мы видим восходящую линейную зависимость. Для нормального распределения все наблюдения должны происходить на прямой линии под углом 45 градусов. Видим ли мы такую ​​связь выше? Делаем частично. Это может сигнализировать нам о том, что дистрибутив, с которым мы работаем, не совсем нормальный, но близок к нему.

Тест Жарка-Бера

Жарке-Бера — это один из тестов на нормальность или, в частности, критерий согласия при сопоставлении асимметрии и эксцесса с нормальным распределением.

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

Тестовая статистика JB Жарка-Бера определяется следующим образом:

где 𝑆 — асимметрия выборки, 𝐾 — эксцесс выборки, а 𝑛 — размер выборки.

Гипотезы:

𝐻0: выборка 𝑆 и выборка 𝐾 незначительно отличаются от нормального распределения

𝐻1: выборка 𝑆 и выборка 𝐾 значительно отличаются от нормального распределения

Теперь мы можем рассчитать статистику теста Харке-Бера и найти соответствующее 𝑝-значение:

from scipy.stats import jarque_bera
result = (jarque_bera(df['return']))
print(f"JB statistic: {result[0]}")
print(f"p-value: {result[1]}")
JB statistic: 1.9374105196180924
p-value: 0.37957417002404925

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

Примечание. Критерий Жака-Бера корректно работает на больших выборках (обычно более 2000 наблюдений), поскольку его статистика имеет распределение хи-квадрат с 2 степенями свободы)

Тест Колмогорова-Смирнова

Одним из наиболее частых тестов на нормальность является тест Колмогорова-Смирнова (или тест К-С). Основное преимущество по сравнению с другими тестами заключается в том, что критерий Колмогорова-Смирнова непараметрический, т. е. не требует распределения.

Здесь мы сосредоточимся на одновыборочном тесте Колмогорова-Смирнова, потому что хотим сравнить одномерное распределение вероятностей с теоретически заданным распределением (в нашем случае это нормальное распределение).

Статистика теста Колмогорова-Смирнова измеряет расстояние между эмпирической функцией распределения (ECDF) выборки и кумулятивной функцией распределения эталонного распределения.

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

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

Шаг 1.

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

data_norm = np.random.normal(np.mean(df['return']), np.std(df['return']), len(df))

Используя np.random.normal(), мы создали data_norm, который представляет собой массив с тем же количеством наблюдений, что и df[‘return’], а также с таким же средним значением и стандартным отклонением.

Здесь интуитивно понятно, что если мы предположим некоторые параметры распределения (среднее значение и стандартное отклонение), какие числа с такими параметрами образуют нормальное распределение.

Шаг 2.

Далее мы собираемся использовать np.histogram() для обоих наборов данных, чтобы отсортировать их и распределить по бинам:

values, base = np.histogram(df['return'])
values_norm, base_norm = np.histogram(data_norm)

Примечание: по умолчанию функция будет использовать bins=10, что можно настроить в зависимости от данных, с которыми вы работаете.

Шаг 3.

Используйте np.cumsum() для вычисления совокупных сумм массивов, созданных выше:

cumulative = np.cumsum(values)
cumulative_norm = np.cumsum(values_norm)

Шаг 4.

Постройте кумулятивные функции распределения:

plt.plot(base[:-1], cumulative, c='blue')
plt.plot(base_norm[:-1], cumulative_norm, c='green')
plt.show()

Где синяя линия – это ECDF (эмпирическая кумулятивная функция распределения) df[‘return’], а зеленая – функция CDF нормального распределения.

Шаг 4 Альтернативный вариант:

Вы можете создать график быстрее, используя seaborn, и вам понадобятся только df[‘return’] и data_norm из шага 1:

import seaborn as sns
sns.ecdfplot(df['return'], c='blue')
sns.ecdfplot(data_norm, c='green')

Теперь вернемся к тесту Колмогорова-Смирнова после визуализации этих двух кумулятивных функций распределения. Критерий Колмогорова-Смирнова основан на максимальном расстоянии между этими двумя кривыми (сине-зелеными) со следующими гипотезами:

𝐻0: два образца из одного дистрибутива

𝐻1: два образца из разных дистрибутивов

Мы определяем ECDF как:

который подсчитывает долю выборочных наблюдений ниже уровня 𝑥.

Мы определяем данный (теоретический) CDF как: 𝐹(𝑥). В случае проверки на нормальность 𝐹(𝑥) — это CDF нормального распределения.

Статистика Колмогорова-Смирнова определяется как:

Интуитивно эта статистика измеряет наибольшее абсолютное расстояние между двумя функциями распределения для всех значений 𝑥.

Используя приведенный выше график, вот мой расчетный супремум:

Здесь синяя линия указывает на супремум. Вычислив значение 𝐷𝑛 и сравнив его с критическим значением (предположим, 5%) 𝐷0,05, мы можем либо отклонить, либо не отклонить нулевую гипотезу.

Возвращаясь к нашему примеру, давайте проведем тест K-S для данных о доходности акций Microsoft:

from scipy.stats import kstest
result = (kstest(df['return'], cdf='norm'))
print(f"K-S statistic: {result[0]}")
print(f"p-value: {result[1]}")
K-S statistic: 0.46976096086398267
p-value: 4.788934452701707e-11

Поскольку значение 𝑝 значительно меньше 0,05, мы отклоняем нулевую гипотезу и принимаем альтернативную гипотезу о том, что две протестированные выборки не относятся к одному и тому же кумулятивному распределению, а это означает, что доходность акций Microsoft не распределяется нормально.

Тест Андерсона-Дарлинга

Тест Андерсона-Дарлинга (тест A-D) является модификацией теста Колмогорова-Смирнова, описанного выше. Он проверяет, взята ли данная выборка наблюдений из заданного распределения вероятностей (в нашем случае из нормального распределения).

𝐻0: данные поступают из указанного дистрибутива

𝐻1: данные не поступают из указанного дистрибутива

Тест AD более эффективен, чем тест K-S, поскольку он учитывает все значения в данных, а не только то, которое дает максимальное расстояние (как в тесте K-S). Он также присваивает больший вес хвостам подобранного распределения.

Этот тест относится к статистике квадратичной эмпирической функции распределения (EDF) и определяется следующим образом:

где 𝐹 — гипотетическое распределение (в нашем случае — нормальное распределение), 𝐹𝑛 — ECDF (расчеты обсуждались в предыдущем разделе), а 𝑤(𝑥) — весовая функция.

Весовая функция определяется следующим образом:

что позволяет придать больший вес наблюдениям в хвостах распределения.

Учитывая такую ​​весовую функцию, статистику теста можно упростить до:

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

Нам нужно отсортировать данные таким образом, чтобы 𝑥1‹𝑥2‹…‹𝑥𝑛, а затем вычислить статистику 𝐴2 следующим образом:

Возвращаясь к нашему примеру, давайте выполним тест A-D в Python для данных о доходности акций Microsoft:

from scipy.stats import anderson
result = (anderson(df['return'], dist='norm'))
print(f"A-D statistic: {result[0]}")
print(f"Critical values: {result[1]}")
print(f"Significance levels: {result[2]}")
A-D statistic: 0.3693823006816217
Critical values: [0.539 0.614 0.737 0.86  1.023]
Significance levels: [15.  10.   5.   2.5  1. ]

Первая строка выходных данных — это статистика теста AD, которая составляет около 0,37; третья строка вывода — список с разным уровнем значимости (от 15% до 1%); вторая строка вывода представляет собой список критических значений для соответствующих уровней значимости.

Допустим, мы хотим проверить нашу гипотезу на уровне 5 %, что означает, что критическое значение, которое мы будем использовать, равно 0,737 (из вывода выше). Поскольку статистика компьютерного теста AD (0,37) меньше критического значения (0,737), мы не можем отвергнуть нулевую гипотезу и сделать вывод, что выборочные данные о доходности акций Microsoft получены из нормального распределения.

Тест Шапиро-Уилка

Тест Шапиро-Уилка (SW-тест) – это еще один тест на нормальность статистики со следующими гипотезами:

𝐻0: распределение выборки существенно не отличается от нормального распределения

𝐻1: распределение выборки значительно отличается от нормального распределения

В отличие от теста Колмогорова-Смирнова и теста Андерсона-Дарлинга, он не основывает свой статистический расчет на ECDF и CDF, а использует константы, сгенерированные из моментов из нормально распределенной выборки.

Статистика теста Шапиро-Уилка определяется следующим образом:

где 𝑥(𝑖) — 𝑖-е наименьшее число в выборке (𝑥1‹𝑥2‹…‹𝑥𝑛); а 𝑎𝑖 — константы, сгенерированные из var, cov, mean из нормально распределенной выборки.

Возвращаясь к нашему примеру, давайте выполним тест S-W на Python для данных о доходности акций Microsoft:

from scipy.stats import shapiro

result = (shapiro(df['return']))

print(f"S-W statistic: {result[0]}")
print(f"p-value: {result[1]}")
S-W statistic: 0.9772366881370544
p-value: 0.41611215472221375

Учитывая большое значение 𝑝 (0,42), превышающее 0,05 (>0,05), мы не можем отвергнуть нулевую гипотезу и сделать вывод, что выборка существенно не отличается от нормального распределения.

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

Чтобы загрузить блокнот и набор данных, перейдите на Github

Примечание. Смещение данных и смещение концепций — обширные темы, о которых можно написать новую книгу. Итак, некоторые другие полезные инструменты для изучения в качестве эталона для получения большей широты и глубины для различных вариантов набора данных, таких как TorchDrift, EvidentlyAI, Обнаружение алиби, River.

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