টেনসরফ্লো ব্যবহার করে ন্যাচারাল ল্যাঙ্গুয়েজ প্রসেসিং

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

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

টোকেনাইজেশন

ASCII সম্পর্কে ধারণা থাকলে জানার কথা যে ইংরেজি বর্ণ গুলোকে একটা ইনডেক্সে সাজিয়ে প্রতিটা বর্ণের জন্য একটা নাম্বার এসাইন করা হয়েছে। যেমন A এর জন্য 65, B এর জন্য 66 ইত্যাদি।

বর্ণ গুলোকে সহজেই নাম্বার দিয়ে রিপ্রেজেন্ট করা যায়। প্রশ্ন আসতে পারে আমরা কিভাবে শব্দ গুলোকে রিপ্রেজেন্ট করব? একটা আইডিয়া হতে পারে প্রতিটা বর্ণের ASCII নাম্বার গুলো ব্যবহার করে। এখন প্রতিটা বর্ণের রিপ্রেজেন্টেটিভ নাম্বার গুলো ব্যবহার করতে গেলে একটা সমস্যা হতে পারে। কারণ অনেক গুলো শব্দ আছে যেগুলোতে একই বর্ণ ব্যবহার হয়েছে। যেমন same এবং seam দুইটা একই বর্ণ দিয়ে তৈরি। earth, hater, heart এই শব্দ গুলোও একই বর্ণ দিয়ে তৈরি। এমন শব্দ গুলোকে আসলে বর্ণের ASCII বা Unicode ভ্যালু ব্যবহার করে এনকোড করলে ওভারল্যাপ হবে। এমন অনেক গুলো Unscramble শব্দ থাকতে পারে। আপনি চাইলে এখানে গিয়ে আরো খুঁজে নিতে পারেন। আর তাই প্রতিটা শব্দের জন্য বর্ণ গুলোকে ওদের ASCII ভ্যালু দিয়ে এনকোড না করে পুরো শব্দটাকে আমরা একটা নাম্বার দিয়ে রিপ্রেজেন্ট করতে পারি।

যেমন ‘Life is short, take risk.’, এর শব্দ গুলোকে যদি আমরা এনকোড করে নেই, তাহলে Life হবে 1, is হবে 2, short হবে 3, take হবে 4, risk হবে 5। এরপর যদি আমরা ‘Life is short, have fun.’ যুক্ত করি, তাহলে শুধু have এবং fun কে এনকোড করতে হবে। তাহলে have হবে 6, fun হবে 7 আর এই প্রক্রিয়াকে বলা হয় টোকেনাইজেশন।

নিচে টেনসরফ্লো তে কিভাবে টোকেনাইজেশন করা যায়, তার দেখানো হলোঃ

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer

sentences = [
             'Life is short, take risk.',
             'Life is short, have fun.'
]

tokenizer = Tokenizer(num_words = 100)
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index
print(word_index)

যার আউটপুট হবে এমনঃ


{'life': 1, 'is': 2, 'short': 3, 'take': 4, 'risk': 5, 'have': 6, 'fun': 7}

এখানে num_words = 100 দিয়ে আমরা বলে দিচ্ছি সর্বোমোট কয়টা শব্দ নিয়ে কাজ করব। যাকে বলে ডিকশনারি সাইজ। যদি আমরা অনেক গুলো বাক্য দেই, যেখানে ১০০ এর বেশি শব্দ থাকে, তাহলে আমরা বলে দিচ্ছি যে সবচেয়ে বেশি ব্যবহৃত ১০০ শব্দ নিতে।

এভাবে আমরা চাইলে এরপর আরো নতুন বাক্য যোগ করতে পারি।

সিকোয়েন্স

আগের প্রোগ্রামে আমরা ওয়ার্ড গুলোকে টোকেনাইজেশন করলাম। যা আমাদের একটা ওয়ার্ড ইনডেক্স রিটার্ণ করবে। ওয়ার্ড ইনডেক্সকে ডিকশনারির সাথে তুলনা করতে পারি, যেখানে আমাদের সব গুলো ওয়ার্ড থাকবে এবং প্রতিটা ওয়ার্ডের রিপ্রেজেন্ট করার জন্য একটা করে নাম্বার থাকবে। এবার এখানে দেখব প্রতিটা ব্যাক্যে শব্দ গুলোর সিকোয়েন্সঃ

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer

sentences = [
             'Stay hungry, stay foolish.',
             'The future belongs to those who prepare for it today.',
       
             'Life is like a box of chocolates.'
]

tokenizer = Tokenizer(num_words = 100)
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index

sequences = tokenizer.texts_to_sequences(sentences)

print(word_index)
print(sequences)

যার আউটপুট পাবো এমনঃ

{'stay': 1, 'hungry': 2, 'foolish': 3, 'the': 4, 'future': 5, 'belongs': 6, 'to': 7, 'those': 8, 'who': 9, 'prepare': 10, 'for': 11, 'it': 12, 'today': 13, 'life': 14, 'is': 15, 'like': 16, 'a': 17, 'box': 18, 'of': 19, 'chocolates': 20}

[[1, 2, 1, 3], [4, 5, 6, 7, 8, 9, 10, 11, 12, 13], [14, 15, 16, 17, 18, 19, 20]]

এখন সিকোয়েন্স থেকে এবং ওয়ার্ড ইনডেক্স ব্যবহার করে আমরা সহজেই মূল বাক্য ফিরে পেতে পারি। যেমন [1, 2, 1, 3] এর মানে হচ্ছে Stay hungry, stay foolish.

আর এই সিকোয়েন্স ব্যবহার করে আমরা নিউরালনেট ট্রেইন করতে পারব।

এখন যদি আমরা এমন কোন ব্যাক্যের সিকোয়েন্স বের করতে চাচ্ছি, যে ব্যাক্যের সব গুলো শব্দ এনকোড করা হয়নি, তাহলে কি হবে?

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

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer

sentences = [
             'Stay hungry, stay foolish.',
             'The future belongs to those who prepare for it today.',
           
             'Life is like a box of chocolates.'
]

tokenizer = Tokenizer(num_words = 100)
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index

sequences = tokenizer.texts_to_sequences(sentences)

print(word_index)
print(sequences)

test_data = [
             'Stay hungry, stay foolish.',
             'A great man is always willing to be little.'
]

test_seq = tokenizer.texts_to_sequences(test_data)

print("\nTest sequence:", test_seq)

যার আউটপুট হচ্ছেঃ

{'stay': 1, 'hungry': 2, 'foolish': 3, 'the': 4, 'future': 5, 'belongs': 6, 'to': 7, 'those': 8, 'who': 9, 'prepare': 10, 'for': 11, 'it': 12, 'today': 13, 'life': 14, 'is': 15, 'like': 16, 'a': 17, 'box': 18, 'of': 19, 'chocolates': 20}

[[1, 2, 1, 3], [4, 5, 6, 7, 8, 9, 10, 11, 12, 13], [14, 15, 16, 17, 18, 19, 20]]


Test sequence: [[1, 2, 1, 3], [17, 15, 7]]

টেস্ট সিকোয়েন্সের প্রথম বাক্যে ছিল ৪টা শব্দ, দ্বিতীয় বাক্যে ছিল ৮টা শব্দ। কিন্তু টোকেনাইজ করার পর দেখলাম দ্বিতীয় বাক্যের মধ্যে মাত্র ৪টা শব্দ এনকোড করা হয়েছে। আমরা দেখব যে, যে শব্দ গুলো এনকোড করা নেই, সেগুলো সিকোয়েন্সে যুক্ত করা নাই। এর মানে হচ্ছে এভাবে মূল ব্যাক্যের লেন্থ কমে যাচ্ছে। লেন্থ ঠিক রাখার জন্য আমরা টোকেনাইজ করার সময় oov_token ব্যবহার করতে পারি। যার কাজ হচ্ছে নতুন শব্দ গুলোতে একটা ডামি টোকেন এসাইন করে দেওয়া।

টেনসরফ্লোতে oov_token এভাবে ব্যবহার করা হয়ঃ

tokenizer = Tokenizer(num_words = 100, oov_token=“<OOV")

এখানে num_words হচ্ছে ডিকশনারি সাইজ। মানে হচ্ছে আমরা সর্বোচ্চ ১০০ ওয়ার্ডের ডিকশনারি নিয়ে কাজ করব। সম্পূর্ণ একটা উদাহরণঃ

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer

sentences = [
             'Stay hungry, stay foolish.',
             'The future belongs to those who prepare for it today.',
             'Life is like a box of chocolates.'
]

tokenizer = Tokenizer(num_words = 100, oov_token="<OOV")
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index

sequences = tokenizer.texts_to_sequences(sentences)

print(word_index)
print(sequences)

test_data = [
             'Stay hungry, stay foolish.',
             'A great man is always willing to be little.'
]
test_seq = tokenizer.texts_to_sequences(test_data)

print("\nTest Sequence:", test_seq)

যার আউটপুট পাবো এমনঃ

{'<OOV': 1, 'stay': 2, 'hungry': 3, 'foolish': 4, 'the': 5, 'future': 6, 'belongs': 7, 'to': 8, 'those': 9, 'who': 10, 'prepare': 11, 'for': 12, 'it': 13, 'today': 14, 'life': 15, 'is': 16, 'like': 17, 'a': 18, 'box': 19, 'of': 20, 'chocolates': 21}

[[2, 3, 2, 4], [5, 6, 7, 8, 9, 10, 11, 12, 13, 14], [15, 16, 17, 18, 19, 20, 21]]

Test Sequence: [[2, 3, 2, 4], [18, 1, 1, 16, 1, 1, 8, 1, 1]]

এখন দেখব যে সিয়কোয়েন্সে যে সব নতুন শব্দ রয়েছে, সেগুলোতে একটা ডামি টোকেন এসাইন করে দিয়েছে।

প্যাডিং সিকোয়েন্স

নিউরাল নেট ট্রেইন করার জন্য সব গুলো ডেটার সাইজ সমান হতে হয়। তো আমরা যখন কোন বাক্যের সিকোয়েন্স বের করি, তখন দেখি এক একটা বাক্যের লেন্থ এক এক রকম। যেমন ‘I love my country’, এবং ‘I love traveling to explore the world.’ এর লেন্থ ভিন্ন। কিন্তু ট্রেইনিং এর জন্য আমাদের দুইটার লেন্থ সমান হতে হবে। আর এই সমস্যা অনেক ভাবেই সমাধান করা যায়।

সবচেয়ে বড় যে বাক্যটা রয়েছে, তার লেন্থ এর সমান করে বাকি বাক্য গুলোকে সমান লেন্থ করার জন্য 0 দিয়ে প্যাডিং করে নিতে পারি।

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences


sentences = [
            'I love my country',
             'I love cricket',
             'Sky is blue',
             'Life is like a box of chocolates.'

]

tokenizer = Tokenizer(num_words = 100, oov_token="<OOV")
tokenizer.fit_on_texts(sentences)
word_index = tokenizer.word_index

sequences = tokenizer.texts_to_sequences(sentences)

padded = pad_sequences(sequences)

print(word_index)
print(sequences)
print(padded)

test_data = [
             'I love my country',
             'I love traveling to explore the world'

]

test_seq = tokenizer.texts_to_sequences(test_data)
test_padd = padded = pad_sequences(test_seq)

print(test_seq)
print("\nPadded Sequence:",test_padd)

প্যাডিং করার পর সিকোয়েন্স print(padded) এর মাধ্যমে দেখতে পারি। আউটোপুট পাবো এমনঃ

{'<OOV': 1, 'i': 2, 'love': 3, 'is': 4, 'my': 5, 'country': 6, 'cricket': 7, 'sky': 8, 'blue': 9, 'life': 10, 'like': 11, 'a': 12, 'box': 13, 'of': 14, 'chocolates': 15}
[[2, 3, 5, 6], [2, 3, 7], [8, 4, 9], [10, 4, 11, 12, 13, 14, 15]]
[[ 0  0  0  2  3  5  6]
 [ 0  0  0  0  2  3  7]
 [ 0  0  0  0  8  4  9]
 [10  4 11 12 13 14 15]]
[[2, 3, 5, 6], [2, 1, 1, 14, 1, 1]]

Padded Sequence: [[ 0  0  2  3  5  6]
 [ 2  1  1 14  1  1]]

আমরা চাইলে প্যাডিং সিকোয়েন্সের আগে হবে না কি পরে হবে, তা সেট করে দিতে পারি। ডিফল্ট ভাবে সিকোয়েন্সের আগে প্যাডিং হয়। সিকোয়েন্সের শেষের দিকে করতে চাইলে padding=‘post’ দিয়ে করতে পারি। আবার সর্বোচ্চ কয়টা শব্দ থাকবে প্রতিটা সিকোয়েন্সে, তাও সেট করে দিতে পারি maxlen এর মাধ্যমে।

এছাড়া অনেক সময় দেখা যাবে একটা বিশাল বাক্য এসেছে টেস্ট বা ট্রেইন ডেটাতে যা ম্যাক্স লেন্থ থেকেও বড়। তখন ঐ বাক্যটাকে ছোট করার দরকার পড়ে। ঐ বাক্য থেকে তখন ম্যাক্স লেন্থ এর সমান শব্দ নিয়ে বাকি গুলো বাদ দিব। এখন বাক্যের শুরু দিকের শব্দ গুলো বাদ দিব নাকি শেষের শব্দ গুলো বাদ দিব, তা বলে দিতে পারি truncating এর মাধ্যমে।

padded = pad_sequences(sequences, padding='post', truncating='post', maxlen=10)

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

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

সেন্টিমেন্ট এনালাইসিস – Movie Review Detaset

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

ওয়ার্ড এমবেডিং (Word Embeddings)

আমরা উদাহরণ হিসেবে  Movie Review Detaset ব্যবহার করব। এখানে পজেটিভ এবং নেগেটিভ রিভিউ রয়েছে। তো আমরা রিভিউতে থাকা শব্দ গুলোকে দুই ভাগে ভাগ করতে পারি। পজেটিভ, অথবা নেগেটিভ। যদি প্লট করি, তাহলে হয়তো এভাবে প্লট করতে পারিঃ

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

আমরা মডেল তৈরি করার সময় tf.keras.layers.Embedding লেয়ার ব্যবহার করতে পারি। এটি তিনটি আর্গুমেন্ট নেয়ঃ

  • ভোকাবুলারি সাইজ
  • ডাইমেনশনের সংখ্যা
  • ইনপুট লেন্থ (সিকোয়েন্স সাইজ)

যেমন ওয়ার্ড এমবেডিং ব্যবহার করে একটা বেসিক সেন্টিমেন্ট এনালাইসিস মডেল এভাবে তৈরি করতে পারি। কোড গুলো কোল্যাবে রান করে দেখা যাবে। চাইলে নিজ কম্পিউটারে এনভারনমেন্ট তৈরি করেও রান করা যাবে। চাইলে গিটহাব থেকে ফাইল গুলো ডাউনলোড করে নিজ কম্পিউটারেও রান করা যাবে।

model = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(6, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])

এই পর্যন্ত আমরা যা শিখেছি, সব কিছু একত্র করে এবার পূর্ণাঙ্গ উদাহরণ দেখতে পারি এভাবেঃ

import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.model_selection import train_test_split
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer

# import data 
df = pd.read_csv('Train.csv')
df.head()

x=df['text']
y=df['label']

# train test split
X_train, X_test , Y_train, Y_test = train_test_split(x,y, test_size=0.2, random_state=50)


# preprocess 
tokenizer = Tokenizer(num_words = 100, oov_token="<OOV>")
tokenizer.fit_on_texts(X_train)
word_index = tokenizer.word_index
# print word index aka dictionary
print(word_index)



vocab_size = 5000
embedding_dim = 32
max_length = 75
trunc_type='post'
pad_type='post'
oov_tok = "<OOV>"

train_seq = tokenizer.texts_to_sequences(X_train)
train_pad_seq = pad_sequences(train_seq,maxlen=max_length,truncating=trunc_type, padding=pad_type)



valid_seq = tokenizer.texts_to_sequences(X_test)
valid_pad_seq = pad_sequences(valid_seq,maxlen=max_length)

training_labels_final = np.array(Y_train)
validation_labels_final = np.array(Y_test)


# creating model
model = tf.keras.Sequential([
    tf.keras.layers.Embedding(vocab_size, embedding_dim, input_length=max_length),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(6, activation='relu'),
    tf.keras.layers.Dense(1, activation='sigmoid')
])
model.compile(loss='binary_crossentropy',optimizer='adam',metrics=['accuracy'])
model.summary()

# train the model 

num_epochs = 10
history = model.fit(train_pad_seq, training_labels_final, epochs=num_epochs,
                    validation_data=(valid_pad_seq, validation_labels_final))


test_reviews = ["This movie is not good at all. I did not enjoyed much",
                "One of the best movie I have ever seen. Recommend everyone to watch this movie"]

# test the model
# create the sequences from test data

test_sequences = tokenizer.texts_to_sequences(test_reviews)
test_padded = pad_sequences(test_sequences, padding=pad_type, maxlen=max_length)

classes = model.predict(test_padded)

# Closer to 1 means positive
for x in range(len(test_reviews)):
  print(test_reviews[x])
  print(classes[x])
  print('\n')

উপরের প্রোগ্রাম রান করলে আমরা কিছুটা এমন আউটপুট পাবোঃ

This movie is not good at all. I did not enjoyed much
[0.4522577]
One of the best movie I have ever seen. Recommend everyone to watch this movie
[0.72242755]

এখানে 1 এর কাছাকাছি মানে হচ্ছে পজেটিভ রিভিউ। 0 এর কাছাকাছি মানে হচ্ছে নেগেটিভ রিভিউ। উপরের দুইটা টেস্ট রিভিউ পড়লেই বুঝা যায় কোনটা পজেটিভ, কোনটা নেগেটিভ। আর আমাদের মডেল মোটামুটি একুরেট প্রিডিকশন করতে পারছে। যদিও উপরের মডেলের ভ্যালিডেশন একুরেসি হচ্ছে 0.6230, এর মানে এই মডেল ইম্প্রুভ করার সুযোগ রয়েছে। পরবর্তীতে আমরা শিখব কিভাবে এই মডেলকে ইম্প্রুভ করা যায় বা ইম্প্রুভের জন্য অন্য কোন অ্যালগরিদম ব্যবহার করা যায়। মেশিন লার্নিং নিয়ে আরো বেশি কিছু লেখা রয়েছে এই ব্লগে। দেখতে পারেন এখান থেকে।

Leave a Reply