<jupyter_start><jupyter_text>আপনই যদি টেন্সরফ্লো ২.* ইন্সটল করতে চান তাহলে pip বা conda প্যাকেজ ম্যানেজার ব্যবহার করে করেতে পারেন । নিচে আমি pip দিয়ে ইন্সটল করা দেখাব । আপনি যদি [colab](colab.research.google.com/) ব্যবহার করেন তাহলে ইন্সটল করার দরকার নাই । colab এ বাইডিফল্ট ইন্সটল করা থাকে । শুধু আপনাকে টেন্সরফ্লো ২.* এনাবল করে নিতে হবে । এর জন্য আপনাকে টেন্সরফ্লো ইম্পোট করার আগে এই কমান্ড দিতে হবে । `%tensorflow_version 2.x`<jupyter_code>!pip install -qq tensorflow
!pip install matplotlib # ডাটা প্লট করার জন্য এইটা লাগবে ।<jupyter_output><empty_output><jupyter_text>আপনার টেন্সরফ্লোর ভার্সন দেখার জন্য এই ভাবে লিখতে হবে ।<jupyter_code>import tensorflow as tf

print(tf.__version__)<jupyter_output>2.1.0<jupyter_text>টেন্সরফ্লো ২.০ ও কেরাস এর সংক্ষিপ্ত বিবরণটেন্সরফ্লো ও কেরাস দুইটাই এখন থেকে প্রায় ২ বছর আগে রিলিজ হয়েছিল । কেরাস ২০১৫ সালের মার্চ মাসে এবং টেন্সরফ্লো নভেম্বার মাসে যাত্রা শুরু করে ।প্রায় ৪ বছর পার হতে চলছে । এই সময়ের মাঝে টেন্সরফ্লো ১.* + কেরাস নিয়ে ডেভলপার ও গবেষকরা যেমন অনেক বড়বড় কাজ কাজ করেছে । এই কাজগুলো করতে  গিয়ে বেশ কিছু সমস্যারও সম্মুখীন হয়েছে । সমস্যাগুলো এই রকম- টেন্সরফ্লো দিয়ে যে কম্পিউটেশন গ্রাফ বানানো হয় সেটা একটা স্ট্যাটিক গ্রাফ । অর্থাৎ কম্পিউটেশন গ্রাফ একবার ডিফাইন কোরার পরে এইটা আর পরিবর্তন কোরা যায় না । বিষয়টা যদি আরও ভেঙ্গে বলতে হয় তাহলে এই ভাবে বলা যেতে পারে । মনে করেন আপনার কম্পিউটেশন গ্রাফে ১০০ টা নোড আছে । এখন আপনি চাচ্ছেন এই কম্পিউটেশন গ্রাফের বিভিন্ন প্যারামিটার যখন কম্পিউট করবেন তখন ডাইন্যামিক্যালই কিছু নোড এড বা ডিলিট করবেন । এই কাজটা আপনি করতে পারবেন না ।   - নর্মাল প্রোগ্রামিং এ ডিবাগ কোরা খুব সহজ । কারণ আপনি লাইন বাই লাইন এক্সিকিউত করে কোন ভারিয়েবল কি করতেছে সেটা প্রিন্ট করে দেখতে পারেন । কিন্তু টেন্সরফ্লো তে আপনি এটা করতে পারবেন না । এবং আমরা যারা প্রোগ্রামার এবং আমরা যেভাবে কাজ করি, টেন্সরফ্লোর এই বিশিষ্ট আমাদের সাথে যায় না । এবং এই ধরনের প্রোগ্রামিং প্যারাডাইমের সাথে খাপ খাওয়া বেশ কঠিন ।- টেন্সরফ্লো এপিআই খুব শক্তিশালী ও ফ্লেক্সিবল কিন্তু এটা কিছু কিছু ক্ষেত্রে কনফিউসিং এবং সঠিক ভাবে এপআই ব্যবহার করা বেশ কঠিন । - অন্যদিকে কেরাস ব্যবহার করা খুব সহজ । কেরাস ব্যবহার করে খুব দ্রুত এক্সপেরিমেন্ট করা যায় । কিন্তু এটা সমস্যা হল এটা টেন্সরফ্লোর মত অতটা ফ্লেক্সিবল না । ফ্লেক্সিবল বলতে বোঝান হচ্ছে যে এটা নিজের মত করে কাস্টমাইজ করা টেন্সরফ্লোর তুলনায় বেশ কঠিন ।   টেন্সরফ্লো ২.* এ টেন্সরফ্লো ১.* কে নতুন করে ডিজাইন করা হয়েছে । এবং টেন্সরফ্লো ও কেরাস কে একসাথে মার্জ করা হয়েছে । যার ফলে এখন টেন্সরফ্লো থেকেই কেরাস এর সকল এপিএই ব্যবহার করা যাবে । টেন্সরফ্লো ১.* এর যে সমস্যা গুলো ছিল সেগুলো ২.* এ সমাধান করা হয়েছে । এবং এটা করা হয়েছে এই দীর্ঘ ৪ বছরে ডেভলপার কাছে থেকে বিভিন্ন সময় ফিডব্যাক নিয়ে ।  টেন্সরফ্লো ২.* এ নিচের আইডিয়া গুলো ইমপ্লিমেন্ট করা হয়েছে যেগুলো টেন্সরফ্লো ১.* ছিল না । - এখন টেন্সরফ্লোর কম্পিউটেশন (Numpy)[https://numpy.org/] মত করা যাবে । এবং আমরা নরমাল পাইথন কোড যেভাবে লিখি সেইভাবেই কোড লিখা যাবে । - স্ট্যাটিক গ্রাফ কম্পিউটেসনের সুবিধা এখানে রাখা হইছে । এটা রাখার মুল কারণ হল এর ফলে কম্পিউটেস খুব দ্রুত করা সম্ভব হয় যেটা ডাইনামিক গ্রাফে এখন পর্যন্ত সম্ভব না । - টেন্সরফ্লো ২.* এ কেরাস কে হাই লেভেল ডিপ লার্নিং এপিআই হিসাবে রাখা হয়েছে । এটা করার কারণ যাতে নতুনরা খুব সহজে এটা নিয়ে কাজ করতে পারে । - টেন্সরফ্লো ২.* এর মাঝে কেরাস কে এমন ভাবে ডিজাইন করা হয়েছে যারা ফলে হাই লেভেল (সহজে ব্যবহার করা যায় কিন্তু কম ফ্লেক্সিবল) + লো-লেভেল এপিআই (ব্যবহার করার জন্য বটেন্সরফ্লোর উপর বেশ ভাল দক্ষতা প্রয়োজন কিন্তু এটা খুবই ফ্লেক্সিবল) কোনরকম হেসেল ছাড়াই একসাথে ব্যবহার করা যায় ।    টেন্সরফ্লোর প্রথমিক ধারনা   টেন্সরফ্লোর নাম কেন টেন্সরফ্লোএটা ডাটার ফ্লো হ্যান্ডলে করে । ডাটা কিভাবে নেটওয়ার্কের মধ্যে দিয়ে পাস হবে সেটা ডিফাইন করে । এবং ডাটাগুলো রাখা হয় টেনসর আকাটা । এই জন্য এটার নাম দেয়া হয়েছে টেন্সরফ্লো ।   টেন্সর কি জিনিস আমরা জানি অ্যারে একটা ডাটা টাইপ । টেন্সর একটা N ডাইমেসন অ্যারে । আপনি যদি একটা [কনস্টেন্ট](https://www.tensorflow.org/api_docs/python/tf/constant) টেন্সর ডিফাইন করতে চান তাহলে এই ভাবে করতে হবে ।<jupyter_code>x = tf.constant([[5, 2], [1, 3]])
print(x)<jupyter_output>tf.Tensor(
[[5 2]
 [1 3]], shape=(2, 2), dtype=int32)<jupyter_text>এখানে আমরা দেখতে পাচ্ছি যে আমরা যে ভেলুগুলো দিয়ে কনস্টেন্ট টাইপ টেন্সর ডিফাইন করলাম সেটা ভালুর সাথে আরও দুইটা জিনিস দেখাচ্ছে । - shape=(2, 2)- dtype=int32এটা যেহেতু ২x২ টেন্সর সেহেতু `shape=(2, 2)` দেখাচ্ছে । আর এখানে যেহেতু সবগুলো ভেলু ইন্টিজার সেহেতু ডাইন্যামিক্যালি ডাটা টাইপ `int32` বুঝে নিয়েছে । আপনি যদি এই ৪ টা সংখ্যার যেকোনো একটা সংখ্যা কে `float` হিসাবে দেন তাহলে দেখবেন `dtype=float32` দেখাচ্ছে । আপনি ৫ এর জায়গায় ৫.১ দিয়ে দেখতে পারেন । আপনি চাইলে টেন্সর ভেরিয়েবল ডিফাইন করার সময়ও ডাটা টাইপ ডিফাইন করে দিতে পারেন । নিচের মত করে<jupyter_code>x = tf.constant([[5, 2], [1, 3]], dtype='int16')
print(x)<jupyter_output>tf.Tensor(
[[5 2]
 [1 3]], shape=(2, 2), dtype=int16)<jupyter_text>এখন আপনি চাচ্ছেন x প্রিন্ট করলে numpy এ যেভাবে প্রিন্ট করে সেভাবে প্রিন্ট করবে । এটা করার জন্য আপনাকে `.numpy()` ব্যবহার করতে হবে । এটা টেন্সরফ্লো ২.* এ নতুন এড করা হয়েছে । টেন্সরফ্লো ১.* আপনি এটা পাবেন না ।<jupyter_code>x.numpy()<jupyter_output><empty_output><jupyter_text>ভেরিয়েবলের ডাটা টাইপ ও শেপ পাওয়ার জন্য আপনি এই ভাবে লিখতে পারেন ।<jupyter_code>print('dtype:', x.dtype)
print('shape:', x.shape)<jupyter_output>dtype: <dtype: 'int16'>
shape: (2, 2)<jupyter_text>আপনি যদি ০ এবং ১ দিয়ে টেন্সর ইনিসিয়ালাইজ করতে চান তাহলে এই ভাবে করতে পারেন ।<jupyter_code>print(tf.ones(shape=(2, 2)))
print(tf.zeros(shape=(2, 2)))<jupyter_output>tf.Tensor(
[[1. 1.]
 [1. 1.]], shape=(2, 2), dtype=float32)
tf.Tensor(
[[0. 0.]
 [0. 0.]], shape=(2, 2), dtype=float32)<jupyter_text>টেন্সরে rank কি ? আমরা অ্যারের ডাইমেসন সম্পর্কে জানি । টেন্সরের Rank এবং ডাইমেসন একই জিনিস । Rank এর উপর নির্ভর করে টেন্সরের গাণিতিক নাম (টেবিল লক্ষ করেন) আলাদা আলাদা হয় । নিচে আমরা কিছু উদারন দেখি ।<jupyter_code>rank_0 = tf.Variable(7)
rank_1 = tf.Variable([7])
rank_2 = tf.Variable([[1, 2], [3, 4]])
rank_3 = tf.Variable([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
rank_4 = tf.reshape(tf.range(start=0, limit=24, delta=1), shape=[2, 3, 2, 2])

ranks = [rank_0, rank_1, rank_2, rank_3, rank_4]

for rank in ranks:
    print("Rank is:", tf.rank(rank).numpy())<jupyter_output>Rank is: 0
Rank is: 1
Rank is: 2
Rank is: 3
Rank is: 4<jupyter_text>কোন একটা টেন্সরের ডাইমেসনের / র‍্যঙ্ক এর উপর ভিত্তি করে এটা কে কি বলে সেটা নিচের টেবিলে দেখান হল । |Rank | Math entity                      | Example              ||-----|----------------------------------|-----------------------| 0   | Scalar (magnitude only)          |[1.5]| 1   | Vector (magnitude and direction) |[2, 3, 5, 8 11]| 2   | Matrix (table of numbers)        |[[1, 2], [3, 4]]| 3   | 3-Tensor (cube of numbers)       |[[[1], [2]], [[3], [4]]]| n   | n-Tensor (you get the idea)      |[[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]]টেবিল থেকে দেখতে পাচ্ছি যে rank ০ হলে সেটার নাম sclar, ১ হলে সেটা নাম ভেক্টর, ২ হলে সেটার নাম মেট্রিক্স ৩ হলে ৩ ডাইমেসন টেন্সর এভাবে চলতে থাকবে   Probability distributionএখন আমরা দেখি যে বিভিন্ন probability distribution এর উপর ভিত্তিক করে টেন্সরফ্লোতে কিভাবে র‍্যান্ডম নাম্বার কিভাবে জেনারেট করা যায় । প্রথমে দেখি [নরমাল ডিসট্রিবিউসন](https://www.tensorflow.org/api_docs/python/tf/random/normal):<jupyter_code>from matplotlib import pyplot as plt

N = 100
normal_dist = tf.random.normal(shape=(N,), mean=0., stddev=1.)
plt.plot(normal_dist)
plt.xlabel('Random number idx')
plt.ylabel('N random value')
plt.show()<jupyter_output><empty_output><jupyter_text>উপরের ছবি থেকে দেখা যাচ্ছে যে । বেশিভাগ র‍্যান্ডম সংখ্যা ০ এর আসে-পাসে জেনারেট করেছে (কারণ আমরা mean=0. বলে দিয়েছি) এবং সংখ্যা গুলো mean থেকে ১ এর আসে পাসে ঘুরা ঘুরি করছে । এর কারণ হল আমরা stddev=1. ব্যবহার করেছি ।  stddev=1. সেট করে আমরা বলে দিয়েছি যে, যে সংখ্যা গুলো জেনারেট হবে সেগুলোর দূরত্ব mean থেকে ১ এর কাছাকাছি হবে । আপনারা mean=0., stddev=1. এর বিভিন্ন মান দিয়ে ডাটা প্লট করে দেখতে পারেন ।  এখন দেখি [ইউনিফরম ডিসট্রিবিউসন](https://www.tensorflow.org/api_docs/python/tf/random/uniform):<jupyter_code>tf.random.uniform(shape=(4, 4), minval=0, maxval=10, dtype='int32')<jupyter_output><empty_output><jupyter_text>এখানে দেখা যাচ্ছে যে সব সংখ্যা ০ থেকে ১০ এর মাঝেই জেনারট করেছেবিভিন্ন সম্পর্কে জানতে দেখতে পারেন [probability distribution](https://www.analyticsvidhya.com/blog/2017/09/6-probability-distributions-data-science/)  Variables কি  এই পর্যন্ত আমরা যে টেন্সর দেখলাম সবগুলো কনস্টেন্ট টেন্সর । অর্থাৎ এটা একবার ইনিসিয়ালাইজ করার পরে আর পরিবর্তন করা যায় না । এখন আমার এমন একটা টেন্সরের প্রয়োজন যেটা ইচ্ছা মত পরে পরিবর্তন করা যাবে । এই ধরনের ক্ষেত্রে আমাকে `tf.Varialbe()` দিয়ে টেন্সর ভারিয়েবল ডিফাইন করতে হবে । প্রোগ্রামিং এর ভাষায় এই ধরনের ভেরিয়েবল কে বলে মিউটেবল ভেরিয়েবল । নেটওয়ার্কে ওয়েট ইনিসিয়ালাইজ করার জন্য এটা ব্যবহার করা হয় কারণ নেটওয়ার্ক যখন ট্রেনিং করানো হয় তখন এই ওয়েট আপডেট করতে হয় । এটা সম্পর্কে আরও জানতে...[Variables](https://www.tensorflow.org/guide/variable)<jupyter_code>initial_value = tf.random.normal(shape=(2, 2))
a = tf.Variable(initial_value)
print(a)<jupyter_output><tf.Variable 'Variable:0' shape=(2, 2) dtype=float32, numpy=
array([[-0.00117146,  0.7254187 ],
       [ 0.8542992 , -0.55369925]], dtype=float32)><jupyter_text>এই ভেরিয়েবল এর ভেলু আপডেট করতে চাইলে আমরা `.assign(value)`, বা `.assign_add(increment)` বা `.assign_sub(decrement)` ব্যবহার করতে পারি । নিচে একটা উদাহরণ দেখি ।<jupyter_code>new_value = tf.ones(shape=(2, 2))
a.assign(new_value)
for i in range(2):
  for j in range(2):
    a[i, j] == new_value[i, j]
print('value of a after assign new value:', a)<jupyter_output>value of a after assign new value: <tf.Variable 'Variable:0' shape=(2, 2) dtype=float32, numpy=
array([[1., 1.],
       [1., 1.]], dtype=float32)><jupyter_text>`.assign_add` এর একটা উদাহরণ দেখি<jupyter_code>added_value = tf.ones(shape=(2, 2))
a.assign_add(added_value)
for i in range(2):
  for j in range(2):
    a[i, j] == new_value[i, j] + added_value[i, j]
print(a)<jupyter_output><tf.Variable 'Variable:0' shape=(2, 2) dtype=float32, numpy=
array([[2., 2.],
       [2., 2.]], dtype=float32)><jupyter_text>টেন্সরফ্লো দিয়ে কমন কিছু ম্যাথ অপারেশনআপনি নামপাই এ যেভাবে ম্যাথ অপারেশন করেন ঠিক একই ভাবে টেন্সরফ্লোতেও করতে পারবেন । নামপাই এবং টেন্সরফ্লো এর মধ্যে মুল পার্থক্য হল টেন্সরফ্লো কোড আপনি CPU, [GPU](https://en.wikipedia.org/wiki/Graphics_processing_unit) এবং [TPU](https://en.wikipedia.org/wiki/Tensor_processing_unit) তে রান করতে পারবেন ।<jupyter_code>a = tf.ones(shape=(2, 2))
b = tf.ones(shape=(2, 2))
print('a:', a.numpy().flatten())
print('b:', b.numpy().flatten())
c = a + b
print('c:', c.numpy().flatten())
d = tf.square(c)
print('square:', d.numpy().flatten())
e = tf.exp(d)
print('exp   :', e.numpy().flatten())<jupyter_output>a: [1. 1. 1. 1.]
b: [1. 1. 1. 1.]
c: [2. 2. 2. 2.]
square: [4. 4. 4. 4.]
exp   : [54.59815 54.59815 54.59815 54.59815]
{'fraction_non_alphanumeric': 0.26791484508648544, 'fraction_numerical': 0.03098270290819236, 'mean_word_length': 4.509424083769633, 'pattern_counts': {'":': 0, '<': 51, '<?xml version=': 0, '>': 51, 'https://': 8, 'lorem ipsum': 0, 'www.': 5, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '14', 'dir': 'jupyter-structured-clean-dedup', 'id': '526626', 'license': 'Apache-2.0', 'n_tokens_mistral': 8784, 'n_tokens_neox': 9959, 'n_words': 1450, 'path': 'basics/dl_basics_part_1.ipynb', 'repo_name': 'menon92/DL-Sneak-Peak', 'size': '10522'}