(খসড়া)
নিউরাল নেটওয়ার্কে কিভাবে ডাটা রিপ্রেজেন্টেশন হয় সেটা হাতেকলমে দেখলে ব্যাপারটা আরো পরিস্কার হবে। “শূন্য থেকে পাইথন মেশিন লার্নিং” বইটাতে এই ব্যাপারে ধারণা দিলেও সেটাকে একটু অন্যভাবে এখানে আলাপ করতে চাচ্ছি। নিউরাল নেটওয়ার্কে যখন ডাটাকে স্টোর করতে গিয়ে যে অনেক ডাইমেনশনের ‘নামপাই’ অথবা ‘টেন্সরফ্লো’তে n সংখ্যক অ্যারে ব্যবহার করছি সেটাকে আমরা ‘টেন্সর’ বলতে পারি। এই ‘টেন্সর’ হচ্ছে ডিপ লার্নিং এর একটা বেসিক অংশ যাকে গুগল ‘টেন্সর’ এর ‘ফ্লো’ মানে টেন্সরের গতিধারা দিয়ে 'টেন্সরফ্লো' বলে একটা ডিপ লার্নিং লাইব্রেরি তৈরি করেছে। আবারো বলছি, টেন্সর এর ফ্লো। টেন্সরফ্লো এবং নামপাই এর ভেতরের অপারেশন কিভাবে এই টেন্সর দিয়ে হচ্ছে সেটাই দেখাবো এখানে।
আমাদের সবচেয়ে ছোট ডাটা কন্টেইনার হচ্ছে এই ‘টেন্সর’ যা প্রায় সব সময় সংখ্যা নিয়ে কাজ করে। কম্পিউটার তো আর সংখ্যা ছাড়া কিছু চেনে না। কেরাস এর জনক ‘ফ্রাঁসোয়া শোলে’ এর একটা অসাধারণ বই আছে এই ধারণাগুলোকে নিয়ে। বর্তমান ‘টেন্সরফ্লো’ এর নতুন ফিচারগুলো এই বইটাতে কাভার না করলেও অনেক বেসিক জিনিস জানতে আমি প্রায় ফেরত যাই ওই “ডিপ লার্নিং উইথ পাইথন” বইটাতে। কিছুটা পুরনো তবে এখনো কাজে লাগে। সেখান থেকে কিছু ধারনা নিয়েছি এখানে।
‘নামপাই’ এবং ‘টেন্সরফ্লো’ এর ‘টেন্সর’ প্রায় একই জিনিস। দুটোই N ডাইমেনশন অ্যারে লাইব্রেরি। নামপাই স্বয়ংক্রিয়ভাবে ডেরিভেটিভ কম্পিউট করতে পারে না। এর পাশাপাশি জিপিইউ ব্যবহার করতে পারে না। তাছাড়া দুটোর কাজ প্রায় একই। যেদুটো কাজ পারছেনা নামপাই, সেগুলো আমাদের কাজে এগুলো এমুহুর্তে লাগবে না। এছাড়া ‘টেন্সরফ্লো’তে নামপাই বলে আলাদা মেথড আছে যা দেখাবো সামনে।
যেই টেন্সরে একটা মাত্র সংখ্যা থাকে সেটাকে আমরা স্কেলার টেন্সর বলতে পারি। একে ০ ডাইমেনশন টেন্সর বলা হচ্ছে যেখানে ০ এক্সিস মানে ০ দিকে এর ডাইমেনশন আছে। আমরা নামপাই টেন্সরে এক্সিস দেখতে পারি ndim অ্যাট্রিবিউট দিয়ে। একটা টেন্সরে এক্সিসের সংখ্যাকে আমরা অনেক সময় রেঙ্ক “rank” বলি।
import numpy as np
ক = np.array(12)
ক
array(12)
ক.ndim
0
সংখ্যার অ্যারেকে আমরা ভেক্টর বা ১ ডাইমেনশন টেন্সর বলি। এখানে আমরা চারটা সংখ্যার অ্যারেকে নিয়ে একটা এক ডাইমেনশনের ‘টেন্সর’ তৈরি করেছি।
খ = np.array([12, 3, 6, 14])
খ
array([12, 3, 6, 14])
খ.ndim
1
আগে আমরা যেই ভেক্টর নিয়ে আলাপ করেছিলাম সেই ভেক্টরের অ্যারেকে আমরা ম্যাট্রিক্স বলতে পারি। এটা দুই ডাইমেনশন এর ‘টেন্সর’। একটা ম্যাট্রিক্সে দুটো এক্সিস থাকে যাকে আমরা ‘সারি’ এবং ‘কলাম’ দিয়ে ডিফাইন করতে পারি। নিচের উদাহরণটা দেখুন, এখানে প্রথম এক্সিস হচ্ছে ‘সারি’ আর পরের এক্সিসটা হচ্ছে ‘কলাম’।
গ = np.array([[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]])
গ
array([[ 5, 78, 2, 34, 0], [ 6, 79, 3, 35, 1], [ 7, 80, 4, 36, 2]])
গ.ndim
2
টেন্সরফ্লোতে সবচেয়ে ছোট অপারেশন শুরু করা যায় কয়েকটা কনস্ট্যান্ট এবং ভ্যারিয়েবল তৈরি করে। সেটা আমরা করবো tf.constant এবং tf.Variable ফাংশন কল করে যাতে সেটা অ্যারে বানাতে পারে।
# টেন্সরফ্লো ২.x সিলেক্ট করি
%tensorflow_version 2.x
import tensorflow as tf
# একটা কনস্ট্যান্ট টেন্সর বানাই, যেটা পাল্টাবে না
ঘ = tf.constant([[3, 2],
[5, 2]])
# ভ্যারিয়েবল টেন্সর, যেটা পাল্টাবে, মানে .Variable
ঙ = tf.Variable([[3, 2],
[5, 2],
[5, 2]])
# tf.shape(ঘ)
# আমরা শেপ দেখি
tf.shape(ঙ)
TensorFlow 2.x selected.
<tf.Tensor: shape=(2,), dtype=int32, numpy=array([3, 2], dtype=int32)>
# আরেকটা উদাহরণ
চ = tf.constant([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])
print(চ.get_shape())
print(চ.shape.dims)
(2, 3) [Dimension(2), Dimension(3)]
যেহেতু ইগার একজিকিউশন যেহেতু ডিফল্ট হিসেবে চালু থাকে, সেকারণে .numpy()কে টেন্সর অবজেক্টে কল করলেই সেটা পাওয়া যাবে। তারা একই মেমরি শেয়ার করে। একটা পাল্টালে আরেকটা পাল্টাবে।
ল = tf.constant([[1, 2], [3, 4]])
র = tf.add(ল, 1)
ল.numpy()
array([[1, 2], [3, 4]], dtype=int32)
র.numpy()
array([[2, 3], [4, 5]], dtype=int32)
tf.multiply(ল, র).numpy()
array([[ 2, 6], [12, 20]], dtype=int32)
আমরা যখন এরকম কয়েকটা ম্যাট্রিক্সকে আগের মতো একটা অ্যারেতে ফেলতে চাই তখন সেটা তিন ডাইমেনশনের টেন্সর হয়ে যায়। এ ধরনের ছবিগুলো আমরা দেখেছিলাম “শূন্য থেকে পাইথন মেশিন লার্নিং” বইটাতে। এর এক্সিস তিনটা। এভাবে আমরা চার ডাইমেনশনের টেন্সর তৈরি করতে পারি। বিশেষ করে আমাদের ‘কম্পিউটার ভিশন’ রিলেটেড সমস্যাগুলো যা ছবি নিয়ে কাজ করে - সেগুলো চার ডাইমেনশন এর হয়ে থাকে। ডিপ লার্নিং এ আমরা সাধারণত: শূন্য থেকে চার ডাইমেনশন নিয়ে কথা বলব। যখন সেটা ভিডিও হবে তখন সেটা পাঁচ ডাইমেনশনে চলে যাবে।
ছ = np.array([[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]],
[[5, 78, 2, 34, 0],
[6, 79, 3, 35, 1],
[7, 80, 4, 36, 2]]])
ছ
array([[[ 5, 78, 2, 34, 0], [ 6, 79, 3, 35, 1], [ 7, 80, 4, 36, 2]], [[ 5, 78, 2, 34, 0], [ 6, 79, 3, 35, 1], [ 7, 80, 4, 36, 2]], [[ 5, 78, 2, 34, 0], [ 6, 79, 3, 35, 1], [ 7, 80, 4, 36, 2]]])
ছ.ndim
3
এটা ইন্টিজার দিয়ে তৈরি করা একটা “টুপল” (tuple) যা বলে দেয় একটা টেন্সরের প্রতিটা এক্সিসে কত ডাইমেনশন আছে। আগের উদাহরণগুলোতে ভেক্টরে শেপ ছিল (৫, ), ম্যাট্রিক্স উদাহরণে শেপ (৩, ৫) পাশাপাশি তিন ডাইমেনশনের এর টেন্সরে শেপ ছিল (৩,৩,৫) আর স্কেলার (), মানে খালি শেপ।
(১) ভেক্টর ডাটা - (samples, features)
(২) টাইমসিরিজ/ সিকোয়েন্স ডাটা ৩D - (samples, timesteps, features)
(৩) ইমেজ ডাটা, যেমন এমনিস্ট ৪D - (samples, height, width, channels) অথবা (samples, channels, height, width)
(৪) ভিডিও ডাটা ৫D - (samples, frames, height, width, channels) অথবা (samples, frames, channels, height, width)
সাধারণতঃ ইমেজে তিনটা ডাইমেনশন হয়, দৈর্ঘ্য, প্রস্থ এবং কালার চ্যানেল। আমাদের এমনিস্ট ডাটাসেট নিয়ে কাজ করতে গেলে একটা মজার জিনিস হয়। যেহেতু এটা গ্রে-স্কেল এর মানে হচ্ছে কালার চ্যানেল একটা দিয়েই হয়ে যায়। এর মানে ২D টেন্সরে হয়ে যাবার কথা। তবে কনভেনশন অনুযায়ী ইমেজ যেহেতু ৩D, সেখানে ১ ডাইমেনশন কালার চ্যানেল ব্যবহার হবে গ্রে-স্কেল ইমেজের জন্য। কালারের জন্য ৩।
ধরুন, একটা ব্যাচে ১২৮টা ২৮ × ২৮ পিক্সেলের গ্রে-স্কেল ইমেজ স্টোর করা যাবে যেই টেন্সরে তার শেপ (১২৮, ২৮, ২৮, ১) অথবা কালারের জন্য শেপ গিয়ে দাড়াবে (১২৮, ২৮, ২৮, ৩)তে।
চিত্রঃ ৪D ইমেজ ডাটা টেন্সর এর উদাহরণ
# টেন্সর শেপকে পাল্টে অন্য শেপে আনা, লাগবে এমনিস্ট ডাটাসেটে
টেন্সর = tf.constant([[3, 2],
[5, 2],
[9, 5],
[1, 3]])
# টেন্সরকে সারি, কলাম ধরে পাল্টাই: shape = [rows, columns]
শেপ_টেন্সর = tf.reshape(tensor = টেন্সর,
shape = [1, 8])
print(('টেন্সর রিশেপিং এর আগে:\n{0}').format(
টেন্সর.numpy()
))
print(('\nটেন্সর রিশেপিং এর পরে:\n{0}').format(
শেপ_টেন্সর.numpy()
))
টেন্সর রিশেপিং এর আগে: [[3 2] [5 2] [9 5] [1 3]] টেন্সর রিশেপিং এর পরে: [[3 2 5 2 9 5 1 3]]
মেশিন/ডিপ লার্নিং এর হৃদয়ে আছে টেন্সরগুলোর ম্যাট্রিক্স মাল্টিপ্লিকেশন, সেটার একটা উদাহরণ দেখাচ্ছি দুটো ম্যাট্রিক্স (এর মধ্যে একটা ভেক্টর) এবং tf.matmul ফাংশন ব্যবহার করে।
# উদাহরণের জন্য ম্যাট্রিক্স "ঝ"
ঝ = tf.constant([[3, 7],
[1, 9]])
# উদাহরণের জন্য ভেক্টর "ঞ"
ঞ = tf.constant([[5],
[2]])
# ম্যাট্রিক্স মাল্টিপ্লিকেশন "ঝ" এবং "ঞ"
ঝঞ = tf.matmul(ঝ, ঞ)
print(('"ঝ" এবং "ঞ" এর ম্যাট্রিক্স মাল্টিপ্লিকেশন দিয়ে নতুন টেন্সর:\n{0}').format(
ঝঞ
))
"ঝ" এবং "ঞ" এর ম্যাট্রিক্স মাল্টিপ্লিকেশন দিয়ে নতুন টেন্সর: [[29] [23]]
এমনিস্ট ডাটাসেটে ট্রেনিং_ইমেজ, ট্রেনিং_লেবেল, টেস্ট_ইমেজ, টেস্ট_লেবেল যে চারটা অংশ আছে সেটার শেপ এবং এক্সিস দেখি এখন। শুরুতে একটা দেখলেই হবে।
import tensorflow as tf
from tensorflow import keras
ফ্যাশন_এমনিস্ট = tf.keras.datasets.fashion_mnist
(ট্রেনিং_ইমেজ, ট্রেনিং_লেবেল), (টেস্ট_ইমেজ, টেস্ট_লেবেল) = ফ্যাশন_এমনিস্ট.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz 32768/29515 [=================================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz 26427392/26421880 [==============================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz 8192/5148 [===============================================] - 0s 0us/step Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz 4423680/4422102 [==============================] - 0s 0us/step
print(ট্রেনিং_ইমেজ.ndim)
3
তিনটা ডাইমেনশন: (ইমেজ সংখ্যা, দৈর্ঘ্য় মানে পিক্সেল সংখ্যা, প্রস্থ মানে পিক্সেল সংখ্যা)
print(ট্রেনিং_ইমেজ.shape)
(60000, 28, 28)
print(টেস্ট_ইমেজ.shape)
(10000, 28, 28)
শুরু এবং শেষ ইনডেক্স দিয়ে স্লাইসের ভেতরে আগের মতো দেখা। ১০ থেকে ১০০তম, তবে ১০০তম ইমেজ বাদে। আমাদের শেপের অ্যারে কি হতে পারে?
পরিক্ষা_ইমেজ১ = ট্রেনিং_ইমেজ[10:100]
print(পরিক্ষা_ইমেজ১.shape)
(90, 28, 28)
পরিক্ষা_ইমেজ২ = ট্রেনিং_ইমেজ[10:100, :, :]
পরিক্ষা_ইমেজ২.shape
(90, 28, 28)
পরিক্ষা_ইমেজ৩ = ট্রেনিং_ইমেজ[10:100, 0:28, 0:28]
পরিক্ষা_ইমেজ৩.shape
(90, 28, 28)
আমরা যখন ডিপ লার্নিংয়ে ডাটা টেন্সরের প্রথম এক্সিস (যাকে বলছি এক্সিস ০, কারণ ইনডেক্সিং শুরু হয় ০ থেকে) নিয়ে কাজ করি সেটাকে samples এক্সিস অথবা samples ডাইমেনশন বলি। আমাদের ফ্যাশন এমনিস্ট ডাটাসেটে এই samples হচ্ছে ড্রেস/জুতার ছবিগুলো।
তবে পাইপলাইন অপটিমাইজেশনের কারণে ডিপ লার্নিং পুরো ডাটাসেট নিয়ে কাজ না করে সেটাকে ছোট ছোট ব্যাচে ভাগ করে ট্রেনিং করে।
আমাদের এমনিস্ট উদাহরণে যদি একটা ব্যাচ সাইজ ১২৮টা করে ইমেজ হয়, তাহলে এভাবে হতে পারে:
ব্যাচ = ট্রেনিং_ইমেজ[:128]
পরের ব্যাচ
ব্যাচ = ট্রেনিং_ইমেজ[128:256]
ব্যাচ
array([[[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], [[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], [[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], ..., [[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], [[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]], [[0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], ..., [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0], [0, 0, 0, ..., 0, 0, 0]]], dtype=uint8)
nতম ব্যাচ হলে;
ব্যাচ = ট্রেনিং_ইমেজ[128 * n:128 * (n + 1)]
আমরা যখন একটা ব্যাচ টেন্সর নিয়ে কাজ করবো, সেখানে প্রথম এক্সিস (এক্সিস ০)কে বলি "ব্যাচ এক্সিস" বা "ব্যাচ ডাইমেনশন"।