Chapter 23 of 28
The questions that come up again and again
নিচে দেওয়া প্রশ্নগুলো হলো মেশিন লার্নিংয়ের ইন্টারভিউতে CNN নিয়ে বারবার ঘুরেফিরে আসা কিছু জনপ্রিয় প্রশ্ন — যা ধারণাভিত্তিক প্রশ্ন (conceptual), গাণিতিক ও প্রযুক্তিগত প্রশ্ন (technical) এবং কোডিং প্রশ্ন (coding) এই তিন ভাগে সাজানো হয়েছে। প্রতিটি উত্তরকে এমনভাবে উপস্থাপন করা হয়েছে যা আপনি ইন্টারভিউয়ারের সামনে মুখ ফুটে গুছিয়ে বলতে পারেন: একটি স্পষ্ট সংজ্ঞা, এটি কেন গুরুত্বপূর্ণ এবং একটি করে সহজ বাস্তব উদাহরণ।
ধারণাভিত্তিক প্রশ্নের ক্ষেত্রে আপনার উত্তরটিকে সংজ্ঞা → কেন এটি গুরুত্বপূর্ণ → বাস্তব উদাহরণ এই কাঠামোতে সাজান। আর গাণিতিক বা প্রযুক্তিগত প্রশ্নের ক্ষেত্রে প্রথমে সূত্রটি লিখুন এবং তারপর ধাপে ধাপে সংখ্যাগুলো বসিয়ে হিসাব করুন — ইন্টারভিউয়াররা সাধারণত আপনার শেষ ফলের চেয়ে হিসাবের প্রক্রিয়াটিকে বেশি মূল্যায়ন করেন।
CNN হলো কনভোলিউশন অপারেশনের ওপর ভিত্তি করে তৈরি একটি নিউরাল নেটওয়ার্ক। সাধারণ fully connected নেটওয়ার্কের মতো ছবির প্রতিটি পিক্সেলকে প্রতিটি নিউরনের সাথে যুক্ত করার বদলে, এটি ছবির ওপর দিয়ে ছোট ছোট লার্নেবল ফিল্টার স্লাইড করে এবং একই ওজনের সেট সব পজিশনে বারবার ব্যবহার করে। এটি মডেলকে দুটি অনন্য বৈশিষ্ট্য দেয় যা MLP-তে থাকে না — লোকাল কানেক্টিভিটি বা local connectivity (যেখানে প্রতিটি আউটপুট শুধু ইনপুটের একটি ছোট লোকাল অঞ্চলের ওপর নির্ভর করে) এবং ওজন শেয়ারিং বা weight sharing (যেখানে একই ফিল্টার পুরো ছবির সব জায়গায় একই প্যাটার্ন সনাক্ত করতে পারে)। অন্যদিকে, একটি MLP-তে ছবিটিকে ফ্ল্যাটেন করে এক লাইনের ভেক্টরে রূপান্তর করতে হয়, যা ছবির স্থানিক গঠন বা spatial structure নষ্ট করে ফেলে এবং ওজনের প্যারামিটার সংখ্যা বা parameters-এর সংখ্যা অবাস্তব রকমের বাড়িয়ে দেয়।
ওজন শেয়ারিং বলতে বোঝায় ছবির প্রতিটি পজিশনে ওজনের আলাদা সেট না শিখে একটি নির্দিষ্ট ফিল্টারের ওজনকে পুরো ছবির সব জায়গায় বারবার ব্যবহার করা। এটি প্রধানত দুটি কাজ সম্পন্ন করে: প্রথমত, এটি প্যারামিটারের সংখ্যা জ্যামিতিক হারে কমিয়ে আনে (যেমন একটি ৩×৩ ফিল্টারে ছবির আকার নির্বিশেষে ওজনের সংখ্যা মাত্র ৯)। দ্বিতীয়ত, এটি মডেলকে অনুবাদ অপরিবর্তনশীলতা বা translation invariance দেয় — যার ফলে ছবির এক কোণে শেখা কোনো প্যাটার্ন ছবির অন্য যেকোনো কোণে চলে গেলেও ফিল্টারটি তা অনায়াসে সনাক্ত করতে পারে, কারণ একই ফিল্টার পুরো ছবির সব জায়গায় চালানো হচ্ছে।
একটি নিউরনের receptive field হলো আসল ইনপুটের সেই নির্দিষ্ট অঞ্চল যা তার আউটপুট মানকে প্রভাবিত করতে পারে। একটি একক কনভোলিউশন লেয়ারের নিউরনের receptive field তার কার্নেলের সাইজের সমান — অর্থাৎ একটি ৩×৩ ফিল্টারের নিউরন সরাসরি ইনপুটের শুধু ৩×৩ অংশকেই দেখতে পায়। তবে মডেলের গভীরতার সাথে সাথে এই receptive field বড় হতে থাকে: যেমন প্রথম লেয়ারের ওপর স্তুপ করা দ্বিতীয় লেয়ারের ৩×৩ কনভোলিউশন পরোক্ষভাবে আসল ইনপুটের ৫×৫ অঞ্চলকে দেখতে পায়, কারণ তার ইনপুটগুলো নিজেই আগের লেয়ারের ৩×৩ প্রতিবেশের সারসংক্ষেপ। এটি কেন CNN-এ একটি বিশাল ফিল্টার ব্যবহার না করে অনেকগুলো ছোট ছোট কনভোলিউশন লেয়ারের স্তুপ বা stack ব্যবহার করা হয় — যা প্যারামিটার সংখ্যা কম রেখেই কার্যকর receptive field বৃদ্ধি করে।
Translation invariance হলো ছবির কোনো প্যাটার্ন বা অবজেক্টের স্থান পরিবর্তনের ফলেও তাকে চিনে ফেলার মডেলের ক্ষমতা — যেমন একটি বিড়ালের ছবি উপরে-বামে থাকুক আর নিচে-ডানে থাকুক, মডেল তা সফলভাবে সনাক্ত করবে। CNN এটি অর্জন করে ওজন শেয়ারিংয়ের মাধ্যমে: যেহেতু একই ফিল্টার পুরো ছবির ওপর দিয়ে স্লাইড করে নিয়ে যাওয়া হয়, তাই ছবির যেকোনো জায়গায় সেই প্যাটার্নটি মিললে ফিল্টারটি সেখানে সক্রিয় বা অ্যাক্টিভেট হবে। এর সাথে Pooling লেয়ার যুক্ত হয়ে একটি লোকাল উইন্ডোর মানকে ছোট করে আনে, যা পিক্সেলের সামান্য স্থানচ্যুতির ক্ষেত্রেও আউটপুটকে অপরিবর্তিত রাখতে সাহায্য করে।
Pooling লেয়ার ফিচার ম্যাপের স্থানিক আকার সংকুচিত করে বা down-sample করে, যার ফলে পরবর্তী লেয়ারগুলোর কম্পিউটেশনাল খরচ ও প্যারামিটার সংখ্যা অনেক কমে যায়। এছাড়া এটি একটি লোকাল অঞ্চলের সারসংক্ষেপ তৈরি করে সামান্য স্থানচ্যুতির নমনীয়তা দেয়। Pooling না থাকলে ফিচার ম্যাপের সাইজ পুরো নেটওয়ার্ক জুড়ে বড়ই থেকে যেত, যার ফলে কম্পিউটেশনাল খরচ অনেক বাড়ত, চূড়ান্ত ক্লাসিফায়ারে যাওয়ার আগে ফিক্সড সাইজে রূপান্তর করা ধীরগতির হতো এবং মডেলটি স্থানচ্যুতির প্রতি অত্যন্ত সংবেদনশীল হয়ে পড়ত।
Max Pooling প্রতিটি পুলিং উইন্ডো থেকে শুধুমাত্র সর্বোচ্চ মানটি বেছে নেয়, যা ছবির সবচেয়ে স্পষ্ট ও শক্তিশালী ফিচারটি ধরে রাখতে সাহায্য করে — কারণ বেশি অ্যাক্টিভেশন মান মানেই সেখানে কাঙ্ক্ষিত প্যাটার্নটি হওয়ার জোরালো সম্ভাবনা। অন্যদিকে, Average Pooling উইন্ডোর মানগুলোর গড় বা mean নিয়ে কাজ করে, যা কিছুটা মসৃণ ও কম স্পষ্ট সারসংক্ষেপ দেয় এবং উইন্ডোর সব পিক্সেলের তথ্য আংশিকভাবে ধরে রাখে। হিডেন লেয়ারগুলোতে Max Pooling বেশি ব্যবহৃত হয় কারণ এটি তীক্ষ্ণ ও গুরুত্বপূর্ণ ফিচারগুলো ভালোভাবে ধরে রাখে; আর Average Pooling সাধারণত ক্লাসিফায়ারে যাওয়ার ঠিক আগের ধাপে (Global Average Pooling) ব্যবহৃত হয় যেখানে সম্পূর্ণ ফিচার ম্যাপের একটি সামগ্রিক গড় সারসংক্ষেপের প্রয়োজন পড়ে।
Sigmoid এবং Tanh অ্যাক্টিভেশন ফাংশনগুলো স্যাচুরেট হয় — অর্থাৎ ইনপুটের মান খুব বড় পজিটিভ বা নেগেটিভ হলে তাদের গ্রেডিয়েন্ট বা পরিবর্তনের হার প্রায় শূন্যের কোঠায় নেমে যায়। গভীর নেটওয়ার্কের ক্ষেত্রে ব্যাকপ্রোপাগেশনের সময় চেইন রুল অনুযায়ী এই গ্রেডিয়েন্ট গুণ হতে হতে শুরুর লেয়ারগুলোতে ওজনের গ্রেডিয়েন্ট শূন্য হয়ে যায়, যা ভ্যানিশিং গ্রেডিয়েন্ট সমস্যা (vanishing gradient problem) নামে পরিচিত। এর বিপরীতে, ReLU-র ক্ষেত্রে পজিটিভ ইনপুটের গ্রেডিয়েন্ট সবসময় ফিক্সড ১ থাকে, ফলে এটি স্যাচুরেট হয় না এবং গভীর লেয়ারগুলোতে গ্রেডিয়েন্ট প্রবাহ সচল রাখে। এছাড়া এটি একটি সাধারণ শূন্যের সাথে তুলনা মাত্র, তাই কম্পিউটেশনের দিক থেকেও অত্যন্ত সাশ্রয়ী।
চেইন রুল অনুযায়ী গ্রেডিয়েন্ট যখন অনেকগুলো লেয়ারের মধ্য দিয়ে ব্যাকপ্রোপাগেট হয়, তখন প্রতিটি লেয়ার সেই গ্রেডিয়েন্টকে আরেকটি ফ্যাক্টর দিয়ে গুণ করে। এই ফ্যাক্টরগুলো যদি ধারাবাহিকভাবে ১-এর চেয়ে ছোট হয় — যা স্যাচুরেটিং অ্যাক্টিভেশন বা অনুপযুক্তভাবে স্কেল করা ওজনের ক্ষেত্রে খুব সহজেই ঘটে — তাহলে নেটওয়ার্কের গভীরতা বাড়ার সাথে সাথে গ্রেডিয়েন্ট সূচকীয় হারে (exponentially) সংকুচিত হতে থাকে, এবং সবচেয়ে প্রথম দিকের লেয়ারগুলো কার্যত শেখা বন্ধ করে দেয়। ResNet এই সমস্যা সমাধান করে skip বা residual connection ব্যবহার করে: প্রতিটি ব্লক একটি রেসিডুয়াল ফাংশন গণনা করে এবং তা তার অপরিবর্তিত ইনপুটের সাথে যোগ করে দেয়, ফলে গ্রেডিয়েন্টের জন্য আগের লেয়ারগুলোতে ফিরে যাওয়ার একটি সরাসরি সংযোজনমূলক (additive) পথ তৈরি হয় যা মাঝের প্রতিটি ট্রান্সফরমেশনের মধ্য দিয়ে যেতে বাধ্য নয়। এই শর্টকাটটিই নিশ্চিত করে যে কয়েক ডজন বা কয়েকশো লেয়ার গভীর নেটওয়ার্কেও গ্রেডিয়েন্ট হারিয়ে যায় না।
কনভোলিউশন ও পুলিং লেয়ারগুলো মাল্টি-ডাইমেনশনাল ফিচার ম্যাপ (উচ্চতা × প্রস্থ × চ্যানেল) তৈরি করে। কিন্তু মডেলের শেষের ক্লাসিফায়ার হেড বা ডেন্স লেয়ারগুলো ইনপুট হিসেবে একটি ১D ভেক্টর প্রত্যাশা করে। Flatten লেয়ারের একমাত্র কাজ হলো এই মাল্টি-ডাইমেনশনাল ফিচার ম্যাপের মানগুলোকে এক লাইনে সাজিয়ে সোজা একটি ১D ভেক্টরে রূপান্তর করা। এর কোনো লার্নেবল প্যারামিটার থাকে না এবং ওজনের মানও পরিবর্তিত হয় না — এটি স্রেফ নেটওয়ার্কের ভিশন অংশের সাথে ডিসিশন অংশের সংযোগকারী সেতু।
Sigmoid প্রতিটি আউটপুটকে আলাদাভাবে রেঞ্জে চেপে ফেলে, অর্থাৎ প্রতিটি ক্লাসকে একটি স্বতন্ত্র হ্যাঁ/না সিদ্ধান্ত হিসেবে বিবেচনা করে — যা multi-label সমস্যার জন্য একদম উপযুক্ত, যেখানে একসাথে একাধিক ক্লাস সত্য হতে পারে, কিন্তু যেখানে ঠিক একটি ক্লাসই সঠিক হওয়া উচিত সেখানে এটি কাজ করে না। এর বদলে Softmax raw logit-গুলোকে সব ক্লাস জুড়ে একটি একক সম্ভাব্যতা বিতরণে (probability distribution) রূপান্তর করে: প্রতিটি আউটপুট পজিটিভ হয় এবং তাদের যোগফল ঠিক ১ হয়। এই কারণে এটি পারস্পরিক exclusive, single-label ক্লাসিফিকেশনের জন্য স্বাভাবিক পছন্দ, এবং এটি cross-entropy loss-এর সাথে নিখুঁতভাবে মিলে গিয়ে একটি সরল ও সুশৃঙ্খল গ্রেডিয়েন্ট তৈরি করে।
প্যাডিং যুক্ত ইনপুটের ওপর দিয়ে বাম থেকে ডানে কার্নেল স্লাইড করার কথা ভাবুন। এটি প্রথম বৈধ পজিশন থেকে শুরু করে ডান সীমানা পর্যন্ত যেতে পারে। ইনপুট সাইজ , প্যাডিং , কার্নেল সাইজ এবং স্ট্রাইড হলে, প্যাডিং সহ মোট প্রস্থ হয় । কার্নেলটি প্রতি পজিশনে জায়গা দখল করে, ফলে বাকি থাকে পরিমাণ সরানোর জায়গা। একে স্ট্রাইড দিয়ে ভাগ করলে মোট কতটি পদক্ষেপ নেওয়া যাবে তা পাওয়া যায় — এবং তার সাথে ওজনের বা শুরুর পজিশনটি যোগ করতে হয়:
এই সূত্রটি উচ্চতা ও প্রস্থের ওপর আলাদা আলাদাভাবে প্রয়োগ করা হয় (ইনপুট বা কার্নেল বর্গাকার না হলে দুটির মান ভিন্নও হতে পারে)।
প্রতিটি ফিল্টারকে অবশ্যই ইনপুটের সম্পূর্ণ গভীরতা জুড়ে বিস্তৃত হতে হয়, তাই এখানে একটি ফিল্টারে টি ওজন থাকবে, সাথে প্রতি ফিল্টারে একটি করে বায়াস টার্ম। অর্থাৎ একটি একক ফিল্টারের জন্য টি প্যারামিটার। এরকম ৬৪টি ফিল্টার থাকলে মোট প্যারামিটার সংখ্যা হবে টি।
Max Pooling-এর কোনো লার্নেবল প্যারামিটার নেই, তাই "এর মধ্য দিয়ে ব্যাকপ্রোপাগেশন" মানে শুধু ইনকামিং গ্রেডিয়েন্টকে সঠিক জায়গায় পাঠানো, কোনো ওজন আপডেট করা নয়। ফরওয়ার্ড পাসের সময় লেয়ারটি মনে রাখে প্রতিটি উইন্ডোর কোন উপাদানটি ছিল সর্বোচ্চ মান। ব্যাকওয়ার্ড পাসের সময়, সেই উইন্ডোর জন্য আসা সম্পূর্ণ ইনকামিং গ্রেডিয়েন্টটি সরাসরি সেই মনে রাখা একটি পজিশনে পাঠানো হয়, আর উইন্ডোর বাকি সব পজিশন শূন্য গ্রেডিয়েন্ট পায় — কারণ সর্বোচ্চ নয় এমন কোনো মান পরিবর্তন করলে pooled আউটপুট পরিবর্তিত হয় না, তাই লসের ওপর তার কোনো প্রভাব নেই।
"Valid" padding মানে একেবারেই কোনো প্যাডিং নেই — কার্নেল শুধু সেই পজিশনগুলোর ওপর স্লাইড করে যা সম্পূর্ণভাবে আসল ইনপুটের ভেতরে থাকে, তাই আউটপুট ইনপুটের চেয়ে ছোট হয়ে যায় (স্ট্রাইড ১ হলে প্রতি ডাইমেনশনে পরিমাণ সংকুচিত হয়)। "Same" padding ইনপুটের চারপাশে ঠিক যতটা প্রয়োজন ততটা জিরো-প্যাডিং যোগ করে, যাতে স্ট্রাইড ১-এর ক্ষেত্রে আউটপুটের স্থানিক আকার ইনপুটের সমান থাকে। যখন ফিচার ম্যাপ ছোট হতে হতে শূন্যে নেমে যাওয়ার চিন্তা না করে নেটওয়ার্কের গভীরতা নিয়ন্ত্রণ করতে চাওয়া হয়, তখন "Same" padding সাধারণত বেশি ব্যবহৃত হয়।
একটি স্ট্যান্ডার্ড কনভোলিউশন ফিল্টার একসাথে ইনপুটের প্রতিটি চ্যানেল জুড়ে বিস্তৃত থাকে, এবং একটিমাত্র অপারেশনে স্থানিক (spatial) ও ক্রস-চ্যানেল তথ্য মিশিয়ে ফেলে। Depthwise Separable Convolution এই কাজটিকে দুটি সস্তা ধাপে ভাগ করে দেয়: প্রথমে একটি depthwise কনভোলিউশন, যা প্রতিটি ইনপুট চ্যানেলে আলাদাভাবে একটি করে ছোট ফিল্টার প্রয়োগ করে (পুরোপুরি স্থানিক, চ্যানেল মেশানো হয় না), তারপর একটি pointwise কনভোলিউশন, যা চ্যানেলগুলোকে একসাথে মিশিয়ে দেয়। যেহেতু ব্যয়বহুল স্থানিক ফিল্টারিং ধাপটি প্রতিটি আউটপুট চ্যানেলের জন্য আর বারবার করতে হয় না, তাই মোট গুণ-যোগ (multiply-add) হিসাবের সংখ্যা একটি স্ট্যান্ডার্ড কনভোলিউশনের তুলনায় মোটামুটি -এর সমানুপাতিক হারে কমে যায় — এই কারণেই MobileNet-এর মতো ফোনের জন্য তৈরি আর্কিটেকচারগুলো এটি ব্যাপকভাবে ব্যবহার করে।
Batch Normalization বর্তমান mini-batch জুড়ে গণনা করা mean ও variance ব্যবহার করে একটি লেয়ারের অ্যাক্টিভেশনগুলোকে normalize করে, তারপর দুটি লার্নেবল প্যারামিটার (একটি scale ও একটি shift) প্রয়োগ করে, যাতে কোনো নির্দিষ্ট লেয়ারের জন্য normalization বাতিল করাটাই যদি ভালো হয় তাহলে নেটওয়ার্ক তা করতে পারে। এটি ট্রেনিংয়ে সাহায্য করে কারণ এটি পুরো ট্রেনিং জুড়ে প্রতিটি লেয়ারের ইনপুট ডিস্ট্রিবিউশনকে একটি স্থিতিশীল ও সামঞ্জস্যপূর্ণ রেঞ্জে ধরে রাখে, তার বদলে আগের লেয়ারগুলোর ওজন পরিবর্তনের সাথে সাথে তা ক্রমাগত পরিবর্তিত হতে দেয় না (এই ঘটনাকে internal covariate shift বলা হয়)। বাস্তবে এর ফলে আরও বড় লার্নিং রেট ব্যবহার করা যায়, ট্রেনিং ইনিশিয়ালাইজেশনের প্রতি কম সংবেদনশীল হয়ে ওঠে, এবং প্রায়ই এটি একটি হালকা রেগুলারাইজারের মতোও কাজ করে।
সাধারণ SGD প্রতিটি প্যারামিটারকে তার বর্তমান গ্রেডিয়েন্টের সাথে একই লার্নিং রেট গুণ করে আপডেট করে — সরল ও সুপরিচিত, কিন্তু লস ল্যান্ডস্কেপের খাদের মতো অংশগুলোতে এটি ধীর হয়ে যেতে পারে এবং প্রতিটি প্যারামিটারকে একই রকমভাবে বিবেচনা করে। RMSProp প্রতিটি প্যারামিটারের জন্য আলাদাভাবে effective লার্নিং রেট মানিয়ে নেয় — প্রতিটি আপডেটকে সেই প্যারামিটারের সাম্প্রতিক স্কোয়ার্ড গ্রেডিয়েন্টগুলোর একটি running average দিয়ে ভাগ করে, ফলে যেসব প্যারামিটারে ধারাবাহিকভাবে বড় গ্রেডিয়েন্ট আসে তারা ছোট স্টেপ পায় এবং উল্টোটাও ঘটে। Adam সেই per-parameter adaptive scaling-কে মোমেন্টামের (গ্রেডিয়েন্টেরই একটি running average, শুধু তার স্কোয়ার নয়) সাথে যুক্ত করে, সাথে একটি bias-correction টার্ম রাখে যা শুরুর দিকের আপডেটগুলোকে সঠিকভাবে ক্যালিব্রেটেড রাখে। বাস্তবে Adam-ই সবচেয়ে বেশি ব্যবহৃত ডিফল্ট অপ্টিমাইজার কারণ এটি সাধারণত অল্প টিউনিংয়েই দ্রুত কনভার্জ করে, যদিও যথেষ্ট সময় নিয়ে টিউন করলে মোমেন্টামসহ সাধারণ SGD মাঝে মাঝে সামান্য ভালো generalize করতে পারে।
একটি কনভোলিউশন কোনো স্থানিক প্রতিবেশকেই দেখে না — এটি একবারে একটি পিক্সেল পজিশনের ওপর কাজ করে, কিন্তু সব ইনপুট চ্যানেল জুড়ে। এটি চ্যানেলের সংখ্যা পরিবর্তনের একটি সস্তা উপায় (কম্পিউটেশন বাঁচাতে চ্যানেল সংখ্যা কমানো, বা বাড়ানো), এবং স্থানিক ডাইমেনশনগুলো একদমই স্পর্শ না করে (তার পরের অ্যাক্টিভেশনের মাধ্যমে) একটি নন-লিনিয়ারিটি যোগ করে। GoogLeNet-এর Inception মডিউলগুলো ব্যয়বহুল বা কনভোলিউশনের আগে "bottleneck" হিসেবে কনভোলিউশন ব্যাপকভাবে ব্যবহার করে, ঠিক যাতে প্রথমেই চ্যানেল সংখ্যা কমিয়ে ফেলা যায়।
একটি ব্লক ইনপুট থেকে আউটপুটে সরাসরি একটি ম্যাপিং শেখার বদলে, একটি রেসিডুয়াল ব্লক শেখে রেসিডুয়াল , এবং ব্লকের প্রকৃত আউটপুট হয় — মানে skip connection-এর মাধ্যমে আসল ইনপুটটা আবার যোগ করে দেওয়া হয়। কোনো ব্লকের জন্য আদর্শ ম্যাপিং যদি identity-এর কাছাকাছি হয়, তাহলে একগাদা নন-লিনিয়ার লেয়ারের মধ্য দিয়ে হুবহু identity ম্যাপিং শেখার চেয়ে -কে শূন্যের দিকে ঠেলে দেওয়া নেটওয়ার্কের জন্য অনেক সহজ। ঠিক সমান গুরুত্বপূর্ণ বিষয় হলো, skip connection গ্রেডিয়েন্টকে পেছনের দিকে যাওয়ার একটি সরাসরি সংযোজনমূলক (additive) পথ দেয়, যা ব্লকের ট্রান্সফরমেশনগুলোকে সম্পূর্ণভাবে এড়িয়ে যায় — এটিই ResNet-কে ৫০, ১০১, এমনকি ১৫২ লেয়ার গভীরতায়ও নির্ভরযোগ্যভাবে ট্রেইন করতে দেয়।
এর কোনো একটিমাত্র উত্তর নেই, বরং একটি পুরো টুলবক্স আছে, এবং একটি ভালো উত্তরে বেশ কয়েকটির নাম উল্লেখ করা উচিত: ট্রেনিংয়ের সময় নিউরনদের এলোমেলোভাবে নিষ্ক্রিয় করতে Dropout যোগ করা, যাতে নেটওয়ার্ক কোনো একটি নিউরনের ওপর অতিরিক্ত নির্ভরশীল হয়ে না পড়ে; ওজনগুলো ছোট রাখতে L2 weight decay যোগ করা; নতুন ডেটা সংগ্রহ না করেই নেটওয়ার্ককে বেশি কার্যকর বৈচিত্র্য দেখাতে data augmentation (random flip, crop, rotation, color jitter) ব্যবহার করা; Batch Normalization যোগ করা, যার একটি হালকা রেগুলারাইজিং প্রভাবও থাকে; validation পারফরম্যান্স উন্নতি বন্ধ হয়ে গেলে ট্রেনিং থামাতে early stopping ব্যবহার করা; এবং এসবেও যথেষ্ট না হলে সরাসরি মডেল সরল করা বা আরও ডেটা সংগ্রহ করা। ইন্টারভিউতে এগুলোর মধ্যে একটির বদলে একাধিকটির নাম বলা এবং প্রতিটি কেন সাহায্য করে তা ব্যাখ্যা করা অনেক বেশি শক্তিশালী উত্তর হিসেবে বিবেচিত হয়।
import numpy as np
def conv2d(x, kernel, stride=1, padding=0):
x = np.pad(x, ((padding, padding), (padding, padding)))
H, W = x.shape
F = kernel.shape[0]
out_h = (H - F) // stride + 1
out_w = (W - F) // stride + 1
out = np.zeros((out_h, out_w))
for i in range(out_h):
for j in range(out_w):
row, col = i * stride, j * stride
patch = x[row:row + F, col:col + F]
out[i, j] = np.sum(patch * kernel)
return outএটি লিখতে লিখতে মুখে বলার মতো মূল আইডিয়া: প্রথমে প্যাডিং করুন, প্রযুক্তিগত অংশের সূত্র দিয়ে আউটপুট সাইজ হিসাব করুন, তারপর কার্নেল উইন্ডোটিকে ধাপে ধাপে স্লাইড করুন, প্রতিবার এলিমেন্ট-ওয়াইজ গুণ করে যোগফল নিন।
import numpy as np
def max_pool2d(x, size=2, stride=2):
H, W = x.shape
out_h = (H - size) // stride + 1
out_w = (W - size) // stride + 1
out = np.zeros((out_h, out_w))
for i in range(out_h):
for j in range(out_w):
row, col = i * stride, j * stride
window = x[row:row + size, col:col + size]
out[i, j] = np.max(window)
return outএটিকে ব্যাকপ্রোপাগেশন পর্যন্ত বাড়াতে বললে উল্লেখ করুন যে, ফরওয়ার্ড পাসের সময় প্রতিটি উইন্ডোর np.argmax সংরক্ষণ করে রাখতে হয়, যাতে ব্যাকওয়ার্ড পাস ঠিক কোন পজিশনে গ্রেডিয়েন্ট পাঠাতে হবে তা জানতে পারে।
import torch
import torch.nn as nn
class SmallCNN(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(32 * 7 * 7, 10),
)
def forward(self, x):
return self.net(x)
model = SmallCNN()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
criterion = nn.CrossEntropyLoss()
# training loop sketch
for images, labels in train_loader:
optimizer.zero_grad()
logits = model(images)
loss = criterion(logits, labels)
loss.backward()
optimizer.step()বলার মতো গুরুত্বপূর্ণ বিষয়: দুটি conv+pool ব্লক ২৮×২৮ ইনপুটকে দুইবার অর্ধেক করে (৭×৭-এ নামিয়ে আনে), স্থানিক আকার ছোট হওয়ার সাথে সাথে চ্যানেল সংখ্যা বাড়তে থাকে, এবং PyTorch-এর CrossEntropyLoss-এর ভেতরেই Softmax অন্তর্ভুক্ত থাকে, তাই মডেলের শেষ লেয়ারের আউটপুট হওয়া উচিত raw logits।
import matplotlib.pyplot as plt
activation = {}
def hook(module, input, output):
activation["conv1"] = output.detach()
model.net[0].register_forward_hook(hook)
model(sample_image.unsqueeze(0))
feature_maps = activation["conv1"][0] # shape: (channels, H, W)
fig, axes = plt.subplots(4, 4, figsize=(8, 8))
for i, ax in enumerate(axes.flat):
ax.imshow(feature_maps[i].cpu(), cmap="viridis")
ax.axis("off")
plt.show()উল্লেখ করার মতো কৌশল: একটি forward hook আপনাকে মডেলের forward মেথড পরিবর্তন না করেই একটি মধ্যবর্তী লেয়ারের আউটপুট ক্যাপচার করতে দেয় — ট্রেনিং কোড স্পর্শ না করে পরীক্ষা-নিরীক্ষা করার জন্য এটি খুবই কাজে লাগে।
import torchvision.transforms as T
augment = T.Compose([
T.RandomHorizontalFlip(p=0.5),
T.RandomRotation(degrees=15),
T.ToTensor(),
])
augmented_dataset = MyImageDataset(root="data/", transform=augment)স্পষ্টভাবে বলে রাখা ভালো: augmentation শুধুমাত্র ট্রেনিং সেটেই প্রয়োগ করা উচিত, কখনোই validation বা test ডেটাতে নয় — কারণ একটি সৎ পারফরম্যান্স মূল্যায়ন পেতে সেগুলোকে অবশ্যই আসল, অপরিবর্তিত ইনপুট প্রতিফলিত করতে হবে।