আগেই বলা হয়েছে, TensorFlow দিয়ে কাজ করতে হলে প্রথমেই একটি পুরনাঙ্গ কম্পিউটেশনাল গ্রাফ তৈরি করতে হয়। এরপর পুরো গ্রাফকে এক সাথে এক্সিকিউট করা যায়। এতে করে পাইথনে আলাদা আলাদা করে ক্যালকুলেশন গুলো লিখে এক্সিকিউট করালে যেমন টাইম বা অন্য কমপ্লেক্সিটি হতে পারতো, তার চেয়ে অনেক ইফেক্টিভলি ক্যালকুলেশন গুলো হয়। গ্রাফে আরও সুবিধা হচ্ছে স্বয়ংক্রিয় ভাবে গ্র্যাডিয়েন্ট ডিসেন্ট বের করা যায় যাতে করে মডেলের ভ্যারিয়েবল (ওয়েট, বায়াস) গুলোকে অপ্টিমাইজ করা যায় সহজেই। যেহেতু পুরো গ্রাফ জুড়ে অনেক গুলো কম্পিউটেশন থাকে এতে করে ডেরিভ্যাটিভ এর চেইন রুল এর মাধ্যমে খুব দ্রুত এবং সহজে পুরো গ্রাফের গ্র্যাডিয়েন্ট ডিসেন্ট বের করা যায়।
এ অবস্থায় আমরা আবার জানবো - একটা TensorFlow গ্রাফ মূলত কি কি নিয়ে গঠিতঃ
১) প্লেসহোল্ডার ভ্যারিয়েবল - যার মাধ্যমে গ্রাফে ইনপুট দেয়া হয়
২) মডেল ভ্যারিয়েবল - ওয়েট, বায়াস ইত্যাদি; মূলত ট্রেনিং করিয়ে এগুলোকে অপ্টিমাইজ করেই একটা ইফিসিয়েন্ট মডেল তৈরি করা হয়
৩) মডেল - সহজ কথায় একটি ম্যাথেম্যাটিক্যাল ফাংশন যেখানে প্লেসহোল্ডারের মাধ্যমে ইনপুট দিয়ে এবং মডেল ভ্যারিয়েবলের সমন্বয়ে আউপুটপুট পাওয়া যায়
৪) Cost - এই মানের উপর ভিত্তি করে মডেল ভ্যারিয়েবল গুলোর অপটিমাইজেশনের দিক নির্দেশনা দেয়া হয়
৫) অপটিমাইজেশন মেথড - এই মেথড মূলত Cost কে মাথায় রেখে মডেল ভ্যারিয়েবল গুলোকে আপডেট করে।
প্লেস হোল্ডার তৈরিঃ
আগেও একবার বলা হয়েছে - এর মাধ্যমে গ্রাফে ইনপুট দেয়া হয়। যেমন, প্লেসহোল্ডারকে মেনে এক এক বার এক এক সেট ইনপুট দেয়া যাবে গ্রাফে। অনেকে বলেন গ্রাফকে ফিড করানো। তো, আমরা আসলে গ্রাফে কি ইনপুট দিবো? ইমেজ বা হাতের লেখা ওয়ালা ফটো গুলোকে, তাই তো? আমরা প্রথমবার হয়ত ১০০টা ইমেজ গ্রাফে ইনপুট দিলাম। পরেরবার আরও ২০০ দিলাম। তাই, ইমেজ ইনপুট দেয়ার জন্য একটা প্লেসহোল্ডার ভ্যারিয়েবল থাকলে ভালো। এই প্লেসহোল্ডার হবে Tensor টাইপের। Tensor মানে? মাল্টিডাইমেনশনাল ভেক্টর বা ম্যাট্রিক্স :) Tensor এর ডাটাটাইপ হবে float32. আর এর সেইপ হবে [None, img_size_flat] None মানে হচ্ছে এই টেনসরটি যেকোনো সংখ্যক ইমেজ নিতে পারবে যে ইমেজ গুলো কিনা এক একটি ফ্ল্যাট অ্যারে অর্থাৎ সবগুলো পিক্সেলের ফ্ল্যাট ভেক্টর তথা আমাদের একটু আগের স্টেটমেন্ট অনুযায়ী img_size_flat. অর্থাৎ স্টেটমেন্টটি হবে,
# Cell 10
x = tf.placeholder(tf.float32, [None, img_size_flat])
আরেকবার বলি - যেমন উপরের স্টেটমেন্ট এর প্লেসহোল্ডারে যদি আমরা যেকোনো সময় মাত্র দুটি 28x28 সাইজের ফটোকে ইনপুট হিসেবে দিয়ে গ্রাফ এক্সিকিউট করি তাহলে উপরের স্টেটমেন্টার অভ্যন্তরীণ চেহারা হবে এরকম, x = tf.placeholder(tf.float32, [2, 784]) এবং ডাটার চেহারা হবে [[p00, p01 .... p0783], [p10, p11 .... p1783]]. pxx হচ্ছে পিক্সেল ভ্যালু।
এবার আরও একটা প্লেসহোল্ডার নেবো যেখানে সময় মত ইনপুট দেবো, একটু আগে ইনপুট দেয়া ইমেজ গুলোর সঠিক লেবেল গুলোকে। এই প্লেসহোল্ডার ভ্যারিয়েবলের সেইপ হবে [None, num_classes] টাইপের? কেন? None মানে যেকোনো সংখ্যক লেবেল সেট নিতে পারবে আর প্রত্যেকটা লেবেল সেট হবে num_classes অর্থাৎ 10 লেন্থ এর ভেক্টর। আগের প্লেসহোল্ডার ভ্যারিয়েবলটির নাম ছিল x এবং এই প্লেসহোল্ডার ভ্যারিয়েবলটির নাম y_true.
# Cell 11
y_true = tf.placeholder(tf.float32, [None, num_classes])
এবার আমাদের আরেকটি প্লেসহোল্ডার দরকার পরবে। এর মধ্যে দেয়া হবে x প্লেসহোল্ডারের প্রত্যেকটি ইমেজের জন্য এর ট্রু ক্লাস। অর্থাৎ এটার ধরন হবে ইন্টিজার টাইপের। কারন ট্রু ক্লাস গুলো তো (0,1,2,3 ... 9) এরকম. এর সেইপ হবে [None] অর্থাৎ, এই প্লেসহোল্ডারটি একটি ওয়ান ডাইমেনশনাল ভেক্টর কিন্তু যার লেন্থ হতে পারে যেকোনো সংখ্যক। অর্থাৎ একটি ইমেজের ক্ষেত্রে এটি শুধুমাত্র ওই ইমেজেটির ট্রু ক্লাস/লেবেল হোল্ড করবে আবার ৫০টা ইমেজের জন্য ৫০টা ট্রু ক্লাস হোল্ড করবে। এই আর কি,
# Cell 12
y_true_cls = tf.placeholder(tf.int64, [None])
এখন পর্যন্ত x, y_true এবং y_true_cls এই তিনটা প্লেসহোল্ডার ভ্যারিয়েবলকে মাথার মধ্যে পরিষ্কার ভাবে স্টোর করুন। দরকার হলে এই সেকশনের শুরু থেকে আরেকবার পরে আসুন।
এবার আসি মডেল ভ্যারিয়েবলেঃ
এই পোস্টের একদম শুরুতে যে নিউরাল নেটওয়ার্কের উদাহরণ দেয়া হয়েছে সেটা মনে আছে? ওখানে কিন্তু আমরা ট্রেইন করে করে কিছু ওয়েট ঠিক করেছিলাম যেগুলোর উপর ভিত্তি করেই পরবর্তীতে ওই মডেল নতুন ইনপুট নিয়ে ওয়েটের সঙ্গে নানা রকম ক্যালকুলেশন করে আউটপুট দিত। এই নিউরাল নেটওয়ার্কে শুধু নতুন যুক্ত হয়েছে বায়াস। অর্থাৎ ওয়েট থাকে Edge -এ আর বায়াস থাকে Node -এ বা নিউরনে।
যাই হোক, এই ওয়েট আর বায়াস-ই কিন্তু মুল ভ্যারিয়েবল, যেগুলোর মান এই মডেল অ্যাডজাস্ট করে নেয় ট্রেনিং করার সময়। আর এই দুটো ভ্যারিয়েবলকেই মডেল ভ্যারিয়েবল বলা হয়ে থাকে। এখন আমরা আমাদের মডেলের এই দুটো ভ্যারিয়েবলকে ডিফাইন করবো। এগুলো কিন্তু প্লেসহোল্ডার নয় যে এগুলোর মান বাইরে থেকে ইনপুট হবে। বরং এগুলো নর্মাল ভ্যারিয়েবল যেগুলো কিনা ট্রেনিং চলাকালীন অবস্থায় ক্যালকুলেশনের মধ্যে সময়ে সময়ে অ্যাডজাস্ট বা অপ্টিমাইজড হবে।
শুরুতেই আমরা ওয়েট ভ্যারিয়েবল ডিফাইন করি। একদম শুরুর উদাহরণে যেমন আমরা কিছু ওয়েট ডিফাইন করেছিলাম র্যান্ডোম ভ্যালু দিয়ে এবং তারপর ট্রেইন শুরু করেছিলাম। এবার আমরা সবগুলো ওয়েটের মান ধরব 0. ভয়ের কিছু নাই, ট্রেনিং শুরু হওয়া মাত্রই এগুলো বদলে ঠিক ঠাক ভ্যালুর দিকেই আগাবে। যা হোক, এদের সেইপ হবে [img_size_flat, num_classes]।
# Cell 13
weights = tf.Variable(tf.zeros([img_size_flat, num_classes]))
কেন এরকম হল? আবার সেই প্রথম উদাহরণের কথাই আনা যায় - ওখানে যেমন এক পাশে তিনটা ইনপুট নিউরন ছিল এবং আউটপুট লেয়ারে একটা নিউরন ছিল। আর আমাদের দরকার হয়েছিল 3x1 ওয়েট ম্যাট্রিক্স। ঠিক এই মডেলও যেহেতু লিনিয়ার মডেল (Accuracy নিয়ে মাথা ঘামাচ্ছি না আমরা, এবং বলছি না যে ক্লাসিফিকেশনের জন্য এটা ভালো কোন মডেল) আর এর প্রথম (ইনপুট) লেয়ারে 784 টা নিউরন আছে এবং আউটপুট লেয়ারে 10 টা নিউরন আছে তাই এর সেইপ এরকম। পরিষ্কার? :)
এ অবস্থায় আমার মনে হয় আমাদের মডেলটার একটা ভিজুয়ালাইজেশন দরকার। নিচে দিয়ে দিলাম,
এবার ডিফাইন করি bias এর জন্য ভ্যারিয়েবল। আগেও বলা হয়েছে, বায়াস থাকে নোডে বা নিউরনে, অর্থাৎ যখন একটি নিউরনের জন্য ওয়েট এবং এইজের ক্যালকুলেশন শেষ হয়ে নোডে জমা হয় তখন এর সাথে যোগ হয় bias. তো আমাদের ইনপুট লেয়ারে একগাদা নিউরন থাকলেও আউটপুট লেয়ারে কিন্তু ১০টাই নিউরন। তাই এই ডাটা অবজেক্ট (টেনসর বা ভেক্টর) এর সেইপ হবে [num_classes]। আর স্টেটমেন্ট হবে নিচের মত,
# Cell 14
biases = tf.Variable(tf.zeros([num_classes]))