paint-brush
Veri Bilimi Röportaj Sorusu: Sıfırdan ROC ve Hassas Geri Çağırma Eğrileri Oluşturmaile@varunnakra1
497 okumalar
497 okumalar

Veri Bilimi Röportaj Sorusu: Sıfırdan ROC ve Hassas Geri Çağırma Eğrileri Oluşturma

ile Varun Nakra8m2024/05/30
Read on Terminal Reader

Çok uzun; Okumak

Bu, ROC ve benzeri eğrilerin sıfırdan oluşturulmasını gerektiren popüler veri bilimi röportaj sorularından biridir. Bu hikayenin amaçları doğrultusunda, okuyucuların bu ölçümlerin ardındaki anlam ve hesaplamaların, neyi temsil ettiklerinin ve nasıl yorumlandıklarının farkında olduklarını varsayacağım. Gerekli kütüphaneleri içe aktararak başlıyoruz (hesaplamalarda o modül kullanıldığı için matematiği de içe aktarıyoruz)
featured image - Veri Bilimi Röportaj Sorusu: Sıfırdan ROC ve Hassas Geri Çağırma Eğrileri Oluşturma
Varun Nakra HackerNoon profile picture
0-item
1-item

Bu, ROC ve benzeri eğrilerin sıfırdan oluşturulmasını gerektiren, yani elde veri olmayan, popüler veri bilimi röportaj sorularından biridir. Bu hikayenin amaçları doğrultusunda, okuyucuların bu ölçümlerin ardındaki anlam ve hesaplamaların, neyi temsil ettiklerinin ve nasıl yorumlandıklarının farkında olduklarını varsayacağım. Bu nedenle konunun uygulama yönüne odaklanacağım. Gerekli kütüphaneleri içe aktararak başlıyoruz (hesaplamalarda o modül kullanıldığı için matematiği de içe aktarıyoruz)

 import pandas as pd import numpy as np import matplotlib.pyplot as plt import math


İlk adım, 1'lerin (kötüler) ve 0'ların (iyiler) 'gerçek' verilerini oluşturmaktır, çünkü bu, yukarıda bahsedilen ölçümler aracılığıyla model doğruluğunu hesaplamak ve karşılaştırmak için kullanılacaktır. Bu yazı için Düzgün dağılımdan “gerçek vektörü” yaratacağız. Sonraki ve ilgili yazımızda Binom dağılımını kullanacağız.

 actual = np.random.randint(0, 2, 10000)


Yukarıdaki kod, gerçek ikili sınıfın vektörümüz olan [0,1]'e ait 10.000 rastgele tamsayı üretir. Şimdi elbette bu gerçek sınıflar için başka bir olasılık vektörüne ihtiyacımız var. Normalde bu olasılıklar bir Makine öğrenimi modelinin çıktısıdır. Ancak burada bazı yararlı varsayımlar yaparak bunları rastgele oluşturacağız. Temel modelin bir 'lojistik regresyon modeli' olduğunu, dolayısıyla bağlantı fonksiyonunun lojistik veya logit olduğunu varsayalım.


Aşağıdaki şekil standart lojistik fonksiyonunu açıklamaktadır. Lojistik regresyon modeli için -k(x-x_0) ifadesinin yerine bir 'puan' gelir. 'Puan', model özelliklerinin ve model parametrelerinin ağırlıklı toplamıdır.

tek değişken x kullanan lojistik regresyon - üs 'puan'dır


Dolayısıyla 'puan' = 0 olduğunda lojistik fonksiyonun Y ekseninde 0,5'i geçmesi gerekir. Bunun nedeni logit(p) = log-odds(p) = log(p/(1-p)) = 0 => p = 1-p => p =0,5'tir. Ayrıca 'puan' yüksek pozitif veya yüksek negatif değerlere ulaştığında fonksiyonun asimptotik olarak 1'e (kötü) veya 0'a (iyi) doğru hareket ettiğine dikkat edin. Dolayısıyla 'puan'ın mutlak değeri ne kadar yüksek olursa, tahmin edilen olasılık da o kadar yüksek olur. Ama ne puan alıyoruz? 'Gerçek vektörümüzde' bulunan her veri girişini puanlıyoruz. O halde, temeldeki lojistik regresyon modelimizin yetenekli, yani tahmine dayalı olduğunu varsaymak istersek; model, kötülere ve iyilere nispeten daha yüksek puanlar vermelidir. Bu nedenle, kötülerin daha fazla pozitif puana sahip olması (tahmin edilen olasılığın 1'e yakın olmasını sağlamak için) ve iyilerin daha fazla negatif puana sahip olması (tahmin edilen olasılığın 0'a yakın olmasını sağlamak için) gerekir. Bu, modele göre sıralama sıralaması olarak bilinir. Başka bir deyişle, puanlar arasında ayrımcılık veya ayrım olmalı ve dolayısıyla iyi ve kötünün tahmin edilen olasılıkları olmalıdır. Çünkü 0 puanının iyi olasılığı = kötü olasılığı = 0,5 anlamına geldiğini gördük; bu, modelin iyi ile kötüyü ayırt edemediği anlamına gelir. Ancak veri noktasının aslında iyi ya da kötü olacağını bildiğimizden, modelden alınabilecek en kötü puan 0,5'tir. Bu bize bir sonraki adıma geçmek için biraz sezgi verir.


Puanlar, ortalaması 0 ve standart sapması 1 olan Standart Normal dağılım kullanılarak rastgele oluşturulabilir. Ancak kötüler ve iyiler için farklı tahmin edilen puanlar istiyoruz. Ayrıca kötü puanların iyi puanlardan yüksek olmasını istiyoruz. Bu nedenle, standart normal dağılımı kullanıyoruz ve iyiler ile kötüler arasında bir ayrım yaratmak için ortalamasını değiştiriyoruz.

 # scores for bads bads = np.random.normal(0, 1, actual.sum()) + 1 # scores for goods goods = np.random.normal(0, 1, len(actual) - actual.sum()) - 1 plt.hist(bads) plt.hist(goods) 

İyi (turuncu) ve kötü (mavi) puanlar

Bahsi geçen kodda, kötü puanları ve iyi puanları iki farklı standart normal dağılımdan örnekledik ancak ikisi arasında bir ayrım oluşturacak şekilde bunları kaydırdık. Kötü puanları (resimdeki mavi renkle temsil edilir) 1 sağa ve tam tersi 1 sola kaydırıyoruz. Bu aşağıdakileri sağlar:

  1. Oldukça yüksek (görsele göre) vakalarda kötü puanlar iyi puanlardan daha yüksektir
  2. Kötü puanlar orantılı olarak daha fazla pozitif puana sahiptir ve iyi puanlar orantılı olarak daha fazla negatif puana sahiptir


Elbette 'shift' parametresini artırarak ve ona 1'den büyük değerler atayarak bu ayrımı maksimuma çıkarabiliriz. Ancak bu hikayede bunu yapmayacağız. Bunu daha sonraki ilgili hikayelerde inceleyeceğiz. Şimdi bu puanların oluşturduğu olasılıklara bakalım.

 # prob for bads bads_prob = list((map(lambda x: 1/(1 + math.exp(-x)), bads))) # prob for goods goods_prob = list((map(lambda x: 1/(1 + math.exp(-x)), goods))) plt.hist(bads_prob) plt.hist(goods_prob) 

İyi (turuncu) ve kötü (mavi) olasılıklar

Daha önce tartışıldığı gibi, 'puanlar' lojistik fonksiyona aktarıldığında olasılıkları elde ederiz. Kötü olasılıkların (mavi renk) iyi olasılıklardan (turuncu renk) (ve 0'a doğru çarpık) daha yüksek (ve 1'e doğru çarpık) olduğu açıktır.


Bir sonraki adım, gerçek ve tahmin edilen vektörleri analiz için tek bir veri çerçevesinde birleştirmektir. Veri örneğinin gerçekten kötü olduğu durumlarda kötü olasılıklar atarız ve bunun tersi de geçerlidir.

 # create predicted array bads = 0 goods = 0 predicted = np.zeros((10000)) for idx in range(0, len(actual)): if actual[idx] == 1: predicted[idx] = bads_prob[bads] bads += 1 else: predicted[idx] = goods_prob[goods] goods += 1 actual_df = pd.DataFrame(actual, columns=['actual']) predicted_df = pd.DataFrame(predicted, columns=['predicted']) predicted_df = pd.concat([actual_df, predicted_df], axis = 1) predicted_df = predicted_df.sort_values(['predicted'], ascending = False).reset_index() predicted_df = predicted_df.drop(columns = 'predicted')


Bir sonraki adım kutular oluşturmaktır. Bunun nedeni, oluşturmak istediğimiz eğrilerin doğası gereği ayrık olmasıdır. Her kutu için istediğimiz metrikleri kümülatif olarak hesaplıyoruz. Başka bir deyişle, ayrık rastgele değişkenler (iyiler ve kötüler) için kümülatif dağılım fonksiyonları üretiyoruz.

  1. Kutu sayısı keyfidir (n_bins = 50 atarız).
  2. Zemin fonksiyonunun kullanımına dikkat edin. Bunun nedeni, veri çerçevesinin uzunluğunun 50 bölmeye eşit olarak bölünemeyebilmesidir. Böylece, sözü alıyoruz ve kodumuzu, son kutunun (50. kutu) ekstra gözlemleri içerecek şekilde (< 50 olacak) değiştiriyoruz.
 n_bins = 50 bin_size = math.floor(len(predicted_df) / n_bins) curve_metrics = []


Referans olarak kötü ve mal hacimlerini ve kutu boyutlarını listelemeliyiz

 print("number of bads:", bads) print("number of goods:", goods) print("number of total data points:", len(actual)) print("bin size:", len(predicted_df) / n_bins)

kötülerin sayısı: 4915

mal sayısı: 5085

toplam veri noktası sayısı: 10000

kutu boyutu: 200,0


Daha sonra, temel metriklerin gerçek hesaplamalarını yaptığımız ana kod pasajı gelir.

 for k in range(1, n_bins + 1): if k < n_bins: TP = predicted_df.loc[ : k*bin_size-1, "actual"].sum() FP = k*bin_size - TP FN = predicted_df.loc[(k*bin_size) : , "actual"].sum() TN = len(actual) - k*bin_size - FN cum_bads = predicted_df.loc[ : k*bin_size-1, "actual"].sum() cum_goods = k*bin_size - cum_bads else: TP = predicted_df.loc[ : , "actual"].sum() FP = len(actual) - TP FN = 0 TN = 0 cum_bads = bads cum_goods = goods curve_metrics.append([k, TP, FP, TN, FN, cum_bads, cum_goods])


For döngüsünün 1'den n_bins'e kadar çalışacağına, yani sonda bir tanesini dışarıda bırakacağına dikkat edin. Bu nedenle 'durma değeri' olarak n_bins + 1'e sahibiz.


k = 1'den k = n_bins-1'e kadar bin_size kullanarak Doğru pozitifler, Yanlış Pozitifler, Yanlış Negatifler, Doğru Negatifler, kümülatif bd'ler ve kümülatif malların kümülatif hesaplamalarını yaparız.


  1. “predicted_df.loc[ : k* bin_size-1, "actual"].sum()” kod parçasının index = 0'dan index = kbin_size-1'e kadar çalışacağına dikkat edin. Böylece k *bin_size değerine eşit olan parçayı çıkarır . Bu nedenle k *bin_size'den 1 çıkarıyoruz

  2. Benzer şekilde, "predicted_df.loc[(k*bin_size) : , "actual"].sum()" snippet'i index = k*bin_size'den son indekse kadar çalışacaktır. Bu nedenle, eğer bin 0 ila 49 arasındaysa (boyut 50), keskin nişancı index = 50'den (bin_size'ye eşittir) itibaren çalışır.


k = n_bins için onu veri kümesinin son dizinine kadar genişletiyoruz. Burada, "predicted_df.loc[ : , "actual"].sum()" pasajı, indeksleme indeks = 0'dan veri çerçevesinin son indeksine kadar çalışırken tüm kötüleri özetler. Bunu “TP = bads” ile de değiştirebiliriz. FN ve TN'nin her ikisi de = 0'dır çünkü son kesimde her şeyi 'kötü' olarak atarız. Bu nedenle, Yanlış Negatif (gerçek kötü) veya Gerçek Negatif (gerçek iyi) kalmamıştır. Çünkü k = n_bins olduğunda negatif sınıf mevcut değildir.


Kümülatif matrisin neye benzediğini kontrol etmek faydalıdır.

 curve_metrics 

ROC ve Hassasiyet-Geri Çağırma eğrileri oluşturma listesi

k = n_bins = 50 için tüm iyileri (5085) ve tüm kötüleri (4915) biriktirdiğimize dikkat edin.


Artık istenen eğriler için gereken gerçek hesaplamaları yapmaya hazırız

 curve_metrics_df = pd.DataFrame(curve_metrics, columns=["cut_off_index", "TP", "FP", "TN", "FN", "cum_bads", "cum_goods"]) curve_metrics_df["cum%bads"] = curve_metrics_df["cum_bads"] / (actual.sum()) curve_metrics_df["cum%goods"] = curve_metrics_df["cum_goods"] / (len(actual) - actual.sum()) curve_metrics_df["precision"] = curve_metrics_df["TP"] / (curve_metrics_df["TP"] + curve_metrics_df["FP"]) curve_metrics_df["recall"] = curve_metrics_df["TP"] / (curve_metrics_df["TP"] + curve_metrics_df["FN"]) curve_metrics_df["sensitivity"] = curve_metrics_df["TP"] / (curve_metrics_df["TP"] + curve_metrics_df["FN"]) # specificity is the recall on the negative class curve_metrics_df["specificity"] = curve_metrics_df["TN"] / (curve_metrics_df["TN"] + curve_metrics_df["FP"])
  1. ROC eğrisi, kümülatif kötüler (Y ekseni) ile kümülatif iyiler (X ekseni) arasındaki bir eğridir.
  2. ROC eğrisi, hassasiyet (aynı zamanda kümülatif kötüler veya hatırlamadır: Y ekseni) ile 1 özgüllük (X ekseni) arasındaki bir eğridir.
  3. Hassasiyet Geri Çağırma eğrisi, Hassasiyet (Y ekseni) ile Geri Çağırma (aynı zamanda hassasiyet veya kümülatif kötüler: X ekseni) arasındaki bir eğridir.


Bu kadar. Artık eğrilerimizi çizmek için gereken her şeye sahibiz.

 plt.plot(curve_metrics_df["cum%goods"], curve_metrics_df["cum%bads"], label ="roc curve") plt.xlabel("cum%goods") plt.ylabel("cum%bads") plt.title("ROC Curve") plt.legend() plt.show() plt.plot(1 - curve_metrics_df["specificity"], curve_metrics_df["sensitivity"], label ="sensitivity specificity curve") plt.xlabel("1 - Specificity") plt.ylabel("Sensitivity") plt.title("Sensitivity vs 1-Specificity Curve") plt.legend() plt.show() plt.plot(curve_metrics_df["recall"], curve_metrics_df["precision"], label ="precision recall curve") plt.xlabel("Precision") plt.ylabel("Recall") plt.title("Precision Recall Curve") plt.legend() plt.show() 

Tüm eğriler oldukça yetenekli bir modelin kullanıldığını doğrulamaktadır (başlangıçta formüle ettiğimiz gibi). Bu, bu eğrileri sıfırdan oluşturma görevini tamamlar.


Düşünmeye değer bir şey: “Sınıflar ciddi oranda dengesiz olduğunda bu eğrilere ne olur?” Bu bir sonraki hikayenin konusu olacak.


Eğer bunu beğendiyseniz lütfen diğer hikayelerime de bir göz atın.