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

Pretraining-এর লক্ষ্য — masked, causal আর span corruption

Pretraining Objectives — Masked Language Modeling, Causal Next-Token and Span Corruption

Taught in Bangla, this lesson shows how pretraining turns unlabeled text into targets, building masked language modeling batches with the 15 percent and 80/10/10 rule, causal next-token batches and T5-style span corruption in in-page NumPy. It proves why -100 drops positions from loss and gradient, then pretrains a small encoder and decoder on the same Bangla text in a real desktop run on the GPU and tests them against TF-IDF and random initialisation with 100 to 1,000 labels.

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

  • লেবেল ছাড়া লেখা থেকে লেবেল বানানোর তিনটি নিয়ম, আর কোন নিয়ম কোন model-কে মানায়
  • একটি বাংলা বাক্য থেকে হাতে আর NumPy-তে MLM, causal আর span corruption batch বানানো
  • -100 কেন loss আর gradient দুটি থেকেই বাদ যায়, আর ভুল হলে কী দেখা যায়
  • একই বাংলা লেখায় ছোট encoder আর decoder pretrain করা, আর ১০০, ৩০০, ১,০০০ label-এ সেগুলো যাচাই করা
  • বড় ডেটায় পরিষ্কার করা, যমজ বাদ দেওয়া আর লাইসেন্সের প্রশ্ন

১ · লেবেল কম, লেখা অনেক — pretraining কেন

বাজারঘরের কথা ভাবুন। রিভিউ জমেছে হাজারে হাজারে, কিন্তু “ভালো” না “খারাপ” হাতে দাগানো আছে মাত্র কয়েকশো। পর্ব ৪২-এ দেখেছি, শূন্য থেকে শেখা neural model এমন অবস্থায় TF-IDF-কে হারায়নি। Model-কে ভাষার প্রাথমিক নিয়ম আর শব্দের ধরনও শিখতে হয়েছে সেই অল্প label থেকেই।

প্রশ্নটি তাই এই: লেবেলহীন লেখাগুলো কি কোনো কাজে লাগানো যায়? লাগে, যদি লেখা থেকেই একটি প্রশ্ন আর তার উত্তর বানিয়ে নেওয়া যায়। একেই বলে self-supervised learning: লেবেল কোনো মানুষ দেন না, ডেটার একটি অংশ লুকিয়ে সেটিই লক্ষ্য বানানো হয়। এভাবে বড় লেখায় আগে থেকে শেখানোই pretraining. পরে অল্প লেবেলে আসল কাজে fine-tuning হয় (পর্ব ২৩)। প্রাথমিক ধারণার জন্য দেখুন AI-এর ভিত্তি কোর্সের পর্ব ৭০ আর পর্ব ৭১; লেবেল কম থাকার অন্য পথগুলো আছে মেশিন লার্নিং কোর্সের পর্ব ৫৪-এ।

নতুন কেউ দোকানে কাজ শিখছেন। ক্রেতার অভিযোগ বিচার করতে শেখানোর আগে মালিক তাঁকে পুরোনো হিসাব খাতা পড়তে দিলেন। মাঝে মাঝে একটি শব্দ ঢেকে জিজ্ঞেস করলেন, “এখানে কী লেখা ছিল?” অথবা বাক্যের অর্ধেক পড়ে থেমে বললেন, “এরপর কী আসবে?” খাতার কোথাও “ভালো অভিযোগ” বা “খারাপ অভিযোগ” লেখা নেই, তবু তিনি পণ্যের নাম, দাম আর বাক্যের ধরন চিনে ফেলছেন। Pretraining-এর কাজ অনেকটা এমন। এই উপমার সীমাও আছে: মানুষ অর্থ বোঝেন; model শুধু সংখ্যার নিয়মে পরিসংখ্যানগত ধরন ধরে।

একটি লক্ষ্যকে (pretraining objective) তিনটি কথা বলতে হয়: model-কে কী দেখানো হবে (input), কী অনুমান করতে হবে (target), আর কোন ঘরের loss গোনা হবে না (mask). পুরো পর্বে এই তিনটিই আমরা হাতে লিখব। নিচের ছবিতে একই ছয়টি token তিন নিয়মে; w1, w2 মানে প্রথম, দ্বিতীয় token.

একই লেখা, তিনটি প্রশ্ন

Three ways to make input and target from the same six tokens

Input আর target: MLM, causal, span corruption MLM (encoder) input label w1MASKw3w4MASKw6 -100-100-100-100 w2w5 Causal (decoder) input label BOSw1w2w3w4w5 w1w2w3w4w5w6 Span corruption (encoder + decoder) input w1X0w4w5X1 target X0w2w3X1w6X2 Amber: mask/sentinel. Label/target: predicted.
ছবি ৪৭.১ — তিনটি লক্ষ্য একই লেখা থেকে আসে। পার্থক্য: model কোন দিক দেখতে পায়, আর কতগুলো ঘরের loss গোনা হয়।

২০১৮ সালে দুটি প্রবন্ধ এই ভাগটি স্পষ্ট করে: Radford ও সহকর্মীদের GPT প্রবন্ধ “Improving Language Understanding by Generative Pre-Training”, আর Devlin ও সহকর্মীদের BERT (arXiv-এ অক্টোবর ২০১৮)। GPT প্রবন্ধে ৭,০০০-এর বেশি অপ্রকাশিত বইয়ের BooksCorpus-এ language model শেখানো হয়। লেখকদের প্রতিবেদন অনুযায়ী, pretraining বাদ দিলে একই model-এর গড় ফল সব কাজে কমে যায় (তাঁদের মাপে ১৪.৮%). পরের বছর Raffel ও সহকর্মীদের T5 প্রবন্ধ (প্রকাশ JMLR ২০২০) denoising ধরনের লক্ষ্যগুলো পাশাপাশি যাচাই করে।

২ · Masked language modeling — ১৫% আর ৮০/১০/১০

পর্ব ৪৪-এ masked language modeling (MLM) এক লাইনে দেখেছি। এবার যন্ত্রটি খুলি। BERT প্রবন্ধ অনুযায়ী নিয়মটি দুই ধাপের:

  1. বাছাই: token-এর ঘরগুলোর ১৫% এলোমেলোভাবে বাছা হয়। Special token আর padding বাছা হয় না। এটি প্রচলিত বাস্তবায়নের নিয়ম; নিচে Hugging Face-এর আসল collator-এ মিলিয়ে দেখব।
  2. বদল: বাছা ঘরের ৮০%-এ বসে [MASK], ১০%-এ এলোমেলো একটি token, আর ১০% যেমন ছিল তেমন।

Label সব ক্ষেত্রেই আসল token. না-বাছা সব ঘরের label হয় -100, মানে সেখানে loss গোনা হবে না।

১০% আর ১০% কেন? [MASK] token শুধু pretraining-এ থাকে; fine-tuning-এ আসে না। BERT প্রবন্ধ এই অমিলটি নিজেই স্বীকার করে। সব সময় [MASK] বসালে model শিখতে পারত, “ঢাকা ঘর ছাড়া অন্য কিছু নিয়ে ভাবার দরকার নেই।” এলোমেলো আর অপরিবর্তিত ঘর থাকায় সে জানে না কোন ঘর পরীক্ষা হবে, তাই প্রতিটি ঘরের ভালো vector রাখতে হয়। অপরিবর্তিত ঘরটি vector-কে আসল শব্দের দিকে একটু টানে; প্রবন্ধের ভাষায় “bias the representation towards the actual observed word”.

u = বাছাইয়ের সংখ্যা, v = বদলের সংখ্যা; ─ মানে -100
ধাপ
কাজ
label
  1. বাক্য. [CLS] আর [SEP] মিলিয়ে ১১টি ঘর, তাতে আসল token ৯টি। Special token দুটি কখনো বাছাই হয় না।
  2. বাছাই. প্রতিটি আসল token-এর জন্য একটি এলোমেলো সংখ্যা u. এখানে ৩টি ঘরে u < ০.১৫. ১৫% মানে গড়ে ১.৩৫টি; এই বাক্যে ৩টি উঠেছে, কারণ প্রতি ঘরের সিদ্ধান্ত স্বাধীন। এমন ওঠানামা স্বাভাবিক।
  3. প্রথম ঘর. “রং”-এ v = ০.৩৫, তাই [MASK]. Label এখন 5, মানে “রং”-এর id.
  4. দ্বিতীয় ঘর. “সুন্দর”-এ v = ০.৮৫, তাই এলোমেলো একটি token বসল, “দাম”। Input দেখে ঠিকঠাক বাক্য মনে হয়, কিন্তু label 8, মানে আসল “সুন্দর”।
  5. তৃতীয় ঘর. “ডেলিভারি”-তে v = ০.৯৫, তাই input বদলায় না, তবু label 10 থাকে। এই ঘরে model-এর সামনে উত্তর খোলা, আর তাকে সেটিই বলতে হয়।
  6. ফল. Input-এ শুধু একটি [MASK] দেখা যাচ্ছে, অথচ তিনটি ঘর পরীক্ষা হবে। Input দেখে model বলতে পারে না সেগুলো কোনটি।
  7. Loss. ধরুন আসল token-কে model দিল ০.৫, ০.২, ০.৪ probability. Cross-entropy-র গড় হয় শুধু ৩টি ঘরে: ৩.২১৮৮ ÷ ৩ = ১.০৭২৯. বাকি ৮টি ঘরের কোনো ভূমিকা নেই।

এবার একই নিয়ম NumPy-তে। প্রথমে একটি বাক্য, তারপর ২,০০,০০০ সারিতে গুনে দেখি অনুপাতগুলো ঠিক আছে কি না। এই ধরনের গোনা পরীক্ষা ছাড়া collator-এর ভুল প্রায়ই ধরা পড়ে না, কারণ ভুল collator-ও দিব্যি চলে। প্রথমবার চালাতে পাতায় Python লোড হতে কয়েক সেকেন্ড লাগতে পারে।

import numpy as np
vocab = ["[PAD]", "[CLS]", "[SEP]", "[MASK]", "শাড়ির", "রং", "ছবির", "মতোই",
         "সুন্দর", "আর", "ডেলিভারি", "দ্রুত", "ছিল", "দাম", "কম"]
PAD, CLS, SEP, MASK, FIRST = 0, 1, 2, 3, 4

def mlm_batch(x, rng, p=0.15):
    pick = (rng.random(x.shape) < p) & (x >= FIRST)   # special ঘর বাদ
    y = np.where(pick, x, -100)                       # বাকি সব ঘর: -100
    u = rng.random(x.shape)
    inp = x.copy()
    inp[pick & (u < 0.8)] = MASK
    rnd = pick & (u >= 0.8) & (u < 0.9)
    inp[rnd] = rng.integers(FIRST, len(vocab), x.shape)[rnd]
    return inp, y                                     # বাকি ১০%: যেমন ছিল

x = np.array([1, 4, 5, 6, 7, 8, 9, 10, 11, 12, 2, 0, 0])
inp, y = mlm_batch(x[None], np.random.default_rng(5), p=0.3)
print("input :", " ".join(vocab[i] for i in inp[0]))
print("labels:", y[0].tolist())

X = np.tile(x, (200_000, 1))
inp, y = mlm_batch(X, np.random.default_rng(42))
pick, real = y != -100, X >= FIRST
print(f"selected share  {pick.sum() / real.sum():.4f}  (target 0.15)")
print(f"[MASK] share    {(inp[pick] == MASK).mean():.4f}  (0.80)")
print(f"unchanged share {(inp[pick] == X[pick]).mean():.4f}  (0.10 + 0.10/11 = {0.1 + 0.1 / 11:.4f})")
print("special selected:", int(pick[~real].sum()), "| labels match:", bool((y[pick] == X[pick]).all()))

আউটপুট (পাতার আসল run):

input : [CLS] শাড়ির রং [MASK] [MASK] সুন্দর আর [MASK] [MASK] ছিল [SEP] [PAD] [PAD]
labels: [-100, -100, -100, 6, 7, -100, -100, 10, 11, -100, -100, -100, -100]
selected share  0.1499  (target 0.15)
[MASK] share    0.7993  (0.80)
unchanged share 0.1099  (0.10 + 0.10/11 = 0.1091)
special selected: 0 | labels match: True

এখানে একটি বাক্যে দেখানোর জন্য p = ০.৩ নিয়েছি, তাই ৪টি ঘর বাছা পড়েছে। নিচের গোনা হয়েছে আসল p = ০.১৫-এ। লক্ষ করুন “unchanged” ঠিক ০.১০ নয়। এলোমেলো token-এর ১১টি সম্ভাব্য মানের একটি আসল token নিজেই, তাই ০.১০ + ০.১০/১১ ≈ ০.১০৯১. আসল vocabulary-তে এই বাড়তি অংশ খুব ছোট; ছোট খেলনা vocabulary-তে চোখে পড়ে।

এবার আসল library. নিচের run-এ Hugging Face-এর DataCollatorForLanguageModeling আর sagorsarker/bangla-bert-base-এর tokenizer (লাইসেন্স MIT, model card অনুযায়ী) ব্যবহার করেছি। শুধু tokenizer-এর ফাইল নামানো হয়েছে, model নয়। do_lower_case=False দেওয়া জরুরি, নইলে হসন্ত আর কার মুছে যায় (পর্ব ৪৪)।

সত্যিকারের run (CPU-তে; transformers 5.22.2, Python 3.14.4). কোড ছোট করে দেখানো হয়েছে:

$ uv run python real_tools.py
tk = AutoTokenizer.from_pretrained("sagorsarker/bangla-bert-base", do_lower_case=False)
col = DataCollatorForLanguageModeling(tk, mlm=True, mlm_probability=0.15, seed=42)
…
['শাড়ির', 'রং', 'ছবির', 'মতোই', 'সুন্দর', 'আর', 'ডেলিভারি', 'দ্রুত', 'ছিল']
selected 5296 of 36000 real tokens = 0.147
[MASK] share 0.799 | unchanged share 0.101 | special selected 0
row 0 input : ['[CLS]', 'শাড়ির', 'রং', 'ছবির', 'মতোই', 'সুন্দর', 'আর', 'ডেলিভারি', 'দ্রুত', '[MASK]', '[SEP]']
row 0 labels: [-100, -100, -100, -100, -100, -100, -100, -100, -100, 2072, -100]

আমাদের হাতের নিয়ম আর library একই আচরণ করছে: special ঘর একবারও বাছা হয়নি, আর অনুপাতগুলো কাছাকাছি। ০.১৪৭ আর ০.১৫-এর ফারাক আকারে ছোট; ৩৬,০০০ ঘরের জন্য প্রমাণ বিচ্যুতি প্রায় ০.০০২, তাই এটি প্রায় ১.৫ গুণ, এলোমেলো ওঠানামার মধ্যে পড়ে।

৩ · Causal next-token — এক ঘর সরানো লক্ষ্য

GPT ধরনের model-এর লক্ষ্য আরও সরল: প্রতিটি ঘরে পরের token অনুমান। লেবেল বানাতে কোনো এলোমেলো কিছু লাগে না, শুধু এক ঘর সরানো। পর্ব ৩৬-এ অক্ষরে এটি করেছি। এবার batch আর padding-সহ, কারণ ভুল সবচেয়ে বেশি হয় সেখানেই। Causal mask ভবিষ্যতের ঘর আটকায় (পর্ব ৩৩); না থাকলে model শুধু পরের ঘর দেখে নকল করত।

vocabulary: 0 [PAD], 1 [BOS], 2 [EOS], 3 শাড়ির, 4 রং, 5 ছবির, 6 মতোই
ধাপ
input
label
  1. x. “শাড়ির রং ছবির মতোই” বাক্য, দুই প্রান্তে [BOS], [EOS], আর শেষে একটি [PAD], কারণ একই batch-এর অন্য সারি লম্বা।
  2. Input. শেষ ঘর বাদ। দৈর্ঘ্য ৬.
  3. Label. প্রথম ঘর বাদ। দৈর্ঘ্যও ৬. এখন প্রতি ঘরে label হলো input-এর পরের token. Model এই সরানোর কাজ নিজে করে না; কোথায় হয়, তা library ভেদে আলাদা (অংশ ৯)।
  4. Padding. শেষ label ছিল 0, মানে [PAD]. এটি -100 হলো। নইলে model-কে শেখানো হতো [EOS]-এর পরে padding অনুমান করতে।
  5. একটি ঘর. t = ২-এ model দেখে শুধু প্রথম তিনটি ঘর, লক্ষ্য “ছবির” (5). Causal mask বাকি ঘর আটকে রাখে।
  6. Loss. ধরুন আসল token-এর probability ০.৩, ০.৫, ০.২, ০.৪, ০.৬. −ln-এর যোগফল ৪.৯৩৩৬; ৫ দিয়ে ভাগে ০.৯৮৬৭. MLM-এ ১১টি ঘরে ৩টি গোনা হয়েছিল; এখানে ৬টিতে ৫টি। Causal লক্ষ্য একটি সারি থেকে অনেক বেশি অনুমান পায়।
import numpy as np
vocab = ["[PAD]", "[BOS]", "[EOS]", "শাড়ির", "রং", "ছবির", "মতোই", "সুন্দর", "ডেলিভারি", "দ্রুত"]
PAD = 0

def causal_batch(x):
    inp = x[:, :-1]                 # শেষ ঘর ইনপুটে নয়
    y = x[:, 1:].copy()             # প্রথম ঘর লক্ষ্যে নয়
    y[y == PAD] = -100              # padding-এর জন্য কোনো loss নয়
    return inp, y

x = np.array([[1, 3, 4, 5, 6, 7, 2],     # শাড়ির রং ছবির মতোই সুন্দর
              [1, 8, 9, 2, 0, 0, 0]])    # ডেলিভারি দ্রুত
inp, y = causal_batch(x)
print("shape x", x.shape, "| inp", inp.shape, "| y", y.shape)
for r in range(2):
    for t in range(inp.shape[1]):
        to = vocab[y[r, t]] if y[r, t] != -100 else "-100 (গোনা হবে না)"
        print(f"row {r} t={t}: {vocab[inp[r, t]]} → {to}")

k = y != -100
print("predictions:", int(k.sum()), "of", y.size)
print("y[t] == x[t+1] where counted:", bool((y[k] == x[:, 1:][k]).all()))
print("first token ever a target?", bool((y == x[:, 0, None]).any()))

আউটপুট (পাতার আসল run):

shape x (2, 7) | inp (2, 6) | y (2, 6)
row 0 t=0: [BOS] → শাড়ির
row 0 t=1: শাড়ির → রং
row 0 t=2: রং → ছবির
row 0 t=3: ছবির → মতোই
row 0 t=4: মতোই → সুন্দর
row 0 t=5: সুন্দর → [EOS]
row 1 t=0: [BOS] → ডেলিভারি
row 1 t=1: ডেলিভারি → দ্রুত
row 1 t=2: দ্রুত → [EOS]
row 1 t=3: [EOS] → -100 (গোনা হবে না)
row 1 t=4: [PAD] → -100 (গোনা হবে না)
row 1 t=5: [PAD] → -100 (গোনা হবে না)
predictions: 9 of 12
y[t] == x[t+1] where counted: True
first token ever a target? False

দুই সারিতে ১২টি ঘর, গোনা হলো ৯টি। ছোট সারির [EOS] আর [PAD] ঘরে input থাকলেও label -100, তাই loss-এ তাদের কোনো অংশ নেই। শেষ দুটি লাইন যাচাই: গোনা ঘরে label সত্যিই x-এর পরের ঘর, আর [BOS] কখনো লক্ষ্য হয় না।

দুই লক্ষ্যের মূল পার্থক্য একটি। MLM-এ model দুই পাশ দেখে, তাই একটি শব্দের vector সারা বাক্যের ছাপ ধরতে পারে, কিন্তু সে লেখা তৈরি করতে শেখে না। Causal লক্ষ্যে model শুধু অতীত দেখে, তাই সরাসরি লেখা তৈরি করতে পারে (অংশ ৬-এ দেখব)। এই ফারাকটিই BERT পরিবার আর GPT পরিবারের পথ আলাদা করেছে; বিস্তারিত পর্ব ৪৮ “BERT পরিবার — encoder model আর fine-tuning” আর পর্ব ৪৯ “GPT আর decoder-only model”-এ।

৪ · Span corruption — টানা টুকরো ফিরিয়ে আনা

T5 প্রবন্ধ এক ধাপ এগোয়। একটি করে token নয়, টানা কয়েকটি token-এর টুকরো (span) সরিয়ে একটি sentinel token বসায়। Encoder সেই ফাঁকওয়ালা লেখা পড়ে, আর decoder একে একে শুধু হারানো টুকরোগুলো লেখে। একে span corruption বলে।

প্রবন্ধের সংখ্যাটি মনে রাখার মতো: ৫০০ token-এ ১৫% নষ্ট আর ২৫টি টুকরো হলে নষ্ট token ৭৫টি, গড় টুকরো ৩টি। লেখকেরা গড় ২, ৩, ৫ আর ১০ যাচাই করে দেখেন, পার্থক্য সীমিত; গড় ৩ বেশিরভাগ অনুবাদ-বহির্ভূত benchmark-এ এলোমেলো একক token নষ্ট করার চেয়ে সামান্য ভালো করে। Target ছোট থাকায় training-ও কিছুটা দ্রুত হয়।

X0, X1, X2 = sentinel; হার 0.3, গড় টুকরো 2
ধাপ
input
target
  1. গোনা. ১৩টি token. হাতে দেখানোর জন্য হার ০.৩: নষ্ট ৩.৯ থেকে ৪টি। গড় টুকরো ২ হলে টুকরো ২টি। (T5-এর নিজের হার ০.১৫, গড় ৩.)
  2. টুকরো. দৈর্ঘ্য এলোমেলো, এখানে ৩ আর ১. শর্ত একটি: দুটি টুকরো পাশাপাশি হতে পারবে না, নইলে তারা একটিই টুকরো।
  3. Input. প্রতিটি টুকরোর জায়গায় একটি নতুন sentinel. একই উদাহরণে প্রতিটি sentinel আলাদা।
  4. Target. Sentinel, তারপর তার হারানো টুকরো। শেষে একটি বাড়তি sentinel, X2, যা জানায় আর কিছু নেই।
  5. ফেরানো. X0-র জায়গায় “শাড়ির রং ছবির”, X1-এর জায়গায় “সুন্দর” বসালে আসল বাক্য ফেরে। ফাংশনের সবচেয়ে ভালো পরীক্ষা এটিই।
  6. হিসাব. Input ১১, target ৭. MLM-এর মতো পুরো দৈর্ঘ্যের label নেই, তাই -100 লাগে শুধু target-এর padding-এ।
import numpy as np

def split_into(total, parts, rng):      # total-কে parts ভাগে, প্রতি ভাগ >= 1
    cuts = np.sort(rng.choice(total - 1, parts - 1, replace=False)) + 1
    return np.diff(np.concatenate(([0], cuts, [total]))).tolist()
def span_corrupt(toks, rng, rate=0.15, mean_span=3):
    n, bad = len(toks), max(1, round(len(toks) * rate))
    k = max(1, round(bad / mean_span))
    span = split_into(bad, k, rng)
    gap = split_into(n - bad + 2, k + 1, rng)   # মাঝের ফাঁক >= 1, দুই প্রান্ত >= 0
    gap[0] -= 1; gap[-1] -= 1
    inp, tgt, i = [], [], 0
    for s in range(k):
        inp += toks[i:i + gap[s]]; i += gap[s]
        inp.append(f"<X{s}>"); tgt.append(f"<X{s}>")
        tgt += toks[i:i + span[s]]; i += span[s]
    inp += toks[i:]; tgt.append(f"<X{k}>")
    return inp, tgt
def restore(inp, tgt):                  # উল্টো পথ: যাচাইয়ের জন্য
    parts, cur = {}, None
    for t in tgt:
        if t.startswith("<X"): cur = t; parts[cur] = []
        else: parts[cur].append(t)
    return [w for t in inp for w in (parts[t] if t in parts else [t])]
s = "শাড়ির রং ছবির মতোই সুন্দর আর ডেলিভারি দ্রুত ছিল তবে দাম একটু বেশি".split()
inp, tgt = span_corrupt(s, np.random.default_rng(3), rate=0.3, mean_span=2)
print("input :", " ".join(inp))
print("target:", " ".join(tgt))
long = [f"w{i}" for i in range(500)]
inp, tgt = span_corrupt(long, np.random.default_rng(42))
n_s = sum(t.startswith("<X") for t in inp)
print(f"500 token: corrupted {len(tgt) - n_s - 1}, spans {n_s}, input {len(inp)}, target {len(tgt)}")
ok = all(restore(*span_corrupt(long, np.random.default_rng(i))) == long for i in range(300))
print("restored exactly in 300 draws:", ok)

আউটপুট (পাতার আসল run):

input : <X0> মতোই <X1> আর ডেলিভারি দ্রুত ছিল তবে দাম একটু বেশি
target: <X0> শাড়ির রং ছবির <X1> সুন্দর <X2>
500 token: corrupted 75, spans 25, input 450, target 101
restored exactly in 300 draws: True

প্রথম দুই লাইন ওপরের হাতের হিসাব। পরের লাইনে প্রবন্ধের ৫০০ token-এর সংখ্যা মিলেছে: ৭৫টি নষ্ট, ২৫টি টুকরো; input ৫০০ − ৭৫ + ২৫ = ৪৫০, target ৭৫ + ২৬ = ১০১. শেষ লাইনে ৩০০টি seed-এ restore আসল লেখা হুবহু ফিরিয়েছে।

আসল T5 tokenizer-এ sentinel-গুলো <extra_id_0> থেকে <extra_id_99>. একটি ছোট কিন্তু জরুরি যাচাই: এই tokenizer কি বাংলা পড়তে পারে?

সত্যিকারের run (google-t5/t5-small-এর tokenizer, লাইসেন্স Apache 2.0, model card অনুযায়ী; শুধু tokenizer):

$ uv run python real_tools.py
…
vocab 32100 | <extra_id_0> = 32099 | <extra_id_99> = 32000
['▁Thank', '▁you', '<extra_id_0>', '▁me', '▁to', '▁your', '▁party', '<extra_id_1>', '▁week', '.']
['▁', 'শাড়ির', '▁', 'রং', '▁', 'সুন্দর'] | unk id 2 | ids [3, 2, 3, 2, 3, 2, 1]

শেষ লাইনটি পড়ুন। Token-গুলো দেখতে বাংলা, কিন্তু id-তে তিনটি শব্দই 2, মানে unknown. প্রতিটি বাংলা শব্দ একই ঘরে পড়ছে। এই tokenizer দিয়ে বাংলায় কিছু শেখানো অসম্ভব। পুরো পর্বের একটি শিক্ষা: লক্ষ্য যাই হোক, ডেটা আর tokenizer মিলতে হবে (পর্ব ৪০)। লক্ষ্য করুন sentinel-এর id-ও উল্টো ক্রমে: <extra_id_0>-এর id সবচেয়ে বড়।

৫ · -100 কেন গোনা হয় না — আর contrastive একটি নোট

তিনটি লক্ষ্যেই একটি ছোট সংখ্যা ফিরে আসছে। পর্ব ১৩-এ দেখেছি, PyTorch-এর CrossEntropyLoss-এ ignore_index-এর ডিফল্ট −100, আর গড় হয় শুধু বাকি ঘরগুলোয়। সংখ্যাটির কোনো জাদু নেই; এটি এমন একটি মান যা কোনো token id হতে পারে না। এবার দেখি তিনটি দাবি সত্যি কি না: সেই ঘরের logit যা-ই হোক loss বদলায় না, gradient শূন্য, আর গড়ের হর কমে।

import numpy as np
np.set_printoptions(precision=4, suppress=True, linewidth=60)

def ce(logits, y, ignore=-100):
    z = logits - logits.max(1, keepdims=True)
    logp = z - np.log(np.exp(z).sum(1, keepdims=True))
    k = y != ignore
    return -logp[k, y[k]].sum() / k.sum()     # শুধু গোনা ঘরের গড়

L = np.array([[2.0, 0.5, 0.1, -1.0], [0.2, 1.5, 0.3, 0.0], [9.0, -3.0, 4.0, 7.0],
              [0.1, 0.2, 2.5, 0.4], [-5.0, 8.0, 3.0, 6.0]])
y = np.array([0, 1, -100, 3, -100])
print("loss with ignore   :", round(ce(L, y), 4))

L2 = L.copy()
L2[[2, 4]] = 1000 * np.random.default_rng(1).normal(size=(2, 4))
print("ignored rows changed:", round(ce(L2, y), 4))

g = np.zeros(5)                               # সংখ্যায় gradient, প্রতি সারি
for r in range(5):
    for c in range(4):
        d = np.zeros_like(L); d[r, c] = 1e-5
        g[r] += ((ce(L + d, y) - ce(L - d, y)) / 2e-5) ** 2
print("grad row norm      :", np.sqrt(g))

all5 = ce(L, np.where(y == -100, 0, y), ignore=-1)
print("PAD=0 as a real label:", round(all5, 4))
print("wrong mean (÷5, not ÷3):", round(ce(L, y) * 3 / 5, 4))

আউটপুট (পাতার আসল run):

loss with ignore   : 1.1037
ignored rows changed: 1.1037
grad row norm      : [0.1179 0.171  0.     0.3962 0.    ]
PAD=0 as a real label: 3.3154
wrong mean (÷5, not ÷3): 0.6622

দ্বিতীয় লাইনে ignored দুই সারিতে হাজার গুণ বড় এলোমেলো সংখ্যা বসানোর পরেও loss একই। Gradient-এর ৩ নম্বর আর ৫ নম্বর সারি শূন্য, তাই ওই ঘর থেকে কোনো শিক্ষা আসে না। পরের লাইন দেখায় -100-এর বদলে 0 রাখলে কী হয়: padding-কে আসল লক্ষ্য ধরে loss বেড়ে যায়, model অকারণে [PAD] অনুমান করতে শেখে। শেষ লাইনটি উল্টো ভুল: ৫ দিয়ে ভাগ করলে loss কম দেখায়, যদিও model একটুও ভালো হয়নি। দুটি batch-এর padding আলাদা হলে এমন loss তুলনাই করা যায় না।

একই খেলনা সংখ্যা আসল PyTorch-এ। সত্যিকারের run (torch 2.15.0, CPU-তে):

$ uv run python real_tools.py
loss 1.1037
grad row norm [0.1179, 0.171, 0.0, 0.3962, 0.0]

হাতে লেখা NumPy আর PyTorch চার দশমিক পর্যন্ত মিলেছে। এমন মিলই আপনার ফাংশনকে বিশ্বাসযোগ্য করে।

Contrastive একটি নোট: চতুর্থ একটি পরিবার আছে, contrastive learning. এখানে কোনো token অনুমান হয় না। একই লেখার দুটি রূপ কাছে, ভিন্ন লেখা দূরে ঠেলা হয়। যেমন SimCSE (Gao ও সহকর্মীরা, ২০২১)-এর লেবেলহীন সংস্করণে একই বাক্য দুবার encoder-এ যায়, আর একমাত্র পার্থক্য আসে dropout থেকে। এতে পুরো vocabulary-র ওপর softmax লাগে না; ফল হয় পুরো বাক্যের একটি vector. এটি মূলত embedding তৈরির কাজে লাগে; এই কোর্সের RAG অংশে আবার আসবে।

৬ · নিজের হাতে pretraining — একই লেখায় encoder আর decoder

এবার সত্যিকারের পরীক্ষা। পাতায় PyTorch চলে না, তাই এটি desktop-এর run. আমরা একই লেখায় দুটি ছোট model শেখাব, একটি MLM দিয়ে, একটি next-token দিয়ে। নকশায় একমাত্র পার্থক্য causal mask.

অংশউৎস আর লাইসেন্সমাপ
মন্তব্য (লেবেল ফেলে দিয়ে)Salim Sazzed-এর বাংলা sentiment corpus, Mendeley Data, DOI 10.17632/p6zc7krs37.4, CC BY 4.0৭,৬৩০ সারি, ১,৪৩,৬৬০ token
গল্পউপেন্দ্রকিশোর রায়চৌধুরী, bn.wikisource, PD-India৩৬টি গল্প, ৫৮,৬৩১ token
Pretraining-এর যাচাইউপরের দুই উৎস থেকে আলাদা রাখা৪০০ মন্তব্য, ৩টি গল্প
Sentiment-এর validation আর testমন্তব্য, যমজ দল ধরে আলাদা৫৩৬ আর ২,১৪৩ সারি; pretraining-এ ছোঁয়া হয়নি

এগুলো YouTube-এর বাংলা নাটকের মন্তব্য, বাজারঘরের রিভিউ নয়; তাই ফল সরাসরি রিভিউতে খাটবে না। Tokenizer হলো ৪,০০০ শব্দভান্ডারের BPE (পর্ব ৪০), শেখানো হয়েছে শুধু pretraining-এর লেখায়। Test-এর মন্তব্যে [UNK] ০.০৫%.

Model: ৪টি pre-norm block (পর্ব ৩৫), d = ১২৮, h = ৪, feed-forward ৫১২, সর্বোচ্চ ৬৪ token, weight tying. দুটিতেই ১৩,১৭,৭৯২ parameter. হাতে মেলানো যায়: embedding ৫,১২,০০০; position ৮,৪৪৮; output bias ৪,০০০; প্রতি block ১,৯৮,২৭২, চারটিতে ৭,৯৩,০৮৮; শেষ LayerNorm ২৫৬. শুরুর loss ln ৪০০০ ≈ ৮.২৯-এর কাছে থাকা উচিত, আর দুটি model-ই সেখান থেকে শুরু হয়েছে।

প্রথম চেষ্টায় MLM-এর শুরুর loss ছিল ৭৫! কারণ আমাদের নিজের ভুল: PyTorch-এর nn.Embedding ডিফল্টে N(০, ১) থেকে শুরু হয়, আর weight tying থাকলে সেই বড় সংখ্যাই logit হয়ে যায়। std = ০.০২ করে আবার সব চালিয়েছি। “শুরুর loss ≈ ln V” পরীক্ষাটি এই ভুল ধরল (পর্ব ১৮)।

সত্যিকারের run (GPU-তে; torch 2.15.0, tokenizers 0.23.3, Python 3.14.4; seed 0; batch ৬৪; AdamW, warmup আর cosine). মেশিনে অন্য কাজও চলছিল, তাই সময় মোটামুটি। “held-out” মানে আলাদা রাখা অংশ। আউটপুটের কিছু লাইন বাদ দেওয়া হয়েছে (…):

~/bazarghar-dl$ uv run python demo.py
unigram baseline | train tokens 205630 | held-out NLL 7.284 | perplexity 1457.5 | top-1 0.014

~/bazarghar-dl$ uv run python pretrain.py causal both 0 3000
    a, y = causal_batch(x)               # MLM-এ mlm_batch(x, V, g)
    loss = F.cross_entropy(m.logits(a).transpose(1, 2), y)   # ignore_index=-100
step     1 | train 8.307 | held-out 8.317 | perplexity  4092.0 | top-1 0.000
step  1000 | train 5.476 | held-out 5.816 | perplexity   335.6 | top-1 0.111
step  3000 | train 4.488 | held-out 5.658 | perplexity   286.5 | top-1 0.133

~/bazarghar-dl$ uv run python pretrain.py mlm both 0 3000
step  1000 | train 6.564 | held-out 6.774 | perplexity   875.0 | top-1 0.038
step  3000 | train 6.027 | held-out 6.109 | perplexity   449.8 | top-1 0.076

~/bazarghar-dl$ uv run python pretrain_long.py causal both 0 9000
step  2000 | train 4.581 | held-out 5.740 | perplexity   311.1 | top-1 0.127
step  5000 | train 3.464 | held-out 6.131 | perplexity   460.1 | top-1 0.134
step  9000 | train 3.351 | held-out 6.287 | perplexity   537.3 | top-1 0.135

~/bazarghar-dl$ uv run python pretrain_long.py mlm both 0 9000
step  3000 | train 5.197 | held-out 5.406 | perplexity   222.7 | top-1 0.124
step  6000 | train 4.586 | held-out 4.923 | perplexity   137.4 | top-1 0.199
step  9000 | train 4.324 | held-out 4.840 | perplexity   126.4 | top-1 0.210
best held-out loss 4.840 | 1365 s | GPU

এই আউটপুটে এই পর্বের একটি বড় শিক্ষা আছে। আমরা প্রথমে দুটিকে ৩,০০০ step দিয়েছিলাম, আর MLM তখনও শিখছিল। ৯,০০০ step-এ দুই লক্ষ্য উল্টো দিকে গেল:

  • Causal মুখস্থ করে: train ৩.৩৫-এ নামল, আর held-out step ২,০০০-এর পরে উঠতেই থাকল (৫.৭৪ থেকে ৬.২৯)। দুই লাখ token-এ সে একই সরানো প্রশ্ন বারবার দেখে।
  • MLM টিকে থাকে: train ৪.৩২, held-out ৪.৮৪, ফাঁক ছোট। প্রতিবার ঢাকা ঘর বদলায়, তাই একই সারি থেকে নতুন প্রশ্ন আসে। এটি একধরনের regularization-এর কাজ করে।

দুটিই unigram (৭.২৮৪) ছাড়িয়েছে। কিন্তু ৪.৮৪ আর ৫.৬৬ দেখে “MLM ভালো” বলা যাবে না। প্রশ্নই আলাদা: MLM দুই পাশ দেখে। তুলনার জায়গা আসল কাজ, পরের অংশে।

সংখ্যার পরে আসল আউটপুট। বাক্যগুলো আগে থেকে ঠিক করা, ফল দেখে বাছাই করা হয়নি। “_” মানে শব্দের শুরু।

--- MLM encoder, 9000 steps, seed 0, top-5
নাটকটা খুব [MASK] লাগলো
   _ভালো 0.798 | _ভাল 0.153 | _সুন্দর 0.025 | _খারাপ 0.005 | _ভালোই 0.005
অভিনয় [MASK] হয়েছে
   _অসাধারণ 0.373 | _দারুন 0.124 | _ভালো 0.122 | _সুন্দর 0.092 | _অসাধারন 0.062
এক [MASK] ছিল এক রাজা
   _কথায় 0.666 | _কথা 0.059 | _কথাই 0.030 | _অসাধারণ 0.024 | মাত্র 0.023
এই নাটকটা [MASK] হয়নি
   _ভালো 0.510 | _ভাল 0.140 | _অসাধারণ 0.090 | _দারুন 0.037 | _ভালোই 0.035
--- causal decoder, 3000 steps, seed 0, top-5
নাটকটা খুব →
   _ভালো 0.417 | _সুন্দর 0.257 | _ভাল 0.131 | _সুন্দর। 0.038 | _খুব 0.024
অভিনয় →
   _খুব 0.157 | _অসাধারণ 0.117 | _অনেক 0.086 | _টা 0.065 | _ভালো 0.048
এক যে ছিল →
   _যে, 0.038 | _যে 0.037 | _না। 0.028 | _সে 0.027 | _নাটকটা 0.026
--- causal decoder, sampling, T=0.8, seed 42
'নাটকটা অনেক সুন্দর হয়েছে'
'এক যে ছিল এই রকম নাটক এরকম, এক কথায় অসাধারণ।'

মন্তব্যের ধরন ধরা পড়েছে: “খুব ___ লাগলো”-তে “ভালো”, আর বানানভেদ “ভাল”-ও। “এক ___ ছিল এক রাজা” গল্পের বাক্য হলেও উত্তর এসেছে “কথায়”, কারণ এই লেখায় “এক কথায় অসাধারণ” ধরনের মন্তব্য অনেক (গল্পের চেয়ে মন্তব্যের token প্রায় আড়াই গুণ)। “এই নাটকটা ___ হয়নি”-তে “ভালো” ব্যাকরণে মানায়। Encoder লেখা তৈরি করে না; decoder করে, আর দ্বিতীয় নমুনাটি দেখায় তার সীমা: ব্যাকরণে অর্ধেক ঠিক, অর্থে নয়। ছোট model আর দুই লাখ token থেকে এর বেশি আশা করা উচিত নয়।

৭ · আসল পরীক্ষা — pretrained বনাম random, ১০০ থেকে ১,০০০ label

Pretraining কি sentiment-এ কাজে লাগল? সব arm-এর শর্ত একই। একটি কথা খোলাখুলি বলা দরকার: ৯,০০০ step-এর arm-গুলো (_long) আমরা পরে যোগ করেছি, ৩,০০০ step-এর ফল আর MLM-এর তখনও কমতে থাকা loss দেখে। তাই সেগুলোর ফল আগে থেকে ঠিক করা পরীক্ষার মতো শক্ত প্রমাণ নয়; একটি নতুন test ভাগে আবার মাপলে তবেই তা হতো।

  • একই ভাগ: প্রতি seed-এ ১০০, ৩০০ বা ১,০০০টি মন্তব্য এলোমেলোভাবে (শ্রেণির অনুপাত রেখে) বাছা। সব arm একই subset, একই validation (৫৩৬) আর একই test (২,১৪৩) পায়। মোট ৬টি seed. Pretrained checkpoint প্রতিটি লক্ষ্যে ২টি, seed অনুযায়ী পালা করে।
  • একই শর্ত: Encoder-এর ওপর mean pooling আর একটি linear head, শ্রেণি-ওজনসহ loss, ৩০০ step. Learning rate ৩×১০−৪ আর ১০−৩, প্রতি arm-এ validation দেখে বাছা, যাতে random-কে অকারণে পিছিয়ে না দেওয়া হয়।
  • সহজ প্রতিদ্বন্দ্বী: TF-IDF + logistic regression (character আর word), C বাছা validation-এ। এটিই পর্ব ৪২-এর বিজয়ী baseline-এর ধরন।
  • মাপ: Macro-F1. জোড়া তুলনা একই seed-এ; ব্যবধান t-ভিত্তিক (মেশিন লার্নিং কোর্সের পর্ব ২২)।

সত্যিকারের run (GPU-তে, একটি মেশিনে একটি স্থানীয় পরীক্ষা, সাধারণ সত্য নয়; scikit-learn 1.9.1):

~/bazarghar-dl$ uv run python analyze.py
labels | arm             | seeds | macro-F1 mean ± SD
   100 | tfidf           |     6 | 0.791 ± 0.031
   100 | random          |     6 | 0.697 ± 0.030
   100 | mlm_story       |     6 | 0.674 ± 0.014
   100 | mlm             |     6 | 0.818 ± 0.015
   100 | causal          |     6 | 0.855 ± 0.008
   100 | mlm_long        |     6 | 0.867 ± 0.012
   100 | causal_long     |     6 | 0.851 ± 0.007
   100 | mlm_long_frozen |     6 | 0.856 ± 0.006
   300 | tfidf           |     6 | 0.864 ± 0.011
   300 | random          |     6 | 0.771 ± 0.014
   300 | mlm_story       |     6 | 0.747 ± 0.011
   300 | mlm             |     6 | 0.840 ± 0.011
   300 | causal          |     6 | 0.867 ± 0.009
   300 | mlm_long        |     6 | 0.883 ± 0.010
   300 | causal_long     |     6 | 0.866 ± 0.006
   300 | mlm_long_frozen |     6 | 0.866 ± 0.009
  1000 | tfidf           |     6 | 0.906 ± 0.006
  1000 | random          |     6 | 0.840 ± 0.006
  1000 | mlm_story       |     6 | 0.836 ± 0.008
  1000 | mlm             |     6 | 0.879 ± 0.004
  1000 | causal          |     6 | 0.885 ± 0.006
  1000 | mlm_long        |     6 | 0.901 ± 0.003
  1000 | causal_long     |     6 | 0.888 ± 0.004
  1000 | mlm_long_frozen |     6 | 0.877 ± 0.003
…

paired difference, same seed (A - B), 95% t interval
   100 | mlm_long   - random  | +0.170 [+0.147, +0.193] | wins 6/6
   100 | mlm_long   - tfidf   | +0.076 [+0.044, +0.108] | wins 6/6
   100 | mlm_long   - causal  | +0.012 [-0.007, +0.031] | wins 4/6
   100 | mlm        - causal  | -0.037 [-0.053, -0.021] | wins 0/6
   100 | mlm_story  - random  | -0.022 [-0.052, +0.007] | wins 2/6
   300 | mlm_long   - random  | +0.112 [+0.100, +0.124] | wins 6/6
   300 | mlm_long   - tfidf   | +0.018 [-0.001, +0.038] | wins 6/6
   300 | mlm_long   - causal  | +0.015 [-0.001, +0.032] | wins 5/6
   300 | mlm        - causal  | -0.027 [-0.043, -0.011] | wins 0/6
  1000 | mlm_long   - random  | +0.061 [+0.053, +0.069] | wins 6/6
  1000 | mlm_long   - tfidf   | -0.005 [-0.012, +0.003] | wins 1/6
  1000 | mlm_long   - causal  | +0.016 [+0.009, +0.023] | wins 6/6
  1000 | mlm        - causal  | -0.006 [-0.012, -0.000] | wins 1/6
…

নাম বুঝে নিন: mlm আর causal ৩,০০০ step; _long মানে ৯,০০০ step-এর run-এর সেরা checkpoint (causal-এ সেটি step ২,০০০-এর কাছে); mlm_story শুধু গল্পে শেখা; frozen মানে body স্থির, শুধু head শেখে।

Label বাড়লে কে কোথায়

Test macro-F1 against labelled comments, one local experiment

Macro-F1 vs number of labels 0.65 0.70 0.75 0.80 0.85 0.90 0.95 100 300 1000 labelled comments (log scale) macro-F1 (mean ± SD over seeds) TF-IDF + LR Random init MLM (9000 steps) Causal (3000) MLM, stories only
ছবি ৪৭.২ — ৬টি seed-এর গড়, দাগ হলো SD. বিন্দুগুলো পড়ার সুবিধার জন্য সামান্য সরানো। ১০০ label-এ ব্যবধান সবচেয়ে বড়; ১,০০০-এ TF-IDF ধরে ফেলে।
সৎ ফল

১) Pretraining এখানে সত্যিই কাজে লেগেছে: ১০০ label-এ random-এর চেয়ে ০.১৭ বেশি, আর ৬টি seed-এর ৬টিতেই। ১,০০০ label-এ পার্থক্য কমে ০.০৬. ২) TF-IDF-কে হারানো শুধু অল্প label-এ পরিষ্কার: ১০০-তে +০.০৭৬; ৩০০-তে ব্যবধান শূন্য ছুঁয়েছে; ১,০০০-এ পার্থক্য দেখা যায় না। পর্ব ৪২-এ লেবেল বেশি ছিল বলেই TF-IDF সামনে ছিল (সেখানে প্রায় ৭,৫০০ label-এ ০.৯৪১, ভিন্ন ভাগে, তাই সরাসরি তুলনা নয়)। ৩) “কোন লক্ষ্য ভালো” এই পরীক্ষা ঠিক করে দেয় না: ৩,০০০ step-এ causal এগিয়ে, ৯,০০০ step-এ MLM. উত্তর বদলে গেল শুধু training-এর দৈর্ঘ্যে। ৪) শুধু গল্পে শেখা MLM random-কে ছাড়ায়নি।

এত ভালো ফলে সন্দেহ করাই উচিত। তাই দুটি নিয়ন্ত্রণ পরীক্ষা: pretraining-এ test ঢুকেছে কি না, আর ভুল label দিলে কী হয়। সত্যিকারের run (causal checkpoint, ১০০ label, ২০০ step; এখানে validation দেখে কিছু বাছা হয়নি):

~/bazarghar-dl$ uv run python control.py
val in pretraining: 0 | test in pretraining: 0
exact test text in pretraining: 0
seed 0 | true labels     | test macro-F1 0.850
seed 0 | shuffled labels | test macro-F1 0.548
seed 1 | true labels     | test macro-F1 0.863
seed 1 | shuffled labels | test macro-F1 0.551
seed 2 | true labels     | test macro-F1 0.838
seed 2 | shuffled labels | test macro-F1 0.506

Label এলোমেলো করলে ফল প্রায় ০.৫-এ নেমে আসে, যা এই দুই শ্রেণির অসম ডেটায় অনুমান-স্তরের কাছাকাছি। তাই ফলটি pipeline-এর কোনো ফাঁকের কারণে নয় বলেই মনে হয়। সিদ্ধান্তের সীমাগুলোও লিখে রাখা দরকার:

  • লেবেলহীন লেখা একই উৎসের. ৭,৬৩০টি মন্তব্য test-এর একই ধরনের। শুধু গল্পে শেখা model-এর domain shift দেখলে বোঝা যায় এটি কতটা জরুরি। তবে সেই arm-এ checkpoint একটিই, আর গল্পের token মাত্র ৫৮,৬৩১.
  • ৫৩৬ validation label বড় সুবিধা. সবাই পেয়েছে, তবে ১০০ label-এর বাস্তব প্রকল্পে এত validation সাধারণত থাকে না।
  • ৬টি seed, ২টি checkpoint, একটি test ভাগ. ব্যবধানগুলো প্রধানত subset আর init-এর ঝাঁকুনি ধরে, checkpoint-এর পুরোটা নয়। এত তুলনার জন্য কোনো সংশোধনও করা হয়নি।
  • এটি BERT নয়. ১৩ লাখ parameter আর দুই লাখ token. আসল আকারের pretrained model-এ কী হয়, তা পর্ব ৪৮ “BERT পরিবার — encoder model আর fine-tuning”-এর প্রশ্ন।

৮ · বড় ডেটায় — পরিষ্কার করা, যমজ বাদ আর লাইসেন্স

আমাদের corpus দুই লাখ token. বড় pretraining-এর ডেটার মাপ এর চেয়ে বহু গুণ বড়, আর সেখানে আসল কাজের বড় অংশই ডেটা ছাঁকা। T5 প্রবন্ধ এই ছাঁকনি লিখে রেখেছে, তাই উদাহরণ হিসেবে কাজের। তাঁদের C4 ডেটাসেট Common Crawl থেকে বানানো; প্রবন্ধ অনুযায়ী Common Crawl প্রতি মাসে প্রায় ২০ TB লেখা তৈরি করত।

C4-এর নিয়ম (T5 প্রবন্ধ)যে সমস্যা ঠেকায়বাংলায় যা ভাবতে হবে
শুধু বিরামচিহ্নে শেষ হওয়া লাইনমেনু, শিরোনামদাঁড়ি “।” নিয়মে না রাখলে প্রায় সব বাংলা বাদ পড়বে
৩ বাক্যের কম পাতা বাদ; লাইনে অন্তত ৫ শব্দটুকরো লেখাএমন নিয়মে ছোট মন্তব্যের প্রায় সবই বাদ পড়তে পারে
“Javascript” লাইন, “lorem ipsum”, { থাকা পাতা বাদসতর্কবার্তা, নকল লেখা, কোডটেমপ্লেটের নকল বাংলা লেখাও খুঁজতে হবে
নিষিদ্ধ শব্দের তালিকায় থাকা শব্দ হলে পুরো পাতা বাদআপত্তিকর লেখাতালিকা যা ছাঁকে তা নিজে পরীক্ষা করা দরকার (নিচে দেখুন)
তিন বাক্যের টুকরো একাধিকবার থাকলে একটি রাখানকল পাতাUnicode NFC আগে; নইলে একই লেখা আলাদা দেখায় (পর্ব ৩৯)
langdetect-এ ইংরেজি হওয়ার সম্ভাবনা অন্তত ০.৯৯অন্য ভাষাএই ধাপে বাংলা পুরোই বাদ; C4 তাই ইংরেজি ডেটা

ছাঁকনিরও দাম আছে। Dodge ও সহকর্মীরা (২০২১) C4 পরীক্ষা করে লেখেন, নিষিদ্ধ-শব্দের তালিকা সংখ্যালঘু মানুষের লেখা আর তাঁদের নিয়ে লেখা বেশি হারে সরিয়ে দেয়। ডেটায় মেশিনে অনুবাদ করা লেখা, আর অন্য benchmark-এর মূল্যায়নের উদাহরণও পাওয়া গেছে। শেষেরটি মানে হলো পরে সেই benchmark-এর ফল সন্দেহজনক হতে পারে। “Clean” নামটি তাই ফলাফল বিচারের যুক্তি নয়।

যমজ বাদ দেওয়ার তিনটি স্তর. ১) হুবহু একই লেখা: hash দিয়ে গোনা। ২) প্রায় একই লেখা (near-duplicate): আমরা character 3-gram Jaccard ≥ ০.৮ দিয়ে ১০,৭০৯টি মন্তব্যে ১০,৩৩১টি দল পেয়েছি (পর্ব ৪২)। বড় ডেটায় সব জোড়া মেলানো সম্ভব নয়, তাই MinHash-এর মতো আনুমানিক পদ্ধতি লাগে। ৩) Split-এর আগে দলবদ্ধ করা: একই দল train আর test-এ গেলে data leakage হয়। Pretraining-এও এই নিয়ম খাটে।

লাইসেন্স আর অধিকার. “ইন্টারনেটে আছে” মানে “ব্যবহারের অনুমতি আছে” নয়। Common Crawl-এর শর্তে (২০২৬ সালের নভেম্বরে পড়া; শেষ হালনাগাদ ৭ মার্চ ২০২৪) বলা আছে, সংগৃহীত লেখা তার মালিকদের আলাদা শর্তের অধীনে থাকতে পারে, ব্যবহারকারীকে তৃতীয় পক্ষের অধিকার মানতে হবে, ঝুঁকি ব্যবহারকারীর, আর আইনি পরামর্শ নেওয়ার কথাও বলা আছে। AI training-এ কপিরাইট নিয়ে আইনি বিতর্ক বিভিন্ন দেশে চলছে, আর উত্তর জায়গাভেদে আলাদা হতে পারে; এখানে কোনো আইনি পরামর্শ দেওয়া হচ্ছে না। নিজের প্রকল্পে অন্তত চারটি জিনিস লিখে রাখুন: উৎস, লাইসেন্স, ছাঁকার নিয়ম, আর ব্যক্তিগত তথ্য কীভাবে সরানো হলো। আমাদের দুটি উৎসের অবস্থা: মন্তব্যের ডেটা CC BY 4.0, আর গল্প উইকিসংকলনে PD-India হিসেবে চিহ্নিত।

৯ · তিন লক্ষ্য পাশাপাশি — আর library-তে কোথায় কী

MLMCausalSpan corruption
ModelEncoderDecoderEncoder + decoder
দেখতে পায়দুই পাশশুধু অতীতফাঁকওয়ালা লেখা, আর লেখা target-এর অতীত
গোনা ঘরপ্রায় ১৫%padding ছাড়া সবtarget-এর সব, padding ছাড়া
-100 কোথায়না-বাছা ঘর, special, paddingpaddingtarget-এর padding
সরাসরি লেখা তৈরিনাহ্যাঁফাঁক পূরণ বা text-to-text
উদাহরণBERTGPTT5

একটি সাধারণ ফাঁদ: শিফট দুবার করা। Hugging Face-এর নথি (transformers 5.22.2, ২০২৬ সালের নভেম্বরে পড়া) অনুযায়ী GPT-2-এর model-এ “labels are shifted inside the model”, তাই সেখানে labels = input_ids দিলেই হয়। পর্ব ৩৬-এর আমাদের নিজের কোডে শিফট হয় ডেটাসেটে। দুটি মিশিয়ে ফেললে model দুই ঘর পরের token অনুমান করবে; কোড চলবে, loss কমবেও কিছুটা, শুধু model ভুল কাজ শিখবে। T5-র নথিতে উদাহরণটি এমন: input-এ The <extra_id_0> walks in <extra_id_1> park, labels-এ <extra_id_0> cute dog <extra_id_1> the <extra_id_2>. MLM-এর collator-এ ঘর সরে না, label একই ঘরের আসল token. প্রতিবার version-এর নথি পড়ে নিন।

আরও গভীরে: T5 প্রবন্ধে পাশাপাশি যাচাইয়ে denoising ধরনের লক্ষ্য language modeling আর deshuffling-এর চেয়ে ভালো করেছিল, আর denoising-এর ভেতরের রূপগুলোয় বড় পার্থক্য দেখা যায়নি। তাই লেখকেরা বলেন, ওই রূপগুলোর মধ্যে বাছাই মূলত গণনার খরচ দেখে করা উচিত। ফল সেই প্রবন্ধের model, ডেটা আর কাজের জন্য; ইংরেজি ডেটায়। Dai আর Le (২০১৫) এর আগে LSTM-এ next-step আর autoencoder pretraining দেখিয়েছিলেন। BERT-এর পরিশিষ্ট C.2-এ masking-এর রূপগুলোর তুলনা আছে।

পরীক্ষা ও ইন্টারভিউতে:

  • BERT-এ ১০% token অপরিবর্তিত কেন? Model যেন না জানে কোন ঘর পরীক্ষা হবে, আর vector যেন আসল শব্দের দিকে একটু ঝোঁকে।
  • ৪০০ আসল token-এর batch-এ MLM-এর গড়ে কত ঘরে loss? ৪০০ × ০.১৫ = ৬০টি; তাদের মধ্যে গড়ে ৪৮টি [MASK], ৬টি এলোমেলো, ৬টি অপরিবর্তিত।
  • Causal-এ দৈর্ঘ্য T-এর সারিতে input আর label? x[:-1] আর x[1:], দুটিই T − ১ দৈর্ঘ্যের।
  • Span corruption-এ n token, k টুকরো, c নষ্ট? Input n − c + k, target c + k + 1.
  • -100 কেন? এই মানের ঘর loss-এর লব আর হর দুটি থেকেই বাদ যায়, তাই gradient শূন্য।

১০ · AI-এর যুগে — batch বানানোর কোড যাচাই

AI সহকারী MLM বা causal batch-এর কোড সহজেই লিখে দেয়। সমস্যা হলো এই কোডের ভুল error দেয় না। শিফট দুবার হলে, special token ঢাকা পড়লে, অথবা padding গোনা হলে training চলে, loss কমে, শুধু ফল খারাপ হয়।

AI-এর সাধারণ ভুলকীভাবে ধরবেন
পুরোনো argument বা বানানো class নাম দেওয়ানিজের version-এর নথি পড়ুন; import করে চালান
Custom shift আর library-র shift একসঙ্গেএকটি সারি decode করে input আর label পাশাপাশি পড়ুন
Padding-এর label 0 রাখাlabels == pad_id গুনে দেখুন; শূন্য হওয়া উচিত
[CLS], [SEP] ঢাকা পড়াঅনেক সারিতে special ঘরের বাছাই গুনুন
Pretraining-এর ডেটায় test ঢোকানোTest সারি পুরোনো corpus-এ আছে কি না মিলিয়ে দেখুন
এক seed-এর ফলকে সিদ্ধান্ত বলাকয়েকটি seed, একই subset-এ জোড়া তুলনা, baseline-সহ
“Model ভাষা বোঝে” বলাTop-5 দেখুন; পরিসংখ্যানগত ধরন আর বোঝা এক নয়

নিচের prompt-এ শুধু কোড নয়, কোডের নিজের পরীক্ষাও চাওয়া হচ্ছে। উত্তরটি নিজে চালিয়ে ছোট batch-এ shape আর গোনা মিলিয়ে নিন। ব্যক্তিগত ডেটা বা API key কখনো পেস্ট করবেন না।

আমি PyTorch [version] আর transformers [version] ব্যবহার করছি। ছোট বাংলা encoder-এর জন্য MLM batch ফাংশন লিখে দাও। ১. নিয়ম: ১৫% আসল token বাছাই; তার ৮০% [MASK], ১০% এলোমেলো, ১০% অপরিবর্তিত। Special token আর padding কখনো বাছাই হবে না; label বাকি সব ঘরে -100. ২. সঙ্গে একটি পরীক্ষা লেখো: ১ লাখ সারিতে তিনটি অনুপাত গুনবে, special ঘরে বাছাই শূন্য কি না দেখবে, আর label আসল token-এর সঙ্গে মেলাবে। ৩. একটি ছোট বাক্যে input আর label পাশাপাশি ছাপাও। ৪. কোনো library class ব্যবহার করলে আমার version-এর নথিতে সেটি কোথায় আছে বলো; নিশ্চিত না হলে বলো “নিশ্চিত নই”।

AI-এর লেখা pretraining কোডের চেকলিস্ট

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

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর উত্তর খুলুন।

প্র ০১BERT যদি ১০০% ক্ষেত্রেই [MASK] বসাত, কী সমস্যা হতো? ৮০/১০/১০ কি কোনো গাণিতিক সত্য?

গাণিতিক সত্য নয়। এটি একটি নকশার সিদ্ধান্ত, আর লেখকেরা পরিশিষ্টে কয়েকটি রূপ যাচাই করেছিলেন। মূল সমস্যা ছিল pretraining আর fine-tuning-এর অমিল, যা BERT প্রবন্ধ নিজেই লিখেছে: [MASK] token fine-tuning-এ আসে না। Input-এ যদি পরীক্ষার ঘর সবসময় চিহ্নিত থাকে, তাহলে model-এর কাছে বাকি ঘরগুলোর জন্য ভালো vector রাখার চাপ কমে যায়। অথচ fine-tuning-এ দরকার ঠিক সেগুলোই।

১০% এলোমেলো token model-কে শেখায় যে input-এর একটি শব্দ ভুলও হতে পারে; তাকে প্রসঙ্গ মিলিয়ে দেখতে হয়। এই ধারণা পরের গবেষণায় আরও এগিয়েছে। ELECTRA প্রতিটি ঘরে “এই token কি বদলানো?” এমন প্রশ্ন করে, যা পর্ব ৪৮-এ নাম হিসেবে আসবে। ১০% অপরিবর্তিত ঘর আবার উল্টো দিকের সংকেত দেয়: সব ঘর সন্দেহজনক নয়, আর আসল শব্দটির vector তার নিজের দিকে ঝুঁকে থাকে।

একই সঙ্গে খেয়াল করুন, ১৫%-ও একটি মাঝামাঝি বাছাই। খুব কম ঘর ঢাকলে প্রতি সারি থেকে সংকেত কম আসে। খুব বেশি ঢাকলে প্রসঙ্গই থাকে না। T5 প্রবন্ধও ফলাফল আর “historical precedent” দেখিয়ে ১৫% রাখে। সে কারণে এই সংখ্যাগুলোকে নিয়ম না ভেবে একটি সুপারিশ হিসেবে দেখা ভালো। ছোট ডেটায় বা ভিন্ন ভাষায় এই মান আপনি পরীক্ষা করে দেখতে পারেন, যদি হাতে একটি downstream কাজ আর যথেষ্ট seed থাকে।

মূল উপলব্ধি: Pretraining-এর লক্ষ্য নিজে শেষ কথা নয়। এটি পরের কাজের জন্য ভালো vector তৈরির একটি কৌশল, তাই নকশার প্রশ্ন হলো পরের কাজের সঙ্গে অমিল কমানো।

প্র ০২T5 প্রবন্ধে denoising লক্ষ্য language modeling-কে হারিয়েছিল। তবু আজকের বহু বড় LLM next-token দিয়ে শেখে কেন?

প্রথমে প্রশ্নটি কী মেপেছিল তা দেখা দরকার। T5 প্রবন্ধ একটি নির্দিষ্ট encoder-decoder model, ইংরেজি ডেটা আর কিছু supervised benchmark-এ fine-tuning-এর পরের ফল মেপেছিল। সেখানে BERT ধরনের ও অন্যান্য denoising লক্ষ্য language modeling-এর চেয়ে এগিয়ে ছিল। এই ফল সব model আর সব উদ্দেশ্যের ওপর চাপানো যায় না।

দ্বিতীয়ত, উদ্দেশ্য বদলে গেছে। GPT-2 আর GPT-3-এর পথে জোর পড়েছিল সরাসরি লেখা তৈরির ওপর: একই model অনুরোধ পড়ে উত্তর লিখবে। Causal লক্ষ্য তার সঙ্গে সোজাসুজি মেলে, কারণ শেখার সময়ের কাজ আর ব্যবহারের সময়ের কাজ একই: পরের token. MLM-এর encoder-কে দিয়ে লেখা তৈরি করানো স্বাভাবিক নয়। পরিসংখ্যানের দিক থেকেও একটি সুবিধা আছে: causal লক্ষ্যে প্রায় প্রতিটি ঘরে একটি অনুমান, আর MLM-এ মাত্র প্রায় ১৫%.

তৃতীয়ত, সরলতা। কোনো এলোমেলো collator নেই, একই ইতিহাস থেকে KV cache চলে (পর্ব ৩৭), আর শেষে instruction tuning-এর ধাপ যোগ করা যায় (পর্ব ৫৬)। এখানে একটি সততার কথা: বড় প্রতিষ্ঠানের অনেক বন্ধ model তাদের training-এর সব বিবরণ প্রকাশ করে না। তাই “কেন এই বাছাই” প্রশ্নের অনেকটা আমাদের প্রকাশিত প্রবন্ধ দেখে অনুমান, প্রমাণ নয়। Encoder-ভিত্তিক model-ও শেষ হয়ে যায়নি; classification আর embedding-এ এদের ব্যবহার এখনও দেখা যায়। আবার আমাদের ছোট পরীক্ষাতেই ক্রমটি training-এর দৈর্ঘ্যে উল্টে গেছে, তাই একটি প্রবন্ধের একটি তুলনাকে সার্বজনীন নিয়ম ভাবা ঠিক নয়।

মূল উপলব্ধি: “কোন লক্ষ্য ভালো” প্রশ্নের একার কোনো উত্তর নেই; প্রশ্নটি হওয়া উচিত “কোন কাজের জন্য, কোন মাপে ভালো”।

প্র ০৩Pretraining কখন উপকারে আসে না? আমাদের ছোট পরীক্ষা থেকে কী বলা যায়, আর কী বলা যায় না?

Pretraining তখনই কাজে আসে যখন লুকোনো প্রশ্নের উত্তর দিতে গিয়ে model এমন কিছু শেখে, যা আসল কাজেও লাগে। এর তিনটি শর্ত আমাদের পরীক্ষায় চোখে পড়ল।

ডেটার ধরন: মন্তব্যে শেখা model ১০০ label-এ random-এর চেয়ে প্রায় ০.১৭ এগিয়ে; গল্পে শেখা model বরং সামান্য পিছিয়ে (−০.০২২, ব্যবধান শূন্য ছুঁয়েছে)। কারণটি গুনে দেখা যায়: মন্তব্যে “অসাধারণ” ১,৭৫৩ বার, “নাটক” ৭,৯৯৩ বার; ৩৯টি গল্পে দুটিই ০ বার। অর্থাৎ pretraining ডেটা আসল কাজের ভাষা না ধরলে শুরুর weight সাহায্য না-ও করতে পারে। তবে এই arm-এ ডেটা ছিল মাত্র ৫৮ হাজার token, তাই দুটি কারণ আলাদা করা যাচ্ছে না।

লেবেলের সংখ্যা: ১,০০০ label-এ TF-IDF আর সেরা pretrained প্রায় সমান, আর পর্ব ৪২-এ প্রায় ৭,৫০০ label-এ TF-IDF ০.৯৪১. এই ডেটায় শব্দ আর অক্ষরের টুকরোই sentiment-এর বড় সংকেত। লেবেল যথেষ্ট থাকলে সহজ model অনেকটা পথ যায়।

Training কতটা হলো: ৩,০০০ step-এর MLM ৯,০০০ step-এর চেয়ে প্রতিটি মাপে পিছিয়ে (১০০ label-এ ০.০৪৯)। অল্প training-এর pretraining-কে “লক্ষ্য খারাপ” ভাবলে ভুল হবে।

কী বলা যায় না: এই ফল থেকে বলা যায় না যে MLM সব সময় causal-এর চেয়ে ভালো, বা pretraining বড় model-এও এই হারে সাহায্য করে। GPT প্রবন্ধও pretraining বাদ দিলে ফল কমার কথা লিখেছিল, তবে অন্য মাপে, অন্য কাজে। Dai আর Le (২০১৫) LSTM-এ একই দিকের ফল দেখান। প্রকাশিত ফলের সঙ্গে দিক মিললেই নিজের ডেটায় প্রমাণ হয়ে যায় না।

মূল উপলব্ধি: Pretraining একটি অনুমান, প্রতিশ্রুতি নয়। নিজের ডেটায়, একই ভাগে, baseline-সহ কয়েকটি seed-এ মেপে তবেই বিশ্বাস করুন।

প্র ০৪Pretraining loss কম হলে কি পরের কাজে ফল ভালো হবে? MLM-এর loss আর causal loss কি পাশাপাশি তুলনা করা যায়?

দুটি প্রশ্নের উত্তরই “নিশ্চিত নয়”, আর কারণ আলাদা।

MLM বনাম causal loss: এগুলো দুটি ভিন্ন পরীক্ষা। MLM-এ ঢাকা ঘরে দুই পাশের প্রসঙ্গ থাকে, আর মাত্র প্রায় ১৫% ঘর গোনা হয়; তা ছাড়া input-এর ১০% ঘর ভুল বা ১০% ঘরে উত্তর খোলা। Causal-এ শুধু অতীত থাকে, আর প্রায় সব ঘর গোনা হয়। একই model-এর দুটি প্রশ্ন-সেট আলাদা হলে নম্বরের তুলনা অর্থহীন, যেমন দুটি আলাদা প্রশ্নপত্রে পাওয়া নম্বর সরাসরি তুলনা করা চলে না। Perplexity পর্ব ৪৬-এ যে শর্তে দেখানো হয়, সেখানেও মূল কথা একই: একই tokenizer, একই test, একই প্রশ্ন।

Pretraining loss বনাম downstream: Loss হলো একটি প্রতিস্থাপক লক্ষ্য। শিল্পে Goodhart-এর নীতি পরিচিত: কোনো মাপকে লক্ষ্য বানালে সেটি আর ভালো মাপ থাকে না। একটি model ঘন ঘন ব্যবহৃত শব্দ, বিরামচিহ্ন আর গল্পের ধরন ভালো অনুমান করে loss কমাতে পারে, অথচ “ফেরত দিতে চাই” বাক্যের নেতিবাচক সুর আলাদা করার ক্ষমতা তাতে না-ও বাড়তে পারে।

তাই বাস্তবে একটি downstream যাচাই রাখা হয়: লক্ষ্যের সঙ্গে সরাসরি জড়িত একটি ছোট, ভালোভাবে লেবেল করা কাজ। Image-এর self-supervised গবেষণায় linear probe-এর চল এ জন্যই; আমাদের পর্ব ৪৪-এর probe-ও একই ধারণা। Probe নিজেও নিখুঁত নয়: এক কাজে ভালো হলে অন্য কাজে ভালো হবে, তার নিশ্চয়তা নেই।

মূল উপলব্ধি: Loss দিয়ে একই লক্ষ্যের training-এর অবস্থা বিচার করুন। আসল উপকার মাপুন আসল কাজে, একই ভাগে।

প্র ০৫বাংলায় একটি বড় pretraining corpus বানাতে চাইলে কী কী প্রশ্নের উত্তর আগে দরকার?

উৎস আর অধিকার. বিনামূল্যে পড়া যায় মানেই পুনর্ব্যবহারের অনুমতি নয়। Common Crawl-এর শর্ত এই ঝুঁকি ব্যবহারকারীর ওপরই রাখে। Bangla BERT-এর model card অনুযায়ী sagorsarker/bangla-bert-base Common Crawl-ভিত্তিক OSCAR আর বাংলা Wikipedia-য় শেখানো, তাই এমন পথ আছে; তবে নিজের ব্যবহারে সেই উৎসগুলোর লাইসেন্স নিজে পড়ে নিতে হবে। পুরোনো সাহিত্যের কপিরাইট-মুক্ত লেখা সবচেয়ে পরিষ্কার অবস্থানে থাকে, কিন্তু সেখানে ভাষার ধরন আজকের রিভিউয়ের মতো নয়।

ভাষা-নির্দিষ্ট ছাঁকনি. C4-এর নিয়ম ইংরেজির জন্য বানানো। দাঁড়ি “।” বিরামচিহ্নের তালিকায় না থাকলে নিয়মটি বাংলার প্রায় সব বাক্য ফেলে দেবে। Banglish বা রোমান হরফের বাংলা, Unicode-এর নানা রূপ (NFC), পুরোনো non-Unicode ফন্টে লেখা পাতা, আর সাধু-চলিত মিশ্রণ: এসব নিয়ে আগেই সিদ্ধান্ত নিতে হয়।

প্রতিনিধিত্ব. সংবাদপত্রের লেখা বেশি হলে model সংবাদের ধরন বেশি শেখে। আঞ্চলিক ভাষা বাদ পড়তে পারে। নিষিদ্ধ-শব্দ তালিকা Dodge ও সহকর্মীদের মতো পরীক্ষা ছাড়া চালালে কার লেখা বাদ পড়ছে তা অজানা থাকে।

ব্যক্তিগত তথ্য. ফোন নম্বর, ঠিকানা, bKash নম্বর, রোগের বিবরণ: খোলা ওয়েবে থাকলেও সেগুলো model-এ নেওয়া উচিত কি না, সেই প্রশ্ন আলাদা। মূল্যায়নের জন্য একটি পরিষ্কার held-out অংশ রাখুন, যাতে benchmark-এর উদাহরণ corpus-এ ঢুকে না যায়। এ দেশে এই বিষয়ে আইনের অবস্থান জানতে একজন আইনজীবীর পরামর্শই নির্ভরযোগ্য পথ; এখানে আমরা কোনো আইনি দাবি করছি না।

মূল উপলব্ধি: বড় model-এর গল্পে আলোচনা হয় GPU নিয়ে, কিন্তু ফল আর দায় দুটিরই বড় অংশ ঠিক হয় ডেটা বাছাইয়ে। এই সিদ্ধান্তগুলো লিখে রাখাই একটি প্রকল্পকে যাচাইযোগ্য করে।

অনুশীলন

প্রথমে নিজে করুন, তারপর উত্তর মিলিয়ে নিন।

  1. হাতে MLM batch (হিসাব): বাক্য: [CLS] দাম কম কিন্তু মান ভালো [SEP]; id: [CLS]=1, [SEP]=2, [MASK]=3, দাম=4, কম=5, কিন্তু=6, মান=7, ভালো=8. পাঁচটি আসল token-এর u: ০.৫০, ০.১০, ০.৭০, ০.৩০, ০.০৪. বাছা ঘরের v ক্রমে ০.৬০ আর ০.৮৫; এলোমেলো token হিসেবে “দাম” আসে। Input আর label লিখুন। আসল token-এর probability ০.২৫ আর ০.৫০ হলে loss কত?

    u < ০.১৫ দুটি ঘরে: “কম” আর “ভালো”। “কম”-এ v = ০.৬০, তাই [MASK]. “ভালো”-তে v = ০.৮৫, তাই এলোমেলো “দাম”।

    Input: 1 4 3 6 7 4 2. Label: -100 -100 5 -100 -100 8 -100.

    Loss = −(ln ০.২৫ + ln ০.৫০) ÷ ২ = (১.৩৮৬৩ + ০.৬৯৩১) ÷ ২ = ১.০৩৯৭. হর ২, ৭ নয়।

  2. আউটপুট অনুমান: নিচের কোড কী ছাপাবে?
    x = np.array([[1, 5, 6, 2, 0, 0]])
    inp, y = x[:, :-1], x[:, 1:].copy()
    y[y == 0] = -100
    print(inp.tolist(), y.tolist(), (y != -100).sum())

    [[1, 5, 6, 2, 0]] [[5, 6, 2, -100, -100]] 3. Input ৫ ঘরের, label ৫ ঘরের, আর গোনা হয় শুধু ৩টি। [EOS]-এর পরের ঘর আর padding-এর পরের ঘর, দুটির label-ই -100.

  3. ফাংশন বদলান (কোড): পাতার mlm_batch-এ একটি ভুল ঢোকান: x >= FIRST শর্তটি সরিয়ে দিন, আর আবার চালান। কোন লাইনটি ভুলটি ধরে? এবার বদলের অনুপাত ০.৮/০.১/০.১-এর বদলে ১/০/০ করে দেখুন। কোন লাইন বদলাল? কোডের ভুল নাকি পরীক্ষার?

    শর্তটি সরালে special selected আর শূন্য থাকে না; [CLS], [SEP], এমনকি [PAD]-ও বাছা পড়ে। তখন selected share-ও ভুল দেখায়, কারণ লবে special ঘর ঢোকে। দ্বিতীয় পরিবর্তনে [MASK] share হয় ১.০, আর unchanged ০. কোড চলছে, পরীক্ষার লক্ষ্যও ঠিক আছে; কিন্তু সে তখন আর BERT-এর নিয়ম নয়। পরীক্ষা থাকলে এই ফারাকটি চোখে পড়ে, না থাকলে পড়ে না।

  4. নকশা ও সমালোচনা: বাজারঘরের একজন সহকর্মী বলছেন: “আমি ৫০,০০০ রিভিউতে MLM pretraining করলাম, loss ৬.৫ থেকে ৫.০ হলো। তাই ১৫০ লেবেলের sentiment-এ এখন আর baseline চালানোর দরকার নেই।” এই যুক্তির তিনটি দুর্বলতা বলুন, আর একটি সৎ পরীক্ষার নকশা দিন।

    দুর্বলতা: ১) MLM loss কমা downstream উপকারের প্রমাণ নয়। ২) Baseline না থাকলে বলা যায় না model কতটা কাজের; অল্প label-এ TF-IDF প্রায়ই শক্ত প্রতিপক্ষ। ৩) ১৫০ label-এ এক run খুব ঝাঁকুনি দেয়।

    পরীক্ষা: Test আর validation আগে আলাদা, যমজ দল ধরে। Pretraining শুধু বাকি লেখায়। একই subset-এ তিনটি arm: TF-IDF + logistic regression, random init, pretrained. অন্তত ৫টি seed-এ জোড়া পার্থক্য আর ব্যবধান দিন। বাছাই শুধু validation-এ; test একবার। Learning rate-এর একই ছোট grid সব neural arm-এ, যাতে random-কে অকারণে পিছিয়ে না দেওয়া হয়।

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

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

ক্রমএকটি MLM batch বানানোর ধাপগুলো সাজান (▲▼ চেপে সরান)

  1. লেখা tokenize করে padding দেওয়া
  2. Special আর padding বাদে আসল ঘরগুলো চিহ্নিত করা
  3. আসল ঘরের ১৫% বাছা, বাকি সব label -100 করা
  4. বাছা ঘরে ৮০% [MASK], ১০% এলোমেলো, ১০% অপরিবর্তিত
  5. Encoder-এর logit থেকে শুধু বাছা ঘরে loss গড় করা

১,০০০টি আসল token-এর একটি batch-এ BERT-এর MLM নিয়ম চললে গড়ে কতটি ঘরে [MASK] বসে?

  • ১৫০
  • ১২০
  • ৮০০
  • ১৫

MLM-এ কোনো ঘরে token অপরিবর্তিত রাখা হলো (১০%-এর দল)। সেই ঘরের label কী?

  • -100
  • [MASK]-এর id
  • সেই আসল token-এর id, যা input-এও আছে
  • একটি এলোমেলো token-এর id

x = [BOS, a, b, EOS, PAD] থেকে causal input আর label কী (PAD-এর label -100)?

  • Input: BOS a b EOS; label: a b EOS -100
  • Input: BOS a b EOS; label: BOS a b EOS
  • Input: a b EOS PAD; label: BOS a b EOS
  • Input: BOS a b EOS; label: a b EOS PAD

T5 ধরনের span corruption-এ ৫০০ token-এ ৭৫টি নষ্ট আর ২৫টি টুকরো হলে input আর target-এর দৈর্ঘ্য কত?

  • ৫০০ আর ৭৫
  • ৪২৫ আর ৭৫
  • ৪৫০ আর ১০০
  • ৪৫০ আর ১০১

একটি batch-এ পাঁচটি ঘর, label [0, 1, -100, 3, -100]. PyTorch-এর CrossEntropyLoss ডিফল্টে গড় কতটি ঘরে করে?

  • ৫টি
  • ৩টি
  • ২টি
  • শুধু -100-এর ২টি

৩০০ label-এ pretrained model আর random init তুলনা করতে চান। কোন পরীক্ষাটি সবচেয়ে ন্যায্য?

  • একটি seed-এ দুটি চালিয়ে বড়টি নেওয়া
  • Pretraining-এ test-এর লেখাও দেওয়া, যাতে model বেশি ডেটা পায়
  • কয়েকটি seed-এ একই subset, একই validation আর test; জোড়া পার্থক্য, baseline-সহ
  • দুটিকে আলাদা আলাদা test-এ চালিয়ে macro-F1 তুলনা করা

শব্দকোষ

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

মনে রাখুন

  1. Pretraining মানে লেখার ভেতর থেকেই প্রশ্ন আর উত্তর বানিয়ে শেখা; প্রতিটি লক্ষ্য বলে input, target আর কোন ঘর গোনা হবে না।
  2. MLM: ১৫% আসল ঘর বাছা; তার ৮০% [MASK], ১০% এলোমেলো, ১০% অপরিবর্তিত। লেবেল আসল token, বাকি ঘর -100.
  3. Causal: input x[:-1], label x[1:], padding -100. Span corruption: টানা টুকরোর জায়গায় sentinel; target-এ sentinel আর টুকরো, শেষে একটি বাড়তি sentinel.
  4. -100 loss-এর লব আর হর দুটি থেকেই বাদ, gradient শূন্য। শিফট দুবার করা আর padding গোনা নীরব ভুল।
  5. এই ছোট পরীক্ষায় অল্প label-এ pretraining বড় উপকার দিয়েছে, ১,০০০-এ TF-IDF ধরে ফেলেছে, আর গল্পে শেখা model উপকার দেয়নি। লক্ষ্যের ক্রম training-এর দৈর্ঘ্যে উল্টে গেছে। নিজের ডেটায় মাপুন।

পরের পর্বে: এই পর্বের ছোট encoder থেকে আসল আকারের দিকে: পর্ব ৪৮ “BERT পরিবার — encoder model আর fine-tuning”-এ একই মন্তব্যের ডেটায় একটি pretrained বাংলা BERT.

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

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