শুরুর ঘটনা

এমবেডিং, ওয়ার্ড এমবেডিং, শব্দ এবং সংখ্যার কাছাকাছি এনকোডিং

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

আমরা বই পড়ছি, নোটবুক কেন পড়বো?

নোটবুক সবসময় আপডেটেড। এই বই থেকেও।

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

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

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

এমবেডিং, ওয়ার্ড এমবেডিং এবং বাংলায় টেক্সট অ্যানালাইসিস

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

সেদিক থেকে মানুষের ভাষা আরো অনেক কমপ্লেক্স। আমরা একেকজন একেক ভাষায় কথা বলি, ভাষাগুলোর মধ্যে সংযোগ/সিমিলারিটি এবং কি বলতে গিয়ে কি বলে ফেললাম এবং তার ফলাফল, তার পাশাপাশি অনেক শব্দ একটা ভাষায় যা বোঝায় সেটা অন্য ভাষায় তার বৈপরীত্য দেখায়। এখন আপনি বাংলায় কথা বললেও সেটার মধ্যে ৪০% বাইরের শব্দ ব্যবহার করলে তো আরো সমস্যা। আর এই কারণে টেক্সট নিয়ে কাজ করা বেশ কমপ্লেক্স।

যেকোনো ল্যাঙ্গুয়েজে তার প্রতিটা শব্দের একটা অর্থ আছে। তবে এটার অর্থ অনেক সময় নির্ভর করে কনটেক্সটে বা শব্দটা বাক্যের মধ্যে কোথায় এই মুহূর্তে আছে। একই শব্দের আবার অনেকগুলো কনটেক্সচুয়াল অর্থ থাকে সে কারণে শব্দকে শুধুমাত্র শব্দ বা অক্ষর লেভেলে কাজ করলে হবে না। কারণ, ডিপ লার্নিং মডেল যেহেতু সংখ্যা ছাড়া কিছু বোঝেনা, সে কারণে একেক ভাষার একেক বুলি এবং বকাবকি এর পাশাপাশি সেই ভাষাগুলোকে ঠিকমতো সংখ্যায় ট্রান্সফার করা একটা চ্যালেঞ্জ এর কাজ অবশ্যই।

কেন টাইম সিরিজ নিয়ে আলাপ হয়নি?

এই বইতে আমি ইচ্ছে করে ‘টাইম সিরিজ’ যোগ করিনি, কারণ সেটার এপ্লিকেশন লেভেল এখনো বেসিক লেভেলে নেই। তবে, এই রিকারেন্ট নিউরাল নেটওয়ার্ককে শিখিয়ে দিলে সে (আরএনএন) ফ্রি ফর্ম (ইচ্ছেমতো) টেক্সট জেনারেট করতে পারে। আমরা যেমন দেখেছি ‘এল এস টি এম’, (লঙ শর্ট টার্ম মেমোরি) নেটওয়ার্কে ‘শেক্সপিয়ার’ ক্লাসিক পড়তে দিলে, সে শেক্সপিয়ারের মতো আরেকটা ক্লাসিক লিখে ফেলেছে, যেখানে এই নেটওয়ার্কের মধ্যে শব্দ, বাক্য এবং ব্যাকরণ তৈরির কোন ধারণা নেই। কারণ, ডিপ লার্নিং প্রচুর ক্লাসিক বই পড়ে বুঝেছে কিভাবে শব্দ, বাক্য বা তার ব্যাকরণ ব্যবহার করতে হয় এর ভেতরে না ঢুকেই। টাইম সিরিজের ব্যাপারটা হচ্ছে সে পরের জিনিসটা প্রেডিক্ট করবে। আগের সময়ে কি ছিলো, সেটাকে ধরে এরপরে কি কি আসতে পারে সেটাই বলবে সে। না বুঝে। যদি শব্দ লেভেলে দেখি, ‘আমি’ এর পর কি আসতে পারে তাহলে ‘ভালো’ আসতে পারে, কারণ ‘আমি ভালো আছি’ একটা বহুল প্রচলিত বাক্য।

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

ন্যাচারাল ল্যাঙ্গুয়েজ প্রসেসিং

সেজন্য এর মধ্যে এসে যোগ হয়েছে ‘ন্যাচারাল ল্যাঙ্গুয়েজ প্রসেসিং’। ন্যাচারাল ল্যাঙ্গুয়েজ প্রসেসিং এর মধ্যে লিঙ্গুইস্টিকস, কম্পিউটার সায়েন্স, ইনফরমেশন ইঞ্জিনিয়ারিং এবং কৃত্রিম বুদ্ধিমত্তা ব্যাপারগুলো চলে এসেছে কাজের স্বার্থে। এমনিতেই মানুষ এবং যন্ত্রের মধ্যে একটা যোগসূত্র স্থাপন করা বেশ ঝামেলার ব্যাপার। ‘স্পিচ রিকগনিশন’ এর পাশাপাশি স্বয়ংক্রিয়ভাবে একটা ভাষা বুঝতে পারা এবং তার পাশাপাশি সেই ভাষায় বুঝে টেক্সট জেনারেট করা সহজ ব্যাপার নয়, যখন সবকিছুর পেছনে কাজ করে সংখ্যা। সবচেয়ে বড় কথা হচ্ছে আপনি একটা যন্ত্রকে শেখাচ্ছেন সে আপনার সাথে ন্যাচারালি যোগসুত্র স্থাপন করতে পারে। এই ‘ন্যাচারাল ল্যাঙ্গুয়েজ প্রসেসিং’ এর শুরুটা হচ্ছে সেই টেক্সটকে ঠিকমতো পড়তে পারা, সেটাকে নিজের মতো করে বোঝা এবং মানুষ যেভাবে একটা বাক্যের ‘ইনটেন্ট’ বুঝতে পারে সেভাবে তাকে বুঝিয়ে তার কাছ থেকে উত্তর বের করা। সহজ ব্যাপার নয়। তবে শুরু করতে হবে কোথাও।

যেহেতু ‘ন্যাচারাল ল্যাঙ্গুয়েজ প্রসেসিং’ একটা বিশাল সাবজেক্ট, আমি এই বইতে ব্যাপারটা আনার ব্যর্থ চেষ্টা করব না। তবে কম্পিউটার কিভাবে ‘টেক্সট’ এর সাথে ‘ইন্টারঅ্যাক্ট’ করে সেটা নিয়েই দেখব আমরা সামনের চ্যাপ্টারগুলোতে। আমরা জানি মেশিন লার্নিং মডেল ভেক্টরকে ইনপুট হিসেবে নেয়। আর এই ভেক্টরগুলো হচ্ছে সংখ্যার অ্যারে। যখন আমরা টেক্সট মানে শব্দ এবং বাক্য নিয়ে আলাপ করব, তখন আমাদের প্রথম কাজ হবে এই স্ট্রিংগুলোকে কিভাবে সংখ্যায় পাল্টানো যায়। অর্থাৎ সেই বাক্য বা শব্দটি কিভাবে ‘ভেক্টরাইজ’ করা যায় মডেলে দেবার আগে। আমাদের এই চ্যাপ্টার থেকে শুরু করব কিভাবে আস্তে আস্তে ডিপ নিউরাল নেটওয়ার্ক দিয়ে বাংলার একটা অ্যাপ্লিকেশনে যাওয়া যায়।

try:
# শুধুমাত্র টেন্সর-ফ্লো ২.x ব্যবহার করবো
%tensorflow_version 2.x
except Exception:
pass
import tensorflow as tf
keras = tf.keras
TensorFlow 2.x selected.

আমরা যেহেতু ডিপ নিউরাল নেটওয়ার্কের কথা বলছি সেখানে সবকিছুতেই লেয়ার কনসেপ্ট কাজ করে। আজকে এখানে আমরা নতুন একটা লেয়ার নিয়ে কথা বলবো যেটাকে আমরা বলছি এম্বেডিং লেয়ার। শুরুতেই অনেকে বলবেন এম্বেডিং মানে কি? এর সাথে সংখ্যার সম্পর্ক কি?

from tensorflow.keras import layers
embedding_layer = layers.Embedding(1000, 5)
# তিনটা সংখ্যা পাঠালাম, দেখি কি করে?
# সংখ্যায় ওয়েট
result = embedding_layer(tf.constant([1,2,3]))
result.numpy()
array([[ 0.01187975, 0.03535623, 0.01777541, -0.02199599, -0.04960341],
[ 0.02448957, -0.02763765, -0.03665041, 0.03637788, -0.01790854],
[ 0.02317014, -0.02551311, -0.03900546, -0.03208224, -0.0314441 ]],
dtype=float32)
# অথবা টেন্সর-ফ্লো দিয়ে টোকেনাইজ করে দেখি
from tensorflow.keras.preprocessing.text import Tokenizer
tokenizer = Tokenizer()
text = ['আমি এখন বই পড়ি', 'এটি আমার অনেক পছন্দের একটা বই']
tokenizer.fit_on_texts(text)
sequences = tokenizer.texts_to_sequences(text)
sequences
[[2, 3, 1, 4], [5, 6, 7, 8, 9, 1]]

ইউনিকোড স্ট্রিং দুভাবে টেন্সর-ফ্লোতে দেখানো যায়। স্ট্রিং স্কেলারে কোড পয়েন্টের সিকোয়েন্সকে এনকোড করবে জানা ক্যারেক্টার এনকোডিং দিয়ে। আমরা ভেক্টর নিয়ে দেখালে। এখানে প্রতিটা পজিশনে একটা সিঙ্গেল কোড পয়েন্ট আছে।

এখানে কয়েকটা উদাহরণ দেখালাম, তবে মাথা খারাপ করার দরকার নেই।

text_chars = tf.constant([ord(char) for char in u"আমি এখন বই পড়ি"])
text_chars

যা দেখছেন, ইউনিকোড স্ট্রিং, দেখাচ্ছে ইউনিকোড কোড পয়েন্ট ভেক্টরে। ব্যাচে দেখি। সব সংখ্যার খেলা।

batch_utf8 = [s.encode('UTF-8') for s in
[u'প্রিয় বন্ধু', u'কেমন চলছে তোমার?', u'আমি ভালো', u'😊']]
batch_chars_ragged = tf.strings.unicode_decode(batch_utf8,
input_encoding='UTF-8')
for sentence_chars in batch_chars_ragged.to_list():
print(sentence_chars)
[2474, 2509, 2480, 2495, 2527, 32, 2476, 2472, 2509, 2471, 2497]
[2453, 2503, 2478, 2472, 32, 2458, 2482, 2459, 2503, 32, 2468, 2507, 2478, 2494, 2480, 63]
[2438, 2478, 2495, 32, 2477, 2494, 2482, 2507]
[128522]
batch_chars_padded = batch_chars_ragged.to_tensor(default_value=-1)
print(batch_chars_padded.numpy())
[[ 2474 2509 2480 2495 2527 32 2476 2472 2509 2471
2497 -1 -1 -1 -1 -1]
[ 2453 2503 2478 2472 32 2458 2482 2459 2503 32
2468 2507 2478 2494 2480 63]
[ 2438 2478 2495 32 2477 2494 2482 2507 -1 -1
-1 -1 -1 -1 -1 -1]
[128522 -1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1]]

মাল্টি ডাইমেনশনে দেখি।

print(tf.strings.unicode_script(batch_chars_ragged))

‘এম্বেডিং’ সিমিলারিটি, একটা থেকে আরেকটা শব্দের দূরত্ব

মেশিন লার্নিং এর ভাষায় ‘এম্বেডিং’ হচ্ছে সিমিলারিটি, যদি শব্দের কথা বলি তাহলে একটা শব্দ থেকে আরেকটা শব্দ কতটুকু যুক্ত বা একটা শব্দ থেকে আরেকটা শব্দ কত দূরে? তাদের মধ্যে সম্পর্ক আছে কিনা? যেমন, রাজা’র সাথে ‘রানী’ শব্দটা কিন্তু সম্পর্কযুক্ত। যেমন, ‘মা’ শব্দের সাথে ‘বাবা’ সম্পর্কযুক্ত। ‘বাংলাদেশ’ শব্দের সাথে ‘ঢাকা’ সম্পর্কযুক্ত। সেই থেকে ‘আকাশ’ শব্দের সাথে ‘টেবিল’ কিন্তু বহু দূরে মানে তাদের মধ্যে সম্পর্ক টানা বেশ কঠিন, যদি না কোনদিন ‘আকাশ’ থেকে ‘টেবিল’ পড়ে।

এই এম্বেডিং ব্যাপারটা এসেছে কিছুটা ‘ওয়ান হট’ এনকোডিং থেকে। তবে, ব্যাপারটা ঠিক সেরকম নয়। শব্দকে যেহেতু আমাদেরকে সংখ্যায় পাল্টাতে হবে তাহলে আর বাকি উপায় কি? মনে আছে, আমাদের এই ‘ওয়ান হট’ এনকোডিং বা ‘ডামি ভেরিয়েবল’ ব্যাপারটা এসেছে শব্দ দিয়ে তৈরি ক্যাটাগরিকাল ভেরিয়েবল থেকে? মেশিন লার্নিং মডেলে আমরা যখন কিছু শব্দকে সংখ্যার ক্যাটাগরিতে ভাগ করতে চাই, যেটা এমুহুর্তে সংখ্যায় নেই। যেমন, আইরিশ ডাটাসেটের তিন প্রজাতির ফুলের জন্য ০,১,২, ভাগে ভাগ করতে পারছিনা, সেখানে চলে এসছে এই ডামি ভেরিয়েবল। আমরা যাকে বলছি শব্দকে দিয়ে তার জন্য একটা করে এনকোডিং। এর অর্থ হচ্ছে, আমাদের ভাষায় যতগুলো শব্দ আছে সেগুলোকে ‘ওয়ান হট’ এনকোডিং করা যেতে পারে। আর সেখানেই সমস্যা।

চিত্রঃ ‘ওয়ান হট’ এনকোডিং এর উদাহরণ

এখানে একটা উদাহরণ দেয়া যাক। একটা বাক্য। ‘আমি এখন বই পড়ি’। এই চারটা শব্দকে যদি আমরা সংখ্যায় পাল্টাতে চাই, তাহলে আমাদের এই ভোকাবুলারির প্রতিটা শব্দকে ‘০’ ভেক্টর দিয়ে শুরু করব। আমাদের এখানে যেহেতু ৪টা ইউনিক শব্দ, সে কারণে এই শূন্য ভেক্টরের দৈর্ঘ্য হবে ৪। প্রতিটি শব্দকে ঠিকমতো রিপ্রেজেন্ট করার জন্য একেকটা শব্দের ইনডেক্সে তার করেসপন্ডিং ‘১’ বসাবো। ছবি দেখি। এখন এই টেবিল থেকে প্রতিটা শব্দের জন্য তার ভেক্টর বের করা সোজা। আমাদের এই চারটা শব্দের জন্য ভেক্টরের দৈর্ঘ্য হচ্ছে ৪ যার বাকি তিনটাই ০। এভাবে আমরা শব্দকে বিভিন্ন ক্যাটাগরিতে ভাগ করতে পারি। তবে ব্যাপারটা সেরকম এফিশিয়েন্ট নয় যখন আমাদের ভোকাবুলারিতে ১০ হাজার শব্দ থাকবে। তার মানে একেকটা ভেক্টরের দৈর্ঘ্য ১০ হাজার হবে - এর মধ্যে ৯৯.৯৯ শতাংশই হচ্ছে ০। এটা দক্ষ সিস্টেম না। আমরা যদি প্রতিটা বাংলা শব্দের জন্য ডামি ভ্যারিয়েবল বানাই, তাহলে কতো বড় ডামি ভ্যারিয়েবলের একেকটা টেবিল হবে?

এই সমস্যা থেকে বের হবার উপায় কি? প্রতিটা শব্দকে একটা করে ইউনিক সংখ্যা দিয়ে দেওয়া। আমাদের আগের ভোকাবুলারিটাকে ‘আমি এখন বই পড়ি’কে আমরা একটা ‘ডেন্স’ ভেক্টরের মত আলাদা আলাদা করে সংখ্যা দিয়ে দিতে পারি। এই জিনিসটা আগের থেকে অনেকটাই এফিশিয়েন্ট। একেকটা শব্দের জন্য একেকটা আলাদা আলাদা সংখ্যা। সবচেয়ে বড় ব্যাপার হচ্ছে বিশাল ‘স্পার্স’ হাই-ডাইমেনশন স্পেস থেকে কম ডাইমেনশন স্পেসে চলে এলাম। তবে, এটার সমস্যা দুটো।

  • ১. আমাদের এই সংখ্যায় এনকোডিং সিস্টেমটাকে ‘ম্যানুয়ালি’ করা হয়েছে। ফলে এদের মধ্যে কোন ‘রিলেশনশিপ’ বের করা যাচ্ছে না। অংকেও এক সংখ্যা থেকে আরেক সংখ্যার মধ্যে যে রিলেশনশিপ সেটা অনুপস্থিত। ফলে আমাদের অংকের ভাষায় কো-সাইন ভেক্টরে কে কোথায় আছে সেটা বের করা মুশকিল।

  • ২. একটা মডেলের জন্য এই ধরনের ‘ম্যানুয়াল’ এনকোডিং শব্দগুলোর মধ্যে সম্পর্ক না বুঝলে সেটা সমস্যা হয়ে দাঁড়াবে। যেহেতু এই একেকটা ফিচারের জন্য একেকটা ‘ওয়েট’ সে কারণে একটা লিনিয়ার ক্লাসিফায়ারের পক্ষে এই ফিচার এবং ওয়েট এর কম্বিনেশন কোন সম্পর্ক দেখাবেনা। এটা বড় সমস্যা।

আর সে কারণেই চলে এসেছে ওয়ার্ড এম্বেডিং।

একটা ছোট্ট উদাহরন, রেস্টুরেন্ট রিভিউ

আমরা এখানে একটা এমবেডিং লেয়ারের উদাহরণ নিয়ে আসি। ইনপুট ডাইমেনশনে কতগুলো ভোকাবুলারি আছে? ধরে নিচ্ছি ৫০টা। আমি সংখ্যা না গুনেই বলছি। ৫০টা ডামি ভ্যারিয়েবল। মানে আমরা কতগুলো ক্যাটেগরিতে এনকোড করছি। আমাদের লুক আপ টেবিলে কতগুলো আইটেম আছে সেটাই এখানে আসবে।

আমাদের ৫০টা ভোকাবুলারির জন্য 'ওয়ান হট' এনকোডিং ব্যবহার করছি, কেরাসের ভেতরে। এটা ব্যবহার করা ঠিক না তবে ছোটখাট উদাহরণে সেটা করা যায়।

from numpy import array
from tensorflow.keras.preprocessing.text import one_hot
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten, Embedding, Dense

আমরা এখানে ছোট একটা দশটা রেস্টুরেন্টের রিভিউ যোগ করেছি। এর পাশাপাশি তার পাঁচটা নেগেটিভ আর পাঁচটা পজেটিভ লেবেল যোগ করেছি। এর উপর আমরা নিউরাল নেটওয়ার্কে ট্রেইন করব।

# ১০টা েস্টুরেন্ট রিভিউএর ইনপুট
reviews = [
'আমি আর আসছি না এখানে!',
'একদম বাজে সার্ভিস',
'কথা শোনে না ওয়েটার',
'একদম ঠান্ডা খাবার',
'বাজে খাবার!',
'অসাধারণ',
'অসাধারণ সার্ভিস!',
'খুব ভালো!',
'মোটামুটি',
'এর থেকে ভালো হয়না']
# লেবেল বলে দিচ্ছি (1=নেগেটিভ, 0=পজেটিভ)
labels = array([1,1,1,1,1,0,0,0,0,0])
print(reviews[0])
আমি আর আসছি না এখানে!

শব্দের সংখ্যা না গুনেও একটা ধারনা নিয়েছি ৫০টা মানে ভোকাবুলারি সাইজ ৫০। ছোট্ট উদাহরণ বলে আমরা ‘ওয়ান হট এনকোডিং’ ব্যবহার করছি। এর কাজ হচ্ছে এই বাক্যগুলোকে শব্দে ভেঙে ফেলা। যাকে আমরা বলছি টোকেনাইজিং। এরপর তাকে নিজস্ব ইনডেক্সে পাঠাবে। শেষে আমরা যা পাব সেটা সংখ্যার সিকোয়েন্স।

# ওয়ান হট এনকোডিং
VOCAB_SIZE = 50
encoded_reviews = [one_hot(d, VOCAB_SIZE) for d in reviews]
print(f"Encoded reviews: {encoded_reviews}")
Encoded reviews: [[21, 2, 26, 32, 20], [40, 45, 8], [3, 28, 32, 23], [40, 43, 27], [45, 27], [22], [22, 8], [23, 17], [46], [9, 24, 17, 47]]

যতগুলো শব্দ ততগুলো করে সংখ্যা একেকটা অংশে। তবে এই সিকোয়েন্সে একটা কনসিস্টেন্সি রাখার জন্য একটা লেনথ ঠিক করে দিতে হবে। শব্দ ধরে এখন কোনটা ২ অথবা ৩ এবং ৪ লেনথে, কিন্তু প্যাডিং এর জন্য আমরা MAX_LENGTH = 4 ধরে দিচ্ছি। দেখুন এখানে বাকি অংশ ০ দিয়ে ভরে দিয়েছে।

MAX_LENGTH = 4
padded_reviews = pad_sequences(encoded_reviews, maxlen=MAX_LENGTH, padding='post')
print(padded_reviews)
[[ 2 26 32 20]
[40 45 8 0]
[ 3 28 32 23]
[40 43 27 0]
[45 27 0 0]
[22 0 0 0]
[22 8 0 0]
[23 17 0 0]
[46 0 0 0]
[ 9 24 17 47]]

আমরা আগের মতো সিকোয়েন্সিয়াল নেটওয়ার্ক তৈরি করছি। আউটপুট ডাইমেনশনে কমিয়ে এনেছি সেটা। এখানে একটা ডেন্স লেয়ার, তার মানে একটা ওয়েট ম্যাট্রিক্স, একটা লার্নিং চলছে এখানে। এই এমবেডিং লেয়ারে যে লার্নিং চলছে সেটা শব্দগুলোকে একটা ইউক্লুডিয়ান স্পেসে সংখ্যাগুলোর মধ্যে একটা রিলেশনশিপ তৈরি হচ্ছে।

মডেলটা দেখুন।

model = Sequential()
embedding_layer = Embedding(VOCAB_SIZE, 8, input_length=MAX_LENGTH)
model.add(embedding_layer)
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['acc'])
print(model.summary())
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding_1 (Embedding) (None, 4, 8) 400
_________________________________________________________________
flatten (Flatten) (None, 32) 0
_________________________________________________________________
dense (Dense) (None, 1) 33
=================================================================
Total params: 433
Trainable params: 433
Non-trainable params: 0
_________________________________________________________________
None
# মডেলকে ট্রেইন করি
model.fit(padded_reviews, labels, epochs=100, verbose=0)

১০০ ইপকের পর ভালো করে লক্ষ্য করুন, প্রতিটা লাইন হচ্ছে একেকটা শব্দের এমবেডিং। এখানে ওয়েটগুলোর অর্থ এখন খুঁজতে যাবো না এমুহুর্তে।

print(embedding_layer.get_weights()[0].shape)
print(embedding_layer.get_weights())
(50, 8)
[array([[-0.05265737, 0.11382533, 0.13469607, -0.1567499 , 0.13913564,
-0.08606423, -0.02678686, -0.07698093],
[ 0.02344643, 0.03931029, 0.04675907, -0.01921111, 0.02968276,
-0.00453656, 0.01263637, 0.01475035],
[-0.09201057, 0.13638408, -0.10323548, 0.06830094, -0.09231077,
-0.1324037 , -0.11571135, -0.07475247],
[-0.08681908, 0.10543869, -0.1482822 , 0.1491603 , -0.05974562,
-0.14459705, -0.11688408, -0.06188915],
[-0.02679553, -0.00559205, 0.01724127, -0.01354901, -0.02006083,
-0.04333702, -0.03456335, 0.04879484],
[ 0.01048509, 0.04804746, 0.01675353, -0.00327244, 0.00998007,
0.03570483, 0.00859393, -0.01275213],
[ 0.03552392, -0.00229199, -0.03412475, 0.04219795, 0.00482456,
-0.03159416, -0.00589075, -0.01066098],
[-0.02867593, 0.03261894, -0.00464242, -0.03158412, 0.0191541 ,
-0.03579491, 0.03718572, 0.03181786],
[ 0.12123187, 0.06596823, -0.12166881, 0.06698894, -0.14485735,
0.07797965, -0.11583629, -0.13997374],
[ 0.12766133, -0.14226912, 0.1280246 , -0.13345785, 0.10734078,
0.13667764, 0.10611239, 0.12766172],
[-0.01263506, 0.0456824 , 0.01305434, -0.03433765, -0.03328185,
0.03810361, -0.00140941, -0.03408922],
[ 0.02760527, -0.03518636, -0.0055925 , -0.01955928, 0.01609213,
0.01068318, -0.04387803, -0.00512721],
[ 0.00079768, 0.03258968, -0.01003956, 0.01334674, 0.03598468,
-0.02812053, -0.01648907, -0.04035139],
[ 0.03973031, -0.00060577, 0.00952489, 0.04756439, -0.03742932,
-0.00386493, 0.0414752 , -0.02521375],
[-0.03779197, -0.0071651 , 0.04498693, 0.00519955, 0.01166051,
-0.026939 , 0.01931213, 0.03768314],
[ 0.03159748, 0.04023388, -0.01872511, -0.03790357, -0.03320817,
0.02230166, 0.03325121, 0.00783906],
[ 0.04694238, -0.00746017, -0.03214586, -0.0041917 , -0.00440829,
-0.03374051, -0.00904058, 0.02770592],
[-0.12453386, 0.13165063, 0.07132025, -0.15561348, -0.13765155,
-0.13624804, -0.13847286, -0.0962723 ],
[-0.00603335, -0.04079239, 0.02889638, -0.03909565, 0.02205982,
0.00081826, 0.02584895, 0.0165041 ],
[ 0.01130301, -0.00623764, -0.02375997, 0.00419625, 0.04207266,
-0.0431019 , -0.04712569, 0.02074375],
[ 0.07099983, -0.16610928, 0.12713604, -0.1435597 , -0.13515739,
0.08648594, -0.14594217, 0.09531832],
[ 0.01898981, 0.04618858, -0.02122737, 0.00475402, -0.00572928,
0.03538061, -0.04839328, -0.02000936],
[ 0.0784552 , -0.09470227, 0.11757675, -0.13445877, 0.07080629,
0.0535766 , 0.05397027, 0.10108405],
[ 0.10054937, -0.13610163, 0.14758286, -0.07658476, 0.13340388,
0.10960875, -0.03239028, 0.09675319],
[ 0.12317961, 0.11413539, 0.04975319, 0.08235742, -0.11445854,
0.06858716, -0.04958973, -0.11873239],
[ 0.03394673, 0.00385653, -0.02558159, -0.0073772 , -0.00029033,
-0.02474689, 0.00900037, -0.00586033],
[-0.12778008, -0.06277162, -0.10699323, -0.13106872, 0.09945107,
-0.05960787, 0.05962683, 0.05685133],
[ 0.10931217, -0.0762489 , -0.1376228 , 0.07703073, 0.09215294,
0.13204688, 0.10688408, 0.14205813],
[-0.10745707, -0.05804486, -0.12429571, -0.08470646, 0.14617923,
-0.06040149, 0.08539802, 0.09242864],
[ 0.0166362 , 0.0112709 , -0.00660858, 0.00249185, -0.03338488,
-0.04912266, -0.00457531, -0.04159953],
[-0.01698687, -0.03746825, -0.01272637, -0.04153359, 0.02364327,
-0.01289871, 0.04172332, -0.02040068],
[ 0.01510055, -0.01989294, -0.00753764, 0.0194471 , 0.02778578,
-0.03580745, -0.00438815, -0.00738833],
[ 0.08136037, -0.07938047, -0.10438767, 0.10918117, -0.10893015,
0.07247414, -0.06342073, 0.1207428 ],
[ 0.01033301, 0.00350778, -0.0171818 , 0.04386062, -0.01932318,
0.04446277, 0.0385475 , -0.00931989],
[-0.01464739, -0.00180029, 0.01730946, -0.04577483, 0.02382242,
0.03708467, 0.00188329, -0.03966125],
[ 0.04023136, 0.02459527, 0.00907837, 0.01315198, 0.04567131,
0.02874405, -0.01617128, -0.01109319],
[-0.02977507, 0.02139706, 0.00186957, 0.01791834, 0.00894784,
0.04122653, -0.03935617, 0.04265356],
[-0.02135285, -0.00426116, 0.04024558, -0.01178813, 0.04960049,
-0.01828406, -0.02143255, -0.03571258],
[-0.04034765, 0.00618743, -0.03516948, 0.00280232, 0.02742325,
-0.03113339, 0.01847934, 0.01025729],
[-0.00046166, 0.01020212, 0.02667919, -0.01975745, 0.01335793,
0.0131151 , 0.00852842, 0.01841596],
[-0.14782608, 0.08766243, -0.10372343, 0.10650612, -0.12792553,
-0.0718106 , -0.10053294, -0.05392171],
[ 0.0225699 , -0.01571887, -0.03575055, -0.01409886, 0.04570072,
-0.0201815 , -0.03145782, -0.0124071 ],
[-0.00920724, 0.03542478, -0.04200239, -0.00488131, -0.04657997,
0.00091873, 0.00453923, 0.03274084],
[-0.14760956, -0.09304588, -0.14819124, -0.12078402, 0.09811702,
-0.06661346, 0.10395604, 0.10184725],
[-0.03379432, 0.02242908, -0.02023163, 0.0476172 , 0.01018156,
0.0101733 , -0.03601196, 0.03673008],
[-0.09486267, -0.07893033, -0.09789907, 0.06893685, -0.10443884,
-0.07708196, 0.14307283, 0.04352848],
[ 0.06240794, -0.07816311, 0.07161161, -0.12877683, 0.08804813,
0.06551442, 0.05856513, 0.12588711],
[-0.08864138, 0.10459234, -0.08691484, 0.10793619, 0.10224804,
-0.11210673, 0.10654733, -0.05361843],
[-0.04803792, 0.0181796 , 0.04984308, -0.00219319, -0.00834718,
-0.00299985, 0.01194783, 0.01612731],
[-0.02486729, -0.03019065, 0.00816693, 0.01028023, 0.04019772,
0.02498468, -0.01943016, 0.03106484]], dtype=float32)]

অ্যাক্যুরেসি কেন ১ এসেছে? কারণ, দশটা বাক্যের মধ্যে সবগুলোই ইউনিক। একটার সাথে আরেকটার মিল নেই। আর, এতো ছোট উদাহরণে আর কি হবে?

loss, accuracy = model.evaluate(padded_reviews, labels, verbose=0)
print(f'Accuracy: {accuracy}')
Accuracy: 1.0

আরেকটা উদাহরণ দেখি। এসেছে স্ট্যাকওভারফ্লো থেকে। আমরা যা আলাপ করেছি সেখানে ওয়ান হট এনকোডিং একদম চোখে দেখা হয়নি। খালি চোখে হট এনকোডিং এর ভেতরে দেখতে চাই।

from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, Dense, LSTM
import numpy as np

এখানেও আমরা একটা বাক্যকে ক্লাসিফাই করবো। 'ব্যাগ অফ ওয়ার্ড' (একটা ব্যাগে যতো শব্দ আছে) হচ্ছে একটা মেকানিজম যার মাধ্যমে টেক্সট থেকে ফিচার বের করা যায়। এটা আসলে আমাদের এই তিনটা উদাহরন থেকে যতো ইউনিক শব্দ আছে সেটা থেকে সে ট্রেনিং নেয়। তবে, আমরা তার থেকেও ভালো মডেল মানে সিকোয়েন্সের ওপর জোর দেবো।

num_classes = 5
max_words = 20
sentences = ['আমি আর আসছি না এখানে!',
'কথা শোনে না ওয়েটার',
'একদম ঠান্ডা খাবার']
# লেবেল তৈরি করি
labels = np.random.randint(0, num_classes, 3)
y = to_categorical(labels, num_classes=num_classes)
words = set(w for sent in sentences for w in sent.split())
word_map = {w : i+1 for (i, w) in enumerate(words)}
sent_ints = [[word_map[w] for w in sent.split()] for sent in sentences]
vocab_size = len(words)
print(vocab_size)
11

বাক্যের শব্দের সিকোয়েন্স, ৫, ৪, ৩

print(sent_ints)
[[6, 9, 8, 2, 7], [3, 5, 2, 1], [4, 11, 10]]

আমাদেরকে প্যাড করতে হবে max_words লেনথ ধরে, যা পাবো len(words) এবং ১ যোগ করে। +১ মানে হচ্ছে আমরা ০কে রিজার্ভ রাখবো যাতে সেটার বাইরে না যায়। এখানে ওয়ান হট এনকোডিং এবং প্যাডিং করছি -

# ট্রেনিং ডেটার X এর হিসেব
X = np.array([to_categorical((pad_sequences((sent,),
max_words)).reshape(20,),vocab_size + 1) for sent in sent_ints])
print(X.shape)
(3, 20, 12)

এখন দেখি আমাদের ওয়ান হট এনকোডিং এর অবস্থা? হাজারো ০।

print(X)
[[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.]]
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
[0. 0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]
[[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0.]]]
# y দেখি
print(y)
[[0. 0. 0. 0. 1.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 0. 1.]]

মডেলে ডেন্স লেয়ার যোগ করছি যাতে ওয়ান হট শব্দগুলোকে ডেন্স ভেক্টরে পাল্টে নেয়া যায়। তো LSTM এর কাজ কি? আমাদের বাক্যের ভেতরে শব্দের ভেক্টরকে কনভার্ট করতে হবে ডেন্স বাক্য ভেক্টরে। একদম শেষ লাইনে সফটম্যাক্স এক্টিভেশন ফাংশন ব্যবহার করছি যাতে ক্লাসের ওপর প্রবাবিলিটি ডিস্ট্রিবিউশন চালাতে পারে।

model = Sequential()
model.add(Dense(512, input_shape=(max_words, vocab_size + 1)))
model.add(LSTM(128))
model.add(Dense(5, activation='softmax'))
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
model.fit(X,y)
Train on 3 samples
3/3 [==============================] - 4s 1s/sample - loss: 1.5783 - accuracy: 0.3333
<tensorflow.python.keras.callbacks.History at 0x7efe930dda90>

শব্দ এবং সংখ্যার কাছাকাছি এনকোডিং

শব্দ যখন সংখ্যা হবে, তখন শব্দের মধ্যে যেরকম সম্পর্ক ছিলো, সেটা আরো ভালো বোঝা যাবে সংখ্যায়। শব্দে "আমি" "তুমি" যেমন কাছাকাছি, সংখ্যায় সেটা আরো পরিস্কার হবে। এমবেডিং এ এটা এমন ধরনের ‘ডেন্স’ রিপ্রেজেন্টেশন যার মধ্যে একই ধরনের শব্দের একই বা কাছাকাছি ধরনের এনকোডিং হবে। ‘পুরুষ’ এবং ‘মহিলা’ এই দুটো অক্ষরের মধ্যে আকাশ পাতাল পার্থক্য হলেও সম্পর্কের কারণে এদুটো কাছাকাছি থাকবে। সংখ্যার এনকোডিং ও একই ধরনের হবে। সবচেয়ে বড় কথা হচ্ছে এ ধরনের এনকোডিং ম্যানুয়ালি বা আমাদের হাতে লিখে দিতে হবে না। বরং যেহেতু এগুলো ট্রেনিংযোগ্য প্যারামিটার ফলে মডেলের ট্রেনিং এর সময় ওয়েটগুলো ‘এডজাস্ট’ হবে এদের সম্পর্কের ভিত্তিতে। একটা ছোট ডাটাসেটের ওয়ার্ড এম্বেডিং ৮ ডাইমেনশনাল হলেও সেটা বড় ডাটাসেটের জন্য ১০২৪ ডাইমেনশন পর্যন্ত যেতে পারে। যত বেশি ডাইমেনশনাল এম্বেডিং ততবেশি শব্দগুলোর মধ্যে সম্পর্কগুলোকে আরো ভালোভাবে বোঝা যাবে তবে তার জন্য প্রচুর ডাটা লাগবে শিখতে।

এমবেডিং এর প্রি-ট্রেইনড মডেল

আমরা যখন ‘ওয়ান হট’ এনকোডিং উদাহরণ দেখছিলাম, সেখানে প্রতিটা শব্দ একটা ৪ ডাইমেনশনাল ফ্লোটিং পয়েন্ট সংখ্যা ভেক্টর দিয়ে রিপ্রেজেন্ট করা হয়েছিল। আমরা এ ধরনের এম্বেডিংকে বলতে পারি ‘লুকআপ’ টেবিল। এই ‘লুকআপ’ টেবিলের ওয়েটগুলো শিখছে যখন আমরা ‘ডেন্স’ ভেক্টরের তার করেসপন্ডিং টেবিল দেখছি। ফলে সেটা ধরে আমরা প্রতিটা শব্দ ধরে এনকোডিং করছি। তবে, এখানে একটা বড় সমস্যা আছে। আমরা যখন এনকোডিং করবো তখন সব বাংলা শব্দকে একসাথে এনকোডিং না করলে আমরা কিভাবে একটা শব্দকে আরেকটা শব্দের সাথে সিমিলারিটি, শব্দগুলোর মধ্যে দূরত্ব বের করবো? তাদের মধ্যে সিমান্টিক এনালিসিস কে করবে? সব ডাটা একসাথে না হলে আমাদের কাজ হবে না। সে সমস্যা মেটাতে এসেছে প্রি-ট্রেইনড মডেল। কোটি কোটি শব্দকে একসাথে ট্রেইন করিয়ে তারপর এনকোডিং করেছে একসাথে। নিজে বানানো কঠিন। সেটার জন্য যে রিসোর্স দরকার সেটা নেই আমাদের কাছে। ফলে এধরনের প্রি-ট্রেইনড মডেলগুলো অসাধারণ পারদর্শী, কারণ সে ট্রেনিং এর সময় সব শব্দ হাতের কাছে পেয়েছে। শুধু নামিয়ে নিতে হবে দরকারের সময়ে। তাদের শক্তি এবং দুর্বলতা নিয়ে আলাপ করবো না এই বইয়ে।

এই প্রি-ট্রেইনড মডেল হিসেবে আমার প্রিয় ফেইসবুকের ফাস্টটেক্সট। ইংরেজি ছাড়াও আরো ১৫৭টা ভাষায় এর প্রি-ট্রেইনড মডেল আছে। বাংলা তো অবশ্যই। পুরোনো লাইব্রেরি হিসেবে ওয়ার্ড২ভেক এখনো জনপ্রিয়। পাশাপাশি পাইথনের জন্য স্পেসি আমার পছন্দের।

ধারণাগুলো এসেছে টেন্সর-ফ্লো ডকুমেন্ট, জেফ হিটনের নোটবুক এবং স্ট্যাকওভারফ্লো থেকে।

https://github.com/jeffheaton/t81_558_deep_learning/blob/master/t81_558_class_11_03_embedding.ipynb