এই পর্বে যা শিখবেন
- হাতে আর কোডে দলভিত্তিক fairness metric বের করা, আর একটি model-এর পক্ষপাত কোথা থেকে আসে তা খুঁজে বের করা
- Protected attribute মুছলেও proxy দিয়ে পক্ষপাত কীভাবে ফিরে আসে, আর fairness-এর সংজ্ঞাগুলো কেন একসঙ্গে মেলে না
- Re-weighting, দলভিত্তিক সীমা, ভালো ডেটা আর “চালু না করা”: প্রতিকারের পথ আর প্রতিটির দাম
- বেনামি ডেটা থেকে মানুষ চেনার ঝুঁকি, k-anonymity আর differential privacy-র মূল ধারণা
- Model card, datasheet, আপিলের পথ, deploy-এর পরে নজরদারি আর একটি pre-deployment checklist লেখা
মডিউল ১২-এর শেষ পর্ব এটি। এই কোর্সে বারবার বলা হয়েছে, “দলভিত্তিক ভুলের হার মাপতে হবে”: পর্ব ৬০-এ সামগ্রিক FPR ছোট হলেও একটি দলের জন্য বড় হওয়া, পর্ব ৫৫-এ অতীতের অন্যায্যতা শিখে নেওয়া, পর্ব ৬-এ “problem of many hands”. এবার সেগুলো হাতে-কলমে: একটি model, কিছু সংখ্যা, কোড আর কয়েকটি নথি। নৈতিকতা, আইন আর নীতির গভীর আলোচনা পরে এই সাইটের একটি আলাদা AI Ethics কোর্সে আসবে।
চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, scikit-learn 1.8.0); প্রথমবার scikit-learn নামতে প্রায় ১০ সেকেন্ড লাগে। ব্লক ১ আর ২ একই namespace ভাগ করে (data-shared), তাই ব্লক ২ আগে চালালে ব্লক ১ নিজে থেকে আগে চলে।
১ · কাজটি — “পরে টাকা দিন” model আর খাতার লুকোনো অতীত
বাজারঘর কিছু গ্রাহককে “পরে টাকা দিন” সুবিধা দেয়: পণ্য হাতে পেয়ে সাত দিনের মধ্যে bKash বা Nagad-এ টাকা পাঠানো। কাকে দেওয়া হবে, তা ঠিক করে একটি logistic regression model. Input: আগের অর্ডারের সংখ্যা (orders), ফেরতের হার (ret), area code (১–৬) আর জেলা। Label: পুরোনো খাতার “সময়মতো দিয়েছেন” (১) বা “দেরি” (০)। সম্ভাবনা ০.৫ বা বেশি হলে অনুমোদন; বাকিরা ক্যাশ অন ডেলিভারিতে কেনেন।
সব ডেটা কাল্পনিক, seed দেওয়া কোড দিয়ে বানানো (নিচে)। দুটি “জেলা”: A (অনেকগুলো জেলা মিলিয়ে) আর B, যাকে এই পর্বে বলব নদীচর, একটি কাল্পনিক নদী-তীরের জেলা। ডেটায় ইচ্ছা করে একটি অতীতের ভুল রাখা হয়েছে, যা বাস্তবেও সহজেই ঘটতে পারে।
পাড়ার মুদি দোকানের বাকির খাতা ভাবুন। নদীর ওপারের গ্রাহকেরা টাকা পাঠান খেয়াঘাটের এক লোকের হাতে, যিনি প্রায়ই দেরিতে পৌঁছান; খাতায় তাই ওপারের অনেক সৎ গ্রাহকের পাশে লেখা “দেরি”. নতুন কর্মচারী নিয়ম বানালেন: “ওপারের লোককে বাকি নয়।” নিয়মটি খাতার সঙ্গে নিখুঁত মেলে, মানুষের সঙ্গে অন্যায় করে। ভুলটি হিসাবে নয়, খাতায়।
এই ধরনের ভুলকে বলা হয় historical bias, আর এখানে তার নির্দিষ্ট রূপ label bias: যা মাপতে চাই (গ্রাহক কি সময়মতো দেন?) আর খাতায় যা লেখা (টাকা কি সময়মতো দোকানে পৌঁছেছে?) এক নয়। Simulation-এ নদীচরের সময়মতো দেওয়া গ্রাহকদের প্রায় ৩০% খাতায় “দেরি”. বাস্তবে আসল সত্য (repaid) কেউ জানে না; simulation-এ জানি বলেই ক্ষতি মাপতে পারছি। বাস্তবে আপনার হাতে থাকে শুধু খাতা।
যে বৈশিষ্ট্যের ভিত্তিতে বৈষম্য করা উচিত নয় (এখানে জেলা; অন্য প্রসঙ্গে লিঙ্গ, ধর্ম, বয়স), তাকে বলে protected attribute. জেলা আইনত সুরক্ষিত না-ও হতে পারে, কিন্তু পিছিয়ে থাকা এলাকার মানুষকে নিয়মিত বাদ দেওয়া ন্যায্যতার প্রশ্ন তোলে।
ব্লকগুলো পাতার ভেতরে রাখা bazar_credit.py module import করে; পড়ার জন্য তার কোড:
def make_data(n=4000, seed=42, b_share=0.3, b_delay=0.3, b_new=0.0):
rng = np.random.default_rng(seed)
b = b_share > rng.random(n) # True = নদীচর জেলা (কাল্পনিক)
pa = [.24, .24, .24, .24, .02, .02] # জেলা A-র area code 1-6
pb = [.03, .03, .02, .02, .45, .45] # নদীচরের বেশিরভাগ 5 আর 6
area = np.where(b, rng.choice(np.arange(1, 7), n, p=pb),
rng.choice(np.arange(1, 7), n, p=pa))
lam = np.where(b, 4.0, 5.0) * np.where(b_new * b > rng.random(n), 0.3, 1.0)
orders = rng.poisson(lam) # আগের অর্ডার
ret = rng.beta(2, 12, n) # ফেরতের হার
p_true = 1 / (1 + np.exp(-(-0.6 + 0.45 * orders - 6 * ret)))
repaid = (p_true > rng.random(n)).astype(int) # আসল সত্য: সময়মতো দিয়েছেন?
late_mark = b & (repaid == 1) & (b_delay > rng.random(n))
label = np.where(late_mark, 0, repaid) # খাতায় লেখা label
return pd.DataFrame({"district": np.where(b, "B", "A"), "area": area,
"orders": orders, "ret": ret.round(3),
"repaid": repaid, "label": label})খেয়াল করুন: আসল টাকা দেওয়ার সম্ভাবনা p_true দুই জেলায় একই সূত্রে, শুধু orders আর ret থেকে। নদীচরে গড়ে আগের অর্ডার একটু কম (Poisson-এর গড় ৪ বনাম ৫), কারণ সেখানে অনলাইন কেনাকাটা নতুন; তাই সেখানে আসল base rate একটু কম হওয়াটা ডেটার একটি সত্যিকারের পার্থক্য। বাকি সব পার্থক্য খাতার ভুল থেকে।
পক্ষপাত কোথায় কোথায় ঢোকে
Where bias enters: history → labels → features → model → threshold → decision → new data
ডান পাশের লাল তীরটি feedback loop: যাঁরা অনুমোদন পান না, তাঁদের “পরে টাকা দিন”-এর কোনো নতুন রেকর্ড তৈরি হয় না, তাই পরের বছরের খাতাও ভুল ধারণা শুধরানোর সুযোগ পায় না। আমরা শুধু অনুমোদিতদের ফল দেখি; একে বলে selective labels সমস্যা। ভাঁজগুলো এক এক করে খুলব।
২ · দলভিত্তিক মাপ হাতে — selection rate, parity আর equal opportunity
ক্রিকেটের স্কোরকার্ডে দলের মোট রান দেখে বোঝা যায় না কে ব্যর্থ হয়েছেন। Fairness-এর মাপও তেমন: একই confusion matrix (পর্ব ৬০) প্রতিটি দলের জন্য আলাদা। “Positive” মানে অনুমোদন (ŷ = ১), আর y = ১ মানে গ্রাহক সত্যিই সময়মতো দেন। চারটি প্রচলিত মাপ:
- Selection rate: দলের কত ভাগ আবেদন অনুমোদিত, P(ŷ = ১ | দল)।
- Demographic parity: সব দলের selection rate সমান; মাপা হয় পার্থক্য (বড় − ছোট) আর অনুপাত (ছোট ÷ বড়) দিয়ে। যুক্তরাষ্ট্রের কর্মী-নিয়োগের ১৯৭৮ সালের Uniform Guidelines-এর “চার-পঞ্চমাংশ নিয়ম”: অনুপাত ০.৮-এর কম হলে disparate impact-এর প্রাথমিক ইঙ্গিত। এটি সেই দেশের একটি নির্দেশিকা, বাংলাদেশের আইন নয়, আর চূড়ান্ত প্রমাণ নয়, শুধু সংকেত।
- Equal opportunity: যাঁরা সত্যিই সময়মতো দেন, তাঁদের মধ্যে অনুমোদনের হার সব দলে সমান। এই হার হলো true positive rate (TPR, অর্থাৎ recall). Moritz Hardt, Eric Price আর Nathan Srebro ২০১৬ সালের প্রবন্ধে এই নাম দেন।
- Equalised odds: TPR আর FPR (দেরি করবেন এমনদের কত ভাগ অনুমোদন পান) দুটিই সব দলে সমান (একই প্রবন্ধ)। Fairlearn-এর “equalized odds difference” দুই ফাঁকের বড়টি।
নিচের খুদে টেবিলে প্রতিটি দলে ১০টি আবেদন। দল A: ৭ জন সত্যিই সময়মতো দেন, ৩ জন দেন না; model ৬ জনকে অনুমোদন দিয়েছে, যার ৫ জন ভালো গ্রাহক (TP) আর ১ জন নয় (FP). দল B: ৬ জন ভালো, ৪ জন নয়; অনুমোদন ৩ জনকে, তিনজনই ভালো গ্রাহক।
| এই ধাপ | |
| দল A | |
| দল B | |
| ফল |
- গোনা। প্রতিটি দলের জন্য আলাদা confusion matrix: কতজন সত্যিই ভালো (y = ১), কতজন অনুমোদিত, তাঁদের কতজন TP আর কতজন FP.
- Selection rate. শুধু অনুমোদিতদের ভাগ: A ০.৬০, B ০.৩০. আসল ফল লাগে না, তাই deploy-এর পরেও সবসময় মাপা যায়।
- Demographic parity. পার্থক্য ০.৬০ − ০.৩০ = ০.৩০; অনুপাত ০.৩০ ÷ ০.৬০ = ০.৫০, যা চার-পঞ্চমাংশের রেখা ০.৮-এর অনেক নিচে।
- Equal opportunity. ভালো গ্রাহকদের মধ্যে অনুমোদন: A-তে ৭ জনের ৫ জন (০.৭১৪), B-তে ৬ জনের ৩ জন (০.৫০০)। ফাঁক ০.২১৪: B-র একজন ভালো গ্রাহকের বাদ পড়ার সম্ভাবনা বেশি।
- FPR. দেরি করবেন এমন গ্রাহকদের মধ্যে অনুমোদন: A-তে ৩ জনের ১ জন (০.৩৩৩), B-তে ৪ জনের ০ জন। Equalised odds চায় TPR আর FPR দুটিই সমান; দুটিই ভাঙছে, আর বড় ফাঁকটি ০.৩৩৩.
- PPV আর রায়। অনুমোদিতদের কত ভাগ সত্যিই ভালো (PPV): A ০.৮৩৩, B ১.০০০. দোকানের চোখে B-র অনুমোদন “বেশি নিরাপদ”, গ্রাহকের চোখে B-র ভালো মানুষ বেশি বাদ। কোন মাপ জরুরি, তা কার ক্ষতি দেখছেন তার ওপর নির্ভর করে।
Selection rate = (TP + FP) ÷ n · TPR = TP ÷ (TP + FN) · FPR = FP ÷ (FP + TN) · PPV = TP ÷ (TP + FP), প্রতিটি দলের ভেতরে। Demographic parity শুধু ŷ দেখে; বাকিগুলো y-ও দেখে, তাই label পক্ষপাতদুষ্ট হলে “সমান TPR” মানে পক্ষপাতদুষ্ট খাতার সঙ্গে সমান মিল।
৩ · কোডে মাপা — অনুমোদন, TPR, FPR আর দলের ভেতরে calibration
এবার পুরো ৪,০০০টি আবেদন: অর্ধেকে train, অর্ধেকে test (জেলা অনুযায়ী stratify). পুরোনো দলের model জেলাও দেখত। report() প্রতিটি দলের আসল হার, খাতার হার, গড় সম্ভাবনা, অনুমোদন, TPR আর FPR ছাপে; TPR আর FPR আসল ফল (repaid) ধরে, যা শুধু simulation-এ জানা। তারপর calibration: একই ঝুড়িতে দুই দলের আসল হার কি এক?
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from bazar_credit import make_data
df = make_data() # ৪০০০ কাল্পনিক আবেদন
tr, te = train_test_split(df, test_size=0.5, random_state=42, stratify=df.district)
def feats(d, use): # use: কোন কলাম model দেখবে
X = pd.get_dummies(d[["orders", "ret"]].assign(area=d.area.astype(str)), dtype=float)
X = X if "area" in use else X[["orders", "ret"]]
return X.assign(is_B=(d.district == "B") * 1.0) if "district" in use else X
def report(p, yhat, d=te):
rows = {}
for g in "AB":
s = (d.district == g).to_numpy(); y = d.repaid.to_numpy()[s]; h = yhat[s]
rows[g] = {"সত্যি_দেন": y.mean(), "লেবেল_হার": d.label[s].mean(), "গড়_p": p[s].mean(),
"অনুমোদন": h.mean(), "TPR": h[y == 1].mean(), "FPR": h[y == 0].mean()}
t = pd.DataFrame(rows).T
sa, sb = t.loc["A", "অনুমোদন"], t.loc["B", "অনুমোদন"]
print(t.round(3).to_string(), f"\nDP পার্থক্য {sa - sb:.3f} | অনুপাত B/A {sb / sa:.3f}"
f" | TPR ফাঁক {t.TPR.A - t.TPR.B:.3f} | FPR ফাঁক {t.FPR.A - t.FPR.B:.3f}")
return t
use = ["district", "area"] # পুরোনো দলের model: জেলাও দেখে
m1 = LogisticRegression(C=1e10, max_iter=1000).fit(feats(tr, use), tr.label)
p1 = m1.predict_proba(feats(te, use))[:, 1]
print("test-এ আবেদন:", te.district.value_counts().to_dict())
t1 = report(p1, (p1 >= 0.5).astype(int))
bins = pd.cut(p1, [0, .3, .5, .7, 1])
print(te.assign(bin=bins).groupby(["district", "bin"], observed=True)
[["label", "repaid"]].mean().round(2).unstack(0).to_string())
ax = t1[["অনুমোদন", "TPR", "FPR"]].set_axis(["approval rate", "TPR", "FPR"], axis=1).T.plot.bar(
rot=0, figsize=(6.4, 3.2), ylim=(0, 1), title="Approval model by district (test set, true outcomes)")
ax.legend(["district A", "district B"]); plt.show()আসল আউটপুট (ব্রাউজারের Python, pandas 3.0.2, scikit-learn 1.8.0; ছবিটি এর নিচে আঁকা হয়):
test-এ আবেদন: {'A': 1390, 'B': 610}
সত্যি_দেন লেবেল_হার গড়_p অনুমোদন TPR FPR
A 0.640 0.640 0.662 0.781 0.890 0.586
B 0.543 0.384 0.409 0.316 0.453 0.154
DP পার্থক্য 0.464 | অনুপাত B/A 0.405 | TPR ফাঁক 0.437 | FPR ফাঁক 0.432
label repaid
district A B A B
bin
(0.0, 0.3] 0.20 0.25 0.20 0.33
(0.3, 0.5] 0.36 0.40 0.36 0.54
(0.5, 0.7] 0.57 0.47 0.57 0.73
(0.7, 1.0] 0.83 0.62 0.83 0.91
প্রথম টেবিল। নদীচরে আসলে ৫৪.৩% সময়মতো দেন, খাতায় মাত্র ৩৮.৪%. Model খাতাকে বিশ্বাস করে: গড় অনুমিত সম্ভাবনা ০.৪০৯. অনুমোদন A-তে ৭৮.১%, নদীচরে ৩১.৬%; অনুপাত ০.৪০৫. সবচেয়ে কষ্টের সংখ্যা TPR: নদীচরের সত্যিই ভালো গ্রাহকদের অর্ধেকের বেশি (১ − ০.৪৫৩) বাদ পড়ছেন, A-তে মাত্র ১১%.
দ্বিতীয় টেবিল (calibration). প্রতিটি সারি একটি সম্ভাবনার ঝুড়ি। A-তে খাতার হার আর আসল হার এক, কারণ সেখানে খাতায় ভুল নেই। নদীচরে ০.৫–০.৭ ঝুড়ির ৭৩% আসলে সময়মতো দেন, A-র একই ঝুড়িতে ৫৭%: আসল ফল ধরে model দলের ভেতরে calibrated নয়। কিন্তু খাতার label ধরে (বাম দুই কলাম) পার্থক্য অনেক ছোট, কোথাও উল্টো। বাস্তবে শুধু বাম দিক দেখতেন, আর model-কে “প্রায় ঠিকঠাক” মনে হতো। তাই label কীভাবে তৈরি, তা জানা মাপের আগের কাজ।
সতর্কতা: test set-এ নদীচরের ৬১০টি আবেদন। ছোট দলে মাপ বেশি নড়ে; প্রতিটি দলের n লিখুন, দরকারে bootstrap ব্যবধান দিন (পর্ব ৬০)।
৪ · জেলার কলাম মুছলেই কি পক্ষপাত যায়? — proxy
প্রথম যে প্রতিকার মাথায় আসে: “model-কে জেলা দেখতে দেব না” (fairness through unawareness). সমস্যা হলো, অন্য কলাম প্রায়ই জেলার কথা বলে দেয়। সুরক্ষিত বৈশিষ্ট্যের সঙ্গে ঘনিষ্ঠভাবে জড়িত এমন feature-কে বলে proxy: পোস্ট কোড, স্কুলের নাম, মোবাইল অপারেটর, এমনকি কেনাকাটার সময়। Solon Barocas আর Andrew Selbst ২০১৬ সালের “Big Data’s Disparate Impact” প্রবন্ধে একে “redundant encoding” বলে আলোচনা করেন।
| এই ধাপ | |
| model যা দেখে | |
| মূল ওজন | |
| অনুমোদন A │ B | |
| অনুপাত B/A |
- শুরু। জেলা দেখে এমন model “নদীচর” কলামে −০.৯১ ওজন দিয়েছে; area-র ওজন প্রায় শূন্য। অনুপাত ০.৪০৫.
- জেলা মোছা। কলামটি বাদ দিয়ে আবার train. কাগজে-কলমে model এখন “জেলা জানে না”.
- Proxy খোঁজা। একটি সরল crosstab: area ৫ বা ৬-এ A-র মাত্র ৪% গ্রাহক থাকেন, নদীচরের ৯০%. Area দেখেই প্রায় নিশ্চিতভাবে জেলা বলা যায়।
- ওজন সরে যায়। খাতায় নদীচরের “দেরি” রয়ে গেছে, তাই model সবচেয়ে কাছের কলাম খুঁজে নেয়: area ৫ (−০.৫৩) আর ৬ (−০.৬৫). কেউ নির্দেশ দেয়নি; loss কমাতেই এটি ঘটে।
- ফল। অনুমোদন A ০.৭৫৯, নদীচর ০.৩৭২; অনুপাত ০.৪৯০. সামান্য উন্নতি, পক্ষপাতের বেশিরভাগই রয়ে গেছে।
- Area-ও মুছলে। শুধু orders আর ret: DP ফাঁক ০.১৭০, TPR ফাঁক ০.১৩০. বাকি ফাঁকের একটি অংশ সত্যিকারের পার্থক্য (আগের অর্ডার কম), একটি অংশ খাতার ভুল label, যা কলাম মুছে যায় না। বাস্তবে proxy প্রায়ই অনেক কলামের সমন্বয়, তাই “সব proxy মুছেছি” দাবি প্রায় যাচাই করা যায় না।
একটি অস্বস্তিকর উপসংহার: পক্ষপাত মাপতে protected attribute জানতে হয়। কলামটি পুরো ফেলে দিলে আপনি আর মাপতেও পারতেন না। প্রচলিত পথ হলো একে model-এর input থেকে আলাদা রেখে, সীমিত প্রবেশাধিকারে শুধু মূল্যায়নের জন্য রাখা; এটি নিজেই গোপনীয়তার সিদ্ধান্ত (অংশ ৭)।
একই মাপ ডেস্কটপে Fairlearn দিয়ে যাচাই (Microsoft-এর শুরু করা, এখন সম্প্রদায় পরিচালিত একটি খোলা Python লাইব্রেরি)। MetricFrame যেকোনো metric দলে দলে ভাগ করে হিসাব করে। সত্যিকারের run, লেখার সময়ের version:
from fairlearn.metrics import (MetricFrame, selection_rate, true_positive_rate,
false_positive_rate, demographic_parity_difference,
demographic_parity_ratio, equalized_odds_difference)
m2 = LogisticRegression(C=1e10, max_iter=1000).fit(feats(tr, ["area"]), tr.label)
yhat = m2.predict(feats(te, ["area"]))
mf = MetricFrame(metrics={"selection": selection_rate, "TPR": true_positive_rate,
"FPR": false_positive_rate},
y_true=te.repaid, y_pred=yhat, sensitive_features=te.district)
print(mf.by_group.round(3))
kw = dict(sensitive_features=te.district)
print("DP difference", round(demographic_parity_difference(te.repaid, yhat, **kw), 3))
print("DP ratio ", round(demographic_parity_ratio(te.repaid, yhat, **kw), 3))
print("EO difference", round(equalized_odds_difference(te.repaid, yhat, **kw), 3))~/bazarghar-ai$ python fairness_check.py
Python 3.14.4 | scikit-learn 1.8.0 | fairlearn 0.15.0
selection TPR FPR
district
A 0.759 0.873 0.556
B 0.372 0.526 0.190
DP difference 0.387
DP ratio 0.49
EO difference 0.366
সংখ্যাগুলো পাতার নিজের report()-এর সঙ্গে হুবহু মেলে (ব্লক ২-এর প্রথম টেবিল দেখুন)। EO difference = max(TPR ফাঁক ০.৩৪৭, FPR ফাঁক ০.৩৬৬) = ০.৩৬৬. লাইব্রেরি ব্যবহার করুন বা নিজে লিখুন, একটি হাতে বানানো ছোট উদাহরণে দুটিকে আগে মিলিয়ে নিন (অনুশীলন ৪)।
৫ · সংজ্ঞাগুলো কেন একসঙ্গে মেলে না — base rate আর অসম্ভাব্যতা
ধরুন বাজারঘর বলল, “আমরা সব সংজ্ঞা একসঙ্গে মানব।” ২০১৬ সালের COMPAS বিতর্কে (ভাবনার প্রশ্ন ১) এক পক্ষ সমান FPR চেয়েছিল, অন্য পক্ষ calibration দেখিয়েছিল, আর দুই পক্ষের সংখ্যাই মোটামুটি ঠিক ছিল বলে পরে বিশ্লেষকেরা দেখান।
Jon Kleinberg, Sendhil Mullainathan আর Manish Raghavan (২০১৬ সালে প্রকাশিত, পরে ITCS ২০১৭) আর আলাদাভাবে Alexandra Chouldechova (Big Data, ২০১৭) গাণিতিকভাবে দেখান: দুই দলের base rate আলাদা হলে, আর model নিখুঁত না হলে, দলের ভেতরে calibration (বা সমান PPV) আর সমান ভুলের হার (সমান FPR ও FNR) একসঙ্গে রাখা অসম্ভব। Chouldechova-র রূপটি একটি সরল অভেদ থেকে আসে। p = দলের base rate হলে:
FPR = (p ÷ (1 − p)) × ((1 − PPV) ÷ PPV) × TPR
এখানে PPV অনুমোদিতদের মধ্যে ভালো গ্রাহকের ভাগ, TPR ভালো গ্রাহকদের মধ্যে অনুমোদনের ভাগ। দুই দলে PPV আর TPR সমান রাখলে FPR-কে p অনুযায়ী বদলাতেই হবে। একটি হাতে-গোনা উদাহরণ, প্রতিটি দলে ১০০ জন, দুই দলেই PPV = ০.৮ আর TPR = ০.৮:
| দল | base rate p | ভালো │ খারাপ | TP | FP | PPV | TPR | FPR |
|---|---|---|---|---|---|---|---|
| A | ০.৬ | ৬০ │ ৪০ | ৪৮ | ১২ | ৪৮ ÷ ৬০ = ০.৮ | ৪৮ ÷ ৬০ = ০.৮ | ১২ ÷ ৪০ = ০.৩০০ |
| B | ০.৩ | ৩০ │ ৭০ | ২৪ | ৬ | ২৪ ÷ ৩০ = ০.৮ | ২৪ ÷ ৩০ = ০.৮ | ৬ ÷ ৭০ ≈ ০.০৮৬ |
সূত্রে যাচাই: A-তে (০.৬ ÷ ০.৪) × (০.২ ÷ ০.৮) × ০.৮ = ১.৫ × ০.২৫ × ০.৮ = ০.৩০; B-তে (০.৩ ÷ ০.৭) × ০.২৫ × ০.৮ ≈ ০.০৮৬. দুটি দলের FPR সমান করতে চাইলে (ধরুন A-র FPR কমিয়ে ০.০৮৬) A-তে FP হবে প্রায় ৩.৪, তখন A-র PPV ≈ ৪৮ ÷ ৫১.৪ ≈ ০.৯৩, B-র ০.৮: সমান PPV ভেঙে গেল। Selection rate-ও আলাদা (০.৬০ বনাম ০.৩০), তাই demographic parity-ও নেই। Base rate সমান হলে বা model নিখুঁত হলে তবেই সব একসঙ্গে সম্ভব।
এর মানে কী নয়: “fairness অসম্ভব, তাই কিছু করার নেই” নয়। কোন সংজ্ঞা বাছবেন, তা প্রসঙ্গ দেখে, ক্ষতিগ্রস্তদের মত নিয়ে আর লিখিতভাবে নেওয়া মূল্যবোধের সিদ্ধান্ত। আর বাজারঘরে ফাঁকের বড় অংশ base rate নয়, খাতার ভুল: সেটি আগে ঠিক করার কাজ।
৬ · প্রতিকার আর তার দাম — re-weighting, দলভিত্তিক সীমা, ভালো ডেটা, না চালানো
চারটি পথ, প্রতিটির নিজস্ব দাম:
⚖️ Re-weighting
প্রতিটি সারির ওজন w = P(দল) × P(label) ÷ P(দল, label), যাতে দল আর label পরস্পর-নিরপেক্ষ দেখায় (Kamiran ও Calders-এর ২০১২ সালের reweighing). দাম: খাতার label-এর সঙ্গে মিল কমে।
sample_weight🎚️ দলভিত্তিক সীমা
দলভেদে আলাদা threshold, যেমন A-তে ০.৫, নদীচরে ০.৩৫ (Hardt ও সহকর্মীদের post-processing). দাম: সিদ্ধান্তে protected attribute সরাসরি ব্যবহার, যা আইনি ও নৈতিক বিতর্কের বিষয়।
threshold📒 ভালো ডেটা
Label-এর উৎস ঠিক করা: courier-এর জমার তারিখ নয়, গ্রাহকের পাঠানোর তারিখ (bKash/Nagad-এর লেনদেনের সময়) দিয়ে “দেরি” মাপা। দাম: সময়, টাকা, আর নতুন ডেটা সংগ্রহে গোপনীয়তার প্রশ্ন।
মূল কারণ⛔ চালু না করা
ক্ষতি ফেরানো কঠিন বা ডেটা ঠিক করা যায় না: তখন সবার জন্য এক সরল নিয়ম (তিনটি সফল অর্ডারের পর সুবিধা) বা মানুষের পর্যালোচনা। দাম: স্বয়ংক্রিয়তা হারানো।
সৎ বিকল্পপরের ব্লক ব্লক ১-এর namespace ব্যবহার করে: জেলা মুছে proxy-র প্রভাব, তারপর re-weighting আর দলভিত্তিক সীমা, শেষে পাঁচটি বিকল্প পাশাপাশি। Accuracy দুইভাবে: আসল ফল ধরে (acc_সত্যি, শুধু simulation-এ জানা) আর খাতার label ধরে (acc_লেবেল, বাস্তবে মাপা যায়)।
def fit(use, w=None):
m = LogisticRegression(C=1e10, max_iter=1000).fit(feats(tr, use), tr.label, sample_weight=w)
return m, m.predict_proba(feats(te, use))[:, 1]
m2, p2 = fit(["area"]) # জেলার কলাম মুছে দিলাম, area রইল
print("জেলা ছাড়া, area আছে:"); t2 = report(p2, (p2 >= 0.5).astype(int))
print("area 5 বা 6-এ থাকেন:", (te.area >= 5).groupby(te.district).mean().round(2).to_dict())
print("area 1-6-এর ওজন:", m2.coef_[0][2:].round(2).tolist())
g, y = tr.district, tr.label # re-weighting: w = P(g)·P(y) / P(g, y)
pg, py, pgy = g.value_counts(normalize=True), y.value_counts(normalize=True), \
pd.crosstab(g, y, normalize="all")
w = np.array([pg[a] * py[b] / pgy.loc[a, b] for a, b in zip(g, y)])
print("ওজন:", {(a, b): round(float(pg[a] * py[b] / pgy.loc[a, b]), 3) for a in "AB" for b in (0, 1)})
m3, p3 = fit(["area"], w)
def thr(p, tA, tB): # দলভিত্তিক সীমা
return (p >= np.where(te.district == "B", tB, tA)).astype(int)
rows = []
for name, yhat in [("জেলা + area, সীমা 0.5", (p1 >= .5) * 1), ("শুধু area, সীমা 0.5", (p2 >= .5) * 1),
("area + re-weighting", (p3 >= .5) * 1), ("area, সীমা A 0.5 / B 0.35", thr(p2, .5, .35)),
("orders + ret শুধু", (fit([])[1] >= .5) * 1)]:
s = {k: yhat[(te.district == k).to_numpy()] for k in "AB"}
yt = {k: te.repaid[te.district == k].to_numpy() for k in "AB"}
tpr = {k: s[k][yt[k] == 1].mean() for k in "AB"}
rows.append([name, (yhat == te.repaid).mean(), (yhat == te.label).mean(),
s["A"].mean() - s["B"].mean(), tpr["A"] - tpr["B"]])
print(pd.DataFrame(rows, columns=["model", "acc_সত্যি", "acc_লেবেল", "DP_ফাঁক", "TPR_ফাঁক"])
.round(3).to_string(index=False))আসল আউটপুট (ব্রাউজারের Python, scikit-learn 1.8.0):
জেলা ছাড়া, area আছে:
সত্যি_দেন লেবেল_হার গড়_p অনুমোদন TPR FPR
A 0.640 0.640 0.649 0.759 0.873 0.556
B 0.543 0.384 0.440 0.372 0.526 0.190
DP পার্থক্য 0.387 | অনুপাত B/A 0.490 | TPR ফাঁক 0.347 | FPR ফাঁক 0.366
area 5 বা 6-এ থাকেন: {'A': 0.04, 'B': 0.9}
area 1-6-এর ওজন: [0.09, -0.11, 0.2, 0.3, -0.53, -0.65]
ওজন: {('A', 0): 1.199, ('A', 1): 0.894, ('B', 0): 0.726, ('B', 1): 1.37}
model acc_সত্যি acc_লেবেল DP_ফাঁক TPR_ফাঁক
জেলা + area, সীমা 0.5 0.692 0.690 0.464 0.437
শুধু area, সীমা 0.5 0.700 0.690 0.387 0.347
area + re-weighting 0.708 0.680 0.126 0.093
area, সীমা A 0.5 / B 0.35 0.702 0.673 0.151 0.124
orders + ret শুধু 0.708 0.686 0.170 0.130
ওজন পড়ুন। খাতায় নদীচরের “সময়মতো” সারি কম, তাই re-weighting সেগুলোকে ১.৩৭ গুণ ভারী করে, আর নদীচরের “দেরি” সারিকে হালকা (০.৭২৬)। A-তে উল্টো, তবে মৃদু। ওজন দেওয়ার পর ওজনসহ গোনায় জেলা আর label পরস্পর-নিরপেক্ষ।
বাণিজ্য-বিনিময়ের টেবিল। জেলা মোছায় TPR ফাঁক ০.৪৩৭ থেকে মাত্র ০.৩৪৭; re-weighting-এ ০.০৯৩ (DP ফাঁক ০.১২৬)। Accuracy খাতার label ধরে ০.৬৯০ থেকে ০.৬৮০-এ নামে (প্রচলিত “fairness-এর দাম”), অথচ আসল ফল ধরে ০.৭০০ থেকে ০.৭০৮-এ ওঠে: ভুল খাতার সঙ্গে কম মিল এখানে আসল মানুষের সঙ্গে বেশি মিল। নদীচরের সীমা ০.৩৫ করলে TPR ফাঁক ০.১২৪. এক seed-এর এক ভাগের ফল; অন্য ভাগে ক্রম বদলাতে পারে।
দলভিত্তিক সীমার বিতর্ক: একপক্ষ বলেন, দল দেখে আলাদা নিয়ম মানেই বৈষম্য। অন্যপক্ষ বলেন, ডেটা নিজেই একটি দলের বিরুদ্ধে ঝুঁকে থাকলে সবার জন্য এক সীমাই অন্যায় ফল দেয়। কিছু দেশের ঋণ ও নিয়োগ আইনে protected attribute সরাসরি ব্যবহারে কড়া সীমা আছে বলে আইন-গবেষকেরা আলোচনা করেন। নিরাপদ পথ: আগে মূল কারণ (label) ঠিক করা, আর যেকোনো দলভিত্তিক সমন্বয় আইনি পরামর্শ, লিখিত কারণ আর সময়সীমাসহ।
৭ · গোপনীয়তা — কম নেওয়া, “বেনামি”-র সীমা আর differential privacy
“পরে টাকা দিন” model-এর জন্য বাজারঘর কী রাখবে? প্রথম নীতি data minimisation: যতটুকু দরকার, তার বেশি নয়; আগের অর্ডারের সংখ্যা আর ফেরতের হার যথেষ্ট, contact list বা সামাজিক মাধ্যমের প্রোফাইল নয়। দ্বিতীয় নীতি purpose limitation: ডেলিভারির জন্য নেওয়া ডেটা বিজ্ঞাপনে ব্যবহার করতে আলাদা ভিত্তি আর সাধারণত সম্মতি লাগে। ছবি আর chart-এ লুকোনো ব্যক্তিগত তথ্যের কথা পর্ব ১৮ আর পর্ব ৩১-এ; এখানে টেবিল।
নাম মুছলেই কি মানুষ হারিয়ে যান?
বাজারঘর একটি বিপণন সংস্থাকে মার্চ মাসের অর্ডারের একটি টেবিল দিল, নাম আর ফোন নম্বর মুছে: শুধু জেলা, বয়সের দল, অর্ডারের তারিখ আর পণ্য। একে সাধারণ ভাষায় “বেনামি” বলা হয়, কিন্তু প্রকৃত anonymisation মানে যুক্তিসংগত চেষ্টাতেও কাউকে আর চেনা যায় না। নাম মোছা বা নামের বদলে একটি কোড বসানোকে বলে pseudonymisation: কোডের তালিকা কারও কাছে থাকলে, বা অন্য তথ্য জুড়ে দিলে, মানুষকে আবার চেনা যায়। যে কলামগুলো একা পরিচয় নয় কিন্তু একসঙ্গে প্রায় পরিচয়ের কাজ করে (জেলা, বয়স, তারিখ, লিঙ্গ), তাদের বলে quasi-identifier.
এদিকে বাজারঘরের ফেসবুক পাতায় মার্চের “লাকি ড্র” বিজয়ীদের পোস্ট: নাম, জেলা, বয়স আর কোন দিন অর্ডার করে জিতেছেন। দুটি টেবিল একই quasi-identifier দিয়ে জুড়লে (পর্ব ২৫-এর merge) কী হয়, একে বলে linkage attack. সব নাম আর অর্ডার কাল্পনিক।
| এই ধাপ | |
| দেখা যাচ্ছে | |
| ফল |
- বেনামি টেবিল। ১২টি অর্ডার, নাম বা ফোন নেই। কিছু পণ্য স্বাস্থ্যের সংবেদনশীল তথ্য বলে দেয়: ডায়াবেটিস স্ট্রিপ, ইনহেলার, প্রেগন্যান্সি টেস্ট কিট।
- প্রকাশ্য পোস্ট। বিজয়ী তালিকা নিজে নিরীহ, কিন্তু নামের পাশে জেলা, বয়স আর দিন আছে।
- Quasi-identifier বাছা। দুই টেবিলে মিল আছে এমন তিনটি কলাম: জেলা, বয়সের দল, তারিখ। একা কোনোটিই পরিচয় নয়।
- Merge. তিনজনের ঠিক একটি করে মিল: রুমানার প্রেগন্যান্সি টেস্ট কিট, তানভীরের ডায়াবেটিস স্ট্রিপ, জামিলের ইনহেলার এখন নামসহ জানা। সাবরিনার দুটি মিল, তবু ৫০% সম্ভাবনায় স্বাস্থ্য-তথ্য ফাঁস।
- k-anonymity. একই quasi-identifier-এর প্রতিটি দলে অন্তত k জন থাকলে টেবিলটি k-anonymous. এখানে সবচেয়ে ছোট দলে ১ জন, অর্থাৎ k = ১, আর ১২টির ৮টি সারি নিজের দলে একা।
- সাধারণীকরণ। বয়স দুই ভাগে, তারিখ শুধু মাসে: তবু k = ১, কারণ ছোট জেলায় অর্ডার কম। k ≥ ২ পেতে ১২টির ৫টি সারি বাদ: গোপনীয়তার দাম ডেটার কাজের মান।
import numpy as np
import pandas as pd
pd.set_option("display.width", 90)
rows = """ঢাকা,25-34,03-02,ডায়াবেটিস স্ট্রিপ|ঢাকা,25-34,03-02,চার্জার|ঢাকা,25-34,03-05,শাড়ি
ঢাকা,35-44,03-02,প্রেসার মেশিন|রাজশাহী,25-34,03-03,প্রেগন্যান্সি টেস্ট কিট|রাজশাহী,45-54,03-03,বই
সিলেট,18-24,03-04,হেডফোন|সিলেট,18-24,03-04,মোবাইল কভার|খুলনা,35-44,03-06,ডায়াবেটিস স্ট্রিপ
খুলনা,35-44,03-07,জুতা|নদীচর,25-34,03-05,ইনহেলার|নদীচর,55+,03-05,চাল"""
orders = pd.DataFrame([r.split(",") for r in rows.replace("\n", "|").split("|")],
columns=["district", "age", "date", "item"]) # নাম-ফোন মোছা "বেনামি" টেবিল
winners = pd.DataFrame({"name": ["রুমানা", "তানভীর", "সাবরিনা", "জামিল"],
"district": ["রাজশাহী", "খুলনা", "ঢাকা", "নদীচর"],
"age": ["25-34", "35-44", "25-34", "25-34"],
"date": ["03-03", "03-06", "03-02", "03-05"]}) # ফেসবুকের লাকি ড্র পোস্ট
QI = ["district", "age", "date"] # quasi-identifier
hit = winners.merge(orders, on=QI, how="left")
hit["মিল"] = hit.groupby("name")["item"].transform("size")
print(hit[["name", "district", "date", "item", "মিল"]].to_string(index=False))
k = orders.groupby(QI).size()
print("\nk-anonymity: k =", k.min(), "| নিজের দলে একা:", (k == 1).sum(), "সারি, মোট", len(orders))
gen = orders.assign(age=orders.age.map(lambda a: "18-34" if a[:2] in ("18", "25") else "35+"),
date="মার্চ") # সাধারণীকরণ: বয়সের বড় দল, শুধু মাস
kg = gen.groupby(QI)["item"].agg(["size", "nunique"])
print("\nসাধারণীকরণের পরে:\n", kg.to_string())
print("k =", kg["size"].min(), "| k ≥ 2 রাখতে বাদ দিতে হয়:", kg["size"][kg["size"] < 2].sum(), "সারি")
rng = np.random.default_rng(42) # differential privacy: Laplace শব্দ
true_count = 37 # নদীচরে মার্চে ইনহেলার কিনেছেন
for eps in [0.1, 0.5, 1.0, 2.0]:
noisy = true_count + rng.laplace(0, 1 / eps, 5) # count-এর sensitivity = 1
mae = np.abs(rng.laplace(0, 1 / eps, 100_000)).mean()
print(f"ε = {eps:<4} প্রকাশ: {np.round(noisy).astype(int)} গড় ভুল ≈ {mae:.2f}")আসল আউটপুট (ব্রাউজারের Python, pandas 3.0.2):
name district date item মিল
রুমানা রাজশাহী 03-03 প্রেগন্যান্সি টেস্ট কিট 1
তানভীর খুলনা 03-06 ডায়াবেটিস স্ট্রিপ 1
সাবরিনা ঢাকা 03-02 ডায়াবেটিস স্ট্রিপ 2
সাবরিনা ঢাকা 03-02 চার্জার 2
জামিল নদীচর 03-05 ইনহেলার 1
k-anonymity: k = 1 | নিজের দলে একা: 8 সারি, মোট 12
সাধারণীকরণের পরে:
size nunique
district age date
খুলনা 35+ মার্চ 2 2
ঢাকা 18-34 মার্চ 3 3
35+ মার্চ 1 1
নদীচর 18-34 মার্চ 1 1
35+ মার্চ 1 1
রাজশাহী 18-34 মার্চ 1 1
35+ মার্চ 1 1
সিলেট 18-34 মার্চ 2 2
k = 1 | k ≥ 2 রাখতে বাদ দিতে হয়: 5 সারি
ε = 0.1 প্রকাশ: [45 36 50 42 20] গড় ভুল ≈ 10.00
ε = 0.5 প্রকাশ: [38 40 39 36 37] গড় ভুল ≈ 2.00
ε = 1.0 প্রকাশ: [37 35 37 38 37] গড় ভুল ≈ 1.00
ε = 2.0 প্রকাশ: [37 36 37 38 36] গড় ভুল ≈ 0.50
প্রথম টেবিল stepper-এর merge ধাপ: তিনজন নিশ্চিত, সাবরিনার দুটি সারি। সাধারণীকরণের টেবিলে nunique কলামটিও দেখুন: খুলনার “35+” দলে দুটি সারি, দুটি আলাদা পণ্য। কোনো দলে সবার পণ্য এক হলে k যত বড়ই হোক, দলে থাকা প্রত্যেকের পণ্য জানা হয়ে যায়; এই দুর্বলতা ধরতে প্রস্তাব করা হয় l-diversity (Machanavajjhala ও সহকর্মী, ২০০৬)।
শেষ চার লাইন differential privacy-র সরলতম রূপ, Laplace mechanism: ৩৭-এর বদলে প্রকাশ ৩৭ + শব্দ, শব্দ আসে Laplace বণ্টন থেকে, scale b = Δ ÷ ε. Δ (sensitivity) হলো একজন মানুষ যোগ বা বাদ দিলে উত্তর সর্বোচ্চ কতটা বদলায়; গোনায় ১. ε (privacy budget) ছোট মানে বেশি শব্দ, বেশি গোপনীয়তা। Laplace(০, b)-এর গড় পরম মান ঠিক b, তাই গড় ভুল ≈ ১/ε.
ধারণাটি Cynthia Dwork, Frank McSherry, Kobbi Nissim আর Adam Smith-এর ২০০৬ সালের প্রবন্ধ থেকে। প্রতিশ্রুতি: একজন মানুষের তথ্য থাকুক বা না থাকুক, প্রকাশিত ফলের বণ্টন প্রায় একই (সর্বোচ্চ eε গুণ পার্থক্য), আক্রমণকারীর হাতে যত বাইরের তথ্যই থাকুক। দাম: ছোট count প্রায় অর্থহীন হয় (৩৭-এর পাশে ±১০), আর প্রতিটি নতুন প্রশ্ন budget খরচ করে, কারণ একই প্রশ্ন বারবার করে শব্দ গড় করা যায়। বিস্তারিত নকশা আর GDPR-এর সারকথা এই সাইটের Cybersecurity কোর্সের privacy engineering পর্বে।
বাংলাদেশের আইন, সতর্কভাবে: এটি আইনি পরামর্শ নয়। সংবাদমাধ্যমের খবর অনুযায়ী ২০২৫ সালের নভেম্বরে অন্তর্বর্তী সরকার ব্যক্তিগত উপাত্ত সুরক্ষা আর জাতীয় উপাত্ত ব্যবস্থাপনা নিয়ে দুটি অধ্যাদেশ গেজেটে প্রকাশ করে; পরে সংশোধনের খবরও আসে। এই সাইটের Cybersecurity কোর্স গেজেটের লেখা পড়ে জানিয়েছে যে ২০২৬ সালে বিষয় দুটি আইন হিসেবে এসেছে, আর কিছু বিধান পরে কার্যকর হওয়ার কথা। আইন ও খসড়া বদলায়, খুঁটিনাটি বিধিমালায় ছাড়া; তাই এখানে কোনো ধারা উল্লেখ করছি না। কাজের আগে অফিসিয়াল বর্তমান লেখা দেখুন, দরকারে আইনি পরামর্শ নিন।
৮ · নথিপত্র — model card আর datasheet লেখা
ওষুধের মোড়কের কাগজে থাকে কী রোগে, কত মাত্রায়, কাদের জন্য নয়। Model card একটি model-এর জন্য সেই কাগজ (Margaret Mitchell ও সহকর্মী, FAT* ২০১৯); ডেটাসেটের জন্য Timnit Gebru ও সহকর্মীদের datasheets for datasets (খসড়া ২০১৮, Communications of the ACM ২০২১)। অন্যের model-এর card পড়া পর্ব ৭১ (Pretrained model ব্যবহার — Hugging Face, model card আর লাইসেন্স)-এ; এখানে নিজের card. সংখ্যাগুলো ব্লক ২-এর re-weighting model-এর (দলভিত্তিক মান ডেস্কটপে একই কোডে মাপা)।
# Model card — বাজারঘর "পরে টাকা দিন" অনুমোদন, v2.0 (কাল্পনিক)
## উদ্দেশ্য
- ৫,০০০ টাকা পর্যন্ত অর্ডারে "পরে টাকা দিন" প্রস্তাব করা হবে কি না
- প্রত্যাখ্যান মানে ক্যাশ অন ডেলিভারি; কোনো গ্রাহককে কেনা থেকে আটকানো নয়
- সীমার কাছের score (0.40–0.60) মানুষ দেখবেন
## যে কাজে ব্যবহার নিষেধ
- ঋণ, চাকরি, ভাড়া বা বিমার সিদ্ধান্ত; অন্য প্রতিষ্ঠানকে score দেওয়া
## Model আর ডেটা
- LogisticRegression (scikit-learn 1.8.0); input: orders, ret, area 1–6
- জেলা input নয়; শুধু মূল্যায়নের জন্য আলাদা, সীমিত প্রবেশাধিকারে রাখা
- Training: 2,000 আবেদন, re-weighting (জেলা × label); datasheet দেখুন
## দলভিত্তিক ফল (test, n: A 1390, নদীচর 610)
- অনুমোদন A 0.699 │ নদীচর 0.574
- TPR A 0.827 │ নদীচর 0.734
- FPR A 0.472 │ নদীচর 0.384
- accuracy (আসল ফল) 0.708; খাতার label ধরে 0.680
## জানা সীমা
- খাতার label নদীচরে পক্ষপাতদুষ্ট (courier-এর দেরি); নতুন label নীতি চলছে
- area code নদীচরের proxy; ওজন মাসে একবার পর্যালোচনা
- নতুন গ্রাহক (আগের অর্ডার কম) ভালো গ্রাহক হলেও কম অনুমোদন পান
## নজরদারি আর যোগাযোগ
- প্রতি মাসে দলভিত্তিক অনুমোদন; অনুপাত ভিত্তির 0.10 নিচে দুই মাস → পর্যালোচনা
- মালিক: ঝুঁকি-দল; ঠিকানা: responsible-ai@bazarghar.exampleকার্ডে ভালো খবর কম, সীমা বেশি: যিনি ব্যবহার করবেন বা যাঁর ওপর ব্যবহার হবে, তিনি যেন জানেন কোথায় ভরসা নেই। বাস্তবে কার্ডে থাকবে খাতার label-এর মাপ আর তার উৎস নিয়ে সতর্কবার্তা।
Datasheet ডেটাসেটের জন্মবৃত্তান্ত। Gebru ও সহকর্মীদের প্রশ্নগুলো সাতটি ভাগে সাজানো; বাজারঘরের জন্য সংক্ষেপে:
| ভাগ | বাজারঘরের উত্তর (কাল্পনিক) |
|---|---|
| উদ্দেশ্য | “পরে টাকা দিন” অনুমোদনের model train আর মূল্যায়ন; ঝুঁকি-দল বানিয়েছে |
| গঠন | ৪,০০০ আবেদন, প্রতিটি সারি একজন গ্রাহকের একটি আবেদন; কলাম: জেলা, area, orders, ret, label. নাম, ফোন, ঠিকানা নেই। নদীচর প্রায় ৩০% |
| সংগ্রহ | অর্ডার ব্যবস্থা থেকে স্বয়ংক্রিয়; label = courier-এর টাকা জমার তারিখ সাত দিনের মধ্যে কি না। জানা সমস্যা: courier-এর দেরি গ্রাহকের দেরি হিসেবে লেখা হয় |
| প্রক্রিয়াকরণ | ফেরতের হার তিন দশমিকে; কাঁচা ডেটা আলাদা |
| ব্যবহার | শুধু এই সুবিধার সিদ্ধান্ত। বিজ্ঞাপন, ঋণ বা অন্য প্রতিষ্ঠানে দেওয়া নিষেধ |
| বিতরণ | বাইরে দেওয়া হয় না; ভেতরে ঝুঁকি-দলের অনুমতিতে |
| রক্ষণাবেক্ষণ | প্রতি তিন মাসে নতুন সংস্করণ; মোছার অনুরোধে সব কপি থেকে মোছা |
“সংগ্রহ” সারিটিই এই পর্বের পুরো গল্প: শুরুতে কেউ “label ঠিক কী মাপে?” জিজ্ঞেস করলে নদীচরের ক্ষতি আগেই ধরা পড়ত। পর্ব ৬২-এর EDA রিপোর্টের “খোলা প্রশ্ন” অংশ এখানে কাজে লাগে।
৯ · Deploy-এর পরে — মানুষের তত্ত্বাবধান, নজরদারি আর ঘটনা সামলানো
প্রত্যাখ্যাত গ্রাহক কী করতে পারেন
মানুষের তত্ত্বাবধান মানে নকশায় লেখা থাকা কে, কখন, কোন ক্ষমতায় সিদ্ধান্ত বদলাতে পারেন। বাজারঘরের আপিলের পথ:
- কারণ জানানো: সহজ ভাষায় প্রধান দুটি কারণ, ওজন × মান থেকে (পর্ব ৫৯)। “Area code” কারণ হিসেবে দেখাতে অস্বস্তি হলে, feature-টি থাকা উচিত নয়।
- বিকল্প: ক্যাশ অন ডেলিভারি বা আগাম পরিশোধ, কোনো অতিরিক্ত খরচ ছাড়া।
- মানুষের পর্যালোচনা: অ্যাপে একটি বোতাম; একজন কর্মী নির্দিষ্ট সময়ে (ধরুন তিন কর্মদিবস) দেখবেন, সিদ্ধান্ত বাতিলের ক্ষমতাসহ।
- তথ্য শোধরানো: bKash/Nagad-এর লেনদেনের প্রমাণে খাতা ঠিক করা; প্রতিটি সফল আপিল নতুন, ভালো label.
- গোনা: জেলাভেদে আপিল আর সফলতার হার মাসে মাসে; কোনো দলে সফল আপিল বেশি মানে model সেখানে ভুল করছে।
একজন কর্মী দিনে ৫০০টি সিদ্ধান্তে শুধু “ঠিক আছে” চাপলে তত্ত্বাবধান শুধু কাগজে (automation bias); তিনি কতবার model-এর বিপরীতে গেলেন, তা-ও মাপুন।
নজরদারি: ১২ মাসের simulation
Deploy-এর দিন ভালো model পরে খারাপ হতে পারে (concept drift, covariate shift; পর্ব ৫৬)। ধরুন নতুন label নীতিতে শুধু orders আর ret দিয়ে model বানানো হলো, আর মাস ৬ থেকে নদীচরে প্রচারণায় অনেক নতুন গ্রাহক এলেন, যাঁদের আগের অর্ডার প্রায় নেই। নিয়ম: প্রথম তিন মাসের গড় অনুপাত ভিত্তি; অনুপাত তার ০.১০-এর বেশি নিচে পরপর দুই মাস থাকলে সতর্কতা।
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression
from bazar_credit import make_data
base = make_data(n=4000, seed=7, b_delay=0.0) # নতুন, পরিষ্কার label-এ train
model = LogisticRegression(C=1e10, max_iter=1000).fit(base[["orders", "ret"]], base.repaid)
log, streak, ref = [], 0, None
for m in range(1, 13): # deploy-এর পরের ১২ মাস
new = 0.0 if m <= 5 else min(0.8, 0.16 * (m - 5)) # মাস ৬ থেকে নদীচরে প্রচার: নতুন গ্রাহক
d = make_data(n=800, seed=100 + m, b_new=new)
d["ok"] = (model.predict_proba(d[["orders", "ret"]])[:, 1] >= 0.5).astype(int)
sel = d.groupby("district")["ok"].mean()
tpr = d[d.repaid == 1].groupby("district")["ok"].mean()
ratio = sel["B"] / sel["A"]
if m == 3: ref = np.mean([r["ratio"] for r in log] + [ratio]) # প্রথম ৩ মাসের ভিত্তি
streak = streak + 1 if m > 3 and ratio < ref - 0.10 else 0
log.append({"month": m, "new_B": new, "sel_A": sel["A"], "sel_B": sel["B"],
"ratio": ratio, "TPR_gap": tpr["A"] - tpr["B"],
"alert": "সতর্কতা!" if streak >= 2 else ""})
mon = pd.DataFrame(log).round(3)
print(mon.to_string(index=False), f"\nভিত্তি অনুপাত {ref:.3f}, সতর্কতার রেখা {ref - 0.10:.3f}")
ax = mon.plot(x="month", y=["ratio", "TPR_gap"], marker="o", figsize=(6.4, 3.2),
title="Monthly fairness monitoring (simulated)")
ax.axhline(ref - 0.10, ls="--", c="gray"); ax.set_xlabel("month after deployment")
ax.legend(["selection ratio B/A", "TPR gap A - B", "alert line"]); plt.show()আসল আউটপুট (ব্রাউজারের Python, scikit-learn 1.8.0; ছবিটি এর নিচে):
month new_B sel_A sel_B ratio TPR_gap alert
1 0.00 0.769 0.656 0.852 0.130
2 0.00 0.774 0.654 0.846 0.132
3 0.00 0.768 0.656 0.855 0.069
4 0.00 0.784 0.609 0.776 0.125
5 0.00 0.794 0.629 0.792 0.121
6 0.16 0.765 0.580 0.758 0.156
7 0.32 0.761 0.462 0.608 0.146
8 0.48 0.805 0.407 0.505 0.330 সতর্কতা!
9 0.64 0.791 0.327 0.413 0.380 সতর্কতা!
10 0.80 0.759 0.284 0.374 0.335 সতর্কতা!
11 0.80 0.746 0.253 0.339 0.419 সতর্কতা!
12 0.80 0.789 0.271 0.343 0.423 সতর্কতা!
ভিত্তি অনুপাত 0.851, সতর্কতার রেখা 0.751
প্রথম পাঁচ মাস অনুপাত ০.৭৮–০.৮৬-এ নড়ে: নদীচরে আগের অর্ডার সত্যিই কম, তাই পরিষ্কার label-এও পুরো সমতা নেই। মাস ৪-এর ০.৭৭৬ রেখার (০.৭৫১) ওপরে; মাসে ৮০০টি আবেদনে এমন ওঠানামা স্বাভাবিক, তাই “পরপর দুই মাস” শর্ত, যাতে মিথ্যা সতর্কতা না বাজে। মাস ৭-এ ০.৬০৮ (প্রথম সংকেত), মাস ৮-এ ০.৫০৫, তখন সতর্কতা। মাস ১২-তে অনুপাত ০.৩৪৩, TPR ফাঁক ০.৪২৩: নতুন ভালো গ্রাহকেরা “আগের অর্ডার নেই” বলে বাদ পড়ছেন।
সৎ সতর্কতা: TPR-এর কলামটি বাস্তবে এভাবে মাপা যায় না। আসল ফল আসে মাসখানেক পরে, আর শুধু অনুমোদিতদের (selective labels). বাস্তবে সঙ্গে সঙ্গে পাওয়া যায় selection rate, score-এর বণ্টন আর নতুন গ্রাহকের ভাগ; আর দেরিতে পাওয়া যায় অনুমোদিতদের ফল আর আপিলের ফল। নজরদারির নকশায় কোন মাপ কবে পাওয়া যাবে, তা আগে লিখে রাখুন (model monitoring).
একটি incident note
সতর্কতা বাজলে কী করবেন, তা ঘটনার আগে লেখা থাকা চাই (incident response). নিচের নোটটি মাস ৮-এর সতর্কতার পর লেখা, কাল্পনিক:
# ঘটনা: নদীচরে "পরে টাকা দিন" অনুমোদন কমে যাওয়া
সনাক্ত: মাস ৮, মাসিক নজরদারি (অনুপাত 0.608 → 0.505, রেখা 0.751)
প্রভাব: নদীচরের নতুন গ্রাহক; ক্ষতি = সুবিধা না পাওয়া, কেনা আটকায়নি
তাৎক্ষণিক পদক্ষেপ: আগের অর্ডার ২-এর কম এমন আবেদন মানুষের পর্যালোচনায়
মূল কারণ: প্রচারণায় নতুন গ্রাহক বেড়েছে; model "অর্ডারের ইতিহাস"-কে
বিশ্বস্ততার একমাত্র সংকেত ধরে, নতুনদের জন্য তথ্য নেই
স্থায়ী সমাধান: নতুন গ্রাহকের জন্য আলাদা নিয়ম (প্রথম অর্ডার ছোট সীমায়);
প্রচারণার আগে ঝুঁকি-দলকে জানানো বাধ্যতামূলক
শিক্ষা: ব্যবসার সিদ্ধান্তও ডেটার বণ্টন বদলায়; নজরদারি কাজ করেছে, তবে ২ মাস দেরিতেচালু করার আগের checklist
- সমস্যা আর ক্ষতি: model ভুল করলে কে, কীভাবে ক্ষতিগ্রস্ত হন? “চালু না করা” বিকল্পটি ভেবে দেখা হয়েছে?
- Label: ঠিক কী মাপে, কে তৈরি করেছে, কোন দলে ভুল হতে পারে? Datasheet লেখা হয়েছে?
- ডেটা: শুধু দরকারি কলাম; উদ্দেশ্য লেখা; সংবেদনশীল কলাম আলাদা, সীমিত প্রবেশাধিকারে।
- মূল্যায়ন: প্রতিটি দলের n, selection rate, TPR, FPR আর calibration, ব্যবধানসহ; proxy খোঁজা হয়েছে।
- Fairness-এর কোন সংজ্ঞা বাছা হলো, কেন, আর কে অনুমোদন দিলেন: লিখিত।
- Model card: উদ্দেশ্য, নিষেধ, দলভিত্তিক ফল, সীমা, মালিক।
- মানুষ: আপিলের পথ, কারণ জানানো, পর্যালোচকের ক্ষমতা আর কাজের চাপ।
- নজরদারি: কোন মাপ, কত দিন পরপর, সতর্কতার নিয়ম, কে দেখবেন; incident-এর পরিকল্পনা আর পুরোনো নিয়মে ফেরার পথ।
- আইন আর গোপনীয়তা: বর্তমান আইনের লেখা দেখা হয়েছে; প্রয়োজনে আইনি পরামর্শ।
Generative AI-এর বাড়তি ঝুঁকি, সংক্ষেপে
গ্রাহকসেবায় ভাষা model ব্যবহার করলে (পর্ব ৭০, LLM কীভাবে কাজ করে — token, পরের শব্দের অনুমান আর সীমা) তিনটি ঝুঁকি যোগ হয়। Hallucination: model আত্মবিশ্বাসের সঙ্গে ভুল নীতি বলতে পারে, তাই নীতির উত্তর অফিসিয়াল নথি থেকে, আর অনিশ্চিত হলে মানুষের কাছে। Prompt injection: বার্তা বা রিভিউয়ে লুকোনো নির্দেশ model-এর আচরণ বদলাতে পারে, আর এটি এখনও পুরো সমাধান হয়নি বলে মনে করা হয়; বিস্তারিত Cybersecurity কোর্সের AI নিরাপত্তা পর্বে। Training ডেটার copyright আর সম্মতি: বড় model-এর training-এ লেখক-শিল্পীদের সম্মতি ছিল কি না, তা নিয়ে কয়েকটি দেশে মামলা আর বিতর্ক চলছে; ফল অমীমাংসিত, আইন দেশভেদে আলাদা। গ্রাহকের চ্যাট দিয়ে model উন্নত করতে চাইলে সেটিও purpose limitation-এর প্রশ্ন।
১০ · AI-এর যুগে — AI assistant-এর লেখা fairness আর privacy কোড যাচাই
“আমার model-এর bias check করে দাও” বললে AI assistant দ্রুত কোড আর একটি আশ্বাসজনক সংখ্যা দেয়। সাধারণ ভুল: protected attribute মুছেই “ন্যায্য” ঘোষণা, train set-এ মাপা, TPR আর FPR গুলিয়ে ফেলা, label-কে প্রশ্নাতীত ধরা, hash করে “বেনামি” বলা, ধারা নম্বরসহ আইনের নিশ্চিত দাবি।
নির্দিষ্ট হোন, যাচাই করা যায় এমন আউটপুট চান। আসল গ্রাহকের ডেটা, নাম, ফোন বা ঠিকানা কখনো পেস্ট করবেন না; কলামের নাম আর কয়েকটি বানানো সারিই যথেষ্ট।
AI-এর লেখা fairness বা privacy কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
আরও গভীরে: Solon Barocas, Moritz Hardt আর Arvind Narayanan-এর খোলা বই Fairness and Machine Learning সংজ্ঞাগুলোকে independence (parity), separation (equalised odds) আর sufficiency (calibration) এই তিন ভাগে সাজায়। Selective labels নিয়ে Himabindu Lakkaraju ও সহকর্মীদের ২০১৭ সালের (KDD) কাজ আলোচিত। যুক্তরাষ্ট্রের ২০২০ সালের আদমশুমারিতে differential privacy ব্যবহার নিয়ে বিতর্ক ε বাছাইয়ের দাম দেখায়।
পরীক্ষা ও ইন্টারভিউতে:
- Demographic parity আর equal opportunity-র পার্থক্য? প্রথমটি সব দলে সমান selection rate চায়, y দেখে না; দ্বিতীয়টি ভালো (y = ১) মানুষদের মধ্যে সমান TPR চায়।
- Selection rate A ০.৬০, B ০.৩৯; চার-পঞ্চমাংশ নিয়মে কী বলে? অনুপাত ০.৩৯ ÷ ০.৬০ = ০.৬৫ < ০.৮, তাই disparate impact-এর প্রাথমিক ইঙ্গিত (যুক্তরাষ্ট্রের একটি নির্দেশিকা)।
- Protected attribute মুছলে bias কেন থাকে? Proxy feature (এলাকা, পোস্ট কোড) তথ্যটি বহন করে, আর পক্ষপাতদুষ্ট label থেকে যায়।
- Calibration আর সমান FPR একসঙ্গে কখন অসম্ভব? দলের base rate আলাদা আর model নিখুঁত নয় এমন অবস্থায় (Kleinberg ও সহকর্মী; Chouldechova).
- k-anonymity আর differential privacy? প্রথমটি টেবিলের প্রতিটি quasi-identifier-দলে অন্তত k জন রাখে; দ্বিতীয়টি ফলে হিসাব করা শব্দ মেশায়, যাতে একজনের থাকা-না-থাকায় ফল প্রায় বদলায় না (Laplace scale = Δ/ε)।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১COMPAS বিতর্কে দুই পক্ষই নিজের সংখ্যাকে ঠিক বলেছিল। বাজারঘর কোন fairness সংজ্ঞা বেছে নেবে, আর সেই বাছাই কে করবেন?
২০১৬ সালে ProPublica-র প্রতিবেদন ফ্লোরিডার একটি কাউন্টির ডেটায় দেখায় যে COMPAS score-এ কৃষ্ণাঙ্গ আসামিদের মধ্যে যাঁরা দুই বছরে আর অপরাধ করেননি, তাঁদের “উচ্চ ঝুঁকি” বলার হার শ্বেতাঙ্গদের প্রায় দ্বিগুণ। নির্মাতার জবাব: একই score-এ দুই দলের পুনরাবৃত্তির হার প্রায় সমান। পরের বছর Chouldechova আর Kleinberg ও সহকর্মীদের কাজ দেখায় যে base rate আলাদা হলে দুটি একসঙ্গে সম্ভব নয়। মানে বিতর্কটি অঙ্কের ভুল নিয়ে নয়, মূল্যবোধ নিয়ে।
বাজারঘরের ক্ষেত্রে প্রশ্নটি করা যায় ক্ষতি দিয়ে। Model-এর দুই রকম ভুল: একজন ভালো গ্রাহককে সুবিধা না দেওয়া (FN), আর একজন দেরি-করা গ্রাহককে সুবিধা দেওয়া (FP). FN-এর ক্ষতি গ্রাহকের: বঞ্চনা, আর feedback loop-এ ভবিষ্যতেও প্রমাণের সুযোগ না পাওয়া। FP-এর ক্ষতি দোকানের: কিছু টাকা দেরিতে আসা। তাই ভালো গ্রাহকদের মধ্যে সমান সুযোগ, অর্থাৎ equal opportunity (সমান TPR), একটি যুক্তিসংগত প্রথম পছন্দ। Demographic parity এখানে কম মানানসই, কারণ আগের অর্ডারের মতো বৈধ পার্থক্যও আছে।
কিন্তু বাছাই শুধু ডেটা বিজ্ঞানীর নয়। ঝুঁকি-দল, আইন বিভাগ, গ্রাহকসেবা, আর সম্ভব হলে নদীচরের গ্রাহকদের প্রতিনিধিদের মত লাগে, কারণ তাঁরাই জানেন কোন ভুল বেশি কষ্ট দেয়। সিদ্ধান্তটি কারণসহ model card-এ লিখে রাখতে হয়। আর এক জায়গায় কোনো বাছাই চলে না: label-এর ভুল। সমান TPR মাপা হয় label দিয়ে; label পক্ষপাতদুষ্ট হলে যেকোনো সংজ্ঞা ভুল জিনিসকে সমান করে।
মূল উপলব্ধি: fairness-এর সংজ্ঞা বাছাই একটি মূল্যবোধের সিদ্ধান্ত; কার কোন ভুলে কী ক্ষতি, তা দিয়ে শুরু করুন, ক্ষতিগ্রস্তদের মত নিন, আর সিদ্ধান্ত ও তার কারণ লিখে রাখুন।
প্র ০২“আমরা জাতি বা জেলা কোথাও ব্যবহার করি না” দাবি কেন যথেষ্ট নয়? একটি প্রকাশিত উদাহরণ দিয়ে ব্যাখ্যা করুন।
২০১৯ সালে Science পত্রিকায় Ziad Obermeyer ও সহকর্মীরা যুক্তরাষ্ট্রের হাসপাতালে ব্যবহৃত একটি বাণিজ্যিক algorithm বিশ্লেষণ করেন, যা বাড়তি যত্নের জন্য রোগী বাছত, জাতি ব্যবহার না করেই। কিন্তু “কার স্বাস্থ্যের প্রয়োজন বেশি” মাপতে তা ভবিষ্যতের চিকিৎসা-খরচ অনুমান করত। গবেষকদের বিশ্লেষণে দেখা যায়, একই score-এর কৃষ্ণাঙ্গ রোগীরা গড়ে বেশি অসুস্থ ছিলেন, কারণ চিকিৎসার সুযোগে বৈষম্যের ফলে একই অসুস্থতায় তাঁদের পেছনে খরচ কম হতো। খরচ ছিল প্রয়োজনের একটি পক্ষপাতদুষ্ট proxy. গবেষকেরা জানান, label বদলে সরাসরি স্বাস্থ্যের মাপ ব্যবহার করলে বৈষম্য অনেকটা কমে।
মিলটি স্পষ্ট: আমাদের label “courier কবে টাকা জমা দিল” আসলে “গ্রাহক সময়মতো দেন কি না”-র একটি proxy, আর তা নদীচরে ভাঙে। Feature-এর দিকেও একই কথা: জেলা মুছলে area code সেই কাজ করে, অংশ ৪-এ দেখেছেন অনুপাত মাত্র ০.৪০৫ থেকে ০.৪৯০. বাস্তবে প্রায়ই একক কলাম নয়, বহু কলামের সমন্বয় একটি দলকে চিনে ফেলে।
তাই “ব্যবহার করি না” মানে শুধু input-এর তালিকা নিয়ে একটি দাবি, ফল নিয়ে নয়। যাচাইয়ের পথ: ফল দলভিত্তিক মাপা; প্রতিটি feature (আর feature-এর সমন্বয়) দিয়ে দল কতটা অনুমান করা যায় দেখা, যেমন একটি ছোট classifier দিয়ে জেলা অনুমান করার accuracy; আর label কী মাপে তা খুঁটিয়ে দেখা। এর জন্য protected attribute মূল্যায়নের কাজে জানা থাকা দরকার, যা নিজেই গোপনীয়তার সঙ্গে একটি সমঝোতা।
মূল উপলব্ধি: ন্যায্যতা input-এর তালিকায় নয়, ফলে মাপা হয়; proxy feature আর proxy label দুটিই মুছে দেওয়া কলামের তথ্য ফিরিয়ে আনতে পারে।
প্র ০৩বেনামি ডেটা থেকে মানুষ চেনার কয়েকটি বিখ্যাত ঘটনা কী শিখিয়েছে, আর differential privacy কেন একটি ভিন্ন ধরনের প্রতিশ্রুতি?
১৯৯০-এর দশকে ম্যাসাচুসেটসে সরকারি কর্মীদের হাসপাতালের রেকর্ড নাম মুছে গবেষকদের দেওয়া হয়। Latanya Sweeney ভোটার তালিকা কিনে পোস্ট কোড, জন্মতারিখ আর লিঙ্গ দিয়ে জুড়ে তখনকার গভর্নরের রেকর্ড খুঁজে পান বলে ব্যাপকভাবে বর্ণিত। তাঁর পরের হিসাবে, ১৯৯০ সালের আদমশুমারি ধরে যুক্তরাষ্ট্রের প্রায় ৮৭% মানুষকে এই তিনটি তথ্যেই আলাদা করা যায়। এ থেকেই ২০০২ সালে তাঁর k-anonymity-র প্রস্তাব। ২০০৬ সালে AOL-এর “বেনামি” search log থেকে সাংবাদিকেরা একজনকে শনাক্ত করেন; ২০০৮ সালে Arvind Narayanan আর Vitaly Shmatikov দেখান যে Netflix Prize-এর rating ডেটা প্রকাশ্য সিনেমা-রিভিউয়ের সঙ্গে মিলিয়ে কিছু ব্যবহারকারীকে চেনা যায়।
শিক্ষা তিনটি। এক, কোন কলাম quasi-identifier, তা আগে বলা কঠিন: rating বা search-এর মতো আচরণও আঙুলের ছাপ। দুই, আক্রমণকারীর বাইরের তথ্য আপনি জানেন না; আমাদের লাকি ড্র-এর পোস্ট দোকান নিজেই দিয়েছিল। তিন, k-anonymity-র মতো টেবিল-ভিত্তিক সুরক্ষা ছোট দলে অনেক ডেটা কেটে ফেলে (১২টির ৫টি), আর তবু দলের সবার সংবেদনশীল মান এক হলে তথ্য ফাঁস হয়।
Differential privacy প্রতিশ্রুতিটিকে টেবিল থেকে প্রক্রিয়ায় সরিয়ে আনে: প্রকাশিত যেকোনো ফলের বণ্টন একজন মানুষ থাকুক বা না থাকুক প্রায় একই, আক্রমণকারীর বাইরের তথ্য যা-ই থাকুক। এটি গাণিতিকভাবে প্রমাণযোগ্য, আর একাধিক প্রকাশের মোট খরচ যোগ করে হিসাব রাখা যায়। দাম হলো নির্ভুলতা, বিশেষ করে ছোট দলে, আর ε কত হবে তা একটি নীতিগত সিদ্ধান্ত, যার কোনো সর্বজনীন সঠিক মান নেই।
মূল উপলব্ধি: নাম মোছা বেনামি নয়; ডেটা জোড়া দেওয়ার ঝুঁকি আগে থেকে পুরো জানা যায় না, তাই কম ডেটা রাখা, সংকলিত ফল দেওয়া আর প্রয়োজনে differential privacy-র মতো প্রমাণযোগ্য পদ্ধতি বেশি নির্ভরযোগ্য।
প্র ০৪নদীচরের জন্য সীমা ০.৩৫ আর অন্যদের জন্য ০.৫ রাখা কি ন্যায্য, নাকি নিজেই বৈষম্য? দুই পক্ষের যুক্তি দিন।
পক্ষে যুক্তি: সবার জন্য এক সীমায় নদীচরের অনেক ভালো গ্রাহক বাদ পড়েন, কারণ খাতার ভুলে তাঁদের score কৃত্রিমভাবে কম। দলভিত্তিক সীমা সেই পরিমাপের ভুল শুধরে দেয়: সীমা ০.৩৫ করলে TPR-এর ফাঁক ০.৩৪৭ থেকে ০.১২৪, আর আসল ফলের সঙ্গে accuracy প্রায় একই থাকে (০.৭০০ থেকে ০.৭০২)। Hardt ও সহকর্মীরা ২০১৬ সালে দেখান যে এমন post-processing দিয়ে equalised odds বা equal opportunity অর্জন করা যায়, model না বদলেও। যুক্তিটি এই: আমরা মানুষকে সমান দেখছি না, ভুল মাপযন্ত্রকে ঠিক করছি।
বিপক্ষে যুক্তি: সিদ্ধান্তের মুহূর্তে একজনের জেলা দেখে তাঁকে ভিন্ন নিয়মে বিচার করা হচ্ছে। একই score-এর দুজনের একজন পান, একজন পান না, শুধু জেলার কারণে। যুক্তরাষ্ট্রের মতো কিছু দেশে ঋণ আর নিয়োগে protected attribute সরাসরি ব্যবহারে কড়া আইনি সীমা আছে বলে আইন-গবেষকেরা আলোচনা করেন, আর ইতিবাচক পদক্ষেপ (affirmative action) নিয়ে সেখানে দীর্ঘ বিতর্ক আছে। তাছাড়া দলভিত্তিক সীমা মূল কারণ (খাতার ভুল) লুকিয়ে রাখে; সংখ্যা ঠিক দেখালে label ঠিক করার চাপ কমে যায়।
একটি মধ্যপথ: সমস্যার উৎস যখন জানা পরিমাপের ভুল, তখন প্রথমে সেটি শোধরানো (label নীতি বদলানো, bKash/Nagad-এর লেনদেনের সময় ব্যবহার), সাময়িকভাবে re-weighting বা মানুষের পর্যালোচনা দিয়ে ক্ষতি কমানো, আর দলভিত্তিক সীমা দরকার হলে তা আইনি পরামর্শ নিয়ে, লিখিত কারণ আর শেষ হওয়ার তারিখসহ।
মূল উপলব্ধি: দলভিত্তিক সীমা একটি কার্যকর কিন্তু বিতর্কিত হাতিয়ার; মূল কারণ ঠিক করা আগে, আর যেকোনো দলভিত্তিক সমন্বয় স্বচ্ছ, আইনসম্মত আর সাময়িক হওয়া উচিত।
প্র ০৫Model card আর datasheet কি সত্যিই ক্ষতি কমায়, নাকি শুধু কাগজ? কীভাবে নথিপত্রকে কাজের করা যায়?
Mitchell ও সহকর্মীদের ২০১৯ সালের প্রস্তাব আর Gebru ও সহকর্মীদের datasheet-এর ধারণা দ্রুত ছড়িয়েছে: Hugging Face-এর মতো model hub-এ card এখন প্রায় প্রতিটি model-এর সঙ্গে থাকে, আর ইউরোপীয় ইউনিয়নের AI Act-এর মতো আইনে উচ্চ-ঝুঁকির ব্যবস্থার জন্য প্রযুক্তিগত নথির দাবি আছে বলে জানা যায় (মানুষের ঋণযোগ্যতা যাচাই সেই তালিকায় আছে বলে আলোচিত)। কিন্তু সমালোচনাও আছে। গবেষকেরা লক্ষ করেছেন, অনেক card-এ “সীমা” আর “নৈতিক বিবেচনা” অংশ ফাঁকা বা গৎবাঁধা থাকে। যে নথি কেউ পড়েন না, বা যা সিদ্ধান্ত বদলায় না, তা “ethics washing”-এর ঝুঁকি তৈরি করে: কাগজে দায়িত্বশীলতা, বাস্তবে নয়।
নথিকে কাজের করার উপায়। এক, নথি আগে, model পরে: datasheet-এর “label ঠিক কী মাপে” প্রশ্নটি প্রথম সপ্তাহে করলে নদীচরের সমস্যা train-এর আগেই ধরা পড়ত। দুই, প্রতিটি দাবির পাশে সংখ্যা আর উৎস (“নদীচরে TPR ০.৭৩৪, n = ৬১০”), যা অন্যে যাচাই করতে পারেন। তিন, নথিকে প্রক্রিয়ায় বাঁধা: card-এর নজরদারির নিয়মই মাসিক কোডে চলে, আর সতর্কতার পর card হালনাগাদ বাধ্যতামূলক। চার, মালিক আর পর্যালোচনার তারিখ। পাঁচ, প্রত্যাখ্যাত গ্রাহকের জন্য সহজ ভাষার সংক্ষিপ্ত রূপ।
পর্ব ৬-এর “problem of many hands” এখানে প্রাসঙ্গিক: অনেক মানুষের ছোট ছোট সিদ্ধান্তে ক্ষতি হলে কেউ দায় নেন না। নথি সেই হাতগুলোর নাম লিখে দেয়।
মূল উপলব্ধি: নথি নিজে ক্ষতি কমায় না; যে নথি আগে লেখা হয়, সংখ্যা ও উৎস দেয়, নজরদারি আর আপিলের সঙ্গে যুক্ত থাকে আর যার একজন মালিক আছেন, সেটিই সিদ্ধান্ত বদলায়।
অনুশীলন
হাতে হিসাব, সমালোচনা, আউটপুট অনুমান আর কোড; প্রতিটির উত্তর নিচে লুকানো আছে।
-
হাতে fairness metric (হিসাব): একটি অনুমোদন model-এর test ফল। দল A: ২০০ আবেদন, তার ১২০ জন সত্যিই সময়মতো দেন; অনুমোদন ১৩০ জনকে, তার ১১০ জন ভালো গ্রাহক। দল B: ১০০ আবেদন, ৫০ জন ভালো; অনুমোদন ৪০ জনকে, তার ৩৫ জন ভালো। প্রতিটি দলের selection rate, TPR, FPR আর PPV; demographic parity-র পার্থক্য ও অনুপাত; আর equalised odds difference বের করুন। চার-পঞ্চমাংশ নিয়ম কী বলে?
- Selection: A = ১৩০ ÷ ২০০ = ০.৬৫০; B = ৪০ ÷ ১০০ = ০.৪০০.
- TPR: A = ১১০ ÷ ১২০ ≈ ০.৯১৭; B = ৩৫ ÷ ৫০ = ০.৭০০.
- FPR: A-তে FP = ১৩০ − ১১০ = ২০, খারাপ ৮০ জন, তাই ২০ ÷ ৮০ = ০.২৫০; B-তে FP = ৫, খারাপ ৫০, তাই ০.১০০.
- PPV: A = ১১০ ÷ ১৩০ ≈ ০.৮৪৬; B = ৩৫ ÷ ৪০ = ০.৮৭৫.
- DP পার্থক্য ০.২৫০, অনুপাত ০.৪০০ ÷ ০.৬৫০ ≈ ০.৬১৫ < ০.৮: চার-পঞ্চমাংশ নিয়মে disparate impact-এর প্রাথমিক ইঙ্গিত।
- TPR ফাঁক ০.২১৭, FPR ফাঁক ০.১৫০; equalised odds difference = ০.২১৭ (বড়টি)।
PPV প্রায় সমান, অথচ TPR আর FPR আলাদা: base rate (০.৬০ বনাম ০.৫০) আলাদা বলে এমনটাই প্রত্যাশিত।
-
সমালোচনা ও নতুন নকশা: একজন সহকর্মী লিখেছেন: “নতুন ঋণ-যাচাই model-এ লিঙ্গ আর জেলার কলাম মুছে দিয়েছি, তাই এটি ন্যায্য। পুরো ডেটায় accuracy ০.৯১. গ্রাহকের ফোন নম্বর SHA-256 দিয়ে hash করে বিপণন সংস্থাকে দিয়েছি, তাই ডেটা বেনামি। Model-এর সিদ্ধান্ত চূড়ান্ত, আপিলের ব্যবস্থা লাগবে না, কারণ model মানুষের চেয়ে নিরপেক্ষ।” কী কী সমস্যা, আর আপনি কীভাবে সাজাবেন?
- কলাম মোছা ≠ ন্যায্য: proxy (এলাকা, পেশা, কেনাকাটার ধরন) তথ্য বহন করে, আর label-এ অতীতের পক্ষপাত থাকতে পারে। দলভিত্তিক selection, TPR, FPR আর calibration মাপতে হবে; তার জন্য এই কলামগুলো মূল্যায়নের কাজে, সীমিত প্রবেশাধিকারে রাখতে হবে।
- পুরো ডেটার accuracy: train আর test মেশানো, আর একটি গড় দলভেদ লুকায়। আলাদা test set, প্রতিটি দলের n আর ব্যবধান।
- Hash = বেনামি নয়: মোবাইল নম্বরের সম্ভাব্য সংখ্যা সীমিত, তাই সব নম্বর hash করে উল্টানো সহজ; এটি pseudonymisation. বিপণনে দেওয়া purpose limitation আর সম্মতির প্রশ্নও তোলে।
- আপিল নেই: model নিরপেক্ষ নয়, ডেটার পক্ষপাত বহন করে; ভুল শোধরানোর পথ না থাকলে ভুল label-ও কখনো ঠিক হয় না (feedback loop).
নতুন নকশা: datasheet দিয়ে শুরু, test set-এ দলভিত্তিক মাপ আর proxy খোঁজা, লিখিত fairness লক্ষ্য, model card, আপিলের পথ, বিপণনে শুধু সংকলিত ও সম্মতিপ্রাপ্ত ডেটা, আর মাসিক নজরদারি।
-
আউটপুট অনুমান: নিচের কোড কী ছাপবে?
import pandas as pd d = pd.DataFrame({"g": list("AAAABBBB"), "y": [1, 1, 0, 0, 1, 1, 1, 0], "h": [1, 1, 1, 0, 1, 0, 0, 0]}) sel = d.groupby("g")["h"].mean() tpr = d[d.y == 1].groupby("g")["h"].mean() print(sel.to_dict(), round(sel["B"] / sel["A"], 3)) print(tpr.to_dict()) k = d.groupby(["g", "y"]).size() print(k.min())সত্যিকারের run (ডেস্কটপ, Python 3.14.4, pandas 3.1.1):
{'A': 0.75, 'B': 0.25} 0.333 {'A': 1.0, 'B': 0.3333333333333333} 1প্রথম লাইন: A-র চারটি h (১, ১, ১, ০), গড় ০.৭৫; B-র (১, ০, ০, ০), গড় ০.২৫; অনুপাত ০.২৫ ÷ ০.৭৫ ≈ ০.৩৩৩. দ্বিতীয় লাইন: y = ১ সারিগুলো, A-তে দুটি (দুটিতেই h = ১), তাই ১.০; B-তে তিনটি (h = ১, ০, ০), তাই ১/৩, যা
roundছাড়া পুরো দশমিকে ছাপা হয়। তৃতীয় লাইন: (g, y) দলের আকার A-০: ২, A-১: ২, B-০: ১, B-১: ৩; সবচেয়ে ছোট ১. Quasi-identifier হিসেবে (g, y) নিলে k = ১, মানে B-র একমাত্র y = ০ সারিটি একা। -
নিজের হাতে metric (কোড):
group_report(y, h, g)লিখুন, যা প্রতিটি দলের selection, TPR, FPR আর PPV, demographic parity-র পার্থক্য ও অনুপাত, আর equalised odds difference ফেরত দেয়। অনুশীলন ১-এর গোনা থেকে সারি বানিয়ে চালান, আর Fairlearn-এর সঙ্গে মিলিয়ে দেখুন।def from_counts(n, pos, appr, tp): # দলের গোনা থেকে সারি বানানো y = np.r_[np.ones(pos), np.zeros(n - pos)].astype(int) h = np.r_[np.ones(tp), np.zeros(pos - tp), np.ones(appr - tp), np.zeros(n - pos - (appr - tp))].astype(int) return y, h def group_report(y, h, g): t = pd.DataFrame({"y": y, "h": h, "g": g}) r = t.groupby("g").apply(lambda s: pd.Series({ "selection": s.h.mean(), "TPR": s.h[s.y == 1].mean(), "FPR": s.h[s.y == 0].mean(), "PPV": s.y[s.h == 1].mean()}), include_groups=False) dp = r.selection.max() - r.selection.min() eo = max(r.TPR.max() - r.TPR.min(), r.FPR.max() - r.FPR.min()) return r.round(3), round(dp, 3), round(r.selection.min() / r.selection.max(), 3), round(eo, 3) ya, ha = from_counts(200, 120, 130, 110) yb, hb = from_counts(100, 50, 40, 35) r, dp, ratio, eo = group_report(np.r_[ya, yb], np.r_[ha, hb], ["A"] * 200 + ["B"] * 100)সত্যিকারের run (ডেস্কটপ, Python 3.14.4, pandas 3.1.1, fairlearn 0.15.0):
selection TPR FPR PPV g A 0.65 0.917 0.25 0.846 B 0.40 0.700 0.10 0.875 DP diff 0.25 | DP ratio 0.615 | EO diff 0.217 fairlearn: 0.217 0.615হাতের হিসাব, নিজের কোড আর Fairlearn তিনটিই মেলে।
include_groups=Falseদেওয়া হয়েছে কারণ pandas-এর নতুন version-এ groupby-apply দলের কলামটি function-এ পাঠায় না; পুরোনো টিউটোরিয়ালের কোডে এটি না থাকলে warning বা ভিন্ন আচরণ দেখতে পারেন। একটি দলে কোনো অনুমোদন না থাকলে PPV হবে NaN (শূন্য দিয়ে ভাগ); বাস্তবের কোডে এটি আলাদা করে সামলান।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি অনুমোদন model দায়িত্বশীলভাবে চালু করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- ক্ষতি কার, আর model আদৌ দরকার কি না তা ঠিক করা
- Datasheet লেখা: label কী মাপে আর কীভাবে তৈরি
- Test set-এ দলভিত্তিক selection, TPR, FPR আর calibration মাপা, proxy খোঁজা
- প্রতিকার প্রয়োগ করে accuracy আর fairness দুটিই আবার মাপা
- Model card আর আপিলের পথ লিখে চালু করা
- মাসিক দলভিত্তিক নজরদারি, সতর্কতা বাজলে incident note
দল A-তে ২০০ আবেদনের ১২০টি অনুমোদিত, দল B-তে ১০০-র ৪৫টি। Demographic parity-র অনুপাত কত, আর চার-পঞ্চমাংশ নিয়মে কী ইঙ্গিত?
- ০.১৫; কোনো ইঙ্গিত নেই
- ০.৭৫; ০.৮-এর কম, তাই প্রাথমিক ইঙ্গিত আছে
- ১.৩৩; ১-এর বেশি, তাই ঠিক আছে
- আসল ফল না জেনে বলা যায় না
একটি model-এ দুই দলের selection rate সমান, কিন্তু যাঁরা সত্যিই সময়মতো টাকা দেন তাঁদের মধ্যে অনুমোদনের হার দল A-তে ০.৯, দল B-তে ০.৬. কোন সংজ্ঞাটি ভাঙছে?
- Demographic parity
- দলের ভেতরে calibration
- Equal opportunity (সমান TPR)
- k-anonymity
একটি model থেকে protected attribute-এর কলাম মুছে দেওয়ার পরও দলভিত্তিক অনুমোদনের বড় ফাঁক থেকে গেল। সবচেয়ে সম্ভাব্য কারণ কোনটি?
- Model আগের কলামটি মনে রেখেছে
- Fairness metric সবসময় ভুল সংখ্যা দেয়
- Test set খুব বড়
- অন্য feature (যেমন এলাকা) proxy হিসেবে তথ্যটি বহন করছে, আর label-এ অতীতের পক্ষপাত আছে
দুই দলের base rate আলাদা (০.৬ আর ০.৩), আর একটি model দুই দলেই PPV = ০.৮ এবং TPR = ০.৮ দেয়। FPR সম্পর্কে কী বলা যায়?
- দুই দলে আলাদা হতেই হবে: প্রায় ০.৩০ আর ০.০৮৬
- দুই দলে সমান
- তথ্য যথেষ্ট নয়
- দুই দলেই শূন্য
একটি টেবিল থেকে নাম আর ফোন নম্বর মুছে দেওয়া হলো, কিন্তু জেলা, বয়সের দল আর অর্ডারের তারিখ রইল। জেলা + বয়স + তারিখের প্রতিটি সমন্বয়ের সবচেয়ে ছোট দলে ১টি সারি। কোন কথাটি ঠিক?
- টেবিলটি বেনামি, কারণ কোনো নাম নেই
- টেবিলটি 12-anonymous
- টেবিলটি k = ১, আর বাইরের একটি তালিকার সঙ্গে জুড়ে কাউকে চেনা যেতে পারে
- টেবিলটি differential privacy মানে
একটি count (sensitivity ১) Laplace mechanism-এ ε = ০.৫ দিয়ে প্রকাশ করা হলো। শব্দের scale আর গড় পরম ভুল প্রায় কত?
- scale ০.৫, গড় ভুল ০.৫
- scale ২, গড় ভুল প্রায় ২
- scale ০, কোনো ভুল নেই
- scale ২, গড় ভুল প্রায় ৪
একটি অনুমোদন model-এর label তৈরি হয়েছে courier-এর টাকা জমার তারিখ থেকে, আর একটি এলাকায় courier নিয়মিত দেরি করেন। Re-weighting-এর পর খাতার label ধরে accuracy সামান্য কমল। কোন ব্যাখ্যাটি সবচেয়ে যুক্তিসঙ্গত?
- Model খারাপ হয়েছে, তাই re-weighting বাদ দিতে হবে
- Re-weighting test set বদলে দিয়েছে
- Accuracy কমলেই fairness বাড়ে, এটি সাধারণ নিয়ম
- খাতার label পক্ষপাতদুষ্ট, তাই তার সঙ্গে মিল কমলেও আসল আচরণের সঙ্গে মিল বাড়তে পারে; label যাচাই করে মাপা দরকার
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- আগে label: খাতা যা মাপে আর আপনি যা মাপতে চান, তা এক কি না জিজ্ঞেস করুন। পক্ষপাতদুষ্ট label-এ “calibrated” model-ও মানুষের প্রতি অন্যায় করে।
- দলে দলে মাপুন: selection rate, TPR, FPR, PPV আর calibration, প্রতিটি দলের n-সহ, test set-এ। অনুপাত ০.৮-এর কম হলে চার-পঞ্চমাংশ নিয়মে একটি সংকেত (যুক্তরাষ্ট্রের নির্দেশিকা)।
- কলাম মোছা যথেষ্ট নয়: proxy (area code) তথ্য ফিরিয়ে আনে। Base rate আলাদা হলে calibration আর সমান ভুলের হার একসঙ্গে মেলে না, তাই সংজ্ঞা বাছাই লিখিত মূল্যবোধের সিদ্ধান্ত।
- প্রতিকারের দাম গুনুন: re-weighting, দলভিত্তিক সীমা, ভালো ডেটা বা না চালানো; প্রতিটির পরে accuracy আর fairness দুটিই মাপুন, আর মূল কারণ আগে ঠিক করুন।
- কম রাখুন, লিখে রাখুন, নজর রাখুন: নাম মোছা বেনামি নয় (k = ১ মানে চেনা যায়); Laplace শব্দের গড় ভুল ১/ε. Model card, datasheet, আপিলের পথ, মাসিক নজরদারি আর incident note দায়িত্বকে নির্দিষ্ট মানুষের হাতে দেয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৭৩ · ক্যাপস্টোন — বাংলা পণ্য-রিভিউ থেকে একটি পূর্ণ AI প্রকল্পপরবর্তী পর্বক্যাপস্টোন: বাংলা রিভিউ থেকে sentiment, সমস্যার ধরন আর মিলে যাওয়া রিভিউ — পুরো প্রকল্প।
- AI-এর ভিত্তি আর Python for AI · কোর্স হোম৭৯টি পর্বপুরো কোর্সের রোডম্যাপ; পরের পর্ব ৭৩ · ক্যাপস্টোন — বাংলা পণ্য-রিভিউ থেকে একটি পূর্ণ AI প্রকল্প.
- পর্ব ৬০ · মূল্যায়ন — accuracy, precision, recall, F1 আর ROCআগের ভিত্তিConfusion matrix, TPR, FPR, calibration আর bootstrap.
- পর্ব ৫৯ · Logistic regression — সম্ভাবনা, sigmoid আর decision boundaryআগের ভিত্তিএই পর্বের অনুমোদন model আর ওজন পড়ে কারণ জানানো।
- পর্ব ২৫ · Merge, join আর reshape — pivot, melt আর stackআগের ভিত্তিLinkage-এর merge আর groupby দিয়ে k গোনা।
- পর্ব ৬ · বাস্তবে AI — কোথায় কাজ করে, কোথায় ভুল করে আর দায় কারআগের পর্বProblem of many hands আর বাস্তব দুর্ঘটনার শিক্ষা।
- Cybersecurity · Privacy engineeringপাশের ট্র্যাকডেটা কমানো, pseudonymisation, k-anonymity, differential privacy আর বাংলাদেশের উপাত্ত আইনের যাচাই।
- Cybersecurity · AI নিরাপত্তাপাশের ট্র্যাকPrompt injection, ডেটা ফাঁস আর AI-র নিরাপদ ব্যবহার।