এই পর্বে যা শিখবেন
- Propositional logic কেন বড় জগতে অচল, আর FOL কোন তিনটি জিনিস দিয়ে জগৎ দেখে: বস্তু, সম্পর্ক আর function
- ∀ আর ∃-এর মানে, তাদের scope, আর ∀-এর সঙ্গে ∧ বা ∃-এর সঙ্গে → লেখার ভুল চিনে ঠিক করা
- “কেউ না”, “শুধু”, “অন্তত দুটি”, “ঠিক একজন”সহ বারোটি বাংলা বাক্যকে FOL-এ লেখা আর FOL থেকে বাংলায় পড়া
- Model আর interpretation সহজ ভাষায়, আর Python-এ একটি ছোট সীমিত জগতে ∀ আর ∃ loop দিয়ে যাচাই
- Universal ও existential instantiation, propositionalisation-এর বিস্ফোরণ, unification হাতে আর কোডে, generalised modus ponens, আর SWI-Prolog-এ backward chaining
আগের পর্বে (পর্ব ৪৫) দেখেছেন, propositional logic-এ প্রতিটি পূর্ণ বাক্য একটি প্রতীক (P, Q), সেগুলো ¬ (না), ∧ (এবং), ∨ (অথবা), → (যদি…তবে) আর ↔ (যদি এবং কেবল যদি) দিয়ে জোড়া হয়, truth table দিয়ে অর্থ যাচাই হয়, আর entailment প্রমাণ করা যায় CNF ও resolution দিয়ে। এই পর্বে সেই ভিত্তির ওপর একটি বড় ধাপ: বাক্যের ভেতরে ঢুকে কার সম্পর্কে, কী সম্পর্ক, তা লেখা। পুরো পর্বের উদাহরণ বাজারঘর (এই কোর্সের কাল্পনিক অনলাইন দোকান); গ্রাহক, পণ্য, দাম আর অর্ডার সব বানানো।
১ · Propositional logic কেন যথেষ্ট নয়
বাজারঘরে চারজন গ্রাহক (রহিম, করিম, মিতু, সুমি) আর পাঁচটি পণ্য (মিনিকেট চাল, চিনিগুঁড়া চাল, মসুর ডাল, সয়াবিন তেল, একটি সাবান)। “কে কী অর্ডার করেছেন” propositional logic-এ লিখতে হলে প্রতিটি জোড়ার জন্য একটি আলাদা প্রতীক লাগে: O_Rahim_Miniket, O_Rahim_Chinigura, … মোট ৪ × ৫ = ২০টি। এখন দোকানের ম্যানেজার একটি নিয়ম যাচাই করতে চান: “যে গ্রাহক চাল অর্ডার করেছেন, তিনি ডালও অর্ডার করেছেন।” Propositional logic-এ এটি একটি বাক্যে লেখা যায় না; প্রত্যেক গ্রাহকের জন্য আলাদা:
(O_Rahim_Miniket ∨ O_Rahim_Chinigura) → O_Rahim_Mosur
(O_Karim_Miniket ∨ O_Karim_Chinigura) → O_Karim_Mosur
(O_Mitu_Miniket ∨ O_Mitu_Chinigura) → O_Mitu_Mosur
(O_Sumi_Miniket ∨ O_Sumi_Chinigura) → O_Sumi_Mosur
সমস্যা তিনটি। এক, আকার: ১,০০০ গ্রাহক আর ৫,০০০ পণ্য হলে শুধু অর্ডারের প্রতীকই ৫০ লাখ, আর নিয়মটি ১,০০০ বার লিখতে হয়। দুই, নতুন গ্রাহক এলে নতুন প্রতীক আর নতুন বাক্য, আর পুরোনো নিয়ম নিজে থেকে তাঁর ওপর খাটে না। তিন, প্রতীকের নামের ভেতরের “Rahim” বা “Miniket” logic-এর চোখে অর্থহীন অক্ষর; O_Rahim_Miniket আর O_Rahim_Mosur যে একই মানুষের কথা বলছে, তা logic জানে না। নতুন চালের ব্র্যান্ড এলে “চাল” শব্দটিও প্রতিটি বাক্যে হাতে যোগ করতে হয়।
পাড়ার মুদি দোকানের খাতার কথা ভাবুন। এক দোকানি প্রতিটি লেনদেন আলাদা বাক্যে লেখেন: “রহিম ভাই মঙ্গলবার দুই কেজি চাল নিলেন”। আরেকজন খাতায় কলাম করেন: নাম, পণ্য, পরিমাণ। দ্বিতীয় খাতায় “যারা চাল নিয়েছেন তাদের সবার নাম” এক নজরে বের হয়, কারণ খাতাটি কে আর কী আলাদা করে রেখেছে। Propositional logic প্রথম খাতা, first-order logic দ্বিতীয়টি।
FOL-এ সেই নিয়মটি একটিমাত্র বাক্য, যা যেকোনো সংখ্যক গ্রাহক আর পণ্যের জন্য খাটে:
∀x ( Customer(x) ∧ ∃y (Rice(y) ∧ Ordered(x, y)) → ∃z (Lentil(z) ∧ Ordered(x, z)) )
পড়ুন এভাবে: “প্রত্যেক বস্তু x-এর জন্য: x যদি গ্রাহক হন আর এমন কোনো y থাকে যা চাল এবং x তা অর্ডার করেছেন, তবে এমন কোনো z আছে যা ডাল এবং x তা অর্ডার করেছেন।” এখানের প্রতিটি চিহ্ন এই পর্বে ধাপে ধাপে আসবে। অংশ ৫-এ এই অনুবাদটিই একটি stepper-এ টুকরো টুকরো করে বানানো হবে, আর দেখা যাবে বাজারঘরের জগতে বাক্যটি মিথ্যা: সুমি চাল নিয়েছেন, ডাল নেননি।
২ · FOL-এর জগৎ — বস্তু, সম্পর্ক আর function
প্রতিটি logic জগৎ সম্পর্কে একটি অনুমান করে নেয়: জগতে কী কী আছে। একে বলে ontological commitment. Russell ও Norvig-এর বই “Artificial Intelligence: A Modern Approach” এই তুলনাটি একটি সারণিতে দেখায়; নিচের সারণি তার সরল রূপ।
| ভাষা | জগতে কী আছে | একটি বাক্য সম্পর্কে বিশ্বাস |
|---|---|---|
| Propositional logic | শুধু তথ্য (fact) | সত্য, মিথ্যা, অজানা |
| First-order logic | তথ্য, বস্তু আর তাদের সম্পর্ক | সত্য, মিথ্যা, অজানা |
| Probability (মডিউল ৯) | তথ্য | ০ থেকে ১-এর মধ্যে বিশ্বাসের মাত্রা |
FOL-এর শব্দভান্ডারে তিন ধরনের প্রতীক, আর প্রতিটির একটি নির্দিষ্ট কাজ:
Constant — নির্দিষ্ট বস্তু
একটি নির্দিষ্ট বস্তুর নাম: Rahim, Miniket, Soap1. যেন খাতায় একজন মানুষের নাম।
Predicate — গুণ আর সম্পর্ক
সত্য বা মিথ্যা হয় এমন কিছু: এক জনের গুণ Rice(x), দুজনের সম্পর্ক Ordered(x, y). কয়টি argument লাগে, তাকে বলে arity.
Function — বস্তু থেকে বস্তু
একটি বস্তু দিলে ঠিক একটি বস্তু ফেরত দেয়: rider(Rahim) = রহিমের ডেলিভারি রাইডার, price(Miniket) = মিনিকেটের দাম। ফল সত্য-মিথ্যা নয়, একটি বস্তু।
এগুলো দিয়ে বানানো হয় বড় অংশ। একটি term মানে যেকোনো বস্তুর নাম: একটি constant (Rahim), একটি variable (x), অথবা function-এর ভেতরে term (rider(Rahim), price(rider(x))-এর মতো গভীরেও)। একটি atomic sentence মানে term-এর ওপর একটি predicate: Ordered(Rahim, Miniket), অথবা দুটি term-এর সমতা: rider(Rahim) = Jamal. Atomic sentence-কে পর্ব ৪৫-এর মতোই logical connective (¬ ∧ ∨ → ↔) দিয়ে জোড়া যায়, আর তার ওপর বসে quantifier (অংশ ৩)।
নামের নিয়ম নিয়ে সাবধান। FOL-এর বইয়ে সাধারণত constant বড় হাতের অক্ষরে (Rahim) আর variable ছোট হাতে (x). Prolog-এ ঠিক উল্টো: rahim constant, X variable. এই পর্বের Python আর Prolog কোড Prolog-এর নিয়ম মানে, গাণিতিক লেখা বইয়ের নিয়ম। পরীক্ষার খাতায় যে নিয়মই নিন, শুরুতে একবার লিখে দিন।
বাজারঘরের ছোট জগৎ
পুরো পর্ব জুড়ে এই জগৎটি ব্যবহার হবে। এতে ৯টি বস্তু: ৪ জন গ্রাহক আর ৫টি পণ্য। দাম টাকায়, বানানো: মিনিকেট ৭৮, চিনিগুঁড়া ১৩৫, মসুর ১১০, সয়াবিন তেল ১৮৫, সাবান ৪৫. রাইডার দুজন: রহিম আর করিমের rider জামাল, মিতু আর সুমির কামাল।
ছবিটি logic-এর আরেকটি মূল ধারণা দেখায়। প্রতীক নিজে কিছু বোঝায় না; Ordered লিখলেই কম্পিউটার জানে না এটি “অর্ডার করা”। একটি interpretation প্রতিটি প্রতীককে জগতের কিছুর সঙ্গে মেলায়: Rahim মানে বাঁ দিকের প্রথম বৃত্ত, Ordered মানে আটটি রেখার সেট, rider মানে গ্রাহক থেকে রাইডারের ম্যাপ। বস্তুর সেট (এখানে ৯টি) হলো domain of discourse, আর domain ও interpretation মিলে একটি model. অংশ ৬-এ এটি আরও গুছিয়ে আসবে।
৩ · Quantifier — ∀ আর ∃, তাদের scope আর দুটি পরিচিত ভুল
Variable একা কিছু বলে না; Rice(x) সত্য না মিথ্যা, তা x কে, তার ওপর নির্ভর করে। Quantifier বলে দেয় x-কে কীভাবে পড়তে হবে:
- ∀x P(x) (“for all”, universal quantifier): domain-এর প্রতিটি বস্তু x-এর জন্য P(x) সত্য। একটি বস্তুতেও মিথ্যা হলে পুরো বাক্য মিথ্যা। বড় ∧-এর মতো: P(বস্তু১) ∧ P(বস্তু২) ∧ …
- ∃x P(x) (“there exists”, existential quantifier): domain-এ অন্তত একটি বস্তু আছে যার জন্য P সত্য। বড় ∨-এর মতো: P(বস্তু১) ∨ P(বস্তু২) ∨ …
Quantifier যে অংশের ওপর খাটে, সেটি তার scope, সাধারণত বন্ধনী দিয়ে দেখানো। Scope-এর ভেতরে থাকা x হলো bound variable; কোনো quantifier-এর আওতায় নেই এমন variable free variable. যেমন ∀x Ordered(x, y)-এ y free, তাই এটি পূর্ণ বাক্য নয়: y কে, না জানলে সত্য-মিথ্যা বলা যায় না। দুটি quantifier একে অপরের সঙ্গে বাঁধা: ¬∀x P(x) ≡ ∃x ¬P(x) (“সবাই নয়” মানে “অন্তত একজন নয়”) আর ¬∃x P(x) ≡ ∀x ¬P(x) (“কেউ না” মানে “প্রত্যেকেই না”)। এখানে ≡ মানে দুই দিকের অর্থ সব model-এ এক।
∀-এর সঙ্গে →, ∃-এর সঙ্গে ∧
বাস্তবের প্রায় সব “সব X হলো Y” বাক্যে ∀ বসে → এর সঙ্গে, আর “কোনো X আছে যা Y” বাক্যে ∃ বসে ∧ এর সঙ্গে। কারণটা domain-এ লুকিয়ে। domain-এ শুধু চাল নেই, গ্রাহকও আছেন। “সব চালের দাম ৬০ টাকার বেশি” বলতে চাইছি শুধু চালের কথা; রহিমের দাম নিয়ে কিছু বলছি না।
| বাক্য | ভুল অনুবাদ | ঠিক অনুবাদ |
|---|---|---|
| সব চালের দাম ৬০ টাকার বেশি। | ∀x (Rice(x) ∧ price(x) > 60) মানে “সবকিছুই চাল এবং দামি”; রহিম চাল নন, তাই মিথ্যা | ∀x (Rice(x) → price(x) > 60) |
| মিতু কোনো একটি চাল অর্ডার করেছেন। | ∃x (Rice(x) → Ordered(Mitu, x)) যেকোনো অ-চাল বস্তু (যেমন সাবান) এটিকে সত্য বানায়, কারণ মিথ্যা → যা-কিছু = সত্য | ∃x (Rice(x) ∧ Ordered(Mitu, x)) |
দ্বিতীয় ভুলটি সূক্ষ্ম। পর্ব ৪৫-এর truth table মনে করুন: A → B মিথ্যা শুধু তখন, যখন A সত্য আর B মিথ্যা। A মিথ্যা হলেই A → B সত্য (একে vacuous truth বলে)। ∃ খোঁজে একটিমাত্র সত্য উদাহরণ; সাবান চাল নয়, তাই Rice(Soap1) → … সত্য, আর ∃ খুশি। নিচের কোড বাজারঘরের জগতে চারটি রূপই যাচাই করে। ∀ মানে domain-এর সব বস্তুর ওপর loop আর all, ∃ মানে loop আর any. A → B লেখা হয়েছে not A or B হিসেবে। এই ব্লকে শুধু Python-এর মৌলিক অংশ; প্রথমবার চালালে ব্রাউজারে Python নামতে কয়েক সেকেন্ড লাগতে পারে।
customers = {"rahim", "karim", "mitu", "sumi"}
category = {"miniket": "rice", "chinigura": "rice", "mosur": "lentil",
"soyoil": "oil", "soap1": "toiletry"}
price = {"miniket": 78, "chinigura": 135, "mosur": 110, # taka, made up
"soyoil": 185, "soap1": 45}
ordered = {("rahim", "miniket"), ("rahim", "mosur"), ("karim", "chinigura"),
("karim", "mosur"), ("karim", "soap1"), ("mitu", "soyoil"),
("sumi", "miniket"), ("sumi", "soyoil")}
D = sorted(customers | set(category)) # domain: every object in this world
Customer = lambda x: x in customers
Rice = lambda x: category.get(x) == "rice"
Lentil = lambda x: category.get(x) == "lentil"
Ordered = lambda x, y: (x, y) in ordered
forall = lambda f: all(f(x) for x in D) # ∀ = loop, every object must pass
exists = lambda f: any(f(x) for x in D) # ∃ = loop, one object is enough
print("domain size:", len(D))
# "Every rice costs more than 60 taka" (A → B is written as: not A or B)
print("good ∀x (Rice(x) → price(x) > 60):", forall(lambda x: not Rice(x) or price[x] > 60))
print("bad ∀x (Rice(x) ∧ price(x) > 60):", forall(lambda x: Rice(x) and price[x] > 60))
# "Mitu ordered some rice"
print("good ∃x (Rice(x) ∧ Ordered(mitu, x)):", exists(lambda x: Rice(x) and Ordered("mitu", x)))
print("bad ∃x (Rice(x) → Ordered(mitu, x)):", exists(lambda x: not Rice(x) or Ordered("mitu", x)))
# "Every customer who ordered rice also ordered lentil"
rice_then_lentil = lambda x: (not (Customer(x) and exists(lambda y: Rice(y) and Ordered(x, y)))
or exists(lambda z: Lentil(z) and Ordered(x, z)))
print("∀x (Customer(x) ∧ ∃y(Rice(y) ∧ Ordered(x,y)) → ∃z(Lentil(z) ∧ Ordered(x,z))):",
forall(rice_then_lentil))
print("counterexample:", [x for x in D if not rice_then_lentil(x)])আসল আউটপুট:
domain size: 9
good ∀x (Rice(x) → price(x) > 60): True
bad ∀x (Rice(x) ∧ price(x) > 60): False
good ∃x (Rice(x) ∧ Ordered(mitu, x)): False
bad ∃x (Rice(x) → Ordered(mitu, x)): True
∀x (Customer(x) ∧ ∃y(Rice(y) ∧ Ordered(x,y)) → ∃z(Lentil(z) ∧ Ordered(x,z))): False
counterexample: ['sumi']
দুই জোড়াতেই ভুল রূপটি উল্টো সত্যমান দিয়েছে। ∀-এর সঙ্গে ∧ লিখলে domain-এর প্রথম অ-চাল বস্তুতেই (chinigura-র পরে karim) all মিথ্যা পায়; ঠিক রূপে গ্রাহক আর অন্য পণ্য “শর্ত মেলে না” বলে বাদ পড়েন, আর দুটি চালই ৬০ টাকার বেশি। ∃-এর সঙ্গে → লিখলে প্রথম অ-চাল বস্তুই any-কে সত্য বানায়, অথচ মিতু আসলে শুধু সয়াবিন তেল নিয়েছেন। শেষ লাইনটি অংশ ১-এর নিয়ম: মিথ্যা, আর কারণ (counterexample) সুমি। খেয়াল করুন, মিতু চাল নেননি বলে তাঁর জন্য শর্তটি vacuously সত্য, তিনি counterexample নন। আরেকটি খুঁটিনাটি: price[x] শুধু পণ্যের জন্য আছে; not Rice(x) or … আর Rice(x) and … short-circuit করে বলে গ্রাহকের দাম কখনো খোঁজা হয় না। খাঁটি FOL-এ function সব বস্তুর জন্য সংজ্ঞায়িত ধরা হয়; কোডে এটি একটি বাস্তব সমঝোতা।
৪ · Nested quantifier আর সমতা — ক্রম বদলালে অর্থ বদলায়
একাধিক quantifier একসঙ্গে বসলে তাকে nested quantifier বলে। একই ধরনের দুটি পাশাপাশি থাকলে ক্রম বদলানো যায়: ∀x∀y মানে ∀y∀x, ∃x∃y মানে ∃y∃x. কিন্তু ∀ আর ∃ মিশলে ক্রমই অর্থ ঠিক করে:
- ∀x ∃y Ordered(x, y): প্রত্যেক গ্রাহকের জন্য নিজের একটি পণ্য আছে যা তিনি কিনেছেন। y-টি x অনুযায়ী আলাদা হতে পারে।
- ∃y ∀x Ordered(x, y): এমন একটি পণ্য আছে যা প্রত্যেক গ্রাহক কিনেছেন। একটি y সবার জন্য।
“প্রত্যেক ছাত্রের একজন প্রিয় শিক্ষক আছেন” আর “একজন শিক্ষক আছেন যিনি প্রত্যেক ছাত্রের প্রিয়” এক কথা নয়। প্রথমটি প্রায় সব স্কুলে সত্য, দ্বিতীয়টি খুব কম জায়গায়। দ্বিতীয়টি সত্য হলে প্রথমটি অবশ্যই সত্য (∃y∀x থেকে ∀x∃y আসে), কিন্তু উল্টোটা নয়।
বাংলা বা ইংরেজি বাক্যে এই পার্থক্য প্রায়ই লুকিয়ে থাকে। “প্রত্যেক গ্রাহক একটি পণ্য অর্ডার করেছেন” দুইভাবেই পড়া যায়; এটি quantifier scope-এর অস্পষ্টতা, যা ভাষাবিজ্ঞানেও পরিচিত সমস্যা। FOL-এ লিখতে গেলেই আপনাকে বেছে নিতে হয়, আর এটাই FOL-এর একটি বড় উপকার: অস্পষ্টতা ধরা পড়ে। নিচের stepper বাজারঘরের অর্ডারের সারণিতে দুটি ক্রম loop দিয়ে যাচাই করে। গ্রাহক আর পণ্য বর্ণানুক্রমে: karim, mitu, rahim, sumi; chinigura, miniket, mosur, soap1, soyoil. “look-up” মানে একবার সারণিতে দেখা, Ordered(x, y) আছে কি না।
1for x in customers: # ∀x2 find y: Ordered(x, y) # ∃y3result: True if all found4for y in products: # ∃y5 all x: Ordered(x, y)? # ∀x6result: True if one y works
| karim | |
| mitu | |
| rahim | |
| sumi | |
| look-up |
- x = karim. ∃y খোঁজে প্রথম পণ্য থেকে: chinigura, আর করিম সেটি কিনেছেন। একটি পাওয়াই যথেষ্ট, তাই এক look-up-এ করিম পাস।
- x = mitu. chinigura, miniket, mosur, soap1 চারটিতেই ✗; পঞ্চম soyoil-এ ✓. ∃y-কে কখনো শেষ পর্যন্ত যেতে হয়; মোট look-up এখন ৬.
- x = rahim. chinigura ✗, miniket ✓. দুই look-up, মোট ৮.
- x = sumi, ফল True. chinigura ✗, miniket ✓. চারজনেরই নিজের একটি পণ্য পাওয়া গেল (করিম chinigura, মিতু soyoil, রহিম আর সুমি miniket), তাই ∀x∃y Ordered(x, y) সত্য, ১০টি look-up-এ।
- এবার উল্টো ক্রম: y = chinigura. এখন একটি পণ্য ধরে দেখা হয় সব গ্রাহক কিনেছেন কি না। করিম ✓, মিতু ✗. ∀x একটি ব্যর্থতাতেই থামে; বাকি দুজনকে দেখার দরকার নেই। look-up ২.
- y = miniket. প্রথম গ্রাহক করিমই miniket কেনেননি। এক look-up-এ বাদ, মোট ৩.
- y = mosur, তারপর soap1. দুটিতেই করিম ✓ কিন্তু মিতু ✗. প্রতিটিতে দুই look-up, মোট ৭.
- y = soyoil, ফল False. করিম সয়াবিন তেল কেনেননি, এক look-up. কোনো পণ্যই সব গ্রাহকের তালিকায় নেই, তাই ∃y∀x Ordered(x, y) মিথ্যা, ৮টি look-up-এ। একই সারণি, একই predicate, শুধু quantifier-এর ক্রম বদলে উল্টো ফল।
সমতা (=) দিয়ে গোনা আর সীমা টানা
FOL-এ একটি বিশেষ predicate আছে: সমতা, t1 = t2, মানে দুটি term একই বস্তুকে বোঝায়। এটি দিয়েই “অন্তত দুটি”, “ঠিক একটি” আর “শুধু” লেখা যায়:
- অন্তত দুটি আলাদা চাল: ∃x ∃y (Rice(x) ∧ Rice(y) ∧ ¬(x = y)). ¬(x = y) না লিখলে x আর y একই মিনিকেট হতে পারত, তখন “অন্তত একটি” হয়ে যেত।
- শুধু করিম সাবান কিনেছেন: Ordered(Karim, Soap1) ∧ ∀x (Ordered(x, Soap1) → x = Karim). প্রথম অংশ বলে করিম কিনেছেন; দ্বিতীয় অংশ বলে আর যে-ই কিনুক, সে করিমই।
- ঠিক একজন গ্রাহক সাবান কিনেছেন: ∃x (Customer(x) ∧ Ordered(x, Soap1) ∧ ∀y (Customer(y) ∧ Ordered(y, Soap1) → y = x)).
একটি সূক্ষ্ম কথা: FOL নিজে ধরে নেয় না যে আলাদা নামের constant আলাদা বস্তু। Miniket আর Chinigura একই বস্তুর দুই নাম হতে পারে, যদি না আপনি ¬(Miniket = Chinigura) লিখে দেন। Database আর Prolog এর উল্টোটা ধরে নেয়; একে বলে unique names assumption. নিচের কোড অংশ ৪-এর দুটি ক্রম আর সমতার তিনটি বাক্য একই জগতে যাচাই করে।
customers = ["karim", "mitu", "rahim", "sumi"]
products = ["chinigura", "miniket", "mosur", "soap1", "soyoil"]
rice = {"miniket", "chinigura"}
ordered = {("rahim", "miniket"), ("rahim", "mosur"), ("karim", "chinigura"),
("karim", "mosur"), ("karim", "soap1"), ("mitu", "soyoil"),
("sumi", "miniket"), ("sumi", "soyoil")}
checks = 0
def O(x, y): # Ordered(x, y), counting each look-up
global checks
checks += 1
return (x, y) in ordered
# ∀x ∃y Ordered(x, y): every customer ordered at least one product
checks = 0
a = all(any(O(x, y) for y in products) for x in customers)
print("∀x∃y Ordered(x,y):", a, "| look-ups:", checks)
for x in customers:
print(" ", x, "->", [y for y in products if (x, y) in ordered][0])
# ∃y ∀x Ordered(x, y): one product that every customer ordered
checks = 0
b = any(all(O(x, y) for x in customers) for y in products)
print("∃y∀x Ordered(x,y):", b, "| look-ups:", checks)
# equality: "at least two different rice products"
print("∃x∃y (Rice(x) ∧ Rice(y) ∧ x ≠ y):",
any(x != y for x in rice for y in rice))
# "only karim ordered soap1": ∀x (Ordered(x, soap1) → x = karim)
print("∀x (Ordered(x, soap1) → x = karim):",
all(x == "karim" for x in customers if (x, "soap1") in ordered))
# "no customer ordered both rice and oil": ¬∃x (... ∧ ...)
both = [x for x in customers if any((x, r) in ordered for r in rice)
and (x, "soyoil") in ordered]
print("¬∃x (OrderedRice(x) ∧ Ordered(x, soyoil)):", not both, both)আসল আউটপুট:
∀x∃y Ordered(x,y): True | look-ups: 10
karim -> chinigura
mitu -> soyoil
rahim -> miniket
sumi -> miniket
∃y∀x Ordered(x,y): False | look-ups: 8
∃x∃y (Rice(x) ∧ Rice(y) ∧ x ≠ y): True
∀x (Ordered(x, soap1) → x = karim): True
¬∃x (OrderedRice(x) ∧ Ordered(x, soyoil)): False ['sumi']
stepper-এর সংখ্যাই এসেছে: ∀x∃y সত্য ১০টি look-up-এ, ∃y∀x মিথ্যা ৮টিতে। প্রতিটি গ্রাহকের পাশে তাঁর “সাক্ষী” পণ্যটি ছাপা হয়েছে; ∃-এর প্রমাণ মানে একটি সাক্ষী দেখানো। all আর any প্রথম সিদ্ধান্তেই থামে বলে look-up সারণির ২০টি ঘরের চেয়ে কম। সমতার বাক্যে x ≠ y শর্তটিই “দুটি আলাদা” নিশ্চিত করে। শেষ লাইনে “কোনো গ্রাহক চাল আর তেল দুটোই কেনেননি” মিথ্যা, আর তালিকা বলে কেন: সুমি। ¬∃ যাচাই করা মানে উদাহরণ খোঁজা, পেলে বাক্য মিথ্যা। এখানে OrderedRice শুধু পড়ার সুবিধার নাম; পুরো রূপ ∃y (Rice(y) ∧ Ordered(x, y)).
৫ · বাংলা বাক্য থেকে FOL, আর FOL থেকে বাংলা
অনুবাদের একটি কাজের নিয়ম: (১) কাদের নিয়ে কথা, ঠিক করুন: “সব”, “প্রত্যেক”, “যে … সে” মানে ∀; “কেউ”, “কিছু”, “একটি আছে” মানে ∃; (২) ∀-এর ভেতরে শর্ত → ফল, ∃-এর ভেতরে গুণ ∧ গুণ; (৩) “না”, “কেউ না” এলে ¬ কোথায় বসছে, দেখুন; (৪) শেষে প্রতিটি variable bound কি না, মিলিয়ে নিন। নিচের stepper অংশ ১-এর নিয়মটি এই ধাপে বানায়।
1who? every customer -> ∀x2if: x ordered some rice3then: x ordered some lentil4join: ∀x (if → then)5check: all variables bound6test in the shop world
| বাক্যাংশ | |
| FOL টুকরো |
- কাদের নিয়ে কথা? “যে গ্রাহক … তিনি” মানে শর্ত মানে এমন প্রত্যেক গ্রাহক। তাই বাইরে ∀x, আর x যে গ্রাহক, সেটি শর্তের অংশ: Customer(x).
- শর্ত: “চাল অর্ডার করেছেন”। কোন চাল, বলা নেই; যেকোনো একটি হলেই চলে। তাই ∃y, আর y-এর দুটি গুণ একসঙ্গে: চাল এবং x তা অর্ডার করেছেন। ∃-এর সঙ্গে ∧.
- ফল: “ডালও অর্ডার করেছেন”। একইভাবে নতুন variable z দিয়ে ∃z (Lentil(z) ∧ Ordered(x, z)). y আবার ব্যবহার করলেও অর্থ ঠিক থাকত, কারণ আগের ∃y-এর scope শেষ; তবু আলাদা নাম পড়তে সহজ।
- জোড়া লাগানো। ∀-এর ভেতরে শর্ত → ফল। এখানে ∧ লিখলে মানে হতো “সবকিছুই গ্রাহক, চাল কিনেছেন আর ডাল কিনেছেন”, যা অংশ ৩-এর ভুল।
- Scope যাচাই। x বাইরের ∀-এর আওতায়, y আর z নিজ নিজ ∃-এর আওতায়। কোনো free variable নেই, তাই এটি পূর্ণ বাক্য, যার সত্য-মিথ্যা একটি model-এ ঠিক হয়।
- জগতে যাচাই। রহিম (মিনিকেট, মসুর) ✓, করিম (চিনিগুঁড়া, মসুর) ✓, মিতু চাল নেননি, তাই শর্ত মিথ্যা আর বাক্যটি তাঁর জন্য সত্য, সুমি (মিনিকেট, তেল) ✗. একটি ✗-ই যথেষ্ট: বাক্যটি এই জগতে মিথ্যা, অংশ ৩-এর কোডের ফলের মতোই।
বারোটি বাক্য, বাজারঘরের জগতে যাচাইসহ
শেষ কলামের সত্যমান অংশ ২-এর জগৎ থেকে একটি ছোট Python script দিয়ে হিসাব করা (অংশ ৩-৪-এর মতো loop). Grocery মানে চাল, ডাল বা তেল; Product আর Customer নিজেদের সেট।
| # | বাংলা বাক্য | FOL | এই জগতে |
|---|---|---|---|
| ১ | রহিম মিনিকেট অর্ডার করেছেন, আর মিনিকেট একটি চাল। | Ordered(Rahim, Miniket) ∧ Rice(Miniket) | সত্য |
| ২ | সব চালই মুদি পণ্য। | ∀x (Rice(x) → Grocery(x)) | সত্য |
| ৩ | কোনো একজন গ্রাহক সাবান অর্ডার করেছেন। | ∃x (Customer(x) ∧ Ordered(x, Soap1)) | সত্য |
| ৪ | কোনো চালের দাম ৬০ টাকার কম নয়। | ¬∃x (Rice(x) ∧ price(x) < 60) ≡ ∀x (Rice(x) → ¬(price(x) < 60)) | সত্য |
| ৫ | কোনো গ্রাহক সব পণ্য অর্ডার করেননি। | ¬∃x (Customer(x) ∧ ∀y (Product(y) → Ordered(x, y))) | সত্য |
| ৬ | প্রত্যেক গ্রাহক অন্তত একটি পণ্য অর্ডার করেছেন। | ∀x (Customer(x) → ∃y (Product(y) ∧ Ordered(x, y))) | সত্য |
| ৭ | এমন একটি পণ্য আছে যা সব গ্রাহক অর্ডার করেছেন। | ∃y (Product(y) ∧ ∀x (Customer(x) → Ordered(x, y))) | মিথ্যা |
| ৮ | শুধু করিমই সাবান অর্ডার করেছেন। | Ordered(Karim, Soap1) ∧ ∀x (Ordered(x, Soap1) → x = Karim) | সত্য |
| ৯ | অন্তত দুটি আলাদা চাল আছে। | ∃x ∃y (Rice(x) ∧ Rice(y) ∧ ¬(x = y)) | সত্য |
| ১০ | ঠিক একজন গ্রাহক সাবান অর্ডার করেছেন। | ∃x (Customer(x) ∧ Ordered(x, Soap1) ∧ ∀y (Customer(y) ∧ Ordered(y, Soap1) → y = x)) | সত্য |
| ১১ | রহিম আর সুমির রাইডার একই জন। | rider(Rahim) = rider(Sumi) | মিথ্যা (জামাল ≠ কামাল) |
| ১২ | যে গ্রাহক চাল অর্ডার করেছেন, তিনি ডালও অর্ডার করেছেন। | ∀x (Customer(x) ∧ ∃y (Rice(y) ∧ Ordered(x, y)) → ∃z (Lentil(z) ∧ Ordered(x, z))) | মিথ্যা (সুমি) |
তিনটি কঠিন রূপ আলাদা করে দেখুন। “কেউ না” / “কোনো … নয়” (৪, ৫): ¬∃ দিয়ে শুরু করা সবচেয়ে নিরাপদ, তারপর চাইলে ¬∃x P ≡ ∀x ¬P দিয়ে বদলানো। ভুল প্রায়ই হয় ¬ ভুল জায়গায় বসিয়ে: ∃x (Rice(x) ∧ ¬(price(x) < 60)) মানে “অন্তত একটি চাল ৬০-এর কম নয়”, অনেক দুর্বল কথা। “শুধু” (৮): দুটি অংশ লাগে, কেউ একজন করেছেন আর অন্য কেউ করেননি; দ্বিতীয় অংশটি → আর = দিয়ে। “অন্তত দুটি” আর “ঠিক একটি” (৯, ১০): সমতা ছাড়া গোনা যায় না। “অন্তত তিনটি” লিখতে তিনটি variable আর তিনটি জোড়া ≠ লাগে; সংখ্যা বড় হলে বাক্যও বড় হয়, কারণ FOL-এ সরাসরি “গোনা”র চিহ্ন নেই।
FOL থেকে বাংলায় পড়া উল্টো দিক থেকে একই নিয়ম। ∀x (Product(x) → ∃y (Customer(y) ∧ Ordered(y, x))) পড়ুন: “প্রত্যেক পণ্যের জন্য অন্তত একজন গ্রাহক আছেন যিনি সেটি অর্ডার করেছেন”, মানে “কোনো পণ্য অবিক্রীত নেই”। এই জগতে সত্য: প্রতিটি পণ্যের পাশে অন্তত একটি রেখা আছে (ছবিটি দেখুন)। আবার ∀x ∀y (Ordered(x, Soap1) ∧ Ordered(y, Soap1) → x = y) মানে “বড়জোর একজন সাবান কিনেছেন”; “ঠিক একজন” নয়, কারণ কেউ না কিনলেও এটি সত্য।
৬ · Model, interpretation আর entailment — সহজ ভাষায়
অংশ ২-এর ছবিটি একটি model. আরও গুছিয়ে বললে, FOL-এর একটি model দুটি জিনিস: একটি অশূন্য domain (বস্তুর সেট), আর একটি interpretation, যা বলে দেয় প্রতিটি constant কোন বস্তু, প্রতিটি predicate কোন কোন বস্তু-তালিকার জন্য সত্য, আর প্রতিটি function কোন বস্তুকে কোন বস্তুতে নেয়। এরপর যেকোনো বাক্যের সত্য-মিথ্যা যান্ত্রিকভাবে ঠিক হয়: atomic sentence সারণি দেখে, connective পর্ব ৪৫-এর truth table দিয়ে, ∀ আর ∃ domain-এর ওপর loop দিয়ে। অংশ ৩ আর ৪-এর কোড ঠিক এটাই করেছে।
কিন্তু একটি জ্ঞানভান্ডার, মানে knowledge base (KB), সাধারণত একটি নির্দিষ্ট জগৎকে পুরো বর্ণনা করে না; কিছু নিয়ম আর তথ্য দেয় মাত্র। পর্ব ৪৫-এর মতোই, KB ⊨ α (পড়ুন “KB entails α”; ⊨ চিহ্নের মানে entailment) মানে: যে যে model-এ KB-র সব বাক্য সত্য, তার প্রতিটিতে α-ও সত্য। একটি model-এ সত্য হওয়া এক জিনিস, KB থেকে অবশ্যম্ভাবী হওয়া আরেক জিনিস। বাক্য ১২ বাজারঘরের এই জগতে মিথ্যা, কিন্তু একটি KB, যাতে শুধু লেখা “মিনিকেট চাল” আর “সুমি মিনিকেট কিনেছেন”, তা থেকে ১২-এর অস্বীকারও entail হয় না, কারণ এমন model-ও আছে যেখানে সুমি ডালও কিনেছেন।
Propositional logic-এ model গোনা যায়, FOL-এ যায় না। n-টি প্রতীকে model ঠিক 2nটি; truth table সব দেখে entailment যাচাই করে। FOL-এ domain যেকোনো আকারের হতে পারে, অসীমও (যেমন সব স্বাভাবিক সংখ্যা), আর interpretation-ও অসংখ্য। তাই “সব model দেখে নেওয়া” চলে না; entailment প্রমাণ করতে লাগে inference rule, যা অংশ ৭-এর বিষয়। অংশ ৩-এর Python কোড শুধু একটি ছোট, সীমিত model-এ সত্য যাচাই করে, যাকে model checking বলে; এটি entailment-এর প্রমাণ নয়।
Database-এর সঙ্গে মিলটি এখানে স্পষ্ট: টেবিলের একটি সেট আসলে একটি সীমিত model, প্রতিটি টেবিল একটি predicate-এর সত্য সারি, আর SQL query সেই model-এ একটি সূত্র যাচাই (অংশ ৯).
৭ · FOL-এ inference — instantiation, unification আর generalised modus ponens
ধরুন KB-তে আছে: ∀x (Rice(x) → Grocery(x)) আর Rice(Miniket). মানুষ সঙ্গে সঙ্গে বলেন “তাহলে মিনিকেট মুদি পণ্য”। এই লাফটি যন্ত্রকে শেখানোর তিনটি ধাপ আছে।
দুটি instantiation নিয়ম
- Universal instantiation (UI): ∀x α থেকে x-এর জায়গায় যেকোনো ground term (variable ছাড়া term) বসিয়ে নতুন বাক্য পাওয়া যায়। ∀x (Rice(x) → Grocery(x)) থেকে Rice(Miniket) → Grocery(Miniket), আবার Rice(Rahim) → Grocery(Rahim), এমনকি Rice(rider(Sumi)) → Grocery(rider(Sumi)). সব সত্য, কারণ “সবার জন্য” সত্য কথা প্রত্যেকের জন্য সত্য।
- Existential instantiation (EI): ∃x α থেকে x-এর জায়গায় একটি নতুন constant বসানো, যা KB-তে আর কোথাও নেই। ∃x (Customer(x) ∧ Ordered(x, Soap1)) থেকে Customer(C1) ∧ Ordered(C1, Soap1). C1-কে বলে Skolem constant: “যে-ই হোন, তাঁকে আপাতত C1 ডাকি”। পুরোনো নাম (যেমন Karim) বসানো চলবে না, কারণ KB জানে না সেই মানুষটি করিমই। EI একবারই প্রয়োগ হয়, আর ∀-এর ভেতরে থাকা ∃-এর জন্য constant নয়, function লাগে (∀x∃y Ordered(x, y) থেকে ∀x Ordered(x, F(x))), কারণ y প্রতিটি x-এর জন্য আলাদা হতে পারে।
Propositionalisation আর তার বিস্ফোরণ
একটি সরল কৌশল: সব ∀ বাক্যে সম্ভাব্য সব ground term বসিয়ে দিন, তাহলে variable আর থাকে না, আর পুরো KB পর্ব ৪৫-এর propositional KB হয়ে যায়; সেখানে resolution বা SAT solver চালানো যায়। একে বলে propositionalisation. সমস্যা আকারে, এক ধরনের combinatorial explosion. অংশ ৮-এর Prolog KB-তে ১৫টি constant আছে (৪ গ্রাহক, ৫ পণ্য, ৬টি category). তিনটি variable-এর একটি নিয়ম, যেমন bought_from(U, C) :- ordered(U, P), in_cat(P, C), থেকে 153 = ৩,৩৭৫টি ground বাক্য আসে, যার প্রায় সবই অর্থহীন (“সাবান মিনিকেট থেকে চাল কিনেছে”)। আর একটি function প্রতীক থাকলেই ground term অসীম: Rahim, rider(Rahim), rider(rider(Rahim)), … তখন সব বসানো অসম্ভব।
এই অসীমতার মধ্যেও একটি আশার কথা Jacques Herbrand-এর ১৯৩০ সালের কাজের সঙ্গে যুক্ত: কোনো বাক্য যদি KB থেকে entail হয়, তবে সীমিত সংখ্যক ground term বসানোর মধ্যেই তার প্রমাণ মেলে। তাই গভীরতা ১, ২, ৩ করে বাড়িয়ে খুঁজলে (পর্ব ৪০-এর iterative deepening-এর মতো) entail হওয়া বাক্যের প্রমাণ একসময় পাওয়া যায়। কিন্তু entail না হলে খোঁজা কখনো শেষ নাও হতে পারে; অংশ ৯-এ এর নাম semi-decidability.
Unification — সব বসানোর বদলে ঠিকটি খোঁজা
বুদ্ধিমান পথ: সব ground term না বসিয়ে শুধু সেই substitution খোঁজা, যা দুটি বাক্যকে হুবহু এক করে। Substitution θ মানে variable থেকে term-এর একটি তালিকা, যেমন {x/Miniket} (“x-এর জায়গায় Miniket”). Unification হলো দুটি atomic sentence p আর q-এর জন্য এমন θ খোঁজা যাতে pθ = qθ. যেমন Ordered(Rahim, x) আর Ordered(y, Miniket) মেলে θ = {x/Miniket, y/Rahim} দিয়ে। Ordered(Rahim, x) আর Ordered(Karim, y) মেলে না, কারণ Rahim আর Karim দুটি আলাদা constant, কোনো substitution তাদের এক করতে পারে না।
প্রায়ই একাধিক unifier থাকে। Ordered(x, y) আর Ordered(z, Miniket) মেলে {x/z, y/Miniket} দিয়ে, আবার {x/Rahim, z/Rahim, y/Miniket} দিয়েও। দ্বিতীয়টি অকারণে x-কে রহিম বানিয়ে ফেলেছে। প্রথমটি most general unifier (MGU): সবচেয়ে কম প্রতিশ্রুতি দেয়, আর অন্য যেকোনো unifier তার ওপর আরও substitution চাপিয়ে পাওয়া যায়। প্রতীকের নাম বাদ দিলে MGU একটিই। নিচের stepper একটি একটু জটিল জোড়া ধাপে ধাপে মেলায়; এখানে P আর f যেকোনো predicate আর function-এর নাম, আর Prolog-এর মতো বড় হাতের অক্ষর variable.
1same functor and arity?2for each argument pair:3 apply θ, then compare4 var? occurs check, bind5return θ (the MGU)6occurs -> FAIL
| জোড়া | |
| θ পরে | |
| ফল |
- বাইরের খোলস। দুই দিকেই predicate P আর তিনটি argument. নাম বা সংখ্যা আলাদা হলে এখানেই FAIL. θ এখনো খালি।
- প্রথম argument: X ≐ karim. X একটি variable আর karim-এর ভেতরে X নেই, তাই X/karim যোগ হলো।
- দ্বিতীয় argument: f(X) ≐ Y. আগে θ বসাতে হয়: f(X) হয়ে যায় f(karim). Y variable; f(karim)-এর ভেতরে Y নেই (occurs check পাস), তাই Y/f(karim).
- তৃতীয় argument: Z ≐ Y. Y-এর মান আগেই f(karim), তাই আসলে Z ≐ f(karim). Z/f(karim) যোগ হলো। এই “আগের বাঁধন অনুসরণ করা”-ই কোডের
walk. - MGU পাওয়া গেল। θ = {X/karim, Y/f(karim), Z/f(karim)} দুই দিকে বসালে দুটিই P(karim, f(karim), f(karim)). কোনো variable অকারণে বাঁধা হয়নি, তাই এটি সবচেয়ে সাধারণ।
- Occurs check-এর উদাহরণ। আলাদা জোড়া P(X, f(X)) ≐ P(Y, Y): প্রথমে X/Y, তারপর দ্বিতীয় argument হয় f(Y) ≐ Y. Y-কে f(Y)-এ বাঁধলে Y = f(f(f(…))), একটি অসীম term. Occurs check দেখে Y নিজেই f(Y)-এর ভেতরে আছে, তাই FAIL.
এবার একই অ্যালগরিদম Python-এ। Term হলো একটি string (বড় হাতের অক্ষরে শুরু হলে variable, নইলে constant) অথবা একটি tuple, যার প্রথম উপাদান functor বা predicate-এর নাম। unify substitution-কে একটি dict হিসেবে নিয়ে নতুন dict ফেরত দেয়, ব্যর্থ হলে None. শেষ জোড়াটি সরাসরি occurs check পরীক্ষা করে।
def is_var(t): return isinstance(t, str) and t[:1].isupper() # X, Y, Z...
def walk(t, s): # follow variable bindings in s
while is_var(t) and t in s:
t = s[t]
return t
def occurs(v, t, s): # does variable v appear inside term t?
t = walk(t, s)
return t == v or (isinstance(t, tuple) and any(occurs(v, a, s) for a in t[1:]))
def unify(a, b, s):
if s is None: return None
a, b = walk(a, s), walk(b, s)
if a == b: return s
if is_var(a): return None if occurs(a, b, s) else {**s, a: b}
if is_var(b): return unify(b, a, s)
if isinstance(a, tuple) and isinstance(b, tuple) and a[0] == b[0] and len(a) == len(b):
for x, y in zip(a[1:], b[1:]):
s = unify(x, y, s)
return s
return None # different constants or functors
def full(t, s): # apply the substitution all the way down
t = walk(t, s)
return (t[0], *[full(a, s) for a in t[1:]]) if isinstance(t, tuple) else t
def show(t): return t if not isinstance(t, tuple) else f"{t[0]}({', '.join(map(show, t[1:]))})"
pairs = [(("Ordered", "rahim", "X"), ("Ordered", "Y", "miniket")),
(("Delivers", ("rider", "Y"), "Y"), ("Delivers", "X", "rahim")),
(("Ordered", "X", "Y"), ("Ordered", "Z", "miniket")),
(("Ordered", "rahim", "X"), ("Ordered", "karim", "Y")),
(("Ordered", "X", "X"), ("Ordered", "rahim", "miniket")),
("X", ("rider", "X"))]
for a, b in pairs:
s = unify(a, b, {})
out = "FAIL" if s is None else "{" + ", ".join(f"{v}/{show(full(v, s))}" for v in sorted(s)) + "}"
print(f"{show(a):22} ≐ {show(b):22} -> {out}")আসল আউটপুট:
Ordered(rahim, X) ≐ Ordered(Y, miniket) -> {X/miniket, Y/rahim}
Delivers(rider(Y), Y) ≐ Delivers(X, rahim) -> {X/rider(rahim), Y/rahim}
Ordered(X, Y) ≐ Ordered(Z, miniket) -> {X/Z, Y/miniket}
Ordered(rahim, X) ≐ Ordered(karim, Y) -> FAIL
Ordered(X, X) ≐ Ordered(rahim, miniket) -> FAIL
X ≐ rider(X) -> FAIL
দ্বিতীয় লাইন দেখায় function কীভাবে মেলে: প্রথম argument-এ X/rider(Y), তৃতীয়তে Y/rahim, আর full সব বাঁধন শেষ পর্যন্ত বসিয়ে X = rider(rahim) দেখায় (“রহিমের রাইডারই ডেলিভারি দেন”)। তৃতীয় লাইনটি MGU: X/Z, X-কে কোনো নির্দিষ্ট মানুষ বানানো হয়নি। পঞ্চম লাইনে X একবার rahim হয়ে গেলে দ্বিতীয় জায়গায় rahim ≐ miniket, দুটি আলাদা constant, তাই FAIL. শেষ লাইনে occurs check কাজ করেছে। এই কোড {**s, a: b} দিয়ে প্রতিবার নতুন dict বানায়, তাই ব্যর্থ শাখা আগের θ নষ্ট করে না; এটি পরে backtracking-এ কাজে লাগবে।
Generalised modus ponens
পর্ব ৪৫-এর modus ponens: P আর P → Q থেকে Q. FOL-এ তার সঙ্গে unification জুড়ে দিলে পাওয়া যায় generalised modus ponens (GMP): যদি KB-তে থাকে p1′, …, pn′ আর (p1 ∧ … ∧ pn → q), আর একটি θ থাকে যাতে প্রতিটি pi′θ = piθ, তবে qθ সিদ্ধান্ত। উদাহরণ:
নিয়ম: Ordered(x, y) ∧ Rice(y) → BoughtRice(x)
তথ্য: Ordered(Sumi, Miniket) Rice(Miniket)
θ = {x/Sumi, y/Miniket}
সিদ্ধান্ত: BoughtRice(Sumi)
একটি ধাপে UI, unification আর modus ponens একসঙ্গে, অথচ অপ্রয়োজনীয় কোনো ground বাক্য বানানো হয়নি। GMP Horn clause-এর (বড়জোর একটি ধনাত্মক literal; সাধারণত “শর্ত ∧ শর্ত → একটি ফল”) KB-তে sound আর complete: যা entail হয়, তা খুঁজে পাবে। GMP দুই দিকে চালানো যায়: তথ্য থেকে সামনে নতুন তথ্য বানানো (forward chaining) বা প্রশ্ন থেকে পেছনে শর্তে যাওয়া (backward chaining); দুটির বিস্তারিত তুলনা, conflict resolution আর expert system পর্ব ৪৭-এ। পূর্ণ FOL-এর জন্য (Horn নয় এমন বাক্যসহ) প্রমাণের complete পদ্ধতি হলো resolution-এর FOL রূপ, যা J. A. Robinson ১৯৬৫ সালে unification-সহ প্রকাশ করেন।
৮ · Prolog — FOL-এর একটি অংশ, সত্যিকারের যন্ত্র হিসেবে
Prolog (“programming in logic”) প্রোগ্রাম লেখে Horn clause দিয়ে, আর প্রশ্নের উত্তর খোঁজে backward chaining আর unification দিয়ে: লক্ষ্যটি কোন নিয়মের মাথার সঙ্গে মেলে, তা দেখা হয়; মিললে সেই নিয়মের শরীরের শর্তগুলো নতুন লক্ষ্য; বাঁ থেকে ডানে, গভীরে আগে (depth-first), ব্যর্থ হলে পিছিয়ে পরের নিয়ম। প্রথমে এর ছোট একটি রূপ নিজে লিখে দেখা যাক। নিচের কোডে category-র ধাপক্রম (চাল ⊂ মুদি ⊂ খাদ্য) parent/ancestor নিয়মের মতো দুটি নিয়মে লেখা: above মানে “সরাসরি বা কয়েক ধাপ ওপরে”।
FACTS = """is_a miniket rice | is_a chinigura rice | is_a mosur lentil
| is_a soyoil oil | sub rice grocery | sub lentil grocery | sub oil grocery
| sub grocery food | ordered sumi miniket | ordered mitu soyoil"""
KB = [(tuple(f.split()), []) for f in FACTS.replace("\n", " ").split("|")]
KB += [(("above", "S", "C"), [("sub", "S", "C")]),
(("above", "S", "C"), [("sub", "S", "M"), ("above", "M", "C")]),
(("in_cat", "P", "C"), [("is_a", "P", "C")]),
(("in_cat", "P", "C"), [("is_a", "P", "S"), ("above", "S", "C")]),
(("bought", "U", "C"), [("ordered", "U", "P"), ("in_cat", "P", "C")])]
var = lambda t: t[:1].isupper()
def walk(t, s):
while var(t) and t in s: t = s[t]
return t
def unify(a, b, s): # atoms are flat tuples of names here
if len(a) != len(b): return None
for x, y in zip(a, b):
x, y = walk(x, s), walk(y, s)
if x == y: continue
if var(x): s = {**s, x: y}
elif var(y): s = {**s, y: x}
else: return None
return s
tries = [0] # [rule tries so far]
def solve(goals, s): # backward chaining, depth-first, left to right
if not goals: yield s; return
for head, body in KB:
ren = lambda atom: tuple(f"{t}_{n}" if var(t) else t for t in atom)
tries[0] += 1; n = tries[0] # rename rule variables: S -> S_7
s2 = unify(goals[0], ren(head), s)
if s2 is not None:
yield from solve([ren(b) for b in body] + goals[1:], s2)
for q in [("above", "rice", "C"), ("in_cat", "P", "food"), ("bought", "U", "grocery")]:
tries[0] = 0
ans = [walk(q[2] if var(q[2]) else q[1], s) for s in solve([q], {})]
print(" ".join(q), "->", ans, "| rule tries:", tries[0])আসল আউটপুট:
above rice C -> ['grocery', 'food'] | rule tries: 135
in_cat P food -> ['miniket', 'chinigura', 'mosur', 'soyoil'] | rule tries: 585
bought U grocery -> ['sumi', 'mitu'] | rule tries: 390
চাল সরাসরি মুদি (প্রথম নিয়ম), আর মুদির ওপরে খাদ্য (দ্বিতীয় নিয়ম, recursion), তাই উত্তর grocery, food. খাদ্যের মধ্যে চারটি পণ্য; সাবান নেই, কারণ প্রসাধনী খাদ্যের নিচে নয়। তৃতীয় প্রশ্নে দুটি GMP-ধাপ একসঙ্গে: সুমি মিনিকেট কিনেছেন আর মিনিকেট মুদি, তাই সুমি; মিতুর তেলও মুদি। প্রতিবার নিয়ম ব্যবহারের আগে variable-এর নাম বদলানো হয় (S থেকে S_7); নইলে একই নিয়মের দুই ব্যবহারের S গুলিয়ে যেত। একে বলে standardizing apart. “rule tries” দেখায় এই সরল যন্ত্রটি প্রতিটি লক্ষ্যের জন্য ১৫টি clause-এর সবগুলো পরখ করে; আসল Prolog প্রথম argument দেখে বাছাই করে (indexing), তাই অনেক কম খোঁজে। এই কোডে occurs check নেই, আর সেটি ইচ্ছাকৃত: আসল Prolog-ও সাধারণত গতির জন্য এটি বাদ দেয়, যা নিচের run-এ দেখবেন।
SWI-Prolog-এ সত্যিকারের run
এবার একই জ্ঞান আসল Prolog-এ। লেখার সময়ে SWI-Prolog-এর সর্বশেষ stable সংস্করণ 10.0.2 (swi-prolog.org-এর download পাতা অনুযায়ী)। Windows-এ অফিসিয়াল installer নামিয়ে তার SHA-256 checksum পাতার সঙ্গে মিলিয়ে, install না করে 7-Zip দিয়ে একটি আলাদা ফোল্ডারে খুলে swipl চালানো হয়েছে; Linux আর macOS-এ সাধারণত package manager দিয়ে বসানো হয়। জ্ঞানভান্ডারের ফাইল (সব নাম আর তথ্য বানানো):
% facts: product -> its own category
is_a(miniket, rice). is_a(chinigura, rice).
is_a(mosur, lentil). is_a(soyoil, oil).
is_a(soap1, toiletry).
% facts: category -> parent category
sub(rice, grocery). sub(lentil, grocery). sub(oil, grocery).
sub(grocery, food).
% facts: who ordered what
ordered(rahim, miniket). ordered(rahim, mosur).
ordered(karim, chinigura). ordered(karim, mosur). ordered(karim, soap1).
ordered(mitu, soyoil).
ordered(sumi, miniket). ordered(sumi, soyoil).
% rules (Horn clauses)
above(S, C) :- sub(S, C).
above(S, C) :- sub(S, M), above(M, C).
in_cat(P, C) :- is_a(P, C).
in_cat(P, C) :- is_a(P, S), above(S, C).
bought_from(Cust, Cat) :- ordered(Cust, P), in_cat(P, Cat).
% "ordered rice but no lentil" (\+ = negation as failure)
rice_no_lentil(C) :- bought_from(C, rice), \+ bought_from(C, lentil).Prolog-এ :- মানে ← (“যদি”), কমা মানে ∧, আর প্রতিটি variable নিয়মের মাথায় নীরবে ∀. প্রশ্নগুলো একটি ফাইলে রেখে একটি ছোট driver (ask.pl, প্রতিটি প্রশ্ন পড়ে সব উত্তর ছাপায়) দিয়ে চালানো হয়েছে। সত্যিকারের run (SWI-Prolog 10.0.2, Windows 64-bit):
~/bazarghar-fol$ swipl ask.pl
SWI-Prolog 10.0.2
?- in_cat(mosur,food).
true.
?- findall(P,in_cat(P,grocery),Ps).
Ps = [miniket,chinigura,mosur,soyoil]
?- above(rice,C).
C = grocery
C = food
?- setof(C,bought_from(C,rice),Cs).
Cs = [karim,rahim,sumi]
?- rice_no_lentil(Who).
Who = sumi
?- bought_from(Who,toiletry).
Who = karim
?- forall(bought_from(C,rice),bought_from(C,lentil)).
false.
?- bought_from(mitu,rice).
false.
?- f(X,b)=f(a,Y).
X = a Y = b
?- unify_with_occurs_check(X,f(X)).
false.
?- X=f(X),cyclic_term(X).
X = @(S_1,[S_1=f(S_1)])
উত্তরগুলো Python-এর ছোট যন্ত্রের সঙ্গে মেলে, আর এবার পুরো অর্ডারের তালিকা আছে। setof চাল-ক্রেতাদের সাজানো তালিকা দিয়েছে: করিম, রহিম, সুমি। অংশ ১-এর নিয়মটি এখানে forall দিয়ে লেখা (“যত C চাল কিনেছেন, সবাই ডালও”), আর উত্তর false; rice_no_lentil বলে দেয় কে দায়ী: সুমি। শেষ তিনটি প্রশ্ন unification নিজেই: = সাধারণ unification, unify_with_occurs_check occurs check-সহ, তাই X ≐ f(X) ব্যর্থ। কিন্তু সাধারণ = occurs check ছাড়াই X-কে f(X)-এ বাঁধে আর একটি চক্রাকার term বানায়; SWI-Prolog এ ধরনের term সমর্থন করে, আর driver সেটিকে @(S_1,[S_1=f(S_1)]) রূপে ছাপিয়েছে, মানে “S_1, যেখানে S_1 = f(S_1)”. Prolog-এর সাধারণ toplevel একই প্রশ্নে সংক্ষেপে X = f(X). দেখায় (একই যন্ত্রে আলাদা run-এ দেখা)। এটি খাঁটি FOL-এর unification নয়; প্রমাণের কাজে Prolog ব্যবহার করলে occurs check চালু রাখার কথা ভাবতে হয় (SWI-Prolog-এ occurs_check নামের একটি flag আছে)।
\+ মানে ¬ নয়। Prolog-এর \+ G হলো negation as failure: “G প্রমাণ করা গেল না, তাই মিথ্যা ধরছি”। এটি closed-world assumption-এর ওপর দাঁড়ানো: KB-তে যা নেই, তা মিথ্যা। খাঁটি FOL-এ open world: KB-তে সুমির ডাল কেনার কথা না থাকলে তিনি কেনেননি, এমন সিদ্ধান্ত আসে না। দোকানের অর্ডার-খাতার মতো পূর্ণ তালিকায় closed world যুক্তিসংগত; রোগীর অসম্পূর্ণ ইতিহাসে নয়।
৯ · FOL-এর সীমা, আর আজ FOL-এর ধারণা কোথায় বেঁচে আছে
Semi-decidable: “হ্যাঁ” মিলবে, “না” নাও মিলতে পারে
FOL-এর জন্য complete প্রমাণ-পদ্ধতি আছে: যা entail হয়, তার প্রমাণ একসময় পাওয়া যায় (Gödel-এর completeness theorem, ১৯২৯–৩০)। কিন্তু Church আর Turing ১৯৩৬ সালের দিকে আলাদাভাবে দেখান, এমন কোনো সাধারণ অ্যালগরিদম নেই যা প্রতিটি FOL বাক্যের জন্য শেষ পর্যন্ত “entail হয়” বা “হয় না” বলে থামবে। তাই FOL-এ entailment semi-decidable: উত্তর “হ্যাঁ” হলে খোঁজা থামে, “না” হলে চিরকাল চলতে পারে। বাস্তবের theorem prover তাই সময়-সীমা দিয়ে চালানো হয়, আর “সময় শেষ” মানে “মিথ্যা” নয়। একই কারণে অনেক ব্যবস্থা FOL-এর এমন সীমিত অংশ বেছে নেয়, যেখানে উত্তর সবসময় থামে: function ছাড়া Horn clause (Datalog), বা নিচের description logic.
Description logic আর knowledge graph — পর্ব ৪৮-এর দিকে
Description logic হলো FOL-এর সাবধানে কাটা অংশ: শুধু এক-argument-এর predicate (concept, যেমন Rice) আর দুই-argument-এর predicate (role, যেমন ordered), আর সীমিত ধরনের quantifier. বিনিময়ে বেশিরভাগ প্রশ্নের উত্তর নিশ্চিতভাবে থামে। ওয়েবের ontology ভাষা OWL এর ওপর দাঁড়িয়ে। আর knowledge graph আসলে দুই-argument-এর atomic sentence-এর বিশাল একটি সংগ্রহ: (miniket, is_a, rice) ধরনের triple. এগুলো, RDF আর SPARQL দিয়ে প্রশ্ন, পর্ব ৪৮-এর বিষয় (Knowledge graph — বস্তু, সম্পর্ক আর প্রশ্ন)।
যেখানে FOL-এর ধারণা রোজ কাজ করে
Database আর SQL
SQL-এর WHERE EXISTS হলো ∃, NOT EXISTS দিয়ে লেখা “সবার জন্য” হলো ∀ (¬∃¬)। পার্থক্য: SQL ফলকে সেট নয়, bag (multiset) হিসেবে রাখে (একই সারি একাধিকবার থাকতে পারে), NULL-এ তিন-মানের logic চলে, আর closed world ধরে নেয়। দেখুন relational algebra আর relational calculus.
Type system
অনেক ভাষার type inference (যেমন Haskell বা OCaml-এর Hindley–Milner পদ্ধতি) ভেতরে এই পর্বের unification-ই চালায়: list[T] ≐ list[int] থেকে T = int. Python-এর type checker-ও generic মেলাতে একই ধরনের কাজ করে (type hint পর্ব)।
যাচাই (verification)
চিপের নকশা, protocol বা প্রোগ্রাম “সব ইনপুটে নিয়ম মানে” প্রমাণ করতে SMT solver আর proof assistant ব্যবহার হয়, যার ভাষা FOL বা তার চেয়েও সমৃদ্ধ logic. Program analysis-এর কিছু টুলে নিয়ম Datalog-এ লেখা হয়।
automated reasoningআরও গভীরে: FOL-এর resolution-এর মূল প্রবন্ধ J. A. Robinson-এর “A Machine-Oriented Logic Based on the Resolution Principle” (Journal of the ACM, ১৯৬৫), যেখানে unification আর MGU-র অ্যালগরিদম প্রথম স্পষ্টভাবে আসে। সরল unification খারাপতম ক্ষেত্রে exponential সময় নিতে পারে; Paterson ও Wegman (১৯৭৮) আর Martelli ও Montanari (১৯৮২) রৈখিক বা প্রায়-রৈখিক সময়ের পদ্ধতি দেন। Horn clause-এর “procedural” ব্যাখ্যা, যার ওপর Prolog দাঁড়িয়ে, সাধারণত Robert Kowalski-র ১৯৭৪ সালের কাজের সঙ্গে যুক্ত। “Second-order” logic quantifier বসাতে দেয় predicate-এর ওপরও (“সব গুণের জন্য…”), কিন্তু তখন complete প্রমাণ-পদ্ধতি আর থাকে না; “first-order” নামটি এই পার্থক্য থেকেই।
১০ · AI-এর যুগে — AI সহকারী দিয়ে বাক্যকে logic-এ লেখা
“এই নিয়মটা first-order logic-এ লিখে দাও” বা “এর Prolog কোড দাও” বললে AI সহকারী দ্রুত উত্তর দেয়, আর বেশিরভাগ সময় দেখতে ঠিক। কিন্তু logic-এ একটি চিহ্নের ভুলে পুরো অর্থ উল্টে যায়, আর ভুলটি চোখে পড়ে না। Large language model-এর উত্তর পরিসংখ্যানভিত্তিক অনুমান (পর্ব ৭০-এ বিস্তারিত); সে বাক্যটি “বোঝে” বলে ধরে নেওয়ার কারণ নেই। ভালো ভাগাভাগি: AI খসড়া লিখুক, আর আপনি একটি ছোট সীমিত জগতে (অংশ ৩-এর মতো) বা Prolog-এ চালিয়ে যাচাই করুন। এ ধরনের কাজে AI সহকারীরা প্রায়ই যা করে:
- ∀-এর সঙ্গে ∧, ∃-এর সঙ্গে →: অংশ ৩-এর দুটি ভুল; মানুষের মতোই, আর আত্মবিশ্বাসের সঙ্গে।
- Quantifier-এর ক্রম বেছে নেওয়া চুপচাপ: “প্রত্যেক গ্রাহক একটি পণ্য কিনেছেন”-কে ∃y∀x লেখা, অস্পষ্টতার কথা না বলে।
- “শুধু” আর “ঠিক একটি”-র অর্ধেক লেখা: সমতার অংশ বাদ দেওয়া, যাতে “অন্তত একটি” হয়ে যায়।
- Prolog-এর
\+-কে খাঁটি ¬ বলা: negation as failure আর closed world-এর কথা না বলা; বা occurs check নেই, তা না জানানো। - বানানো built-in বা পুরোনো আচরণ: অস্তিত্বহীন predicate বা অন্য Prolog সংস্করণের আচরণ SWI-Prolog-এর বলে চালানো।
- যাচাই ছাড়া “প্রমাণিত” বলা: একটি উদাহরণে সত্য দেখে entailment দাবি করা; অংশ ৬-এর পার্থক্য গুলিয়ে ফেলা।
প্রম্পটে চিহ্নের নিয়ম, অস্পষ্টতা নিয়ে প্রশ্ন আর যাচাইয়ের দাবি নিজেই লিখে দিন। নিচের প্রম্পটে কোনো আসল গ্রাহকের তথ্য নেই।
AI-এর লেখা FOL বা Prolog হাতে পেলে — যাচাইয়ের চেকলিস্ট
পরীক্ষা ও ইন্টারভিউতে: বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে।
- “সব চাল মুদি পণ্য” FOL-এ? ∀x (Rice(x) → Grocery(x)); ∧ দিলে মানে হয় “সবকিছুই চাল আর মুদি”।
- ∀x∃y Loves(x, y) আর ∃y∀x Loves(x, y)-এর পার্থক্য? প্রথমটিতে প্রত্যেকের নিজের কেউ, দ্বিতীয়টিতে একজন সবার; দ্বিতীয়টি থেকে প্রথমটি আসে, উল্টো নয়।
- Knows(John, x) আর Knows(y, Mother(y))-এর MGU? {y/John, x/Mother(John)}.
- Occurs check কেন? X ≐ f(X)-এর মতো জোড়া মেলালে অসীম term হয়; occurs check এমন unification ব্যর্থ করে। Prolog গতির জন্য সাধারণত এটি বাদ দেয়।
- EI-তে নতুন constant কেন? ∃ শুধু বলে কেউ আছেন; পুরোনো নাম বসালে অপ্রমাণিত দাবি হয়ে যায়।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Quantifier-এর চিহ্ন কোথা থেকে এল, আর “first-order” logic কীভাবে গণিত আর AI দুই জায়গাতেই মানদণ্ড হয়ে উঠল?
Aristotle-এর syllogism (“সব মানুষ মরণশীল, সক্রেটিস মানুষ, তাই সক্রেটিস মরণশীল”) দুই হাজার বছরের বেশি সময় যুক্তির প্রধান কাঠামো ছিল, কিন্তু তাতে “প্রত্যেক গ্রাহকের একটি পণ্য আছে”-র মতো nested quantifier স্বচ্ছন্দে লেখা যেত না। ১৮৭৯ সালে Gottlob Frege তাঁর “Begriffsschrift” বইয়ে variable আর quantifier-সহ একটি পূর্ণ logic-এর ভাষা দেন; প্রায় একই সময়ে Charles Sanders Peirce আলাদাভাবে quantifier-এর ধারণায় পৌঁছান বলে ইতিহাসবিদেরা উল্লেখ করেন। আজকের ∀ আর ∃ চিহ্ন পরে এসেছে: ∃ সাধারণত Peano-র সঙ্গে, আর উল্টো A (∀) Gentzen-এর ১৯৩০-এর দশকের লেখার সঙ্গে যুক্ত বলে ধরা হয়, যদিও চিহ্নের ইতিহাসে ছোটখাটো মতভেদ আছে।
Kurt Gödel ১৯২৯–৩০ সালে প্রমাণ করেন first-order logic complete: প্রতিটি valid বাক্যের একটি প্রমাণ আছে। পরের বছরের তাঁর বিখ্যাত incompleteness theorem এর সঙ্গে প্রায়ই গুলিয়ে ফেলা হয়, কিন্তু সেটি আলাদা কথা: পাটিগণিতের মতো যথেষ্ট সমৃদ্ধ তত্ত্ব-এর সব সত্য একটি নির্দিষ্ট axiom-সেট থেকে প্রমাণ করা যায় না। প্রমাণ-পদ্ধতি পূর্ণ, কিন্তু axiom দিয়ে সব সত্য ধরা যায় না।
AI-তে FOL এল ১৯৫০-এর শেষ থেকে, symbolic AI-এর মূল ভাষা হিসেবে। John McCarthy-র ১৯৫৮ সালের “Programs with Common Sense” প্রস্তাবে জ্ঞানকে logic-এর বাক্যে লেখার আর যন্ত্রকে তা থেকে সিদ্ধান্ত টানতে দেওয়ার স্বপ্ন ছিল। ১৯৬৫ সালে Robinson-এর resolution সেই স্বপ্নকে একটি যান্ত্রিক পদ্ধতি দেয়। FOL-এর আকর্ষণ ছিল তিনটি: সুনির্দিষ্ট অর্থ (model দিয়ে), যাচাই করা যায় এমন প্রমাণ, আর মানুষের পড়ার মতো নিয়ম।
মূল উপলব্ধি: FOL টিকে আছে কারণ এটি প্রকাশের ক্ষমতা আর ভালো গাণিতিক আচরণের (complete প্রমাণ-পদ্ধতি) মধ্যে একটি বিরল ভারসাম্য দেয়; আরও শক্তিশালী logic নিলে সেই ভারসাম্য ভাঙে।
প্র ০২Hilbert-এর Entscheidungsproblem কী ছিল, আর Church ও Turing-এর উত্তর আজকের AI reasoner-এর নকশায় কী প্রভাব ফেলে?
১৯২৮ সালের দিকে David Hilbert আর Wilhelm Ackermann একটি প্রশ্নকে স্পষ্ট রূপ দেন, যা “Entscheidungsproblem” (সিদ্ধান্তের সমস্যা) নামে পরিচিত: এমন কোনো যান্ত্রিক পদ্ধতি কি আছে, যা যেকোনো first-order বাক্য দেখে সীমিত সময়ে বলে দেবে বাক্যটি valid কি না? Gödel-এর completeness দেখিয়েছিল valid বাক্যের প্রমাণ আছে, তাই সব প্রমাণ একে একে তৈরি করলে valid বাক্যের ক্ষেত্রে একসময় থামা যায়। প্রশ্ন ছিল invalid বাক্যের ক্ষেত্রে।
১৯৩৬ সালে Alonzo Church (lambda calculus দিয়ে) আর প্রায় একই সময়ে Alan Turing (তাঁর কাল্পনিক যন্ত্র দিয়ে, প্রবন্ধটি ১৯৩৬–৩৭ সালে প্রকাশিত) আলাদাভাবে দেখান, এমন সাধারণ পদ্ধতি নেই। এর জন্য তাঁদের প্রথমে “যান্ত্রিক পদ্ধতি”-র গাণিতিক সংজ্ঞা দিতে হয়েছিল, যা থেকে computability তত্ত্বের জন্ম।
AI-এর জন্য এর মানে বাস্তব। পূর্ণ FOL-এর একটি theorem prover “সময় শেষ” বললে সেটি “মিথ্যা”র প্রমাণ নয়। তাই নকশাকারীরা তিনটি পথ নেন। এক, সময়-সীমা আর সতর্ক রিপোর্ট (“প্রমাণ পাওয়া যায়নি”, “অপ্রমাণযোগ্য” নয়)। দুই, decidable অংশ বেছে নেওয়া: Datalog (function নেই, তাই ground fact সীমিত), description logic-এর অনেক রূপ, propositional logic আর SAT (NP-complete, কিন্তু অন্তত থামে)। তিন, সমস্যার বিশেষ গঠন কাজে লাগানো, যেমন সীমিত domain-এ model checking (অংশ ৩-এর মতো)।
মূল উপলব্ধি: FOL-এর semi-decidability একটি মৌলিক সীমা, প্রকৌশলের দুর্বলতা নয়; ভালো reasoner এই সীমা জেনে হয় সময়-সীমা দেয়, নয়তো decidable একটি অংশে কাজ করে।
প্র ০৩Prolog-এর জন্ম আর জাপানের Fifth Generation প্রকল্প থেকে logic programming সম্পর্কে কী শেখা যায়?
Prolog-এর জন্ম ১৯৭২ সালের দিকে ফ্রান্সের Marseille-এ, Alain Colmerauer আর Philippe Roussel-এর হাতে; প্রথম লক্ষ্য ছিল ফরাসি ভাষার বাক্য প্রক্রিয়া করা। Edinburgh-এর Robert Kowalski-র সঙ্গে আলোচনা থেকে Horn clause-এর “procedural interpretation” আসে: “A যদি B এবং C” কথাটিকে পড়া যায় “A প্রমাণ করতে B আর C প্রমাণ করতে হবে” হিসেবে। এই দ্বৈত ব্যাখ্যাই Prolog-কে বিশেষ করে: একই লেখা একসঙ্গে জ্ঞানের বর্ণনা আর একটি প্রোগ্রাম।
১৯৮২ সালে জাপান সরকার Fifth Generation Computer Systems নামে প্রায় দশ বছরের একটি বড় প্রকল্প শুরু করে, যার কেন্দ্রে ছিল logic programming আর সমান্তরাল inference যন্ত্র। এটি পশ্চিমে উদ্বেগ আর পাল্টা প্রকল্প তৈরি করে। সাধারণ মূল্যায়ন হলো প্রকল্পটি তার বড় ঘোষিত লক্ষ্যে পৌঁছায়নি: সাধারণ কাজের কম্পিউটার আর কম দামি workstation দ্রুত উন্নত হচ্ছিল, বিশেষ যন্ত্রের সুবিধা কমে গিয়েছিল, আর জ্ঞান হাতে লেখার খরচ (knowledge acquisition bottleneck, পর্ব ৪৭-এ) রয়ে গিয়েছিল। তবে গবেষণাগুলো সমান্তরাল logic programming আর constraint programming-এ অবদান রাখে; “ব্যর্থ” বলাটা অতি সরলীকরণ বলেও অনেকে মনে করেন।
শিক্ষাটি দুই দিকের। Prolog দেখিয়েছে, ঘোষণামূলক নিয়ম থেকে কার্যকর প্রোগ্রাম হতে পারে, আর তার উত্তরসূরিরা (Datalog, constraint logic programming, answer set programming) আজও database, program analysis আর পরিকল্পনায় ব্যবহার হয়। আবার দেখিয়েছে, খাঁটি logic আর কার্যকর যন্ত্রের মাঝে সমঝোতা লাগে: occurs check বাদ, cut, negation as failure, নিয়মের ক্রমের ওপর নির্ভরতা (বাম-recursive নিয়ম অসীম loop দিতে পারে)। এই সমঝোতা না জানলে Prolog-এর উত্তরকে খাঁটি FOL-এর প্রমাণ ভাবার ভুল হয়।
মূল উপলব্ধি: Logic programming একটি টেকসই ধারণা, কিন্তু একটি ভাষা বা হার্ডওয়্যারই AI-এর সমাধান, এই বাজি টেকেনি; ধারণাগুলো ছোট, নির্দিষ্ট জায়গায় বেঁচে আছে।
প্র ০৪Database “closed world” ধরে, ontology “open world”. বাজারঘরের একটি প্রশ্নে এই পার্থক্য কীভাবে ভিন্ন উত্তর দেয়, আর কখন কোনটি ঠিক?
প্রশ্ন: “সুমি কি ডাল কিনেছেন?” বাজারঘরের অর্ডার-টেবিলে সুমির নামে ডাল নেই। SQL-এর NOT EXISTS বা Prolog-এর \+ উত্তর দেবে “না, কেনেননি”, কারণ দুটিই closed-world assumption-এ চলে: যা খাতায় নেই, তা ঘটেনি। Raymond Reiter ১৯৭৮ সালের দিকে এই অনুমানটিকে আনুষ্ঠানিক রূপ দেন। এটি দোকানের খাতার জন্য খুবই স্বাভাবিক, কারণ প্রতিটি অর্ডার সিস্টেম দিয়েই হয়, খাতাটি পূর্ণ।
খাঁটি FOL আর OWL-এর মতো description logic-ভিত্তিক ontology open world ধরে: KB-তে না থাকা মানে “অজানা”। একই প্রশ্নে উত্তর হবে “KB থেকে বলা যায় না”। কেন এমন নকশা? ওয়েবের জ্ঞান সবসময় অসম্পূর্ণ। একটি চিকিৎসা-ontology-তে কোনো রোগীর অ্যালার্জির উল্লেখ নেই মানে তাঁর অ্যালার্জি নেই, এমন সিদ্ধান্ত বিপজ্জনক। Open world-এ সেই ফাঁক “অজানা” থাকে, আর তথ্য আসার আগে কোনো ভুল সিদ্ধান্ত তৈরি হয় না।
দুই জগতের আরেকটি পার্থক্য nonmonotonicity. খাঁটি FOL-এ নতুন তথ্য যোগ করলে আগের কোনো সিদ্ধান্ত বাতিল হয় না (monotonic). Closed world-এ হয়: কাল সুমির ডালের অর্ডার এলে “সুমি ডাল কেনেননি” সিদ্ধান্ত উল্টে যাবে, আর rice_no_lentil-এর উত্তরও বদলাবে।
বাস্তবের ব্যবস্থায় প্রায়ই দুটি মেশানো হয়: নিজের লেনদেনের টেবিলে closed world, বাইরের থেকে আসা জ্ঞানে open world. নকশার প্রশ্নটি হলো, “আমার তথ্য কি সত্যিই পূর্ণ?” উত্তর হ্যাঁ হলে closed world সরল আর দ্রুত; না হলে open world নিরাপদ।
মূল উপলব্ধি: “নেই” আর “জানা নেই” এক নয়; কোন অনুমানে চলছেন তা ঠিক করে নেওয়া logic-ভিত্তিক যেকোনো ব্যবস্থার, এমনকি একটি সাধারণ SQL query-রও, একটি মৌলিক নকশার সিদ্ধান্ত।
প্র ০৫Large language model কি বাংলা বাক্যকে নির্ভরযোগ্যভাবে FOL-এ অনুবাদ করতে পারে? Neuro-symbolic পথে logic-এর ভূমিকা কী?
প্রাকৃতিক ভাষা থেকে logic-এ অনুবাদ (semantic parsing) AI-এর একটি পুরোনো লক্ষ্য। ১৯৭০-এর দশকের শুরুতে Richard Montague দেখান, ইংরেজির একটি অংশকে গাণিতিকভাবে logic-এ রূপান্তর করা যায়। কঠিন জায়গাগুলো এই পর্বেই দেখা গেছে: quantifier scope-এর অস্পষ্টতা (“প্রত্যেক গ্রাহক একটি পণ্য”), “শুধু” আর “অন্তত দুটি”-র মতো গোনা, “না”-এর জায়গা, আর প্রসঙ্গ থেকে বোঝা domain (“সব” মানে দোকানের সব, পৃথিবীর সব নয়)। বাংলায় এর সঙ্গে যোগ হয় “ও”, “-ই” (শুধু করিমই) আর ক্রিয়ার রূপের সূক্ষ্মতা।
Large language model এই কাজে খসড়া হিসেবে প্রায়ই কাজে লাগে। বিভিন্ন গবেষণায় এমন benchmark তৈরি হয়েছে যেখানে মডেলকে বাক্য থেকে FOL লিখতে বলা হয় আর একটি theorem prover দিয়ে ফল যাচাই করা হয়; রিপোর্ট হওয়া ফল মডেল আর সময়ের সঙ্গে দ্রুত বদলাচ্ছে, তাই কোনো নির্দিষ্ট সংখ্যা এখানে দেওয়া নিরাপদ নয়। পুনরাবৃত্ত পর্যবেক্ষণ হলো, ভুলগুলো ঠিক অংশ ৩ আর ৫-এর ধরনের: → আর ∧ গুলানো, ক্রম বদল, সমতা বাদ। আর ভুল logic-ও দেখতে সুন্দর।
Neuro-symbolic পথের মূল ভাবনা এখানেই: ভাষার মডেল ভাষা থেকে প্রতীকে যায়, আর একটি symbolic যন্ত্র (Prolog, SMT solver, theorem prover) সেই প্রতীকে যুক্তি চালায়, যার ফল প্রতিটি ধাপে পরীক্ষা করা যায়। এতে ব্যাখ্যাযোগ্যতা আসে: “সুমির জন্য নিয়মটি ভাঙে, কারণ এই দুটি তথ্য”। তবে অনুবাদের ধাপে ভুল থাকলে solver নিখুঁতভাবে ভুল প্রশ্নের উত্তর দেবে। তাই মানুষের পর্যালোচনা আর ছোট পরীক্ষামূলক জগতে যাচাই (একটি বাক্য যা মিথ্যা হওয়ার কথা, সেটি মিথ্যা আসে কি না) জরুরি। এই পথ শেষ পর্যন্ত কতটা টিকবে, সে বিষয়ে গবেষকদের মধ্যে মতভেদ আছে।
মূল উপলব্ধি: ভাষার মডেল ভাষা থেকে logic-এ যাওয়ার কাজ দ্রুত করে, logic যুক্তিকে যাচাইযোগ্য করে; দুটির জোড়া তখনই নির্ভরযোগ্য, যখন অনুবাদটি নিজেই স্বাধীনভাবে পরীক্ষা করা হয়।
অনুশীলন
প্রথমটি অনুবাদ আর হাতে unification-এর, দ্বিতীয়টি আউটপুট অনুমানের, তৃতীয়টি সমালোচনার, শেষটি কোড লিখে গোনার।
-
হাতে অনুবাদ আর unification: (ক) FOL-এ লিখুন: “কোনো গ্রাহক নিজের রাইডারের কাছ থেকে সাবান কেনেননি” (predicate BoughtFrom(x, y, z): x, y-এর কাছ থেকে z কিনেছেন; function rider(x)). (খ) “অন্তত দুজন গ্রাহক মসুর ডাল কিনেছেন”। (গ) Ordered(x, price(y), y) আর Ordered(Rahim, z, Mosur)-এর MGU বের করুন। (ঘ) Q(x, x) আর Q(y, g(y)) কি unify হয়?
(ক) ¬∃x (Customer(x) ∧ BoughtFrom(x, rider(x), Soap1)), অথবা সমতুল্য ∀x (Customer(x) → ¬BoughtFrom(x, rider(x), Soap1)).
(খ) ∃x ∃y (Customer(x) ∧ Customer(y) ∧ ¬(x = y) ∧ Ordered(x, Mosur) ∧ Ordered(y, Mosur)). বাজারঘরের জগতে সত্য: রহিম আর করিম।
(গ) প্রথম argument: x/Rahim. দ্বিতীয়: price(y) ≐ z, তাই z/price(y). তৃতীয়: y ≐ Mosur, তাই y/Mosur, আর z হয়ে যায় price(Mosur). MGU = {x/Rahim, y/Mosur, z/price(Mosur)}; দুই দিকই Ordered(Rahim, price(Mosur), Mosur).
(ঘ) না। প্রথম argument থেকে x/y; দ্বিতীয়টি তখন y ≐ g(y), আর y নিজেই g(y)-এর ভেতরে, তাই occurs check ব্যর্থ। Occurs check ছাড়া একটি অসীম term g(g(g(…))) তৈরি হতো।
-
আউটপুট অনুমান করুন: চালানোর আগে তিন লাইনের আউটপুট লিখুন, তারপর যেকোনো Python ব্লকে বসিয়ে মিলিয়ে দেখুন।
D = ["a", "b", "c"] likes = {("a", "b"), ("b", "b"), ("c", "a")} L = lambda x, y: (x, y) in likes print(all(any(L(x, y) for y in D) for x in D)) print(any(all(L(x, y) for x in D) for y in D)) print(any((not L(x, "c")) or L(x, x) for x in D), all(L(x, "b") and x != "c" for x in D))True False True Falseপ্রথম লাইন ∀x∃y Likes(x, y): a পছন্দ করে b, b পছন্দ করে b, c পছন্দ করে a, তাই সত্য। দ্বিতীয় ∃y∀x Likes(x, y): b-কে a আর b পছন্দ করে, কিন্তু c করে না; a আর c-কে সবাই নয়, তাই মিথ্যা। তৃতীয় লাইনের প্রথমটি ∃-এর সঙ্গে → লেখার ভুল রূপ: কেউই c-কে পছন্দ করে না, তাই প্রথম বস্তু a-তেই শর্ত মিথ্যা আর implication সত্য, ফল True. দ্বিতীয়টি ∀-এর সঙ্গে ∧: c-তে L(c, b) মিথ্যা, তাই False (লেখকের যন্ত্রে Python 3.14 দিয়ে মেলানো)।
-
নকশা ও সমালোচনা: একটি AI সহকারী বাজারঘরের তিনটি নিয়মের এই অনুবাদ দিয়েছে আর বলেছে “সব ঠিক আছে”। প্রতিটির ভুল খুঁজুন আর ঠিক রূপ লিখুন। (১) “সব দামি পণ্য মুদি পণ্য”: ∀x (Expensive(x) ∧ Grocery(x)). (২) “কিছু গ্রাহক চাল কিনেছেন”: ∃x (Customer(x) → ∃y (Rice(y) ∧ Ordered(x, y))). (৩) “শুধু রহিম মিনিকেট কিনেছেন”: Ordered(Rahim, Miniket). সঙ্গে Prolog-এ লেখা
not_bought(C, P) :- \+ ordered(C, P).-কে সে বলেছে “খাঁটি logic-এর ¬Ordered”.- (১) ∀-এর সঙ্গে ∧: মানে “সবকিছুই দামি আর মুদি”, যা গ্রাহকসহ যেকোনো জগতে মিথ্যা। ঠিক: ∀x (Expensive(x) → Grocery(x)).
- (২) ∃-এর সঙ্গে →: যেকোনো অ-গ্রাহক বস্তু (যেমন সাবান) বাক্যটিকে সত্য বানায়। ঠিক: ∃x (Customer(x) ∧ ∃y (Rice(y) ∧ Ordered(x, y))).
- (৩) “শুধু”-র অর্ধেক: শুধু বলা হয়েছে রহিম কিনেছেন, অন্য কেউ কেনেননি তা নয়। ঠিক: Ordered(Rahim, Miniket) ∧ ∀x (Ordered(x, Miniket) → x = Rahim). বাজারঘরের জগতে এটি মিথ্যা, কারণ সুমিও মিনিকেট কিনেছেন।
- Prolog-এর দাবি ভুল:
\+negation as failure, closed world-এ চলে; আর এখানে C আর P বাঁধা না থাকলে\+ ordered(C, P)শুধু জিজ্ঞেস করে “কোনো অর্ডারই কি নেই?”, কোনো নাম ফেরত দেয় না। ভালো নকশা:not_bought(C, P) :- customer(C), product(P), \+ ordered(C, P)., যাতে আগে C আর P বাঁধা হয়, আর ডকুমেন্টে closed-world অনুমানটি লেখা থাকে।
-
কোড লিখে গোনা: অংশ ৭-এর propositionalisation-এর আকার নিজে মাপুন। একটি Python function লিখুন যা constant-এর তালিকা আর একটি নিয়মের variable-সংখ্যা নিয়ে সব ground instance-এর সংখ্যা দেয়, আর
itertools.productদিয়ে আসলে বানিয়ে গুনে মিলিয়ে নেয়। ১৫টি constant আর ৩টি variable-এ কত? আর একটি এক-argument function (যেমন rider) যোগ করলে গভীরতা ১ পর্যন্ত (rider(c) পর্যন্ত, rider(rider(c)) নয়) ground term কতটি, আর তখন কত?from itertools import product def ground_count(terms, nvars): built = sum(1 for _ in product(terms, repeat=nvars)) return built, len(terms) ** nvars consts = [f"c{i}" for i in range(15)] print(ground_count(consts, 3)) depth1 = consts + [f"rider({c})" for c in consts] print(len(depth1), ground_count(depth1, 3))লেখকের যন্ত্রে সত্যিকারের run (Python 3.14.4):
(3375, 3375)আর30 (27000, 27000). মানে ১৫টি constant-এ একটি তিন-variable-এর নিয়ম থেকে ৩,৩৭৫টি ground বাক্য; একটি function আর মাত্র এক ধাপ গভীরতাতেই term দ্বিগুণ (৩০), আর ground বাক্য আট গুণ (২৭,০০০)। গভীরতা আরও বাড়ালে সংখ্যা সীমাহীন বাড়ে। Unification ঠিক এই কারণেই দরকার: সে শুধু মেলে এমন substitution খোঁজে।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমBackward chaining দিয়ে একটি প্রশ্নের উত্তর খোঁজার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📝 তথ্য আর নিয়মকে Horn clause হিসেবে KB-তে লেখা
- ❓ প্রশ্নটিকে একটি লক্ষ্য (goal) হিসেবে নেওয়া
- 🏷️ একটি নিয়ম বেছে তার variable-এর নাম নতুন করা
- 🔗 লক্ষ্য আর নিয়মের মাথা unify করে MGU বের করা
- ➡️ নিয়মের শরীরের শর্তগুলোকে নতুন লক্ষ্য করা, θ বসিয়ে
- ↩️ ব্যর্থ হলে পিছিয়ে পরের নিয়ম; লক্ষ্য ফুরালে θ-ই উত্তর
“সব চালের দাম ৬০ টাকার বেশি” বাক্যের সঠিক first-order logic রূপ কোনটি?
- ∀x (Rice(x) ∧ price(x) > 60)
- ∀x (Rice(x) → price(x) > 60)
- ∃x (Rice(x) → price(x) > 60)
- ∃x (Rice(x) ∧ price(x) > 60)
একটি জগতে গ্রাহক {A, B} আর পণ্য {p, q}; অর্ডার শুধু (A, p) আর (B, q). কোন বাক্যটি এই জগতে সত্য?
- ∃y ∀x Ordered(x, y)
- ∀x ∀y Ordered(x, y)
- ∀x ∃y Ordered(x, y)
- ¬∃x ∃y Ordered(x, y)
Knows(John, x) আর Knows(y, Mother(y))-এর most general unifier কোনটি?
- {y/John, x/Mother(John)}
- {x/Mother(y)}
- {John/y, x/Mother(y)}
- unify হয় না
Unification-এ X ≐ f(X) জোড়াটি occurs check-সহ মেলানোর চেষ্টা করলে কী হয়?
- MGU = {X/f(X)}
- X মুছে যায়, ফল f
- একটি অসীম term X = f(f(f(…))) বানানো হয়
- Unification ব্যর্থ হয়
KB-তে আছে ∃x (Customer(x) ∧ Ordered(x, Soap1)), আর KB-তে Karim নামে একজন গ্রাহকও আছেন। Existential instantiation-এর সঠিক প্রয়োগ কোনটি?
- Customer(Karim) ∧ Ordered(Karim, Soap1)
- Customer(C1) ∧ Ordered(C1, Soap1), যেখানে C1 একটি নতুন constant
- ∀x (Customer(x) ∧ Ordered(x, Soap1))
- Customer(x) ∧ Ordered(x, Soap1), x free রেখে
Prolog-এ KB-তে ordered(sumi, lentil) তথ্যটি নেই। প্রশ্ন \+ ordered(sumi, lentil)-এর উত্তর true আসে। এটি কোন অনুমানের ফল?
- Unique names assumption
- Open-world assumption
- Closed-world assumption (negation as failure)
- Occurs check
First-order logic-এ entailment যাচাই সম্পর্কে কোন বাক্যটি সঠিক?
- এটি semi-decidable: entail হলে প্রমাণ একসময় মেলে, না হলে খোঁজা না-ও থামতে পারে
- Truth table দিয়ে সব model দেখে সবসময় যাচাই করা যায়
- সবসময় থামে এমন একটি সাধারণ অ্যালগরিদম আছে
- কোনো complete প্রমাণ-পদ্ধতি নেই
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- FOL জগৎকে দেখে বস্তু, সম্পর্ক আর function হিসেবে: constant, predicate, function; term বস্তুর নাম, atomic sentence সত্য-মিথ্যা হয়। একটি নিয়ম সব গ্রাহকের জন্য খাটে, propositional logic-এর মতো প্রতিজনের জন্য আলাদা লিখতে হয় না।
- ∀-এর সঙ্গে →, ∃-এর সঙ্গে ∧; উল্টালে অর্থ বদলায়। ¬∀ ≡ ∃¬, ¬∃ ≡ ∀¬. ∀x∃y আর ∃y∀x আলাদা; “শুধু”, “অন্তত দুটি”, “ঠিক একটি” লিখতে লাগে সমতা।
- Model = domain + interpretation; সীমিত model-এ ∀ আর ∃ loop দিয়ে যাচাই করা যায়, কিন্তু entailment মানে KB-র সব model-এ সত্য, যা গুনে দেখা যায় না।
- UI, EI (নতুন Skolem constant), unification আর MGU, occurs check, generalised modus ponens: propositionalisation-এর বিস্ফোরণ এড়িয়ে শুধু মেলে এমন substitution খোঁজা।
- Prolog = Horn clause + backward chaining + unification, সঙ্গে সমঝোতা: occurs check নেই,
\+মানে negation as failure (closed world). পূর্ণ FOL semi-decidable, তাই বাস্তবে Datalog, description logic আর SQL-এর মতো সীমিত অংশ।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৪৭ · Rule-based আর expert system — forward ও backward chainingপরবর্তী পর্বProduction rule, chaining-এর trace আর একটি ছোট expert system.
- AI-এর ভিত্তি আর Python for AI · কোর্স হোম৭৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- DBMS · Relational calculusপাশের ট্র্যাকFOL-এর ভাষায় database query; SQL-এর তাত্ত্বিক ভিত্তি।
- Computer Fundamentals · Boolean algebraপাশের ট্র্যাক¬, ∧, ∨-এর বীজগণিত আর truth table-এর ভিত্তি।
- DSA · Depth-first searchপাশের ট্র্যাকProlog-এর খোঁজার ক্রম: গভীরে আগে, ব্যর্থ হলে পিছিয়ে আসা।