যা যা আছে

Feed-forward ANN - 3
(Data থেকে Training)

VersicolorSetosaVirginica


আগের পোস্টে আমরা শিখেছি, কি করে একটা Artificial Neuron (AN) জুড়ে জুড়ে একটা fully connected বা linear layer বানানো যায়। তারপর তার ওপর চাপানো যায় activation function (AF), যা আমরা Mathematicaয় করি element-wise layer দিয়ে। এই ধরনের layer এর পর layer জুড়ে জুড়ে তৈরি করা যায় একটা fully connected artificial neural network, যেটা কিনা একটা feed-forward network ও বটে। এই নেটওয়ার্ক-এর একদিকে input দিলে, আর যেকোন random weight আর bias (bias সাধারণত 0 থাকে) দিয়ে নেটটা initialize করলে, আমরা একটা output পাই নেটের অন্যদিকে। আমরা এটাও ঠিক করেছি, যে একটা classification-এর কাজ করাবো একটা ANN বানিয়ে। তার জন্যে আমাদের দরকার একটা curated data সেট। আমাদের প্রথম প্র্যাক্টিসের জন্যে আমরা ব্যবহার করবো ‘Fisher’s Iris Data-set’।

অন্য কথা:
1920-র দশকের ব্রিটেনের এক বিকেল। লন্ডনের কিছু উত্তরে Rothamsted-এর এক কৃষিবিজ্ঞান গবেষণাগারে এক অঙ্কবিদ তাঁর জীববিজ্ঞানী বান্ধবীকে এক কাপ চা বানিয়ে এগিয়ে দিলেন। মহিলা নাক কুঁচকে সরাসরি প্রত্যাখ্যান করলেন সেই চায়ের কাপ। কারণ? চা-টা বানানোর সময় কাপে আগে চা ঢালা হয়েছিলো, পরে দুধ। সেটা ঠিক নিয়ম নয়। আগে দুধ দিয়ে পরে চা ঢালাই দস্তুর। মহিলা তাই ও-জিনিস খাবেন না। হতভম্ব mathematician পরিষ্কার বললেন, দুই-ই এক। স্বাদে কিছুই টের পাওয়া যায় না। শুরু হলো তর্ক। যোগ দিলেন রসায়নশাস্ত্রবিদ (যিনি আবার মহিলার ভবিষ্যত স্বামী)। তিনজনেই সিদ্ধান্তে উপনীত হলেন যে, পরীক্ষা প্রার্থনীয়। আট কাপ চা করে আনা হলো, চার কাপে দুধ আগে, চা পরে। অন্য চার কাপে তার উলটো। ততক্ষণে কিছু উৎসাহী দর্শকও জোগাড় হয়ে গেছে। সবার সামনে অঙ্কবিদকে হতবাক করে বান্ধবী প্রতিটি চায়ের কাপের সঠিক ক্রম বলে দিলেন ম্যাজিকের মতো।

ব্যাপার এখানেই শেষ হতে পারতো, কিন্তু লজ্জিত অঙ্কবিদের মাথা থেকে কিছুতেই বেরোচ্ছিলো না এই সম্ভাবনা – পুরোটাই যদি নেহাত কাকতালীয় ব্যাপার হয়? হিসেব নিকেশ শুরু হলো, আর কি কি ভাবে গোটা পরীক্ষাটা তৈরি করা যেতে পারতো, যাতে দৈবাৎ ঠিক উত্তর পাওয়ার সম্ভাবনা কমানো যায়? দিবারাত্রি এই একই বিষয়ের উপর গবেষণা চলতে লাগলো। যত ভাবলেন, জট ছাড়লো, আর পরিষ্কার হলো ধারণা। পুরো গবেষণার ফলাফল প্রকাশ করতে লাগলো দু’টো পুরো বই: Statistical Methods for Research Workers আর The Design of Experiments (দ্বিতীয় বইটার কিছু অংশ এখানে পাওয়া যাবে)। আজকের জগতের Frequentist statistical analysis-এর প্রায় সমস্ত গুরুত্বপূর্ণ ধারণার উৎস ওই বই। ভদ্রলোকের নাম Sir Ronald A. Fisher। আধুনিক statistics-এর জনক। আমাদের কাজের জন্যে প্রয়োজনীয় Iris dataset-এর প্রণেতা।

আজ আমরা জানি যে চা আর দুধের ক্রম আলাদা হলে আলাদা রাসায়নিক বিক্রিয়া হয় আর স্বাদে বেশ পার্থক্য হয়। অতএব, Muriel Bristol প্রতিবারই হয়তো ঠিক বলতে পারতেন। কিন্তু ওই বাজি যদি কেউ জিতে থাকে, তা মানবসভ্যতা। তবু, সতর্কবাণী - সকলেই ভুলে ভরা মানুষ আর সকলেই তার সময়ের সন্তান। এহেন মেধাবী মানুষটিও সারাজীবন Eugenics-এর মতো ফালতু ধারণার হয়ে লড়াই করেছেন। কিন্তু আজ থাক সে কথা। সঙ্গের গল্প আর তার আনুষঙ্গিক ঘটনাবলীর বিবরণ পাওয়া যাবে Royal Statistical Society-র এই পেপারে, আর এই মিষ্টি ব্লগ-পোস্টে

Fisher’s Iris Data-set: স্কুলে জীবনবিজ্ঞানের ক্লাসে ‘ফুলের বিভিন্ন অংশ’ পড়ার সময় কখনো মনে হয়েছিলো, যে biology না পড়লেও এইসব জ্ঞান কখনো আবার কাজে লাগবে? আমার তো হয়নি। সে যাক গে, সঙ্গের ছবিগুলো থেকে ঝালিয়ে নাও, ফুলের sepal আর petal কারে কয়। তা, আমাদের এই data-set-এ Iris গোত্রের তিন প্রজাতির (Setosa, Versicolor, আর Virginica) ফুলের sepal আর petal-এর দৈর্ঘ্য আর প্রস্থ দেওয়া আছে। প্রতি প্রজাতির থেকে 50টা করে উদাহরণ আছে। বোঝাই যাচ্ছে, একই প্রজাতির নানা রকম ফুলেরও এই মাপগুলো সমান নয়। আমাদের প্রশ্ন, কোন একটা নতুন Iris ফুলের এই চাররকমের মাপ পেলে, ফুলটা কোন প্রজাতির, সেটা বোঝা কি আমাদের পক্ষে সম্ভব?
Interactive Version:--->


Mathematicaর যেকোন installation-এ আমাদের ব্যবহার করার জন্যে বেশকিছু data, সাজিয়ে-গুছিয়ে রাখা আছে। সেগুলো নানা বিষয়ের, নানা কাজের হতে পারে। সাধারণত, ExampleData functionটা ব্যবহার করে সেগুলো ব্যবহার করতে হয়। এ ছাড়াও, অনেক ধরনের data, যা মেশিনে লোড করা নেই, তা সরাসরি internet থেকে ডাউনলোড করা যায়। একবার ডাউনলোড করলেই সেটা মেশিনের Mathematica installation-এ থেকে যাবে। এটা করা হয় ResourceObject ব্যবহার করে (আশ্চর্য ব্যাপার হলো, পোস্ট লেখার সময়, Wolfram Data Repository তে, অর্থাৎ ResourceObject যেখান থেকে data নামায়, Iris dataয় একটা ভয়ানক ভুল আছে। 4 রকমের input: PetalLength, PetalWidth, SepalWidth, আর SepalLength এর মধ্যে PetalWidthটা বেমালুম নেই। Feedback দিয়েছি। ঠিক করেছে দেখতে পেলে পোস্টে কমেন্ট কোরো। সেক্ষেত্রে এই অংশের লেখাটা সরিয়ে দেব)। আমাদের data শুরু থেকেই যেহেতু মেশিনে আছে, তাই এইভাবে সেটা ডাকবো:

In[1]:= obj = ExampleData[{"MachineLearning", "FisherIris"}]
Out[1]:= Fisher's iris data.

সঙ্গের notebook-এ এই dataset নিয়ে নেড়েঘেঁটে দেখা আর ব্যবহার করার সবিস্তার উদাহরণ দেওয়া আছে। Stratified sampling করে পুরো data-কে 70% - 30% এ ভাগ করা হয়েছে আর তাদের যথাক্রমে Training আর Test Data বলা হয়েছে। কিছু উপরের gif –এ দেখো, বিভিন্ন feature এর scatter-plot দেওয়া আছে, সব species এর জন্যে। তার পরের ছবিতে সেই feature গুলোর mean দেওয়া আছে, তুলনামূলক চার্টে। এই data কেমন দেখতে, তা জানার জন্যে, Training Data থেকে 5 sample randomly তুলে আনা যাক:

In[2]:= dataE = ExampleData[{"MachineLearning", "FisherIris"}, "TrainingData"];
In[3]:= RandomSample[dataE, 5]
Out[3]:= {{6.3, 2.8, 5.1, 1.5} -> virginica, {6.4, 3.1, 5.5, 1.8} -> virginica, {6.3, 2.3, 4.4, 1.3} -> versicolor, {5.5, 2.5, 4., 1.3} -> versicolor, {4.4, 3.2, 1.3, 0.2} -> setosa}

তাহলে বোঝা গেলো, প্রতিটি data-point আসলে একটা 4-length Vector, আর তার label (যা ব্যবহার করে training হবে) হলো একটা string, যা species-এর নাম বোঝায়। বেশ, তবে আগের জ্ঞান কাজে লাগিয়ে একটা ANN বানিয়ে ফেলা যাক? ছোট্ট dataset, features আর class-এর সংখ্যাও বেশি না – একটা খুব ছোট নেটওয়ার্কই এর জন্যে যথেষ্ট (network বানানোর বিশদ – পরে আলোচনা হবে) – প্রথমে একটা 10-neuron এর linear layer, একটা activation layer (আপাতত একটা Sigmoid), তারপর একটা 3-neuron এর layer, প্রতি species-এর জন্যে একটা:

In[4]:= net1 = NetChain[{10, LogisticSigmoid, 3, SoftmaxLayer[]}]
Out[4]:=


কিন্তু দাঁড়াও! সবার শেষে ওই SoftmaxLayer-টা আবার কোথা থেকে এলো? মাথায় রাখা দরকার, আমরা classify করতে বসেছি, আর LinearLayer-এর output হবে কতগুলো সংখ্যা, যাদের value, \( W \) আর \( B \) এর ওপর নির্ভর করে যা ইচ্ছে হতে পারে। এই সংখ্যাটা যে ক্লাসের সবচেয়ে বেশি হবে, আমাদের input-এর সেই ক্লাসে থাকার probability সবচেয়ে বেশি, তাই না? তার জন্যে সংখ্যাগুলোর মধ্যে যেটা সবথেকে বেশি, সেটা বাছলেই হলো। কিন্তু না, আমাদের দরকার ওই সংখ্যাগুলোকে probability-র চেহারা দেওয়া, যাতে একেকটা output দেখে আমরা জানতে পারি, বসানো input-এর কোন class-এ (এক্ষেত্রে species) থাকার probability কত। Softmax function, যাকে normalized exponential function-ও বলা হয়, কি করে? যেকোন \( K \) সংখ্যক input \( x_i \) পেলে (তারা \( < 0 \), \( > 1 \) – যা ইচ্ছে হতে পারে, আর তাদের যোগফল 1 না-ও হতে পারে, যেমন কিনা probability হলে হওয়ার কথা) তাদের দিয়ে একটা probability distribution বানায়, যাতে প্রত্যেক output, 0 থেকে 1 এর মধ্যে থাকে, আর তাদের যোগফল ঠিক 1 হয়। এইভাবে:

\[ S(\vec{x})_i = \frac{e^{x_i}}{\sum_{i=1}^K e^{x_i}} ~~~\forall x_i \in \mathbb{R}^K \]

এইরকম নামের কারণ আর কিছুই না। যে computer function দিয়ে কোন একটা mathematical function কোথায় maximum তা খুঁজে বের করা হয়, তার নাম argmax, আর এটা, তার ‘soft’, probabilistic ভার্সন। বোঝা গেলো, এবার তবে আর টালবাহানা না করে training করানো যাক। এটা করে NetTrain। আপাতত এটুকু জানলেই চলবে, যে training করতে NetTrain-এর লাগবে একটা training dataset আর একটা নেটওয়ার্ক। দুই-ই আছে আমাদের কাছে:

In[5]:= trnet1 = NetTrain[net1, dataE]
Out[5]:=



Output-এর প্রথম gif-টা হলো তুমি training চালালে যা দেখতে পাবে। Operating System (OS), CPU speed, Mathematica Version – এ সবের ওপর নির্ভর করে এগুলো বেশ আলাদা হতে পারে, কিন্তু একটা Windows-X মেশিনে Mathematica 12.1 চালালে কমবেশি এটাই দেখতে পাওয়ার কথা। অনেক ঘটনা ঘটছে আর অনেক অজানা নাম দেখে ভয় পাওয়ার কারণ নেই – এ সবই আমরা ধীরে ধীরে বুঝবো। পরের ছবিটা হলো training শেষ হওয়ার পর তুমি যা দেখতে পাবে। এটা তোমার নেটওয়ার্কটার-ই চেহারা, কিন্তু এখন trnet1 একটা ‘trained’ network, যাকে যেকোন Iris ফুলের 4 রকম feature দিয়ে দিলে উত্তরে তার species এর নামটা বেরিয়ে আসার কথা। dataE-এর থেকে যেকোন একটা উদাহরণ নিয়ে তার ওপর চালিয়ে দেখো, তোমার জানা উত্তরের সঙ্গে নেটের output মিলছে কি না…

কখনো কখনো না-ও মিলতে পারে – আমরা তো জানি-ই, যে মেশিনের accuracy 100% নয়। কিন্তু, তা-ও, যে data দিয়ে train করলাম, তার ওপরেই চালিয়ে দেখবো? কেমন গঙ্গাজলে গঙ্গাপুজো হয়ে যাচ্ছে না? তার-ও সমাধান আছে। ওই যে, dataset-এর মধ্যে test data একটা সেট জমা করা আছে, সেটা তো training data-র থেকে আলাদা, আর তোমার নেটও সে-সব আগে দেখেনি। তাহলে তার থেকেই কিছু নিয়ে চালিয়ে দেখো। মজা বাকি আছে। আগের পোস্টের শেষে ‘কেমন?’ অংশে আমরা জেনেছিলাম না, মেশিন classification-এর পরীক্ষায় কত ভালভাবে পাশ করলো, জানা দরকার? তার জন্যে অঢেল পরীক্ষার একসঙ্গে ব্যবস্থা করা আছে ClassifierMeasurements নামের একটা function-এ। এ নিয়ে আমাদের পরে অনেক তলিয়ে জানতে হবে, কিন্তু আপাতত আমরা শুধু জানতে চাই, আমাদের নেটের accuracy কত – অর্থাৎ, শতকরা কতগুলো উদাহরণকে ও ঠিকভাবে classify করতে পারে। সে জন্যে ClassifierMeasurements কে পুরো test dataset-এর ওপর একেবারে চালিয়ে নেওয়া যাক:

In[6]:= dataTest = ExampleData[{"MachineLearning", "FisherIris"}, "TestData"];
In[7]:= ClassifierMeasurements[trnet1, dataTest, "Accuracy"]
Out[7]:= 0.977778


এই শেষ বারের মতো বলছি, আর বলবো না – এই উত্তরটা আমার নিজের মেশিনের। এ সব ক্ষেত্রে তোমার উত্তর আলাদা আসতেই পারে, ঘাবড়ানোর কিছু নেই। তাহলে আমাদের নেটের accuracy দাঁড়ালো ~ 97.78%। বেশ ভাল! আরও মজার কথা, Mathematica-র নিজের automated classification-এর জন্যে যে ‘Classify’ function-টা আছে, এই dataset-এ তার accuracy-ও এটাই!

net1’ আমাদের untrained net, আর ‘trnet1’ হলো তার training এর পরের চেহারা। আমরা আগেই জেনেছি, fully-connected বা linear layer বানানোর পরে, তাকে initialize করা যায়। net1-এর ওপর NetInitialize চাপিয়ে দেখো, output আসবে - ‘$Failed’। সঙ্গে error message –

In[6]:= NetInitialize@net1
NetInitialize::nninit: Cannot initialize net: unspecified or partially specified shape for array "Weights" of first layer.
Out[8]:= $Failed

কি হলো ব্যাপারটা? Initialize-ই যদি না হবে, NetTrain তবে training করালো কি করে? আরও আছে – আমাদের নেটের output যে একটা probabilistic number distribution, সে তো আমরা দেখতেই পেয়েছি। কিন্তু তারা তো label (যাতে species-এর নাম আছে) নয়! তাহলে, trnet1 কোন 4-length vector এর ওপর চাপালে label string-এ উত্তর আসছে কি করে? তাছাড়া, network-এর width (layer-এ neuron-এর সংখ্যা) আর depth (layer-এর সংখ্যা) বদলালে accuracy কিভাবে বদলায়? সেটা ঠিকই বা করবো কি করে? আর সব্বার থেকে বড় প্রশ্ন, এই অদ্ভুতুড়ে ‘training’-এর সময় ঠিক হচ্ছেটা কি?

আমরা আসলে সবে frozen pizza গরম করে খেতে শিখেছি। Pizza কি করে বানাতে হয়, এবার তা শিখতে হবে। ক্রমশ…

No comments