Part 02 · Machine Learning

Feature Engineering

ScalingOne-hot encodingMissing valuesData leakage

Model শুধু সেই সংখ্যা দেখে যেটা তুমি দাও। Scaling, category encode করা, ফাঁকা value পূরণ, আর data leakage, যে bug একটা অকেজো model কেও perfect দেখায়।

⏱ 1-2 ঘণ্টা · Station 11 / 92

জিনিসটা কী, কেন দরকার

Model কখনো একটা flat দেখে না। দেখে তুমি তাকে যে সংখ্যাগুলো দাও। "1,230 sqft, 4 bedroom, Uttara, bedroom অজানা" কে আগে একটা সংখ্যার row বানাতে হয়, আর row টা কীভাবে বানাচ্ছো তার উপর অনেকটাই নির্ভর করে model কী শিখতে পারবে। এই কাজটাই feature engineering।

Table এর data তে ভালো feature সাধারণত জটিল model এর চেয়ে বেশি কাজ দেয়। আর এখানকার একটা ভুল, data leakage, হলো সবচেয়ে common কারণ যেজন্য একটা model test এ দারুণ দেখায় আর production এর প্রথম দিনেই ফেল করে। চারটা কাজে প্রায় সবটা ধরা যায়: সংখ্যা scale করা, category encode করা, missing value সামলানো, আর ভবিষ্যৎকে training data থেকে দূরে রাখা।

ভেতরে কীভাবে চলে

Scaling। Size হাজারের ঘরে, bedroom এক অঙ্কে। দূরত্ব ব্যবহার করে এমন যেকোনো কিছু (k-NN, k-means) বা gradient descent 30 sqft এর পার্থক্যকে 2 bedroom এর পার্থক্যের চেয়ে বড় ধরবে। প্রতিটা feature standardise করো: (x − mean) / std, যাতে সব feature এর mean 0 আর spread 1 হয়। Tree এর এটা লাগে না; দূরত্ব আর gradient নির্ভর method এর লাগে।

Category। "Gulshan, Mirpur, Uttara" সংখ্যা না। Label encoding (0, 1, 2) এমন একটা ক্রম আর দূরত্ব বানিয়ে দেয় যেটার কোনো অস্তিত্ব নেই: Uttara হয়ে যায় Gulshan থেকে Mirpur এর "দ্বিগুণ দূরে"। One-hot encoding প্রতিটা এলাকাকে নিজের 0/1 column দেয়, তাই সব এলাকা সমান আলাদা। হাজার হাজার মানের category তে (user ID, product ID) one-hot বেশি চওড়া হয়ে যায়; তখন embedding (Part 4) বা target statistics নাও।

Missing value। উপায়গুলো: row টা বাদ দাও (data হারায়), training data র median দিয়ে ভরো (outlier এ টলে না), বা ভরো আর সাথে একটা was_missing flag column রাখো। Flag টা জরুরি কারণ কোনো মান না থাকাটাই প্রায়ই একটা সংকেত: যে বাড়িওয়ালা bedroom এর সংখ্যা লেখেনি, সে হয়তো কিছু লুকাচ্ছে।

Data leakage মানে training data তে এমন তথ্য থাকা যেটা আসলে model ব্যবহারের সময় থাকবে না। দুইটা common ধরন:

  • ভবিষ্যতের তথ্য: এমন column যেটা result হয়ে যাওয়ার পরে ভরা হয়। "Refund requested" cancel হওয়া একদম ঠিকঠাক predict করে, কারণ মানুষ cancel করার পরে refund চায়।
  • Test data training এ ঢুকে পড়া: scaling এর mean আর std (বা ভরার median) পুরো data দিয়ে হিসাব করা, test set সহ। প্রতিটা transformation শুধু training data দিয়ে fit করো, তারপর test data তে লাগাও।
আগে অনুমান করো

একটা hospital এর model patient record এর column দিয়ে predict করে কার intensive care লাগবে। Test এ 98% পায়। কোন column টা নিয়ে সবচেয়ে বেশি চিন্তা করা উচিত?

ছবিতে দেখো

time →last monthlogins: 2day 0: predictwill they cancel?day 20cancelsday 21refund requestedtraining rowfeatures the model seeslogins = 2refund = 1known on day 0: fineonly known after cancellingleak: copied from the future100% in testing, useless live
Model prediction দেয় day 0 তে। গত মাসের login তখন জানা, এটা ঠিকঠাক feature। Customer cancel করে day 20 এ, refund চায় day 21 এ। "Refund requested" কে training row তে ফিরিয়ে আনা মানে model কে ভবিষ্যতের তথ্য দিয়ে দেওয়া: test এ perfect, live এ কোনো কাজের না।

Code এ চারটা কাজ

চারটা কাজই ছোট ছোট উদাহরণে: scaling সহ আর ছাড়া কোন flat "সবচেয়ে কাছের", label বনাম one-hot encoding, flag সহ median দিয়ে ভরা, আর একটা leaky feature যেটা দেখতে perfect।

import math
import statistics

# Flats: size (sqft), bedrooms, district, rent (thousand taka). One bedroom count is missing.
FLATS = [
    (1200, 3, "Mirpur", 22), (1250, 2, "Mirpur", 20), (900, 2, "Uttara", 18),
    (1800, 4, "Gulshan", 95), (1150, 3, "Uttara", 25), (1400, 4, "Uttara", 30), (1750, None, "Gulshan", 90),
]

# 1) Scaling. Which flat is most like a new 1230 sqft, 4-bedroom flat?
query = (1230, 4)
def nearest(rows, q):
    return min(rows, key=lambda r: math.dist(r[:2], q))
complete = [f for f in FLATS if f[1] is not None]
print("1) nearest flat, raw numbers:   ", nearest(complete, query)[:3])
sizes = [f[0] for f in complete]
beds = [f[1] for f in complete]
ms, ss = statistics.mean(sizes), statistics.pstdev(sizes)
mb, sb = statistics.mean(beds), statistics.pstdev(beds)
scale = lambda s, b: ((s - ms) / ss, (b - mb) / sb)
scaled = [(*scale(f[0], f[1]), f) for f in complete]
best = min(scaled, key=lambda r: math.dist(r[:2], scale(*query)))
print("   nearest flat, scaled numbers:", best[2][:3])

# 2) Categories. Label encoding invents an order; one-hot keeps districts equally different.
label = {"Gulshan": 0, "Mirpur": 1, "Uttara": 2}
onehot = {d: tuple(int(d == k) for k in label) for d in label}
print("\n2) label encoding:", label)
print("   'distance' Gulshan-Mirpur", abs(label["Gulshan"] - label["Mirpur"]),
      " Gulshan-Uttara", abs(label["Gulshan"] - label["Uttara"]))
print("   one-hot:", onehot)
print(f"   distance Gulshan-Mirpur {math.dist(onehot['Gulshan'], onehot['Mirpur']):.3f}"
      f"  Gulshan-Uttara {math.dist(onehot['Gulshan'], onehot['Uttara']):.3f}")

# 3) Missing values: fill with the training median, and keep a flag so the model knows.
median_beds = statistics.median(beds)
filled = [(s, b if b is not None else median_beds, int(b is None), d, r) for s, b, d, r in FLATS]
print(f"\n3) median bedrooms = {median_beds}; filled row: {filled[-1]}")

# 4) Leakage. Predict which customers will cancel next month.
# 'refund_requested' is only ever filled in AFTER a customer has decided to cancel.
CUSTOMERS = [  # (logins last month, refund_requested, cancelled)
    (2, 1, 1), (15, 0, 0), (1, 1, 1), (12, 0, 0), (3, 1, 1), (20, 0, 0),
    (4, 1, 1), (9, 0, 0), (2, 0, 0), (5, 1, 1), (18, 0, 0), (6, 1, 1),
]
acc = lambda rule: sum(rule(c) == c[2] for c in CUSTOMERS) / len(CUSTOMERS)
print(f"\n4) rule 'refund_requested == 1':  {acc(lambda c: c[1]):.0%} accurate on history")
print(f"   rule 'logins < 7':             {acc(lambda c: int(c[0] < 7)):.0%} accurate on history")
print("   at prediction time nobody has requested a refund yet, so the first rule says 'stays' for everyone")

Output:

1) nearest flat, raw numbers:    (1250, 2, 'Mirpur')
   nearest flat, scaled numbers: (1400, 4, 'Uttara')

2) label encoding: {'Gulshan': 0, 'Mirpur': 1, 'Uttara': 2}
   'distance' Gulshan-Mirpur 1  Gulshan-Uttara 2
   one-hot: {'Gulshan': (1, 0, 0), 'Mirpur': (0, 1, 0), 'Uttara': (0, 0, 1)}
   distance Gulshan-Mirpur 1.414  Gulshan-Uttara 1.414

3) median bedrooms = 3.0; filled row: (1750, 3.0, 1, 'Gulshan', 90)

4) rule 'refund_requested == 1':  100% accurate on history
   rule 'logins < 7':             92% accurate on history
   at prediction time nobody has requested a refund yet, so the first rule says 'stays' for everyone

কী দেখলাম:

  • Scaling answer বদলে দেয়। কাঁচা সংখ্যায় (1,230 sqft, 4 bedroom) এর সবচেয়ে কাছের flat হলো একটা 2 bedroom এর flat, কারণ 20 sqft এর পার্থক্য 2 bedroom এর চেয়ে ভারী। Standardise করার পরে সবচেয়ে কাছের হলো 1,400 sqft এর 4 bedroom এর flat, একজন মানুষ যেটা বাছত।
  • Label encoding বলে Gulshan থেকে Mirpur 1 দূরে আর Uttara 2 দূরে। আজগুবি কথা, কিন্তু model আনন্দে শিখে নেবে। One-hot প্রতিটা জোড়াকে 1.414 দূরে রাখে: সমান আলাদা।
  • ভরা missing row তে median, মানে 3 bedroom বসায় আর was_missing column এ 1 দেয়, যাতে model বুঝতে পারে মানটা আন্দাজ করা।
  • Leakage: "refund requested" ইতিহাসে 100% ঠিক, সৎ login এর rule (92%) এর চেয়েও ভালো। কিন্তু যেদিন prediction টা দরকার, সেদিন কেউ refund চায়নি, তাই leaky model সবার জন্য "থাকবে" বলে, কাউকেই ধরে না।

Production এ যা মাথায় রাখবে

  • Training আর serving এ একইভাবে transform করতে হবে। Training এর ঠিক সেই mean, std, median আর category list save করো, prediction এর সময় সেগুলোই লাগাও। Production এ আলাদা scaling একটা নীরব bug: কোনো error নেই, শুধু ভুল prediction। Pipeline (scikit-learn Pipeline, feature store) মূলত এই কারণেই আছে।
  • অজানা category আসবেই। Training এর পরে নতুন একটা এলাকা এলো। কী হবে আগে ঠিক করো: একটা "other" column, নয়তো সব শূন্য। Service crash করতে দিও না।
  • প্রতিটা feature এর সময় check করো। প্রতিটা column এর জন্য জিজ্ঞেস করো "prediction এর মুহূর্তে কি আমি এই মান জানতাম?" নিশ্চিত না হলে বাদ দাও। কঠিন problem এ অবিশ্বাস্য ভালো score (99%+) মানে সাধারণত leakage।
  • Transform এর আগে ভাগ করো। আগে train আর test এ ভাগ, scaler আর filler train এ fit, তারপর test এ লাগাও। সময় নির্ভর data তে সময় ধরে ভাগ করো।
  • Feature ও code। Version করো, test করো, আর live এ তাদের distribution চোখে রাখো। যে feature এর গড় হঠাৎ দ্বিগুণ হয়ে যায়, সেটা সাধারণত upstream এর bug, দুনিয়া বদলায়নি।

এক কথায়

Model শুধু তোমার দেওয়া সংখ্যা দেখে: সংখ্যার feature scale করো, category one-hot করো, missing value training এর statistics আর একটা flag দিয়ে ভরো, আর কোনো feature কে prediction এর মুহূর্তের পরের তথ্য বহন করতে দিও না।

নিজে করে দেখো

তুমি predict করছো food delivery দেরি হবে কিনা। এই column গুলোকে "safe" আর "leakage" এ ভাগ করো: গত সপ্তাহে restaurant এর গড় রান্নার সময়, customer পর্যন্ত দূরত্ব, delivery র পরে এই customer rider কে যে rating দিয়েছে, দিনের কোন সময় order হয়েছে, আসল delivery তে কত মিনিট লেগেছে, customer complain করেছে কিনা।

Answer

Safe (order দেওয়ার সময়েই জানা): গত সপ্তাহের গড় রান্নার সময়, দূরত্ব, দিনের সময়। Leakage (শুধু delivery র পরে জানা): customer এর দেওয়া rider rating, আসল delivery র মিনিট (এটা তো answer ই), আর customer complain করেছে কিনা (দেরি হয়েছে বলেই মানুষ complain করে)।

Station stamp

Chapter শেষ করো

পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।

Pro নিয়ে XP পাও →