Эпизод 1: Полезные показатели в медицинской диагностике

Авторы: Симоне Азельо, Арианна Ди Бернардо, Николо Тоскано, Карло Альберто Мария Барбано

Одна из основных задач при построении модели машинного обучения - оценка ее эффективности. Это фундаментально и очень сложно. Работая над любым проектом, особенно в контексте здравоохранения, вы должны спросить себя: «Как я могу измерить успех этого проекта?» и «Как я узнаю, добился ли я успеха и когда?». Эти вопросы позволяют реалистично ставить цели и знать, когда остановиться. Иногда они мешают вам работать над плохо сформулированными проектами, в которых понятие «хорошее измерение» расплывчато или неосуществимо. Все дело в светофорах. Итак, как можно измерить зеленый свет: успех модели машинного обучения? Чтобы ответить на этот вопрос, давайте проведем обзорную экскурсию по оценке моделей машинного обучения для диагностики заболеваний.

Самые серьезные ошибки не совершаются из-за неправильных ответов. Поистине опасно задавать неправильные вопросы. Питер Друкер

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

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

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

Теперь мы готовы принять эстафету и перефразировать формулировку «задать правильный вопрос» в терминах машинного обучения, то есть как нам выбрать лучшую метрику оценки для нашей проблемы?

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

То, что мы собираемся сделать сейчас, - это демонстрация различных показателей оценки в конкретной обстановке: медицинский диагноз. Делая это, мы надеемся пролить свет на полезность этих инструментов, сохраняя при этом некоторую конкретность с точки зрения широко известного варианта использования. Но сначала мы собираемся представить вам набор данных, который мы выбрали для сегодняшнего дня, а именно Набор данных Chest X-ray 14.

Набор данных

Набор данных рентгеновского снимка грудной клетки 14 был выпущен Клиническим центром NIH и состоит из более чем 100 000 анонимных рентгеновских снимков грудной клетки, что считается огромным вкладом в открытую науку и исследования в области машинного обучения.

Что касается данных, существует 15 различных классов, соответствующих 14 различным заболеваниям, и один класс для «без результатов». Для полноты мы сообщаем название каждого класса, поскольку мы собираемся использовать их позже: Ателектаз, Консолидация, Инфильтрация, Пневмоторакс, Отек, Эмфизема, Фиброз, Выпот, Пневмония, Утолщение плевры, Кардиомегалия, Узелковая масса и грыжа.

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

Насколько мы видели, мы имеем дело с проблемой многоклассовой классификации (т.е. результат не двоичный, а категориальный). Прежде чем приступить к определению различных показателей, давайте быстро взглянем на наш набор данных. Для простоты мы решили сделать выборку из ранее процитированного набора данных и предварительно вычислить выходные данные модели для тестовых случаев, чтобы вам не о чем беспокоиться. Таким образом вы можете найти два разных файла train_preds.csv и valid_preds.csv (вы можете найти каждый ресурс в нашем репозитории Github)

Используя pandas, мы создаем два фрейма данных из наших файлов .csv, правильно называем метки классов и определяем предсказанные метки, которые соответствуют нормализованным вероятностям по отношению к определенному классу, т. Е. Выход модели.

#importing .csv files as pandas dataframes
train_results = pd.read_csv("train_preds.csv")
valid_results = pd.read_csv("valid_preds.csv")
# labels in our dataset
class_labels = ['Cardiomegaly',
'Emphysema',
'Effusion',
'Hernia',
'Infiltration',
'Mass',
'Nodule',
'Atelectasis',
'Pneumothorax',
'Pleural_Thickening',
'Pneumonia',
'Fibrosis',
'Edema',
'Consolidation']
# the labels for prediction values in our dataset
predicted_labels = [l + "_pred" for l in class_labels]
# extract the ground trouth (class_values) and the predictions (pred)
class_values = valid_results[class_labels].values
pred = valid_results[pred_labels].values

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

#Bar Chart 
#For the sake of visual clarity --> sort bars in descending order
class_values = valid_results[class_labels].values #occurences
cnt_values = class_values.sum(axis = 0) #occurences x disease 
df_to_plot = pd.DataFrame({"Disease": class_labels, "Count": cnt_values}) 
df_sorted = df_to_plot.sort_values("Count")

#Creating our plot as horizontal bar chart 
plt.figure(figsize=(12,6))
plt.title('Disease Incidence', pad=10)
plt.xlabel("Number of Patients", size=15)
plt.barh("Disease", "Count", data = df_sorted, color=(240/255, 167/255, 95/255))
plt.grid(alpha = 0.3);
plt.tight_layout()

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

Базовая статистика

Все начинается с четырех основных статистических данных, которые мы можем вычислить на основе прогнозов модели: истинные положительные результаты (TP), истинные отрицательные результаты (TN ), ложные срабатывания (FP) и ложноотрицательные (FN).

Как следует из названия:

  • истинно положительный: модель классифицирует пример как положительный, и фактическая метка также положительна;
  • истинно отрицательный: модель классифицирует пример как отрицательный, и фактическая метка также отрицательна;
  • ложное срабатывание: модель классифицирует пример как положительный, но фактическая метка отрицательная;
  • ложноотрицательный: модель классифицирует пример как отрицательный, но фактическая метка положительная.

Эти четыре имеют невообразимо важное значение: на их основе можно построить все показатели.

Теперь немного технической жесткости: напомним, что модель выводит действительные числа от 0 до 1, но, как вы понимаете, четыре статистики по определению являются двоичными. Не волнуйтесь, нам просто нужно определить пороговое значение th, и любые выходы выше th будут установлены на 1, а ниже th до 0.

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

#-------------- TRUE POSITIVES --------------#
def true_positives(y, pred, th=0.5):
TP = 0 #true positives
thresholded_preds = pred >= th # get thresholded predictions
TP = np.sum((y == 1) & (thresholded_preds == 1)) # compute TP
return TP
#-------------- TRUE NEGATIVES --------------#
def true_negatives(y, pred, th=0.5):
TN = 0 #true negatives
thresholded_preds = pred >= th # get thresholded predictions
TN = np.sum((y == 0) & (thresholded_preds == 0)) # compute TN
return TN
#-------------- FALSE POSITIVES --------------#
def false_positives(y, pred, th=0.5):
FP = 0 # false positives
thresholded_preds = pred >= th # get thresholded predictions
FP = np.sum((y == 0) & (thresholded_preds == 1)) # compute FP
return FP
#-------------- FALSE NEGATIVES --------------#
def false_negatives(y, pred, th=0.5):
FN = 0 # false negatives
thresholded_preds = pred >= th # get thresholded predictions
FN = np.sum((y == 1) & (thresholded_preds == 0)) # compute FN
return FN

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

df = pd.DataFrame({'y_test': [1,1,0,0,0,0,0,0,0,1,1,1,1,1],
'preds_test': [0.8,0.7,0.4,0.3,0.2,0.5,0.6,0.7,0.8,0.1,0.2,0.3,0.4,0],
'category': ['TP','TP','TN','TN','TN','FP','FP','FP','FP','FN','FN','FN','FN','FN']
})
df # Show data 

Мы можем сравнить прогнозируемые результаты с фактическими данными.

# take a look at predictions and ground truth 
y_test = df['y_test']
preds_test = df['preds_test']
threshold = 0.5
print(f"""Predicted results:
TP: {true_positives(y_test, preds_test, threshold)}
TN: {true_negatives(y_test, preds_test, threshold)}
FP: {false_positives(y_test, preds_test, threshold)}
FN: {false_negatives(y_test, preds_test, threshold)}
""")
print("Expected results:")
print(f"TP: {sum(df['category'] == 'TP')}")
print(f"TN {sum(df['category'] == 'TN')}")
print(f"FP {sum(df['category'] == 'FP')}")
print(f"FN {sum(df['category'] == 'FN')}")

Мы получаем те же результаты, нашу модель можно считать «качественно хорошей», но как мы количественно насколько хороша наша модель?

Пришло время взглянуть на наш набор данных: давайте вычислим TP, TN, FP, FN для наших случаев.

#TP computation
TP=[]
for i in range(len(class_labels)):
    TP.append(true_positives(class_values[:,i], pred[:,i], 0.5))
#TN computation    
TN=[]
for i in range(len(class_labels)):
    TN.append(true_negatives(class_values[:,i], pred[:,i], 0.5))
   
#FP computation 
FP=[]
for i in range(len(class_labels)):
    FP.append(false_positives(class_values[:,i], pred[:,i], 0.5))
   
#FN computation 
FN=[]
for i in range(len(class_labels)):
    FN.append(false_negatives(class_values[:,i], pred[:,i], 0.5))
#create a results table
table=pd.DataFrame({'category' : class_labels,
'TP': TP,
'TN': TN,
'FP': FP,            
'FN': FN,            
})
table.set_index('category')

Первый кирпич: точность

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

Точный ответ на вопрос «насколько хороша наша модель?», только измеряя, как часто наша классификационная модель делает правильный прогноз:

Вероятностным образом мы можем интерпретировать точность как вероятность быть правильным: с точки зрения TP, TN, FN и FP, точность определяет долю истинно положительных и истинно отрицательных людей в общей группе испытуемых. Потом,

Теперь поработаем с нашим набором данных: мы проиллюстрируем, как вычислить точность, а затем протестируем ее на всем наборе данных.

def get_accuracy(y, pred, th=0.5):
accuracy = 0.0
TP = true_positives(y, pred, th = th)
FP = false_positives(y, pred, th = th)
TN = true_negatives(y, pred, th = th)
FN = false_negatives(y, pred, th = th)
accuracy = (TP + TN)/(TP + TN + FP + FN)  # Accuracy computation return accuracy
# Compute accuracy for the dataset classes
acc=[]
for i in range(len(class_labels)):
    acc.append(get_accuracy(class_values[:,i], pred[:,i], 0.5))
#create a results table
table2=pd.DataFrame({'category' : class_labels,
'accuracy': acc          
})
table2.set_index('category')

Что бы мы сказали о характеристиках нашей модели? Хорошо это или нет?

Если мы рассмотрим обнаружение пневмонии (точность = 0,675), модель определенно не будет точной. Взгляните вместо этого на обнаружение эмфиземы (точность = 0,889): это немного точнее! Таким образом, мы можем сказать, что наша классификационная модель лучше обнаруживает эмфизему, чем пневмонию.

Что произойдет, если мы рассмотрим другую модель, способную предсказать, нет ли у субъекта болезни эмфиземы (т.е. новая модель представляет собой простой бинарный классификатор)?

Вычислите точность для этой простой модели:

print('Emphysema disease accuracy =', get_accuracy(valid_results["Emphysema"].values, np.zeros(len(valid_results))))

Это отличный результат! Эта модель явно лучше первой.

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

Распространенность

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

Этот показатель относится к доле людей с болезнью в общем количестве субъектов (здоровые + больные) и легко определяется как соотношение между положительными примерами и размером выборки:

где y= 1 для положительных примеров.

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

def get_prevalence(y):
prevalence = 0.0
prevalence = np.sum(y)/len(y) #Prevalence computation
return prevalence
# Compute accuracy for the dataset classes
prev=[]
for i in range(len(class_labels)):
    prev.append(get_prevalence(class_values[:,i]))
#create a results table
table3=pd.DataFrame({'category' : class_labels,
'prevalence': prev         
})
table3.set_index('category')

Высокие баллы указывают на распространенные заболевания (например, инфильтрация, 0,192); низкие баллы указывают на редкие заболевания (например, грыжа, 0,002)

Чувствительность и специфичность

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

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

Специфичность - это вероятность того, что модель предсказывает отрицательные результаты для субъекта без заболевания: это доля примеров, классифицированных как отрицательные, в общем количестве отрицательных случаев.

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

Теперь давайте посмотрим, как вычислить эти метрики и как они работают с нашим набором данных:

#------SENSITIVITY------#
def get_sensitivity(y, pred, th=0.5):
sensitivity = 0.0
TP = true_positives(y, pred, th = th)
FN = false_negatives(y, pred, th = th)
sensitivity = TP/(TP + FN)
return sensitivity
#------SPECIFICITY------#
def get_specificity(y, pred, th=0.5):
specificity = 0.0
TN = true_negatives(y, pred, th = th)
FP = false_positives(y, pred, th = th)
specificity = TN/(TN + FP)
return specificity
# Compute accuracy for the dataset classes
sens=[]
spec=[]
for i in range(len(class_labels)):
    sens.append(get_sensitivity(class_values[:,i], pred[:,i], 0.5))
    spec.append(get_specificity(class_values[:,i], pred[:,i], 0.5))
#create a results table
table4=pd.DataFrame({'category' : class_labels,
'sensitivity': sens,
'specificity': spec         
})
table4.set_index('category')

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

PPV и NPV

Как мы только что убедились, чувствительность и специфичность не помогают с диагностической точки зрения. В клинике врач, использующий какую-либо модель, может быть заинтересован в другом подходе: если модель дает положительный прогноз для пациента, какова вероятность того, что он действительно болен? Это называется положительной прогностической ценностью (PPV) модели. Точно так же врач может захотеть узнать вероятность того, что пациент здоров, учитывая отрицательный прогноз модели. Это называется отрицательной прогностической ценностью (NPV) модели.

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

Так же, как и раньше, давайте посмотрим, как мы можем вычислить PPV и NPV, используя наши данные.

#------ PPV ------#
def get_ppv(y, pred, th=0.5):
    
    PPV = 0.0

  # get TP and FP using our previously defined functions
    TP = true_positives(y, pred, th = th)
    FP = false_positives(y, pred, th = th)
  # use TP and FP to compute PPV
    PPV = TP/(TP + FP)
    
    return PPV
#------ NPV ------#
def get_npv(y, pred, th=0.5):
    
    NPV = 0.0
 
  # get TN and FN using our previously defined functions
    TN = true_negatives(y, pred, th = th)
    FN = false_negatives(y, pred, th = th)
  # use TN and FN to compute NPV
    NPV = TN/(TN + FN)

    return NPV
# Compute PPV and NPV for the dataset classes
PPV=[]
NPV=[]
for i in range(len(class_labels)):
    PPV.append(get_ppv(class_values[:,i], pred[:,i], 0.5))
    NPV.append(get_npv(class_values[:,i], pred[:,i], 0.5))
#create a results table
table5=pd.DataFrame({'category' : class_labels,
'PPV': PPV,
'NPV': NPV        
})
table5.set_index('category')

Кривая рабочих характеристик приемника (ROC)

До сих пор мы действовали в предположении, что наша модель имеет порог прогнозирования 0,5. Но что будет, если мы его изменим? Как это изменение повлияет на эффективность нашей модели?

Кривая рабочих характеристик приемника (ROC) - это график, который показывает нам производительность нашей модели при изменении ее порогового значения прогноза. Чтобы построить кривую ROC, мы строим график истинных положительных результатов (TPR) против ложноположительных результатов (FPR) при различных настройках пороговых значений.

from sklearn.metrics import roc_curve, roc_auc_score
def get_ROC(y, pred, target_names):
    for i in range(len(target_names)):
      curve_function = roc_curve
      auc_roc = roc_auc_score(y[:, i], pred[:, i])
      label = target_names[i] + " AUC: %.3f " % auc_roc
      xlabel = "False positive rate"
      ylabel = "True positive rate"
      a, b, _ = curve_function(y[:, i], pred[:, i])
      plt.figure(1, figsize=(7, 7))
      plt.plot([0, 1], [0, 1], 'k--')
      plt.plot(a, b, label=label)
      plt.xlabel(xlabel)
      plt.ylabel(ylabel)
      plt.legend(loc='upper center', bbox_to_anchor=(1.3, 1),
                       fancybox=True, ncol=1)
#plot the curve
get_ROC(class_values, pred, class_labels)

Форма кривой ROC и площадь под ней (A rea Under the Curve AUC) говорят нам, насколько эффективна наша модель: хорошая модель представлена Кривая ROC закрывается до верхнего левого угла (где TPR составляет приблизительно 1, а FPR равно 0) и значением AUC, равным 1; плохая модель имеет кривую ROC, которая стремится к диагонали, и соответствующее значение AUC составляет около 0,5.

Доверительные интервалы

Наш набор данных - это всего лишь образец реального мира, а наши рассчитанные значения для всех вышеуказанных показателей являются оценкой реальных значений. Следовательно, было бы лучше количественно оценить эту неопределенность из-за выборки нашего набора данных. Мы сделаем это с помощью доверительных интервалов. 95% доверительный интервал для оценки 𝑠̂ параметра 𝑠 - это интервал 𝐼 = (𝑎, 𝑏), такой, что 95% времени, когда проводится эксперимент , истинное значение 𝑠 содержится в 𝐼. Более конкретно, если бы мы запускали эксперимент много раз, то доля тех экспериментов, для которых 𝐼 содержит истинный параметр, стремилась бы к 95%. Таким образом, 95% -ная достоверность не означает, что существует 95% -ная вероятность того, что s находится в пределах интервала. Это также не говорит о том, что 95% точности выборки находятся в пределах этого интервала.

В то время как некоторые оценки включают методы для аналитического вычисления доверительного интервала, более сложные статистические данные, такие как, например, AUC, затруднены. В этих случаях мы можем использовать метод под названием bootstrap. Бутстрап оценивает неопределенность путем повторной выборки набора данных с заменой. Для каждой передискретизации 𝑖 мы будем получать новую оценку 𝑠̂ (i). Затем мы можем оценить распределение 𝑠̂, используя распределение 𝑠̂ (i) для наших образцов начальной загрузки.

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

#------ computing intervals -------#
def bootstrap_auc(y, pred, classes, bootstraps = 100, fold_size = 1000):
    statistics = np.zeros((len(classes), bootstraps))
    for c in range(len(classes)):
        df = pd.DataFrame(columns=['y', 'pred'])
        df.loc[:, 'y'] = y[:, c]
        df.loc[:, 'pred'] = pred[:, c]
        # get positive examples for stratified sampling
        df_pos = df[df.y == 1]
        df_neg = df[df.y == 0]
        prevalence = len(df_pos) / len(df)
        for i in range(bootstraps):
            pos_sample = df_pos.sample(n = int(fold_size * prevalence), replace=True)
            neg_sample = df_neg.sample(n = int(fold_size * (1-prevalence)), replace=True)
            y_sample = np.concatenate([pos_sample.y.values, neg_sample.y.values])
            pred_sample = np.concatenate([pos_sample.pred.values, neg_sample.pred.values])
            score = roc_auc_score(y_sample, pred_sample)
            statistics[c][i] = score
    return statistics
statistics = bootstrap_auc(class_values, pred, class_labels)
#------ printing table ------#
table7 = pd.DataFrame(columns=["Mean AUC (CI 5%-95%)"])
for i in range(len(class_labels)):
    mean = statistics.mean(axis=1)[i]
    max_ = np.quantile(statistics, .95, axis=1)[i]
    min_ = np.quantile(statistics, .05, axis=1)[i]
    table7.loc[class_labels[i]] = ["%.2f (%.2f-%.2f)" % (mean, min_, max_)]

Кривая точности-отзыва

Точность и отзывчивость - два показателя, которые часто используются вместе:

  • Точность - это мера релевантности результата: она позволяет количественно оценить способность нашей модели не помечать отрицательный предмет как положительный. Оценка точности эквивалентна PPV
  • Напомнить - это показатель количества возвращаемых действительно релевантных результатов: это вероятность того, что положительный прогноз действительно окажется положительным. Оценка отзыва эквивалентна чувствительности

Кривая точности-отзыва (PRC) иллюстрирует взаимосвязь между точностью и отзывом для разных пороговых значений. Давайте посчитаем это:

#----- PRC -----#
from sklearn.metrics import average_precision_score, precision_recall_curve
def get_PRC(y, pred, target_names):
    for i in range(len(target_names)):
            precision, recall, _ = precision_recall_curve(y[:, i], pred[:, i])
            average_precision = average_precision_score(y[:, i], pred[:, i])
            label = target_names[i] + " Avg.: %.3f " % average_precision
            plt.figure(1, figsize=(7, 7))
            plt.step(recall, precision, where='post', label=label)
            plt.xlabel('Recall')
            plt.ylabel('Precision')
            plt.ylim([0.0, 1.05])
            plt.xlim([0.0, 1.0])
            plt.legend(loc='upper center', bbox_to_anchor=(1.3, 1),
                       fancybox=True, ncol=1)
#----- plot the curve -----#
get_PRC(class_values, pred, class_labels) 

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

F1-Score

F1-score сочетает в себе точность и запоминаемость по среднему значению:

#----- F1-score -----#
def get_f1(y, pred, th=0.5):
  
    F1 = 0.0
    
    # get precision and recall using our previously defined functions
 
    precision=get_ppv(y, pred, th = th)
    recall=get_sensitivity(y, pred, th = th)
# use precision and recall to compute F1
    F1 = 2 * (precision * recall) / (precision + recall)
    
    
    return F1

В качестве альтернативы мы можем просто использовать метрическую функцию полезности sklearn f1_score для вычисления этой меры:

from sklearn.metrics import f1_score

Теперь давайте посчитаем оценку F1 для классов в нашем наборе данных:

f1=[]
for i in range(len(class_labels)):
 f1.append(get_f1(class_values[:,i], pred[:,i]))
 
#create a results table
table8=pd.DataFrame({‘category’ : class_labels,
‘F1’: f1 
})
table8.set_index(‘category’)

F1-оценка находится в диапазоне [0,1]: идеальная диагностическая модель имеет F1-оценку 1.

Будьте осторожны, злоупотребляя оценкой F1, хотя на практике разные типы неправильной классификации влекут за собой разные затраты. Дэвид Хэнд и многие другие критикуют широкое распространение, поскольку оно придает одинаковое значение точности и запоминанию.

Заключение

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

table9=pd.DataFrame({'category' : class_labels,
'TP': TP,
'TN': TN,
'FP': FP,            
'FN': FN,
'accuracy': acc,
'prevalence': prev,
'sensitivity': sens,
'specificity': spec, 
'PPV': ppv,
'NPV': npv,
'AUC': auc_value,
'Mean AUC (CI 5%-95%)' :table7['Mean AUC (CI 5%-95%)'],                  
'F1': f1       
})
table9.set_index('category')

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

Это первая серия из серии, посвященной применению ИИ в Медицинской диагностике, история только начинается. В следующем мы покажем вам, как работать с набором данных и что такое Исследовательский анализ данных (EDA). Поскольку мы собираемся иметь дело с рентгеновскими изображениями, необходимо будет ввести особый вид нейронной сети, а именно архитектуру сверточной нейронной сети (CNN).

Взгляните на полную реализацию нашего кода здесь, на Github (и отметьте нас, если вам нравится наш проект!)

Использованная литература:

  1. Меры диагностической точности: основные определения, А.М. Симундич.

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

3. Оценка моделей машинного обучения, А. Чжэн, O’Reilly Media Inc.

4. AI для медицинской диагностики, deeplearning.ai, Coursera, Эндрю Н.Г.