সরাসরি পর্বে যান
পর্ব ৩৮ · ৭৯-এর মধ্যে · মডিউল ৬

Entropy আর information — অনিশ্চয়তা মাপা আর cross-entropy

Entropy and Information — Surprise, Shannon Entropy, Compression, Mutual Information, Information Gain, Cross-Entropy, KL Divergence and Log Loss

Taught in Bangla, this lesson defines surprise and Shannon entropy, computes it by hand and with scipy.stats.entropy, and links entropy to compression with Huffman codes and real zlib runs. It then covers cross-entropy, KL divergence, mutual information, decision-tree information gain, log loss with clipping and underflow, and perplexity.

এই পর্বে যা শিখবেন

  • Surprise আর information content কেন −log p, bit কী, আর কেন log ছাড়া চলে না
  • মুদ্রা, ছক্কা আর বাজারঘরের পেমেন্টের মিশ্রণের entropy হাতে বের করা, তারপর NumPy, pandas আর scipy.stats.entropy দিয়ে মেলানো; bit আর nat-এর পার্থক্য
  • Entropy আর compression-এর সম্পর্ক: Huffman কোডের ধারণা আর Python-এর zlib দিয়ে সত্যিকারের তুলনা
  • Joint আর conditional entropy, mutual information, আর decision tree-তে একটি ভাগের information gain
  • Cross-entropy, KL divergence, log loss, clip আর underflow-এর ফাঁদ, আর perplexity-র ধারণা

এই পর্ব দিয়ে মডিউল ৬ শেষ হচ্ছে। নতুন প্রশ্নটি হলো: একটি distribution কতটা অনিশ্চিত, আর একটি মডেলের সম্ভাবনা আসল ঘটনার সঙ্গে কতটা মেলে, তা একটি সংখ্যায় কীভাবে মাপা যায়? উত্তরটি এসেছে information theory থেকে, আর আজকের প্রায় প্রতিটি classifier আর language model এই হিসাব দিয়েই train হয়। আগের মতোই চলমান উদাহরণ বাজারঘর (এই কোর্সের কাল্পনিক অনলাইন দোকান): ২৪টি অর্ডারের একটি ছোট টেবিল, যেখানে জেলা, পেমেন্টের ধরন আর ডেলিভারির দিন আছে। ৩ দিনের বেশি লাগলে অর্ডারটিকে “দেরি” ধরা হবে। সম্ভাবনার মৌলিক নিয়ম (যোগ, গুণ, স্বাধীনতা) পর্ব ৩৫-এ; এখানে শুধু দরকারটুকু মনে করিয়ে দেওয়া হবে।

১ · Surprise — বিরল ঘটনায় বেশি তথ্য

সকালের আবহাওয়ার পূর্বাভাসের কথা ভাবুন। বর্ষার মাঝামাঝি ঢাকায় রেডিও বলল, “আজ বৃষ্টি হতে পারে।” আপনি প্রায় কিছুই নতুন জানলেন না; ছাতা তো এমনিতেই ব্যাগে ছিল। কিন্তু পৌষ মাসে একই খবর শুনলে আপনি অবাক হবেন, পরিকল্পনা বদলাবেন। একই বাক্য, কিন্তু দ্বিতীয়বার তাতে বেশি তথ্য। information theory-র প্রথম ধারণাটি ঠিক এই: একটি ঘটনা যত কম সম্ভাব্য, তা ঘটলে আমরা তত বেশি জানি। একে বলে surprise বা information content.

ক্রিকেটের স্কোরকার্ড দেখুন। একটি বলে “ডট বল” খুব সাধারণ, স্কোরকার্ডে আলাদা করে কেউ মনে রাখে না। কিন্তু “হ্যাটট্রিক” বিরল, তাই পরদিনের খবরের শিরোনাম। খবরের মূল্য যেন সম্ভাবনার উল্টো দিকে চলে। তবে “উল্টো” মানে শুধু 1/p নয়; কেন log লাগে, তা নিচে।

সম্ভাবনা p (০ থেকে ১-এর মধ্যে একটি সংখ্যা) হলে information content:

I(x) = −log2 p(x)

এখানে log2 মানে ভিত্তি ২-এর logarithm: “২-কে কতবার গুণ করলে এই সংখ্যা হয়”। p ≤ 1 বলে log2 p ঋণাত্মক বা শূন্য, তাই সামনে একটি বিয়োগ চিহ্ন দিয়ে মানটিকে ধনাত্মক করা হয়। একক bit: ½ সম্ভাবনার ঘটনা (ন্যায্য মুদ্রায় head) ঘটলে ঠিক 1 bit তথ্য, মানে একটি হ্যাঁ/না প্রশ্নের উত্তরের সমান। ¼ সম্ভাবনায় 2 bit, ⅛-এ 3 bit, আর 1/1024-এ 10 bit. নিশ্চিত ঘটনা (p = 1) কোনো তথ্য দেয় না: −log2 1 = 0.

Surprise −log₂ p, সম্ভাবনার সঙ্গে 01236 0¼½1 সম্ভাবনা p surprise (bit) p = ½ → 1 bit ¼ → 2 bit ⅛ → 3 bit 0.01 → 6.64 bit নিশ্চিত → 0
p কমলে surprise বাড়ে, আর শূন্যের কাছে গিয়ে সীমাহীনভাবে বাড়ে। এই খাড়া দেয়ালটিই পরে log loss-এর আত্মবিশ্বাসী ভুলের শাস্তি (অংশ ৮)।

কেন log? স্বাধীন ঘটনার তথ্য যোগ হয়

শুধু 1/p নিলে কী সমস্যা? দুটি স্বাধীন ঘটনা ভাবুন: একটি মুদ্রায় head (p = ½) আর বাজারঘরের চারটি সমান সম্ভাব্য গুদামের মধ্যে একটি নির্দিষ্ট গুদাম থেকে অর্ডার যাওয়া (p = ¼)। দুটি একসঙ্গে ঘটার সম্ভাবনা গুণ হয়: ½ × ¼ = ⅛. আমরা চাই “দুটি খবর একসঙ্গে পেলে মোট তথ্য = আলাদা দুটি তথ্যের যোগ”, ঠিক যেমন দুটি হ্যাঁ/না প্রশ্নের উত্তর মিলিয়ে দুই প্রশ্নের তথ্য। Log-এর মূল ধর্ম হলো এটাই: log(a × b) = log a + log b. তাই −log2(⅛) = 3 = 1 + 2. সম্ভাবনার গুণকে তথ্যের যোগে বদলানো একমাত্র মসৃণ function (একটি ধ্রুবক গুণক ছাড়া) logarithm; ভিত্তি বদলালে শুধু একক বদলায়, যা অংশ ৩-এ। এ কারণেই মেশিন লার্নিংয়ে সম্ভাবনার বদলে প্রায় সবসময় log-probability নিয়ে কাজ হয়।

এই পুরো কাঠামো দাঁড়িয়ে আছে Claude Shannon-এর ১৯৪৮ সালের প্রবন্ধ “A Mathematical Theory of Communication”-এর ওপর, যা Bell System Technical Journal-এ দুই কিস্তিতে ছাপা হয়। Shannon তখন Bell Labs-এ টেলিফোন আর টেলিগ্রাফের লাইনে কত দ্রুত, কত নির্ভুলভাবে বার্তা পাঠানো যায়, সেই প্রশ্নে কাজ করছিলেন। “bit” শব্দটি ওই প্রবন্ধেই প্রথম ছাপা হয়, আর Shannon নিজেই শব্দটির জন্য সহকর্মী John Tukey-কে কৃতিত্ব দেন। তথ্যকে logarithm দিয়ে মাপার ধারণা তারও আগে Ralph Hartley (১৯২৮) দিয়েছিলেন, তবে সম্ভাবনা মিলিয়ে গড় তথ্য, মানে entropy, Shannon-এর অবদান হিসেবেই পরিচিত।

“তথ্য” এখানে অর্থ নয়, অনিশ্চয়তা কমা। Shannon-এর information বার্তার মানে, সত্যতা বা গুরুত্ব মাপে না, শুধু মাপে খবরটি কতটা অপ্রত্যাশিত। তাই AI মডেলের কম loss মডেলের “বোঝা”-র প্রমাণ নয়; শুধু এটুকু যে মডেলের সম্ভাবনা ডেটার সঙ্গে ভালো মেলে।

২ · Entropy — গড় surprise, মুদ্রা আর ছক্কায় হাতে হিসাব

একটি ঘটনার surprise জানলাম। কিন্তু একটি পুরো distribution, ধরুন একটি মুদ্রা বা ক্রেতাদের পেমেন্টের ধরন, গড়ে কতটা অপ্রত্যাশিত? প্রতিটি সম্ভাব্য ফলের surprise-কে তার সম্ভাবনা দিয়ে ওজন করে যোগ করলেই পাওয়া যায় গড় surprise. এর নাম Shannon entropy:

H(p) = ∑i pi · (−log2 pi) = −∑i pi log2 pi

এখানে ∑ (sigma) মানে “সবগুলো যোগ করুন”, i দিয়ে প্রতিটি সম্ভাব্য ফল বোঝানো হচ্ছে, আর pi সেই ফলের সম্ভাবনা। এটি আসলে surprise-এর expected value (প্রত্যাশিত মান, যার ধারণা পর্ব ৩৬-এ)। একটি নিয়ম মনে রাখুন: pi = 0 হলে 0 · log 0-কে 0 ধরা হয়, কারণ যে ফল কখনো ঘটে না, সে গড়ে কিছু যোগ করে না। কোডে এটি সামলাতে হয়, নইলে nan আসে।

তিনটি হাতে হিসাব

distributionহাতে হিসাবH (bit)
ন্যায্য মুদ্রা (½, ½)½ × 1 + ½ × 11.0
হেলানো মুদ্রা (0.9, 0.1)0.9 × 0.152 + 0.1 × 3.322 = 0.137 + 0.3320.469
ন্যায্য ছক্কা (৬টি ⅙)6 × ⅙ × log2 6 = log2 62.585
নিশ্চিত ফল (1, 0)1 × 0 + 00

হেলানো মুদ্রায় tail পড়লে অনেক surprise (3.322 bit), কিন্তু তা ঘটে মাত্র ১০% সময়; গড়ে 0.469 bit, ন্যায্য মুদ্রার অর্ধেকেরও কম, মানে ফলটি অনেকটা আন্দাজ করা যায়। ছক্কার ফল জানাতে গড়ে আড়াইটি হ্যাঁ/না প্রশ্নের একটু বেশি লাগে।

মূল ধারণা

k-টি ফলের মধ্যে entropy সবচেয়ে বেশি হয় যখন সব ফল সমান সম্ভাব্য, আর তখন H = log2 k. সবচেয়ে কম হয় (০) যখন একটি ফল নিশ্চিত। তাই 0 ≤ H ≤ log2 k. চার রকম পেমেন্টের জন্য সর্বোচ্চ 2 bit. সমান ভাগ মানে “কোনো দিকে ঝোঁক নেই”, আর সেখানেই আন্দাজ করা সবচেয়ে কঠিন। এর প্রমাণ এক লাইনে KL divergence থেকে আসে (অংশ ৫): KL(p ‖ uniform) = log2 k − H(p) ≥ 0.

এবার বাজারঘরের আসল উদাহরণ, যা অংশ ৩-এ কোড দিয়ে মেলাবেন। ঢাকার ১২টি অর্ডারের মধ্যে ৬টি bKash, আর COD (cash on delivery, হাতে পেয়ে টাকা দেওয়া), card ও Nagad ২টি করে। মানে সম্ভাবনা ½, ⅙, ⅙, ⅙. নিচে entropy-টি প্রতিটি পদ ধরে ধরে হিসাব করুন।

1p = [6, 2, 2, 2] / 122for pi in p:3    H += pi * -log2(pi)4print(H, log2(4))
মান (Watch)
পদ
pi
−log2 pi
পদের মান
H (চলতি যোগ)
  1. সম্ভাবনা। ১২টি অর্ডারের গণনা [6, 2, 2, 2]-কে ১২ দিয়ে ভাগ: ½, ⅙, ⅙, ⅙. যোগ 1, তাই এটি একটি বৈধ distribution.
  2. bKash. p = 0.5, surprise 1 bit. ওজনসহ পদ 0.5 × 1 = 0.5. অর্ধেক অর্ডার bKash বলে এর surprise কম, কিন্তু ওজন বেশি।
  3. COD. p = ⅙ ≈ 0.1667, surprise log2 6 ≈ 2.585 bit. পদ 0.4308. চলতি যোগ 0.9308.
  4. card. একই সম্ভাবনা, একই পদ 0.4308. চলতি যোগ 1.3617.
  5. Nagad. শেষ পদ 0.4308. মোট H = 0.5 + 3 × 0.4308 = 1.7925 bit.
  6. তুলনা। চার রকম পেমেন্টে সর্বোচ্চ সম্ভব log2 4 = 2 bit. ঢাকা 1.7925, মানে bKash-এর দিকে কিছুটা ঝোঁক আছে বলে অনিশ্চয়তা সর্বোচ্চের চেয়ে একটু কম।

৩ · বাজারঘরের পেমেন্টের মিশ্রণ — pandas, bit আর nat

বাজারঘরের ব্যবস্থাপক জানতে চান, কোন জেলায় পেমেন্টের ধরন সবচেয়ে “ছড়ানো”। প্রায় সবাই COD-তে দিলে কুরিয়ারের হাতে নগদ সামলানোর ব্যবস্থা জরুরি; মিশ্রণ ছড়ানো হলে সব পথ চালু রাখতে হবে। মিশ্রণকে একটি সংখ্যায় প্রকাশের একটি উপায় entropy. pandas-এ প্রথমে জেলা × পেমেন্টের একটি গণনার টেবিল বানাতে হবে, যাকে contingency table বলে; pd.crosstab ঠিক এটি দেয়। এটি আসলে পর্ব ২৪-এর split-apply-combine: জেলা ধরে ভাগ, প্রতিটি দলে গণনা, তারপর প্রতিটি সারিতে entropy.

Bit না nat — log-এর ভিত্তি

Log-এর ভিত্তি শুধু এককটি ঠিক করে। ভিত্তি ২ হলে bit; ভিত্তি e (≈ 2.718, স্বাভাবিক logarithm, np.log) হলে একককে বলে nat. সম্পর্কটি সরল: 1 nat = 1/ln 2 ≈ 1.4427 bit, আর 1 bit = ln 2 ≈ 0.6931 nat. তাই ঢাকার 1.7925 bit = 1.7925 × 0.6931 ≈ 1.2425 nat. মেশিন লার্নিংয়ের library-গুলো (PyTorch, scikit-learn-এর log_loss, mutual_info_score) সাধারণত nat ব্যবহার করে, কারণ derivative-এ e-ভিত্তির log সবচেয়ে সহজ (পর্ব ৩৪)। communication আর compression-এর বইয়ে bit বেশি চলে। দুটি সংখ্যা তুলনার আগে একক মিলিয়ে নিন।

SciPy-র scipy.stats.entropy(pk, base=None) তিনটি সুবিধা দেয়। এক, এটি গণনা নিলেও নিজেই ভাগ করে সম্ভাবনা বানায় (normalize), তাই [6, 2, 2, 2] সরাসরি দেওয়া যায়। দুই, base না দিলে nat, base=2 দিলে bit. তিন, শূন্য সম্ভাবনার পদ ঠিকভাবে 0 ধরে। নিচের কোড প্রথমে surprise আর মুদ্রা-ছক্কার entropy নিজের লেখা function দিয়ে বের করে, তারপর pandas আর SciPy দিয়ে জেলাভিত্তিক মিশ্রণ। প্রথমবার চালালে NumPy, pandas আর SciPy নামতে কয়েক সেকেন্ড লাগতে পারে।

import numpy as np
import pandas as pd
from scipy.stats import entropy

def surprise(p):                  # information content, bit-এ
    return -np.log2(p)

print("surprise (bit):", surprise(np.array([1/2, 1/4, 1/8, 1/1024])))
print("দুই স্বাধীন ঘটনা:", surprise(1/2 * 1/4), "=", surprise(1/2) + surprise(1/4))

def H(p):                         # entropy = গড় surprise
    p = np.asarray(p, dtype=float)
    p = p[p > 0]                  # 0 · log 0 = 0 ধরা হয়
    return float((p * np.log2(1 / p)).sum())

print("ন্যায্য মুদ্রা:", H([0.5, 0.5]))
print("হেলানো মুদ্রা:", round(H([0.9, 0.1]), 4))
print("ছক্কা:", round(H([1/6] * 6), 4), "| log2(6) =", round(np.log2(6), 4))

df = pd.read_csv("orders.csv")
mix = pd.crosstab(df["district"], df["payment"])
print(mix)
share = mix.div(mix.sum(axis=1), axis=0)       # প্রতি জেলায় ভাগ
res = pd.DataFrame({
    "n": mix.sum(axis=1),
    "H_bit": share.apply(H, axis=1).round(4),
    "scipy_bit": entropy(mix, base=2, axis=1).round(4),
    "scipy_nat": entropy(mix, axis=1).round(4),
})
print(res)
allmix = df["payment"].value_counts()
print("সব জেলা মিলিয়ে:", round(H(allmix / allmix.sum()), 4),
      "| সর্বোচ্চ সম্ভব:", np.log2(allmix.size))

আসল আউটপুট:

surprise (bit): [ 1.  2.  3. 10.]
দুই স্বাধীন ঘটনা: 3.0 = 3.0
ন্যায্য মুদ্রা: 1.0
হেলানো মুদ্রা: 0.469
ছক্কা: 2.585 | log2(6) = 2.585
payment    COD  Nagad  bKash  card
district                          
চট্টগ্রাম    2      2      2     2
ঢাকা         2      2      6     2
সিলেট        3      0      1     0
            n   H_bit  scipy_bit  scipy_nat
district                                   
চট্টগ্রাম   8  2.0000     2.0000     1.3863
ঢাকা       12  1.7925     1.7925     1.2425
সিলেট       4  0.8113     0.8113     0.5623
সব জেলা মিলিয়ে: 1.9108 | সর্বোচ্চ সম্ভব: 2.0

প্রথম দুই লাইন অংশ ১-এর দাবি মেলায়: ½, ¼, ⅛ আর 1/1024-এর surprise 1, 2, 3 আর 10 bit, আর ⅛-এর 3 bit ঠিক ½-এর 1 আর ¼-এর 2-এর যোগ। মুদ্রা আর ছক্কার সংখ্যা হাতে হিসাবের সঙ্গে মিলেছে। crosstab-এর কলাম বর্ণানুক্রমে সাজানো (বড় হাতের অক্ষর আগে, তাই COD আর Nagad আগে)। জেলাভিত্তিক ফলে তিনটি গল্প: চট্টগ্রামের ৪টি ধরনই সমান (২টি করে), তাই entropy ঠিক সর্বোচ্চ 2 bit; ঢাকা 1.7925, stepper-এর সংখ্যা; আর সিলেটের ৪টি অর্ডারের ৩টিই COD, তাই মাত্র 0.8113 bit, মানে সিলেটের পরের অর্ডারটি কীভাবে আসবে তা অনেকটা আন্দাজ করা যায়। নিজের function আর scipy.stats.entropy হুবহু মিলেছে; SciPy-কে গণনা সরাসরি দেওয়া হয়েছে, ভাগ করতে হয়নি। scipy_nat কলাম bit-এর মান × 0.6931: 2 × 0.6931 = 1.3863. সব জেলা মিলিয়ে entropy 1.9108, যা কোনো একক জেলার চেয়ে আলাদা; মিশ্রণের গড় entropy আর মেশানো মিশ্রণের entropy এক নয় (অংশ ৬-এ এর মানে স্পষ্ট হবে)।

৪টি অর্ডার দিয়ে সিদ্ধান্ত নয়। সিলেটের entropy মাত্র ৪টি অর্ডার থেকে, আর card বা Nagad একবারও না আসা মানে তাদের সম্ভাবনা শূন্য নয়, শুধু এই ছোট নমুনায় দেখা যায়নি। গণনা থেকে সরাসরি বের করা (“plug-in”) entropy ছোট নমুনায় গড়ে আসল মানের চেয়ে কম আসে, কারণ বিরল ফলগুলো অনেক সময় নমুনায় ধরা পড়ে না। এই প্রবণতা অংশ ৪-এর বাংলা লেখার পরীক্ষাতেও দেখবেন।

৪ · Entropy আর compression — Huffman, zlib আর বাংলা লেখা

Shannon-এর আসল প্রশ্ন ছিল: একটি উৎসের বার্তা লিখতে গড়ে কমপক্ষে কত bit লাগে? উত্তর: entropy. তাঁর source coding theorem মোটামুটি বলে, কোনো lossless compression (যেখানে ফাইল খুললে হুবহু আগের ডেটা ফেরে) পদ্ধতি স্বাধীনভাবে আসা চিহ্নগুলোকে গড়ে entropy-র চেয়ে কম bit-এ লিখতে পারে না, আর লম্বা ক্রমে entropy-র যত কাছে চাই, তত কাছে যাওয়া সম্ভব।

টেলিগ্রাফের Morse code-এ ইংরেজির সবচেয়ে সাধারণ অক্ষর E-এর সংকেত সবচেয়ে ছোট, একটি মাত্র ডট; বিরল অক্ষরের সংকেত লম্বা। চায়ের দোকানের হিসাব খাতায়ও দোকানি রোজকার “চা”-কে লেখেন এক টানে, আর কালেভদ্রে বিক্রি হওয়া জিনিসের পুরো নাম লেখেন। ধারণা একটাই: সাধারণ জিনিসকে ছোট সংকেত, বিরল জিনিসকে লম্বা সংকেত। আদর্শ দৈর্ঘ্য হলো ঠিক −log2 p bit, মানে surprise.

Huffman কোড — ঢাকার পেমেন্টে

David Huffman ১৯৫২ সালে (MIT-তে ছাত্র থাকার সময়, একটি কোর্সের লেখা হিসেবে) Huffman coding নামের একটি পদ্ধতি দেন, যা প্রতিটি চিহ্নকে পূর্ণসংখ্যার bit-এর সবচেয়ে ভালো কোড দেয়: বারবার সবচেয়ে কম সম্ভাবনার দুটি চিহ্ন জুড়ে একটি গাছ বানানো। ঢাকার মিশ্রণে প্রথমে card আর Nagad (⅙ + ⅙ = ⅓) জোড়ে, তারপর COD আর সেই জোড়া (⅙ + ⅓ = ½), শেষে bKash-এর ½-এর সঙ্গে। অ্যালগরিদমের পুরো ধাপ আর প্রমাণ আছে DSA কোর্সের Huffman coding পর্বে; এখানে শুধু ফলটি entropy-র পাশে রাখি।

পেমেন্টpআদর্শ −log2 pHuffman কোডনির্দিষ্ট ২-bit কোড
bKash½1.00 (1 bit)00
COD⅙2.58510 (2 bit)01
card⅙2.585110 (3 bit)10
Nagad⅙2.585111 (3 bit)11
গড় bitH = 1.7925½·1 + ⅙·(2 + 3 + 3) = 1.83332.0

নির্দিষ্ট দৈর্ঘ্যের কোড ২ bit নেয়; Huffman নেয় 1.8333; আর কোনো কোড entropy 1.7925-এর নিচে যেতে পারে না। Huffman একেবারে entropy-তে পৌঁছাতে পারে না, কারণ কোডের দৈর্ঘ্য পূর্ণসংখ্যা হতে হয়, অথচ আদর্শ দৈর্ঘ্য 2.585 bit. Arithmetic coding-এর মতো পদ্ধতি অনেকগুলো চিহ্ন একসঙ্গে লিখে এই ফাঁক প্রায় বন্ধ করে।

zlib দিয়ে সত্যিকারের তুলনা

Python-এর মানক library-র zlib সেই DEFLATE পদ্ধতি ব্যবহার করে, যা ZIP, PNG আর gzip-এর ভেতরে চলে: আগে দেখা অংশের পুনরাবৃত্তি খোঁজা (LZ77), তারপর Huffman কোড। নিচের কোডে ১০,০০০ byte-এর চারটি ক্রম: একই অক্ষর বারবার; চারটি অক্ষর ঢাকার পেমেন্টের ভাগে random; একটি ৮ অক্ষরের শব্দের পুনরাবৃত্তি; আর পুরো random byte. প্রতিটির জন্য প্রতি byte-এর entropy (প্রতিটি byte-কে আলাদা চিহ্ন ধরে), সেই entropy অনুযায়ী আদর্শ আকার, আর zlib-এর আসল আকার। শেষে একটি ছোট মৌলিক বাংলা অনুচ্ছেদের প্রতিটি অক্ষরের entropy, মজার জন্য।

import zlib
from collections import Counter
import numpy as np

def H_symbols(seq):               # প্রতি চিহ্নে entropy, bit-এ
    counts = np.array(list(Counter(seq).values()), dtype=float)
    p = counts / counts.sum()
    return float((p * np.log2(1 / p)).sum())

rng = np.random.default_rng(42)
N = 10_000
data = {
    "একই অক্ষর": b"A" * N,
    "৪ চিহ্ন, ঢাকার ভাগে": bytes(rng.choice(list(b"BCKN"), size=N,
                                 p=[0.5, 1/6, 1/6, 1/6]).astype(np.uint8)),
    "৮ অক্ষরের পুনরাবৃত্তি": b"bKashCOD" * (N // 8),
    "random byte": rng.integers(0, 256, size=N, dtype=np.uint8).tobytes(),
}
print("ধরন │ H(bit/byte) │ H অনুযায়ী │ zlib")
for name, b in data.items():
    h = H_symbols(b)
    z = len(zlib.compress(b, level=9))
    print(f"{name}: {h:.3f} │ {h * len(b) / 8:5.0f} │ {z}")

text = ("বাজারঘরের গুদামে সকাল শুরু হয় চায়ের কাপে। রফিক ভাই অর্ডারের "
        "তালিকা দেখে প্যাকেট সাজান, আর মিনা আপা ঠিকানা মিলিয়ে কুরিয়ারের "
        "খাতায় লেখেন। বৃষ্টি নামলে ডেলিভারি একটু দেরি হয়, তবু ক্রেতারা "
        "ফোনে হাসিমুখে বলেন, পৌঁছালেই চলবে।")
chars = list(text)
print("code point:", len(chars), "| আলাদা:", len(set(chars)))
print("entropy:", round(H_symbols(chars), 3), "bit/চিহ্ন",
      "| সর্বোচ্চ:", round(np.log2(len(set(chars))), 3))
print("সবচেয়ে বেশি:", Counter(chars).most_common(4))
u8 = text.encode("utf-8")
print("UTF-8 byte:", len(u8), "| zlib:", len(zlib.compress(u8, 9)))

আসল আউটপুট:

ধরন │ H(bit/byte) │ H অনুযায়ী │ zlib
একই অক্ষর: 0.000 │     0 │ 34
৪ চিহ্ন, ঢাকার ভাগে: 1.790 │  2237 │ 2967
৮ অক্ষরের পুনরাবৃত্তি: 3.000 │  3750 │ 50
random byte: 7.985 │  9981 │ 10011
code point: 225 | আলাদা: 42
entropy: 4.573 bit/চিহ্ন | সর্বোচ্চ: 5.392
সবচেয়ে বেশি: [(' ', 34), ('া', 26), ('ে', 21), ('র', 17)]
UTF-8 byte: 601 | zlib: 263

প্রথম আর শেষ সারি প্রত্যাশামতো: একই অক্ষরের entropy 0, আর ১০,০০০ byte zlib-এ মাত্র কয়েক ডজন byte হয়েছে (ফাইলের মাথার তথ্য আর “এটি ১০,০০০ বার” লেখার খরচ)। পুরো random byte-এর entropy প্রায় 8 bit/byte, মানে কোনো ফাঁক নেই; zlib তা ছোট করতে পারেনি, উল্টো কয়েক byte বড় করেছে। এটি গাণিতিকভাবেই অনিবার্য: কোনো lossless compressor সব ইনপুটকে ছোট করতে পারে না।

মাঝের দুটি সারি দেখায় কেন compressor নিখুঁত entropy মাপার যন্ত্র নয়। চার চিহ্নের random ক্রমে আদর্শ আকার প্রায় ২,২৩৭ byte (Huffman-এ প্রায় ২,২৯২), কিন্তু zlib নিয়েছে ২,৯৬৭: পুনরাবৃত্তি খোঁজার অংশ random ডেটায় শুধু খরচ বাড়ায়। আর “bKashCOD”-এর পুনরাবৃত্তিতে প্রতি byte-এর entropy 3 bit, যা থেকে আদর্শ আকার ৩,৭৫০ byte মনে হয়, অথচ zlib নিয়েছে মাত্র ৫০! কারণ প্রতিটি অক্ষর আগেরটি থেকে পুরোপুরি আন্দাজ করা যায়, আর “প্রতি চিহ্নের entropy” ক্রমের এই কাঠামো দেখে না। Language model ঠিক এই নির্ভরতাই ধরার চেষ্টা করে (অংশ ৯)।

বাংলা অনুচ্ছেদে ২২৫টি Unicode code point, যার মধ্যে আলাদা ৪২টি। এক অক্ষরের entropy প্রায় 4.57 bit, যেখানে ৪২টি সমান হলে হতো 5.39. সবচেয়ে বেশি এসেছে ফাঁকা জায়গা, তারপর আ-কার (া), এ-কার (ে) আর র। লক্ষ করুন, “কার”-চিহ্ন আলাদা code point; চোখে দেখা একটি অক্ষর (grapheme cluster) যেমন “ক্রে” কয়েকটি code point দিয়ে তৈরি (পর্ব ১০)। UTF-8-এ প্রতিটি বাংলা code point ৩ byte, তাই ৬০১ byte, আর zlib তা অর্ধেকের নিচে নামিয়েছে।

ছোট নমুনা entropy কমিয়ে দেখায়। মাত্র চার বাক্যের লেখায় অনেক বাংলা অক্ষর (ঋ, ঢ়, ঊ, যুক্তাক্ষরের বহু অংশ) একবারও আসেনি, তাই তাদের অবদান শূন্য ধরা হয়েছে। বড় সংগ্রহে আলাদা চিহ্ন বাড়বে, entropy-ও সম্ভবত বাড়বে। তাই এই 4.57 সংখ্যাটি “বাংলার entropy” নয়, শুধু এই অনুচ্ছেদের। আর অক্ষরগুলো স্বাধীন নয় (হসন্তের পরে প্রায় সবসময় ব্যঞ্জন), তাই আগের অক্ষর জানলে পরেরটির অনিশ্চয়তা আরও কম।

৫ · Cross-entropy আর KL divergence — ভুল মডেলে লেখার বাড়তি খরচ

Huffman কোড বানাতে আসল সম্ভাবনা জানতে হয়। কিন্তু বাস্তবে আমাদের কাছে থাকে একটি অনুমান, একটি মডেল q. ধরুন বাজারঘরের নতুন একজন কর্মী ভাবলেন, “ঢাকায় চার রকম পেমেন্ট নিশ্চয়ই সমান,” আর সেই অনুমানে ২-bit কোড বানালেন। আসল মিশ্রণ p (½, ⅙, ⅙, ⅙)। এখন প্রতিটি অর্ডারের পেছনে গড়ে কত bit খরচ হবে? প্রতিটি পেমেন্ট আসে আসল সম্ভাবনা p হারে, কিন্তু কোডের দৈর্ঘ্য ঠিক হয়েছে q দেখে, −log2 q. গড় খরচকে বলে cross-entropy:

H(p, q) = −∑i pi log2 qi

এখানে q = (¼, ¼, ¼, ¼), তাই প্রতিটি −log2 ¼ = 2, আর H(p, q) = 2 bit. আসল মিশ্রণ জেনে বানানো সেরা কোডের খরচ H(p) = 1.7925. পার্থক্যটি হলো ভুল মডেলের জন্য বাড়তি দাম, যার নাম Kullback–Leibler divergence:

KL(p ‖ q) = H(p, q) − H(p) = ∑i pi log2 (pi / qi)

এখানে KL(p ‖ q) = 2 − 1.7925 = 0.2075 bit প্রতি অর্ডার। “‖” চিহ্নটি পড়ুন “থেকে” বা “তুলনায়”: আসল p-কে মডেল q দিয়ে আঁকলে কতটা হারায়।

KL divergence-এর তিনটি ধর্ম

১. সবসময় KL ≥ 0, আর শুধু p = q হলেই 0 (Gibbs' inequality নামে পরিচিত)। মানে ভুল মডেল কখনো খরচ কমায় না, তাই H(p, q) ≥ H(p). ২. দুই দিকে সমান নয়: KL(q ‖ p)-তে ওজন আসে q থেকে: ¼ log2(¼ ÷ ½) + 3 × ¼ log2(¼ ÷ ⅙) = −0.25 + 0.4387 = 0.1887 bit, 0.2075 নয়। তাই KL একটি “দূরত্ব” (distance) নয়, যদিও প্রায়ই সেভাবে বলা হয়। ৩. q-তে শূন্য, p-তে নয়, তাহলে KL = ∞: যে ফলকে মডেল অসম্ভব বলেছে, তা ঘটলে তার কোডই নেই।

তৃতীয় ধর্মটি বাস্তবে খুব গুরুত্বপূর্ণ। সিলেটের ৪টি অর্ডার থেকে বানানো মডেল (bKash ¼, COD ¾, card 0, Nagad 0) দিয়ে ঢাকার অর্ডার লিখতে গেলে প্রথম card অর্ডারেই মডেল ভেঙে পড়ে। এ কারণেই সম্ভাবনার মডেলে কোনো ফলকে ঠিক শূন্য সম্ভাবনা দেওয়া হয় না; গণনায় একটি ছোট সংখ্যা যোগ করা হয় (Laplace smoothing, যা পর্ব ৫১-এর Naive Bayes-এ কাজে লাগবে)। SciPy-তে দুটি পথ: scipy.stats.entropy(p, q) দুটি distribution দিলে KL(p ‖ q) দেয়, আর scipy.special.rel_entr(p, q) প্রতিটি পদ pi ln(pi/qi) আলাদা দেয় (nat-এ), যার যোগ KL. কোডে এগুলো অংশ ৭-এ দেখবেন, mutual information-এর সঙ্গে।

কোন দিকের KL কখন? Train-এর সময় সাধারণত KL(ডেটা ‖ মডেল) কমানো হয়; H(ডেটা) মডেলের ওপর নির্ভর করে না, তাই এটি কমানো আর cross-entropy কমানো একই কাজ (অংশ ৮)। এই দিক শাস্তি দেয় যেখানে ডেটা আছে অথচ মডেল কম সম্ভাবনা দিয়েছে। উল্টো দিক KL(মডেল ‖ ডেটা) variational inference-এর মতো পদ্ধতিতে আসে; বিস্তারিত পরের মেশিন লার্নিং কোর্সে।

৬ · Joint, conditional entropy আর mutual information

এবার দুটি variable একসঙ্গে: X = জেলা, Y = দেরি হয়েছে কি না। ২৪টি অর্ডারের contingency table (প্রতিটি ঘর গণনা):

জেলা (X)সময়মতোদেরিমোটদেরির হার
ঢাকা10212⅙ ≈ 0.167
চট্টগ্রাম448½
সিলেট134¾
মোট159240.375
  • H(Y): জেলা না জেনে দেরির অনিশ্চয়তা। P(দেরি) = 9/24 = 0.375, তাই H(Y) = −(0.375 log2 0.375 + 0.625 log2 0.625) = 0.531 + 0.424 ≈ 0.9544 bit. প্রায় ন্যায্য মুদ্রার মতো অনিশ্চিত।
  • Joint entropy H(X, Y): ছয়টি ঘরকে (জেলা, দেরি)-র ছয়টি সম্ভাব্য জোড়া ধরে তাদের entropy. ঘরগুলোর সম্ভাবনা 10/24, 2/24, 4/24, 4/24, 1/24, 3/24; H(X, Y) ≈ 2.2527 bit.
  • Conditional entropy H(Y | X): জেলা জানার পরে দেরির গড় অনিশ্চয়তা। হিসাবের সহজ পথ chain rule: H(Y | X) = H(X, Y) − H(X). জেলার entropy H(X) (১২, ৮, ৪ থেকে) ≈ 1.4591, তাই H(Y | X) ≈ 2.2527 − 1.4591 = 0.7936 bit. অন্য পথটি অংশ ৭-এর stepper-এ: প্রতিটি জেলার ভেতরের entropy-র ওজনসহ গড়।

জেলা জানায় দেরির অনিশ্চয়তা 0.9544 থেকে কমে 0.7936 হলো। এই কমাটুকুই mutual information:

I(X; Y) = H(Y) − H(Y | X) = H(X) + H(Y) − H(X, Y)

এখানে I(X; Y) ≈ 0.9544 − 0.7936 ≈ 0.1609 bit (শেষ অঙ্কের পার্থক্য round করার জন্য; কোডে পুরো নির্ভুলতায় 0.1609). কথায়: “X জানলে Y সম্পর্কে অনিশ্চয়তা কতটা কমে।” এটি সমমিত: Y জানলে X সম্পর্কেও ঠিক ততটাই কমে। আর X ও Y স্বাধীন হলে I = 0. আরেকটি সুন্দর রূপ: I(X; Y) = KL(p(x, y) ‖ p(x)p(y)), মানে আসল joint distribution আর “স্বাধীন হলে যা হতো” তার KL; তাই I ≥ 0.

Correlation-এর সঙ্গে পার্থক্য। পর্ব ৩৭-এর correlation শুধু সংখ্যার মধ্যে সরলরৈখিক সম্পর্ক মাপে। Mutual information যেকোনো রকম নির্ভরতা ধরে, “জেলা”-র মতো category-তেও চলে। তবে ছোট নমুনায় এটি ধনাত্মক দিকে পক্ষপাতদুষ্ট: পুরো স্বাধীন দুটি variable-এর নমুনাতেও সাধারণত 0-এর একটু ওপরে আসে। ২৪টি অর্ডারের 0.161 bit তাই ইঙ্গিত, প্রমাণ নয়।

৭ · Information gain — decision tree-র একটি ভাগ

Decision tree হলো এমন একটি মডেল, যা হ্যাঁ/না বা category-র প্রশ্ন করে করে ডেটাকে ভাগ করে, যেমন ডাক্তার রোগনির্ণয়ে একটির পর একটি প্রশ্ন করেন: “জ্বর আছে? কত দিন? কাশি?” প্রতিটি ধাপে গাছকে বাছতে হয়, কোন প্রশ্নটি সবচেয়ে কাজের। ID3 নামের পুরোনো অ্যালগরিদম (Ross Quinlan, ১৯৮৬ সালের প্রবন্ধ) আর তার উত্তরসূরিরা বাছে সেই প্রশ্ন, যা লক্ষ্যের entropy সবচেয়ে বেশি কমায়। কমার পরিমাণটিকে বলে information gain:

IG = H(মা) − ∑সন্তান (nসন্তান / n) · H(সন্তান)

এখানে “মা” মানে ভাগের আগের পুরো দল, “সন্তান” মানে ভাগের পরের প্রতিটি দল, আর n হলো অর্ডারের সংখ্যা। লক্ষ করুন, ওজনসহ গড়টি ঠিক H(Y | X), তাই information gain আর mutual information একই সংখ্যা, শুধু দুই সম্প্রদায় দুই নাম দিয়েছে। বাজারঘরে প্রশ্ন: “দেরি হবে কি না” আন্দাজ করতে প্রথম প্রশ্নটি কি “কোন জেলা?” হওয়া উচিত? ধাপে ধাপে দেখুন।

জেলা ধরে ভাগের information gain ২৪টি অর্ডার, ৯টি দেরি H = 0.954 জেলা? ঢাকা ১২টি, ২টি দেরি H = 0.650 চট্টগ্রাম ৮টি, ৪টি দেরি H = 1.000 সিলেট ৪টি, ৩টি দেরি H = 0.811
  1. মা। ২৪টি অর্ডারের ৯টি দেরি, P = 0.375. H = 0.9544 bit, মানে ভাগের আগে দেরি নিয়ে অনিশ্চয়তা প্রায় সর্বোচ্চ (১ bit)-এর কাছে।
  2. ঢাকা। ১২টির মধ্যে ২টি দেরি, P = ⅙. H(⅙, ⅚) = 0.650 bit. দলের ওজন ১২/২৪ = ½, তাই অবদান 0.325.
  3. চট্টগ্রাম। ৮টির ৪টি দেরি, ঠিক অর্ধেক: H = 1.0 bit, এই দলে কোনো সাহায্যই হয়নি। ওজন ৮/২৪ = ⅓, অবদান 0.333.
  4. সিলেট। ৪টির ৩টি দেরি: H(¾, ¼) = 0.811 bit. ওজন ৪/২৪ = ⅙, অবদান 0.135.
  5. ভাগের পরের গড়। 0.325 + 0.333 + 0.135 = 0.794 bit. এটিই H(Y | X), অংশ ৬-এর 0.7936.
  6. Information gain. 0.954 − 0.794 = 0.161 bit. অংশ ৬-এর mutual information-এর সঙ্গে হুবহু এক। গাছ অন্য প্রশ্নগুলোর (যেমন পেমেন্ট) IG-ও হিসাব করে, আর সবচেয়ে বড়টি বাছে (অনুশীলন ৪)।

এবার অংশ ৫, ৬ আর ৭-এর সব সংখ্যা একসঙ্গে কোডে মেলান: ঢাকার মিশ্রণের cross-entropy আর দুই দিকের KL, শূন্যওয়ালা মডেলের KL, তারপর জেলা × দেরির টেবিল থেকে joint, conditional entropy, দুই পথে mutual information, আর information gain.

import numpy as np
from scipy.stats import entropy
from scipy.special import rel_entr

p = np.array([6, 2, 2, 2]) / 12       # ঢাকার আসল ভাগ: bKash, COD, card, Nagad
q = np.array([1, 1, 1, 1]) / 4        # মডেল: সব সমান
H_p = entropy(p, base=2)
CE = -(p * np.log2(q)).sum()          # cross-entropy H(p, q)
print("H(p) =", round(H_p, 4), "| H(p,q) =", round(CE, 4))
print("KL(p‖q) =", round(entropy(p, q, base=2), 4), "= H(p,q) - H(p) =", round(CE - H_p, 4))
print("KL(q‖p) =", round(entropy(q, p, base=2), 4))
print("rel_entr (nat), যোগ:", rel_entr(p, q).round(4), rel_entr(p, q).sum().round(4))
q0 = np.array([0.25, 0.75, 0.0, 0.0])  # সিলেটের ভাগ, card আর Nagad শূন্য
print("KL(p‖সিলেট) =", entropy(p, q0, base=2))

# জেলা × দেরি (সারি: ঢাকা, চট্টগ্রাম, সিলেট; কলাম: সময়মতো, দেরি)
T = np.array([[10, 2], [4, 4], [1, 3]])
J = T / T.sum()                       # joint সম্ভাবনা p(x, y)
px, py = J.sum(axis=1), J.sum(axis=0)
H_XY = entropy(J.ravel(), base=2)
H_X, H_Y = entropy(px, base=2), entropy(py, base=2)
print("H(X) =", round(H_X, 4), "H(Y) =", round(H_Y, 4), "H(X,Y) =", round(H_XY, 4))
print("H(Y|X) =", round(H_XY - H_X, 4))
MI = H_X + H_Y - H_XY
MI_kl = entropy(J.ravel(), np.outer(px, py).ravel(), base=2)
print("I(X;Y) =", round(MI, 4), "| KL দিয়ে:", round(MI_kl, 4))

# information gain: জেলা ধরে ভাগ করলে দেরির অনিশ্চয়তা কতটা কমে
child = [entropy(row, base=2) for row in T]
w = T.sum(axis=1) / T.sum()
print("সন্তানদের H:", np.round(child, 4), "| ওজন:", np.round(w, 4))
print("IG =", round(H_Y - (w * child).sum(), 4))

আসল আউটপুট:

H(p) = 1.7925 | H(p,q) = 2.0
KL(p‖q) = 0.2075 = H(p,q) - H(p) = 0.2075
KL(q‖p) = 0.1887
rel_entr (nat), যোগ: [ 0.3466 -0.0676 -0.0676 -0.0676] 0.1438
KL(p‖সিলেট) = inf
H(X) = 1.4591 H(Y) = 0.9544 H(X,Y) = 2.2527
H(Y|X) = 0.7936
I(X;Y) = 0.1609 | KL দিয়ে: 0.1609
সন্তানদের H: [0.65   1.     0.8113] | ওজন: [0.5    0.3333 0.1667]
IG = 0.1609

প্রথম তিন লাইন অংশ ৫-এর হাতে হিসাব: H(p, q) = 2, KL(p ‖ q) = 0.2075, আর দুই পথে (SciPy-র entropy(p, q) আর H(p, q) − H(p)) একই সংখ্যা। উল্টো দিকে KL(q ‖ p) = 0.1887, মানে KL সত্যিই সমমিত নয়। rel_entr-এর পদগুলো nat-এ, আর কয়েকটি ঋণাত্মক (যেখানে p < q): একক পদ ঋণাত্মক হতে পারে, কিন্তু যোগ (0.1438 nat = 0.2075 × 0.6931) কখনো নয়। সিলেটের মডেলে card আর Nagad-এর সম্ভাবনা শূন্য, তাই KL = inf, ঠিক অংশ ৫-এর তৃতীয় ধর্ম। এরপর অংশ ৬-এর সব সংখ্যা: H(Y | X) = 0.7936, mutual information দুই সূত্রে (entropy-র যোগ-বিয়োগ আর joint বনাম স্বাধীনের KL) একই 0.1609, আর stepper-এর information gain-ও 0.1609. হাতে round করা 0.9544 − 0.7936 = 0.1608 শেষ অঙ্কে আলাদা, কারণ কোড পুরো নির্ভুলতায় বিয়োগ করে তারপর round করে।

দুটি সাবধানতা। অনেক মানের feature (যেমন order_id) IG-তে অন্যায্য সুবিধা পায়; এর কারণ আর প্রতিকার ভাবনার প্রশ্ন ৫-এ। আর scikit-learn-এর decision tree default-এ Gini impurity ব্যবহার করে (criterion="gini"); criterion="entropy" দিলে এই পর্বের হিসাব চলে, আর দুটির ফল প্রায়ই কাছাকাছি। পুরো আলোচনা পরের মেশিন লার্নিং কোর্সে।

৮ · Log loss — classifier-এর সম্ভাবনার বিচার

ধরুন বাজারঘর একটি মডেল বানিয়েছে, যা প্রতিটি অর্ডারের জন্য একটি সংখ্যা দেয়: P(দেরি). মডেলটি কতটা ভালো? শুধু “ঠিক না ভুল” গুনলে (accuracy, পর্ব ৬০) 0.51 আর 0.99-এর পার্থক্য হারিয়ে যায়। আমরা চাই এমন একটি মাপ, যা ঠিক উত্তরে আত্মবিশ্বাস-কে পুরস্কার আর ভুল উত্তরে আত্মবিশ্বাসকে শাস্তি দেয়। উত্তর আবার cross-entropy. প্রতিটি অর্ডারের আসল distribution হলো নিশ্চিত: দেরি হলে (1, 0), না হলে (0, 1). মডেলের distribution (p̂, 1 − p̂)। এদের cross-entropy-তে একটি পদই বাঁচে, আর সব অর্ডারের গড় নিলে binary cross-entropy বা log loss:

Log loss = −(1/N) ∑i [ yi ln p̂i + (1 − yi) ln(1 − p̂i) ]

এখানে N অর্ডারের সংখ্যা, yi আসল লেবেল (দেরি হলে 1, নইলে 0), আর p̂i (“p hat”) মডেলের দেওয়া P(দেরি)। y = 1 হলে শুধু −ln p̂ থাকে, y = 0 হলে শুধু −ln(1 − p̂)। মানে প্রতিটি অর্ডারের loss = যা আসলে ঘটেছে, মডেল তাকে যে সম্ভাবনা দিয়েছিল তার surprise. ln মানে স্বাভাবিক log, তাই একক nat; scikit-learn-ও তাই করে।

পরীক্ষার খাতা দেখার এমন একটি নিয়ম ভাবুন, যেখানে ছাত্র উত্তরের পাশে লেখে সে কতটা নিশ্চিত। “৯৫% নিশ্চিত” লিখে ঠিক হলে প্রায় পুরো নম্বর; “৫০%” লিখলে মাঝারি; কিন্তু “৯৯% নিশ্চিত” লিখে ভুল হলে বড় কাটা। এই নিয়মে ছাত্রের সবচেয়ে লাভ হয় যখন সে নিজের আসল বিশ্বাসটি সৎভাবে লেখে, ভান করে বেশি বা কম নয়। এ ধরনের নিয়মকে proper scoring rule বলে, আর log loss তার একটি।

আসল লেবেল y = 1 (দেরি হয়েছে) ধরে মডেলের p̂ ধীরে ধীরে ভুলের দিকে নিলে loss কীভাবে বাড়ে, দেখুন।

−ln p̂, p̂ শূন্যের দিকে গেলে 02468 00.51 মডেলের P(দেরি) = p̂ loss (nat) ছবির বাইরে: 34.5 বা 36.0
  1. p̂ = 0.9. মডেল বলেছিল দেরি হবে, ৯০% নিশ্চিত, আর হয়েছেও। loss −ln 0.9 = 0.1054 nat, প্রায় শূন্য।
  2. p̂ = 0.5. মুদ্রা ছোড়ার মতো অনিশ্চিত। loss ln 2 = 0.6931. যে মডেল সবসময় 0.5 বলে, তার log loss ঠিক এটুকু; এটি একটি সহজ baseline.
  3. p̂ = 0.1. মডেল প্রায় নিশ্চিত ছিল দেরি হবে না, কিন্তু হয়েছে। loss 2.3026, আগের তুলনায় তিন গুণের বেশি।
  4. p̂ = 0.01. loss 4.6052. সম্ভাবনা ১০ ভাগের ১ ভাগ করলে loss বাড়ে ln 10 ≈ 2.3 করে; প্রতিটি দশমিক ঘর এক ধাপ।
  5. p̂ = 0.001. loss 6.9078. রেখা প্রায় খাড়া দেয়াল।
  6. p̂ = 10−15. loss 34.54. একটি মাত্র এমন অর্ডার একশোটি ভালো ভবিষ্যদ্বাণীর loss-কে ছাপিয়ে যেতে পারে।
  7. p̂ = 0. −ln 0 = ∞. একটি অসীম পুরো গড়কে অসীম করে দেয়। তাই p̂-কে [ε, 1 − ε]-এর মধ্যে আটকানো (clip) হয়, যেখানে ε float64-এর machine epsilon ≈ 2.2 × 10−16; তখন loss −ln ε ≈ 36.04.

Underflow আর log(0) — সত্যিকারের সংখ্যার সীমা

Float64 (পর্ব ৯ আর ১৬-এর floating-point) প্রায় 10−308 পর্যন্ত স্বাভাবিক সংখ্যা রাখতে পারে, আর বিশেষ “subnormal” রূপে প্রায় 5 × 10−324 পর্যন্ত। তার চেয়ে ছোট হলে মানটি চুপচাপ 0.0 হয়ে যায়। একে বলে underflow. অনেক স্বাধীন ঘটনার সম্ভাবনা গুণ করলে এটি সহজেই ঘটে: ৪০০টি ঘটনা, প্রতিটির সম্ভাবনা 0.05, গুণফল প্রায় 10−520, যা float64-এ 0.0. তারপর log নিলে −inf. সমাধান হলো আগে log, তারপর যোগ: log-এর যোগে কোনো underflow নেই। এ কারণেই মডেলগুলো সম্ভাবনা নয়, log-probability নিয়ে কাজ করে। নিচের কোডে একটি ছোট log loss টেবিল (আত্মবিশ্বাসী ঠিক থেকে আত্মবিশ্বাসী ভুল), নিজের লেখা সূত্র আর sklearn.metrics.log_loss-এর তুলনা, underflow, আর শেষে অংশ ৬-এর mutual information-এর scikit-learn দিয়ে যাচাই। scikit-learn প্রথমবার নামতে প্রায় ১০ সেকেন্ড লাগতে পারে।

import numpy as np
from sklearn.metrics import log_loss, mutual_info_score

y = np.array([1, 1, 1, 0, 1, 0])                 # 1 = দেরি হয়েছে
p = np.array([0.95, 0.6, 0.5, 0.4, 0.01, 1.0])   # মডেলের P(দেরি)
eps = np.finfo(float).eps                        # ≈ 2.2e-16
pc = np.clip(p, eps, 1 - eps)
loss = -(y * np.log(pc) + (1 - y) * np.log(1 - pc))
print(" y │  P(দেরি) │ loss (nat)")
for yi, pi, li in zip(y, p, loss):
    print(f" {yi} │ {pi:7.2f} │ {li:8.4f}")
print("নিজের গড়:", round(loss.mean(), 4))
print("sklearn  :", round(log_loss(y, p), 4))
print("শেষটি বাদে:", round(log_loss(y[:5], p[:5]), 4))

print("clip ছাড়া log(1 - 1.0):", np.log(1 - p[-1]))
probs = np.full(400, 0.05)                       # ৪০০টি ঘটনা, প্রতিটি 0.05
print("গুণফল:", np.prod(probs), "| তার log:", np.log(np.prod(probs)))
print("log-এর যোগ:", round(np.log(probs).sum(), 2))
print("1e-320 আছে, 1e-330 =", 1e-320, 1e-330)

d = ["ঢাকা"] * 12 + ["চট্টগ্রাম"] * 8 + ["সিলেট"] * 4
late = [1]*2 + [0]*10 + [1]*4 + [0]*4 + [1]*3 + [0]
mi = mutual_info_score(d, late)
print("mutual_info_score:", round(mi, 4), "nat =", round(mi / np.log(2), 4), "bit")

আসল আউটপুট:

 y │  P(দেরি) │ loss (nat)
 1 │    0.95 │   0.0513
 1 │    0.60 │   0.5108
 1 │    0.50 │   0.6931
 0 │    0.40 │   0.5108
 1 │    0.01 │   4.6052
 0 │    1.00 │  36.0437
নিজের গড়: 7.0692
sklearn  : 7.0692
শেষটি বাদে: 1.2743
clip ছাড়া log(1 - 1.0): -inf
গুণফল: 0.0 | তার log: -inf
log-এর যোগ: -1198.29
1e-320 আছে, 1e-330 = 1e-320 0.0
mutual_info_score: 0.1115 nat = 0.1609 bit

আত্মবিশ্বাসী ঠিক (0.95) মাত্র 0.0513; কম নিশ্চিত ঠিক (0.6) 0.5108; মুদ্রা ছোড়া 0.6931; চতুর্থটিতে দেরি হয়নি আর মডেল “সময়মতো”-কে 0.6 দিয়েছিল, তাই আবার 0.5108. তারপর দুটি আত্মবিশ্বাসী ভুল: 0.01-এ 4.6052, আর 1.0-এ (clip না করলে ∞) 36.0437. নিজের গড় আর scikit-learn-এর log_loss হুবহু মিলেছে (7.0692), মানে scikit-learn-ও ভেতরে একই ε দিয়ে clip করে। শেষ অর্ডারটি বাদ দিলে গড় নেমে আসে 1.2743-এ: একটি আত্মবিশ্বাসী ভুল ছয়টি অর্ডারের গড় loss-কে পাঁচ গুণের বেশি বাড়িয়েছে।

পরের লাইনগুলো সংখ্যার সীমা: clip ছাড়া log(1 − 1.0) = log(0) = −inf. ০.০৫-এর ৪০০টি গুণফল 0.0 (underflow), তার log −inf, অথচ log-এর যোগ নিরাপদে −1198.29. 1e-320 এখনো ধরা যায় (subnormal), 1e-330 সরাসরি 0.0. শেষ লাইনে mutual_info_score দিয়েছে 0.1115, কারণ এটি nat-এ হিসাব করে; ln 2 দিয়ে ভাগ করলে 0.1609 bit, অংশ ৬ আর ৭-এর সংখ্যা।

ব্রাউজারের Python (Pyodide, WebAssembly-তে চলে) np.log(0)-এ কোনো সতর্কবার্তা দেয় না, কারণ WebAssembly-তে floating-point-এর ব্যতিক্রমের সংকেত নেই। লেখকের যন্ত্রে সাধারণ Python-এ চালালে (সত্যিকারের run, Python 3.14.4, NumPy 2.5.4, scikit-learn 1.9.1) সংখ্যাগুলো একই আসে, কিন্তু সঙ্গে এই সতর্কবার্তা আসে (ফাইলের পথ বাদ দেওয়া):

…log_loss_table.py:16: RuntimeWarning: divide by zero encountered in log
  print("clip ছাড়া log(1 - 1.0):", np.log(1 - p[-1]))
…log_loss_table.py:18: RuntimeWarning: divide by zero encountered in log
  print("গুণফল:", np.prod(probs), "| তার log:", np.log(np.prod(probs)))

Train-এর সময় loss হঠাৎ inf বা nan হলে প্রায়ই কারণ এমন একটি log(0). Deep learning library-গুলো তাই মডেলের কাঁচা স্কোর (logit) থেকে সরাসরি loss হিসাব করে, log-sum-exp কৌশলে। Logistic regression-এ এই loss sigmoid-এর সঙ্গে কীভাবে বসে, তা পর্ব ৫৯-এ।

আরও গভীরে: cross-entropy কমানো = likelihood বাড়ানো। ধরুন ডেটা x1, …, xN স্বাধীনভাবে এসেছে আর মডেল q প্রতিটিকে সম্ভাবনা দেয়। পুরো ডেটার likelihood (মডেলের চোখে এই ডেটা দেখার সম্ভাবনা) L = ∏n q(xn). log নিয়ে −1/N দিয়ে গুণ করলে: −(1/N) log L = −(1/N) ∑n log q(xn) = −∑x p̂(x) log q(x) = H(p̂, q), যেখানে p̂(x) হলো ডেটায় x-এর আপেক্ষিক হার (মাঝের ধাপে একই x-গুলো একসঙ্গে জোড়া হয়েছে)। log একঘেয়ে বাড়ে, তাই L সর্বোচ্চ করা আর H(p̂, q) সর্বনিম্ন করা হুবহু একই কাজ; আর H(p̂) মডেলের ওপর নির্ভর করে না বলে এটি KL(p̂ ‖ q) সর্বনিম্ন করাও। Maximum likelihood estimation-এর এই সম্পর্কের জন্যই প্রায় প্রতিটি classifier আর language model cross-entropy দিয়ে train হয়।

৯ · Perplexity আর AI-তে এই হিসাবগুলো কোথায়

একটি language model প্রতিটি ধাপে পরের token-এর (শব্দ বা শব্দাংশ) জন্য একটি distribution দেয়। আসল লেখায় পরের token-টি যা ছিল, মডেল তাকে যে সম্ভাবনা দিয়েছিল তার surprise-এর গড়ই মডেলের cross-entropy, সাধারণত nat-এ। সংখ্যাটিকে বেশি বোধগম্য করতে তার exp নেওয়া হয়, যার নাম perplexity:

Perplexity = eH(p, q) (cross-entropy nat-এ হলে; bit-এ হলে 2H)

এর একটি সুন্দর ব্যাখ্যা আছে: perplexity হলো “মডেল যেন গড়ে এতগুলো সমান সম্ভাব্য বিকল্পের মধ্যে দোটানায় আছে”। ন্যায্য ছক্কার entropy log2 6 bit, তাই perplexity 22.585 = 6: ঠিক ৬টি সমান বিকল্প। কোনো মডেলের cross-entropy 2.0 nat হলে perplexity e2 ≈ 7.39. কম perplexity মানে মডেল পরের token-এ কম অবাক হয়।

Perplexity তুলনায় সাবধান। দুটি মডেলের perplexity তুলনা করা যায় শুধু একই test লেখায় আর একই tokenizer-এ, কারণ token ছোট-বড় হলে প্রতি token-এর surprise বদলে যায়; ভিন্ন tokenizer-এর মডেল তুলনায় অনেকে প্রতি অক্ষর বা প্রতি byte-এর bit ব্যবহার করেন। আর কম perplexity মানে লেখার পরিসংখ্যান ভালো ধরা, তথ্যের সত্যতা বা উত্তরের উপযোগিতা নয়: কম perplexity-র মডেলও আত্মবিশ্বাসের সঙ্গে ভুল তথ্য বানাতে পারে (hallucination). LLM কীভাবে token ধরে ধরে পরের শব্দ অনুমান করে, তা পর্ব ৭০-এ।

পুরো পর্বের ধারণাগুলো পরের কোর্সের কোথায় ফিরে আসবে, এক নজরে:

ধারণাকোথায় ব্যবহার হয়কোন পর্বে
Log-probability, underflow এড়ানোNaive Bayes অনেক শব্দের সম্ভাবনা গুণ করে, তাই log যোগ করেপর্ব ৫১
Binary cross-entropy (log loss)Logistic regression-এর loss, যা gradient descent দিয়ে কমানো হয়পর্ব ৫৯
Log loss মূল্যায়নের মাপ হিসেবেAccuracy, precision, recall-এর পাশে সম্ভাবনার মান যাচাইপর্ব ৬০
Information gainDecision tree-র ভাগ বাছাইপরের মেশিন লার্নিং কোর্স
Mutual informationকোন feature লক্ষ্য সম্পর্কে বেশি বলে, তা বাছাইপর্ব ৬৩
Softmax-এর সঙ্গে cross-entropyNeural network-এর বহু-শ্রেণির classificationপর্ব ৬৮
Cross-entropy আর perplexityLanguage model-এর train আর মূল্যায়নপর্ব ৭০
KL divergenceDistribution-এর বদল (drift) মাপা, আর generative modelপরের deep learning ও generative AI কোর্স

পরীক্ষা ও ইন্টারভিউতে (বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এ ধরনের প্রশ্ন সাধারণ):

  • ৮টি সমান সম্ভাব্য ফলের entropy কত? log2 8 = 3 bit; k-টি ফলে এটিই সর্বোচ্চ।
  • (0.5, 0.25, 0.25)-এর entropy? 0.5 × 1 + 2 × 0.25 × 2 = 1.5 bit.
  • KL divergence কি দূরত্ব? না; এটি ≥ 0 আর p = q-তে 0, কিন্তু সমমিত নয় আর ত্রিভুজ-অসমতা মানে না।
  • আসল লেবেল 1, মডেল 0.5 বলেছে; log loss কত? ln 2 ≈ 0.693 nat (বা 1 bit).
  • Information gain আর mutual information-এর সম্পর্ক? একই: H(Y) − H(Y | X), যেখানে X হলো ভাগের feature.

১০ · AI-এর যুগে — AI সহকারীর লেখা loss আর entropy-র কোড যাচাই

“log loss হিসাব করো” বা “feature-গুলোর mutual information বের করো” লিখলে AI সহকারী দ্রুত কোড দেবে, আর এই বিষয়ে ভুলগুলো প্রায়ই চুপচাপ থাকে, কারণ ফল একটি বিশ্বাসযোগ্য সংখ্যা। AI সহকারীরা প্রায়ই যা করে:

  • এককের গোলমাল: np.log (nat), np.log2 (bit) আর log10 মিশিয়ে তুলনা; mutual_info_score-এর nat-কে bit বলা।
  • পুরোনো বা বানানো API: log_loss(..., eps=1e-15), যা scikit-learn-এর নতুন version-এ আর নেই (এখন dtype অনুযায়ী নিজেই clip করে); scipy.stats.kl_div-এর মতো অস্তিত্বহীন function; np.float বা pandas-এর append.
  • log(0) চাপা দেওয়া: clip ছাড়া np.log(p), তারপর np.nan_to_num বা সব warning বন্ধ, ফলে আত্মবিশ্বাসী ভুলগুলো হিসাব থেকে উধাও।
  • KL-এর দিক উল্টে ফেলা, KL-কে সমমিত “দূরত্ব” ধরা, q-তে শূন্যের কথা ভুলে যাওয়া।
  • ভুল ডেটায় মাপা: train ডেটাতেই log loss দেখানো, ভাগের আগে পুরো ডেটায় mutual information দিয়ে feature বাছাই (data leakage), ছোট নমুনার সংখ্যাকে নিশ্চিত সত্য বলা।

প্রম্পটে একক, version, ডেটার আকার আর যাচাইয়ের উপায় নিজেই বলে দিন। নিচের প্রম্পটে আসল ক্রেতার ডেটা নেই, শুধু বানানো ছোট উদাহরণ।

আমি Python 3.14, NumPy 2.x, SciPy 1.18 আর scikit-learn 1.8 ব্যবহার করছি। একটি binary classifier আছে, লেবেল y (1 = দেরি) আর মডেলের P(দেরি) p. বানানো উদাহরণ: y = [1, 1, 0, 1, 0], p = [0.9, 0.2, 0.1, 0.99, 1.0] ১. প্রতিটি সারির log loss nat-এ হিসাব করো, নিজের NumPy সূত্রে, p-কে np.finfo(float).eps দিয়ে clip করে; কেন clip লাগে এক লাইনে বলো। ২. sklearn.metrics.log_loss দিয়ে গড় মেলাও। আমার version-এ log_loss-এর কোন parameter আছে, অফিসিয়াল docs দেখে বলো; eps নামের parameter আছে কি না নিশ্চিত না হলে বলো। ৩. কোন সারিটি গড়কে সবচেয়ে বেশি বাড়াচ্ছে, দেখাও। ৪. সব সংখ্যার একক (nat না bit) স্পষ্ট লেখো। কোনো warning চাপা দেবে না। ৫. এই সংখ্যাগুলো test ডেটার না train ডেটার হওয়া উচিত, আর কেন, বলো। কোনো function বা parameter নিয়ে নিশ্চিত না হলে বানিয়ে লিখবে না, সরাসরি বলবে।

AI-এর কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১Shannon কেন একটি টেলিফোন কোম্পানির প্রশ্ন থেকে এমন একটি তত্ত্বে পৌঁছালেন, যা আজ AI-এর loss function-এ বসে আছে?

১৯৪০-এর দশকে Bell Labs-এর সামনে ব্যবহারিক প্রশ্ন ছিল: একটি তারে কত দ্রুত বার্তা পাঠানো যায়, আর শব্দ (noise) থাকলে কতটা নির্ভুলভাবে? প্রকৌশলীরা তখন প্রতিটি মাধ্যমের (টেলিগ্রাফ, টেলিফোন, রেডিও, টেলিভিশন) জন্য আলাদা কৌশল ভাবতেন। Shannon-এর মূল পদক্ষেপ ছিল বার্তার অর্থ পুরোপুরি বাদ দেওয়া। তাঁর ১৯৪৮ সালের প্রবন্ধে তিনি স্পষ্ট লেখেন, বার্তার অর্থগত দিক প্রকৌশলের সমস্যার সঙ্গে প্রাসঙ্গিক নয়; গুরুত্বপূর্ণ হলো বার্তাটি সম্ভাব্য বার্তাগুলোর একটি সেট থেকে বাছা হয়েছে।

এই বিমূর্ততা থেকে দুটি বড় ফল আসে। প্রথমটি source coding: উৎসের entropy হলো তার বার্তা লেখার গড় bit-এর নিচের সীমা, যা থেকে আজকের ZIP, PNG আর ছবি-ভিডিওর compression-এর তাত্ত্বিক ভিত্তি এসেছে। দ্বিতীয়টি channel capacity: noise থাকা সত্ত্বেও একটি নির্দিষ্ট হারের নিচে প্রায় নির্ভুল যোগাযোগ সম্ভব, যদি কোড যথেষ্ট চালাক হয়। এই দ্বিতীয় ফল সে সময় অনেকের কাছে প্রায় অবিশ্বাস্য ছিল, আর পরের কয়েক দশক ধরে error-correcting code-এর গবেষণা সেই সীমার কাছে পৌঁছানোর চেষ্টা করেছে।

AI-তে এর পথটি পরোক্ষ কিন্তু সরাসরি। Shannon নিজেই ১৯৫১ সালে “Prediction and Entropy of Printed English” প্রবন্ধে মানুষকে পরের অক্ষর আন্দাজ করতে দিয়ে ইংরেজির প্রতি অক্ষরের entropy অনুমান করেন, আর সেখানে আগের অক্ষরগুলো দেখে পরেরটি আন্দাজ করার ধারণা আসে, যা আজকের language model-এর মূল কাজ। পরিসংখ্যানে Kullback আর Leibler ১৯৫১ সালে KL divergence প্রকাশ করেন; পরে speech recognition গবেষণায় perplexity মাপ হিসেবে জনপ্রিয় হয়। মেশিন লার্নিং যখন সম্ভাবনার মডেল হয়ে উঠল, তখন “মডেলটি ডেটাকে কতটা কম খরচে লিখতে পারে” প্রশ্নটি আর “মডেলের likelihood কত” প্রশ্নটি একই হয়ে গেল (অংশ ৮-এর আরও গভীরে)।

মূল উপলব্ধি: বার্তার অর্থ বাদ দিয়ে শুধু সম্ভাবনা রাখার সাহসী বিমূর্ততাই information theory-কে টেলিফোনের তার থেকে compression, পরিসংখ্যান আর language model পর্যন্ত নিয়ে গেছে।

প্র ০২Information theory-র entropy আর পদার্থবিদ্যার entropy কি একই জিনিস? নামটি কোথা থেকে এল?

পদার্থবিদ্যায় entropy শব্দটি উনিশ শতকে Rudolf Clausius তাপগতিবিদ্যায় আনেন। পরে Ludwig Boltzmann আর J. Willard Gibbs একে পরিসংখ্যানের ভাষায় লেখেন: একটি গ্যাসের বড় মাপের অবস্থা (তাপমাত্রা, চাপ) কত রকম আণবিক বিন্যাসে ঘটতে পারে, তার logarithm. Gibbs-এর সূত্র দেখতে প্রায় হুবহু Shannon-এর মতো: −k ∑ pi ln pi, যেখানে k হলো Boltzmann ধ্রুবক, আর pi হলো একটি আণবিক অবস্থার সম্ভাবনা।

নামটি নিয়ে একটি বহুল প্রচলিত গল্প আছে। Myron Tribus-এর পরের এক লেখায় বর্ণনা আছে যে Shannon তাঁকে বলেছিলেন, John von Neumann পরামর্শ দেন এটিকে entropy বলতে, দুটি কারণে: সূত্রটি পরিসংখ্যানগত বলবিদ্যায় এই নামে আগেই আছে, আর “কেউ আসলে জানে না entropy কী, তাই বিতর্কে আপনিই সুবিধা পাবেন।” গল্পটি সেকেন্ডহ্যান্ড, আর Shannon নিজে অন্য সাক্ষাৎকারে বিষয়টি নিয়ে ভিন্নভাবে বলেছেন বলে শোনা যায়; তাই এটিকে ঐতিহাসিক সত্যের চেয়ে প্রচলিত কাহিনি হিসেবে নেওয়াই নিরাপদ।

গাণিতিক মিল কি গভীর সম্পর্ক? ১৯৫৭ সালে E. T. Jaynes যুক্তি দেন যে পরিসংখ্যানগত বলবিদ্যাকে information theory-র “maximum entropy” নীতি দিয়ে দেখা যায়: যা জানা আছে (যেমন গড় শক্তি) তার শর্ত মেনে সবচেয়ে বেশি entropy-র distribution বেছে নিন, কারণ তা অজানা কিছু ধরে নেয় না। অংশ ২-এর “শর্ত না থাকলে uniform সবচেয়ে বেশি entropy” এই নীতির সবচেয়ে সরল রূপ। মেশিন লার্নিংয়ে maximum entropy মডেল (যা logistic regression-এর আত্মীয়) এই ধারণা থেকেই এসেছে।

তবু পার্থক্য আছে। পদার্থবিদ্যার entropy একটি ভৌত রাশি, এককসহ (জুল প্রতি কেলভিন), আর দ্বিতীয় সূত্র মেনে বিচ্ছিন্ন ব্যবস্থায় কমে না। Shannon-এর entropy একটি সম্ভাবনার মডেলের ধর্ম; আপনি মডেল বদলালে তা বদলায়, আর এতে কোনো “বাড়তেই হবে” সূত্র নেই। দুটিকে এক বলে চালানো জনপ্রিয় লেখায় বিভ্রান্তি তৈরি করে।

মূল উপলব্ধি: সূত্রের রূপ এক আর maximum entropy-র মতো সেতু আছে, কিন্তু একটি ভৌত রাশি, অন্যটি সম্ভাবনার মডেলের মাপ; নামের গল্পটি প্রচলিত হলেও যাচাই-করা ইতিহাস নয়।

প্র ০৩Classifier train করতে accuracy নয়, cross-entropy কেন? আর log loss-এর কঠোর শাস্তি কি সবসময় ভালো?

প্রথম কারণ গাণিতিক। Accuracy একটি সিঁড়ির মতো function: মডেলের weight সামান্য বদলালে বেশিরভাগ সময় কোনো ভবিষ্যদ্বাণী 0.5-এর সীমা পার হয় না, তাই accuracy একটুও বদলায় না, আর তার derivative প্রায় সব জায়গায় শূন্য। Gradient descent (পর্ব ৩৪) তাই কোন দিকে যাবে বুঝতে পারে না। Cross-entropy মসৃণ: p̂ 0.6 থেকে 0.61 হলেও loss একটু কমে, আর gradient দিক দেখায়। Logistic regression-এ cross-entropy-কে sigmoid-এর সঙ্গে মেলালে loss convex হয়, মানে একটিমাত্র নিচু জায়গা, যা পর্ব ৫৯-এ দেখবেন।

দ্বিতীয় কারণ পরিসংখ্যানগত। অংশ ৮-এর আরও গভীরে দেখিয়েছে, cross-entropy কমানো মানে maximum likelihood. আর log loss একটি “strictly proper scoring rule”: গড় loss সবচেয়ে কম হয় যখন মডেল আসল সম্ভাবনাটিই বলে। এর ফলে মডেলের দেওয়া সংখ্যা সম্ভাবনা হিসেবে অর্থপূর্ণ হওয়ার একটি তাত্ত্বিক ভিত্তি পায় (calibration), যদিও বাস্তবে বড় neural network প্রায়ই অতি-আত্মবিশ্বাসী হয়, যা ২০১৭ সালের আশেপাশে Guo ও সহকর্মীদের একটি পরিচিত গবেষণায় দেখানো হয়েছিল।

কিন্তু কঠোর শাস্তির দাম আছে। ডেটায় কিছু লেবেল ভুল হলে (ধরুন কুরিয়ারের অ্যাপে ভুল করে “দেরি” লেখা হয়েছে), মডেল সেই অর্ডারে আত্মবিশ্বাসী আর ঠিক থাকলেও বিশাল loss পায়, আর train-এর সময় সেই ভুল লেবেলগুলো মডেলকে অকারণে টানে। এ জন্য label smoothing (লক্ষ্য 1-এর বদলে 0.9-এর মতো) বা focal loss-এর মতো বিকল্প এসেছে। মূল্যায়নেও একটি মাত্র আত্মবিশ্বাসী ভুল গড়কে ছাপিয়ে যায় (অংশ ৮-এ 1.27 থেকে 7.07), যা কখনো ঠিক, কখনো বিভ্রান্তিকর। তাই অনেকে log loss-এর পাশে Brier score (সম্ভাবনা আর লেবেলের পার্থক্যের বর্গের গড়), accuracy আর calibration chart-ও দেখেন।

মূল উপলব্ধি: Cross-entropy মসৃণ, likelihood-এর সঙ্গে যুক্ত আর সৎ সম্ভাবনাকে পুরস্কার দেয়, তাই train-এর স্বাভাবিক পছন্দ; কিন্তু ভুল লেবেল আর একক বড় ভুলের প্রতি এর সংবেদনশীলতা মাথায় রেখে পাশাপাশি অন্য মাপও দেখুন।

প্র ০৪“Compression মানেই বুদ্ধি” — এই দাবিটি কতটা সত্য?

দাবিটির পেছনে একটি সত্যিকারের গাণিতিক সম্পর্ক আছে। যে মডেল পরের চিহ্নের সম্ভাবনা ভালো আন্দাজ করে, তাকে arithmetic coding-এর সঙ্গে মেলালে লেখা ছোট হয়: প্রতিটি চিহ্নের খরচ −log2 q(চিহ্ন) bit. তাই কম cross-entropy মানে ভালো compression, আর উল্টোটাও। এই সম্পর্ক থেকেই Marcus Hutter ২০০৬ সালে একটি পুরস্কার ঘোষণা করেন, যেখানে Wikipedia-র একটি নির্দিষ্ট অংশ যত ছোট করা যায়, তার জন্য টাকা; তাঁর যুক্তি ছিল ভালো compression-এর জন্য লেখার জগৎ সম্পর্কে ভালো মডেল লাগে। তাত্ত্বিক দিকে Ray Solomonoff আর Andrey Kolmogorov-এর ১৯৬০-এর দশকের কাজ (একটি ডেটার সবচেয়ে ছোট বর্ণনার দৈর্ঘ্য) আর Jorma Rissanen-এর minimum description length নীতিও বলে: যে ব্যাখ্যা ডেটাকে সবচেয়ে কম কথায় বলে, তা প্রায়ই সবচেয়ে ভালো সাধারণীকরণ করে।

এবার সীমাগুলো। এক, অংশ ৪-এর zlib-এর উদাহরণই দেখায় compression মডেলের ধরনের ওপর নির্ভর করে; “bKashCOD”-এর পুনরাবৃত্তি zlib ধরেছে কিন্তু প্রতি-অক্ষরের entropy ধরেনি। কোন কাঠামো খোঁজা হবে, তা মডেলের নকশা ঠিক করে। দুই, ভালো আন্দাজ মানে পরিসংখ্যানের নিয়মিততা ধরা; তা থেকে লক্ষ্য ঠিক করা, পরিকল্পনা, নতুন পরিস্থিতিতে কারণ খোঁজা বা নিজের ভুল জানা আপনা থেকে আসে না। তিন, ডেটায় যা নেই (বাংলার মতো কম-সম্পদের ভাষার অনেক বিষয়), তা compress করে শেখা যায় না। চার, কম loss-এর মডেলও বিশ্বাসযোগ্য মিথ্যা বানাতে পারে, কারণ সম্ভাব্য বাক্য আর সত্য বাক্য এক নয়।

তাই “বুদ্ধি” শব্দটি এখানে সাবধানে ব্যবহার করা ভালো। Compression একটি দরকারি ক্ষমতার মাপ, AI গবেষণায় অনেকে একে বুদ্ধির একটি উপাদান মনে করেন, কিন্তু সম্প্রদায়ের মধ্যে এ নিয়ে মতভেদ আছে, আর একে বুদ্ধির পূর্ণ সংজ্ঞা বলার পক্ষে প্রমাণ নেই।

মূল উপলব্ধি: ভালো ভবিষ্যদ্বাণী আর ভালো compression গাণিতিকভাবে এক মুদ্রার দুই পিঠ, কিন্তু তা বুদ্ধির পুরো সংজ্ঞা নয়; দাবিটিকে একটি উপযোগী দৃষ্টিভঙ্গি হিসেবে নিন, প্রমাণিত সত্য হিসেবে নয়।

প্র ০৫Decision tree-র information gain অনেক মানের feature পছন্দ করে কেন, আর বাস্তবে এর প্রতিকার কী?

ধরুন বাজারঘরের ২৪টি অর্ডারে একটি কলাম আছে order_id, প্রতিটির আলাদা। এটি দিয়ে ভাগ করলে ২৪টি সন্তান, প্রতিটিতে একটি অর্ডার, আর একটি অর্ডারের দলে দেরি নিয়ে কোনো অনিশ্চয়তা নেই: সবার entropy 0. তাই IG = H(Y) − 0 = 0.954 bit, জেলার 0.161-এর প্রায় ছয় গুণ! অথচ নতুন অর্ডারের id আগে কখনো দেখা যায়নি, তাই এই “নিয়ম” ভবিষ্যদ্বাণীতে শূন্য সাহায্য করে। এটি overfitting-এর একটি চরম রূপ: মডেল train ডেটা মুখস্থ করেছে।

গণিতের দিক থেকে কারণটি অংশ ৬-এর সাবধানতা: ছোট নমুনায় mutual information ধনাত্মক দিকে পক্ষপাতদুষ্ট, আর feature-এর মান যত বেশি, প্রতিটি দলে নমুনা তত কম, পক্ষপাত তত বেশি। পুরো স্বাধীন একটি random কলামও অনেক মান থাকলে কিছু IG দেখাবে।

Ross Quinlan-এর ID3 (১৯৮৬ সালের প্রবন্ধ) এই সমস্যায় পড়ত; তাঁর পরের C4.5 (১৯৯৩ সালের বই) gain ratio আনে: IG-কে ভাগের নিজের entropy (split information) দিয়ে ভাগ করা, যাতে অনেক ছোট দলে ভাঙা feature শাস্তি পায়। প্রায় একই সময়ে Breiman ও সহকর্মীদের CART (১৯৮৪) শুধু দুই ভাগে (binary) ভাঙে আর Gini impurity ব্যবহার করে, যা এই পক্ষপাত কিছুটা কমায়। আধুনিক অনুশীলনে আরও কয়েকটি স্তরে প্রতিরক্ষা থাকে: গাছের গভীরতা আর পাতার ন্যূনতম নমুনা সীমিত রাখা, validation ডেটায় যাচাই, আর id-র মতো কলাম feature থেকে বাদ দেওয়া (পর্ব ৬৩)। আর এলোমেলো ভাগের পরিসংখ্যানগত পরীক্ষা (permutation test) দিয়ে দেখা যায়, পাওয়া IG কি শুধু ভাগ্যের ফল।

একটি বৃহত্তর শিক্ষা আছে: একটি মাপ যখন লক্ষ্য হয়ে যায়, তখন তা নিজের দুর্বলতার সুযোগ নেওয়া সমাধানকে পুরস্কার দেয় (Goodhart's law). IG-কে অন্ধভাবে সর্বোচ্চ করা ঠিক সেটিই করে।

মূল উপলব্ধি: Information gain train ডেটার অনিশ্চয়তা কমা মাপে, নতুন ডেটায় কাজের ক্ষমতা নয়; gain ratio, binary ভাগ, গাছের আকার সীমিত রাখা আর validation দিয়ে এই ফাঁক বন্ধ করতে হয়।

অনুশীলন

প্রথমটি হাতে হিসাবের, দ্বিতীয়টি আউটপুট অনুমানের, তৃতীয়টি নকশা ও সমালোচনার, শেষটি কোড লিখে একটি ভাগ বাছাইয়ের।

  1. হাতে হিসাব: একটি দোকানে তিন রকম পেমেন্টের ভাগ p = (0.5, 0.25, 0.25). (ক) H(p) bit আর nat-এ। (খ) একজন ধরলেন সব সমান, q = (⅓, ⅓, ⅓); H(p, q), KL(p ‖ q) আর KL(q ‖ p) bit-এ। (গ) একটি অর্ডারে আসল লেবেল y = 1 আর মডেলের P = 0.8; log loss nat-এ। একই P-তে y = 0 হলে?

    (ক) 0.5 × 1 + 0.25 × 2 + 0.25 × 2 = 1.5 bit; nat-এ 1.5 × 0.6931 ≈ 1.0397.

    (খ) প্রতিটি −log2 ⅓ = log2 3 ≈ 1.585, তাই H(p, q) = 1.585 bit. KL(p ‖ q) = 1.585 − 1.5 = 0.0850 bit. KL(q ‖ p) = ⅓ [log2(⅓ ÷ 0.5) + 2 log2(⅓ ÷ 0.25)] = ⅓ [−0.585 + 2 × 0.415] ≈ 0.0817 bit. দুটি আলাদা, যেমন প্রত্যাশিত।

    (গ) y = 1: −ln 0.8 ≈ 0.2231 nat. y = 0: −ln(1 − 0.8) = −ln 0.2 ≈ 1.6094 nat, প্রায় সাত গুণ।

    লেখকের যন্ত্রে SciPy দিয়ে মেলানো: 1.5 1.0397 1.585 0.085 0.0817 0.2231 1.6094.

  2. আউটপুট অনুমান করুন: চালানোর আগে প্রতিটি লাইনের আউটপুট লিখুন, তারপর যেকোনো ব্লকে বসিয়ে মিলিয়ে দেখুন।
    import numpy as np
    from scipy.stats import entropy
    print(entropy([1, 1, 1, 1], base=2))
    print(entropy([0.9, 0.1]) > entropy([0.5, 0.5]))
    print(entropy([2, 2]), np.log(2))
    print(np.log(0.0), -np.log2(1/8))
    2.0
    False
    0.6931471805599453 0.6931471805599453
    -inf 3.0

    প্রথম লাইন: গণনা [1, 1, 1, 1] নিজেই ভাগ হয়ে চারটি ¼, তাই log2 4 = 2. দ্বিতীয়: হেলানো মুদ্রার entropy ন্যায্য মুদ্রার চেয়ে কম, তাই False. তৃতীয়: base না দিলে nat; [2, 2] ভাগ হয়ে (½, ½), যার entropy ln 2, তাই দুটি সংখ্যা হুবহু এক। চতুর্থ: log(0) = −inf, আর ⅛-এর surprise 3 bit. সাধারণ Python-এ চতুর্থ লাইনের আগে একটি RuntimeWarning: divide by zero আসে; ব্রাউজারের Python-এ আসে না (লেখকের যন্ত্রে মেলানো, SciPy 1.18.1).

  3. নকশা ও সমালোচনা: একজন সহকর্মী (বা একটি AI সহকারী) বাজারঘরের দেরির মডেলের রিপোর্টের জন্য নিচের কোড লিখেছেন। অন্তত পাঁচটি সমস্যা চিহ্নিত করে একটি ভালো সংস্করণের নকশা বলুন।
    import warnings; warnings.filterwarnings("ignore")
    def report(model, X_train, y_train):
        p = model.predict_proba(X_train)[:, 1]
        loss = -np.mean(y_train * np.log10(p) + (1 - y_train) * np.log10(1 - p))
        loss = np.nan_to_num(loss)
        kl = entropy(p_model_mix, p_true_mix)    # "দূরত্ব", দিক যা-ই হোক
        return {"log_loss_bits": loss, "distance": kl}
    • Train ডেটায় মাপা: মডেল যা দেখে শিখেছে, তাতেই loss মাপলে ফল অতিরিক্ত ভালো আসে; লাগবে আলাদা test ডেটা (পর্ব ৫৬)।
    • ভুল একক: log10 দিলে একক “hartley” (বা ban), অথচ নাম log_loss_bits. bit চাইলে log2, scikit-learn-এর সঙ্গে মেলাতে np.log (nat).
    • clip নেই: p = 0 বা 1 হলে log(0) = −inf, আর 0 × (−inf) = nan.
    • সমস্যা চাপা দেওয়া: global filterwarnings("ignore") আর nan_to_num মিলে nan-কে 0 (বা inf-কে বিশাল সংখ্যা) বানায়; সবচেয়ে খারাপ মডেলটিই হয়তো “নিখুঁত” loss দেখাবে।
    • KL-এর দিক উল্টো আর “দূরত্ব” বলা: আসল distribution প্রথমে, KL(ptrue ‖ pmodel); KL সমমিত নয়, তাই “distance” নামটি বিভ্রান্তিকর। p_model_mix-এ শূন্য থাকলে inf, তাও দেখা দরকার।
    • baseline নেই: সব অর্ডারে একই হার (train-এর দেরির ভাগ) বলা মডেলের log loss পাশে না থাকলে সংখ্যাটির অর্থ বোঝা যায় না।

    ভালো নকশা: report(model, X_test, y_test, base_rate); ভেতরে clip, sklearn.metrics.log_loss দিয়ে nat-এ মাপ, পাশে baseline, entropy(p_true, p_model, base=2) আর ফলের নামে একক (log_loss_nat, kl_bit). কোনো warning global ভাবে বন্ধ নয়।

  4. কোড লিখে ভাগ বাছাই: এই পর্বের ২৪টি অর্ডারের CSV (orders.csv, পাতায় আছে) পড়ে late = delivery_days > 3 বানান। তিনটি ভাগের information gain bit-এ বের করুন: (ক) জেলা, (খ) পেমেন্টের চার ধরন, (গ) “COD কি না” (দুই ভাগ)। Decision tree প্রথম প্রশ্ন হিসেবে কোনটি বাছবে?
    import pandas as pd
    from scipy.stats import entropy
    
    df = pd.read_csv("orders.csv")
    df["late"] = df["delivery_days"] > 3
    
    def info_gain(df, col, target="late"):
        H_parent = entropy(df[target].value_counts(), base=2)
        t = pd.crosstab(df[col], df[target])
        w = t.sum(axis=1) / len(df)
        H_child = entropy(t, base=2, axis=1)
        return H_parent - (w * H_child).sum()
    
    df["is_cod"] = df["payment"].eq("COD")
    for col in ["district", "payment", "is_cod"]:
        print(col, round(info_gain(df, col), 4))

    লেখকের যন্ত্রে সত্যিকারের run (pandas 3.1.1, SciPy 1.18.1): district 0.1609, payment 0.0208, is_cod 0.0036. জেলা অনেক এগিয়ে, তাই গাছ প্রথমে জেলা দিয়ে ভাগ করবে। পেমেন্টের ধরন দেরি সম্পর্কে প্রায় কিছুই বলে না (COD-এর ৭টির ৩টি দেরি, বাকি ১৭টির ৬টি)। ২৪টি অর্ডারে এসব সংখ্যা ভঙ্গুর; validation ডেটায় যাচাই না করে সিদ্ধান্ত নয়।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি feature দিয়ে ভাগের information gain হিসাবের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🎯 লক্ষ্য ঠিক করা: “দেরি” মানে delivery_days > 3
  2. 📊 ভাগের আগে পুরো দলের লক্ষ্যের entropy H(মা) হিসাব
  3. ✂️ feature-এর প্রতিটি মান ধরে দলটিকে সন্তানে ভাগ
  4. 🧮 প্রতিটি সন্তানের entropy আর তার ওজন nসন্তান/n বের করা
  5. ➖ H(মা) থেকে সন্তানদের ওজনসহ গড় entropy বিয়োগ
  6. 🏁 সব feature-এর IG তুলনা করে সবচেয়ে বড়টি বাছা, validation-এ যাচাই

একটি distribution-এর চারটি ফলের সম্ভাবনা (0.5, 0.25, 0.125, 0.125). এর entropy কত bit?

  • 2.0
  • 1.5
  • 1.75
  • 1.0

p আর q দুটি distribution. KL divergence KL(p ‖ q) সম্পর্কে কোন বাক্যটি সবসময় সত্য?

  • KL(p ‖ q) = KL(q ‖ p)
  • KL(p ‖ q) ≥ 0, আর p = q হলেই কেবল 0
  • যোগফল ঋণাত্মকও হতে পারে
  • q-তে কোনো ফলের সম্ভাবনা শূন্য হলেও KL সবসময় সীমিত থাকে

একটি binary classifier-এর একটি অর্ডারে আসল লেবেল 1, আর মডেল বলেছে P(1) = 0.01. এই অর্ডারের log loss (স্বাভাবিক log, nat) প্রায় কত?

  • 0.01
  • 0.99
  • 2.0
  • 4.605

২৪টি অর্ডারের মধ্যে ৯টি দেরি (H ≈ 0.954 bit). জেলা ধরে ভাগের পর সন্তানদের ওজনসহ গড় entropy 0.794 bit. এই ভাগের information gain কত, আর এটি আর কী নামে পরিচিত?

  • ≈ 0.161 bit; জেলা আর দেরির mutual information
  • ≈ 0.794 bit; conditional entropy
  • ≈ 1.748 bit; joint entropy
  • ≈ 1.20; KL divergence

NumPy-তে ৪০০টি স্বাধীন ঘটনার সম্ভাবনা, প্রতিটি 0.05, প্রথমে np.prod দিয়ে গুণ করে তারপর np.log নেওয়া হলো। ফল কী আসবে, আর ঠিক উপায় কী?

  • সঠিক মান প্রায় −1198.3; কোনো সমস্যা নেই
  • একটি OverflowError
  • −inf; ঠিক উপায় হলো log-গুলোর যোগ
  • nan; ঠিক উপায় হলো np.nan_to_num

একটি language model-এর একটি test লেখায় গড় cross-entropy প্রতি token 2.0 nat. এর perplexity প্রায় কত, আর এর একটি সঠিক ব্যাখ্যা কোনটি?

  • 4; মডেলটি ৪টি শব্দ জানে
  • ≈ 7.39; গড়ে প্রায় ৭.৪টি সমান বিকল্পের মতো অনিশ্চয়তা
  • 4.0; cross-entropy-র বর্গ
  • ≈ 7.39; মডেলের উত্তরের ৭.৩৯% ভুল

scikit-learn-এর mutual_info_score(x, y) দুটি category-র variable-এ 0.1115 দিয়েছে। একই ডেটায় হাতে H(Y) − H(Y | X) bit-এ হিসাব করলে কত পাওয়ার কথা?

  • 0.1115
  • ≈ 0.0773
  • 1 − 0.1115 = 0.8885
  • ≈ 0.1609

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. Surprise −log2 p: বিরল ঘটনায় বেশি তথ্য; log বলে স্বাধীন ঘটনার তথ্য যোগ হয়। Entropy গড় surprise, 0 থেকে log2 k, সর্বোচ্চ যখন সব ফল সমান।
  2. Log-এর ভিত্তি = একক: ২ হলে bit, e হলে nat (1 bit = 0.6931 nat). scipy.stats.entropy গণনা নিজেই ভাগ করে, base না দিলে nat; scikit-learn-ও nat দেয়।
  3. Entropy compression-এর নিচের সীমা; Huffman তার কাছে যায়, zlib কাঠামো খোঁজে, কিন্তু কোনো compressor নিখুঁত entropy মাপক নয়, আর ছোট নমুনা entropy কম দেখায়।
  4. H(p, q) = H(p) + KL(p ‖ q): KL ≥ 0, সমমিত নয়, q-তে শূন্য থাকলে অসীম। Mutual information = information gain = H(Y) − H(Y | X).
  5. Log loss = আসল ঘটনার surprise-এর গড়: আত্মবিশ্বাসী ভুলে বিশাল শাস্তি, তাই clip; গুণ নয়, log-এর যোগ। Cross-entropy কমানো = likelihood বাড়ানো; perplexity = ecross-entropy, শুধু একই লেখা আর tokenizer-এ তুলনীয়।

পরের পর্বে: মডিউল ৭ শুরু, search দিয়ে সমস্যা সমাধান। একটি সমস্যাকে state, action আর goal দিয়ে লেখা, যাতে কম্পিউটার রিকশার রুট থেকে ধাঁধা পর্যন্ত পথ খুঁজতে পারে।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান