paint-brush
ডেটা সায়েন্স ইন্টারভিউ প্রশ্ন: স্ক্র্যাচ থেকে ROC এবং যথার্থ রিকল কার্ভ তৈরি করাদ্বারা@varunnakra1
501 পড়া
501 পড়া

ডেটা সায়েন্স ইন্টারভিউ প্রশ্ন: স্ক্র্যাচ থেকে ROC এবং যথার্থ রিকল কার্ভ তৈরি করা

দ্বারা Varun Nakra8m2024/05/30
Read on Terminal Reader

অতিদীর্ঘ; পড়তে

এটি একটি জনপ্রিয় ডেটা সায়েন্স ইন্টারভিউ প্রশ্ন যার জন্য একজনকে শুরু থেকে ROC এবং অনুরূপ বক্ররেখা তৈরি করতে হয়। এই গল্পের উদ্দেশ্যে, আমি অনুমান করব যে পাঠকরা এই মেট্রিক্সের অর্থ এবং গণনা সম্পর্কে সচেতন এবং তারা কী উপস্থাপন করে এবং কীভাবে ব্যাখ্যা করা হয়। আমরা প্রয়োজনীয় লাইব্রেরি আমদানি করে শুরু করি (আমরা গণিতও আমদানি করি কারণ সেই মডিউলটি গণনায় ব্যবহৃত হয়)
featured image - ডেটা সায়েন্স ইন্টারভিউ প্রশ্ন: স্ক্র্যাচ থেকে ROC এবং যথার্থ রিকল কার্ভ তৈরি করা
Varun Nakra HackerNoon profile picture
0-item
1-item

এটি জনপ্রিয় ডেটা সায়েন্স ইন্টারভিউ প্রশ্নগুলির মধ্যে একটি যার জন্য একজনকে স্ক্র্যাচ থেকে ROC এবং অনুরূপ বক্ররেখা তৈরি করতে হয়, অর্থাৎ হাতে কোনও ডেটা নেই৷ এই গল্পের উদ্দেশ্যে, আমি অনুমান করব যে পাঠকরা এই মেট্রিক্সের অর্থ এবং গণনা সম্পর্কে সচেতন এবং তারা কী উপস্থাপন করে এবং কীভাবে ব্যাখ্যা করা হয়। অতএব, আমি একই বাস্তবায়নের দিকে মনোনিবেশ করব। আমরা প্রয়োজনীয় লাইব্রেরি আমদানি করে শুরু করি (আমরা গণিতও আমদানি করি কারণ সেই মডিউলটি গণনায় ব্যবহৃত হয়)

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


প্রথম ধাপ হল 1s (খারাপ) এবং 0s (মাল) এর 'প্রকৃত' ডেটা তৈরি করা, কারণ এটি পূর্বোক্ত মেট্রিক্সের মাধ্যমে মডেলের নির্ভুলতা গণনা এবং তুলনা করতে ব্যবহৃত হবে। এই নিবন্ধটির জন্য, আমরা ইউনিফর্ম ডিস্ট্রিবিউশন থেকে "প্রকৃত ভেক্টর" তৈরি করব। পরবর্তী এবং সম্পর্কিত নিবন্ধের জন্য, আমরা দ্বিপদ বিতরণ ব্যবহার করব।

 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 এর স্কোর বোঝায় ভাল সম্ভাবনা = খারাপের সম্ভাবনা = 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) 

পণ্য (কমলা) বনাম খারাপ (নীল) সম্ভাবনা

যেমন আগে আলোচনা করা হয়েছে, যখন 'স্কোর' লজিস্টিক ফাংশনের মাধ্যমে পুশ করা হয়, তখন আমরা সম্ভাব্যতা পাই। এটা স্পষ্ট যে খারাপ সম্ভাবনাগুলি (নীল রঙ) ভাল সম্ভাবনার (কমলা রঙের) চেয়ে বেশি (এবং 1 এর দিকে তির্যক) (এবং 0 এর দিকে তির্যক)।


পরের ধাপ হল বাস্তব এবং পূর্বাভাসিত ভেক্টরগুলিকে বিশ্লেষণের জন্য একটি একক ডেটা ফ্রেমে একত্রিত করা। আমরা খারাপ সম্ভাব্যতা নির্ধারণ করি যেখানে ডেটা উদাহরণটি আসলে খারাপ এবং এর বিপরীত

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


লক্ষ্য করুন যে for loop 1 থেকে n_bins পর্যন্ত চলবে, অর্থাৎ, এটি শেষে একটি ছেড়ে যায়। অতএব, আমাদের কাছে 'স্টপ ভ্যালু' হিসেবে n_bins + 1 আছে।


k = 1 থেকে k = n_bins-1-এর জন্য, আমরা bin_size ব্যবহার করে সত্য ধনাত্মক, মিথ্যা ধনাত্মক, মিথ্যা নেতিবাচক, সত্য নেতিবাচক, ক্রমবর্ধমান বিডিএস এবং ক্রমবর্ধমান পণ্যগুলির ক্রমবর্ধমান গণনা করি।


  1. লক্ষ্য করুন যে স্নিপেট “predicted_df.loc[ : k* bin_size-1, "actual"].sum()” index = 0 থেকে index = 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 

ROC এবং যথার্থ-রিকল কার্ভ তৈরি করার জন্য তালিকা

লক্ষ্য করুন যে 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. ROC বক্ররেখা হল ক্রমবর্ধমান ব্যাডস (Y-অক্ষ) এবং ক্রমবর্ধমান পণ্যের (X-অক্ষ) মধ্যে একটি বক্ররেখা।
  2. ROC বক্ররেখা হল সংবেদনশীলতা (যা ক্রমবর্ধমান খারাপ বা প্রত্যাহার : Y-অক্ষ) এবং 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() 

সমস্ত বক্ররেখা একটি অত্যন্ত দক্ষ মডেলের ব্যবহার নিশ্চিত করে (যেমন আমরা শুরুতে প্রণয়ন করেছি)। এই সম্পূর্ণ হল স্ক্র্যাচ থেকে এই বক্ররেখা তৈরির কাজ।


চিন্তার জন্য খাদ্য - "যখন ক্লাসগুলি মারাত্মকভাবে ভারসাম্যহীন হয় তখন এই বক্ররেখাগুলির কী হয়?" এটি পরবর্তী গল্পের বিষয় হবে।


আপনি যদি এটি পছন্দ করেন, তাহলে অনুগ্রহ করে আমার অন্যান্য গল্পগুলি দেখুন।