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

BERT পরিবার — encoder model আর fine-tuning

The BERT Family — Encoder Models and Fine-Tuning

Taught in Bangla, this lesson explains BERT's input of tokens, segments, positions, CLS and SEP, counts Bangla BERT's parameters by hand to match the real checkpoint, and traces the family from RoBERTa to BanglaBERT. A real desktop run on the GPU fine-tunes it on the same 15 splits as the TF-IDF baseline, compares CLS and mean pooling, freezing and layer-wise learning rates, reads corrected paired intervals, weighs speed, memory and size, and catches a silent tokenizer default.

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

  • BERT-এর input: token, segment, position, [CLS], [SEP] আর mask, একটি বাংলা বাক্য-জোড়ায়
  • BERT-এর parameter হাতে গোনা, আর RoBERTa থেকে BanglaBERT পর্যন্ত পরিবারের বদল
  • [CLS] বনাম mean pooling, freeze বনাম unfreeze, layer-wise learning rate
  • Baseline-এর একই ভাগে fine-tuning আর paired সংশোধিত ব্যবধান পড়া
  • সময়, memory আর আকার ধরে fine-tune করা বা না করার সিদ্ধান্ত

১ · পর্ব ৪২-এর খোলা প্রশ্ন আর encoder model

পর্ব ৪২-এ শূন্য থেকে শেখা কোনো neural model TF-IDF + logistic regression-কে হারাতে পারেনি। সেখানে একটি প্রশ্ন খোলা ছিল: আগে থেকে বিপুল বাংলা লেখা পড়ে আসা encoder আনলে কী হয়? ভাবুন, দোকানে নতুন কর্মচারী নেবেন। এক, যিনি বাংলাই পড়তে জানেন না, সাত হাজার রিভিউ দেখিয়ে ভাষা আর কাজ একসঙ্গে শেখাতে হবে। দুই, যিনি বছরের পর বছর পত্রিকা পড়েছেন, তাঁকে শুধু কাজটি বুঝিয়ে দিলেই চলে। দ্বিতীয়জনই pretrained model, আর কাজ বুঝিয়ে দেওয়াই fine-tuning.

Devlin ও সহকর্মীদের BERT (arXiv অক্টোবর ২০১৮) এই ধারাকে কেন্দ্রে আনে। Encoder-only মানে এতে decoder নেই, পরের শব্দ তৈরি করা এর কাজ নয়; প্রতিটি token দুই পাশের সবাইকে দেখে একটি vector পায়। প্রবন্ধের ভাষায়, শেখা শেষে একটি অতিরিক্ত output layer জুড়ে fine-tune করা যায়। Contextual vector-এর ধারণা পর্ব ৪৪-এ, ছবির transfer learning পর্ব ২৩-এ, আর model card পড়ার অভ্যাস AI-এর ভিত্তি কোর্সের পর্ব ৭১-এ আছে। এখানে আমরা কাঠামোর ভেতরে যাব আর প্রশ্নটির মাপা উত্তর দেব।

এই পর্বের সীমানা. PyTorch-এ নিজের ছোট loop লিখে পরীক্ষাটি চালানো হয়েছে, যাতে প্রতিটি সিদ্ধান্ত চোখে দেখা যায়। Trainer API আর পুরো রেসিপি আসবে পর্ব ৫৪-তে (Transformer fine-tuning — classification, NER আর বাংলা ডেটা)। পাতার চারটি ব্লক ব্রাউজারের Python আর NumPy-তে চলে; PyTorch-এর সব আউটপুট ডেস্কটপের সত্যিকারের run.

২ · Input — token, segment, position আর [CLS]

BERT একটি লেখা বা দুটি লেখার জোড়া নেয়। জোড়া দরকার হয় যেমন প্রশ্ন আর অনুচ্ছেদ, কিংবা দুটি বাক্যের সম্পর্ক বোঝার কাজে। নিচে আসল tokenizer-এর আসল আউটপুট, একটি কাল্পনিক বাজারঘর রিভিউয়ের দুই ভাগ।

A = শাড়ির রং সুন্দর। · B = কিন্তু ডেলিভারি দেরিতে এসেছে।
ধাপ
সারি
Shape
  1. দুটি লেখা. ট্রেনের টিকিটে যেমন দুই যাত্রীর নাম আলাদা ঘরে থাকে, BERT-এর একটি sequence-এ দুটি লেখা বসে। এখানে দ্বিতীয়টি প্রথমটির উল্টো কথা বলে, তাই সম্পর্কটিই আসল তথ্য।
  2. Token. WordPiece tokenizer শব্দ ভাঙে। do_lower_case=False দিলে এই বাক্যের সব শব্দ vocabulary-তে পুরোটাই পাওয়া গেছে, “।” আলাদা token (পর্ব ৪০)।
  3. বিশেষ token. শুরুতে [CLS], প্রতিটি লেখার শেষে [SEP]. প্রবন্ধ অনুযায়ী [CLS]-এর শেষ hidden state পুরো sequence-এর প্রতিনিধি হিসেবে classification-এ যায়।
  4. input_ids. Vocabulary-র সারি নম্বর। [CLS] = 101, [SEP] = 102. দুটি “।” একই সংখ্যা 1014; একই token, একই সারি।
  5. token_type_ids. প্রথম অংশে 0, দ্বিতীয় অংশে 1. প্রথম [SEP] প্রথম অংশে পড়ে। এই সংখ্যা segment embedding-এর দুটি সারির একটি বেছে নেয়। একটি মাত্র লেখা হলে সব 0.
  6. Position. 0 থেকে 11. BERT-এ এটি শেখা table, ৫১২ সারির, তাই ৫১২-র বেশি token একবারে যায় না। দুটি “।”-এর token এক হলেও position আলাদা।
  7. যোগ. প্রবন্ধের ছবি অনুযায়ী তিনটি embedding যোগ হয়, জোড়া নয়। তারপর LayerNorm আর dropout. প্রতিটি vector ৭৬৮ মাত্রার, তাই (1, 12, 768).
  8. attention_mask. Mask-এ আসল token 1, PAD 0. এই একটি বাক্যে PAD নেই। Batch-এ ছোট লেখায় PAD বসে, যেমন [CLS] ভালো [SEP] [PAD] … আর mask 1 1 1 0 …. Mask না দিলে PAD-ও attention পায় (পর্ব ২৭)।
  9. Output. প্রতিটি token-এর জন্য একটি ৭৬৮-মাত্রার vector, আর pooler_output: [CLS]-এর vector একটি linear layer আর tanh পেরিয়ে। Classification-এর head এখান থেকে শুরু। পরের প্রশ্ন, কোন vector নেবেন।

৩ · ভেতরের গঠন আর parameter গোনা

বিয়েবাড়ির খরচের হিসাব মনে করুন। মোট টাকার অঙ্ক শুনে কিছু বোঝা যায় না, খাত ধরে ভাঙলে বোঝা যায় টাকা গেল কোথায়। Model-এর parameter-ও তাই। BERT-এর খাত চারটি: embedding, attention, FFN আর LayerNorm (block-এর গঠন পর্ব ৩৫-এ পড়েছেন), আর শেষে একটি pooler.

Bangla BERT: টাকা কোথায় গেল

Parameter counts for V = 102025, d = 768, 12 layers

BERT architecture with parameter counts Classification head768 × 2 + 2 = 1,538 (new, random) [CLS] vector, or poolerpooler: 590,592 Encoder layer × 12 Self-attention + LayerNorm2,363,904 FFN (768 → 3072 → 768) + LayerNorm4,723,968 Embedding: 78,751,488token + segment + position, LayerNorm input_ids · token_type_ids · attention_mask
ছবি ৪৮.১ — মোট ১৬,৪৩,৯৬,৫৪৪-এর প্রায় ৪৮% শুধু embedding-এ। Classification head নতুন, বাকি সব pretrained.

নিয়ম একটাই: Linear(a, b)-এ parameter a·b + b (bias-সহ); LayerNorm(d)-এ 2d; Embedding(n, d)-এ n·d. এবার হাতে গুনুন।

হাতের হিসাব
খাত
সূত্র
ফল
জমা
  1. Token table. প্রতিটি token-এর জন্য ৭৬৮টি সংখ্যা। Vocabulary ১,০২,০২৫ বলে এই এক খাতেই প্রায় ৭.৮ কোটি।
  2. Position আর segment. ৫১২টি অবস্থান আর ২টি segment. পুরো শেখা table, sinusoid নয় (পর্ব ৩৪)। খরচ খুব কম।
  3. LayerNorm. প্রতি মাত্রায় একটি scale আর একটি shift, তাই 2d. Embedding অংশ এখানে শেষ: E = ৭,৮৭,৫১,৪৮৮.
  4. Q, K, V. তিনটি Linear(768, 768). লক্ষ্য করুন, head-এর সংখ্যা নেই। ১২ head-এ ৬৪ মাত্রা করে ভাগ করলেও মোট weight একই (পর্ব ৩৩)।
  5. Attention output. আরেকটি Linear(768, 768) আর LayerNorm. এক layer-এর attention অংশ ২৩,৬৩,৯০৪.
  6. FFN. ৭৬৮ থেকে ৩০৭২, আবার ৭৬৮, সঙ্গে LayerNorm. Attention-এর প্রায় দ্বিগুণ। অনেকেই ভাবেন attention-ই সবচেয়ে ভারী; গুনলে উল্টো।
  7. এক layer. ২৩,৬৩,৯০৪ + ৪৭,২৩,৯৬৮ = ৭০,৮৭,৮৭২. প্রতিটি layer হুবহু একই মাপের।
  8. ১২ layer. L = ৮,৫০,৫৪,৪৬৪. গভীরতা বাড়ালে খরচ সোজা গুণ হয়; এই কারণেই DistilBERT আর ALBERT এখানে হাত দেয়।
  9. Pooler. একটি Linear(768, 768) আর tanh. [CLS] vector-কে একবার রূপান্তর করে।
  10. মোট. ৭,৮৭,৫১,৪৮৮ + ৮,৫০,৫৪,৪৬৪ + ৫,৯০,৫৯২ = ১৬,৪৩,৯৬,৫৪৪. আসল checkpoint-ও ঠিক এই সংখ্যা দেয়, নিচে দেখুন।

এবার একই হিসাব ফাংশনে। ভিন্ন vocabulary দিলে কী হয়, সেটিই এখানে দেখার জিনিস।

def bert_params(V, d=768, L=12, ffn=3072, P=512, S=2):
    emb = {"token": V * d, "position": P * d, "segment": S * d, "LayerNorm": 2 * d}
    attn = 3 * (d * d + d) + (d * d + d) + 2 * d       # Q, K, V, output, LayerNorm
    ff = (d * ffn + ffn) + (ffn * d + d) + 2 * d       # ওপরে, নিচে, LayerNorm
    layer = attn + ff
    pooler = d * d + d
    total = sum(emb.values()) + L * layer + pooler
    return emb, attn, ff, layer, pooler, total

emb, attn, ff, layer, pooler, total = bert_params(102025)
print("Bangla BERT, V = 102025")
for k, v in emb.items():
    print(f"  {k:10}{v:>12,}")
print(f"  {'attention':10}{attn:>12,} (এক layer)")
print(f"  {'FFN':10}{ff:>12,} (এক layer)")
print(f"  {'12 layers':10}{12 * layer:>12,}")
print(f"  {'pooler':10}{pooler:>12,}")
print(f"  {'total':10}{total:>12,}")
print("embedding-এর ভাগ:", round(sum(emb.values()) / total, 3))
print("আসল checkpoint-এর সঙ্গে মিল:", total == 164396544)
print(f"\n{'V':>8}{'total':>14}{'token ভাগ':>11}")
for V in (30522, 32000, 102025, 250002):
    e, *_, t = bert_params(V)
    print(f"{V:>8}{t:>14,}{e['token'] / t:>11.3f}")
E = 128
print("ALBERT-ধাঁচের token table, E = 128:", f"{102025 * E + E * 768:,}")
print("সাধারণ token table:", f"{102025 * 768:,}")

আউটপুট পড়ুন: V = 30522 দিলে 109,482,240, প্রবন্ধের “110M” এটিই। সেই একই কাঠামোতে V = 102025 দিলে 164,396,544: লাফটা পুরোটাই token table-এর। এজন্য model card-এর “110M” কথাটি কাঠামোর জন্য সত্য, এই checkpoint-এর জন্য নয় (পর্ব ৪৪-এ এটি দেখেছেন)। V = 250002 মানে এখানে শুধু একটি কল্পিত বড় vocabulary; XLM-R-এর আসল কাঠামো হুবহু এটি নয়। শেষ দুই লাইন: E = 128-এ token table প্রায় ছয় ভাগের এক ভাগ, যা factorized embedding-এর লাভ।

এবার আসল checkpoint. সত্যিকারের run (Python 3.14.4, torch 2.15.0, transformers 5.22.2):

~/bazarghar-dl$ uv run python inspect_model.py
{'vocab_size': 102025, 'hidden_size': 768, 'num_hidden_layers': 12, 'num_attention_heads': 12, 'intermediate_size': 3072, 'max_position_embeddings': 512, 'type_vocab_size': 2}
missing_keys [] | unexpected 7 ['cls.predictions.bias', ...]
total 164396544
embeddings 78751488 {'word_embeddings': 78355200, 'position_embeddings': 393216, 'token_type_embeddings': 1536, 'LayerNorm': 1536}
one layer 7087872 attention 2363904 self qkv 1771776 attn output 592128 ffn in 2362368 ffn out 2361600
all layers 85054464 pooler 590592
sizes MB float32 658

প্রতিটি সংখ্যা হাতের হিসাবের সঙ্গে মেলে। unexpected 7 মানে checkpoint-এ MLM আর NSP-র pretraining head-ও ছিল (cls.*); classification-এ সেগুলো কাজে লাগে না, তাই ফেলে দেওয়া হয়। Pooler-এর weight checkpoint-এই আছে, এলোমেলো নয়। ফলে শেখার সময়ে নতুন, এলোমেলো অংশ শুধু আমাদের head.

৪ · পরিবারের সদস্যরা — প্রত্যেকে কী বদলাল

পরিবারটিকে একটি মোটরসাইকেল মডেলের সংস্করণের মতো ভাবুন: ইঞ্জিন প্রায় এক, কেউ চালানোর নিয়ম বদলেছে, কেউ ওজন কমিয়েছে, কেউ অন্য রাস্তার জন্য বানিয়েছে। নিচের তথ্য মূল প্রবন্ধ আর model card থেকে, ২০২৬ সালের নভেম্বরে পড়া। প্রবন্ধের দাবি প্রবন্ধের নিজের ডেটায়; কোনোটিকে “সেরা” ধরবেন না।

Model (প্রবন্ধ)মূল বদলশেখার লক্ষ্যকোন ব্যয় কমে বা বাড়ে
BERT (Devlin ও সহকর্মীরা, ২০১৮)Encoder-only, দুই পাশের প্রসঙ্গ; L=12, H=768, A=12, প্রবন্ধে “110M”MLM + NSPভিত্তি
RoBERTa (Liu ও সহকর্মীরা, ২০১৯)কাঠামো প্রায় এক, শেখানোর পদ্ধতি বদল: dynamic masking, NSP বাদ, বড় batch, বেশি ডেটা (১৬০ GB পর্যন্ত), BPEশুধু MLMPretraining-এ বেশি খরচ
DistilBERT (Sanh ও সহকর্মীরা, ২০১৯)Layer অর্ধেক, token-type embedding আর pooler বাদ; প্রবন্ধে ৪০% কম parameter, ৬০% দ্রুত, GLUE-তে BERT-এর ৯৭%Distillation + MLMচালানোর খরচ কম
ALBERT (Lan ও সহকর্মীরা, ২০১৯)Factorized embedding, সব layer-এ একই weightMLM + sentence orderParameter কম; গণনা সেই অনুপাতে কমে না
ELECTRA (Clark ও সহকর্মীরা, ২০২০)ছোট generator token বদলায়, discriminator ধরে; শেষে শুধু discriminator থাকেReplaced token detectionপ্রবন্ধে: একই compute-এ MLM-এর চেয়ে দক্ষ
XLM-R (Conneau ও সহকর্মীরা, ২০১৯)১০০ ভাষা, ২ TB-র বেশি filter করা CommonCrawl; বাংলা তালিকায় আছে (৮.৪ GiB, রোমান হরফের বাংলা আলাদা)MLMBase ২৭০M, বড়টি ৫৫০M, প্রবন্ধে
BanglaBERT (Bhattacharjee ও সহকর্মীরা, NAACL Findings ২০২২)ELECTRA base, ১১০M; ১১০টি বাংলা সাইটের ২৭.৫ GB; নিজস্ব normalizerReplaced token detectionCard অনুযায়ী বাংলা-র জন্য বিশেষ

একটি জিনিস খেয়াল করুন: এখানে কয়েকটি প্রবন্ধের নিজের প্রথম পাতায় “state-of-the-art” লেখা। সেটি সেই সময়, সেই ডেটার দাবি। RoBERTa প্রবন্ধের মূল বার্তাটি বরং নম্র: BERT-কে যথেষ্ট শেখানো হয়নি, শুধু training-এর পদ্ধতি বদলেই অনেক লাভ। Encoder তাই তিনভাবে আলাদা হয়: কাঠামো, শেখার লক্ষ্য, আর ডেটা। Pretraining-এর তিনটি লক্ষ্যের হিসাব আছে পর্ব ৪৭ (Pretraining-এর লক্ষ্য — masked, causal আর span corruption)-এ।

লাইসেন্স আলাদা, card পড়ুন. ২০২৬ সালের নভেম্বরে card অনুযায়ী sagorsarker/bangla-bert-base MIT. BanglaBERT-এর Hub পাতায় লাইসেন্স tag দেখা যায়নি, আর তার repository-র README বলে বিষয়বস্তু অ-বাণিজ্যিক গবেষণার জন্য সীমিত (CC BY-NC-SA 4.0). তাই এই পর্বে BanglaBERT চালানো হয়নি, শুধু প্রবন্ধ আর card থেকে তথ্য নেওয়া। DistilBERT multilingual-এর card-এ Apache 2.0. লাইসেন্স বদলাতে পারে; ব্যবসায় লাগানোর আগে card নিজে পড়ুন (AI-এর ভিত্তি কোর্সের পর্ব ৭১)।

৫ · পরীক্ষার প্রস্তুতি — একই ডেটা, একই ভাগ, আর tokenizer-এর ফাঁদ

তুলনা সৎ হতে হলে দুই পক্ষকে একই প্রশ্নপত্র দিতে হয়। তাই আগে পর্ব ৪২-এর ডেটা নতুন করে Mendeley থেকে নামিয়ে (Salim Sazzed-এর corpus, DOI 10.17632/p6zc7krs37.4, CC BY 4.0; YouTube-এর বাংলা নাটকের মন্তব্য, বাজারঘরের রিভিউ নয়) একই পরিষ্কার করার কোড আর একই seed-এর StratifiedGroupKFold চালানো হলো। ভাগ সত্যিই এক কি না, তার প্রমাণ: TF-IDF আবার চালালে পর্ব ৪২-এর রেকর্ড করা স্কোর হুবহু আসা উচিত। সত্যিকারের run; এই পর্বের আউটপুটগুলো ছেঁটে আর কয়েকটি আলাদা চালানোর লাইন এক জায়গায় সাজিয়ে দেখানো, সংখ্যাগুলো হুবহু:

~/bazarghar-dl$ uv run python tc_prep.py
lines 11807 | negative 3307 | positive 8500
unique after NFC + whitespace 10711 | same text, both labels 2 (dropped) | kept 10709
clean.json identical to lesson 42 copy: True
~/bazarghar-dl$ uv run python baseline.py ...   # rep, fold, নতুন স্কোর, পর্ব ৪২-এর স্কোর
0 0 0.9412 old 0.9412 abs diff 0.0 C 16
0 1 0.9337 old 0.9337 abs diff 0.0 C 16
0 2 0.9286 old 0.9286 abs diff 0.0 C 64
…                                   # ১৫টি ভাগেই abs diff 0.0

এবার tokenizer. পর্ব ৪৪-এ দেখেছেন, sagorsarker/bangla-bert-base-এ tokenizer_config.json নেই, তাই ডিফল্টে lowercase আর accent মোছা চালু থাকে। হসন্ত, উ-কার আর নুকতা তাতে মুছে যায়। প্রশ্ন হলো, এটি আসল কাজে কত ক্ষতি করে? এই পর্বের জন্য নতুন মাপ, সত্যিকারের run:

A, B = "শাড়ির রং সুন্দর।", "কিন্তু ডেলিভারি দেরিতে এসেছে।"
do_lower_case=False  ['[CLS]', 'শাড়ির', 'রং', 'সুন্দর', '।', '[SEP]', 'কিন্তু', 'ডেলিভারি', 'দেরিতে', 'এসেছে', '।', '[SEP]']
default              ['[CLS]', 'শা', '##ডির', 'রং', 'সনদ', '##র', '।', '[SEP]', 'কিনত', 'ডেলিভারি', 'দেরিতে', 'এসেছে', '।', '[SEP]']

পুরো ডেটা, 10709 সারি:
do_lower_case=False | mean len 19.4 | p50 14 | p99 99 | max 320 | rows over 128 52 | unk share 0.0208
default             | mean len 25.3 | p50 17 | p99 132 | max 431 | rows over 128 124 | unk share 0.0164
rows with identical ids under both tokenizers 787 of 10709

“সুন্দর” হলো “সনদ ##র”, “কিন্তু” হলো “কিনত”. ১০,৭০৯-এর মধ্যে মাত্র ৭৮৭টি সারির id দুই tokenizer-এ এক. বাকি সব সারিতে input বদলে গেছে, অথচ কোনো error নেই। এই fine-tuning-এর সব run-এ তাই do_lower_case=False, শুধু একটি পরীক্ষা ছাড়া, যার ফল অংশ ৭-এর টেবিলে। লম্বা লেখা max_length=100-এ কাটা হয়েছে: ৯৯% সারি এতেই আসে, আর GPU-তে memory সীমায় রাখা যায়। (প্রথম চেষ্টা ১২৮-এ ছিল; এক fold-এর peak allocation প্রায় ৪.২ GiB দেখে কমানো হয়।)

tok = AutoTokenizer.from_pretrained(name, do_lower_case=False)
class Clf(nn.Module):
    def __init__(s, name, pool):
        super().__init__()
        s.enc = AutoModel.from_pretrained(name)
        s.pool, s.drop = pool, nn.Dropout(0.1)
        s.fc = nn.Linear(s.enc.config.hidden_size, 2)   # নতুন head
    def forward(s, x):                                  # x (N, T)
        m = x != pad
        h = s.enc(input_ids=x, attention_mask=m.long()).last_hidden_state
        if s.pool == "cls":                             # h (N, T, d)
            v = h[:, 0]
        else:
            w = m.unsqueeze(-1).float()
            v = (h * w).sum(1) / w.sum(1)
        return s.fc(s.drop(v))                          # (N, 2)

Training-এর নিয়ম প্রবন্ধের সুপারিশের সীমার মধ্যে আগেই ঠিক করা, স্কোর দেখে নয়: AdamW, lr ২ × ১০−৫, weight decay ০.০১ (bias আর LayerNorm বাদে), batch ৩২, ৩ epoch, প্রথম ১০% ধাপে warmup তারপর linear কমা (পর্ব ১৫), gradient clipping ১.০, পর্ব ১২-এর bf16 autocast, পর্ব ৪২-এর মতোই class weight আর validation macro-F1 দিয়ে epoch বাছাই। এই কোডে token_type_ids দেওয়া হয়নি, কারণ একটি লেখায় সবই 0, যা ডিফল্ট। [CLS] নেওয়া হয়েছে শেষ layer থেকে; pooler-এর weight checkpoint-এ থাকলেও ব্যবহার হয়নি। ওপরের কোডটি পুরো ফাইল থেকে ছোট করা; পুরো loop-এ সময় আর memory মাপার লাইনও আছে। Version: Python 3.14.4, torch 2.15.0, transformers 5.22.2, GPU-তে, লেখার সময়ে।

৬ · Head — [CLS] নাকি সবার গড়

একটি ক্লাসের মত জানতে চান। দুটি উপায়: ক্লাস ক্যাপ্টেনের কাছ থেকে শোনা, অথবা সবাইকে জিজ্ঞেস করে গড় নেওয়া। [CLS] সেই ক্যাপ্টেন: নিজে কোনো শব্দ নয়, বাকি সবার দিকে attention দিয়ে একটি সারাংশ বানায়, আর fine-tuning-এর সময় সে কাজটিই আরও ভালো শেখে। Mean pooling মানে সব আসল token-এর vector-এর গড়। ভুলের জায়গা একটিই: PAD-কে গড়ে ঢোকানো।

import numpy as np
# (N, T, d) = (2, 5, 3): কল্পিত output. সারি ১-এ ৩টি আসল token + ২টি PAD,
# সারি ২-এ ৪টি আসল + ১টি PAD. PAD-এর vector শূন্য হয় না।
h = np.array([[[1., 0, 2], [3, 1, 0], [5, 2, 1], [9, 9, 9], [9, 9, 9]],
              [[2., 2, 2], [0, 4, 2], [4, 0, 2], [2, 2, 2], [8, 8, 8]]])
mask = np.array([[1, 1, 1, 0, 0],
                 [1, 1, 1, 1, 0]])
cls = h[:, 0]                              # শুধু প্রথম token
w = mask[..., None]                        # (2, 5, 1)
mean = (h * w).sum(1) / w.sum(1)           # শুধু আসল token-এর গড়
wrong = h.mean(1)                          # mask ভুলে গেলে
print("shape", h.shape, "| w", w.shape)
print("CLS       ", cls.tolist())
print("mean+mask ", mean.round(3).tolist())
print("mask ছাড়া", wrong.round(3).tolist())
# একই সারি ১, এবার batch-এ PAD নেই
short, m2 = h[:1, :3], mask[:1, :3, None]
print("PAD ছাড়া, mean+mask:", ((short * m2).sum(1) / m2.sum(1)).round(3).tolist())
print("PAD ছাড়া, mask ছাড়া:", short.mean(1).round(3).tolist())
print("আসল token প্রতি সারিতে:", mask.sum(1).tolist())

আউটপুট পড়ুন: সারি ১-এ mean+mask দেয় [3.0, 1.0, 1.0], কিন্তু mask ছাড়া [5.4, 4.2, 4.2]: শুধু PAD-এর ৯-এর কারণে। পরের দুই লাইনে একই সারি PAD ছাড়া: এবার দুটি পদ্ধতিই [3.0, 1.0, 1.0]. অর্থাৎ mask ছাড়া গড়ে একই লেখার vector নির্ভর করে batch-এ কত লম্বা লেখা পড়েছে তার ওপর। Error কখনো আসে না, শুধু model চুপচাপ খারাপ করে। এখানে PAD-এর ৯ সংখ্যাটি কল্পিত। আসল model-এও PAD-এর output শূন্য নয়: mask শুধু attention-এ PAD-কে উপেক্ষা করে, PAD নিজের output ঠিকই পায়। তাই masked pooling আলাদা করে লাগে।

আসল ডেটায় repeat ০-এর ৫টি ভাগে। সত্যিকারের run, কলাম ছাঁটা (head মানে encoder জমানো, full মানে সব শেখে):

config            n  macro     sd  F1neg  F1pos    val   ep
tfidf             5  0.939  0.008  0.913  0.965  0.945    0
head-cls          5  0.876  0.010  0.824  0.927  0.883   26
head-mean         5  0.881  0.013  0.833  0.929  0.887   22
full-cls          5  0.940  0.008  0.914  0.965  0.942    3
full-mean         5  0.940  0.010  0.915  0.965  0.941    3

head-mean - head-cls: J=5  mean diff +0.0052 | corrected t 0.82 p=0.46  | CI [-0.0126, +0.0231]
# ১৫ ভাগে একই head: cls 0.874, mean 0.880

পড়ুন: Encoder জমানো থাকলে গড় একটু এগিয়ে, যদিও ৫ ভাগে পার্থক্য শূন্য থেকে আলাদা করা যায় না। পুরো শেখালে দুটি কার্যত সমান (দুটিই ০.৯৪০). যুক্তিটি সহজ: জমানো অবস্থায় [CLS] এখনো কোনো sentiment-এর কাজ জানে না, আর গড় অন্তত শব্দের তথ্য তুলে আনে; পুরো শেখালে [CLS] নিজেই কাজটি শেখে। তবে এটি ব্যাখ্যা, এই পরীক্ষা তা প্রমাণ করেনি, আর লম্বা লেখা বা অন্য ডেটায় ছবি ভিন্ন হতে পারে। পর্ব ৪৪-এ দেখা word_ids()-ভিত্তিক শব্দের গড় আলাদা কাজ: সেখানে একটি শব্দের vector, এখানে পুরো লেখার।

৭ · Freeze, unfreeze আর layer-wise learning rate

নতুন শাখা ম্যানেজারের কথা ভাবুন। প্রথমে শুধু রিপোর্টের ফর্ম শেখানো (head-only). তারপর ওপরের দুই স্তরের নিয়ম বদলাতে দেওয়া (শেষ layer-গুলো খোলা)। শেষে পুরো অফিস, তবে নিচের স্তরে সাবধানে (layer-wise decay). মাপার প্রশ্ন: কতটা খুললে কত লাভ। ধারণাটি ছবির ক্ষেত্রে পর্ব ২৩-এ দেখেছেন; Sun ও সহকর্মীদের ২০১৯ সালের প্রবন্ধে BERT-এ এর সূত্র হলো:

ηk−1 = ξ · ηk

ηk হলো layer k-এর learning rate, ξ (উচ্চারণ “জাই”) একটি গুণক, ১ বা তার কম। ξ = ১ মানে সবার সমান। প্রবন্ধে IMDb ডেটায় ξ = ০.৯৫ আর lr = ২ × ১০−৫ উপযুক্ত সেটিং হিসেবে এসেছে; এটি তাঁদের ডেটার ফল, সর্বজনীন নিয়ম নয়। নিচে আমাদের checkpoint-এর গোনা parameter ধরে টেবিল।

base = 5e-5                                # head-এর learning rate
layer, emb, head = 7087872, 78751488, 592130   # পর্বের গোনা parameter
rows = [("head", head, 0), ("layer 12", layer, 1), ("layer 9", layer, 4),
        ("layer 6", layer, 7), ("layer 3", layer, 10), ("layer 1", layer, 12),
        ("embedding", emb, 13)]            # k = head থেকে কত ধাপ নিচে
xs = (1.0, 0.95, 0.9, 0.8)
print(f"{'group':10}" + "".join(f"{'xi=' + str(x):>10}" for x in xs))
for n, p, k in rows:
    print(f"{n:10}" + "".join(f"{base * x ** k:>10.2e}" for x in xs))
ks = [0] + list(range(1, 13)) + [13]
ps = [head] + [layer] * 12 + [emb]
print()
for x in xs:
    mean = sum(p * base * x ** k for p, k in zip(ps, ks)) / sum(ps)
    print(f"xi={x}: parameter-ভারিত গড় lr {mean:.2e}"
          f" | ওপর/নিচ অনুপাত {1 / x ** 13:.1f}")
print("embedding-এর ভাগ:", round(emb / sum(ps), 3))

আউটপুট পড়ুন: ξ = ০.৯-এ embedding-এর lr 1.27e-05, মানে head-এর প্রায় চার ভাগের এক ভাগ (3.9 গুণ পার্থক্য)। Bangla BERT-এ একটি বিশেষ ব্যাপার: প্রায় 0.479 অংশ parameter থাকে সবচেয়ে নিচের embedding-এ। তাই head-এ ৫ × ১০−৫ দেখে বিভ্রান্ত হবেন না: parameter-ভারিত গড় 2.02e-05, অর্থাৎ আমাদের সাধারণ সেটিংয়ের কাছাকাছি। এজন্য তুলনায় আমরা এই দুটিকেই নিয়েছি। ξ = ০.৮-এ নিচের দিকের অনুপাত 18.2 গুণ; এত কম lr-এ সেই layer প্রায় স্থির থাকে।

আসল পরীক্ষা, repeat ০-এর একই ৫ ভাগ। সত্যিকারের run, GPU-তে; একই run-এর কয়েকটি কলাম এক টেবিলে সাজানো:

config        শিখছে (parameter)  macro    sd   F1neg  F1pos  val
head-cls               1,538     0.876  0.010  0.824  0.927  0.883
last2-cls         14,177,282     0.900  0.011  0.860  0.940  0.902
full-cls         164,398,082     0.940  0.008  0.914  0.965  0.942
full-cls-llrd    164,398,082     0.938  0.009  0.912  0.964  0.941
trap-cls         164,398,082     0.929  0.008  0.899  0.959  0.928
distil-mlang     134,735,618     0.924  0.010  0.893  0.956  0.931
full-cls - last2-cls: J=5 mean diff +0.0392 | corrected t 8.44 p=0.0011 | CI [+0.0263, +0.0520]
full-cls-llrd - full-cls: J=5 mean diff -0.0016 | corrected t -0.84 p=0.45 | CI [-0.0067, +0.0036]
full-cls - full-mean: J=5 mean diff -0.0007 | corrected t -0.26 p=0.81 | CI [-0.0082, +0.0068]
full-cls - trap-cls: J=5 mean diff +0.0108 | corrected t 8.40 p=0.0011 | CI [+0.0072, +0.0144]
  • Head-only থেকে full: ০.৮৭৬ থেকে ০.৯৪০. এই ডেটায় encoder না খুললে সে নাটকের মন্তব্যের ভাষায় খাপ খায় না। শেষ দুটি layer খোলা (last2-cls) মাঝামাঝি, ০.৯০০; ৩ epoch আর একই lr-এ। বেশি epoch বা বড় lr-এ এই সংখ্যা হয়তো বদলাত, যা মাপা হয়নি।
  • Layer-wise decay: সুবিধা দেখা গেল না (০.৯৩৮ বনাম ০.৯৪০), ৫ ভাগে পার্থক্য শূন্য থেকে আলাদা নয়। ξ আর lr নিয়ে খোঁজা হয়নি, মাত্র একটি জোড়া চেষ্টা করা হয়েছে; তাই “LLRD কাজ করে না” বলা যাবে না, শুধু বলা যায় এই ছোট কাজে একটি যুক্তিসঙ্গত সেটিংয়ে কোনো লাভ দেখা যায়নি।
  • ফাঁদের দাম: শুধু do_lower_case ভুল থাকায় ০.৯৪০ থেকে ০.৯২৯. Hyperparameter খোঁজার বহু ঘণ্টার চেয়ে এই এক লাইন বেশি মূল্যবান হতে পারে।
  • DistilBERT multilingual (distilbert/distilbert-base-multilingual-cased, Apache 2.0, ৬ layer) ০.৯২৪: বাংলা-বিশেষ নয়, ছোট আর দ্রুত। এটি শুধু এই একটি পরীক্ষায় একটি বিন্দু, দুই model-এর ক্রম নয়।

কোন configuration নিয়ে মূল তুলনা হবে, সেটি আগেই ঠিক করা ছিল (full, CLS, lr ২ × ১০−৫); test স্কোর দেখে বদলানো হয়নি। Encoder-এর সেটিংগুলোর মধ্যে validation-এও সেটি সবচেয়ে ওপরে (০.৯৪২). পরের অংশ তার সঙ্গে TF-IDF-এর মুখোমুখি।

৮ · পর্ব ৪২-এর প্রশ্নের উত্তর — একই ১৫ ভাগে মুখোমুখি

ফলটি আগে বলে নিই, এরপর প্রমাণ। ১৫টি একই ভাগে fine-tuned Bangla BERT-এর গড় macro-F1 ০.৯৩৯, TF-IDF + logistic regression-এর ০.৯৪১. শূন্য থেকে শেখা model-গুলোর (০.৯০৪ থেকে ০.৯১৭) চেয়ে encoder অনেক এগিয়ে, কিন্তু baseline-কে হারায়নি। হেরেছেও বলা যাবে না। সত্যিকারের run:

~/bazarghar-dl$ uv run python analyze.py   # সব ভাগ, কয়েকটি কলাম ও সারি ছাঁটা
config            n  macro     sd  F1neg  F1pos    val
tfidf            15  0.941  0.007  0.916  0.966  0.942
full-cls         15  0.939  0.006  0.913  0.965  0.940
head-mean        15  0.880  0.009  0.832  0.929  0.884
head-cls         15  0.874  0.009  0.823  0.925  0.876

full-cls - tfidf: J=15 mean diff -0.0016 | sd 0.0063 | full-cls ahead 7/15
  naive t -0.97 p=0.35 | corrected t -0.42 p=0.68 | CI [-0.0095, +0.0064]
McNemar repeat 0, all rows: only tfidf wrong 251 | only bert wrong 245 | both 286 | chi2 0.1 p=0.82
confusion tfidf [[2818, 235], [302, 7354]] bert [[2819, 234], [297, 7359]]

Fold-এর স্কোরগুলো নিজের হাতে পরীক্ষা করুন। নিচের ব্লকে ঠিক এই run-এর ১৫ জোড়া সংখ্যা বসানো; corrected resampled t-এর সূত্র মেশিন লার্নিং কোর্সের পর্ব ২২-এ।

import numpy as np
from scipy import stats
# ১৫টি একই ভাগে আসল macro-F1 (সত্যিকারের run থেকে, ক্রম একই)
bert = np.array([.9385, .9360, .9298, .9498, .9436, .9411, .9380, .9442,
                 .9322, .9349, .9339, .9340, .9364, .9527, .9401])
tfidf = np.array([.9412, .9337, .9286, .9506, .9402, .9528, .9307, .9414,
                  .9452, .9451, .9344, .9391, .9330, .9475, .9451])
def ci(d, ratio=2143 / 7510):
    J = len(d); s2 = d.var(ddof=1)
    se_naive = np.sqrt(s2 / J)
    se = np.sqrt(s2 * (1 / J + ratio))         # Nadeau-Bengio সংশোধন
    q = stats.t.ppf(0.975, J - 1)
    return d.mean(), se_naive, se, d.mean() - q * se, d.mean() + q * se, q * se
d = bert - tfidf
m, sn, se, lo, hi, half = ci(d)
print(f"J={len(d)} গড় পার্থক্য {m:+.4f} | BERT এগিয়ে {(d > 0).sum()} ভাগে")
print(f"naive SE {sn:.5f} | corrected SE {se:.5f} | গুণ {se / sn:.2f}")
print(f"corrected 95% CI [{lo:+.4f}, {hi:+.4f}]")
print(f"t = {m / se:.2f} | p = {2 * stats.t.sf(abs(m / se), len(d) - 1):.2f}")
print(f"ব্যবধানের অর্ধেক প্রস্থ: ±{half:.4f}")
print("--- শুধু প্রথম ৫ ভাগ")
m5, _, _, l5, h5, _ = ci(d[:5])
print(f"গড় {m5:+.4f} | CI [{l5:+.4f}, {h5:+.4f}]")

আউটপুট পড়ুন: সংশোধিত ব্যবধান [-0.0095, +0.0064], ভেতরে শূন্য। “সমান” প্রমাণ হয়নি; শুধু বলা যায়, প্রায় ±0.0080-এর মতো ছোট পার্থক্য এই পরীক্ষা নির্ভরযোগ্যভাবে আলাদা করতে পারে না। সংশোধনে SE 2.30 গুণ বড় হয়। শুধু প্রথম ৫ ভাগ নিলে গড় হয় +0.0007, ব্যবধান [-0.0040, +0.0054]: এবার BERT গড়ে সামান্য এগিয়ে, অর্থাৎ পার্থক্যের চিহ্নটিও ভাগের ওপর নির্ভর করে। Repeat ০-এর ওপরের ০.৯৪০ বনাম ০.৯৩৯ তাই কোনো ইঙ্গিত নয়।

যা দাঁড়াল

এই ডেটায়, এই run-এ, Bangla BERT আর TF-IDF + LR-এর পার্থক্য মাপার সীমার ভেতরে. পর্ব ৪২-এর খোলা প্রশ্নের উত্তর: pretrained encoder শূন্য থেকে শেখা সব model-কে পেছনে ফেলেছে, এবং encoder না খোলা অবস্থায় baseline-এর অনেক নিচে থেকেছে। কিন্তু দামি পথটি সস্তা পথের চেয়ে ভালো, এমন প্রমাণ এখানে নেই। এটি একটি দুই-শ্রেণির, ছোট মন্তব্যের ডেটা; এক checkpoint-এর মাত্র একটি সেটিং। অন্য ডেটায়, অন্য checkpoint-এ, বা তিন শ্রেণির কাজে ফল আলাদা হতে পারে। BanglaBERT আমরা চালাইনি (অংশ ৪-এর লাইসেন্সের কারণে), তাই তার সম্পর্কে এই মাপ কিছু বলে না।

কিন্তু ভুলগুলো এক নয়. ওপরের McNemar দেখুন: দুজনই ভুল ২৮৬টিতে, শুধু TF-IDF ২৫১, শুধু BERT ২৪৫. প্রায় সমান সংখ্যা, কিন্তু অর্ধেকের বেশি ভুল আলাদা জায়গায়। পর্ব ৪২-এ TF-IDF যে সারিগুলোতে ভুল করেছিল, তার কয়েকটি (সবগুলো আগেই প্রকাশিত, নাম বা গালি নেই) এবার দেখুন। P = ধনাত্মক সম্ভাবনা, repeat ০-এর test ভাগে।

লেখালেবেলTF-IDF PBERT P
ধেত,শেষটা মানতে পারলাম না...ঋণাত্মক0.820.00
সময় টাই মাটি করলামঋণাত্মক0.900.03
আধিক্যেতায় ভরা একটি নাটক।ঋণাত্মক0.920.02
লো বাজেট নাটক বাট ভালো হইছেধনাত্মক0.370.86
ভাললাগচ্ছেধনাত্মক0.350.89
বেতালা ভালা লাগেরধনাত্মক0.330.05
মজা পেলামঋণাত্মক0.900.98
হাসতে হাসতে আমি শেষঋণাত্মক0.790.93

বাগধারা আর বিরল শব্দে এখানে BERT ঠিক; আঞ্চলিক “বেতালা ভালা লাগের”-এ দুজনই ভুল; আর পর্ব ৪২-এ “লেবেল সন্দেহজনক” বলে চিহ্নিত দুটি সারিতে দুজনই লেবেলের উল্টো দিকে, বরং আরও জোর দিয়ে। এটি হাতে বাছা উদাহরণ, ভাগের গড় নয়; বাছাই না দেখে এগুলোকে BERT-এর শ্রেষ্ঠত্বের প্রমাণ ভাববেন না।

একটি ছোট বাড়তি পরীক্ষা. দুই পরীক্ষক আলাদা জায়গায় ভুল করলে, নম্বর গড় করা কাজে আসে। দুটি probability-র সরল ৫০/৫০ গড়, কোনো ওজন না খুঁজে, একই ১৫ ভাগে। এটি পরিকল্পনায় ছিল না, ভুলের ওপরের ছবি দেখে পরে যোগ করা, তাই একে অনুসন্ধানী ফল হিসেবেই পড়ুন। সত্যিকারের run:

tfidf 0.9406 0.0073
bert 0.939 0.0064
mean50 0.9468 0.0069
mean50 - tfidf: J=15 diff +0.0062 | ahead 13/15 | corrected t 2.24 p=0.042 | CI [+0.0003, +0.0122]
mean50 - bert: J=15 diff +0.0078 | ahead 15/15 | corrected t 2.85 p=0.013 | CI [+0.0019, +0.0137]

এই একটি বিন্দুতে সংমিশ্রণ দুজনের চেয়েই এগিয়ে, যদিও TF-IDF-এর সঙ্গে ব্যবধানের নিচের সীমা শূন্যের গা ঘেঁষে। লাভ সামান্য, দাম দুই model-এর সমান।

৯ · সময়, memory আর সিদ্ধান্ত — fine-tune নাকি baseline

স্কোর সমান হলে সিদ্ধান্ত হয় খরচে। Training-এর সময় ১৫ fold-এর মধ্যমা; অনুমানের সময় আলাদা করে তিনবার মাপা, একই ১,০০০টি test সারি। সত্যিকারের run. একই মেশিনে অন্যদের কাজও চলছিল, আর প্রথম কয়েকটি fold GPU-র ভিড়ে অনেক ধীর ছিল (৮৪০ সেকেন্ড পর্যন্ত, ভিড় কমলে প্রায় ১০০), তাই সংখ্যাগুলো একটি মোটামুটি মাপ। Device-এর নাম আর শেষের মন্তব্য-লাইন সাজানো, কিছু ঘর ছাঁটা:

~/bazarghar-dl$ uv run python timing.py
tfidf+lr  CPU  1000 rows 66 ms | pickle 1.5 MB
bangla-bert-base           GPU  1000 rows     356 ms | float32 658 MB | inference peak alloc 873 MiB
bangla-bert-base           CPU  1000 rows    7629 ms
distilbert-base-multilingu GPU  1000 rows     261 ms | float32 539 MB | inference peak alloc 668 MiB
distilbert-base-multilingu CPU  1000 rows    7068 ms
# training, মধ্যমা: full-cls 102 s, peak alloc 3950 MiB | TF-IDF (৪টি C মিলিয়ে) 3.9 s, CPU-তে
খাতTF-IDF + LRBangla BERTমোটা অনুপাত
Training, এক ভাগ৩.৯ সেকেন্ড, CPU১০২ সেকেন্ড, GPUপ্রায় ২৬ গুণ, আর GPU দরকার
অনুমান, হাজারে, CPU৬৬ ms৭,৬২৯ msপ্রায় ১১৫ গুণ
Model-এর আকার১.৫ MB৬৫৮ MBপ্রায় ৪৪০ গুণ
Macro-F1, ১৫ ভাগ০.৯৪১০.৯৩৯পার্থক্য ব্যবধানের ভেতরে

১,০০০ সারিতে ৭.৬ সেকেন্ড মানে প্রতি রিভিউতে প্রায় ৮ ms; বাজারঘরের একটি ছোট কাজে এটি মোটেও অচল নয়। দামটা মূলত memory, GPU আর ঝামেলার। বরং দেখুন peak allocation: training-এ প্রায় ৩.৯ GiB, অনুমানে এক GiB-এর নিচে; দুটিই শুধু এই run-এর নিজের হিসাব। ক্রিকেটের দল বাছার মতো: সমান রান করা দুই ব্যাটারের মধ্যে যিনি কম পারিশ্রমিক নেন, দল তাঁকেই নেবে, যদি না অন্য খেলার কথা ওঠে।

একই প্রশ্নগুলো, ক্রমে
প্রশ্ন
মাপা সংখ্যা
মানে
  1. Baseline. প্রথম কাজ, এবং সস্তা: একই ভাগে TF-IDF. এই সংখ্যা না থাকলে ০.৯৩৯-কে “দারুণ” বলে ভুল করতেন।
  2. Head-only. পরের সস্তা ধাপ, ১,৫৩৮টি parameter. এখানে ০.৮৭৪ আর ০.৮৮০, baseline-এর অনেক নিচে। ডেটা কম হলে বা GPU না থাকলে এই ধাপের দাম খুব কম, তাই আগে দেখে নেওয়া ভালো।
  3. Full. ১৬.৪ কোটি parameter খোলা। ০.৯৩৯. আর tokenizer ভুল থাকলে ৫ ভাগে ০.৯২৯; আগে সেটি মিলিয়ে নেওয়া চাই।
  4. পার্থক্য আসল? ব্যবধানে শূন্য আছে, ১৫-এর ৭ ভাগে BERT এগিয়ে। কেউ জিতেছে বলা যায় না।
  5. দাম. অনুমানে প্রায় ১১৫ গুণ, আকারে প্রায় ৪৪০ গুণ, সঙ্গে GPU. একই স্কোরে বেশি দামের যুক্তি নেই।
  6. ভুল আলাদা? হ্যাঁ, ফলে সরল গড় ০.৯৪৭, অনুসন্ধানী ফল। প্রায় ০.০০৬ লাভের জন্য দুটি model চালানো যুক্তিসঙ্গত কি না, সেটি কাজের খরচের হিসাব।
  7. রায়, এই ডেটায়. TF-IDF + logistic regression চালান: সস্তা, ব্যাখ্যা করা সহজ। মনে রাখুন, এটি নাটকের মন্তব্যের একটি মাপ, বাজারঘরের রিভিউ নয়।
  8. অন্য ক্ষেত্রে. লেবেল খুব কম, প্রশ্ন-উত্তর বা দুটি লেখার সম্পর্কের কাজে, কিংবা অর্থ যেখানে শব্দের বাইরে, সেখানে encoder-এর সুযোগ বেশি থাকতে পারে; এগুলো এখানে মাপা হয়নি। কম লেবেলের প্রশ্নে পর্ব ৪৭ (Pretraining-এর লক্ষ্য — masked, causal আর span corruption) দেখতে পারেন। যেকোনো ক্ষেত্রে একই ভাগে baseline রেখে মাপুন।

আরও গভীরে: Sun ও সহকর্মীদের ২০১৯ সালের প্রবন্ধ জানায়, লম্বা লেখা কাটতে প্রথম ১২৮ আর শেষের ৩৮২ token রাখা (head+tail) তাদের IMDb আর Sogou ডেটায় সবচেয়ে ভালো ছিল, আর ওপরের layer শ্রেণিবিভাগে বেশি কাজের। Sanh ও সহকর্মীদের DistilBERT প্রবন্ধে CPU-তে ৬৬M parameter, ৪১০ সেকেন্ড বনাম BERT-base ১১০M, ৬৬৮ সেকেন্ড, STS-B-র এক পাসে; এটি তাদের ব্যবস্থায় মাপা। ELECTRA প্রবন্ধ FLOP ধরে তুলনা করে, শুধু step ধরে নয়। আমাদের নিজের DistilBERT-এর সময় ওপরে আছে, ভিন্ন checkpoint আর ভিন্ন ডেটায়।

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

  • V = 30522, L = 12, d = 768, FFN = 3072 হলে BERT-এর parameter? ১০,৯৪,৮২,২৪০: embedding ২,৩৮,৩৭,১৮৪ + ১২ × ৭০,৮৭,৮৭২ + pooler ৫,৯০,৫৯২.
  • Head কমালে মোট parameter কি বদলায়? না, যদি d স্থির থাকে; ভাগ শুধু d/h টুকরোয়।
  • Mask ছাড়া mean pooling-এ কী হয়? PAD গড়ে ঢোকে, ফল batch-ভেদে বদলায়, কোনো error ছাড়াই।
  • DistilBERT বা ALBERT কীভাবে ছোট হয়? DistilBERT layer কমিয়ে distillation-এ; ALBERT embedding ভেঙে আর layer-এ weight ভাগ করে।
  • LLRD কী? ওপর থেকে নিচে প্রতি layer-এ lr-কে ξ দিয়ে গুণ; নিচের layer কম বদলায়।

১০ · AI-এর যুগে — fine-tuning কোড আর দাবি যাচাই

AI assistant-কে “BERT দিয়ে sentiment classifier” বললে দশ লাইনে কোড আসে, আর একটি উঁচু সংখ্যাও। এই পর্বের ফাঁদগুলো ঠিক সেই দশ লাইনেই লুকায়।

AI প্রায়ই যা করেকেন সমস্যাকীভাবে ধরবেন
Tokenizer ডিফল্টে রেখে দেয়এই checkpoint-এ হসন্ত, উ-কার মুছে যায়, কোনো error ছাড়াকয়েকটি বাক্যের token নিজে ছাপানো
Mask ছাড়া .mean(1)PAD-এর vector গড়ে ঢোকে; batch-ভেদে ফল বদলায়একই বাক্য ভিন্ন padding-এ চালিয়ে মেলানো
পুরোনো argument, যেমন বাদ পড়া evaluation_strategyVersion-ভেদে ভাঙেনিজের version-এর নথি পড়া, ছোট batch-এ চালানো
Freeze করে ভাবে সব জমে আছেmodel.train()-এ dropout তখনও চলে; optimizer-এ সব parameter গেলে ভুল ধরা পড়ে নাকত parameter শিখছে গোনা; এক batch পরে weight মেলানো
Test দিয়ে epoch বা lr বাছাTest আর অচেনা থাকে নাValidation-এ বাছাই, test একবার
“BERT মানেই ভালো”, baseline ছাড়াপার্থক্য মাপা হলো না; খরচ দেখা হলো নাএকই ভাগে TF-IDF, paired ব্যবধান
Parameter বা benchmark সংখ্যা স্মৃতি থেকে বলেCard-এর “110M” আর এই checkpoint-এর ১৬.৪ কোটি আলাদাnumel() দিয়ে নিজে গোনা, মূল প্রবন্ধ পড়া
“Model বাংলা বোঝে” ধরনের ভাষামাপা হয়েছে শুধু একটি লেবেলের মিলদাবিকে ডেটা, ভাগ আর ব্যবধানে নামানো

Assistant-কে কোড চাওয়ার সময় পরীক্ষাগুলোও চাইতে বলুন। গ্রাহকের আসল রিভিউ prompt-এ দেবেন না; কয়েকটি বানানো সারি যথেষ্ট।

একটি বাংলা BERT checkpoint-কে দুই-শ্রেণির sentiment কাজে fine-tune করার PyTorch কোড লিখে দাও। আমার version: torch 2.15, transformers 5.x. যে API নিয়ে নিশ্চিত নও, তা চিহ্নিত করো। ১. আগে ৫টি বাংলা বাক্যের token ছাপাও, হসন্ত আর উ-কার ঠিক আছে কি না যাচাইয়ের লাইনসহ। ২. Head দুটি রকম: [CLS] আর mask-সহ গড়। প্রতিটি tensor-এর shape মন্তব্যে লেখো। ৩. Train, validation আর test আমি দেব, group split করা। Epoch বাছাই শুধু validation-এ। ৪. কত parameter শিখছে আর peak GPU allocation ছাপাও। ৫. একই ভাগে TF-IDF + logistic regression চালাও, আর fold-ভিত্তিক পার্থক্যে corrected resampled t দাও। ৬. এক batch overfit করার পরীক্ষা আগে রাখো। কোনো স্কোর আগাম লিখো না, কোনো প্রবন্ধ বানিয়ে লিখো না।

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

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

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

প্র ০১১৬ কোটির বেশি parameter-এর একটি model কেন দেড় MB-র TF-IDF-কে এই ডেটায় স্পষ্টভাবে হারাতে পারল না?

Wang আর Manning ২০১২ সালে “Baselines and Bigrams” প্রবন্ধে দেখান, sentiment-এ সরল bigram-ভিত্তিক model অনেক সময় খুব শক্ত প্রতিপক্ষ। কারণটি এই ডেটায়ও সম্ভবত খাটে: মন্তব্য ছোট, গড়ে ১৩ শব্দ, আর মত অনেক সময় “ফালতু” বা “অসাধারণ”-এর মতো কয়েকটি শব্দেই স্পষ্ট। অক্ষর n-gram বানানের ভুল আর বাংলা শব্দের রূপ ভালোই ধরে। ফলে BERT-এর আসল শক্তি, দূরের শব্দের সম্পর্ক ধরা, এখানে কম কাজে লাগে।

দ্বিতীয় সম্ভাবনা, একটি ছাদ। দুজনই ২৮৬টি সারিতে ভুল করেছে; পর্ব ৪২-এর ৬০টি ভুলের হাতে পড়া থেকে আমরা জানি এর একটি অংশ মিশ্র মত, নাটকের বাইরের কথা, বা সন্দেহজনক লেবেল। ওই সারিগুলোয় আরও ভালো model-ও পুরো নম্বর পায় না। মানুষের মধ্যে মিল (kappa) এই ডেটায় আমরা মাপিনি, তাই ছাদটি ঠিক কোথায়, জানা নেই।

তৃতীয়, শর্ত। আমরা একটি checkpoint একটি মাত্র সেটিংয়ে চালিয়েছি। Devlin ও সহকর্মীরা নিজেরাই বলেন, ১,০০,০০০-এর বেশি লেবেলের ডেটার চেয়ে ছোট ডেটায় hyperparameter বেশি গুরুত্বপূর্ণ। Bhattacharjee ও সহকর্মীরা ভিন্ন, তিন শ্রেণির SentNoB ডেটায় নিজেদের model-এর পক্ষে ফল দেখান; সেই তুলনা TF-IDF-এর সঙ্গে নয়, আর ডেটাও আমাদের নয়। অর্থাৎ এখানকার সমতা কোনো সাধারণ রায় নয়, আবার BERT-এর পক্ষে অন্য জায়গার ফলও এখানে খাটে না।

শিক্ষাটি উল্টো দিক থেকেও সত্য: শূন্য থেকে শেখা Transformer-এর ০.৯০৪ আর pretrained encoder-এর ০.৯৩৯-এর পার্থক্য পর্ব ৪২-এর সব neural পার্থক্যের চেয়ে বড়। Pretraining সত্যিই কাজ করেছে; শুধু baseline ইতিমধ্যে অনেক ওপরে ছিল।

মূল উপলব্ধি: বড় model-এর লাভ ধরা পড়ে শুধু তখনই, যখন কাজে সেই ক্ষমতার দরকার আছে আর baseline সেই জায়গা আগেই নিয়ে নেয়নি।

প্র ০২প্রবন্ধ [CLS] বলেছে, অনেকে mean pooling নেন, library আবার pooler নেয়। তাহলে সঠিক কোনটি?

২০১৮ সালের BERT প্রবন্ধ লেখে, sequence-এর প্রথম token [CLS]-এর শেষ hidden state classification-এর জন্য পুরো sequence-এর প্রতিনিধি। এটি একটি নকশার সিদ্ধান্ত, কোনো উপপাদ্য নয়। [CLS]-এর নিজের কোনো অর্থ নেই; pretraining-এ সে NSP-র মতো কাজের সঙ্গে জুড়ে থেকে শেখে। Hugging Face-এর BertModel আবার pooler_output দেয়, যা [CLS]-কে একটি linear layer আর tanh-এর ভেতর দিয়ে নেয়, আর sequence classification-এর head সাধারণত সেটিই নেয়। RoBERTa আর DistilBERT-এ pooler-এর গঠন ভিন্ন বা নেই। অর্থাৎ একই নামের “CLS” তিন জায়গায় ভিন্ন জিনিস হতে পারে।

অন্যদিকে sentence embedding-এর কাজে, যেমন Reimers আর Gurevych-এর ২০১৯ সালের Sentence-BERT ধারায়, mean pooling বহুল ব্যবহৃত। সেখানে লক্ষ্য একটি সাধারণ বাক্য-vector, একটি নির্দিষ্ট শ্রেণি নয়। আমাদের মাপে জমানো encoder-এ গড় সামান্য এগিয়ে (১৫ ভাগে ০.৮৮০ বনাম ০.৮৭৪), আর পুরো শেখালে কোনো পার্থক্য ধরা পড়েনি। Sun ও সহকর্মীরা BERT-এর classification-এ কোন layer থেকে vector নেওয়া ভালো, তা-ও পরীক্ষা করে শেষ layer-এর পক্ষে ফল পান তাঁদের ডেটায়।

ব্যবহারিক নিয়ম তাই “সঠিক” খোঁজা নয়, যাচাই করা। Mask ঠিক রাখুন, যেটি নেন তা লিখে রাখুন, পরিবর্তন করলে একই ভাগে মাপুন, আর অন্য কারও model-এর head-এর নিয়ম ধরে নেবেন না; card দেখুন, না থাকলে কোড পড়ুন। [CLS] বেছে নিয়ে padding-এর দুশ্চিন্তা কমে, কারণ এটি সবসময় অবস্থান ০-তে। Left padding করা batch-এ, যা decoder model-এ সাধারণ, “প্রথম token” আবার অন্য জিনিস হয়ে যায়।

মূল উপলব্ধি: Pooling একটি hyperparameter. একে নিয়ম নয়, পরীক্ষা হিসেবে দেখুন, আর ফাঁদটি mask-এ, নামে নয়।

প্র ০৩RoBERTa প্রবন্ধ বলে BERT যথেষ্ট শেখানো হয়নি। তাহলে “নতুন model” আর “একই model, ভালো training”-এর সীমা কোথায়?

২০১৯ সালে Liu ও সহকর্মীরা একটি অস্বস্তিকর প্রশ্ন তোলেন। BERT প্রকাশের পরের কয়েক মাসে নতুন নতুন কাঠামো আর নতুন objective আসছিল, প্রতিটিই আগেরটিকে হারানোর দাবি নিয়ে। তাঁরা BERT-এর কাঠামো প্রায় অপরিবর্তিত রেখে শুধু শেখানোর পদ্ধতি বদলান: প্রতিবার নতুন mask (dynamic masking), NSP বাদ, অনেক বড় batch, ১৬ GB থেকে ১৬০ GB ডেটা, আর দীর্ঘ training. প্রবন্ধের সারাংশে তাঁদের নিজের ভাষা, BERT “significantly undertrained” ছিল। এর মানে, পরের কিছু “কাঠামোর জয়” হয়তো আংশিকভাবে training-এর জয়ও ছিল। তাঁরা এই সাবধানতাও দেখান যে একই base কাঠামোর ফলই শুধু batch, data আর শেখার সময়ে অনেকটা নড়ে।

এই শিক্ষা শুধু গবেষণার নয়। এই পর্বের ফলের টেবিলে ঠিক একই জিনিস ছোট পরিসরে দেখা গেছে: একই checkpoint, একই ভাগ, শুধু কোনটি শিখছে আর tokenizer-এর একটি সেটিং বদলালে macro-F1 অনেকখানি নড়ে। ক্রিকেটের মতো ভাবুন: একই ব্যাটার ভালো কোচের কাছে রান বাড়ালে আমরা বলি না তিনি নতুন মানুষ, কিন্তু স্কোরকার্ডে তুলনা করলে কোচের প্রভাবকে ব্যাটারের প্রতিভা বলে ভুল করা সহজ।

তাই model-এর নাম কোনো একক দাবি নয়। সঠিক তুলনায় কাঠামো, pretraining objective, ডেটা, training বাজেট, tokenizer, আর fine-tuning-এর সব সেটিং আলাদা করে লিখতে হয়। ELECTRA প্রবন্ধও একটি তুলনা দেয় FLOP ধরে, শুধু step ধরে নয়, কারণ দুটি পদ্ধতিতে এক step-এর খরচ এক নয়। আর Dodge ও সহকর্মীদের ২০১৯ সালের কাজ (Show Your Work) বলে, কত কনফিগারেশন চেষ্টা হয়েছে না লিখলে একটি সেরা স্কোর দিয়ে বেশি কিছু বলা যায় না।

মূল উপলব্ধি: নামের তুলনা নয়, শর্তের তুলনা করুন। একটি বাড়তি শব্দ “RoBERTa” বা “BERT” নিজে কোনো ফলের ব্যাখ্যা নয়।

প্র ০৪Bangla BERT শেখা হয়েছে OSCAR আর Wikipedia-তে, BanglaBERT নিজে ১১০টি সাইট থেকে। Pretraining-এর ডেটা নিয়ে এত ভাবার দরকার কী?

Pretrained encoder তার pretraining-এর ডেটা থেকে যা শিখেছে, আপনার fine-tuning সেটি মুছে ফেলে না। Bangla BERT-এর card বলে তা Bengali Common Crawl (OSCAR-এর মাধ্যমে) আর Wikipedia থেকে শেখা। BanglaBERT প্রবন্ধের লেখকরা একটি ভিন্ন পথ নেন, আর কারণটি লক্ষণীয়: তাঁরা লেখেন, OSCAR আর CCNet-এর মতো উৎসে অনেক আপত্তিকর লেখা ছিল, যা পুরোপুরি পরিষ্কার করা তাঁদের পক্ষে সম্ভব মনে হয়নি। তাই তাঁরা ১১০টি জনপ্রিয় বাংলা সাইট থেকে নিজেরা সংগ্রহ করে ২৭.৫ GB-র Bangla2B+ তৈরি করেন।

এই দুই সিদ্ধান্তের ফল আমরা এখানে মাপিনি, আর প্রবন্ধের নিজের benchmark আমাদের ডেটার সঙ্গে সরাসরি তুলনীয় নয়। কিন্তু প্রশ্নগুলো ব্যবহারিক। এক, ভাষার ধরন: নাটকের মন্তব্যে যে কথ্য রূপ, আঞ্চলিক বানান আর ইংরেজি মেশানো শব্দ আছে, Wikipedia-র লেখায় তা কম। দুই, সামাজিক পক্ষপাত: ধর্ম, লিঙ্গ বা অঞ্চল নিয়ে পক্ষপাতদুষ্ট লেখা বেশি থাকলে vector-এও তার ছাপ পড়তে পারে; পরে দায়িত্বশীল AI-এর মডিউলে এটি বিস্তারিত দেখা যাবে। তিন, অনুমতি: বিপুল crawl-এ থাকা লেখার মালিকানা আর ব্যক্তিগত তথ্যের প্রশ্ন আলাদা। XLM-R-এর ক্ষেত্রেও প্রবন্ধ জানায় ডেটা filter করা CommonCrawl, ২ TB-র বেশি।

লাইসেন্সও এক নয়। ২০২৬ সালের নভেম্বরে Bangla BERT-এর card-এ MIT; BanglaBERT-এর repository-র README বলে বিষয়বস্তু অ-বাণিজ্যিক গবেষণার জন্য সীমিত, CC BY-NC-SA 4.0, আর Hub-এ কোনো লাইসেন্স tag দেখা যায়নি। এই কারণে এই পর্বে BanglaBERT চালানো হয়নি। বিজ্ঞাপনে চলা একটি শিক্ষামূলক সাইট বাণিজ্যিক কি না, তা আইনের প্রশ্ন, যা আমরা সিদ্ধান্ত নিতে পারি না; তাই সাবধানের পথ নিয়েছি।

মূল উপলব্ধি: Model বাছা মানে তার ডেটা, লাইসেন্স আর পক্ষপাতও সঙ্গে নেওয়া। শুধু একটি স্কোর দেখে সেই দায় এড়ানো যায় না।

প্র ০৫একটি ৬৫৮ MB model চালানোর আসল খরচ কী কী? শুধু অনুমানের সময় দেখলে কী বাদ পড়ে?

আমাদের মাপে CPU-তে হাজার সারিতে ৭.৬ সেকেন্ড; এটি দেখে অনেকে বলবেন, এটুকু তো তুচ্ছ। কিন্তু অনুমানের সময় হিসাবের একটি লাইন মাত্র। সার্ভারে ৬৫৮ MB-র weight সবসময় RAM-এ রাখতে হয়। কনটেইনার মোটা হয়, চালু হতে সময় লাগে, ঠিক সেই সময়টাতে খরচ বাড়ে। ফোনে বা ছোট VPS-এ চালাতে হলে এটি বড় প্রশ্ন। TF-IDF-এর দেড় MB এসব ক্ষেত্রে প্রায় অদৃশ্য।

দ্বিতীয় খরচ বদলের। Fine-tuning-এ GPU, lr-এর যত্ন, কয়েকটি seed লাগে; নতুন ডেটা এলে আবার। লগ পড়াও ভিন্ন: TF-IDF-এর weight খুলে দেখা যায় কোন শব্দে সিদ্ধান্ত ঘুরল, যেখানে encoder-এর জন্য মেশিন লার্নিং কোর্সের পর্ব ৫৭-এর ধরনের ব্যাখ্যার পদ্ধতি লাগে, আর সেগুলোও অনুমান মাত্র। DistilBERT-এর প্রবন্ধ এই খরচ কমানোর একটি সরাসরি চেষ্টা; পরে পর্ব ৫৯ (ছোট আর দ্রুত model — quantization, distillation)-এ এই পথ আরও আসবে। Quantization, ONNX আর অনুমানে batch করা আরও উপায়, যা এখানে মাপা হয়নি।

আবার উল্টো খরচও আছে। TF-IDF-এর vocabulary ডেটার সঙ্গে পুরোনো হয়, নতুন বানান ধরতে পারে না; pretrained encoder হয়তো কিছু নতুন শব্দ আগে থেকেই চেনে। লেবেল নতুন শ্রেণিতে গেলে দুটিকেই আবার শেখাতে হয়। ডিস্ক আর বিদ্যুতের খরচের প্রশ্নও আছে, যদিও এখানে আমরা তা মাপিনি, আর কোনো সংখ্যা অনুমান করে লিখছি না। বাজারঘরের একটি ছোট টিমের জন্য প্রশ্নটি তাই এমন: বাড়তি একটি জটিল অংশ রক্ষণাবেক্ষণের দাম কি এই কাজে মাপা লাভের চেয়ে কম? এই ডেটায় মাপা লাভ শূন্যের কাছে।

মূল উপলব্ধি: Model-এর খরচ মানে সব মিলিয়ে চালানো, বদলানো, বোঝা আর ঠিক করার খরচ। একটি F1 বা একটি ms এই হিসাব শেষ করে না।

অনুশীলন

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

  1. পূর্ণ numerical: একটি ছোট BERT: V = 30000, d = 512, L = 6, FFN = 2048, P = 512, S = 2, pooler আছে। (ক) Embedding অংশ। (খ) এক layer. (গ) মোট। (ঘ) ALBERT-ধাঁচে E = 128 নিলে token table কত হয়?

    (ক) token ১,৫৩,৬০,০০০ + position ২,৬২,১৪৪ + segment ১,০২৪ + LayerNorm ১,০২৪ = ১,৫৬,২৪,১৯২. (খ) Attention: ৪ × (৫১২² + ৫১২) + ২ × ৫১২ = ১০,৫১,৬৪৮. FFN: (৫১২ × ২০৪৮ + ২০৪৮) + (২০৪৮ × ৫১২ + ৫১২) + ১,০২৪ = ২১,০০,৭৩৬. মোট ৩১,৫২,৩৮৪. (গ) ১,৫৬,২৪,১৯২ + ৬ × ৩১,৫২,৩৮৪ + pooler ২,৬২,৬৫৬ = ৩,৪৮,০১,১৫২. (ঘ) ৩০০০০ × ১২৮ = ৩৮,৪০,০০০, আর প্রজেকশন ১২৮ × ৫১২ = ৬৫,৫৩৬; মোট প্রায় ৩৯ লাখ, মূলের প্রায় চার ভাগের এক ভাগ।

  2. নকশা ও সমালোচনা: একজন সহকর্মী লিখেছেন: “AutoTokenizer ডিফল্টে Bangla BERT, h.mean(1) দিয়ে গড়, একটি ভাগে test স্কোর দেখে সেরা epoch রেখেছি, ০.৯৬. TF-IDF দেখার দরকার নেই। Model-এর ১১০M parameter, card-এ লেখা আছে। ফ্রিজ করেছি বলে dropout নিয়ে ভাবিনি।” কী কী বলবেন?

    (১) Tokenizer: token ছাপিয়ে দেখুন; এই checkpoint-এ do_lower_case=False লাগে, আমাদের মাপে এর দাম প্রায় ০.০১১ F1. (২) Mask: PAD বাদ দিয়ে গড়। (৩) Test দিয়ে epoch: validation-এ বাছুন, test একবার। (৪) একটি ভাগ: কয়েকটি ভাগ, সংশোধিত ব্যবধান। (৫) যমজ: group split করা হয়েছে কি? (৬) Baseline: একই ভাগে TF-IDF; এই পর্বে দুটি প্রায় সমান। (৭) Parameter: numel() দিয়ে গুনুন; card-এর “110M” এই checkpoint-এ ১৬.৪ কোটি। (৮) Dropout: model.train() করলে জমানো encoder-এও dropout চলে; head-only-তে enc.eval() রাখুন, আর কতটা parameter শিখছে গুনুন।

  3. আউটপুট অনুমান করুন: কোড না চালিয়ে তিনটি লাইন লিখুন।
    h = np.array([[[2., 4], [4, 0], [6, 2], [7, 7]]])
    m = np.array([[1, 1, 1, 0]])[..., None]
    print(h[:, 0])
    print((h * m).sum(1) / m.sum(1))
    print(h.mean(1))

    প্রথম লাইন [[2. 4.]]: শুধু প্রথম token. দ্বিতীয়: আসল তিন token-এর গড়, (২+৪+৬)/৩ = ৪ আর (৪+০+২)/৩ = ২, তাই [[4. 2.]]. তৃতীয়: PAD-সহ চারটির গড় [[4.75 3.25]]. দুটি সংখ্যার তফাত সবটাই শেষ সারির ৭, ৭-এর জন্য।

  4. একটি সিদ্ধান্তের হিসাব: ১৫ ভাগে পার্থক্য d̄ = −০.০০১৬, s = ০.০০৬৩, ntest/ntrain = ০.২৮৫, ১৪ df-এর দুই-দিকের ৫% সীমা ২.১৪৫. (ক) সংশোধিত SE আর ৯৫% ব্যবধান। (খ) কী বলা যায়? (গ) একই d̄ আর s কিন্তু naive SE নিলে ব্যবধান কত হতো?

    (ক) SE = ০.০০৬৩ × √(১/১৫ + ০.২৮৫) = ০.০০৬৩ × ০.৫৯৩ = ০.০০৩৭৪. অর্ধ-প্রস্থ = ২.১৪৫ × ০.০০৩৭৪ = ০.০০৮০; ব্যবধান প্রায় [−০.০০৯৬, +০.০০৬৪] (run-এ, পুরো নির্ভুলতায়, −০.০০৯৫). (খ) শূন্য ভেতরে; কোনো পক্ষের জয় দাবি করা যায় না, “সমান” প্রমাণও হয় না। (গ) Naive SE = ০.০০৬৩/√১৫ = ০.০০১৬৩, অর্ধ-প্রস্থ ০.০০৩৫; ব্যবধান প্রায় [−০.০০৫১, +০.০০১৯]. Naive ব্যবধান প্রায় ২.৩ গুণ সরু, অর্থাৎ অতি-আত্মবিশ্বাসী।

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

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

ক্রমএকটি pretrained encoder দিয়ে সৎ তুলনার ধাপগুলো সাজান (▲▼ চেপে সরান)

  1. Model card পড়া: লাইসেন্স, ডেটা, tokenizer-এর সেটিং
  2. নিজের ভাষার বাক্যে token ছাপিয়ে দেখা
  3. Baseline-এর একই ভাগ নেওয়া বা পুনর্গঠন করে স্কোর মেলানো
  4. Head-only আগে, তারপর full fine-tuning; বাছাই validation-এ
  5. Test-এ macro-F1, fold-ভিত্তিক paired সংশোধিত ব্যবধান
  6. সময়, memory আর আকার ধরে চালানোর সিদ্ধান্ত

BERT-ধাঁচের একটি model-এ vocabulary V = 30,000 আর d = 768. শুধু token embedding table-এ কতটি parameter?

  • 15,360,000
  • 23,040,000
  • 109,482,240
  • 7,087,872

d = 768 স্থির রেখে BERT layer-এ head সংখ্যা ১২ থেকে ৮ করলে Q, K, V-এর মোট weight কত বদলায়?

  • প্রায় এক-তৃতীয়াংশ কমে
  • প্রায় ৫০% বাড়ে
  • বদলায় না
  • FFN-এর দিকে সরে যায়

Sequence: [CLS] a b [SEP] c [SEP]. BERT-এর সাধারণ নিয়মে token_type_ids কী?

  • 0 0 0 0 1 1
  • 0 0 0 1 1 1
  • 2 0 0 0 1 1
  • 1 1 1 1 1 1

একটি pipeline output-এ h.mean(1) দিয়ে গড় নেয়, attention mask ছাড়া। বিপদ কোনটি?

  • Shape ভুল হয়ে error আসে
  • [CLS] বাদ পড়ে যায়
  • কিছুই হয় না, কারণ PAD-এর vector শূন্য
  • PAD গড়ে ঢোকে, একই লেখার ফল batch-ভেদে বদলায়, কোনো error ছাড়াই

Layer-wise decay: head-এর lr = 1e-4, ξ = 0.5, আর head-এর ঠিক নিচের layer-এর k = 1. ওপর থেকে তৃতীয় layer-এর lr কত?

  • 2.5e-5
  • 1.25e-5
  • 8e-4
  • 1e-4

একই ১৫ ভাগে দুই model-এর macro-F1 পার্থক্যের গড় −০.০০১৬, সংশোধিত ৯৫% ব্যবধান [−০.০০৯৫, +০.০০৬৪], আর দুটির মধ্যে একটি প্রায় ১১৫ গুণ ধীর ও প্রায় ৪৪০ গুণ বড়। সবচেয়ে যুক্তিসঙ্গত সিদ্ধান্ত কোনটি?

  • ছোট model স্পষ্টভাবে জিতেছে, প্রমাণিত
  • দুটি সম্পূর্ণ সমান, প্রমাণিত
  • পার্থক্য ধরা পড়েনি, তাই এই ডেটায় সস্তাটি নেওয়া, অন্য ডেটায় আবার মাপা
  • বড় model জেতা পর্যন্ত test দেখে সেটিং বদলানো

শব্দকোষ

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

মনে রাখুন

  1. Input: [CLS] A [SEP] B [SEP]; token + segment + position যোগ, তারপর LayerNorm. Mask ছাড়া PAD-ও attention পায়।
  2. গোনা: Embedding(n, d) = nd, Linear(a, b) = ab + b, LayerNorm = 2d. BERT base ১০,৯৪,৮২,২৪০; Bangla BERT ১৬,৪৩,৯৬,৫৪৪, যার প্রায় ৪৮% embedding-এ।
  3. পরিবার: RoBERTa শেখানোর পদ্ধতি, DistilBERT ও ALBERT আকার, ELECTRA লক্ষ্য, XLM-R ভাষা, BanglaBERT বাংলা ডেটা বদলায়। লাইসেন্স আলাদা।
  4. Fine-tuning: এই ডেটায় ৫ ভাগে head-only ০.৮৭৬, শেষ দুই layer ০.৯০০, পুরোটা ০.৯৪০; LLRD আর mean-এ লাভ ধরা পড়েনি। Tokenizer ফাঁদের দাম ০.০১১.
  5. উত্তর: একই ১৫ ভাগে BERT ০.৯৩৯, TF-IDF ০.৯৪১, ব্যবধান [−০.০০৯৫, +০.০০৬৪]. দাম বেশি, তাই এখানে baseline থাকে। ভুল আলাদা বলে সরল গড় ০.৯৪৭, অনুসন্ধানী ফল।

পরের পর্বে: পর্ব ৪৯ (GPT আর decoder-only model — next-token prediction): encoder ছেড়ে এবার যে model পরের token অনুমান করে, তার কাঠামো আর parameter.

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

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