Постановка задачи
Многие слышали о ROC-кривой, которая часто используется в ML. Расшифровывая данную аббревиатуру мы получаем, что ROC (англ. receiver operating characteristic). При переводе с английского это означает РХП (рабочая характеристика приемника). Данное понятие позаимствовано из теории обнаружения сигналов. ROC-кривую можно связать с радиолокационной станцией (РЛС), рассматривая ее с точки зрения обнаружения объекта. Опишем это более формально.
РЛС посылает импульсы, которые отражаются от объектов. Отражённый сигналвоспринимается приёмной антенной радара (Рис. 1). Если есть какой-либо объект, находящийся в зоне обнаружения (ЗО)
, то отраженный сигнал будет выше порога детектирования
и это будет означать наличие объекта
. Если отражённый сигнал ниже порога детектирования, то это означает отсутствие объекта
.

ЗО
Зоной обнаружения РЛС называется область пространства, в пределах которой РЛС обеспечивает обнаружение объектов с вероятностями правильного обнаружения не хуже требуемых.
Наличие объекта обозначим как гипотеза, а его отсутствие как гипотеза
. Сигнал
— это непрерывная случайная величина. Предположим, что мы имеем условные распределения
, которые нам известны.
Очевидно, что мы можем говорить о том, чтоимеет одинаковое распределение для обоих гипотез, но математические ожидания условных распределений
будут различными. Так же реалистично предположить, что
Учитывая отклонения , возникающие из-за шума, получаем:

На Рис. 2 — есть условные математические ожидания случайной величины
при условии гипотезы
и
соответственно. При этом, согласно
, получаем, что
.
Как уже было сказано ранее, сигнал сравнивается с неким порогом, который мы обозначили как
. Решение о том есть ли объект в ЗО РЛС или нет обозначим как
и
соответственно. Дополняя Рис. 2
и
, получаем:

Получаем следующие условные вероятности:
Условные вероятностиможно интерпретировать так:
-
– есть вероятность обнаружения объекта при условии, что выполняется гипотеза
, т.е. объект действительно находится в ЗО.
-
– есть вероятность ложной тревоги, т.е. мы утверждаем
, которое означает принятие решения о наличии объекта в ЗО, когда в действительности выполняется
, т.е. объекта в ЗО нет.
Визуализируя интегралы , получаем:

Очевидно, что с уменьшением порогаплощадь под кривыми будет больше, а значит, что
и
будут увеличиваться.
Кривая, показывающая зависимость как функцию от
для различных
, называется ROC-кривая (англ. Receiver Operating Characteristic, рабочая характеристика приёмника).
Дисперсия случайной величиныопределяется характеристикой Отношение сигнал-шум (ОТШ, англ. Signal-to-Noise Ratio, сокр.
), которая записывается как:
-
– мощность сигнала;
-
– мощность шума.
Принимая в , а
– дисперсия, получаем:
Построение ROC-кривых и их анализ
Пусть заданы следующие начальные условия:
Извидно, что мы делаем предположение о том, что условные плотности имеют нормальное распределение, т.е.
и
, где
– обозначение нормального распределения с математическим ожиданием
и дисперсией
. Ранее мы обозначали
как условное математическое ожидание случайной величины
. Исходя из
, получаем, что
, а
.
Из диапазона мы можем понять, какой у нас диапазон дисперсии
. Для этого выразим из
:
Подставляя граничные точки отрезка из , получаем:
Зададим вектор значений для построения ROC-кривой для каждого из них:
Шаг изменения значений вектора возьмем равным
, т.е.
. Определим теперь ROC-кривую как параметрическую функцию
от
, где в качестве параметра выступает
. Из
и
, получаем:
– задает конкретную ROC-кривую из семейства ROC-кривых.
Зададим теперь вектор значений :
Шаг изменения значений вектора возьмем равным
, т.е.
. Вектор
содержит
значение, т.е. имеем
точку для каждого графика. Таким образом, графики будут визуально выглядеть гладкими.
Имеющихся данных достаточно для построения ROC-кривых.
Воспользуемся для этого языком Python.
Подключение библиотек
# подключение дополнительных библиотек from scipy.stats import norm from scipy.misc import derivative import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns; sns.set() %matplotlib inline
Дополнительные функции и начальные условия
# зададим вектор SNR и lambda SNR_values = np.arange(-1, 5.5, 1/2) lambda_values = np.arange(0, 1.01, 1/100) # функция, которая возвращает значение sigma по заданному значению SNR def snr_to_sigma(SNR): return np.power(10, -SNR/20) # функция, которая возвращает значения Pfa и Pd по заданным sigma и lambda def roc_curve(sigma, lambda_): return 1 - norm.cdf(lambda_/sigma), 1 - norm.cdf((lambda_ - 1)/sigma) # создание DataFrame в котором будут храниться значения Pfa, Pd, SNR data = [] for SNR in SNR_values: for lambda_ in lambda_values: Pfa, Pd = roc_curve(snr_to_sigma(SNR), lambda_) data.append([Pfa, Pd, str(SNR)]) data = pd.DataFrame(data, columns=['Pfa', 'Pd', 'SNR'])
Код для построения графика
fig, ax = plt.subplots(figsize=(18, 14)) sns.set_context('poster') plt.title('$ROC$-кривые в зависимости от значения $SNR$', fontsize=40) plt.xlabel('$P_{fa}$', fontsize=40) plt.xticks(np.arange(0, 0.7, 0.05), fontsize=30) plt.yticks(np.arange(0.5, 1, 0.05), fontsize=30) plt.ylabel('$P_d$', fontsize=40, rotation=0, labelpad=40) sns.lineplot(x='Pfa', y='Pd', hue='SNR', data=data, palette='gist_ncar', ax=ax) ax.annotate("Возрастание $\lambda$", xy=(0.15, 0.81), xycoords='data', xytext=(90, 150), textcoords='offset points', size=30, ha='left', arrowprops=dict(arrowstyle="->", color='black', connectionstyle="arc3,rad=0.1")) plt.grid(color='black', linestyle='--', linewidth=1, alpha=0.3) plt.legend(fontsize='small', shadow=True, title='$SNR(dB)$', borderpad=0.9, fancybox=True);

Из графика видно следующее:
-
Все кривые начинают свое движение с линии
и заканчивают на линии
;
-
Чем больше значение
, тем ближе ROC-кривая к точке
;
-
Все кривые выпуклы вверх;
-
ROC — кривая монотонно не убывает. Чем выше лежит кривая, тем лучше качество РЛС.
Первое объясняется ограниченностью значения порога детектирования . Посмотрим, как будут выглядеть ROC-кривые, если увеличить диапазон
, например, до
.
Дополнительный код
# создание DataFrame в котором будут храниться значения Pfa, Pd, SNR data2 = [] for SNR in SNR_values: for lambda_ in np.arange(-10, 10.1, 1/10) : Pfa, Pd = roc_curve(snr_to_sigma(SNR), lambda_) data2.append([Pfa, Pd, str(SNR)]) data2 = pd.DataFrame(data2, columns=['Pfa', 'Pd', 'SNR'])
Код для построения графика
fig, ax = plt.subplots(figsize=(18, 14)) sns.set_context('poster') plt.title('$ROC$-кривые в зависимости от значения $SNR$', fontsize=40) plt.xlabel('$P_{fa}$', fontsize=40) plt.xticks(np.arange(0, 1.05, 0.1), fontsize=20) plt.yticks(np.arange(0, 1.05, 0.1), fontsize=20) plt.ylabel('$P_d$', fontsize=40, rotation=0, labelpad=40) sns.lineplot(x='Pfa', y='Pd', hue='SNR', data=data2, palette='gist_ncar', ax=ax) ax.annotate("Возрастание $\lambda$", xy=(0.15, 0.81), xycoords='data', xytext=(90, 110), textcoords='offset points', size=20, ha='left', arrowprops=dict(arrowstyle="->", color='black', connectionstyle="arc3,rad=0.1")) plt.grid(color='black', linestyle='--', linewidth=1, alpha=0.3) plt.legend(fontsize='small', shadow=True, title='$SNR(dB)$', borderpad=0.9, fancybox=True);

Второе объясняется тем, что можно достичь большего значения при меньшем значении
. Это происходит потому, что при увеличении значения
, уменьшается значение
, что видно из
. Посмотрим, как визуально изменяются кривые условных плотностей
при разных значениях
.
Дополнительный код
# функция, которая возвращает значение условных плотностей в зависимости от sigma def conditional_density(x, sigma): return norm.pdf(x/sigma), norm.pdf((x - 1)/sigma) # создание DataFrame в котором будут храниться значения условных плотностей и SNR data3 = [] x_range = np.linspace(-5, 5, 100) for SNR in SNR_values: for x in x_range : cond_dens1, cond_dens2 = conditional_density(x, snr_to_sigma(SNR)) data3.append([cond_dens1, cond_dens2, str(SNR), x]) data3 = pd.DataFrame(data3, columns=['cond_dens1', 'cond_dens2', 'SNR', 'x'])
Код для построения графика
fig, ax = plt.subplots(figsize=(20, 10)) sns.set_context('poster') plt.title('Условные распределения в зависимости от значения $SNR$', fontsize=30) plt.xlabel('$x$', fontsize=40) plt.xticks(np.arange(-5, 5.5, 0.5), fontsize=20) plt.yticks(np.arange(0, 0.45, 0.05), fontsize=20) plt.ylabel('Значение условной плотности', fontsize=20, labelpad=30) sns.lineplot(x='x', y='cond_dens2', hue='SNR', data=data3, palette='gist_ncar', ax=ax) sns.lineplot(x='x', y='cond_dens1', hue='SNR', data=data3, palette='gist_ncar', ax=ax, legend=False) ax.legend(fontsize='small', shadow=True, title='$SNR(dB)$', borderpad=0.9, fancybox=True, loc='upper left') ax.annotate("$f_{X|H_0}(x|H_0)$", xy=(-0.6, 0.35), xycoords='data', xytext=(-90, 20), textcoords='offset points', size=30, ha='right', arrowprops=dict(arrowstyle="->", color='black', connectionstyle="arc3,rad=0.1")) ax.annotate("$f_{X|H_1}(x|H_1)$", xy=(1.6, 0.35), xycoords='data', xytext=(100, 20), textcoords='offset points', size=30, ha='left', arrowprops=dict(arrowstyle="->", color='black', connectionstyle="arc3,rad=0.1")) plt.grid(color='black', linestyle='--', linewidth=1, alpha=0.3);

По Рис. 7 видно, что при увеличении , кривые становятся уже, а, значит, пересекаются в меньшей степени. Посмотрим теперь на зависимость вероятности обнаружения
от
:
Дополнительный код
data4 = [] for SNR in SNR_values: for lambda_ in lambda_values: Pfa, Pd = roc_curve(snr_to_sigma(SNR), lambda_) data4.append([Pfa, Pd, str(SNR), lambda_]) data4 = pd.DataFrame(data4, columns=['Pfa', 'Pd', 'SNR', 'lambda'])
Код для построения графика
fig, ax = plt.subplots(figsize=(16, 12)) sns.set_context('poster') plt.title('Кривые $P_d$ от $\lambda$ при разных $SNR$', fontsize=40) plt.xlabel('$\lambda$', fontsize=40) plt.xticks(np.arange(0, 1.05, 0.1), fontsize=20) plt.yticks(np.arange(0.5, 1.05, 0.1), fontsize=20) plt.ylabel('$P_d$', fontsize=40, rotation=0, labelpad=40) sns.lineplot(x='lambda', y='Pd', hue='SNR', data=data4, palette='gist_ncar', ax=ax) ax.annotate("Возрастание $\lambda$", xy=(0.5, 0.85), xycoords='data', xytext=(-10, 50), textcoords='offset points', size=20, ha='right', arrowprops=dict(arrowstyle="->", color='black', connectionstyle="arc3,rad=-0.2")) plt.grid(color='black', linestyle='--', linewidth=1, alpha=0.3) plt.legend(fontsize='small', shadow=True, title='$SNR(dB)$', borderpad=0.9, fancybox=True);

достигает максимального значения при
и
. Но при таком низком пороге детектирования мы получаем:
Код для построения графика
fig, ax = plt.subplots(figsize=(16, 12)) sns.set_context('poster') plt.title('Кривые $P_{fa}$ от $\lambda$ при разных $SNR$', fontsize=40) plt.xlabel('$\lambda$', fontsize=40) plt.ylabel('$P_{fa}$', fontsize=40, rotation=0, labelpad=40) sns.lineplot(x='lambda', y='Pfa', hue='SNR', data=data4, palette='gist_ncar', ax=ax) ax.annotate("Возрастание $\lambda$", xy=(0.5, 0.35), xycoords='data', xytext=(-10, 90), textcoords='offset points', size=30, ha='right', arrowprops=dict(arrowstyle="->", color='black', connectionstyle="arc3,rad=0.2")) plt.grid(color='black', linestyle='--', linewidth=1, alpha=0.3) plt.legend(fontsize='small', shadow=True, title='$SNR(dB)$', borderpad=0.9, fancybox=True);

При получается, что вероятность ложной тревоги равна
, так как определенный интеграл от условной плотности распределения
считается от значения математического ожидания до , что в точности равно
, в силу симметричности плотности нормального распределения.
Третье объясняется тем, что тангенс угла наклона ROC-кривой в некоторой ее точке равен значению плотностей от порога детектирования
, необходимому для достижения
и
в этой точке.
Пусть
тогда:
Исследуем функцию :
Из и
следует, что тангенс угла наклона ROC-кривой монотонно изменяется от
до
при
от
до
. Таким образом, начиная построение графика от
и, следовательно,
. Заканчивается все в точке
, когда
, т.е.
(касательная в данной точке перпендикулярна оси абсцисс).
Дополнительный код
# функция, которая возвращает значения Pfa и Pd по заданным sigma и lambda def dif_roc_curve(sigma, lambda_): return (derivative(lambda x: 1 - norm.cdf(x/sigma), lambda_, dx=1e-10), derivative(lambda x: 1 - norm.cdf((x - 1)/sigma), lambda_, dx=1e-10)) data5 = [] for SNR in SNR_values: for lambda_ in [0, 0.5, 1]: dPfa, dPd = dif_roc_curve(snr_to_sigma(SNR), lambda_) Pfa, Pd = roc_curve(snr_to_sigma(SNR), lambda_) tan = dPd/dPfa for i in np.arange(0.01/tan, 0.2/tan, 0.01/tan): data5.append([tan*(i - Pfa) + Pd, i, str(SNR), lambda_]) data5 = pd.DataFrame(data5, columns=['y', 'x', 'SNR', 'lambda'])
Код для построения графика
fig, ax = plt.subplots(figsize=(18, 14)) sns.set_context('poster') plt.title('$ROC$-кривая с $3^{мя}$ касательными', fontsize=40) plt.xlabel('$P_{fa}$', fontsize=40) plt.xticks(np.arange(0, 1.05, 0.1), fontsize=20) plt.yticks(np.arange(0, 1.05, 0.1), fontsize=20) plt.ylabel('$P_d$', fontsize=40, rotation=0, labelpad=40) sns.lineplot(x='Pfa', y='Pd', hue='SNR', legend=False, data=data[data['SNR'] == '5.0'], palette='gist_ncar', ax=ax) sns.lineplot(x='x', y='y', hue='lambda', linestyle='--', data=data5[data5['SNR'] == '5.0'], palette='dark:b', legend=True, ax=ax) plt.grid(color='black', linestyle='--', linewidth=1, alpha=0.3) plt.legend(fontsize='small', shadow=True, title='Касательная для $\lambda=$', borderpad=0.9, fancybox=True, loc=4);
/

На рис. 10 рассматриваются точки .
Вывод
В ходе исследования были построены множество ROC-кривых на основе аналитического выражения . Были также получены свойства ROC-кривых на основе графиков и аналитических выражений. То, каким образом будет выглядеть характеристика рабочего приемника, зависит от значения
, из которого однозначно определяется значение
. Также область, в которой определена ROC-кривая, зависит от диапазона
.
Использованная литература
-
Ван-Трис Гарри Л. Теория обнаружения, оценок и модуляции. Том 1. Теория обнаружения, оценок и линейной модуляции -Нью-Йорк, 1968, Пер, с англ. , под ред. проф. В. И. Тихонова. М., «Советское радио», 1972, 744 с.
-
Тяпкин В.Н. Основы построения радиолокационных станций радиотехнических войск: учебник / В.Н. Тяпкин, А.Н. Фомин, Е.Н. Гарин [и др.]; под общ. ред. В.Н. Тяпкина. – Красноярск : Сиб. федер. ун-т. – 2011. – 536 с.
Дополнительно
Ссылка на github с исходным кодом.
ссылка на оригинал статьи https://habr.com/ru/post/549376/
Добавить комментарий