यह डेटा साइंस इंटरव्यू के लोकप्रिय प्रश्नों में से एक है, जिसके लिए किसी व्यक्ति को आरओसी और इसी तरह के वक्रों को स्क्रैच से बनाना होता है, यानी, हाथ में कोई डेटा नहीं होता। इस कहानी के उद्देश्यों के लिए, मैं मान लूंगा कि पाठक इन मीट्रिक्स के पीछे के अर्थ और गणनाओं से अवगत हैं और वे क्या दर्शाते हैं और उनकी व्याख्या कैसे की जाती है। इसलिए, मैं इसके कार्यान्वयन पहलू पर ध्यान केंद्रित करूंगा। हम आवश्यक लाइब्रेरीज़ को आयात करने से शुरू करते हैं (हम गणित को भी आयात करते हैं क्योंकि उस मॉड्यूल का उपयोग गणनाओं में किया जाता है)
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) को 'स्कोर' से बदल दिया जाता है। 'स्कोर' मॉडल सुविधाओं और मॉडल मापदंडों का भारित योग है।
इस प्रकार, जब 'स्कोर' = 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 से अधिक मान देकर इस पृथक्करण को अधिकतम कर सकते हैं। हालाँकि, इस कहानी में, हम ऐसा नहीं करेंगे। हम बाद की संबंधित कहानियों में इसका पता लगाएँगे। अब, आइए इन अंकों द्वारा उत्पन्न संभावनाओं को देखें।
# 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')
अगला चरण बिन बनाना है। ऐसा इसलिए है क्योंकि हम जो वक्र बनाना चाहते हैं, वे प्रकृति में असतत हैं। प्रत्येक बिन के लिए, हम अपने वांछित मीट्रिक्स की संचयी गणना करते हैं। दूसरे शब्दों में, हम असतत यादृच्छिक चर - गुड्स और बैड्स के लिए संचयी वितरण फ़ंक्शन बनाते हैं।
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 और संचयी माल की संचयी गणना करते हैं।
ध्यान दें कि स्निपेट “predicted_df.loc[ : k* bin_size-1, "actual"].sum()” इंडेक्स = 0 से इंडेक्स = kbin_size-1 तक चलेगा। इस प्रकार, यह k *bin_size के बराबर चंक निकालता है। इसलिए, हम k *bin_size से 1 घटाते हैं
इसी तरह, स्निपेट “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"])
बस इतना ही। अब हमारे पास वक्र रेखा खींचने के लिए आवश्यक सभी चीजें मौजूद हैं।
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()
सभी वक्र एक उच्च कुशल मॉडल के उपयोग की पुष्टि करते हैं (जैसा कि हमने शुरुआत में तैयार किया था)। यह इन वक्रों को शुरू से बनाने का हमारा कार्य पूरा करता है।
विचारणीय विषय - "जब वर्ग गंभीर रूप से असंतुलित हो जाते हैं तो इन वक्रों का क्या होता है?" यह अगली कहानी का विषय होगा।
यदि आपको यह पसंद आया तो कृपया मेरी अन्य कहानियाँ भी देखें।