Reproducibility — আবার চালালে কি একই ফল পাওয়া যায়?
Reproducibility
একটা documented সংকট — psychology-তে শুরু, এখন ML/CS পর্যন্ত পৌঁছেছে: প্রকাশিত ফলাফলের একটা বড় অংশ আবার চালালে একই উত্তর দেয় না। Reproducibility বনাম replicability-র পার্থক্য, আর নিজের কাজকে reproducible রাখার একটা বাস্তব checklist।
আগে এটা বুঝি
২০১৫ সালে Open Science Collaboration একটা বড় প্রকল্প চালায় — মনোবিজ্ঞানের ১০০টা প্রকাশিত, peer-reviewed গবেষণার ফলাফল স্বাধীনভাবে পুনরুৎপাদন (replicate) করার চেষ্টা। ফলাফল: মাত্র ৩৬-৩৯% মূল ফলাফলের মতো তাৎপর্যপূর্ণ effect আবার পাওয়া গেছে। বাকি ৬০%-এর বেশি — হয় effect সম্পূর্ণ উধাও, নয়তো আকারে অনেক ছোট।
এই একটা সংখ্যাই “replication crisis” নামে পরিচিত একটা ব্যাপক
সংকটের সবচেয়ে উদ্ধৃত প্রমাণ। আর এটা শুধু মনোবিজ্ঞানের সমস্যা না —
cs-theory/ai-ml-foundations-from-first-principles লেসনে আমরা দেখেছি
ML-এর তত্ত্ব এখনো অসম্পূর্ণ; একই কারণে ML গবেষণায়ও reproducibility
একটা সক্রিয়, স্বীকৃত সমস্যা — অনেক বিখ্যাত paper-এর ফলাফল স্বাধীনভাবে
পুনরুৎপাদন করতে গিয়ে গবেষকরা দেখেছেন missing hyperparameter, অনির্দিষ্ট
random seed, অথবা undocumented data preprocessing ধাপের কারণে তা
সম্ভব হয়নি।
আগের লেসনে আমরা শিখেছি কীভাবে একটা claim পরিসংখ্যানগতভাবে প্রতিরক্ষাযোগ্য করতে হয়। কিন্তু একটা পরিসংখ্যানগতভাবে নিখুঁত ফলাফলও অর্থহীন যদি কেউ সেটা আবার চালিয়ে যাচাই করতে না পারে। এই লেসন সেই শেষ, কিন্তু প্রায়ই সবচেয়ে অবহেলিত, ধাপ নিয়ে।
মূল ধারণা
Reproducibility বনাম Replicability — দুইটা শব্দ যা প্রায়ই গুলিয়ে ফেলা হয়
এই দুইটা শব্দ দৈনন্দিন ব্যবহারে প্রায় সমার্থক মনে হয়, কিন্তু গবেষণা-পদ্ধতিতে তাদের আলাদা, নির্দিষ্ট অর্থ আছে — আর এই পার্থক্যটা স্পষ্ট না বুঝলে একটা বিপজ্জনক ভুল আত্মবিশ্বাস তৈরি হতে পারে।
| Reproducibility | Replicability | |
|---|---|---|
| প্রশ্ন | একই code, একই data চালালে একই সংখ্যাগত ফলাফল আসে? | স্বাধীন code, স্বাধীন data, একই research question-এ একই উপসংহার আসে? |
| কী স্থির থাকে | Code ও data — সম্পূর্ণ অভিন্ন | শুধু প্রশ্ন/hypothesis — বাকি সব নতুন করে বানানো |
| কী প্রমাণ করে | Computational correctness — কোনো hidden randomness/bug নেই | Scientific validity — ফলাফলটা একটা বাস্তব, সাধারণীকরণযোগ্য ঘটনা, শুধু একটা নির্দিষ্ট setup-এর কাকতালীয়তা না |
| কে করে | সাধারণত মূল লেখক নিজেই (বা একই দল) | সম্পূর্ণ স্বাধীন একটা দল, প্রায়ই ভিন্ন প্রতিষ্ঠানে |
| উদাহরণ | একই training script, একই seed, একই GPU দিয়ে আবার train করে একই accuracy পাওয়া | ভিন্ন dataset, ভিন্ন implementation দিয়ে একই ধরনের architecture-এর সুবিধা আবার দেখানো |
কেন এই পার্থক্যটা গুরুত্বপূর্ণ — একটা concrete দৃশ্য: ধরুন একটা
paper একটা নতুন algorithm-এর ফলাফল প্রকাশ করেছে, আর তার সাথে সম্পূর্ণ
code, data, আর একটা Docker container দিয়েছে। আপনি সেটা চালিয়ে
হুবহু একই সংখ্যা পেলেন — এটা reproducible। কিন্তু এর মানে
এই না যে ফলাফলটা সাধারণীকরণযোগ্য — হয়তো একটা নির্দিষ্ট random
seed দুর্ঘটনাক্রমে ভালো ফল দিয়েছিল (cs-theory/hypothesis-formation-and-experiment-design
লেসনের p-hacking-এর কথা মনে করুন — একাধিক seed চেষ্টা করে সবচেয়ে
ভালোটা রিপোর্ট করাও একটা p-hacking-এরই রূপ)। শুধু replicability —
একটা স্বাধীন দল, ভিন্ন seed, হয়তো ভিন্ন dataset-এও একই ধরনের সুবিধা
দেখাতে পারা — প্রকৃত বৈজ্ঞানিক নিশ্চয়তা দেয়।
ভেতরে কী ঘটছে
কম্পিউটেশনাল গবেষণাকে reproducible করার নির্দিষ্ট উপাদান
১. Pinned dependencies — সংস্করণ নির্দিষ্ট করা। pip install numpy লেখা যথেষ্ট না — numpy==1.26.4 লিখতে হবে, একটা lockfile-এ
(requirements.txt with pinned versions, poetry.lock, conda-lock.yml)।
Library-র নতুন সংস্করণ numerical ফলাফল সামান্য বদলে দিতে পারে —
default hyperparameter বদলে যাওয়া, internal algorithm-এর পরিবর্তন
(যেমন একটা sorting algorithm-এর ভিন্ন tie-breaking), বা bug fix
নিজেই ফলাফল বদলে দিতে পারে।
২. Fixed random seed — সবগুলো উৎসে। এটা শুনতে সহজ, কিন্তু practice-এ একাধিক জায়গায় seed set করতে হয়:
import random, numpy as np, torch
SEED = 42
random.seed(SEED) # Python-এর নিজস্ব random module
np.random.seed(SEED) # NumPy-র নিজস্ব generator
torch.manual_seed(SEED) # PyTorch CPU
torch.cuda.manual_seed_all(SEED) # PyTorch GPU — আলাদা generator!প্রতিটা library-র নিজস্ব, স্বাধীন random number generator থাকে — একটা seed set করলে বাকিগুলো বদলায় না। একটা মিস হওয়া seed মানে সেই একটা উৎস থেকে non-determinism ফাঁক গলে ঢুকে যাবে।
৩. Environment capture — শুধু library না, পুরো পরিবেশ। OS
version, Python version, GPU driver version, CUDA/cuDNN version —
এই সবকিছুই ফলাফল প্রভাবিত করতে পারে (বিশেষত GPU-নির্ভর numerical
কাজে)। Docker container বা conda environment export (environment.yml)
এই পুরো পরিবেশ একসাথে “জমাট” (freeze) করে রাখার সবচেয়ে নির্ভরযোগ্য
উপায়।
৪. Released code + data। Code ছাড়া শুধু method বর্ণনা যথেষ্ট না — বাস্তব implementation-এ প্রায়ই এমন ছোট সিদ্ধান্ত থাকে (numerical stability-র জন্য একটা epsilon যোগ করা, একটা edge case handle করা) যা paper-এর টেক্সটে উল্লেখই থাকে না। Data-ও version করে রাখা দরকার — শুধু “আমরা ImageNet ব্যবহার করেছি” যথেষ্ট না, কারণ dataset নিজেই সময়ের সাথে সংস্করণ বদলাতে পারে (curation, filtering পরিবর্তন)।
৫. Documented hardware। Floating-point গণনা hardware-নির্ভর হতে পারে (নিচে বিস্তারিত) — GPU model, CPU architecture, এমনকি কতটা parallel thread ব্যবহার হয়েছে তা লিখে রাখা উচিত, বিশেষত যদি timing/performance claim করা হয়।
কেন fixed seed দেওয়ার পরও ফলাফল বদলে যায়
এখানেই সবচেয়ে বেশি বিভ্রান্তি হয় — “আমি তো seed fix করেছি, তাও প্রতিবার সামান্য ভিন্ন সংখ্যা আসছে কেন?” তিনটা সাধারণ, সূক্ষ্ম উৎস:
GPU non-determinism। cuDNN-এর মতো library প্রায়ই একটা operation
(যেমন convolution) করার একাধিক সমতুল্য algorithm রাখে, আর runtime-এ
হার্ডওয়্যারের অবস্থা দেখে দ্রুততমটা বেছে নেয় — এই নির্বাচন
non-deterministic হতে পারে, আর ভিন্ন algorithm ভিন্ন floating-point
rounding দেয়। torch.backends.cudnn.deterministic = True সেট
করলে এটা বন্ধ হয়, কিন্তু প্রায়ই ধীরগতির বিনিময়ে।
Floating-point non-associativity। computer-representation
module-এ আমরা দেখেছি floating-point addition associative না —
(a + b) + c সবসময় a + (b + c)-এর সমান না, rounding error-এর
কারণে। একটা parallel reduction (যেমন GPU-তে একটা array-র sum করা)
থ্রেডগুলো কোন ক্রমে যোগ করবে তা প্রতিবার সামান্য ভিন্ন হতে পারে
(thread scheduling-এর কারণে) — ফলে চূড়ান্ত যোগফলও সামান্য ভিন্ন
হতে পারে, শেষ কয়েকটা bit-এ।
Hash randomization ও thread scheduling। Python-এর dict/set-এর
iteration order (কিছু সংস্করণে/context-এ) hash randomization-এর
কারণে run-প্রতি ভিন্ন হতে পারে; multi-threaded code-এ race condition
থাকলে ফলাফল থ্রেড schedule-এর উপর নির্ভরশীল হয়ে যায়।
উদাহরণ
দুইটা reproducibility ব্যর্থতার গল্প — বাস্তব প্যাটার্ন
গল্প ১ — অনুল্লেখিত hyperparameter। একটা paper দাবি করে একটা নতুন optimizer বিদ্যমান standard-এর (Adam) চেয়ে ভালো। Reproduce করতে গিয়ে একজন independent গবেষক দেখেন paper-এ learning rate schedule-এর একটা বিস্তারিত অংশ (warm-up steps সংখ্যা) উল্লেখ নেই। তিনি একটা “যুক্তিসঙ্গত” ডিফল্ট ধরে চালান — ফলাফল paper-এর দাবির ধারেকাছেও আসে না। পরে জানা যায় সেই নির্দিষ্ট hyperparameter-এর মান ফলাফলে বিশাল পার্থক্য তৈরি করে — কিন্তু এটা code release-এর একটা config ফাইলে ছিল, paper-এর টেক্সটে না।
গল্প ২ — data split-এর অস্পষ্টতা। একটা paper একটা নির্দিষ্ট accuracy দাবি করে একটা “standard 80/20 train/test split”-এ। কিন্তু “standard split” আসলে দুই ভিন্নভাবে করা যায় — random split (প্রতিবার সামান্য ভিন্ন), নাকি একটা নির্দিষ্ট, পূর্ব-নির্ধারিত split (যা community-তে প্রচলিত কিন্তু paper-এ explicit reference নেই)। এই দুইটা differently চ্যালেঞ্জিং, ফলে accuracy সংখ্যা directly তুলনাযোগ্য না।
এই দুইটা গল্পই একটা সাধারণ প্যাটার্ন দেখায় — সমস্যা প্রায় কখনো একটা বড়, স্পষ্ট ভুল না, সবসময় ছোট, “trivial মনে হওয়া” বিস্তারিত যা লেখার সময় গুরুত্বপূর্ণ মনে হয়নি। এটাই কারণ একটা systematic checklist ব্যক্তিগত মনে-রাখার চেয়ে অনেক বেশি নির্ভরযোগ্য।
নিজে চালিয়ে দেখুন
Fixed seed-এও non-determinism — নিজের চোখে দেখা
import random
def partial_seed_bug():
"""শুধু একটা উৎসে seed দেওয়ার সাধারণ ভুল।"""
random.seed(42) # Python-এর built-in random-এ seed দেওয়া হলো
results = []
for _ in range(3):
# কিন্তু ধরুন কোডের অন্য অংশ os.urandom বা একটা আলাদা
# generator instance ব্যবহার করছে যেখানে seed যায়নি:
independent_rng = random.Random() # নতুন, seed-বিহীন generator!
results.append(independent_rng.random())
return results
print("Run ১:", partial_seed_bug())
print("Run ২:", partial_seed_bug())
print("দুইটা ভিন্ন — যদিও 'seed দেওয়া হয়েছে' বলে মনে হচ্ছিল")
def full_seed_fixed(seed):
"""সঠিক উপায় — একটাই explicit generator instance পুরো কোড জুড়ে পাস করা।"""
rng = random.Random(seed)
return [rng.random() for _ in range(3)]
print("\nRun ১ (ঠিক করা):", full_seed_fixed(42))
print("Run ২ (ঠিক করা):", full_seed_fixed(42))
print("এখন হুবহু অভিন্ন — সব randomness একটাই, explicitly seeded generator থেকে")Illustrative output:
Run ১: [0.6394267984578837, 0.025010755222666936, 0.27502931836911926]
Run ২: [0.6394267984578837, 0.15601864044243652, 0.02053547631640304]
দুইটা ভিন্ন — যদিও 'seed দেওয়া হয়েছে' বলে মনে হচ্ছিল
Run ১ (ঠিক করা): [0.6394267984578837, 0.025010755222666936, 0.27502931836911926]
Run ২ (ঠিক করা): [0.6394267984578837, 0.025010755222666936, 0.27502931836911926]
এখন হুবহু অভিন্ন — সব randomness একটাই, explicitly seeded generator থেকেলক্ষ্য করুন — প্রথম function-এ random.seed(42) কল হয়েছে,
কিন্তু ভেতরে একটা নতুন random.Random() instance বানানো হয়েছে
যা সেই global seed থেকে independent — বাস্তব কোডে এটা ঘটে যখন
একটা library internally তার নিজস্ব generator instance রাখে, বা
multiprocessing-এ প্রতিটা worker process আলাদা initial state নিয়ে
শুরু হয়। শিক্ষা: পুরো codebase-এ ঠিক কয়টা random source আছে
তা explicitly ট্রেস করা reproducibility-র একটা অপরিহার্য audit।
একটা library-র seed fix করলেই যথেষ্ট না — একাধিক random-number উৎস থাকলে প্রতিটাতে আলাদাভাবে seed দিতে হয়, নাহলে reproducibility নিঃশব্দে ভেঙে যায়।
নিজে বানান
একটা Reproducibility Checklist — নিজের প্রজেক্টে প্রয়োগ
- নিজের একটা বিদ্যমান প্রজেক্ট/experiment বেছে নিন
- নিচের checklist-এর প্রতিটা আইটেম যাচাই করুন — কয়টা এখনই সন্তুষ্ট, কয়টা না
- একটা REPRODUCE.md ফাইল লিখুন যা একটা নতুন মেশিনে শূন্য থেকে ঠিক কী কমান্ড চালাতে হবে তা ধাপে ধাপে বলে
- সম্ভব হলে একজন সহকর্মী বা বন্ধুকে (বা একটা সম্পূর্ণ পরিষ্কার Docker container-এ নিজে) সেই ফাইল অনুসরণ করে চালাতে বলুন
# Reproducibility Checklist
## Environment
- [ ] সব dependency-র exact version pinned আছে (lockfile/requirements.txt)
- [ ] OS/Python/GPU-driver version লেখা আছে
- [ ] Container/environment export ফাইল আছে (Dockerfile / environment.yml)
## Randomness
- [ ] প্রতিটা random-number উৎসে explicit seed দেওয়া আছে (উপরের
experiment-এর মতো audit করে)
- [ ] GPU ব্যবহার হলে, deterministic mode-এর trade-off ডকুমেন্ট করা
আছে (গতি বনাম bit-perfect reproducibility)
## Data
- [ ] Dataset-এর exact version/checksum লেখা আছে
- [ ] Train/test split ঠিক কীভাবে তৈরি হয়েছে তা কোডে আছে (হাতে না,
script দিয়ে) — একটা ফাইল হিসেবে split-ও save করে রাখা ভালো
- [ ] Preprocessing-এর প্রতিটা ধাপ code-এ আছে, হাতে-করা কোনো ধাপ নেই
## Code
- [ ] সম্পূর্ণ training/experiment code প্রকাশিত/সংরক্ষিত
- [ ] একটা single entry-point command আছে যা পুরো experiment চালায়
- [ ] Hyperparameter সব একটা config ফাইলে, hardcode করা না, আর
সেই config ফাইলটাও version-এ আছে
## Hardware ও Reporting
- [ ] ব্যবহৃত hardware (CPU/GPU model, RAM) লেখা আছে যদি performance
claim থাকে
- [ ] Reproduced ফলাফল bit-perfect, নাকি শুধু statistically consistent
তা স্পষ্ট করে লেখা আছেনিজে বাড়ান:
- একটা সম্পূর্ণ পরিষ্কার virtual machine/container-এ (কোনো আগের
অবস্থা ছাড়া) আপনার
REPRODUCE.mdঅনুসরণ করে চালানোর চেষ্টা করুন — কোথায় প্রথম আটকে গেলেন? - আপনার প্রজেক্টের কতগুলো random-number উৎস আছে তা explicitly
গণনা করুন (
grep -r "random\|seed\|shuffle"দিয়ে শুরু করা যায়) - একটা সম্পূর্ণ container export (
docker save/conda-lock) বানিয়ে সেটার আকার আর build-time নোট করুন — reproducibility-র একটা বাস্তব খরচও আছে, সেটা measure করুন
বাস্তব সিস্টেমে
Reproducibility যেখানে প্রাতিষ্ঠানিকভাবে গুরুত্ব পায়
ACM Artifact Review and Badging। ACM conference-এ প্রকাশিত paper-এর সাথে এখন প্রায়ই badge দেওয়া হয় — Available (code/data প্রকাশিত), Functional (চালানো যায়), Reproduced (মূল লেখকদের claim পুনরুৎপাদন করা গেছে স্বতন্ত্র মূল্যায়নকারী দ্বারা), Replicated (স্বাধীন data/setup-এও একই উপসংহার)। এই লেসনের reproducibility/replicability পার্থক্যটা সরাসরি এই badge system-এর ভিত্তি।
NeurIPS/ICLR Reproducibility Challenge। প্রতি বছর ML conference-এ
student ও গবেষকরা সাম্প্রতিক প্রকাশিত paper-এর ফলাফল স্বাধীনভাবে
reproduce করার চেষ্টা করে, আর একটা রিপোর্ট প্রকাশ করে — কোথায় সফল
হয়েছে, কোথায় হয়নি, আর কেন। এটা cs-theory/how-to-read-academic-papers
লেসনের দফা-৩ পড়ার একটা প্রাতিষ্ঠানিক, systematic রূপ।
Docker/container সংস্কৃতি গবেষণায়। আজকাল অনেক systems/ML paper-এর সাথে একটা সম্পূর্ণ Docker image দেওয়া হয় যাতে পুরো environment (OS, library, এমনকি GPU driver-এর compatible সংস্করণ) জমাট থাকে — এই লেসনের “environment capture” নীতির সরাসরি বাস্তবায়ন।
Industry-তে feature store ও data versioning। Production ML system-এ (Uber-এর Michelangelo, Netflix-এর internal platform) “feature store” এর একটা বড় উদ্দেশ্যই হলো কোন model কোন exact data snapshot দিয়ে train হয়েছে তা ট্রেস করা যায় — এটাই এই লেসনের “data versioning” নীতির industrial-scale রূপ, model debugging আর audit-এর জন্য অপরিহার্য।
এই module-এরই “Paper Reproduction” প্রজেক্ট। এই curriculum-এর পরবর্তী প্রজেক্টে আপনি নিজেই একটা systems paper বেছে তার key experiment reproduce করবেন — এই লেসনের প্রতিটা checklist আইটেম সেই প্রজেক্টে সরাসরি প্রয়োগ হবে।
যে ভুলগুলো সবাই করে
“আমি নিজের কোড আবার চালিয়ে একই ফলাফল পাই, তাই আমার গবেষণা reproducible।”
এটা একটা প্রয়োজনীয় কিন্তু যথেষ্ট না শর্ত।
নিজের মেশিনে নিজের কোড আবার চালানো একটা দুর্বল পরীক্ষা — আপনার মেশিনের একই library version, একই hidden environment variable, একই cached data সবকিছুই এখনো একইভাবে আছে। এটা প্রমাণ করে না যে অন্য কেউ, অন্য একটা মেশিনে, শুধু আপনার প্রকাশিত code/data/instructions অনুসরণ করে একই ফলাফল পাবেন।
প্রকৃত reproducibility test একটা স্বতন্ত্র পরিবেশে — আদর্শভাবে একটা সম্পূর্ণ পরিষ্কার container বা অন্য কারো মেশিনে — করা উচিত। এই লেসনের BuildIt অংশের পরামর্শ (“একটা পরিষ্কার container-এ চালানোর চেষ্টা করুন”) ঠিক এই কারণেই গুরুত্বপূর্ণ।
“Reproducibility আর correctness একই জিনিস — যদি ফলাফল reproducible হয়, তাহলে সেটা সঠিকও।”
সম্পূর্ণ আলাদা দুইটা গুণ। একটা কোড reproducibly ভুল হতে পারে — একটা bug (যেমন একটা off-by-one error, বা train/test data-র মধ্যে leak) প্রতিবার একই ভুল সংখ্যা দিতে পারে, বিটে-বিটে অভিন্ন, প্রতিটা রান-এ।
Reproducibility নিশ্চিত করে measurement প্রক্রিয়াটা স্থিতিশীল — এটা correctness সম্পর্কে কিছু বলে না। Correctness যাচাই করতে হয় ভিন্নভাবে — code review, unit test, sanity check (যেমন এমন একটা সরল ক্ষেত্রে চালানো যার সঠিক উত্তর আগে থেকে জানা), আর সবচেয়ে শক্তিশালীভাবে, replicability — একটা স্বাধীন, ভিন্ন implementation একই উপসংহারে পৌঁছায় কি না।
এই কারণেই reproducibility ও replicability-র পার্থক্যটা (এই লেসনের কেন্দ্রীয় বিষয়) শুধু pedantic শব্দচয়ন না — একটা reproducibly-ভুল ফলাফল আর একটা replicated-সঠিক ফলাফলের মধ্যে বৈজ্ঞানিক মূল্যে বিশাল পার্থক্য।
বুঝেছেন কি না দেখুন
1“আমাদের ফলাফল reproducible” আর “আমাদের ফলাফল replicated হয়েছে” —
এই দুই বাক্যের মধ্যে ঠিক কী পার্থক্য?
স্মরণ
“Reproducible” মানে — একই code, একই data দিয়ে (সাধারণত মূল লেখকরাই, বা যে কেউ প্রকাশিত জিনিস চালিয়ে) একই সংখ্যাগত ফলাফল পাওয়া যায়। এটা computational সততার প্রমাণ — কোনো hidden bug, undocumented randomness, বা ভুল রিপোর্টিং নেই।
“Replicated” মানে — একটা স্বাধীন দল, ভিন্ন code implementation এবং/অথবা ভিন্ন data দিয়ে, একই গবেষণা প্রশ্নে একই উপসংহারে পৌঁছেছে। এটা বৈজ্ঞানিক বৈধতার (scientific validity) অনেক শক্তিশালী প্রমাণ — এটা দেখায় ফলাফলটা একটা নির্দিষ্ট setup-এর কাকতালীয়তা না, একটা বাস্তব, সাধারণীকরণযোগ্য ঘটনা।
একটা ফলাফল reproducible হয়েও replicated না হতে পারে (অন্য কেউ এখনো স্বাধীনভাবে চেষ্টা করেনি, বা চেষ্টা করে ভিন্ন উপসংহারে পৌঁছেছে) — এই দুইটা দাবি স্বাধীন, একটা অন্যটাকে গ্যারান্টি দেয় না।
2একটা GPU-তে training করা deep learning experiment torch.manual_seed(42)
সেট করা সত্ত্বেও প্রতিবার সামান্য ভিন্ন final accuracy দেয় (যেমন
91.2% বনাম 91.4%)। এটা কি একটা bug? কী করা উচিত?
যুক্তি
torch.manual_seed(42)
সেট করা সত্ত্বেও প্রতিবার সামান্য ভিন্ন final accuracy দেয় (যেমন
91.2% বনাম 91.4%)। এটা কি একটা bug? কী করা উচিত?এটা সাধারণত একটা bug না — এটা GPU non-determinism-এর একটা পরিচিত, নথিভুক্ত ফলাফল। cuDNN-এর মতো library কিছু operation-এর জন্য একাধিক সমতুল্য algorithm রাখে আর runtime-এ বেছে নেয়, যা ভিন্ন floating-point rounding দিতে পারে; parallel reduction-এর floating-point non-associativity-ও একটা উৎস।
করণীয়:
১. যদি সত্যিকার bit-perfect reproducibility দরকার হয় (debugging-এর
জন্য, বা একটা খুব কড়া reproducibility দাবি করতে চাইলে),
torch.backends.cudnn.deterministic = True আর
torch.use_deterministic_algorithms(True) সেট করুন — এর বিনিময়ে
কিছুটা ধীর গতি মেনে নিতে হবে।
২. যদি সেটা সম্ভব না হয়, বা প্রয়োজন না হয়, স্বীকার করুন এবং
রিপোর্ট করুন: “ফলাফল bit-perfect reproducible না, GPU non-determinism-এর
কারণে, কিন্তু একাধিক seed-জুড়ে accuracy 91.0-91.5%-এর মধ্যে
স্থিতিশীল (bootstrap CI: […])”। এটা cs-theory/benchmarking-and-statistical-analysis
লেসনের পদ্ধতির সরাসরি প্রয়োগ — নির্দিষ্ট সংখ্যার বদলে একটা distribution
রিপোর্ট করা।
যা করা উচিত না: একাধিক seed চেষ্টা করে সবচেয়ে ভালোটা বেছে রিপোর্ট করা — এটা p-hacking-এর একটা রূপ, আর ঠিক সেই কারণেই আসল variance লুকিয়ে ফেলে যা একজন পাঠকের জানা দরকার।
3আপনি একটা thesis/project জমা দেওয়ার আগে সিদ্ধান্ত নিয়েছেন এটা
সম্পূর্ণ reproducible করবেন। তিনটা সবচেয়ে গুরুত্বপূর্ণ, প্রথম
পদক্ষেপ কী নেবেন, আর কেন সেই ক্রমে?
ডিজাইন
একটা যুক্তিসঙ্গত অগ্রাধিকার-ক্রম:
১. Dependency pin করা এবং environment capture (সবার আগে)। এটা
সবচেয়ে সস্তা (একটা কমান্ড, pip freeze > requirements.txt বা
conda env export) আর সবচেয়ে বেশি ব্যর্থতা প্রতিরোধ করে — একটা
library-র নতুন সংস্করণ পুরো experiment ভেঙে দিতে পারে মাসের পর
মাস পরে।
২. সব random-number উৎসের audit ও explicit seeding। এই লেসনের experiment অংশের মতো, codebase-এ যত জায়গায় randomness ব্যবহৃত হয় তা খুঁজে বের করে একটা centralized, explicit seeding নিশ্চিত করা — এটা তুলনামূলক সস্তা কিন্তু সহজে ভুলে যাওয়া যায়, তাই আগেভাগে করা ভালো।
৩. একটা single entry-point script + REPRODUCE.md লেখা। সবশেষে, পুরো pipeline-কে একটা কমান্ডে (বা একটা স্পষ্ট, ধাপে-ধাপে documented sequence-এ) নামিয়ে আনা, আর সেটা একটা পরিষ্কার environment-এ নিজে পরীক্ষা করে দেখা।
কেন এই ক্রম: প্রথম দুইটা “নীরব ব্যর্থতা” প্রতিরোধ করে — এমন সমস্যা যা কোনো error message ছাড়াই ভুল সংখ্যা দিয়ে দেয়, ধরাই কঠিন। তৃতীয়টা “usability” সমস্যা সমাধান করে — অন্য কেউ (বা ভবিষ্যতের আপনি) কীভাবে আসলে চালাবেন তা স্পষ্ট করে। Silent failure প্রথমে ঠেকানো বেশি জরুরি, কারণ সেটাই সবচেয়ে বেশি বিশ্বাসযোগ্যতা নষ্ট করে যদি পরে ধরা পড়ে।
এরপর কী
আমরা এখন জানি কীভাবে একটা প্রশ্নকে falsifiable বানাতে হয় (২০তম লেসন), কীভাবে সেটা পরিসংখ্যানগতভাবে কঠোরভাবে পরীক্ষা করতে হয় (২১তম লেসন), আর কীভাবে সেই পুরো প্রক্রিয়া অন্যরা যাচাই করতে পারে এমনভাবে ডকুমেন্ট করতে হয় (এই লেসন)। এই তিনটা দক্ষতা মিলেই একটা সম্পূর্ণ, প্রতিরক্ষাযোগ্য গবেষণা ফলাফল তৈরি করে।
কিন্তু একটা শেষ ধাপ বাকি — এই সবকিছু অন্য মানুষকে বোঝানো। এই curriculum-এর শেষ লেসনে আমরা দেখব কীভাবে একটা ভালো technical report/paper লিখতে হয় (ঠিক যে কাঠামো আমরা ১৯তম লেসনে পাঠক হিসেবে শিখেছিলাম, এবার লেখকের দৃষ্টিতে), আর peer review আসলে কী যাচাই করে — এবং কেন সেটা নিখুঁত না।
আরও পড়ুন
- Estimating the reproducibility of psychological science — Open Science Collaboration · Science, 2015 — ১০০টা প্রকাশিত ফলাফলের মাত্র ~৩৬-৩৯% পুনরুৎপাদনযোগ্য প্রমাণিত হয়, replication crisis-এর সবচেয়ে উদ্ধৃত প্রমাণ
- Troubling Trends in Machine Learning Scholarship — Zachary C. Lipton, Jacob Steinhardt · ICML 2018 Debates — ML paper-এ missing hyperparameter, ambiguous baseline, আর overclaiming নিয়ে সরাসরি সমালোচনা
- ACM Artifact Review and Badging · Association for Computing Machinery-র আনুষ্ঠানিক badge system — Available, Functional, Reproduced, Replicated