ইতোমধ্যে একটা ধারনা পেয়ে গেছেন যে, ডিপ লার্নিং এর মাধ্যমে বেশিরভাগ সময়েই ডাটা ক্লাসিফিকেশনের কাজ করা হয়ে থাকে। কিন্তু যদি আপনার মেশিন লার্নিং সম্পর্কে ধারনা থেকে থাকে তাহলে হয়ত এটাও জানেন যে, মেশিন লার্নিং এর বিভিন্ন অ্যালগরিদম ব্যবহার করেও ডাটা ক্লাসিফিকেশনের কাজ করা যায়। কিন্তু দুটো মাধ্যমের কাজ করার ধরনে বেশ কিছু পার্থক্য আছে। যেমন- মেশিন লার্নিং ব্যবহার করে ইমেজ ক্লাসিফিকেশনের কাজ করার সময় প্রথমেই ট্রেনিং ডাটা(ইমেজ) থেকে আপনার নিজেকেই ফিচার (যেমন-এইজ, কর্নার ইত্যাদি) এক্সট্র্যাক্ট করে নিতে হবে আপনার মেশিন লার্নিং মডেলকে ট্রেনিং করানোর জন্য। এরপর নতুন ডাটা(ইমেজ) নিয়ে সেটার ধরন প্রেডিক্ট করার সময় আপনার মেশিন লার্নিং মডেল সেই ফিচার গুলোকেই ভ্যারিয়েবল (চেনার হাতিয়ার) হিসেবে কাজে লাগিয়ে নতুন ইমেজটাকে অ্যানালাইস করে ডিসিশন নেয়। এভাবে ইমেজের মধ্যে কোন একটি নির্দিষ্ট অবজেক্ট রিকগনিশন বা ডিটেকশণ এর কাজও করা হয়ে থাকে।
অন্যদিকে ডিপ লার্নিং টেকনিকে কাজ করার সময় আপনি ম্যানুয়ালি সেই ফিচার এক্সট্র্যাকশনের কাজ থেকে বেঁচে যেতে পারেন। এক্ষেত্রে আপনি পুরো ইমেজটাকেই আপনার ডিপ নিউরাল নেটওয়ার্কে ইনপুট হিসেবে দিয়ে দিতে পারেন এবং সেই নেটওয়ার্ক স্বয়ংক্রিয় ভাবেই লেবেলের সাথে সম্পর্ক রেখে ওই ইমেজের গুরুত্বপূর্ণ ফিচারগুলোকে কন্সিডার করে লার্নিং করে নিতে পারবে যাতে করে সে পরবর্তীতে নতুন ইমেজ থেকে একই ধরনের অবজেক্ট খুঁজে নিতে পারে বা তার টাস্ক সম্পন্ন করতে পারে।
কখন আপনার জন্য মেশিন লার্নিং অ্যাপ্রোচ ভালো হবে এবং কখন আপনি ডিপ লার্নিং নিয়ে কাজ করলে সুবিধা হবে সেটা নির্ভর করে আপনার নির্দিষ্ট সমস্যাটির টাইপের উপর এবং আপনার কাছে থাকা ডাটার পরিমাণ ও ধরনের উপর। তবে খুব সহজ ভাবেও প্রাথমিক একটা সিদ্ধান্ত নিতে পারেন। যেমন- আপনার কাছে যদি তুলনামূলক কম পরিমাণ ডাটা থাকে এবং আপনার কম্পিউটেশন পাওয়ারও সীমাবদ্ধ হয় তাহলে আপনার জন্য মেশিন লার্নিং অ্যাপ্রোচ ভালো হবে। এতে করে আপনি একদিকে কম রিসোর্স ব্যবহার করেই সমস্যাটি নিয়ে কাজ করতে পারবেন এবং সাথে সাথে যেহেতু আপনি নিজেই ডাটা থেকে ফিচার পছন্দ করেন আর বর্তমানে অনেক গুলো মেশিন লার্নিং অ্যালগরিদম আছে তাই, বিভিন্ন ফিচার এবং অ্যালগরিদম এর কম্বিনেশন ব্যবহার করে আপনার মডেল এর পারফর্মেন্স চেক করে দেখতে পারেন।
অন্যদিকে যদি আপনার কাছে অনেক পরিমাণ ডাটা থাকে এবং সাথে সাথে আপনার কাছে যথেষ্ট পরিমাণ কম্পিউটেশন পাওয়ার থাকে তাহলে আপনার জন্য ডিপ লার্নিং অ্যাপ্রোচ ভালো হবে। এতে করে অনেক অনেক ডাটা থেকে ট্রেনিং করানোর সময় আপনার ডিজাইন করা নিউরাল নেটওয়ার্কটি অনেক বেশি পারফেকশন দেখাতে পারবে। এমনকি সেই ডাটা গুলো থেকে ধরে ধরে আপনাকে ফিচার পছন্দ না করে দিলেও চলবে। তবে এর জন্য মারাত্মক রকম কম্পিউটেশন পাওয়ার এবং সময়ও দরকার পরবে।
আর্টিফিশিয়াল নিউরাল নেটওয়ার্ক
নিউরাল নেটওয়ার্ক এবং অন্য যেকোনো রকম নেটওয়ার্ক (যেমন – কিছু কম্পিউটার মিলে একটি লোকাল এরিয়া নেটওয়ার্ক অথবা পুরো ওয়েব নেটওয়ার্ক) বস্তুত একই। বেশ কিছু নোড বা পয়েন্ট একে ওপরের সাথে নির্দিষ্ট কিছু নিয়মে যুক্ত থেকে নিজেদের মধ্যে তথ্য আদান প্রদান করলেই তাকে একটা নেটওয়ার্ক বলা যায়। নিউরাল নেটওয়ার্কের ক্ষেত্রে সেই নোড (Node) হচ্ছে এক একটি নিউরন। আমাদের ব্রেইনের মধ্যে বস্তুত বিলিয়ন সংখ্যক নিউরনের একটা নেটওয়ার্ক তৈরি করা আছে। মোটামুটি সেই গঠন শৈলীর উপর ভিত্তি করেই ডাটা থেকে প্যাটার্ন রিকগনিশনের জন্য এক ধরনের কার্যপদ্ধতির নামই হচ্ছে আর্টিফিশিয়াল নিউরাল নেটওয়ার্ক। অর্থাৎ সত্যিকারের নিউরান যে নীতিতে কাজ করে, এই নিউরনও একইভাবে কাজ করে। কিন্তু যেহেতু এগুলো সত্যিকারের নিউরন নয় তাই এটার নাম আর্টিফিশিয়াল নিউরাল নেটওয়ার্ক।
ভিত্তি
তো আমরা যদি একটু দুঃসাহস করে সত্যিকারের একটা নিউরনের কার্যনীতি দেখি তাহলে আমরা জানতে পারি যে – একটা নিউরনের কিছু ইনপুট দরকার এবং সেই ইনপুট গুলো আসে Dendrite নামের কিছু ডাল পালার মত অংশ দিয়ে, এরপর নিউরন বডি বা Soma নামের অংশে কিছু ক্যালকুলেশন হয় সেই ইনপুট গুলোর উপর। অতঃপর Axon নামের লেজের মত একটা অংশ দিয়ে সেই ক্যালকুলেশনের আউটপুট বের হয় যা কিনা আবার অন্য এক বা একাধিক নিউরনের ইনপুট স্লট তথা Dendrite এ চলে যায়। একটি নিউরনের এক্সন এবং অন্য নিউরনের ডেন্ড্রাইটের মাঝে Synapse নামের কিছু তরল থাকে। এটাই বস্তুত এক নিউরন থেকে আরেক নিউরনের কাছে এক্সনের আউটপুট কে ডেন্ড্ররাইটে ইনপুট দেয়ার ক্ষেত্রে ট্রান্সমিশনের ভূমিকা রাখে। যদি একটি নিউরনের যথেষ্ট পরিমাণ সিন্যাপ্টিক ইনপুট ফায়ার (আশানুরূপ একটা ভ্যালু তৈরি করে) করে তাহলে সেই নিউরনটা ফায়ার করে বা বলা যেতে পারে যে, সেই নিউরনটা অ্যাকটিভ হয়। বিষয় হচ্ছে – এই ঘটনাকেই চিন্তা করা বলে।
সহজ একটি নিউরাল নেটওয়ার্ক
উপরোক্ত প্রসেসটাকেই আমরা কম্পিউটারে প্রোগ্রাম লিখে সিমুলেট করতে পারি। তাহলেই কিন্তু একটা আর্টিফিশিয়াল নিউরন বানাতে পারছি বলে ধরে নয়া যায়। তাই না? তবে এটা সত্যিকারের নিউরনের মত জটিল হবে না কারণ, আগেই বলা হয়েছে যে আর্টিফিশিয়াল নিউরাল নেটওয়ার্ক শুধুমাত্র সত্যিকারের নিউরনের নেটওয়ার্কের কনসেপ্ট এর উপর ভিত্তি করে তৈরি। তার মানে এই না যে এটা শতভাগ একই। যাই হোক আমরা একটি সিঙ্গেল নিউরন তৈরি করি যার মাধ্যমেই আমরা ছোট্ট একটা সমস্যা সমাধানের চেষ্টা করবো। ধরে নেই আমাদের আর্টিফিশিয়াল ব্রেনে একটাই মাত্র নিউরন।
সমস্যাটা নিচের মত,

উপরের প্যাটার্ন গুলো নিয়ে একটু চিন্তা করলেই আমরা বুঝতে পারবো যে প্রত্যেকটি ইনপুট কম্বিনেশনের প্রথম ভ্যালুটি বস্তুত ওই কম্বিনেশনের আউটপুট। পরের দুটি ভ্যালু অনর্থক। তাই নতুন অচেনা কম্বিনেশন 1 0 0 এর আউটপুট হবে 1. আমরা নিজেরা এটা সহজেই চিন্তা করে ধরে ফেলেছি। এই কাজটাই আমরা আরটিফিশিয়াল নিউরনের মাধ্যমে করে দেখতে চাই।
এখন আমাদের আর্টিফিশিয়াল ব্রেনকে উপরের চারটা উদাহরণ (কিছু ইনপুট কম্বিনেশন এবং তার সাপেক্ষে একটি করে আউপুটপুট) দেখিয়ে বললাম এটার প্যাটার্ন শিখে নাও যাতে করে একই প্যাটার্নেরই কিন্তু নতুন অচেনা একটা ইনপুট কম্বিনেশনের আউটপুট কত হবে সেটা বলতে পারো। ধরে নেই আমাদের নিউরন দেখতে নিচের মত।

এই নিউরনেরও তিনটা ইনপুট নেয়ার জন্য তিনটা ডেন্ড্রাইট আছে আর একটা এক্সন আছে যেখানে আমরা আউপুট পেয়ে যাবো কারণ এই ব্রেনে আর কোন নিউরন নাই যাকে এই এক্সনের আউপুটকে ইনপুট হিসেবে দিতে হবে।
ট্রেনিং এর পদ্ধতি
আমরা যেটা করতে পারি – নিউরনটার তিনটা ইনপুটে আমাদের প্রথম উদাহরণের তিনটা ভ্যালু দেবো আর আউপুট প্রান্তে সেই উদাহরণ মোতাবেক সঠিক আউপুটটা রাখবো। এরপর ইনপুট লাইন (গ্রাফ কনসেপ্টে Edge) গুলোতে কিছু Weight (ভ্যালু) সেট করবো। এরপর প্রত্যেকটা ইনপুট ভ্যালুর সাথে ওই Edge এর ভ্যালু তথা Weight গুন করে নিউরন-বডি/Soma বা Node এ একটা Weighted Sum জমা করবো। এক্ষেত্রে এই Node টী হচ্ছে আমাদের আউপুট নোড। যাই হোক, এই নোডে জমা হওয়া ভ্যালুর পরিমাণ কিন্তু 1 এর অনেক বেশি বা 0 এর চেয়ে কম হতে পারে। কিন্তু আমরা তো চাই, ভ্যালুটা 1 আর 0 এর মাঝা মাঝি থাকুক যাতে করে আমরা আমাদের আগে থেকেই সেট করা আউপুট ভ্যালু (যেমন – প্রথম উদাহরণ মোতাবেক 0) –এর সাথে তুলনা করতে পারি। তাই আউটপুট নোডে জমা হওয়া ভ্যালুকে আমরা গণিতের একটা স্পেশাল গ্যারাকলে ফেলে কোন না কোন ভাবে 1 ও 0 এর মাঝেই রাখবো। এটাকে বলে Activation Function (নিচেই আমরা এর চেহারা এবং ব্যবহার দেখবো)। এরপর সেই ক্যালকূলেটেড ভ্যালু এবং প্রথম উদাহরণ মোতাবেক আউটপুটের ভ্যালুর মধ্যে তুলনা করে আমরা ভুলের পরিমাণ দেখবো।
অর্থাৎ প্রথম ট্রেনিং হচ্ছে – ইনপুট দিলাম 0 0 1 এবং আউটপুট যেন হয় 0. কিন্তু ধরি Edge গুলোতে সেট করা আমাদের র্যান্ডম ওয়েট গুলো কন্সিডার করে ওয়েটেড সাম থেকে অ্যাক্টিভেশন ফাংশনের মাধ্যমে ভ্যালু পেলাম 0.9
তার মানে ট্রেনিং এর মাথা মুণ্ড কিছুই হয় নি। এরপর আমরা যা করতে পারি সেটা হল, সেই ওয়েট গুলোকে একটু চেঞ্জ (Adjust) করে দেখতে পারি। অর্থাৎ প্রথম লাইনে (Edge) যে ভ্যালু ছিল সেটাকে একটু বাড়িয়ে, আবার দ্বিতীয় লাইনের ওয়েটকে একটু কমিয়ে আবারও Activation Function এর আউটপুট দেখতে পারি। এবার যদি দেখি যে এর মান আসলো 0.4 তার মানে আউটপুটের সাথে মিল আসতেছে। আবারও একটু ওয়েট গুলোকে অ্যাডজাস্ট করে Activation Function এর আউটপুট দেখতে পারি। এবার যদি দেখি যে এর মান আসলো 0.1 তার মানে প্রথম ট্রেনিং সেট মোতাবেক যে 0 পাওয়ার চেষ্টা সেটা মোটামুটি সফল।
অর্থাৎ প্রথম ডাটা সেট (একটা Row যার ইনপুট/ভ্যারিয়েবল 0 0 1 এবং আউটপুট/লেবেল 0) এর জন্য আমাদের নিউরনের ট্রেনিং সম্পন্ন। অর্থাৎ, নিউরনটি তার ওয়েট গুলোকে অ্যাডজাস্ট করে এমন একটা অবস্থায় নিয়ে এসেছে যে এর তিনটি ইনপুটে 0 0 1 দিলে আউটপুটে 0 এর কাছাকাছি একটা ভ্যালু আসে। এরপর আবার নিউরনকে দ্বিতীয় ট্রেনিং ডাটা সেট দেয়া হল। এবার তার কাজ হচ্ছে নতুন তিনটি ইনপুট 1 1 1 এবং এর জানা আউটপুট 1 এর সাপেক্ষে নিজের Edge গুলোর ওয়েট এমনভাবে অ্যাডজাস্ট করা, যাতে করে ওয়েটেড সাম এবং Activation Function এর কাজের পর আউটপুট মোটামুটি 1 এর কাছাকাছি আসে।
কিন্তু খেয়াল রাখতে হবে যে আগের ট্রেনিং সেটের ব্যাপারটাও যাতে ঠিক থাকে। অর্থাৎ, দ্বিতীয় ট্রেনিং সেটের জন্য ওয়েট অ্যাডজাস্ট করে ইনপুট আউটপুট মিলাতে গিয়ে যেন প্রথম ট্রেনিং সেটের বারোটা না বেজে যায়। তার মানে তাকে খুবি ধীরে সুস্থে এবং ক্ষুদ্র ক্ষুদ্র পরিমাণে ওয়েটের ভ্যালু অ্যাডজাস্ট করতে হবে যাতে 0 0 1 দিলেও 0 এর মত মান আসে আবার 1 1 1 দিলেও যাতে 1 এর মত মান আসে। তো, বোঝাই যাচ্ছে যে, একবার দুইবার অ্যাডজাস্ট করে এই কাজ হাসিল করা সম্ভব না। বার বার এরর চেক করে বার বার ওয়েট অ্যাডজাস্ট করে করে দেখতে হবে। মাথা গরম করার কিছু নাই, নিচে আবারও আমরা এসব কথা বার্তা আরেকবার ধাপে ধাপে দেখবো।
তবে বুঝতে পারছি যে ঠিক কি কি কাজ আমাদের করতে হবে একটা ট্রেনিং সাইকেল সম্পন্ন করতে হলেঃ
এক) ট্রেনিং ডাটা টেবিল থেকে একটা সেট নিয়ে ইনপুট গুলো দেবো। এইজ গুলোতে কিছু র্যান্ডম ওয়েট (ভ্যালু) সেট করবো। কিছু গুন আর যোগ করে এবং স্পেশাল একটা ফাংশনের মাধ্যমে এর আউটপুট বের করবো।
দুই) এরর এর পরিমাণ বের করবো অর্থাৎ – এই ধাপের আউটপুট এবং আসলেই ট্রেনিং সেট মোতাবেক আসল আউটপুটের পার্থক্য দেখবো।
তিন) এরর এর গতবিধি মোতাবেক ওয়েটগুলোকে খুব অল্প পরিমাণে অ্যাডজাস্ট করবো
চার) উপরের তিনটি ধাপকে হাজার হাজার বার রিপিট করাবো
এভাবে সামনের দিকে ক্যালকুলেশন এগিয়ে নিয়ে (Forward Propogation) এরর সাথে তুলনা করে আবার পিছনে ফিরে এসে (Back Propogation) ভ্যালু (ইনপুট ভ্যালু না কিন্তু। ওয়েট বা আপনার সেট করা ভ্যালু) গুলোকে অ্যাডজাস্ট করে আবার আউটপুটের সাথে তুলনা করার যে চক্র তাকে বলে Back Propogation.
এক নাম্বার ধাপের কাজ করি – প্রত্যেকটা ইনপুটের সাথে প্রত্যেক লাইনের ওয়েট গুন করে যোগফল বের করার সূত্র হবে নিচের মতঃ

এর পর এই যোগফলকে 1 ও 0 এর মাঝা মাঝি রাখার জন্য গণিতের একটা স্পেশাল ফাংশন যার নাম Sigmid Function (এখানে এটাই আমাদের Activation Function. এরকম আরও আছে।) সেটাকে ব্যবহার করতে পারি। এই ফাংশনের কাজ হচ্ছে – একে ইনপুট হিসেবে যে মাণই দেয়া হোক না কেন, আউটপুট আসবে 1 থেকে শুনের মধ্যেই। এটাই তো দরকার 🙂 যাই হোক ফাংশনের ম্যাথেমেটিক্যাল রিপ্রেজেন্টেশন এবং গ্রাফটা দেখতে নিচের মত


তো আমরা আমাদের ওয়েটেড সাম কে এই ফাংশনের সাহায্যে 1 ও 0 এর মধ্যে এনে ফেলতে পারি। Sigmoid Function নিয়ে পড়ার জায়গা এটা না। দরকার হলে আলাদা করে দেখে ফিরে আসতে পারেন এই পোস্টে।
দ্বিতীয় ধাপে – এরর এর পরিমাণ বের করবো। এটা খুবি সহজ কাজ। প্রত্যেকটি ইনপুট কম্বিনেশনের জন্য আউটপুট থেকে এই নিউরাল নেটওয়ার্কের হিসেব করা আউটপুটকে বিয়োগ দিতে হবে।
তৃতীয় ধাপে – আমরা এররের উপর ভিত্তি করে ওয়েট অ্যাডজাস্ট করবো। কিন্তু কি পরিমাণে অ্যাডজাস্ট করবো? এক্ষেত্রেও আমরা একটা ফর্মুলা “Error Weighted Derivative” ব্যবহার করতে পারি। সূত্রটা দেখতে নিচের মতোঃ

সূত্রে বিভিন্ন ফ্যাক্টরের প্রয়োজনীয়তা নিয়ে প্রশ্ন আসতেই পারে। বিষয়টা সহজ – যেহেতু আমরা এরর সমানুপাতে ওয়েট অ্যাড জাস্ট করবো তাই এটাকে লাগছে। আবার সাথে আমরা ইনপুটকেও নিচ্ছি যাতে করে ইনপুট যদি 0 হয় তাহলে ওয়েট অ্যাডজাস্ট করবো না (ডান পাশে শুন্য আসবে)। আসলে ইনপুট শুন্য হলে অ্যাডজাস্ট করে লাভও নাই। ওই Edge এর মাণ এমনিতেই শূন্য আসবে। তিন নাম্বার ফ্যাক্টরটা গুরুত্বপূর্ণ যা কিনা একটি ভ্যালুর জন্য (এক্ষেত্রে আমাদের ক্যালকুলেট করা আউটপুট) প্রাপ্ত Sigmoid Curve এর Gradient (ঢাল)। সূত্রের ডান পাশে এই ফ্যাক্টর এর তাৎপর্য এরকম – Sigmoid Curve এর মাধ্যমে আমরা নিউরনের আউপুট হিসেবে করছি। যদি এই আউটপুটের মাণ খুব বেশি পজিটিভ বা খুব বেশি নেগেটিভ হয় তার মাণে নিউরনটি ট্রেনিং সেটের আউটপুট ভ্যালুর দিকে ঝুঁকতে খুবি আত্মবিশ্বাসী তথা ওয়েট অ্যাডজাস্ট করার খুব একটা দরকার নাই। অন্যদিকে আমরা Sigmoid Function এর গ্রাফ থেকে দেখতে পারি যে – ভ্যালু যত বেশি, এই Curve এর Gradient বা ঢাল তত কম। তাই এই ফ্যাক্টরকে ডান পাশে রাখলে এবং আউটপুট খুব বেশি পজিটিভ/নেগেটিভ আসলে এই ফ্যাক্টরের মাণও কম আসতেছে আর তাই বাম পাশে অ্যাডজাস্টের পরিমাণও কম হচ্ছে। বুদ্ধি 🙂
যাই হোক Sigmoid Curve এর Gradient বের করার সহজ সূত্র হচ্ছেঃ

অর্থাৎ Adjust weight by সমীকরণ দাড়ায়ঃ

ফিরে দেখা
এ অবস্থায় আমরা আমাদের

এখানে n এর মান 3 ধরলেই আমাদের সমস্যার সাথে উপরের ফিগারটি মিলে যাবে।
এই যে আমরা নিউরাল নেটওয়ার্ক এর কাহিনীকে ফলো করে সেরকম নীতিতে আমাদের বাস্তবের কিছু সমস্যা সমাধানের জন্য একটা পদ্ধতি নিয়ে চিন্তা করলাম সেটা তো আর খাতা কলমে করে কুলাবে না। এই কাজটা কম্পিউটার দিয়ে করালে খুব দ্রুত আমাদের উদ্দেশ্য পুড়ন হবে। আর সবাই জানে, কম্পিউটারকে দিয়ে ইচ্ছামত কামলা খাটুনি খাটিয়ে নেয়া যায়। শুধুমাত্র তাকে তার ভাষায় আদেশ দিতে হবে। এর নাম নাকি আবার কম্পিউটার প্রোগ্রাম। তো, কি আর করা, লিখে ফেলি; কম্পিউটার বোঝে এবং আমাদের লিখতে সহজ এমন একটা ভাষায় একটা প্রোগ্রাম, যার মাধ্যমে বস্তুতপক্ষে আমরা উপড়ে আলোচ্য কাজ গুলোকেই করবো।
যদি আপনার পাইথন প্রোগ্রামিং ল্যাঙ্গুয়েজে ভালো দখল থাকে তাহলে আপনার জন্য ডাটা সায়েন্স, মেশিন লার্নিং এবং ডিপ লার্নিং নিয়ে কাজ করা সহজ হয়ে যায়। আমরা নিচে একটা পূর্ণ প্রোগ্রাম দেখবো যার মাধ্যমে তিনটি ইনপুট ওয়ালা একটি সিঙ্গেল নিউরন তৈরি করা হয়েছে এবং সেই ইনপুট এইজ গুলোতে প্রথমে কিছু র্যান্ডোম ওয়েট সেট করা হয়েছে। এরপর ওই নিউরনে ট্রেনিং ডাটাসেট অর্থাৎ কিছু ইনপুট row এবং row সাপেক্ষে একটি করে আউটপুট দিয়ে দেয়া হয়েছে। Sigmoid Function ব্যবহার করে নিউরনের চিন্তা অনুযায়ী আউটপুট বের করা হয়েছে। সত্যিকারের আউপুট এবং নিউরনের হিসাব করে বের করা আউটপুটের তুলনা করে এরর চেক করা হয়েছে। ১০০০০ বার লুপ চালিয়ে (ট্রেনিং করিয়ে) উপড়ে আলোচ্য ওয়েট অ্যাডজাস্ট করার সূত্র দিয়ে প্রত্যেক লুপের মধ্যে একবার করে ওয়েট অ্যাডজাস্ট করা হয়েছে। সবশেষে একই নিউরনে নতুন একটি ডাটাসেট দিয়ে তার আউপুট জানতে চাওয়া হয়েছে। যদি সে আমাদের ধারনা করা আউপুটকেই আউটপুট হিসেবে দিতে পারে তাহলে বলা যায় যে, এই সিঙ্গেল নিউরন ওয়ালা নেটওয়ার্কটি ৪টি ট্রেনিং ডাটাসেট থেকেই প্যাটার্ন খুঁজে নিতে সফল হয়েছে এবং সেই প্যাটার্ন মোতাবেক নতুন ডাটা সেটের জন্য আউটপুট বলে দিতে পারছে।
Medium কমিউনিটির ব্লগার @miloharper এর gist থেকে ফর্ক করা প্রোগ্রামটি নিচে দেয়া হলঃ
from numpy import exp, array, random, dotclass NeuralNetwork():def __init__(self):# Seed the random number generator, so it generates the same numbers# every time the program runs.random.seed(1)# We model a single neuron, with 3 input connections and 1 output connection.# We assign random weights to a 3 x 1 matrix, with values in the range -1 to 1# and mean 0.self.synaptic_weights = 2 * random.random((3, 1)) - 1# The Sigmoid function, which describes an S shaped curve.# We pass the weighted sum of the inputs through this function to# normalise them between 0 and 1.def __sigmoid(self, x):return 1 / (1 + exp(-x))# The derivative of the Sigmoid function.# This is the gradient of the Sigmoid curve.# It indicates how confident we are about the existing weight.def __sigmoid_derivative(self, x):return x * (1 - x)# We train the neural network through a process of trial and error.# Adjusting the synaptic weights each time.def train(self, training_set_inputs, training_set_outputs, number_of_training_iterations):for iteration in range(number_of_training_iterations):# Pass the training set through our neural network (a single neuron).output = self.think(training_set_inputs)# print("\nOutput of the Above Function After Sigmoid Applied: \n",output)# Calculate the error (The difference between the desired output# and the predicted output).error = training_set_outputs - output# print("\nTraining Set Output Matrix: \n", training_set_outputs)# print("\nError: Training Set Output Matrix 4x1 - Above Matrix 4x1 \n", error)# Multiply the error by the input and again by the gradient of the Sigmoid curve.# This means less confident weights are adjusted more.# This means inputs, which are zero, do not cause changes to the weights.adjustment = dot(training_set_inputs.T, error * self.__sigmoid_derivative(output))# print("\nAdjustment Matrix: \n", adjustment)# Adjust the weights.self.synaptic_weights += adjustment# The neural network thinks.def think(self, inputs):dot_product = dot(inputs, self.synaptic_weights)# print("\nDot Product of Input Matrix and Weight Matrix: \n",dot_product)# Pass inputs through our neural network (our single neuron).return self.__sigmoid(dot_product)if __name__ == "__main__":#Intialise a single neuron neural network.neural_network = NeuralNetwork()print ("\n\nRandom starting synaptic weights: ")print (neural_network.synaptic_weights)# The training set. We have 4 examples, each consisting of 3 input values# and 1 output value.training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])training_set_outputs = array([[0, 1, 1, 0]]).T# Train the neural network using a training set.# Do it 10,000 times and make small adjustments each time.neural_network.train(training_set_inputs, training_set_outputs, 10000)print ("\nNew synaptic weights after training: ")print (neural_network.synaptic_weights)# Test the neural network with a new situation.print ("\nConsidering new situation [1, 0, 0] -> ?: ")print (neural_network.think(array([1, 0, 0])))
যদি প্রোগ্রামটির
if __name__ == “__main__”:
থেকে দেখা শুরু করেন তাহলে ধাপে ধাপে বুঝতে পারার কথা কিভাবে কোডের মাধ্যমে এই নিউরাল নেটওয়ার্ক তৈরি করা হয়েছে। আমি যথা সম্ভব আরেক্টূ সহজে ব্যাখ্যা করার চেষ্টা করছি। প্রোগ্রামের শুরুতেই numpy লাইব্রেরী যুক্ত করা হয়েছে যাতে করে খুব সহজে ম্যাট্রিক্স পদ্ধতিতে কিছু ক্যালকুলেশনের কাজ করা যায় কারণ ন্যাটিভ পাইথনে ম্যাট্রিক্স টাইপের কোন ডাটা স্ট্রাকচার নাই। অন্যদিকে নিউরাল নেটওয়ার্কের গঠন মোতাবেক ইনপুট এবং ওয়েট নিয়ে গুন/যোগ ইত্যাদি করার সময় ম্যাট্রিক্স স্টাইল ভালো উপায়।
যেমন, এর মাধ্যমে আমাদের ট্রেনিং ডাটাসেট গুলোকে খুব সহজে ম্যাট্রিক্স এর রূপ দিতে পারি নিচের মত করে।
training_set_inputs = array([[0, 0, 1], [1, 1, 1], [1, 0, 1], [0, 1, 1]])training_set_outputs = array([[0, 1, 1, 0]]).T
উল্লেখ্য – আউপুট কলামের ডাটা গুলোকে প্রথমে একটি 1×4 সাইজের ম্যাট্রিক্সে স্টোর করে তারপর ট্রান্সপোজ করে 4×1 সাইজে কনভার্ট করা হয়েছে যাতে ভিজুয়াল রিপ্রেজেন্টেশন মনে করা যেতে পারে এমন –
ইনপুট ম্যাট্রিক্স –

আউটপুট ম্যাট্রিক্স –

এভাবে ডাটা স্টোর করার ফলে আমরা একবারে পুরো ইনপুট ডাটা টেবিলকে আমাদের নিউরাল নেটওয়ার্কে ইনপুট দিয়ে খুব সহজে প্রত্যেকটা ইনপুট সেট (এ ক্ষেত্রে এক একটা row) -এর সাথে ওয়েট সেট ডট গুন করে একবারেই একটা আউটপুট ম্যাট্রিক্স পেয়ে যেতে পারি যেখানে ৪টা ইনপুট সেটের (৪টা row) জন্যই ৪টা আউটপুট ভ্যালু থাকবে 4×1 সাইজে। এতে করে প্রত্যেকটা Epoch এ পুরো অপারেশনটা একবার পুরোপুরি শেষ হবে। এছাড়াও এই লাইব্রেরী থেকে আরও কিছু ফাংশনের সাহায্য নিয়ে কিছু অপারেশনকে সহজ বোধ্য করা হয়েছে।
স্ক্রিপ্ট হিসেবে এই প্রোগ্রামকে রান করালে ৫৮ নাম্বার লাইনে থেকেই এই প্রোগ্রামটির কার্যক্রম শুরু হয়। শুরুতেই NeuralNetwork ক্লাসের একটি অবজেক্ট তৈরি করা হয়েছে যার মাধ্যমে ফ্রেশ একটি নিউরাল নেটওয়ার্ক তৈরি করা যায়। তো, দেখে আসি সেই ক্লাসের চেহারাটা। ৪নাম্বার লাইনে ক্লাসকে ডিফাইন করা হয়েছে। এর কন্সট্রাক্টরের মধ্যেই আমাদের সেই বহুল আলোচিত র্যান্ডম ওয়েট তিনটি তৈরি করা হচ্ছে।
যেহেতু আমাদের নিউরনের ৩টি ইনপুট তাই তিনটি ইনপুটের জন্য তিনটি ওয়েট নির্ধারণ করে ইনপুট গুলোর সাথে গুন করতে কাজ করার সুবিধার্থে 3×1 সাইজের একটি ম্যাট্রিক্স নেয়া/তৈরি করা হয়েছে synaptic_weights নামে। প্রথমবার অর্থাৎ ওয়েট অ্যাডজাস্ট হবার আগে এর চেহারা হতে পারে এমন –

নোটঃ আপনি প্রোগ্রাম রান করানোর সময় আলাদা ভ্যালু পেতে পারেন কারন র্যান্ডমলি জেনারেটেড।
এই ক্লাসের মধ্যে আরেকটি মেথড বানানো হয়েছে যার মাধ্যমে Sigmoid Function ব্যবহার করে ভ্যালু নরমালাইজেশন অর্থাৎ আউটপুট ভ্যালুকে 1 ও 0 মাঝে রাখা হয়। তার নিচেই আছে আরেকটি ফাংশন যার মাধ্যমে আমরা যেকোনো একটি আউটপুট ভ্যালুর জন্য Sigmoid Curve এর Gradient বের করতে পারি। এটি কাজে লাগে ওয়েট অ্যাডজাস্টমেন্ট এর মান ঠিক করতে। উপড়ে এটা নিয়ে আলোচনা করা হয়েছে। এরপরেই আছে গুরুত্বপূর্ণ train ফাংশন যার মাধ্যমে আমাদের নিউরাল নেটওয়ার্কটি প্যাটার্ন চেনা শিখে নেয়।
প্রথমেই একটি লুপ চালানো হয়েছে যার মাণ নির্ধারণ করবে আপনি যতগুলো Epoch বা ট্রেনিং সাইকেল করাতে চান তার উপর। এখানে ১০০০০ বার Forward এবং Back Propogaion করাতে বলা হচ্ছে। ১০০০০ লুপের প্রথম iteration -এ লুপের মধ্যের প্রথম কাজ হচ্ছে think ফাংশনের ব্যবহার করে এবং র্যান্ডম ওয়েটের উপর ভিত্তি করে একটা আউটপুট ম্যাট্রিক্স তৈরি করা যার মধ্যে নিউরনের হিসাব মোতাবেক পাওয়া আউটপুট গুলো থাকবে। এটির ডাইমেনশন হবে 4×1 অর্থাৎ ৪সেট ইনপুট ডাটার (৪টি row) জন্য ৪টি আউটপুট তথা নিচের মত একটি ম্যাট্রিক্স।

যদি think ফাংশনের কোড দেখি তাহলে দেখতে পারবো যে এখানে 4×3 সাইজের পুরো ইনপুট ডাটা টেবিল যাকে ম্যাট্রিক্সে কনভার্ট করা হয়েছে, তার সাথে 3×1 সাইজের ওয়েট ম্যাট্রিক্সের গুন করা হয়েছে। এতে করে বস্তুত প্রত্যেকটি ইনপুট সেট যেমন প্রথমত 0 0 1 এর সাথে তিনটি ওয়েট

– কে ডট গুন করা হয়েছে। আবার দ্বিতীয় ইনপুট সেট 1 1 1 এর সাথে একই ওয়েট ম্যাট্রিক্স

– কে ডট গুন করা হয়েছে। অর্থাৎ এভাবে সব গুলো ইনপুট কম্বিনেশনের সাথেই একবার করে ওই তিনটি ওয়েট ডট গুন করা হয়েছে। এভাবে যে আউটপুট ম্যাট্রিক্স পাওয়া যায় সেটাও কিন্তু 4×1 সাইজের ম্যাট্রিক্স। সেই ম্যাট্রিক্সকে একবার করে __sigmoid মধ্যে চালিয়ে নিয়ে ভ্যালু গুলোকে নরমালাইজ করা হয়েছে। তো, সব গুলো ইনপুট কম্বিনেশন এর সাথে ওয়েট গুলোর ডট গুন (গুন ও গুন গুলোর যোগ) করে নরমালাইজ করার পর নিচের মত একটি ম্যাট্রিক্স পাওয়া যাবে,

এই ম্যাট্রিক্সকে output ভ্যারিয়েবলে স্টোর করা হচ্ছে। এরপর এরর হিসাবের জন্য আমরা 4×1 সাইজের ট্রেনিং আউটপুট ম্যাট্রিক্স তথা,

থেকে উপরের 4×1 সাইজের output ম্যাট্রিক্স বিয়োগ করে নিচের মত একটি ম্যাট্রিক্স পেতে পারি,

এরপর এই এরর ম্যাট্রিক্স কে সাথে নিয়ে ইনপুট ডাটা সেট ম্যাট্রিক্স এবং Sigmoid Derivative কে কাজে লাগিয়ে অ্যাডজাস্টমেন্ট এর পরিমাণ বের করা হচ্ছে। এই অ্যাডজাস্টমেন্ট ম্যাট্রিক্সটিও ওয়েট ম্যাট্রিক্স এর মত 3×1 সাইজের। আর তাই train ফাংশনের শেষ লাইনে মুল ওয়েট ম্যাট্রিক্স এর সাথে এই অ্যাডজাস্ট ম্যাট্রিক্স যোগ করে ওয়েট ম্যাট্রিক্সে পরিবর্তন করে নেয়া হচ্ছে।
NeuralNetwork ক্লাসের কোড বোঝার পর আবারও ফিরে আসি পাইথন প্রোগ্রামের রেগুলার এক্সিকিউশন স্টেজে। ক্লাস ইনিসিয়ালাইজ করার পর পর্যবেক্ষণের স্বার্থে প্রথমবার সেট হওয়া র্যান্ডম ওয়েট ম্যাট্রিক্সকে প্রিন্ট করে দেখা হচ্ছে ওয়েট গুলো কি কি –

এরপর আমাদের ডাটা টেবিল থেকে ইনপুট এবং আউটপুট গুলোকে গুছিয়ে 4×3 সাইজের ট্রেনিং সেট ইনপুট এবং 4×1 সাইজের ট্রেনিং সেট আউটপুট ম্যাট্রিক্স বানিয়ে নেয়া হচ্ছে। এরপরেই উপড়ে আলোচ্য NeuralNetwork ক্লাসের অবজেক্ট neural_network –র মেথড, train এর মধ্যে এগুলো পাঠিয়ে দেয়া হচ্ছে। ১০০০০ বার চক্কর দেয়ার পর অপ্টিমাইজ ওয়েট ম্যাট্রিক্সটি কেমন রূপ ধারণ করলো সেটাও প্রিন্ট করা হচ্ছে।

সবশেষে, একটি নতুন ইনপুট সেট কে think ফাংশনে পাঠিয়ে আমাদের নিউরাল নেটওয়ার্ক এর কাছে আউটপুট জানতে চাওয়া হচ্ছে। এবার think ফাংশন, এই ইনপুট ডাটা সেট তথা 1×3 ম্যাট্রিক্সের সাথে আপডেটেড 3×1 ওয়েট ম্যাট্রিক্স এর ডট গুন করে Sigmoid অ্যাপ্লাই করে নরমালাইজ ডাটা তথা 1 থেকে 0 মধ্যের একটা ভ্যালুকে প্রিন্ট করে 1×1 সাইজের ম্যাট্রিক্স আকারে যেটা কিনা আমাদের নিউরাল নেটওয়ার্কের প্রেডিকশন।
আর সেটি হচ্ছে,

অর্থাৎ আমাদের নিউরাল নেটওয়ার্ক ভালোমতই ইনপুট ডাটা থেকে প্যাটার্ন খুঁজে তার উপর ভিত্তি করে পরবর্তী নতুন ইনপুট ডাটার জন্য তার আউটপুট কি হবে সেটা বলে দিতে পারছে।
আপনি যদি প্রথম iteration এর সব গুলো কাজের ধাপকে লগ করে দেখতে চান যে একটা ট্রেনিং লুপে কি কি ঘটছে তাহলে ৭৩ নাম্বার লাইনে 10000 এর পরিবর্তে 1 পাঠিয়ে এবং পুরো প্রোগ্রামের মধ্যে থাকা কমেন্ট করা প্রিন্ট স্টেটমেন্ট গুলোকে অ্যাক্টিভ করে দেখতে পারেন নিচের মত আউটপুট এবং সেগুলো ম্যানুয়ালি বিচার করতে পারেন।

পুরো ১০০০০ বার লুপের পর অর্থাৎ ট্রেনিং শেষের পর ওয়েটেড ম্যাট্রিক্স এর ফাইনাল রূপ আসবে নিচের মত,

0 comments:
Post a Comment