শুরুর ঘটনা

নিউরাল নেটওয়ার্কে ডাটা কিভাবে থাকে?

(পুরো চ্যাপ্টার একটা নোটবুক)

এই বইটার একটা বড় ফিচার হচ্ছে, প্রতিটা মেশিন/ডিপ লার্নিং প্রোডাক্ট তৈরির পাইপলাইনে প্রতিটা ট্রান্সফরমেশন, প্রসেসিং/প্রি-প্রসেসিং নিজে 'হাতেকলমে' করে দেখে বুঝে নেবেন। একজন বলেছেন অথবা বইয়ের থিওরির ধারণা থেকে বিশ্বাস করবেন, সেটা ফেলে দিয়ে নিজে হাতে করে বুঝে নেবেন প্রতিটা স্টেপ। আর সেকারণে এই নোটবুকগুলো খুব ইম্পর্ট্যান্ট। নিজে হাতে চালিয়ে দেখবেন যতক্ষণ না বুঝছেন। সময়ের সাথে 'আপডেটেড' এই নোটবুকগুলো নিয়ে 'প্র্যাকটিস' এবং 'প্র্যাকটিস' আপনাকে নিয়ে যাবে অন্য উচ্চতায়।

যেহেতু গিটবুকে নোটবুক ঠিকমতো রেন্ডার হয়না, সেকারণে গুগল কোলাব এবং গিটহাবে দেখা উচিৎ। গিটহাব লিংক:

নিজে নিজে প্র্যাকটিস করুন: https://github.com/raqueeb/TensorFlow2/blob/master/tensor_numpy_shape_batch.ipynb এবং https://nbviewer.jupyter.org/github/raqueeb/TensorFlow2/blob/master/tensor_numpy_shape_batch.ipynb

কোলাব লিংক: https://colab.research.google.com/github/raqueeb/TensorFlow2/blob/master/tensor_numpy_shape_batch.ipynb

নিউরাল নেটওয়ার্কে ডাটা কিভাবে থাকে?

(খসড়া)

নিউরাল নেটওয়ার্কে কিভাবে ডাটা রিপ্রেজেন্টেশন হয় সেটা হাতেকলমে দেখলে ব্যাপারটা আরো পরিস্কার হবে। “শূন্য থেকে পাইথন মেশিন লার্নিং” বইটাতে এই ব্যাপারে ধারণা দিলেও সেটাকে একটু অন্যভাবে এখানে আলাপ করতে চাচ্ছি। নিউরাল নেটওয়ার্কে যখন ডাটাকে স্টোর করতে গিয়ে যে অনেক ডাইমেনশনের ‘নামপাই’ অথবা ‘টেন্সরফ্লো’তে 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
# পাশাপাশি একটু সরাসরি টেন্সরফ্লোতে দেখি
# টেন্সরফ্লো ২.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)]

টেন্সরফ্লো ২.x এ কিভাবে টেন্সর অবজেক্টকে নামপাই অ্যারেতে কনভার্ট করা যায়?

যেহেতু ইগার একজিকিউশন যেহেতু ডিফল্ট হিসেবে চালু থাকে, সেকারণে .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

শেপ (shape)

এটা ইন্টিজার দিয়ে তৈরি করা একটা “টুপল” (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.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()
print(ট্রেনিং_ইমেজ.ndim)
3

তিনটা ডাইমেনশন: (ইমেজ সংখ্যা, দৈর্ঘ্য় মানে পিক্সেল সংখ্যা, প্রস্থ মানে পিক্সেল সংখ্যা)

print(ট্রেনিং_ইমেজ.shape)
(60000, 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)]

আমরা যখন একটা ব্যাচ টেন্সর নিয়ে কাজ করবো, সেখানে প্রথম এক্সিস (এক্সিস ০)কে বলি "ব্যাচ এক্সিস" বা "ব্যাচ ডাইমেনশন"।