এই পর্বে যা শিখবেন
- পুরো joint table কেন অচল আর conditional independence কীভাবে বাঁচায়, সংখ্যা দিয়ে
- DAG আর CPT দিয়ে একটি Bayesian network আঁকা, আর chain rule দিয়ে যেকোনো joint probability হাতে হিসাব করা
- Enumeration দিয়ে প্রশ্নের উত্তর (P(Rain ∣ Complaint)), হাতে আর NumPy ছাড়া Python-এ
- Chain, fork, collider, explaining away আর d-separation-এর নিয়ম; variable elimination আর sampling-এর ধারণা
- ডেটা থেকে গুনে CPT শেখা, pgmpy দিয়ে একই network চালানো, আর তীরকে কারণ ধরার সীমা বোঝা
মডিউল ৯-এ আমরা অনিশ্চয়তা নিয়ে যুক্তি করছি। পর্ব ৩৫-এ conditional probability, independence আর Bayes-এর নিয়মের প্রথম পরিচয় হয়েছে; পর্ব ৫০-এ (Bayes-এর উপপাদ্য) একটি অনুমান আর একটি প্রমাণ নিয়ে গভীরে যাওয়া হয়েছে, আর পর্ব ৫১-এ (Naive Bayes) অনেক শব্দকে প্রমাণ হিসেবে নেওয়া হয়েছে। এবার অনেক চলক, যারা একে অপরের ওপর নির্ভর করে, একসঙ্গে। উদাহরণ বাজারঘর, এই কোর্সের কাল্পনিক অনলাইন দোকান; network-এর গঠন আর প্রতিটি সম্ভাবনা আমাদের বানানো, কোনো আসল কোম্পানির ডেটা নয়। চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy 2.4.6, matplotlib 3.10.8); প্রথমবার package নামতে প্রায় ১০ সেকেন্ড লাগতে পারে। চারটি ব্লক একই namespace ভাগ করে, তাই পরের ব্লক চালালে আগেরগুলো নিজে থেকে আগে চলে।
১ · পুরো joint table কেন চলে না — 2n-এর বিস্ফোরণ
বাজারঘরের ডেলিভারি দল একটি প্রশ্নের উত্তর চায়: “আজ একজন গ্রাহক দেরির অভিযোগ করেছেন। বৃষ্টি হয়েছিল বলে কতটা মনে হয়?” এমন প্রশ্নে অনেক চলক জড়িত: বৃষ্টি, যানজট, ঈদের ভিড়, রাইডারের সংকট, দেরিতে ডেলিভারি, অভিযোগ, খারাপ রিভিউ। প্রতিটি হ্যাঁ বা না (১ বা ০)।
সবচেয়ে সরল উপায় একটি joint probability-র টেবিল: সাতটি চলকের প্রতিটি সম্ভাব্য মানের জোড়ার জন্য একটি সংখ্যা। পর্ব ৩৫-এর crosstab-এর মতোই, শুধু দুইটির বদলে সাতটি কলাম। এমন টেবিল থাকলে যেকোনো প্রশ্নের উত্তর দেওয়া যায়: শর্তের সঙ্গে মেলা সারিগুলো যোগ করে ভাগ। সমস্যা হলো আকার।
| হ্যাঁ/না চলক | টেবিলের সারি 2n | স্বাধীন সংখ্যা 2n − 1 | float64-এ মেমরি |
|---|---|---|---|
| ৭ (বাজারঘর) | ১২৮ | ১২৭ | ১ KB |
| ১০ | ১,০২৪ | ১,০২৩ | ৮ KB |
| ৩০ | ১,০৭,৩৭,৪১,৮২৪ | ১,০৭,৩৭,৪১,৮২৩ | ৮ GiB |
| ১০০ | প্রায় ১.২৭ × ১০৩০ | একই রকম | অসম্ভব |
স্বাধীন সংখ্যা এক কম, কারণ সব সারির যোগফল ১. তবে মেমরি আসল সমস্যা নয়। প্রথমত, এত সংখ্যা কেউ জানে না: একজন অভিজ্ঞ ম্যানেজার “বৃষ্টি হলে যানজটের সম্ভাবনা কত” বলতে পারেন, ৩০টি চলকের একটি নির্দিষ্ট জোড়ার সম্ভাবনা নয়। দ্বিতীয়ত, ডেটা থেকে মাপতে হলে প্রতিটি ঘরে উদাহরণ লাগে; ১০৭ কোটি ঘরের বেশিরভাগই খালি থাকবে। তৃতীয়ত, প্রতিটি প্রশ্নে অনেক সারি যোগ করতে হয়। একে বলে combinatorial explosion, পর্ব ৪৫-এর truth table-এ যা দেখেছেন।
বাঁচার পথ: কে কার ওপর সরাসরি নির্ভর করে
বাস্তবে প্রতিটি চলক সবকিছুর ওপর সরাসরি নির্ভর করে না। খারাপ রিভিউ নির্ভর করে মূলত দেরির ওপর; দেরি জানা থাকলে বৃষ্টি হয়েছিল কি না জানা রিভিউ সম্পর্কে নতুন কিছু বলে না। এটিই conditional independence, যার এক লাইনের পরিচয় পর্ব ৩৫-এ পেয়েছেন: P(A ∩ B ∣ C) = P(A ∣ C) · P(B ∣ C). এমন সম্পর্ক অনেক থাকলে বড় টেবিলটি ছোট ছোট টেবিলের গুণফলে ভেঙে ফেলা যায়।
ডাক্তারের রোগনির্ণয়ের কথা ভাবুন। ডাক্তার মাথায় হাজারো উপসর্গের প্রতিটি জোড়ার টেবিল রাখেন না। তিনি জানেন জ্বর হয় সংক্রমণে, কাশি হয় ফুসফুসের সমস্যায়, আর এই ছোট ছোট “কারণ থেকে ফল” জ্ঞান জোড়া লাগিয়ে রোগীর উপসর্গ থেকে উল্টো দিকে কারণ অনুমান করেন। Bayesian network এই জোড়া লাগানোর গাণিতিক নিয়ম।
২ · Bayesian network কী — DAG আর প্রতিটি node-এর CPT
একটি Bayesian network-এর দুটি অংশ।
- গঠন, একটি DAG (directed acyclic graph): প্রতিটি চলক একটি node; A থেকে B-তে তীর মানে B-এর সম্ভাবনা সরাসরি A-এর ওপর লেখা হবে। তীর ধরে ঘুরে আবার একই node-এ ফেরা যায় না (acyclic, চক্রহীন)। যে node থেকে তীর আসে, সে parent. Graph-এর মৌলিক কথা DSA কোর্সের graph পর্বে আছে।
- সংখ্যা, প্রতিটি node-এ একটি CPT (conditional probability table): parent-দের প্রতিটি মানের জোড়ার জন্য ওই node-এর সম্ভাবনা। Parent না থাকলে CPT শুধু একটি সাধারণ সম্ভাবনা।
বাজারঘরের দেরিতে ডেলিভারির network (কাল্পনিক)
7 binary variables, 6 arrows; the badge shows how many CPT rows each node needs
এবার সংখ্যা। নিচের সব সম্ভাবনা আমাদের বানানো, কোনো মাপা ডেটা নয়। প্রতিটি সারিতে শুধু “= ১”-এর সম্ভাবনা লেখা; “= ০” হলো ১ থেকে বিয়োগ, কারণ প্রতিটি সারির যোগফল ১।
| Node | Parent | CPT: P(node = 1 ∣ parent) | সংখ্যা |
|---|---|---|---|
| Rain (R) | — | ০.৩ | ১ |
| EidRush (E) | — | ০.১ | ১ |
| Traffic (T) | R | R=1: ০.৮ · R=0: ০.৩ | ২ |
| Shortage (S) | E | E=1: ০.৬ · E=0: ০.১ | ২ |
| Late (L) | T, S | (1,1): ০.৯ · (1,0): ০.৫ · (0,1): ০.৬ · (0,0): ০.০৫ | ৪ |
| Complaint (C) | L | L=1: ০.৬ · L=0: ০.০৫ | ২ |
| BadReview (B) | L | L=1: ০.৩ · L=0: ০.০২ | ২ |
| মোট (পুরো joint table-এ ১২৭) | ১৪ | ||
১৪টি সংখ্যার প্রতিটি একজন মানুষ বুঝতে আর যাচাই করতে পারেন: “যানজট আর রাইডার সংকট দুটোই থাকলে ৯০% অর্ডার দেরি হয়” একটি অর্থপূর্ণ দাবি। সাধারণভাবে, n টি হ্যাঁ/না node-এর প্রতিটির সর্বোচ্চ k টি parent থাকলে সংখ্যা লাগে সর্বোচ্চ n · 2k. ৩০টি node, প্রতিটির সর্বোচ্চ ৩টি parent হলে ২৪০টি, ১০৭ কোটির বদলে।
“Bayesian network” নামটি Judea Pearl-এর ১৯৮৫ সালের একটি সম্মেলন-প্রবন্ধে প্রথম আসে বলে সাধারণত ধরা হয়, আর ১৯৮৮ সালে তাঁর বই Probabilistic Reasoning in Intelligent Systems ধারণাটিকে AI-এর মূলধারায় আনে। পূর্বসূরি হিসেবে জীববিজ্ঞানী Sewall Wright-এর ১৯২০-এর দশকের path analysis-এর কথা বলা হয়, আর একই সময়ে Steffen Lauritzen, David Spiegelhalter-সহ অনেকে inference-এর পদ্ধতি গড়েন। Pearl ২০১১ সালে Turing Award পান। একে belief network-ও বলা হয়; নামে “Bayesian” থাকলেও Bayesian পরিসংখ্যানের দর্শন মানা জরুরি নয়।
৩ · Chain rule থেকে factorisation — একটি পূর্ণ joint হাতে হিসাব
১৪টি সংখ্যা থেকে ১২৮টি সারির যেকোনোটি কীভাবে পাওয়া যায়? প্রথম ধাপ chain rule of probability (calculus-এর chain rule নয়), যা যেকোনো joint-এর জন্য সবসময় সত্য:
P(x1, …, xn) = P(x1) · P(x2 ∣ x1) · P(x3 ∣ x1, x2) · … · P(xn ∣ x1, …, xn−1)
এটি multiplication rule (পর্ব ৩৫) বারবার খাটানো। কোনো অনুমান নেই, কিন্তু শেষ গুণনীয়কটি একাই বিশাল। এখানে network-এর অনুমান ঢোকে: চলকগুলো এমন ক্রমে সাজান যাতে প্রতিটি parent তার child-এর আগে আসে (একে বলে topological order; DSA কোর্সের topological sort পর্বে এর algorithm আছে), তাহলে network বলে প্রতিটি চলক, তার parent জানা থাকলে, আগের বাকি সবার থেকে conditionally independent. ফলে প্রতিটি লম্বা শর্ত ছোট হয়ে শুধু parent-এ নামে:
P(x1, …, xn) = ∏i P(xi ∣ parents(Xi))
∏ মানে সব i-এর গুণফল। বাজারঘরের network-এ:
P(R, E, T, S, L, C, B) = P(R) · P(E) · P(T ∣ R) · P(S ∣ E) · P(L ∣ T, S) · P(C ∣ L) · P(B ∣ L)
উদাহরণ: “বৃষ্টি হয়েছে, ঈদের ভিড় নেই, যানজট আছে, রাইডার সংকট নেই, অর্ডার দেরি, গ্রাহক অভিযোগ করেছেন, কিন্তু খারাপ রিভিউ দেননি” — এই পূর্ণ অবস্থার সম্ভাবনা কত? নিচের stepper প্রতিটি node-এ একটি করে গুণনীয়ক নেয়।
- প্রশ্ন। সাতটি চলকেরই মান ঠিক করা আছে: R=1, E=0, T=1, S=0, L=1, C=1, B=0. এটি ১২৮টি সারির একটি। গুণফল শুরু ১ থেকে।
- Root, বৃষ্টি। Parent নেই, তাই CPT থেকে সরাসরি P(R=1) = ০.৩.
- Root, ঈদের ভিড়। E=0 চাওয়া হয়েছে, তাই ১ − ০.১ = ০.৯. গুণফল ০.৩ × ০.৯ = ০.২৭.
- যানজট, parent বৃষ্টি। CPT-এর R=1-এর সারি দেখুন: ০.৮. বাকি চলকের মান এখানে লাগেই না। গুণফল ০.২১৬.
- রাইডার সংকট, parent ঈদের ভিড়। E=0-এর সারিতে P(S=1) = ০.১, তাই S=0-এর জন্য ০.৯. গুণফল ০.১৯৪৪.
- দুই parent-এর দেরি। চার সারির মধ্যে (T=1, S=0)-এর সারি: ০.৫. গুণফল ০.০৯৭২.
- অভিযোগ, parent দেরি। L=1-এর সারি: ০.৬. গুণফল ০.০৫৮৩২.
- খারাপ রিভিউ, parent দেরি। B=0, তাই ১ − ০.৩ = ০.৭. চূড়ান্ত joint ০.০৪০৮২৪, প্রায় ৪%. প্রতিটি node একবার, প্রতিবার শুধু নিজের CPT-এর একটি ঘর।
DAG-এর প্রতিটি অনুপস্থিত তীর একটি দাবি। বৃষ্টি থেকে রিভিউতে সরাসরি তীর নেই মানে: “দেরি জানা থাকলে বৃষ্টি রিভিউ সম্পর্কে নতুন কিছু বলে না”. Factorisation ঠিক ততটাই সঠিক, যতটা এই দাবিগুলো। পরীক্ষায় joint probability-র প্রশ্নে কাজ একটাই: প্রতিটি node-এর CPT থেকে সঠিক ঘর বেছে গুণ করা।
৪ · Enumeration দিয়ে inference — অভিযোগ এলে বৃষ্টির সম্ভাবনা
বাস্তবের প্রশ্ন সাধারণত একটি পূর্ণ সারি নয়। আমরা কিছু চলক দেখি (evidence, প্রমাণ), একটি চলক সম্পর্কে জানতে চাই (query, প্রশ্ন), আর বাকিগুলো অজানা থাকে, যাদের বলে hidden variable বা লুকানো চলক। প্রমাণ দেখে প্রশ্নের চলকের সম্ভাবনা বের করাকে বলে inference. আমাদের প্রশ্ন: অভিযোগ এসেছে (C=1); বৃষ্টি হওয়ার সম্ভাবনা কত? লুকানো চলক E, T, S, L, B.
Inference by enumeration সবচেয়ে সরাসরি পথ। Conditional probability-র সংজ্ঞা থেকে:
P(R ∣ C=1) = α · ∑E ∑T ∑S ∑L ∑B P(R, E, T, S, L, C=1, B)
প্রতিটি লুকানো চলকের সব মানের ওপর যোগ করা (sum out করা) মানে সেই চলককে marginalise করা। α হলো normalising constant: R=1 আর R=0 দুটোর যোগফল হিসাব করে তাদের যোগফল দিয়ে ভাগ, যাতে উত্তর দুটির যোগ ১ হয়। α আসলে 1 / P(C=1). প্রতিটি R-এর জন্য 25 = ৩২টি পূর্ণ সারি, প্রতিটি সাতটি গুণনীয়কের গুণফল। হাতে ৬৪টি সারি অনেক, তাই নিচের stepper একটি কৌশল নেয়: যে গুণনীয়ক কোনো যোগের চলকের ওপর নির্ভর করে না, তাকে সেই যোগের বাইরে আনা। উত্তর একই।
- সূত্র লিখুন। Joint-এর সাতটি গুণনীয়ক, C=1 বসানো, বাকি পাঁচটি লুকানো চলকের ওপর যোগ। সরাসরি করলে R-এর প্রতিটি মানে ৩২টি পদ।
- B বাদ। শুধু P(B ∣ L)-এ B আছে, আর যেকোনো CPT সারির যোগফল ১. তাই B-এর যোগ ১ দেয় আর অংশটি উঠে যায়: প্রশ্ন আর প্রমাণের নিচে (descendant) থাকা, অথচ পর্যবেক্ষিত নয় এমন node কোনো প্রভাব ফেলে না।
- E বাদ। E আছে শুধু P(E) আর P(S ∣ E)-এ। এদের E-এর ওপর যোগ হলো total probability, ফল রাইডার সংকটের marginal: P(S=1) = ০.১৫.
- S বাদ। P(S)-কে P(L ∣ T, S)-এর সঙ্গে গুণ করে S-এর ওপর যোগ। ফল: যানজট থাকলে দেরির সম্ভাবনা ০.৫৬, না থাকলে ০.১৩২৫.
- T বাদ। একইভাবে বৃষ্টি থেকে দেরি: বৃষ্টিতে ০.৪৭৪৫, শুকনো দিনে ০.২৬০৭৫.
- L বাদ। দেরি থেকে অভিযোগ: বৃষ্টির দিনে P(C=1 ∣ R=1) ≈ ০.৩১১, শুকনো দিনে ০.১৯৩.
- শেষে normalise করা। Prior দিয়ে গুণ করে দুটি সংখ্যা পাই; তাদের যোগফলই P(C=1) = ০.২২৮৭. ভাগ করলে P(R=1 ∣ C=1) ≈ ০.৪০৮. অভিযোগ আসায় বৃষ্টির বিশ্বাস ০.৩০ থেকে ০.৪১-এ উঠেছে, কিন্তু নিশ্চিত হওয়ার ধারেকাছে নয়।
এবার একই হিসাব কোডে, কোনো কৌশল ছাড়া: সব ১২৮টি সারি ঘুরে প্রমাণের সঙ্গে মেলাগুলো যোগ। CPT সাধারণ Python dict-এ (Python কোর্সের dict পর্ব), সব সারি দেয় itertools.product.
from itertools import product
# বাজারঘরের কাল্পনিক CPT: প্রতিটি মান P(চলক = 1 | parents)
P_R = 0.3 # Rain
P_E = 0.1 # EidRush
P_T = {1: 0.8, 0: 0.3} # Traffic | Rain
P_S = {1: 0.6, 0: 0.1} # RiderShortage | EidRush
P_L = {(1, 1): 0.9, (1, 0): 0.5, (0, 1): 0.6, (0, 0): 0.05} # Late | T, S
P_C = {1: 0.6, 0: 0.05} # Complaint | Late
P_B = {1: 0.3, 0: 0.02} # BadReview | Late
NAMES = ["R", "E", "T", "S", "L", "C", "B"]
def b(p, x): # P(X = x), যখন P(X = 1) = p
return p if x == 1 else 1 - p
def joint(R, E, T, S, L, C, B): # DAG ধরে ৭টি গুণনীয়ক
return (b(P_R, R) * b(P_E, E) * b(P_T[R], T) * b(P_S[E], S)
* b(P_L[(T, S)], L) * b(P_C[L], C) * b(P_B[L], B))
def query(var, evidence): # enumeration: বাকি চলকের ওপর যোগ
score = {0: 0.0, 1: 0.0}
for vals in product((0, 1), repeat=7):
w = dict(zip(NAMES, vals))
if all(w[k] == v for k, v in evidence.items()):
score[w[var]] += joint(*vals)
return score[1] / (score[0] + score[1])
total = sum(joint(*v) for v in product((0, 1), repeat=7))
print("128টি সারির যোগফল:", round(total, 12))
print("P(R=1,E=0,T=1,S=0,L=1,C=1,B=0) =", round(joint(1, 0, 1, 0, 1, 1, 0), 6))
print("P(Late = 1) =", round(query("L", {}), 4))
print("P(Complaint = 1) =", round(query("C", {}), 4))
print("P(Rain = 1) =", P_R)
print("P(Rain = 1 | Complaint) =", round(query("R", {"C": 1}), 4))আসল আউটপুট (ব্রাউজারের Python, শুধু standard library):
128টি সারির যোগফল: 1.0
P(R=1,E=0,T=1,S=0,L=1,C=1,B=0) = 0.040824
P(Late = 1) = 0.3249
P(Complaint = 1) = 0.2287
P(Rain = 1) = 0.3
P(Rain = 1 | Complaint) = 0.408
প্রথম লাইন একটি স্বাস্থ্য-পরীক্ষা: ১২৮টি সারির যোগফল ঠিক ১, তাই CPT-গুলো মিলে একটি বৈধ বণ্টন দিয়েছে। দ্বিতীয় লাইন stepper-এর ০.০৪০৮২৪-এর সঙ্গে মেলে। P(Rain ∣ Complaint) = ০.৪০৮, হাতের হিসাবের সঙ্গে হুবহু। পাশে দুটি marginal: কাল্পনিক network অনুযায়ী প্রায় ৩২% অর্ডার দেরি হয় আর প্রায় ২৩% অর্ডারে অভিযোগ আসে।
query function-টি সাধারণ: যেকোনো চলক, যেকোনো প্রমাণ। দাম হলো প্রতিবার ১২৮টি সারি। চলক ৩০টি হলে ১০৭ কোটি সারি; তখন এই কোড কার্যত অচল। এখানে সংখ্যা ছোট বলে সরাসরি গুণ করা নিরাপদ; শত শত গুণনীয়ক হলে underflow এড়াতে log probability-তে যোগ করতে হয় (পর্ব ৩৫, পর্ব ৩৮)।
৫ · তিনটি মূল গঠন — chain, fork আর collider
যেকোনো Bayesian network তিন চলকের তিন রকম টুকরো দিয়ে গড়া। তথ্য একটি পথ ধরে “প্রবাহিত” হয় কি না, তা নির্ভর করে মাঝের node-এর গঠন আর সেটি জানা কি না তার ওপর।
তিনটি গঠন, বাজারঘরের network থেকে
Chain, fork (common cause) and collider (common effect)
- Chain (A → B → C): বৃষ্টি জানলে দেরির অনুমান বদলায়; কিন্তু যানজট জানা থাকলে বৃষ্টি আর নতুন কিছু বলে না, কারণ বৃষ্টির সব প্রভাব যানজট দিয়েই যায়।
- Fork (A ← B → C), common cause: দেরি থেকে অভিযোগ আর খারাপ রিভিউ দুটোই আসে, তাই অভিযোগ দেখলে খারাপ রিভিউর সম্ভাবনাও বাড়ে; দেরি জানা থাকলে সম্পর্কটি মুছে যায়। এটিই confounding-এর গঠন (পর্ব ২৪)।
- Collider (A → B ← C), common effect: যানজট আর রাইডার সংকট স্বাধীন, কিন্তু দুটোই দেরি ঘটায়। দেরি হয়েছে জানলে এরা সম্পর্কিত হয়ে যায়: একটি না থাকলে অন্যটি থাকার সম্ভাবনা বাড়ে।
কথাগুলো সংখ্যায় দেখি। নিচের ব্লক আগের query ব্যবহার করে (data-shared, তাই আগের ব্লক নিজে থেকে আগে চলবে)।
def show(text, var, ev):
print(f" {text:<22} {query(var, ev):.4f}")
print("১. Chain Rain → Traffic → Late")
show("P(L=1)", "L", {})
show("P(L=1 | R=1)", "L", {"R": 1})
show("P(L=1 | T=1)", "L", {"T": 1})
show("P(L=1 | T=1, R=1)", "L", {"T": 1, "R": 1})
print("২. Fork Complaint ← Late → BadReview")
show("P(B=1)", "B", {})
show("P(B=1 | C=1)", "B", {"C": 1})
show("P(B=1 | L=1)", "B", {"L": 1})
show("P(B=1 | L=1, C=1)", "B", {"L": 1, "C": 1})
print("৩. Collider Traffic → Late ← Shortage")
show("P(T=1)", "T", {})
show("P(T=1 | S=1)", "T", {"S": 1})
show("P(T=1 | L=1)", "T", {"L": 1})
show("P(T=1 | L=1, S=1)", "T", {"L": 1, "S": 1})
print("৪. Explaining away: Rain দিয়ে Shortage")
show("P(S=1)", "S", {})
show("P(S=1 | R=1)", "S", {"R": 1})
show("P(S=1 | L=1)", "S", {"L": 1})
show("P(S=1 | L=1, R=1)", "S", {"L": 1, "R": 1})
show("P(S=1 | L=1, R=0)", "S", {"L": 1, "R": 0})আসল আউটপুট (ব্রাউজারের Python):
১. Chain Rain → Traffic → Late
P(L=1) 0.3249
P(L=1 | R=1) 0.4745
P(L=1 | T=1) 0.5600
P(L=1 | T=1, R=1) 0.5600
২. Fork Complaint ← Late → BadReview
P(B=1) 0.1110
P(B=1 | C=1) 0.2587
P(B=1 | L=1) 0.3000
P(B=1 | L=1, C=1) 0.3000
৩. Collider Traffic → Late ← Shortage
P(T=1) 0.4500
P(T=1 | S=1) 0.4500
P(T=1 | L=1) 0.7757
P(T=1 | L=1, S=1) 0.5510
৪. Explaining away: Rain দিয়ে Shortage
P(S=1) 0.1500
P(S=1 | R=1) 0.1500
P(S=1 | L=1) 0.3394
P(S=1 | L=1, R=1) 0.2655
P(S=1 | L=1, R=0) 0.3969
Chain: বৃষ্টি জানলে দেরির সম্ভাবনা ০.৩২৫ থেকে ০.৪৭৪-এ ওঠে, অর্থাৎ নির্ভরশীল। কিন্তু যানজট জানা থাকলে বৃষ্টি যোগ করলেও ০.৫৬০০-ই থাকে: যানজট জানা থাকলে বৃষ্টি আর দেরি স্বাধীন। Fork: অভিযোগ দেখলে খারাপ রিভিউর সম্ভাবনা ০.১১১ থেকে ০.২৫৯; দেরি জানা থাকলে অভিযোগ যোগ করে ০.৩০০০-ই থাকে। Collider: প্রথম দুই লাইন সমান (০.৪৫০০), অর্থাৎ যানজট আর রাইডার সংকট স্বাধীন। দেরি জানলে যানজটের সম্ভাবনা ০.৭৭৬, কিন্তু সঙ্গে রাইডার সংকটও জানলে তা ০.৫৫১-এ নামে। দেরি জানার আগে যারা স্বাধীন ছিল, দেরি জানার পর তারা নির্ভরশীল।
চতুর্থ অংশ পরের অংশের বিষয়।
৬ · Explaining away আর d-separation-এর অন্তর্দৃষ্টি
ধরুন একটি অর্ডার দেরিতে পৌঁছেছে। ম্যানেজার ভাবেন, “নিশ্চয়ই রাইডার কম ছিল।” তারপর জানলেন সেদিন ঢাকায় মুষলধারে বৃষ্টি হয়েছিল। এখন রাইডার সংকটের সন্দেহ কমে, কারণ বৃষ্টি দেরির একটি যথেষ্ট ব্যাখ্যা। একে বলে explaining away: একটি সাধারণ ফলের এক কারণ প্রমাণিত হলে অন্য কারণের সম্ভাবনা কমে। বাজারঘরের network-এ বৃষ্টি সরাসরি দেরির parent নয়, তবে বৃষ্টি → যানজট → দেরি ← রাইডার সংকট পথটি দেরিতে collider পায়, আর প্রভাবটি সেখান দিয়েই আসে।
- কিছু জানা নেই। রাইডার সংকটের prior ০.১৫ (আগের stepper-এর ধাপ ২)। বৃষ্টি জানলেও ০.১৫-ই থাকে: collider-এর আগে দুই দিকের কারণ স্বাধীন।
- দেরি দেখা গেল। দেরির দুটি সম্ভাব্য ব্যাখ্যা, তাই দুটোরই সন্দেহ বাড়ে: রাইডার সংকট ০.১৫ থেকে ০.৩৩৯.
- বৃষ্টিও হয়েছিল। বৃষ্টি যানজটের সম্ভাবনা ০.৮-এ তোলে, আর যানজট দেরির ভালো ব্যাখ্যা। ফলে রাইডার সংকটের সন্দেহ ০.৩৩৯ থেকে ০.২৬৬-এ নামে। দেরিকে বৃষ্টি “ব্যাখ্যা করে দূর করেছে”.
- উল্টো: বৃষ্টি হয়নি। তখন যানজটের সম্ভাবনা মাত্র ০.৩, দেরির অন্য ব্যাখ্যা দুর্বল, তাই রাইডার সংকট ০.৩৯৭-এ ওঠে। একই দেরি, প্রমাণ বদলালে অনুমান দুই দিকে যায়।
- হাতে যাচাই। বৃষ্টিতে T=1-এর সম্ভাবনা ০.৮, তাই P(L=1 ∣ S=1, R=1) = ০.৮×০.৯ + ০.২×০.৬ = ০.৮৪ আর P(L=1 ∣ S=0, R=1) = ০.৮×০.৫ + ০.২×০.০৫ = ০.৪১. Bayes: ০.১৫×০.৮৪ ÷ (০.১৫×০.৮৪ + ০.৮৫×০.৪১) = ০.১২৬ ÷ ০.৪৭৪৫ ≈ ০.২৬৬.
Collider-এ condition করা ডেটার ফাঁদ: শুধু দেরি হওয়া অর্ডারের ডেটা নিয়ে বিশ্লেষণ করলে (যেমন শুধু অভিযোগের রেকর্ড) যানজট আর রাইডার সংকটের মধ্যে একটি ঋণাত্মক সম্পর্ক দেখবেন, যদিও আসলে তারা স্বাধীন। একে বলে Berkson's paradox বা collider bias. ভাবনার প্রশ্ন ২-এ এর বাস্তব উদাহরণ।
d-separation — পথ খোলা না বন্ধ
বড় network-এ দুটি চলক স্বাধীন কি না বুঝতে প্রতিবার হিসাব করতে হয় না; graph দেখেই বলা যায়। নিয়মটির নাম d-separation (d = directed), যা Pearl আর তাঁর সহকর্মীরা ১৯৮০-এর দশকে আনুষ্ঠানিক করেন। এখানে প্রমাণ নয়, অন্তর্দৃষ্টি: A আর B-এর মধ্যে তীরের দিক না দেখে যত পথ আছে, প্রতিটিকে একটি পাইপ ভাবুন। জানা চলকের সেট Z দেওয়া থাকলে প্রতিটি পথের প্রতিটি মাঝের node দেখুন:
| মাঝের node-এর গঠন | মাঝখান জানা নেই | মাঝখান জানা (Z-এ আছে) |
|---|---|---|
| Chain A → M → B | খোলা: তথ্য বয় | বন্ধ |
| Fork A ← M → B | খোলা | বন্ধ |
| Collider A → M ← B | বন্ধ (যদি M-এর কোনো উত্তরসূরিও জানা না থাকে) | খোলা; M-এর কোনো উত্তরসূরি (descendant) জানা থাকলেও খোলে |
একটি পথ বন্ধ, যদি তার অন্তত একটি মাঝের node বন্ধ থাকে। A আর B-এর মধ্যের প্রতিটি পথ বন্ধ হলে তারা Z দেওয়া থাকলে d-separated, আর তখন network-এর CPT-তে যে সংখ্যাই বসান, তারা conditionally independent. একটি পথও খোলা থাকলে সাধারণত নির্ভরশীল। বাজারঘরের দুটি উদাহরণ: (১) যানজট আর রাইডার সংকট, অভিযোগ জানা থাকলে? একমাত্র পথ T → L ← S, collider L জানা নেই, কিন্তু তার উত্তরসূরি C জানা, তাই পথ খোলা। হিসাবেও তাই: P(T=1 ∣ C=1) ≈ ০.৭০৪, আর রাইডার সংকটও জানলে ≈ ০.৫৪০. (২) বৃষ্টি আর অভিযোগ, দেরি জানা থাকলে? পথ R → T → L → C, chain-এ L জানা, তাই বন্ধ; P(R=1 ∣ L=1) আর P(R=1 ∣ L=1, C=1) দুটোই ≈ ০.৪৩৮ (দুটি সংখ্যাই আগের query দিয়ে হিসাব করা)।
৭ · দ্রুত নির্ভুল হিসাব আর আনুমানিক inference — elimination আর sampling
Variable elimination — একই উত্তর, কম গুণ
৪ নম্বর অংশের stepper-এ আমরা আসলে variable elimination-ই করেছি: যোগগুলোকে গুণফলের ভেতরে যতটা সম্ভব ঢুকিয়ে একটি একটি চলক বাদ দেওয়া, আর মাঝের ফল (যেমন P(S), P(L ∣ T)) জমিয়ে রেখে আবার ব্যবহার করা। এটি DSA কোর্সের dynamic programming-এর মতো একই কৌশল: একই উপ-হিসাব বারবার না করা। ধারণাটি ১৯৯০-এর দশকের মাঝামাঝি Nevin Zhang আর David Poole-এর একটি প্রবন্ধে এই নামে পরিচিত হয়, আর Rina Dechter-এর bucket elimination একই ধারার কাজ।
| পদ্ধতি | P(R ∣ C=1)-এর জন্য গুণ (মোটামুটি) | কীভাবে গোনা |
|---|---|---|
| Enumeration | ৩৮৪ | R-এর ২টি মান × ৩২টি পূর্ণ সারি × প্রতি সারিতে ৬টি গুণ |
| Variable elimination | ২২ | E: ৪, S: ৮, L: ৪, T: ৪, শেষে prior: ২; B-এর যোগ ১, কোনো গুণ লাগেনি |
গোনার নিয়মভেদে সংখ্যা সামান্য বদলায়, মাত্রা নয়। আসল লাভ বড় network-এ: enumeration-এর খরচ লুকানো চলকের সংখ্যায় exponential, আর যে network-এ দুটি node-এর মধ্যে একটির বেশি পথ নেই (polytree), সেখানে variable elimination-এর খরচ network-এর আকারে মোটামুটি linear. তবে সাধারণ network-এ নির্ভুল inference কঠিন: Gregory Cooper ১৯৯০ সালে দেখান এটি NP-hard. খরচ নির্ভর করে বাদ দেওয়ার ক্রম আর মাঝের টেবিল কত বড় হয় তার ওপর (treewidth). তাই বড় বা ঘন network-এ আনুমানিক পদ্ধতি লাগে।
Prior sampling আর rejection sampling
আনুমানিক inference-এর সবচেয়ে সহজ ধারণা পর্ব ৩৬-এর Monte Carlo: network থেকে অনেক কাল্পনিক “দিন” বানানো, তারপর গোনা। Prior sampling (forward sampling) topological ক্রমে চলে: আগে বৃষ্টি আর ঈদের ভিড়ের মান টানা হয় তাদের prior থেকে, তারপর সেই মান দেখে যানজট আর রাইডার সংকট তাদের CPT-এর সঠিক সারি থেকে, তারপর দেরি, শেষে অভিযোগ আর রিভিউ। প্রতিটি নমুনা ১২৮টি সারির একটি, আর কোনো সারি যত ঘন ঘন আসে তা তার joint probability-র কাছাকাছি।
প্রমাণ থাকলে সবচেয়ে সরল উপায় rejection sampling: প্রমাণের সঙ্গে না মেলা নমুনা (C=0) ফেলে দিন, বাকিগুলোর মধ্যে বৃষ্টির অনুপাত গুনুন। নিচের ব্লক NumPy দিয়ে একসঙ্গে অনেক নমুনা টানে, বিভিন্ন N-এ অনুমান বের করে, আর enumeration-এর সঠিক উত্তরের সঙ্গে ছবিতে তুলনা করে।
import numpy as np
import matplotlib.pyplot as plt
def prior_sample(n, rng): # DAG-এর ক্রমে: আগে parent, পরে child
u = lambda p: rng.random(n) < p
R, E = u(P_R), u(P_E)
T = u(np.where(R, P_T[1], P_T[0]))
S = u(np.where(E, P_S[1], P_S[0]))
L = u(np.select([T & S, T & ~S, ~T & S],
[P_L[(1, 1)], P_L[(1, 0)], P_L[(0, 1)]], P_L[(0, 0)]))
C = u(np.where(L, P_C[1], P_C[0]))
B = u(np.where(L, P_B[1], P_B[0]))
return {"R": R, "E": E, "T": T, "S": S, "L": L, "C": C, "B": B}
exact = query("R", {"C": 1})
rng = np.random.default_rng(42)
sizes = [100, 300, 1000, 3000, 10000, 30000, 100000]
est = []
for n in sizes:
d = prior_sample(n, rng)
keep = d["C"] # rejection: Complaint = 1 না হলে বাদ
est.append(d["R"][keep].mean())
print(f"N = {n:>6}: রাখা হলো {keep.sum():>5}, অনুমান {est[-1]:.4f}")
print("সঠিক (enumeration):", round(exact, 4))
fig, ax = plt.subplots(figsize=(6.4, 3.4))
ax.semilogx(sizes, est, "o-", label="rejection sampling estimate")
ax.axhline(exact, color="crimson", ls="--", label=f"exact = {exact:.4f}")
ax.axhline(P_R, color="gray", ls=":", label="prior P(Rain) = 0.3")
ax.set_xlabel("number of prior samples N (log scale)")
ax.set_ylabel("P(Rain = 1 | Complaint = 1)")
ax.set_title("Rejection sampling converges to the exact answer")
ax.legend(fontsize=8)আসল আউটপুট (ব্রাউজারের Python, NumPy 2.4.6, matplotlib 3.10.8; ছবিটি আউটপুটের নিচে আসে):
N = 100: রাখা হলো 28, অনুমান 0.3571
N = 300: রাখা হলো 73, অনুমান 0.4932
N = 1000: রাখা হলো 232, অনুমান 0.4052
N = 3000: রাখা হলো 689, অনুমান 0.3977
N = 10000: রাখা হলো 2292, অনুমান 0.4058
N = 30000: রাখা হলো 6921, অনুমান 0.4102
N = 100000: রাখা হলো 22730, অনুমান 0.4108
সঠিক (enumeration): 0.408
np.select চারটি শর্ত থেকে প্রতিটি নমুনার জন্য P(L=1 ∣ T, S)-এর সঠিক ঘর বাছে; বাকিগুলোতে np.where যথেষ্ট। প্রতি N-এ মাত্র প্রায় ২৩% নমুনা রাখা হয়, কারণ P(C=1) ≈ ০.২২৯: বাকি ৭৭% হিসাব ফেলে দেওয়া। ১০০টি নমুনায় (রাখা ২৮টি) অনুমান ০.৩৫৭, ৩০০-তে ০.৪৯৩, তারপর N বাড়লে ধীরে ধীরে ০.৪০৮-এর কাছে স্থির হয়। ছবিতে ধূসর রেখা prior ০.৩; শুরুর বড় দোলা আর পরের স্থিরতা দুটোই law of large numbers-এর চেনা চেহারা: ভুল মোটামুটি 1/√(রাখা নমুনা) হারে কমে (standard error, পর্ব ৩৭)। এই নির্দিষ্ট সংখ্যাগুলো seed 42-এর একটি run; seed বদলালে দোলা বদলাবে, প্রবণতা নয়।
দুর্বলতা স্পষ্ট: P(প্রমাণ) = ০.০০১ হলে ১০ লাখ নমুনার মাত্র প্রায় ১০০০টি কাজে লাগে।
Likelihood weighting, সংক্ষেপে: প্রমাণের চলক টানা হয় না; সরাসরি C=1 বসানো হয়, আর প্রতিটি নমুনাকে ওজন দেওয়া হয় প্রমাণের সম্ভাবনা দিয়ে, এখানে সেই নমুনার L দেওয়া থাকলে P(C=1)-এর মান। তারপর বৃষ্টির ওজনযুক্ত অনুপাত। কোনো নমুনা ফেলতে হয় না, তাই একই N-এ সাধারণত কম দোলা; তবে প্রমাণ নেটওয়ার্কের নিচের দিকে আর খুব অসম্ভাব্য হলে প্রায় সব ওজন অল্প কয়েকটি নমুনায় জমে যেতে পারে। পদ্ধতিটি ১৯৮৯ সালে Robert Fung ও Kuo-Chu Chang এবং আলাদাভাবে Ross Shachter ও Mark Peot-এর কাজে আসে বলে ধরা হয়। অনুশীলন ৪-এ এটি নিজে লিখবেন।
৮ · ডেটা থেকে CPT শেখা, আর pgmpy দিয়ে সত্যিকারের run
বাস্তবে CPT-র সংখ্যা আসে বিশেষজ্ঞের অনুমান বা ডেটা থেকে। গঠন জানা থাকলে আর সব চলক প্রতিটি সারিতে দেখা গেলে শেখা মানে শুধু গোনা। P(T=1 ∣ R=1) = (বৃষ্টি এবং যানজটের দিনের সংখ্যা) ÷ (বৃষ্টির দিনের সংখ্যা)। এটিই maximum likelihood estimate, আর প্রতিটি CPT সারি আলাদা আলাদা গোনা যায়। পরীক্ষা করার সেরা উপায়: জানা network থেকে seeded ডেটা বানিয়ে আবার শিখে দেখা, আসল সংখ্যা ফিরে আসে কি না।
import numpy as np
d = prior_sample(5000, np.random.default_rng(2026))
R, E, T, S, L, C = (d[k] for k in "RETSLC")
everyone = np.ones(5000, dtype=bool)
rows = [("P(R=1)", R, everyone, P_R),
("P(T=1 | R=1)", T, R, P_T[1]), ("P(T=1 | R=0)", T, ~R, P_T[0]),
("P(S=1 | E=1)", S, E, P_S[1]), ("P(S=1 | E=0)", S, ~E, P_S[0]),
("P(L=1 | T=1,S=1)", L, T & S, P_L[(1, 1)]),
("P(L=1 | T=1,S=0)", L, T & ~S, P_L[(1, 0)]),
("P(L=1 | T=0,S=1)", L, ~T & S, P_L[(0, 1)]),
("P(L=1 | T=0,S=0)", L, ~T & ~S, P_L[(0, 0)]),
("P(C=1 | L=1)", C, L, P_C[1]), ("P(C=1 | L=0)", C, ~L, P_C[0])]
print(f"{'CPT-এর ঘর':<17}{'n':>5}{'আসল':>7}{'গোনা':>8}{'Laplace':>9}")
for name, child, mask, true in rows:
n = int(mask.sum())
k = int((child & mask).sum())
mle = k / n # গোনা: k ÷ n
lap = (k + 1) / (n + 2) # প্রতিটি মানে ১ যোগ
print(f"{name:<17}{n:>5}{true:>7.2f}{mle:>8.3f}{lap:>9.3f}")
m = (T & S)[:50] # মাত্র প্রথম ৫০টি অর্ডার
n, k = int(m.sum()), int((L[:50] & m).sum())
print(f"৫০টি সারিতে T=1,S=1: n={n}, k={k}, গোনা {k/n:.3f}, "
f"Laplace {(k+1)/(n+2):.3f}, আসল 0.9")আসল আউটপুট (ব্রাউজারের Python, NumPy 2.4.6):
CPT-এর ঘর n আসল গোনা Laplace
P(R=1) 5000 0.30 0.291 0.291
P(T=1 | R=1) 1456 0.80 0.797 0.797
P(T=1 | R=0) 3544 0.30 0.303 0.303
P(S=1 | E=1) 463 0.60 0.613 0.613
P(S=1 | E=0) 4537 0.10 0.097 0.097
P(L=1 | T=1,S=1) 332 0.90 0.907 0.904
P(L=1 | T=1,S=0) 1902 0.50 0.503 0.503
P(L=1 | T=0,S=1) 391 0.60 0.598 0.598
P(L=1 | T=0,S=0) 2375 0.05 0.057 0.058
P(C=1 | L=1) 1628 0.60 0.590 0.590
P(C=1 | L=0) 3372 0.05 0.053 0.053
৫০টি সারিতে T=1,S=1: n=1, k=0, গোনা 0.000, Laplace 0.333, আসল 0.9
৫,০০০টি কাল্পনিক অর্ডারে বেশিরভাগ ঘর দ্বিতীয় দশমিক পর্যন্ত মিলেছে। কিন্তু n কলাম দেখুন: P(R=1)-এর জন্য ৫,০০০টি সারি, অথচ (T=1, S=1)-এর জন্য মাত্র ৩৩২টি আর ঈদের দিনের জন্য ৪৬৩টি। প্রতিটি CPT সারি শুধু তার parent-জোড়ার সারিগুলো থেকে শেখে, তাই parent বেশি হলে প্রতিটি ঘরের ডেটা দ্রুত কমে। শেষ লাইনটি এর চরম রূপ: প্রথম ৫০টি অর্ডারে (T=1, S=1) মাত্র একবার এসেছে, আর সেটি দেরি হয়নি, তাই গোনা বলছে P(L=1 ∣ T=1, S=1) = ০, যেখানে আসল মান ০.৯. ৩০টি সারি নিলে এই ঘরে n = ০, আর ০ ÷ ০ সংজ্ঞাহীন।
সমাধান smoothing: প্রতিটি মানে একটি কাল্পনিক গণনা যোগ করা, (k + 1) ÷ (n + 2). এটিই পর্ব ৫১-এর Laplace smoothing; ১ ÷ ৩ ≈ ০.৩৩৩ নিখুঁত নয়, কিন্তু “অসম্ভব” বলার চেয়ে অনেক নিরাপদ, আর ডেটা বাড়লে প্রভাব মিলিয়ে যায় (৫,০০০ সারিতে দুই কলাম প্রায় সমান)। কিছু মান অনুপস্থিত থাকলে গোনা চলে না (তখন EM algorithm), আর গঠনটিও ডেটা থেকে শেখা (structure learning) অনেক কঠিন।
সত্যিকারের run: pgmpy দিয়ে একই network
কাজে সাধারণত লাইব্রেরি লাগে; Python-এ পরিচিত একটি open-source লাইব্রেরি pgmpy. এটি ব্রাউজারের Python-এ নেই, তাই লেখকের যন্ত্রে একটি আলাদা virtual environment-এ (Python 3.14.4) চালানো হয়েছে। লেখার সময়ের সর্বশেষ সংস্করণ pgmpy 1.1.2; সঙ্গে নিজে থেকে এসেছে numpy 2.5.4, pandas 3.1.1, scipy 1.18.1, networkx 3.7 আর scikit-learn 1.9.1 (PyTorch লাগেনি)।
Class-এর নাম বদলেছে: পুরোনো টিউটোরিয়ালে BayesianModel, পরে BayesianNetwork; pgmpy 1.x-এ discrete network-এর class DiscreteBayesianNetwork. 1.1.2-এ পুরোনো BayesianNetwork([...]) লিখলে সরাসরি ImportError আসে: “BayesianNetwork is deprecated. Please use DiscreteBayesianNetwork instead.” CPT শেখার fit-এও API বদলেছে: estimator=MaximumLikelihoodEstimator (class) দিলে TypeError আসে, এখন একটি object দিতে হয়, যেমন DiscreteMLE(). দুটি error-ই এই run-এ সত্যিই দেখা গেছে।
from pgmpy.models import DiscreteBayesianNetwork
from pgmpy.factors.discrete import TabularCPD
from pgmpy.inference import VariableElimination
model = DiscreteBayesianNetwork([("R", "T"), ("E", "S"), ("T", "L"),
("S", "L"), ("L", "C"), ("L", "B")])
def cpd(var, p1, parents=()): # p1 = P(var=1), parent অবস্থার ক্রমে
p1 = list(p1)
return TabularCPD(var, 2, [[1 - p for p in p1], p1],
evidence=list(parents) or None,
evidence_card=[2] * len(parents) or None)
model.add_cpds(
cpd("R", [0.3]), cpd("E", [0.1]),
cpd("T", [0.3, 0.8], ["R"]), # R=0, R=1
cpd("S", [0.1, 0.6], ["E"]),
cpd("L", [0.05, 0.6, 0.5, 0.9], ["T", "S"]), # 00, 01, 10, 11
cpd("C", [0.05, 0.6], ["L"]),
cpd("B", [0.02, 0.3], ["L"]))
print("check_model:", model.check_model())
ve = VariableElimination(model)
def p1(var, ev=None):
f = ve.query([var], evidence=ev, show_progress=False)
return round(float(f.values[1]), 4)
print("P(R=1 | C=1) =", p1("R", {"C": 1}))
print("P(S=1) =", p1("S"))
print("P(S=1 | L=1) =", p1("S", {"L": 1}))
print("P(S=1 | L=1, R=1) =", p1("S", {"L": 1, "R": 1}))
print(ve.query(["R"], evidence={"C": 1}, show_progress=False))
print("T, S d-connected? ", model.is_dconnected("T", "S"))
print("T, S d-connected given L? ", model.is_dconnected("T", "S", observed=["L"]))
print("C, B d-connected given L? ", model.is_dconnected("C", "B", observed=["L"]))
print(model.local_independencies("L"))
from pgmpy.sampling import BayesianModelSampling
from pgmpy.parameter_estimator import DiscreteMLE
data = BayesianModelSampling(model).forward_sample(size=5000, seed=42,
show_progress=False)
learned = DiscreteBayesianNetwork(model.edges())
learned.fit(data, estimator=DiscreteMLE())
c = learned.get_cpds("L")
print("rows:", len(data), "| L-এর parent ক্রম:", c.variables[1:])
print("শেখা P(L=1 | ...):\n", c.values[1].round(3))~/bazarghar-ai$ pip install pgmpy==1.1.2
…
~/bazarghar-ai$ python bn_pgmpy.py
~/bazarghar-ai/.venv/…/pgmpy/estimators/__init__.py:4: FutureWarning: `pgmpy.estimators.StructureScore` is deprecated and will be removed in v1.3.0. Use `pgmpy.structure_score` instead.
from .StructureScore import (
check_model: True
P(R=1 | C=1) = 0.408
P(S=1) = 0.15
P(S=1 | L=1) = 0.3394
P(S=1 | L=1, R=1) = 0.2655
+------+----------+
| R | phi(R) |
+======+==========+
| R(0) | 0.5920 |
+------+----------+
| R(1) | 0.4080 |
+------+----------+
T, S d-connected? False
T, S d-connected given L? True
C, B d-connected given L? False
(L ⟂ E, R | S, T)
rows: 5000 | L-এর parent ক্রম: ['S', 'T']
শেখা P(L=1 | ...):
[[0.055 0.534]
[0.635 0.907]]
প্রথম দুই লাইন pgmpy-র নিজের import থেকে আসা একটি FutureWarning: একটি module 1.3.0-এ সরানো হবে। Warning লুকাবেন না; পরের সংস্করণে কী ভাঙবে তা এখানেই লেখা। check_model দেখে CPT-এর সারির যোগ ১ আর parent মিলছে কি না। VariableElimination আমাদের সংখ্যাগুলোই দেয়: ০.৪০৮, ০.১৫, ০.৩৩৯৪, ০.২৬৫৫. is_dconnected graph দেখে d-separation খাটায়, ফল ৬ নম্বর অংশের নিয়মের সঙ্গে মেলে। (L ⟂ E, R ∣ S, T) মানে parent জানা থাকলে দেরি বাকি পূর্বসূরিদের থেকে স্বাধীন, ঠিক factorisation-এর অনুমান।
শেষ অংশে pgmpy ৫,০০০টি নমুনা টেনে (নিজের random ব্যবস্থায়, তাই সংখ্যা আমাদের ব্লক থেকে আলাদা) আবার CPT শিখেছে। একটি ফাঁদ দেখুন: আমরা দেরির parent লিখেছিলাম ["T", "S"] ক্রমে, কিন্তু শেখা CPT-তে ক্রম ['S', 'T']. তাই মানের সারণির প্রথম সূচক S: [[0.055 0.534] [0.635 0.907]] পড়ুন (S=0,T=0) = ০.০৫৫, (S=0,T=1) = ০.৫৩৪, (S=1,T=0) = ০.৬৩৫, (S=1,T=1) = ০.৯০৭; আসল ০.০৫, ০.৫, ০.৬, ০.৯. Array-র সূচক দিয়ে CPT পড়ার আগে সবসময় variables দেখে নিন।
৯ · তীরের মানে কী — কারণ, সম্পর্ক, Naive Bayes আর বাস্তব ব্যবহার
আমরা তীর এঁকেছি কারণ থেকে ফলের দিকে (বৃষ্টি → যানজট), কারণ সেভাবে CPT-র সংখ্যা অনুমান করা সহজ। কিন্তু গাণিতিকভাবে একটি Bayesian network শুধু একটি সম্ভাবনা-বণ্টনের দাবি করে, কারণ-ফলের নয়। একই বণ্টন প্রায়ই একাধিক DAG দিয়ে লেখা যায়: R → T → L, R ← T ← L আর R ← T → L, তিনটিই বলে “T জানা থাকলে R আর L স্বাধীন”; শুধু observational ডেটা দেখে এদের আলাদা করা যায় না। শুধু collider (R → T ← L) অন্য রকম স্বাধীনতা বলে, তাই ডেটা থেকে collider-ই সবচেয়ে বেশি চেনা যায়। এজন্য ডেটা থেকে শেখা network-এর তীরকে কারণ হিসেবে পড়া একটি বাড়তি অনুমান, প্রমাণ নয়; লুকানো confounder থাকলে তো বটেই। Correlation আর causation নিয়ে আরও আলোচনা পর্ব ৩৭-এ।
Intervention, এক অনুচ্ছেদে: “অভিযোগ এসেছে দেখলে দেরির সম্ভাবনা” আর “অভিযোগের বোতাম সরিয়ে অভিযোগ বন্ধ করলে দেরির সম্ভাবনা” এক জিনিস নয়। প্রথমটি P(L ∣ C=0), আমাদের network-এ ≈ ০.১৬৮: অভিযোগ না আসা মানে সম্ভবত অর্ডারটি সময়মতো এসেছিল। দ্বিতীয়টি Pearl-এর do-operator দিয়ে লেখা P(L ∣ do(C=0)): বাইরে থেকে C বসালে C-এর দিকে আসা তীর কেটে যায়, আর দেরি তার কারণগুলোর ওপরই থাকে, P(L=1) ≈ ০.৩২৫. বোতাম সরালে ডেলিভারি দ্রুত হয় না। এমন হিসাব তখনই বৈধ যখন তীরগুলো সত্যিই কারণ-ফল বোঝায়; সেটি যাচাইয়ের নিয়ম (do-calculus, randomised experiment বা A/B test) causal inference-এর বিষয়, যা এই কোর্সের বাইরে। (pgmpy-তে এর জন্য do method আছে।)
Naive Bayes — একটি ছোট্ট Bayesian network
পর্ব ৫১-এর Naive Bayes আসলে একটি বিশেষ network: একটি শ্রেণি-node (spam বা ham) থেকে প্রতিটি শব্দ-node-এ তীর, শব্দগুলোর মধ্যে কোনো তীর নেই। এটি একটি বড় fork: শ্রেণি জানা থাকলে প্রতিটি শব্দ বাকিগুলো থেকে স্বাধীন। Factorisation সরাসরি দেয় P(শ্রেণি, w1, …, wn) = P(শ্রেণি) ∏ P(wi ∣ শ্রেণি), আর একটি SMS দেখে শ্রেণির inference হলো Bayes-এর নিয়ম। “Naive” অংশটি হলো অনুপস্থিত তীরগুলো; বাস্তবে “অফার” আর “জিতুন” শব্দ শ্রেণি জানা থাকলেও সম্পর্কিত, তবু model প্রায়ই ভালো কাজ করে।
কোথায় ব্যবহার হয়
- রোগনির্ণয় ও চিকিৎসা-সহায়তা: রোগ → উপসর্গ ও পরীক্ষার ফল, তারপর উপসর্গ থেকে উল্টো inference. ১৯৮০-৯০-এর দশকে গবেষণায় একাধিক বড় network তৈরি হয়েছিল (ভাবনার প্রশ্ন ৫); বাস্তব ক্লিনিকে ব্যবহারের ব্যাপ্তি সীমিত আর প্রসঙ্গভেদে আলাদা।
- Troubleshooting: “প্রিন্টার চলছে না” থেকে সম্ভাব্য কারণ সাজানো, আর কোন পরীক্ষাটি আগে করলে সবচেয়ে বেশি তথ্য মিলবে তা ঠিক করা (পর্ব ৫৪-এর value of information-এর সঙ্গে মিলিয়ে)। কিছু সফটওয়্যার-সহায়তা ব্যবস্থায় এমন পদ্ধতি ব্যবহারের প্রকাশিত বিবরণ আছে।
- ঝুঁকির মডেল: নির্ভরযোগ্যতা প্রকৌশল, পরিবেশ ও কৃষির ঝুঁকি, ঋণ বা জালিয়াতির ঝুঁকি; বিশেষত যেখানে ডেটা কম, বিশেষজ্ঞের জ্ঞান বেশি, আর সিদ্ধান্তের যুক্তি ব্যাখ্যা করতে হয়।
সীমাও স্পষ্ট: গঠন আর সংখ্যা ভুল হলে উত্তরও আত্মবিশ্বাসীভাবে ভুল, শত শত CPT বিশেষজ্ঞের কাছ থেকে আদায় করা ক্লান্তিকর (পর্ব ৪৭-এর knowledge acquisition bottleneck), আর ছবি বা লেখার মতো বিশাল কাঁচা ডেটায় আজকের neural network সাধারণত বেশি সফল।
১০ · AI-এর যুগে — Bayesian network-এর কোড আর হিসাব যাচাই
AI assistant Bayesian network-এর কোড দ্রুত লিখে দেয়, কিন্তু এই বিষয়ে তার ভুলগুলো চেনা আর প্রায়ই নীরব: কোড চলে, সংখ্যা আসে, শুধু ভুল সংখ্যা।
| AI যা প্রায়ই করে | কীভাবে ধরবেন |
|---|---|
পুরোনো pgmpy API: BayesianModel, BayesianNetwork, fit(estimator=MaximumLikelihoodEstimator) | pip show pgmpy দেখে সেই সংস্করণের অফিসিয়াল docs মিলিয়ে নিন; চালিয়ে error পড়ুন |
| TabularCPD-এর কলামের ক্রম ভুল (কোন parent দ্রুত বদলায়), বা সারি-কলাম উল্টো | print(cpd) করে লেবেলসহ টেবিল দেখুন; একটি ঘর হাতে মিলিয়ে নিন; check_model() চালান |
| Collider-এর ভুল ব্যাখ্যা: “দুই কারণই তো স্বাধীন, তাই দেরি জানলেও স্বাধীন” | দুটি query চালিয়ে দেখুন: P(S ∣ L) বনাম P(S ∣ L, R) |
| ডেটা থেকে শেখা তীরকে কারণ বলে নীতির সুপারিশ | জিজ্ঞেস করুন: এই তীরের দিক কি ডেটা আলাদা করতে পারে? Intervention-এর প্রমাণ কোথায়? |
| বিরল parent-জোড়ায় ০ বা ১ সম্ভাবনা রেখে দেয়, n দেখায় না | প্রতিটি CPT সারির n ছাপান; smoothing যোগ করুন |
| Sampling-এর অনুমানকে নির্ভুল উত্তর বলে, seed বা N জানায় না | ছোট network-এ enumeration দিয়ে মিলিয়ে নিন; কয়েকটি seed-এ চালান |
প্রম্পটে network-এর গঠন আর সংস্করণ স্পষ্ট লিখুন, আর একটি স্বাধীন যাচাই চেয়ে নিন। আসল গ্রাহক বা রোগীর ডেটা কখনো পেস্ট করবেন না; চলকের নাম আর কাল্পনিক CPT-ই যথেষ্ট।
AI-এর লেখা Bayesian network হাতে পেলে — যাচাইয়ের চেকলিস্ট
আরও গভীরে: নির্ভুল inference-এর মানক পদ্ধতি junction tree (Lauritzen ও Spiegelhalter, ১৯৮৮): network-কে moralise আর triangulate করে cluster-এর একটি tree বানানো, যাতে একবারে সব চলকের marginal পাওয়া যায়; খরচ treewidth-এ exponential. Polytree-তে Pearl-এর belief propagation নির্ভুল, আর চক্রযুক্ত network-এ একই message passing (loopy BP) আনুমানিকভাবে চলে, যা পরে error-correcting code-এর decoding-এ বিখ্যাত হয়। আনুমানিক inference-ও সাধারণভাবে কঠিন (Dagum ও Luby, ১৯৯৩)। সময়ের সঙ্গে বদলানো চলকের জন্য dynamic Bayesian network, যার একটি বিশেষ রূপ পর্ব ৫৩-এর hidden Markov model.
পরীক্ষা ও ইন্টারভিউতে:
- A → B → C network-এ P(A, B, C)? P(A) · P(B ∣ A) · P(C ∣ B); B জানা থাকলে A আর C স্বাধীন।
- ৫টি হ্যাঁ/না চলক; A আর B root, C-এর parent A ও B, D আর E-এর parent C. কতটি স্বাধীন সংখ্যা? ১ + ১ + ৪ + ২ + ২ = ১০, পুরো joint table-এ ৩১.
- Explaining away কী? সাধারণ ফল জানা থাকলে এক কারণ নিশ্চিত হলে অন্য কারণের সম্ভাবনা কমে; collider-এ ঘটে।
- Fork A ← B → C-এ B জানা থাকলে A আর C? স্বাধীন; B জানা না থাকলে সাধারণত নির্ভরশীল।
- Rejection sampling কখন খারাপ? প্রমাণের সম্ভাবনা ছোট হলে প্রায় সব নমুনা বাতিল হয়; likelihood weighting বা অন্য পদ্ধতি ভালো।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১MYCIN-এর certainty factor কাজ করছিল বলে মনে হচ্ছিল, তবু ১৯৮০-এর দশকে AI গবেষকেরা কেন সম্ভাবনা আর Bayesian network-এর দিকে সরে গেলেন?
পর্ব ৪৭-এ দেখেছেন, MYCIN (১৯৭০-এর দশক, Stanford) প্রতিটি নিয়মের সঙ্গে একটি certainty factor রাখত, আর নিয়ম মিলিয়ে CF জোড়া লাগাত সহজ সূত্রে। সূত্রগুলো দ্রুত, স্থানীয় (প্রতিটি নিয়ম আলাদাভাবে কাজ করে) আর বিশেষজ্ঞের কাছে বোঝার মতো। অনেক দিন ধরে মনে করা হতো, পূর্ণ সম্ভাবনা-তত্ত্ব ব্যবহার করতে গেলে বিশাল joint table লাগবে, যা কেউ দিতে পারবে না; তাই এমন “প্রায়-সম্ভাবনা” ব্যবস্থা ব্যবহারিক সমঝোতা।
১৯৮০-এর দশকের মাঝামাঝি David Heckerman দেখান যে CF-এর জোড়া লাগানোর নিয়মগুলোকে সম্ভাবনার ভাষায় পড়া যায় শুধু কিছু কঠোর স্বাধীনতার অনুমানে, যা বাস্তব নিয়ম-জালে প্রায়ই ভাঙে। সবচেয়ে বড় সমস্যা দুবার গোনা: একই মূল প্রমাণ দুটি আলাদা নিয়মের পথে একই সিদ্ধান্তে পৌঁছালে CF দুবার বাড়ে, অথচ আসলে প্রমাণ একটিই। আরেকটি সমস্যা explaining away: স্থানীয় নিয়মে “ফল থেকে কারণ” যুক্তি করলে এক কারণ নিশ্চিত হলে অন্য কারণ কমানোর কোনো স্বাভাবিক উপায় নেই। আমাদের network-এ বৃষ্টি জানায় রাইডার সংকটের সম্ভাবনা ০.৩৩৯ থেকে ০.২৬৬-এ নেমেছে; এই আচরণ নিয়ম-ভিত্তিক স্থানীয় হিসাবে আসে না।
Pearl-এর কাজ দুই দিক থেকে বাধাটি সরায়। প্রথমত, conditional independence দিয়ে joint-কে ছোট CPT-তে ভাঙা যায়, তাই বিশেষজ্ঞ শুধু “কারণ থেকে ফল” সংখ্যা দেন, যা মানুষের জন্য স্বাভাবিক। দ্বিতীয়ত, inference-এর পদ্ধতি (polytree-তে message passing, পরে junction tree) বাস্তব আকারের network-এ চলে। এর পর থেকে অনিশ্চয়তা নিয়ে AI-এর মূলধারা সম্ভাবনার দিকে সরে যায়।
মূল উপলব্ধি: স্থানীয়, সহজ নিয়ম তখনই নিরাপদ যখন তাদের পেছনের স্বাধীনতার অনুমান সত্য; Bayesian network সেই অনুমানগুলো graph-এ স্পষ্ট করে, আর তাই দুবার গোনা বা explaining away-এর মতো আচরণ ঠিকভাবে সামলায়।
প্র ০২Berkson's paradox বা collider bias কী, আর হাসপাতাল বা একটি অ্যাপের ব্যবহারকারীদের ডেটায় এটি কীভাবে ভুয়া সম্পর্ক তৈরি করে?
১৯৪৬ সালে মার্কিন চিকিৎসা-পরিসংখ্যানবিদ Joseph Berkson একটি সমস্যা দেখান: শুধু হাসপাতালে ভর্তি রোগীদের ডেটা নিলে দুটি রোগের মধ্যে সম্পর্ক দেখা দিতে পারে, যা সাধারণ জনগোষ্ঠীতে নেই। কারণটি গঠনগত। “হাসপাতালে ভর্তি” হলো একটি collider: রোগ A থেকেও ভর্তি হয়, রোগ B থেকেও। শুধু ভর্তি রোগীদের দেখা মানে collider-এ condition করা, আর আমরা জানি তাতে স্বাধীন কারণগুলো নির্ভরশীল হয়ে যায়: যিনি ভর্তি আছেন অথচ রোগ A নেই, তাঁর ভর্তির অন্য কারণ (যেমন B) থাকার সম্ভাবনা বেশি। ফলে A আর B-এর মধ্যে ঋণাত্মক সম্পর্ক দেখা যায়, যা পুরোপুরি বাছাইয়ের ফল।
একটি পরিচিত সাম্প্রতিক উদাহরণ: ২০২০ সালে Gareth Griffith ও সহকর্মীরা Nature Communications-এ লেখেন যে COVID-19-এর শুরুর দিকের অনেক গবেষণা শুধু পরীক্ষা করা বা হাসপাতালে ভর্তি মানুষের ডেটা ব্যবহার করেছিল, আর পরীক্ষা বা ভর্তি নিজেই নানা ঝুঁকির উপাদানের সাধারণ ফল। তাই কিছু ঝুঁকি-উপাদান আর রোগের তীব্রতার মধ্যে পাওয়া সম্পর্ক অংশত collider bias হতে পারে বলে তাঁরা সতর্ক করেন। সঠিক মাত্রা নিয়ে গবেষকদের মধ্যে আলোচনা ছিল, কিন্তু গঠনগত ঝুঁকিটি স্বীকৃত।
বাজারঘরের মতো অ্যাপেও একই কথা। শুধু অভিযোগকারী গ্রাহকদের সমীক্ষা করলে, আর অভিযোগ দেরি থেকেও আসে, খারাপ পণ্য থেকেও, তাহলে এই দলে “দেরি হয়নি” গ্রাহকদের মধ্যে খারাপ পণ্যের হার অস্বাভাবিক বেশি দেখাবে; “দ্রুত ডেলিভারি মানে খারাপ পণ্য” সিদ্ধান্তটি আসলে বাছাইয়ের ছায়া। Machine learning-এর ডেটাসেটেও এমন বাছাই সাধারণ: শুধু অনুমোদিত ঋণের ডেটা, শুধু ক্লিক করা বিজ্ঞাপনের ডেটা।
মূল উপলব্ধি: ডেটা কীভাবে বাছাই হয়েছে, তা একটি চলক; সেই চলক যদি দুটি বৈশিষ্ট্যের সাধারণ ফল হয়, তাহলে বাছাই করা ডেটায় তাদের সম্পর্ক বিশ্বাস করার আগে DAG এঁকে দেখুন collider-এ condition হচ্ছে কি না।
প্র ০৩সাধারণ Bayesian network-এ নির্ভুল inference NP-hard, এমনকি ভালো আনুমানিক উত্তরও কঠিন। তবু বাস্তবে এত network কীভাবে কাজ করে?
১৯৯০ সালে Gregory Cooper দেখান যে সাধারণ Bayesian network-এ নির্ভুল সম্ভাবনা বের করা NP-hard: পর্ব ৪৫-এর SAT সমস্যাকে একটি network-এর inference-এ রূপান্তর করা যায়। ১৯৯৩ সালে Paul Dagum আর Michael Luby দেখান, নির্দিষ্ট নির্ভুলতার আনুমানিক উত্তরও সাধারণভাবে NP-hard. শুনে পুরো ধারণাটিই অব্যবহারিক মনে হতে পারে।
কিন্তু “সবচেয়ে খারাপ ক্ষেত্রে কঠিন” আর “আমাদের ক্ষেত্রে কঠিন” এক নয়। Variable elimination বা junction tree-এর খরচ নির্ভর করে network-এর গঠনের একটি মাপে, treewidth: বাদ দেওয়ার সময় মাঝের টেবিলে সর্বোচ্চ কতগুলো চলক একসঙ্গে আসে। বাস্তবের অনেক network বিরল সংযোগের: প্রতিটি node-এর অল্প কয়েকটি parent, আর লম্বা চক্র কম। আমাদের বাজারঘর network একটি polytree, যেখানে খরচ প্রায় linear. Treewidth ছোট থাকলে কয়েক শ বা হাজার node-এর network-ও নির্ভুলভাবে চলে।
যেখানে গঠন ঘন, সেখানে পথ কয়েকটি: sampling (likelihood weighting, Gibbs sampling ও অন্যান্য Markov chain Monte Carlo), loopy belief propagation বা variational পদ্ধতি, যাদের নিশ্চয়তা কম কিন্তু অভিজ্ঞতায় প্রায়ই ভালো; অথবা নকশা পর্যায়েই network সরল রাখা আর গুরুত্বহীন তীর বাদ দেওয়া। SAT-এর ক্ষেত্রেও একই গল্প দেখেছেন: সমস্যাটি NP-complete, তবু আধুনিক solver বাস্তবের বিশাল উদাহরণ সমাধান করে। জটিলতা-তত্ত্ব সবচেয়ে খারাপ ক্ষেত্রের কথা বলে; বাস্তবের গঠন চিনে কাজে লাগানোই প্রকৌশল।
মূল উপলব্ধি: NP-hard মানে “সব network-এর জন্য দ্রুত পদ্ধতি সম্ভবত নেই”, “আপনার network ধীর” নয়; গঠন (treewidth) মাপুন, সরল রাখুন, আর দরকারে আনুমানিক পদ্ধতির উত্তর নির্ভুল উত্তরের সঙ্গে ছোট উদাহরণে মিলিয়ে নিন।
প্র ০৪Judea Pearl বলেন সম্ভাবনা দিয়ে “দেখা”, “করা” আর “কল্পনা করা” তিনটি আলাদা স্তরের প্রশ্ন। Bayesian network এর কোন স্তরে আছে, আর এটি কেন গুরুত্বপূর্ণ?
Pearl আর Dana Mackenzie-র ২০১৮ সালের বই The Book of Why-তে একটি “ladder of causation” প্রস্তাব করা হয়েছে। প্রথম ধাপ association, অর্থাৎ দেখা: “অভিযোগ দেখলে দেরির সম্ভাবনা কত?” এটি P(L ∣ C), আর একটি সাধারণ Bayesian network বা যেকোনো ভালো পরিসংখ্যানিক model এর উত্তর দেয়। দ্বিতীয় ধাপ intervention, অর্থাৎ করা: “রাইডার বাড়ালে দেরি কতটা কমবে?” এটি P(L ∣ do(S=0)), যার জন্য তীরগুলোকে কারণ-ফল হিসেবে বিশ্বাস করতে হয়। তৃতীয় ধাপ counterfactual: “এই নির্দিষ্ট অর্ডারে সেদিন বৃষ্টি না হলে কি দেরি হতো?” এর জন্য আরও শক্ত causal model লাগে।
একটি সাধারণ Bayesian network গাণিতিকভাবে প্রথম ধাপে থাকে: এটি একটি বণ্টন, আর একই বণ্টন উল্টো দিকের তীরেও লেখা যায়। তীরগুলোকে কারণ হিসেবে ঘোষণা করলে সেটি একটি causal Bayesian network হয়, আর তখন do-operator দিয়ে দ্বিতীয় ধাপের প্রশ্ন হিসাব করা যায়। Pearl ১৯৯৫ সালে do-calculus-এর নিয়ম প্রকাশ করেন, আর ২০০০ সালে বই Causality-তে পুরো কাঠামো দাঁড় করান। আমাদের উদাহরণে অভিযোগ বন্ধ করলে দেরি কমে না (০.৩২৫-ই থাকে), কিন্তু অভিযোগ না আসা দেখলে দেরির সম্ভাবনা ০.১৬৮; দুটি প্রশ্নের উত্তরে প্রায় দুই গুণ পার্থক্য।
পার্থক্যটি AI-এর জন্য বড়। বেশিরভাগ machine learning model প্রথম ধাপে থাকে, অথচ ব্যবসা বা নীতির সিদ্ধান্ত প্রায়ই দ্বিতীয় ধাপের প্রশ্ন: দাম কমালে, নিয়ম বদলালে কী হবে। তখন spurious correlation বা confounding থেকে ভুল নীতি আসতে পারে। Causal inference-এ অন্য ঘরানাও আছে (যেমন Donald Rubin-এর potential outcomes কাঠামো), আর Pearl-এর কিছু দাবি নিয়ে সমালোচনাও আছে।
মূল উপলব্ধি: network-এর তীর কারণ-ফল বোঝায় কি না, তা ডেটা নয়, আপনার অনুমান ঠিক করে; “দেখলে কী” আর “করলে কী” আলাদা প্রশ্ন, আর দ্বিতীয়টির উত্তরে experiment বা স্পষ্ট causal অনুমান লাগে।
প্র ০৫১৯৮০-৯০-এর দশকে চিকিৎসার জন্য বড় Bayesian network বানানো হয়েছিল, কিছু গবেষণায় ভালো ফলও এসেছিল। তবু এগুলো কেন সব হাসপাতালে সাধারণ হয়নি?
সবচেয়ে উল্লেখিত উদাহরণগুলোর একটি Pathfinder, lymph node-এর রোগনির্ণয়ে pathologist-দের সাহায্যের জন্য David Heckerman, Eric Horvitz, Bharat Nathwani ও সহকর্মীদের ১৯৮০-৯০-এর দশকের কাজ। প্রকাশিত মূল্যায়নে এর একটি Bayesian network সংস্করণ আগের, কম আনুষ্ঠানিক সংস্করণের চেয়ে ভালো করেছিল বলে জানানো হয়। আরেকটি পরিচিত কাজ QMR-DT, সাধারণ অভ্যন্তরীণ চিকিৎসার একটি বড় জ্ঞানভান্ডারকে রোগ আর উপসর্গের দুই স্তরের network-এ রূপ দেওয়া। এই network এত বড় আর ঘন ছিল যে নির্ভুল inference অনেক ক্ষেত্রে অসম্ভব হয়ে পড়ে, আর এটি আনুমানিক inference-এর গবেষণার একটি পরীক্ষাক্ষেত্র হয়ে ওঠে।
দৈনন্দিন ক্লিনিকে ব্যাপক ব্যবহার না হওয়ার কারণ শুধু গণিত নয়। প্রথমত, জ্ঞান আদায়: শত শত রোগ আর হাজারো উপসর্গের CPT বিশেষজ্ঞের কাছ থেকে নেওয়া আর হালনাগাদ রাখা বিশাল কাজ, rule-based system-এর মতোই knowledge acquisition bottleneck. দ্বিতীয়ত, কাজের ধারায় মেলানো: ডাক্তারের সময় কম, আর তথ্য আলাদা করে টাইপ করতে হলে system ব্যবহার কমে। তৃতীয়ত, দায় আর বিশ্বাস: ভুল সুপারিশের দায় কার, আর একটি সংখ্যা কেন দেওয়া হলো তা ব্যাখ্যা করা যায় কি না। চতুর্থত, স্থানান্তর: একটি দেশ বা হাসপাতালের base rate দিয়ে বানানো network অন্য জায়গায় ভুল prior দিতে পারে (পর্ব ৩৫-এর base rate-এর সমস্যা)।
তবু ধারণাগুলো হারায়নি; চিকিৎসা-AI নিয়ে আজকের আলোচনায়ও (ডেটা কোথা থেকে, ব্যাখ্যা কতটা, দায় কার) একই প্রশ্ন ফিরে আসে। যেকোনো দেশে, বাংলাদেশেও, এমন system নিয়ে প্রথম প্রশ্ন হওয়া উচিত: স্থানীয় ডেটায় যাচাই হয়েছে কি না।
মূল উপলব্ধি: একটি AI system-এর সাফল্য শুধু সঠিক inference-এর ওপর নয়, জ্ঞান রক্ষণাবেক্ষণ, কাজের ধারা, দায় আর স্থানীয় base rate-এর ওপরও নির্ভর করে; ভালো গবেষণা-ফল আর ব্যাপক ব্যবহারের মাঝে এই দূরত্ব প্রায়ই সবচেয়ে বড়।
অনুশীলন
হাতের হিসাব, নকশার সমালোচনা, আউটপুট অনুমান আর কোড; প্রতিটির উত্তর নিচে লুকানো আছে।
-
চেকআউট ব্যর্থতার network (হিসাব): বাজারঘরের সার্ভার ধীর (S) আর পেমেন্ট গেটওয়ে সমস্যা (G) স্বাধীন root, দুটোই চেকআউট ব্যর্থতার (F) parent (কাল্পনিক সংখ্যা): P(S=1) = ০.১, P(G=1) = ০.০৫; P(F=1 ∣ S, G): (1,1) = ০.৯৫, (1,0) = ০.৬, (0,1) = ০.৮, (0,0) = ০.০২. হিসাব করুন: (ক) P(S=1, G=0, F=1), (খ) P(F=1), (গ) P(G=1 ∣ F=1), (ঘ) P(G=1 ∣ F=1, S=1). (গ) আর (ঘ)-এর তুলনা কী দেখায়?
- (ক) factorisation: ০.১ × ০.৯৫ × ০.৬ = ০.০৫৭.
- (খ) চারটি পথ: (1,1) ০.১ × ০.০৫ × ০.৯৫ = ০.০০৪৭৫; (1,0) ০.০৫৭; (0,1) ০.৯ × ০.০৫ × ০.৮ = ০.০৩৬; (0,0) ০.৯ × ০.৯৫ × ০.০২ = ০.০১৭১. যোগ P(F=1) = ০.১১৪৮৫.
- (গ) G=1-এর পথ: ০.০০৪৭৫ + ০.০৩৬ = ০.০৪০৭৫; ০.০৪০৭৫ ÷ ০.১১৪৮৫ ≈ ০.৩৫৫.
- (ঘ) S=1 জানা: ০.০০৪৭৫ ÷ (০.০০৪৭৫ + ০.০৫৭) = ০.০০৪৭৫ ÷ ০.০৬১৭৫ ≈ ০.০৭৭.
Explaining away: চেকআউট ব্যর্থ হলে গেটওয়ে সমস্যার সম্ভাবনা prior ০.০৫ থেকে ০.৩৫৫-এ ওঠে, কিন্তু সার্ভার ধীর ছিল জানলে তা ০.০৭৭-এ নামে, prior-এর কাছাকাছি, কারণ ধীর সার্ভার একাই ব্যর্থতার ভালো ব্যাখ্যা। (সংখ্যাগুলো একটি ছোট Python script দিয়ে মিলিয়ে নেওয়া।)
-
সমালোচনা ও নতুন নকশা: একজন সহকর্মী লিখেছেন: “গত মাসের ৪০০টি অভিযোগের রেকর্ড থেকে একটি structure-learning টুল চালিয়ে network পেয়েছি। তাতে Complaint → Late তীর এসেছে, তাই অভিযোগই দেরির কারণ; আমরা অভিযোগের বোতাম লুকিয়ে দেব। এছাড়া P(Late=1 ∣ Traffic=1, Shortage=1) = 1.0 এসেছে (সেই ঘরে ২টি রেকর্ড), তাই যানজট আর রাইডার সংকট একসঙ্গে থাকলে দেরি নিশ্চিত।” কী কী ভুল, আর আপনি কীভাবে কাজটি সাজাবেন?
- তীরের দিক কারণ নয়: observational ডেটা প্রায়ই একাধিক DAG-কে সমান সমর্থন করে; Complaint → Late আর Late → Complaint অনেক সময় আলাদা করা যায় না। অভিযোগের বোতাম লুকানো একটি intervention, do(C=0), যা দেরি কমায় না।
- Collider-এ বাছাই: শুধু অভিযোগের রেকর্ড মানে C=1-এ condition করা; C হলো দেরির উত্তরসূরি, তাই যানজট আর রাইডার সংকটের মতো কারণের মধ্যে ভুয়া সম্পর্ক তৈরি হয়, Berkson-এর সেই ফাঁদ। সব অর্ডারের ডেটা লাগবে।
- ছোট n আর ০/১ সম্ভাবনা: ২টি রেকর্ড থেকে 1.0 খুব নড়বড়ে; প্রতিটি CPT সারির n দেখাতে হবে, আর Laplace বা অন্য smoothing দিতে হবে ((2+1)/(2+2) = ০.৭৫)।
নতুন নকশা: ডেলিভারি দলের জ্ঞান দিয়ে গঠন আঁকুন, সব অর্ডারের ডেটা থেকে smoothing-সহ CPT শিখুন আর প্রতিটি সারির n দেখান। Structure learning-এর ফল প্রস্তাব, সিদ্ধান্ত নয়; নীতি বদলের আগে (যেমন বাড়তি রাইডার) একটি ছোট A/B test চালান।
-
আউটপুট অনুমান: নিচের কোড কী ছাপবে? দ্বিতীয় লাইনের দুটি সংখ্যা কী বোঝায়?
P_A = 0.2 P_B = {1: 0.9, 0: 0.1} # P(B=1 | A) num = P_A * P_B[1] den = num + (1 - P_A) * P_B[0] print(round(den, 2), round(num / den, 3)) print(2 ** 10 - 1, 1 + 9 * 2)0.26 0.692 1023 19প্রথম লাইন দুই node-এর network A → B:
den= P(B=1) = ০.২ × ০.৯ + ০.৮ × ০.১ = ০.১৮ + ০.০৮ = ০.২৬ (total probability), আরnum / den= P(A=1 ∣ B=1) = ০.১৮ ÷ ০.২৬ ≈ ০.৬৯২, Bayes-এর নিয়মে। দ্বিতীয় লাইন: ১০টি হ্যাঁ/না চলকের পুরো joint table-এ ১,০২৩টি স্বাধীন সংখ্যা, আর ১০টি node-এর একটি chain (X1 → X2 → … → X10)-এ মাত্র ১৯টি: root-এ ১, বাকি ৯টির প্রতিটিতে ২. (লেখকের যন্ত্রে desktop Python 3.14.4-এ সত্যিকারের run.) -
Likelihood weighting লিখুন (কোড):
rejection_sampling.py-এরprior_sampleবদলে একটি function লিখুন যা Complaint টানে না, বরং প্রতিটি নমুনার ওজন দেয় P(C=1 ∣ L), আর ওজনযুক্ত অনুপাত দিয়ে P(Rain=1 ∣ Complaint=1) অনুমান করে। N = 100, 1000, 10000, 100000-এ চালিয়ে ০.৪০৮-এর সঙ্গে তুলনা করুন।import numpy as np P_R, P_E = 0.3, 0.1 P_T = {1: 0.8, 0: 0.3} P_S = {1: 0.6, 0: 0.1} P_L = {(1, 1): 0.9, (1, 0): 0.5, (0, 1): 0.6, (0, 0): 0.05} P_C = {1: 0.6, 0: 0.05} def lw(n, rng): # evidence: Complaint = 1 u = lambda p: rng.random(n) < p R, E = u(P_R), u(P_E) T = u(np.where(R, P_T[1], P_T[0])) S = u(np.where(E, P_S[1], P_S[0])) L = u(np.select([T & S, T & ~S, ~T & S], [P_L[(1, 1)], P_L[(1, 0)], P_L[(0, 1)]], P_L[(0, 0)])) w = np.where(L, P_C[1], P_C[0]) # C নমুনা নয়, ওজন = P(C=1 | L) return (w * R).sum() / w.sum() rng = np.random.default_rng(42) for n in (100, 1000, 10000, 100000): print(n, round(lw(n, rng), 4))সত্যিকারের run (লেখকের যন্ত্রে desktop Python 3.14.4, NumPy 2.5.4; CPT-গুলো একই, লাইন বাঁচাতে B আর
queryবাদ):100 0.4355 1000 0.3989 10000 0.4018 100000 0.4092কোনো নমুনা ফেলা হয়নি: N = ১০০০-এ ১০০০টিই কাজে লেগেছে, আর অনুমান ০.৩৯৯; একই N-এ rejection sampling শুধু ২৩২টি নমুনা রেখেছিল। ওজন দুই রকম, ০.৬ (দেরি) আর ০.০৫ (সময়মতো), তাই এখানে ওজনের বৈষম্য মাঝারি; প্রমাণ আরও বিরল হলে অল্প কয়েকটি ভারী নমুনা ফলকে টেনে নিতে পারে। এগুলো seed 42-এর একটি run, সাধারণ ফল নয়।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি Bayesian network বানিয়ে enumeration দিয়ে প্রশ্নের উত্তর বের করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- প্রাসঙ্গিক চলক আর তাদের সম্ভাব্য মান ঠিক করা
- কে কার ওপর সরাসরি নির্ভর করে তা দেখে চক্রহীন graph (DAG) আঁকা
- প্রতিটি node-এর parent-দের প্রতিটি মানের জন্য CPT পূরণ করা
- প্রতিটি CPT সারির যোগফল ১ কি না যাচাই করা
- প্রমাণের মান বসিয়ে লুকানো চলকগুলোর ওপর joint যোগ করা
- প্রশ্নের প্রতিটি মানের ফলকে তাদের যোগফল দিয়ে ভাগ করে normalise করা
পাঁচটি হ্যাঁ/না চলক: A আর B-এর কোনো parent নেই, C-এর parent A ও B, D আর E-এর parent C. Bayesian network-এ মোট কতটি স্বাধীন সংখ্যা লাগে?
- ৩১
- ৫
- ১০
- ১৬
একটি Bayesian network-এ তীর Rain → Traffic → Late. P(Rain, Traffic, Late)-এর সঠিক factorisation কোনটি?
- P(Rain) · P(Traffic) · P(Late)
- P(Rain) · P(Traffic ∣ Rain) · P(Late ∣ Traffic)
- P(Rain) · P(Traffic ∣ Rain) · P(Late ∣ Traffic, Rain), আর এটিই network-এর factorisation
- P(Late) · P(Traffic ∣ Late) · P(Rain ∣ Traffic)
Traffic → Late ← Shortage, আর Traffic ও Shortage-এর কোনো parent বা অন্য সংযোগ নেই। কোন বক্তব্যটি সঠিক?
- Late জানা না থাকলেও Traffic আর Shortage নির্ভরশীল
- Late জানা থাকলে Traffic আর Shortage স্বাধীন হয়ে যায়
- গঠন দেখে কিছুই বলা যায় না, সব CPT-র ওপর নির্ভর করে
- Late জানা না থাকলে স্বাধীন, Late জানা থাকলে সাধারণত নির্ভরশীল
একটি network-এ বৃষ্টি (যানজটের মাধ্যমে) আর রাইডার সংকট দুটোই দেরির কারণ, আর বৃষ্টি ও রাইডার সংকট স্বাধীন। একটি অর্ডার দেরি হয়েছে জানার পর জানা গেল সেদিন বৃষ্টি হয়েছিল। রাইডার সংকটের সম্ভাবনার কী হবে?
- শুধু দেরি জানার সময়ের চেয়ে কমবে
- শুধু দেরি জানার সময়ের চেয়ে বাড়বে
- বদলাবে না, কারণ বৃষ্টি আর রাইডার সংকট স্বাধীন
- শূন্য হয়ে যাবে
একটি network-এ প্রমাণের সম্ভাবনা P(e) = ০.০১. Rejection sampling-এ ১০,০০০টি prior sample টানলে গড়ে মোটামুটি কতটি নমুনা রাখা হবে?
- ১০,০০০
- প্রায় ১০০
- প্রায় ১,০০০
- প্রায় ১০
শুধু observational ডেটা থেকে শেখা একটি Bayesian network-এ X → Y তীর এসেছে। কোন সিদ্ধান্তটি সবচেয়ে যুক্তিসঙ্গত?
- X নিশ্চিতভাবে Y-এর কারণ
- X আর Y-এর মধ্যে কোনো সম্পর্ক নেই
- X আর Y সম্পর্কিত, কিন্তু তীরের দিক কারণ প্রমাণ করে না
- X বদলালে Y অবশ্যই বদলাবে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Joint table 2n হারে বাড়ে; conditional independence দিয়ে Bayesian network সেটিকে ছোট CPT-তে ভাঙে। n টি node, সর্বোচ্চ k টি parent হলে সর্বোচ্চ n · 2k সংখ্যা।
- Network = DAG + প্রতিটি node-এর CPT; P(x1, …, xn) = ∏ P(xi ∣ parents). যেকোনো পূর্ণ সারির সম্ভাবনা: প্রতিটি CPT থেকে একটি ঘর গুণ।
- Inference: enumeration লুকানো চলকের ওপর যোগ করে normalise করে; variable elimination একই উত্তর কম গুণে দেয়; rejection sampling আর likelihood weighting আনুমানিক, N বাড়লে সঠিক উত্তরের কাছে যায়।
- Chain আর fork-এ মাঝখান জানলে পথ বন্ধ, collider-এ খোলে; তাই explaining away আর Berkson's paradox. d-separation graph দেখেই স্বাধীনতা বলে।
- CPT শেখা মানে গোনা, বিরল ঘরে smoothing; লাইব্রেরির (pgmpy 1.x) API আর CPT-র ক্রম যাচাই করুন; আর তীরকে কারণ ধরা একটি অনুমান, “দেখা” আর “করা” আলাদা প্রশ্ন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৩ · Markov chain আর HMM — অবস্থা থেকে অবস্থায়পরবর্তী পর্বTransition matrix, stationary distribution, n-gram, forward algorithm আর Viterbi.
- AI-এর ভিত্তি আর Python for AI · কোর্স হোম৭৯টি পর্বপুরো কোর্সের রোডম্যাপ; মডিউল ৯-এর বাকি পর্বগুলোও এখানে।
- পর্ব ৩৫ · AI-এর জন্য সম্ভাবনা — event, conditional আর independenceআগের ভিত্তিConditional probability, independence, total probability আর Bayes-এর নিয়ম, যার ওপর এই পর্ব দাঁড়িয়ে।
- পর্ব ৩৬ · Distribution আর samplingআগের ভিত্তিMonte Carlo simulation আর law of large numbers, যা sampling-ভিত্তিক inference-এর ভিত্তি।
- DSA · Topological sortপাশের ট্র্যাকDAG-এর node-কে parent আগে, child পরে ক্রমে সাজানোর algorithm, prior sampling-এ যা লাগে।
- DSA · Dynamic programming-এর ভিত্তিপাশের ট্র্যাকমাঝের ফল জমিয়ে রেখে আবার ব্যবহার, variable elimination-এর মূল কৌশল।