Анализ и построение ROC-кривых: связь с РЛС
Постановка задачи
Многие слышали о ROC-кривой, которая часто используется в ML. Расшифровывая данную аббревиатуру мы получаем, что ROC (англ. receiver operating characteristic). При переводе с английского это означает РХП (рабочая характеристика приемника). Данное понятие позаимствовано из теории обнаружения сигналов. ROC-кривую можно связать с радиолокационной станцией (РЛС), рассматривая ее с точки зрения обнаружения объекта. Опишем это более формально.
РЛС посылает импульсы, которые отражаются от объектов. Отражённый сигнал
ЗО
Наличие объекта обозначим как гипотеза
Очевидно, что мы можем говорить о том, что
Учитывая отклонения
На Рис. 2
Как уже было сказано ранее, сигнал
Получаем следующие условные вероятности:
Условные вероятности
– есть вероятность обнаружения объекта при условии, что выполняется гипотеза , т.е. объект действительно находится в ЗО. – есть вероятность ложной тревоги, т.е. мы утверждаем , которое означает принятие решения о наличии объекта в ЗО, когда в действительности выполняется , т.е. объекта в ЗО нет.
Визуализируя интегралы
Очевидно, что с уменьшением порога
Кривая, показывающая зависимость
Дисперсия случайной величины
– мощность сигнала; – мощность шума.
Принимая в
Построение 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 - кривая монотонно не убывает. Чем выше лежит кривая, тем лучше качество РЛС.
Первое объясняется ограниченностью значения порога детектирования
Дополнительный код
# создание 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-кривой в некоторой ее точке равен значению плотностей
Пусть
тогда:
Исследуем функцию
Из
Дополнительный код
# функция, которая возвращает значения 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-кривых на основе аналитического выражения
Использованная литература
Ван-Трис Гарри Л. Теория обнаружения, оценок и модуляции. Том 1. Теория обнаружения, оценок и линейной модуляции -Нью-Йорк, 1968, Пер, с англ. , под ред. проф. В. И. Тихонова. М., «Советское радио», 1972, 744 с.
Тяпкин В.Н. Основы построения радиолокационных станций радиотехнических войск: учебник / В.Н. Тяпкин, А.Н. Фомин, Е.Н. Гарин [и др.]; под общ. ред. В.Н. Тяпкина. – Красноярск : Сиб. федер. ун-т. – 2011. – 536 с.
Дополнительно
Ссылка на github с исходным кодом.