paint-brush
डेटा साइंस साक्षात्कार प्रश्न: स्क्रैच से आरओसी और सटीक रिकॉल कर्व्स बनानाद्वारा@varunnakra1
501 रीडिंग
501 रीडिंग

डेटा साइंस साक्षात्कार प्रश्न: स्क्रैच से आरओसी और सटीक रिकॉल कर्व्स बनाना

द्वारा Varun Nakra8m2024/05/30
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

यह डेटा साइंस इंटरव्यू के लोकप्रिय प्रश्नों में से एक है, जिसमें किसी व्यक्ति को आरओसी और इसी तरह के वक्रों को स्क्रैच से बनाना होता है। इस कहानी के उद्देश्यों के लिए, मैं मान लूंगा कि पाठक इन मीट्रिक्स के पीछे के अर्थ और गणनाओं से अवगत हैं और वे क्या दर्शाते हैं और उनकी व्याख्या कैसे की जाती है। हम आवश्यक लाइब्रेरीज़ को आयात करने से शुरू करते हैं (हम गणित को भी आयात करते हैं क्योंकि उस मॉड्यूल का उपयोग गणनाओं में किया जाता है)
featured image - डेटा साइंस साक्षात्कार प्रश्न: स्क्रैच से आरओसी और सटीक रिकॉल कर्व्स बनाना
Varun Nakra HackerNoon profile picture
0-item
1-item

यह डेटा साइंस इंटरव्यू के लोकप्रिय प्रश्नों में से एक है, जिसके लिए किसी व्यक्ति को आरओसी और इसी तरह के वक्रों को स्क्रैच से बनाना होता है, यानी, हाथ में कोई डेटा नहीं होता। इस कहानी के उद्देश्यों के लिए, मैं मान लूंगा कि पाठक इन मीट्रिक्स के पीछे के अर्थ और गणनाओं से अवगत हैं और वे क्या दर्शाते हैं और उनकी व्याख्या कैसे की जाती है। इसलिए, मैं इसके कार्यान्वयन पहलू पर ध्यान केंद्रित करूंगा। हम आवश्यक लाइब्रेरीज़ को आयात करने से शुरू करते हैं (हम गणित को भी आयात करते हैं क्योंकि उस मॉड्यूल का उपयोग गणनाओं में किया जाता है)

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


पहला कदम 1 (बुरे) और 0 (अच्छे) का 'वास्तविक' डेटा तैयार करना है, क्योंकि इसका उपयोग उपरोक्त मेट्रिक्स के माध्यम से मॉडल सटीकता की गणना और तुलना करने के लिए किया जाएगा। इस लेख के लिए, हम यूनिफ़ॉर्म वितरण से "वास्तविक वेक्टर" बनाएंगे। बाद के और संबंधित लेख के लिए, हम द्विपद वितरण का उपयोग करेंगे।

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


उपरोक्त कोड [0,1] से संबंधित 10,000 यादृच्छिक पूर्णांक उत्पन्न करता है जो वास्तविक बाइनरी वर्ग का हमारा वेक्टर है। अब, निश्चित रूप से हमें इन वास्तविक वर्गों के लिए संभावनाओं के एक और वेक्टर की आवश्यकता है। आम तौर पर, ये संभावनाएँ मशीन लर्निंग मॉडल का आउटपुट होती हैं। हालाँकि, यहाँ हम कुछ उपयोगी धारणाएँ बनाते हुए उन्हें यादृच्छिक रूप से उत्पन्न करेंगे। आइए मान लें कि अंतर्निहित मॉडल एक 'लॉजिस्टिक रिग्रेशन मॉडल' है, इसलिए, लिंक फ़ंक्शन लॉजिस्टिक या लॉगिट है।


नीचे दिया गया चित्र मानक लॉजिस्टिक फ़ंक्शन का वर्णन करता है। लॉजिस्टिक रिग्रेशन मॉडल के लिए, अभिव्यक्ति -k(x-x_0) को 'स्कोर' से बदल दिया जाता है। 'स्कोर' मॉडल सुविधाओं और मॉडल मापदंडों का भारित योग है।

एक चर x का उपयोग करके लॉजिस्टिक रिग्रेशन - घातांक 'स्कोर' है


इस प्रकार, जब 'स्कोर' = 0, तो लॉजिस्टिक फ़ंक्शन को Y-अक्ष पर 0.5 से गुज़रना चाहिए। ऐसा इसलिए है क्योंकि logit(p) = log-odds(p) = log(p/(1-p)) = 0 => p = 1-p => p =0.5। यह भी ध्यान दें कि जब 'स्कोर' उच्च सकारात्मक या उच्च नकारात्मक मान प्राप्त करता है, तो फ़ंक्शन या तो 1 (खराब) या 0 (अच्छा) की ओर बढ़ता है। इस प्रकार, 'स्कोर' का निरपेक्ष मान जितना अधिक होगा, पूर्वानुमानित संभावना भी उतनी ही अधिक होगी। लेकिन हम क्या स्कोर कर रहे हैं? हम अपने 'वास्तविक वेक्टर' में मौजूद प्रत्येक डेटा इनपुट को स्कोर कर रहे हैं। फिर, यदि हम यह मानना चाहते हैं कि हमारा अंतर्निहित लॉजिस्टिक प्रतिगमन मॉडल कुशल है, यानी, पूर्वानुमानित है; तो मॉडल को खराब बनाम सामान को तुलनात्मक रूप से उच्च स्कोर प्रदान करना चाहिए। इस प्रकार, खराब के पास अधिक सकारात्मक स्कोर होने चाहिए (यह सुनिश्चित करने के लिए कि पूर्वानुमानित संभावना 1 के करीब है) इसे मॉडल द्वारा रैंक ऑर्डरिंग के रूप में जाना जाता है। दूसरे शब्दों में, स्कोर के बीच भेदभाव या अलगाव होना चाहिए और इसलिए बुरे बनाम अच्छे की अनुमानित संभावनाएं होनी चाहिए। चूंकि, हमने देखा है कि 0 का स्कोर अच्छे की संभावना = बुरे की संभावना = 0.5 का तात्पर्य है; इसका मतलब यह होगा कि मॉडल अच्छे और बुरे के बीच अंतर करने में असमर्थ है। लेकिन चूंकि हम जानते हैं कि डेटा बिंदु वास्तव में या तो अच्छा या बुरा होगा, इसलिए, 0.5 का स्कोर मॉडल से सबसे खराब संभावित स्कोर है। यह हमें अगले चरण पर जाने के लिए कुछ अंतर्ज्ञान देता है।


मानक सामान्य वितरण का उपयोग करके स्कोर को यादृच्छिक रूप से उत्पन्न किया जा सकता है, जिसका माध्य 0 और मानक विचलन 1 है। हालाँकि, हम बुरे और अच्छे के लिए अलग-अलग पूर्वानुमानित स्कोर चाहते हैं। हम यह भी चाहते हैं कि बुरे स्कोर अच्छे स्कोर से अधिक होने चाहिए। इस प्रकार, हम मानक सामान्य वितरण का उपयोग करते हैं और अच्छे और बुरे के बीच अलगाव बनाने के लिए इसके माध्य को स्थानांतरित करते हैं।

 # 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) 

अच्छा (नारंगी) बनाम बुरा (नीला) स्कोर

उपर्युक्त कोड में, हमने दो अलग-अलग मानक सामान्य वितरणों से खराब स्कोर और अच्छे स्कोर का नमूना लिया, लेकिन हमने दोनों के बीच एक अलगाव बनाने के लिए उन्हें स्थानांतरित कर दिया। हम खराब स्कोर (छवि में नीले रंग द्वारा दर्शाए गए) को दाईं ओर 1 से और इसके विपरीत बाईं ओर 1 से स्थानांतरित करते हैं। यह निम्नलिखित सुनिश्चित करता है:

  1. काफी उच्च (दृश्य के अनुसार) मामलों के लिए खराब स्कोर अच्छे स्कोर से अधिक हैं
  2. खराब स्कोर में आनुपातिक रूप से सकारात्मक स्कोर की संख्या अधिक होती है और अच्छे स्कोर में आनुपातिक रूप से नकारात्मक स्कोर की संख्या अधिक होती है


हम निश्चित रूप से 'शिफ्ट' पैरामीटर को बढ़ाकर और इसे 1 से अधिक मान देकर इस पृथक्करण को अधिकतम कर सकते हैं। हालाँकि, इस कहानी में, हम ऐसा नहीं करेंगे। हम बाद की संबंधित कहानियों में इसका पता लगाएँगे। अब, आइए इन अंकों द्वारा उत्पन्न संभावनाओं को देखें।

 # 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) 

अच्छा (नारंगी) बनाम बुरा (नीला) संभावनाएँ

जैसा कि पहले चर्चा की गई थी, जब 'स्कोर' को लॉजिस्टिक फ़ंक्शन के माध्यम से आगे बढ़ाया जाता है, तो हमें संभावनाएँ मिलती हैं। यह स्पष्ट है कि खराब संभावनाएँ (नीला रंग) अच्छी संभावनाओं (नारंगी रंग) (और 0 की ओर झुकी हुई) की तुलना में अधिक (और 1 की ओर झुकी हुई) हैं।


अगला कदम विश्लेषण के लिए वास्तविक और अनुमानित वैक्टर को एक ही डेटा फ़्रेम में संयोजित करना है। हम खराब संभावनाएँ तब निर्दिष्ट करते हैं जब डेटा इंस्टेंस वास्तव में खराब होता है और इसके विपरीत

 # 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')


अगला चरण बिन बनाना है। ऐसा इसलिए है क्योंकि हम जो वक्र बनाना चाहते हैं, वे प्रकृति में असतत हैं। प्रत्येक बिन के लिए, हम अपने वांछित मीट्रिक्स की संचयी गणना करते हैं। दूसरे शब्दों में, हम असतत यादृच्छिक चर - गुड्स और बैड्स के लिए संचयी वितरण फ़ंक्शन बनाते हैं।

  1. डिब्बों की संख्या मनमाना है (हम n_bins = 50 निर्दिष्ट करते हैं)।
  2. फ्लोर फ़ंक्शन के उपयोग पर ध्यान दें। ऐसा इसलिए है क्योंकि डेटा फ़्रेम की लंबाई 50 डिब्बों में समान रूप से विभाजित नहीं हो सकती है। इस प्रकार, हम इसका फ़्लोर लेते हैं और अपने कोड को इस तरह संशोधित करते हैं कि अंतिम डिब्बे (50वाँ डिब्बा) में अतिरिक्त अवलोकन शामिल होंगे (जो < 50 होंगे)।
 n_bins = 50 bin_size = math.floor(len(predicted_df) / n_bins) curve_metrics = []


हमें अपने संदर्भ के लिए खराब और माल की मात्रा और बिन के आकार को सूचीबद्ध करना चाहिए

 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)

खराब की संख्या: 4915

माल की संख्या: 5085

कुल डेटा बिंदुओं की संख्या: 10000

बिन आकार: 200.0


इसके बाद मुख्य कोड स्निपेट आता है जहां हम अंतर्निहित मेट्रिक्स की वास्तविक गणना करते हैं

 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])


ध्यान दें कि फॉर लूप 1 से n_bins तक चलेगा, यानी, यह अंत में एक को छोड़ देता है। इसलिए, हमारे पास 'स्टॉप वैल्यू' के रूप में n_bins + 1 है।


k = 1 से k = n_bins-1 के लिए, हम bin_size का उपयोग करके सत्य सकारात्मक, असत्य सकारात्मक, असत्य नकारात्मक, सत्य नकारात्मक, संचयी bds और संचयी माल की संचयी गणना करते हैं।


  1. ध्यान दें कि स्निपेट “predicted_df.loc[ : k* bin_size-1, "actual"].sum()” इंडेक्स = 0 से इंडेक्स = kbin_size-1 तक चलेगा। इस प्रकार, यह k *bin_size के बराबर चंक निकालता है। इसलिए, हम k *bin_size से 1 घटाते हैं

  2. इसी तरह, स्निपेट “predicted_df.loc[(k*bin_size) : , "actual"].sum()” इंडेक्स = k*bin_size से अंतिम इंडेक्स तक चलेगा। इस प्रकार, यदि बिन 0 से 49 (साइज़ 50) तक है, तो स्निपर इंडेक्स = 50 (जो bin_size के बराबर है) से आगे चलता है


k = n_bins के लिए, हम इसे डेटासेट के अंतिम इंडेक्स तक बढ़ाते हैं। जहाँ, स्निपेट “predicted_df.loc[ : , "actual"].sum()” इंडेक्सिंग के दौरान इंडेक्स = 0 से डेटाफ़्रेम के अंतिम इंडेक्स तक चलने पर सभी खराबियों को जोड़ देता है। हम इसे “TP = bads” से भी बदल सकते हैं। FN और TN दोनों = 0 हैं क्योंकि अंतिम कट-ऑफ पर हम सब कुछ 'खराब' के रूप में असाइन करते हैं। इसलिए, कोई गलत नकारात्मक (वास्तविक खराब) या सही नकारात्मक (वास्तविक अच्छा) नहीं बचा है। क्योंकि, k = n_bins होने पर नकारात्मक वर्ग मौजूद नहीं होता है।


यह जांचना उपयोगी है कि संचयी मैट्रिक्स कैसा दिखता है।

 curve_metrics 

आरओसी और प्रेसिजन-रिकॉल वक्र उत्पन्न करने के लिए सूची

ध्यान दें कि k = n_bins = 50 के लिए, हमने सभी सामान (5085) और सभी खराब सामान (4915) संचित कर लिए हैं।


अब हम वांछित वक्रों के लिए आवश्यक वास्तविक गणना करने के लिए तैयार हैं

 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. आरओसी वक्र संचयी खराब (Y-अक्ष) और संचयी माल (X-अक्ष) के बीच का वक्र है
  2. आरओसी वक्र संवेदनशीलता (जो संचयी बैड या रिकॉल भी है: वाई-अक्ष) और 1-विशिष्टता (एक्स-अक्ष) के बीच का वक्र है
  3. प्रेसिजन रिकॉल वक्र प्रेसिजन (Y-अक्ष) और रिकॉल (जो संवेदनशीलता या संचयी बुराइयाँ भी हैं: X-अक्ष) के बीच का वक्र है


बस इतना ही। अब हमारे पास वक्र रेखा खींचने के लिए आवश्यक सभी चीजें मौजूद हैं।

 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() 

सभी वक्र एक उच्च कुशल मॉडल के उपयोग की पुष्टि करते हैं (जैसा कि हमने शुरुआत में तैयार किया था)। यह इन वक्रों को शुरू से बनाने का हमारा कार्य पूरा करता है।


विचारणीय विषय - "जब वर्ग गंभीर रूप से असंतुलित हो जाते हैं तो इन वक्रों का क्या होता है?" यह अगली कहानी का विषय होगा।


यदि आपको यह पसंद आया तो कृपया मेरी अन्य कहानियाँ भी देखें।