সরাসরি পর্বে যান
পর্ব ২৪ · ৭৭-এর মধ্যে · মডিউল ৫

Robust আর quantile regression — outlier আর অনুমানের পরিসর

Robust and Quantile Regression — Leverage, Cook's Distance, Huber, RANSAC, Theil–Sen, Pinball Loss and Prediction Intervals

Taught in Bangla, this lesson separates outliers, leverage and influence using hat values and Cook's distance in statsmodels, then compares OLS with Huber, RANSAC and Theil–Sen on planted errors. It fits 10th, 50th and 90th percentile lines with pinball loss, checks 80% interval coverage by distance band on held-out data, and fixes quantile crossing, all in browser runs.

এই পর্বে যা শিখবেন

  • Outlier, leverage আর influence আলাদা করে চেনা, আর hat value ও Cook's distance হাতে হিসাব করা
  • statsmodels-এর OLSInfluence দিয়ে প্রভাবশালী সারি খোঁজা, আর বাদ দেওয়ার আগে কী যাচাই করতে হয় তা জানা
  • Huber loss-কে M-estimator আর ওজনের ভাষায় পড়া, আর RANSAC ও Theil–Sen-এর কৌশল ও breakdown point বোঝা
  • Pinball loss দিয়ে ১০, ৫০ আর ৯০তম percentile-এর regression চালানো, statsmodels আর scikit-learn মিলিয়ে
  • Quantile থেকে prediction interval বানানো, held-out ডেটায় coverage মাপা, আর quantile crossing ধরা ও সারানো

AI-এর ভিত্তি কোর্সের পর্ব ৫৮-এ residual plot-এ “একা দূরের বিন্দু” দেখেছেন, আর পর্ব ৩৭-এ z-score ও IQR দিয়ে একটি column-এর outlier ধরেছেন। এই কোর্সের পর্ব ০২-এ Huber আর pinball loss-এর আকার দেখেছেন, আর দেখেছেন যে absolute loss median আর pinball loss একটি quantile শেখায়। পর্ব ০৭-এ least squares-কে projection আর hat matrix হিসেবে পড়েছেন। এই পর্ব সেগুলো regression-এ নিয়ে আসে: কোন বিন্দু রেখাকে সত্যিই টেনে নেয়, কীভাবে টান সীমিত রাখা যায়, আর গড়ের বদলে পুরো পরিসর কীভাবে অনুমান করা যায়। চালানো যায় এমন চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, NumPy, pandas, statsmodels, scikit-learn আর matplotlib); প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে, scikit-learn আর statsmodels-এ প্রায় দশ সেকেন্ড।

১ · একটি ভুল সারির জোর — outlier, leverage আর influence

দাঁড়িপাল্লার একটি লম্বা দণ্ডের কথা ভাবুন, মাঝখানে কব্জা। মাঝের কাছে একটি ভারী বস্তা রাখলে দণ্ড সামান্যই হেলে। একই বস্তা একদম প্রান্তে রাখলে পুরো দণ্ড কাত হয়ে যায়। Regression-এর রেখাও এমন একটি দণ্ড, কব্জা থাকে ডেটার গড় x̄-এর কাছে। কোনো বিন্দু রেখা থেকে কত দূরে, শুধু সেটি নয়; সে কব্জা থেকে কত দূরে বসে আছে, সেটিও সমান জরুরি।

বাজারঘরের ডেলিভারি দল প্রতিটি অর্ডারের দুটি সংখ্যা রাখে: hub থেকে দূরত্ব (km) আর ডেলিভারিতে কত ঘণ্টা লেগেছে। সাধারণ least squares, অর্থাৎ OLS, এর মধ্যে একটি সরলরেখা টানে। তিন ধরনের অস্বাভাবিক বিন্দু সেই রেখাকে তিনভাবে প্রভাবিত করে:

  • Outlier: y-এর মান রেখা থেকে অনেক দূরে, যেমন মাঝারি দূরত্বের একটি অর্ডারে ৫০ ঘণ্টা, কারণ ক্রেতা দুদিন বাড়িতে ছিলেন না। x মাঝামাঝি হলে রেখা একটু ওপরে ওঠে, কিন্তু ঢাল তেমন বদলায় না।
  • উঁচু leverage: x-এর মান বাকিদের থেকে অনেক দূরে, যেমন ১৫০ km দূরের একমাত্র অর্ডার। সেটি রেখার ধারা মেনে চললে ক্ষতি নেই, বরং ঢালের অনুমান আরও নিশ্চিত হয় (“ভালো leverage”).
  • প্রভাবশালী বিন্দু (influence): উঁচু leverage আর রেখার ধারা ভাঙা, দুটি একসঙ্গে। এটিই রেখাকে নিজের দিকে টেনে নেয়, আর টেনে নেয় বলে তার নিজের residual ছোট দেখায়।

তিন ধরনের অস্বাভাবিক বিন্দু

নীল বিন্দু সাধারণ অর্ডার; সবুজ রেখা আসল ধারা; লাল ভাঙা রেখা C-সহ OLS

Outlier, high leverage and influential point in a simple regression A B C x̄ (কব্জা) দূরত্ব km → A: outlier — y-তে দূরে, leverage কম B: leverage বেশি, ধারা মানে — ক্ষতি নেই C: leverage + outlier = প্রভাবশালী লাল রেখা C-এর টানে প্রায় সমতল
ছবি ১.১ — A রেখাকে সামান্য ওপরে তোলে, B কিছুই নষ্ট করে না, কিন্তু C একাই ঢাল প্রায় শূন্যে নামিয়ে আনে। C-এর নিজের residual লাল রেখা থেকে মাপলে ছোট, তাই residual plot-এ সে লুকিয়ে থাকে।

দুটি শব্দের পার্থক্য মনে রাখুন। Outlier একটি অবস্থা (মান অস্বাভাবিক), influence একটি প্রভাব (বাদ দিলে ফল বদলায়)। Outlier মানেই ভুল নয়: ঈদের আগের সপ্তাহে সত্যিই ডেলিভারি দেরি হয়। আবার ভুল মানেই outlier নয়: ১৫-কে ১৫০ লিখলে বিন্দুটি y-এ একদম স্বাভাবিক দেখায়। তাই প্রথম কাজ মাপা, তারপর কারণ খোঁজা, আর শেষে সিদ্ধান্ত।

২ · Hat value আর Cook's distance — হাতে হিসাব থেকে statsmodels

OLS-এর অনুমান ŷ = Hy, যেখানে H = X(XᵀX)−1Xᵀ হলো hat matrix (y-এর মাথায় “টুপি” পরায়)। H-এর কর্ণের i-তম মান hii-কে বলে i-তম সারির hat value বা leverage: yi এক একক বদলালে তার নিজের অনুমান ŷi কতটা বদলায়। একটি feature আর intercept থাকলে সূত্রটি সরল:

hii = ১/n + (xi − x̄)² / Σj(xj − x̄)²

প্রতিটি hii ০ থেকে ১-এর মধ্যে, আর সবগুলোর যোগফল p, অর্থাৎ parameter-এর সংখ্যা (H একটি projection, তার trace তার rank). তাই গড় leverage p/n, আর প্রচলিত একটি নিয়ম: hii > ২p/n হলে সারিটি দেখার মতো। hii শুধু x-এর ওপর নির্ভর করে, y-এর ওপর একটুও নয়। নিচের stepper পাঁচটি অর্ডারে হিসাবটি হাতে করে, তারপর Cook's distance পর্যন্ত যায়।

(km, ঘণ্টা): (10, 8) (20, 11) (30, 13) (40, 16) (100, 12)p = 2

            
  1. ডেটা। চারটি অর্ডার ১০ থেকে ৪০ km-এর মধ্যে, পঞ্চমটি ১০০ km. গড় দূরত্ব x̄ = ৪০, এটিই দাঁড়িপাল্লার কব্জা।
  2. দূরত্বের বর্গ। প্রতিটি km থেকে ৪০ বাদ দিয়ে বর্গ: পঞ্চমটির একার বর্গ ৩৬০০, মোট ৫০০০-এর ৭২%.
  3. Hat value. h৫ = ১/৫ + ৩৬০০/৫০০০ = ০.৯২. অর্থাৎ পঞ্চম অর্ডারের ঘণ্টা ১ বাড়ালে তার নিজের অনুমান ০.৯২ ঘণ্টা বাড়ে: রেখা প্রায় পুরোপুরি এই একটি বিন্দুর কথা শোনে।
  4. সবগুলো মিলিয়ে। পাঁচটি hat value-এর যোগফল ঠিক ২, parameter-এর সংখ্যা (intercept আর ঢাল)। গড় ০.৪০, তাই ২p/n = ০.৮০-এর নিয়মে শুধু পঞ্চমটি চিহ্নিত হয়।
  5. Residual ছোট! সব সারি নিয়ে OLS-এর ঢাল মাত্র ০.০২৬, কারণ রেখা পঞ্চম বিন্দুর দিকে হেলে গেছে। তাই সেই বিন্দুর residual মাত্র −১.৫৬, অথচ চতুর্থ অর্ডারের residual +৪.০০. শুধু residual দেখলে ভুল বিন্দুকে সন্দেহ করতেন।
  6. Cook's distance. Residual-এর বর্গ আর leverage-এর গুণক h/(১ − h)² মিলিয়ে D৫ = ১৭.১৪, অন্যদের সর্বোচ্চ ০.৫০. বিন্দুটি বাদ দিলে ঢাল ০.০২৬ থেকে ০.২৬: দশ গুণ।

Cook's distance (R. Dennis Cook, ১৯৭৭) প্রশ্ন করে: i-তম সারি বাদ দিয়ে আবার fit করলে সব অনুমান মিলিয়ে কতটা সরে? আবার fit না চালিয়েই:

Di = ei² / (p · MSE) × hii / (1 − hii)²

প্রথম গুণনীয়ক residual কত বড়, দ্বিতীয়টি leverage কত বড়। দুটির যেকোনো একটি শূন্যের কাছে হলে D ছোট থাকে: এ কারণেই A বা B ধরনের বিন্দু D-তে উঁচু আসে না, C আসে। প্রচলিত সীমা D > ৪/n বা D > ১; এগুলো দেখার সংকেত, মোছার নিয়ম নয়। আরেকটি সহায়ক মাপ studentized residual: residual-কে তার নিজের standard error σ̂(i)√(1 − hii) দিয়ে ভাগ করা, যেখানে σ̂(i) সারিটি বাদ দিয়ে মাপা। উঁচু leverage-এর বিন্দুর সাধারণ residual ছোট দেখায়, কিন্তু √(1 − h) দিয়ে ভাগ করলে সে বড় হয়ে ধরা পড়ে।

এবার আসল আকারে: ৩০টি সাধারণ অর্ডার, সঙ্গে একটি অর্ডার যার দূরত্ব ভুল করে ১৫-এর জায়গায় ১৫০ লেখা হয়েছে; তার ঘণ্টা ১৫ km-এর অর্ডারের মতোই, ১০.২. সব ডেটা seed দিয়ে বানানো, কোনো আসল দোকানের নয়; আসল ঢাল ০.২৫ ঘণ্টা প্রতি km, যা model জানে না। চারটি ব্লকই নিচের module থেকে ডেটা নেয়।

# বাজারঘর (কাল্পনিক দোকান): ডেলিভারির সময় বনাম hub থেকে দূরত্ব, seed দিয়ে বানানো
import numpy as np

TRUE_SLOPE = 0.25                    # আসল ঢাল: প্রতি km-এ 0.25 ঘণ্টা (model জানে না)

def clean(n=30, seed=24):
    rng = np.random.default_rng(seed)
    km = rng.uniform(5, 60, n).round(1)
    hours = (6 + TRUE_SLOPE * km + rng.normal(0, 2, n)).round(1)
    return km, hours

def gross_errors(n=200, frac=0.10, seed=24):
    km, hours = clean(n, seed)       # তারপর 10% সারিতে বড় ভুল বসানো
    rng = np.random.default_rng(seed + 1)
    far = np.flatnonzero(km > 35)    # দূরের এলাকায় স্ট্যাটাস দেরিতে আপডেট
    bad = rng.choice(far, int(frac * n), replace=False)
    hours[bad] += rng.uniform(25, 50, bad.size).round(1)
    return km, hours, bad

def fan(n=400, seed=24):
    rng = np.random.default_rng(seed)
    km = rng.uniform(2, 80, n).round(1)
    noise = (rng.gamma(2.0, 1.0, n) - 2.0) / np.sqrt(2)   # গড় 0, SD 1, ডানে লেজ
    hours = 6 + TRUE_SLOPE * km + (0.5 + 0.08 * km) * noise
    return km, hours.round(2)

তিনটি function তিন ধরনের ডেটা দেয়: clean পরিষ্কার ডেটা, gross_errors দূরের এলাকার ১০% সারিতে ২৫ থেকে ৫০ ঘণ্টার বাড়তি ভুল (কুরিয়ার নেটওয়ার্ক না পেয়ে স্ট্যাটাস দেরিতে দিয়েছেন), আর fan এমন ডেটা যেখানে দূরত্ব বাড়লে সময়ের ছড়ানোও বাড়ে, ডানে লম্বা লেজসহ। প্রথম ব্লক statsmodels-এর OLSInfluence দিয়ে প্রতিটি সারির hat value, studentized residual আর Cook's distance বের করে। ছবির লেখা ইংরেজিতে, কারণ পাতার matplotlib বাংলা অক্ষর ঠিকমতো আঁকে না।

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from statsmodels.stats.outliers_influence import OLSInfluence
from bazar_delivery import clean

km, hours = clean(n=30)
km = np.append(km, 150.0)            # ভুল লেখা: 15 km-এর জায়গায় 150
hours = np.append(hours, 10.2)       # সময় ঠিক আছে, 15 km-এর মতোই
X = sm.add_constant(km)
fit = sm.OLS(hours, X).fit()
inf = OLSInfluence(fit)

n, p = X.shape
tab = pd.DataFrame({"km": km, "hours": hours, "hat": inf.hat_matrix_diag,
                    "stud_resid": inf.resid_studentized_external,
                    "cooks_d": inf.cooks_distance[0]})
print(tab.sort_values("cooks_d", ascending=False).head(4).round(3))
print(f"rules: hat > 2p/n = {2 * p / n:.3f}, Cook's D > 4/n = {4 / n:.3f}")

keep = np.arange(n) != tab.cooks_d.idxmax()   # শুধু সবচেয়ে প্রভাবশালী সারি বাদ
fit2 = sm.OLS(hours[keep], X[keep]).fit()
print("slope with the row    =", round(fit.params[1], 4))
print("slope without the row =", round(fit2.params[1], 4), "(true 0.25)")

grid = np.array([0, 160])
plt.figure(figsize=(6, 3.6)).subplots_adjust(bottom=0.16)
plt.scatter(km, hours, s=16, label="orders")
plt.scatter([150], [10.2], s=70, facecolors="none", edgecolors="r",
            label="km typed as 150")
plt.plot(grid, fit.params[0] + fit.params[1] * grid, "r--", label="OLS, all rows")
plt.plot(grid, fit2.params[0] + fit2.params[1] * grid, "g-", label="OLS, row dropped")
plt.xlabel("distance from hub (km)"); plt.ylabel("delivery time (hours)")
plt.legend(fontsize=8); plt.show()

আসল আউটপুট (ব্রাউজারের Python; ছবিটি ব্লকের নিচে আঁকা হয়):

       km  hours    hat  stud_resid  cooks_d
30  150.0   10.2  0.700      -7.553   22.669
24    8.0    3.7  0.071      -2.138    0.155
13    6.7    5.0  0.075      -1.737    0.114
12   56.9   20.2  0.055       1.358    0.053
rules: hat > 2p/n = 0.129, Cook's D > 4/n = 0.129
slope with the row    = 0.0597
slope without the row = 0.2561 (true 0.25)

ভুল সারিটি (সূচক ৩০) সব মাপে আলাদা: hat value ০.৭০০, সীমা ০.১২৯-এর পাঁচ গুণের বেশি; studentized residual −৭.৫৫, অর্থাৎ নিজের standard error-এর সাড়ে সাত গুণ নিচে; Cook's D ২২.৬৭, যেখানে পরের সারির মাত্র ০.১৫৫. ঢাল এই এক সারির জন্য ০.২৫৬১ থেকে নেমে ০.০৫৯৭: “দূরত্বে ডেলিভারির সময় প্রায় বাড়েই না”, যা পুরো ভুল সিদ্ধান্ত। সারিটি বাদ দিলে ঢাল আসল ০.২৫-এর কাছে। লক্ষ করুন, সূচক ২৪-এর Cook's D-ও (০.১৫৫) ৪/n-এর সীমা পেরিয়েছে, অথচ সেটি একটি সাধারণ, সত্যিকারের অর্ডার: ৩১টি সারিতে কোনো একটির সীমা পেরোনো খুবই স্বাভাবিক। তাই সীমা হলো “খুলে দেখুন”, “মুছে ফেলুন” নয়। ছবিতে লাল ভাঙা রেখা ১৫০ km-এর বিন্দুর দিকে শুয়ে পড়েছে, সবুজ রেখা বাকি ৩০টি অর্ডারের ভেতর দিয়ে গেছে।

মুছে ফেলার আগে: প্রভাবশালী সারি পেলে আগে উৎস দেখুন (অর্ডারের ঠিকানা থেকে দূরত্ব আবার হিসাব করা যায় কি না), তারপর ভুল হলে ঠিক করুন, আর না পারলে বাদ দেওয়ার কারণ লিখে রাখুন। সত্যিকারের কিন্তু বিরল ঘটনা হলে মুছবেন না; robust পদ্ধতি বা আলাদা model ভাবুন। আর মনে রাখুন, Cook's D একবারে একটি সারি বাদ দেওয়ার প্রভাব মাপে: পাশাপাশি দুটি ভুল সারি একে অপরকে আড়াল করতে পারে (masking).

৩ · Robust loss আবার — Huber আর M-estimator

ভুল সারি খুঁজে খুঁজে বাদ দেওয়ার বিকল্প হলো এমন loss নেওয়া, যা নিজেই বড় ভুলের টান সীমিত রাখে। পর্ব ০২-এ Huber loss দেখেছেন: residual r ছোট (∣r∣ ≤ δ) হলে r²/২, বড় হলে δ(∣r∣ − δ/২)। এখানে প্রশ্নটি অন্য: এই loss regression-এ কীভাবে কাজ করে, আর কেন কাজ করে?

যেকোনো loss ρ-এর জন্য Σρ(yi − xiᵀβ) কমানোর সমীকরণ হলো Σψ(ri)xi = ০, যেখানে ψ = ρ′ হলো প্রতিটি বিন্দুর রেখার ওপর “টান”। এমন অনুমানকে বলে M-estimator (maximum-likelihood ধাঁচের)। Squared loss-এ ψ(r) = r: টান residual-এর সমানুপাতিক, সীমাহীন। Huber-এ ψ(r) = r যতক্ষণ ∣r∣ ≤ δ, তারপর ±δ-তে থেমে যায়। সমীকরণটিকে Σwirixi = ০ লেখা যায়, যেখানে ওজন wi = ψ(ri)/ri = min(১, δ/∣ri∣)। অর্থাৎ Huber regression আসলে একটি weighted least squares, যার ওজন residual দেখে বদলায়: পর্ব ২০-এর IRLS-এর মতোই বারবার ওজন হালনাগাদ করে চালানো যায়।

Residual r মাপা হয় scale σ-এর এককে; δ = 1.35 (scikit-learn-এর default epsilon)
residual r
squared: r²/2
Huber ρ(r)
টান ψ(r)
ওজন w = ψ/r
  1. ছোট ভুল। r = ০.৫ সীমার ভেতরে, তাই Huber আর squared হুবহু এক: loss ০.১২৫, টান ০.৫, ওজন ১. সাধারণ বিন্দুর সঙ্গে Huber ঠিক OLS-এর মতো আচরণ করে।
  2. সীমায়। r = δ = ১.৩৫-এ দুই সূত্র একই মান দেয় (০.৯১১), ঢালও এক: বক্ররেখা মসৃণভাবে জোড়া, কোনো কোণ নেই। এখান থেকে Huber সরলরেখায় চলে।
  3. মাঝারি ভুল। r = ৩-এ squared ৪.৫, Huber ৩.১৩৯. টান squared-এ ৩, Huber-এ ১.৩৫-এ আটকে: ওজন ০.৪৫, অর্থাৎ এই বিন্দু অর্ধেকের কম কথা বলে।
  4. বড় ভুল। r = ৩০-এ squared loss ৪৫০, Huber ৩৯.৫৯. টান ৩০ বনাম ১.৩৫: ওজন ০.০৪৫. বিন্দুটি বাদ পড়েনি, কিন্তু তার কণ্ঠ প্রায় ফিসফিস।
  5. মোট টান। চারটি বিন্দুর মোট টান squared-এ ৩৪.৮৫, যার ৮৬% একা r = ৩০-এর বিন্দু থেকে। Huber-এ মোট ৪.৫৫, সেই বিন্দুর ভাগ ৩০%: এক বিন্দু আর রেখা দখল করতে পারে না।

δ কীভাবে বাছবেন? residual-এর একক ঘণ্টা হলে δ-ও ঘণ্টায়, আর ডেটার scale বদলালে δ-ও বদলাতে হয়। তাই scikit-learn-এর HuberRegressor একটি scale σ-ও একসঙ্গে অনুমান করে, আর সীমা ধরে ∣r/σ∣ > ε. Default ε = ১.৩৫; Gaussian ভুলে এটি OLS-এর প্রায় ৯৫% efficiency রাখে, অর্থাৎ ভুল পরিষ্কার হলে সামান্যই দাম দিতে হয়। আরও দুটি খুঁটিনাটি: এর alpha default ০.০০০১, একটি ছোট L2 শাস্তি (পর্ব ১৭-এর ridge); আর এর loss-এর সূত্র পাঠ্যবইয়ের Huber-এর দ্বিগুণ (z² আর ২ε∣z∣ − ε²), যা সর্বনিম্ন বিন্দু বদলায় না।

Huber-এর একটি বড় সীমা আছে। ওজন residual দেখে কমে, কিন্তু অংশ ২-এর C বিন্দু রেখাকে টেনে নিজের residual ছোট করে ফেলে। তাই x-এ দূরের ভুলের বিরুদ্ধে Huber প্রায় OLS-এর মতোই দুর্বল। এই দুর্বলতার মাপের নাম breakdown point: ডেটার সবচেয়ে কম কত ভাগ ইচ্ছামতো নষ্ট করলে অনুমানকে যত খুশি দূরে সরানো যায়। গড় আর OLS-এর breakdown point ০ (একটি বিন্দুই যথেষ্ট), median-এর ৫০%. Huber-এর মতো M-estimator y-এর outlier-এ ভালো, কিন্তু leverage-সহ ভুলে তার breakdown point-ও ০. পরের দুটি পদ্ধতি অন্য পথে হাঁটে।

৪ · RANSAC আর Theil–Sen — ভোট আর median দিয়ে রেখা

RANSAC (RANdom SAmple Consensus; Fischler আর Bolles, ১৯৮১) এসেছে computer vision থেকে, যেখানে ছবির মিলের অর্ধেকও ভুল হতে পারে। কৌশলটি নির্বাচনের মতো: এলোমেলোভাবে ন্যূনতম কয়েকটি বিন্দু (সরলরেখায় ২টি) নিয়ে একটি রেখা বানান, তারপর গুনুন কতগুলো বিন্দু সেই রেখা থেকে একটি সীমার (threshold) ভেতরে আছে। এই কাজ বহুবার করে যে রেখার পক্ষে সবচেয়ে বেশি “ভোট” (inlier), তাকে রাখুন, আর শেষে শুধু সেই inlier-দের নিয়ে OLS চালান। একটি এলোমেলো জোড়া পরিষ্কার হওয়ার সম্ভাবনা যথেষ্ট থাকলে, বহু চেষ্টার মধ্যে অন্তত একটি পরিষ্কার জোড়া প্রায় নিশ্চিতভাবে আসে। দুর্বলতা: threshold আপনাকে বলে দিতে হয়, আর ফল এলোমেলো চেষ্টার ওপর নির্ভর করে, তাই random_state দিতে হয়। scikit-learn-এ threshold না দিলে default হলো y-এর median absolute deviation, residual-এর নয়: y-এর ছড়ানো বেশি হলে সীমাও ঢিলে হয়ে যায়।

Theil–Sen (Henri Theil ১৯৫০, Pranab Kumar Sen ১৯৬৮) আরও সরল: প্রতিটি জোড়া বিন্দুর ঢাল বের করুন, তারপর তাদের median নিন। একটি ভুল বিন্দু শুধু তার নিজের জোড়াগুলো নষ্ট করে, আর median সেগুলো এক পাশে ঠেলে দেয়। এর breakdown point প্রায় ২৯.৩%. নিচের stepper পাঁচটি বিন্দুতে পুরো হিসাব করে।

(km, ঘণ্টা): (10, 8.6) (20, 11.0) (30, 13.4) (40, 16.2) (50, 45.0)৫ নম্বর ভুল

            
  1. OLS টেনে নেয়। চারটি পরিষ্কার বিন্দু প্রায় ০.২৫ ঢালে চলে, কিন্তু পঞ্চমটির ভুলের জন্য OLS-এর ঢাল ০.৭৮.
  2. দশটি জোড়া। ৫টি বিন্দু থেকে ১০টি জোড়া, প্রতিটির ঢাল আলাদা করে। যে জোড়ায় ৫ নম্বর আছে, তার ঢাল বড়: ০.৯১ থেকে ২.৮৮.
  3. সাজানো। পরিষ্কার ৬টি জোড়া নিচে, নষ্ট ৪টি ওপরে। ভুল বিন্দু যত বড় ভুলই করুক, তার জোড়াগুলো শুধু ওপরের দিকে জমা হয়।
  4. Median. ১০টি সংখ্যার median হলো ৫ম আর ৬ষ্ঠের গড়, ০.২৭০: পুরোপুরি পরিষ্কার জোড়া থেকে। নষ্ট জোড়ার ভাগ ৪০%; আরও একটি বিন্দু নষ্ট হলে (৫টির ২টি) নষ্ট জোড়া ৭টি হতো আর median-ও নষ্ট হতো।
  5. Intercept. প্রতিটি বিন্দু থেকে ০.২৭·km বাদ দিয়ে median: ৫.৬. ভুল বিন্দুর ৩১.৫ আবার এক পাশে পড়ে থাকে। scipy-র theilslopes intercept-এর জন্য আরেকটি প্রচলিত সংজ্ঞা নেয়, তাই ৫.৩ দেয়; ঢাল একই।

n টি বিন্দুতে জোড়া n(n − 1)/২টি, তাই বড় ডেটায় সব জোড়া গোনা ধীর। scikit-learn-এর TheilSenRegressor অনেক feature-এর জন্য সাধারণ রূপ চালায়: ছোট ছোট উপসেটে least squares, তারপর সব সমাধানের spatial median, আর উপসেটের সংখ্যা max_subpopulation (default ১০,০০০) ছাড়ালে এলোমেলো নমুনা। তাই এক feature-এও উত্তর ক্লাসিক “ঢালের median”-এর হুবহু সমান না-ও হতে পারে। এবার চারটি পদ্ধতি পাশাপাশি: ২০০টি অর্ডার, যার দূরের এলাকার ২০টিতে (১০%) ২৫ থেকে ৫০ ঘণ্টার বাড়তি ভুল।

import numpy as np
import pandas as pd
from sklearn.linear_model import (LinearRegression, HuberRegressor,
                                  RANSACRegressor, TheilSenRegressor)
from bazar_delivery import gross_errors, TRUE_SLOPE

km, hours, bad = gross_errors()          # 200 সারি, 20টিতে বড় ভুল
X = km.reshape(-1, 1)
print("rows:", len(km), "| planted errors:", bad.size)

models = {
    "OLS": LinearRegression(),
    "Huber": HuberRegressor(epsilon=1.35),
    "RANSAC": RANSACRegressor(residual_threshold=5.0, random_state=42),
    "TheilSen": TheilSenRegressor(random_state=42),
}
rows = []
for name, m in models.items():
    m.fit(X, hours)
    est = m.estimator_ if name == "RANSAC" else m
    slope, icpt = est.coef_[0], est.intercept_
    rows.append([name, icpt, slope, abs(slope - TRUE_SLOPE)])
res = pd.DataFrame(rows, columns=["model", "intercept", "slope", "slope_err"])
print(res.round(4).to_string(index=False))

hub = models["Huber"]
w_out = hub.outliers_                    # |r| > epsilon·sigma হলে True
print("Huber: scale sigma =", round(hub.scale_, 3),
      "| flagged =", int(w_out.sum()), "| planted among flagged =",
      int(np.isin(np.flatnonzero(w_out), bad).sum()))
ran = models["RANSAC"].inlier_mask_
print("RANSAC: inliers =", int(ran.sum()), "| planted kept as inliers =",
      int(ran[bad].sum()))

আসল আউটপুট (ব্রাউজারের Python):

rows: 200 | planted errors: 20
   model  intercept  slope  slope_err
     OLS     2.1837 0.4828     0.2328
   Huber     5.9591 0.2599     0.0099
  RANSAC     6.6923 0.2269     0.0231
TheilSen     5.4706 0.2651     0.0151
Huber: scale sigma = 1.7 | flagged = 71 | planted among flagged = 20
RANSAC: inliers = 175 | planted kept as inliers = 0

OLS দূরের এলাকার ভুলে ঢাল প্রায় দ্বিগুণ করেছে (০.৪৮২৮, আসল ০.২৫), আর intercept নামিয়ে ২.১৮-এ এনেছে: রেখা ডান দিকে উঠে বাঁ দিকে নেমেছে। তিনটি robust পদ্ধতির ঢালের ভুল ০.০১ থেকে ০.০২৩, OLS-এর দশ থেকে বিশ ভাগের এক ভাগ। এখানে Huber সবচেয়ে কাছে, কিন্তু এটি একটি ডেটার একটি run; অন্য seed-এ ক্রম বদলাতে পারে, তাই পদ্ধতি বাছতে কয়েকটি seed বা fold-এ তুলনা করুন। Huber-এর outliers_ ৭১টি সারি চিহ্নিত করেছে, যার মধ্যে বসানো ২০টি ভুলের সবগুলো আছে; বাকি ৫১টি সাধারণ অর্ডার, যাদের residual শুধু ε·σ = ১.৩৫ × ১.৭ ≈ ২.৩ ঘণ্টা ছাড়িয়েছে (ভুলের SD ২ ঘণ্টা)। অর্থাৎ outliers_ মানে “ওজন কমানো সারি”, “ভুল সারি” নয়। RANSAC ৫ ঘণ্টার সীমায় ১৭৫টি inlier রেখেছে, আর বসানো ভুলের একটিও তাদের মধ্যে নেই।

৫ · গড় যথেষ্ট নয় — heteroscedasticity আর quantile regression

ক্রেতা জানতে চান: “আমার পার্সেল কবে আসবে?” গড় বলে দিলে অর্ধেক ক্রেতা হতাশ হন। বাজারঘর বরং বলতে চায়: “৯০% সম্ভাবনায় ২২ ঘণ্টার মধ্যে।” আবহাওয়ার পূর্বাভাসও তাই করে: “তাপমাত্রা ৩০ থেকে ৩৪ ডিগ্রি।” এর জন্য গড় নয়, শর্তসাপেক্ষ বণ্টনের একটি নির্দিষ্ট quantile দরকার: এমন একটি সংখ্যা, যার নিচে ৯০% অর্ডার পড়ে।

ঢাকার কাছের অর্ডার প্রায় সবসময় একই সময়ে পৌঁছায়; দূরের জেলায় কখনো ১২ ঘণ্টায়, কখনো দুদিনে, কারণ ফেরি, বৃষ্টি আর রাস্তা। ভুলের ছড়ানো x-এর সঙ্গে বদলানোকে বলে heteroscedasticity. OLS তখনও গড়ের একটি ভালো রেখা দিতে পারে, কিন্তু “গড় ± ধ্রুব প্রস্থ” দিয়ে বানানো পরিসর কাছে অযথা চওড়া আর দূরে বিপজ্জনকভাবে সরু। Quantile regression (Roger Koenker আর Gilbert Bassett, ১৯৭৮) প্রতিটি τ-এর জন্য আলাদা রেখা দেয়: Qτ(y ∣ x) = xᵀβτ, অর্থাৎ দূরত্ব x হলে সময়ের τ-quantile. ছড়ানো বাড়লে ১০ আর ৯০তম quantile-এর রেখার ঢাল আলাদা হয়, রেখাগুলো পাখার মতো খোলে।

Loss হলো pinball loss: r = y − q̂ হলে r ≥ ০-এ τ·r, আর r < ০-এ (τ − ১)·r. কম বলা (আসল সময় অনুমানের চেয়ে বেশি) আর বেশি বলার দাম অসমান। নিচের stepper একটি residual দিয়ে শুরু করে দেখায়, এই অসমতা কেন ঠিক ৯০তম percentile-এ গিয়ে থামে।

প্রতিশ্রুতি q̂ = 20 ঘণ্টা; r = y − q̂; r ≥ 0 হলে 0.9·r, নইলে 0.1·∣r∣
অবস্থা
residual
pinball
তুলনা
  1. নিয়ম। τ = ০.৯ মানে প্রতিশ্রুতির চেয়ে দেরিতে আসার প্রতি ঘণ্টায় দাম ০.৯, আগে আসার প্রতি ঘণ্টায় ০.১. প্রতিশ্রুতি ভাঙা ক্রেতার কাছে নয় গুণ খারাপ।
  2. দেরি। পার্সেল এল ২৬ ঘণ্টায়, প্রতিশ্রুতি ছিল ২০: r = +৬, loss ০.৯ × ৬ = ৫.৪.
  3. আগে। পার্সেল এল ১৪ ঘণ্টায়: r = −৬, loss ০.১ × ৬ = ০.৬. একই ৬ ঘণ্টার ফারাক, কিন্তু দাম নয় ভাগের এক ভাগ। Squared বা absolute loss দুটি অবস্থাকে সমান ধরত।
  4. সাম্যের শর্ত। q̂ এক ঘণ্টা বাড়ালে q̂-এর ওপরের প্রতিটি অর্ডারে loss ০.৯ কমে, নিচের প্রতিটিতে ০.১ বাড়ে। বাড়ানো লাভজনক যতক্ষণ ওপরের ভাগ × ০.৯ > নিচের ভাগ × ০.১. সাম্য আসে যখন নিচে ৯০%, ওপরে ১০%: ঠিক ৯০তম percentile.
  5. দশটি অর্ডারে। মোট pinball q̂ = ১৯-এ ১৮.৪, ২১-এ ১৪.৪, ২৪-এ ১১.৪, ৩০-এও ১১.৪. দশটি মানে ৯০% মানে ঠিক ৯টি, তাই ৯ম (২৪) আর ১০ম (৩০) মানের মাঝের যেকোনো সংখ্যা সমান ভালো। NumPy-র np.quantile এখানে interpolation করে ২৪.৬ দেয়: সমাধান অনন্য না হলে library একটি প্রথা বেছে নেয়।

Regression-এ q̂-এর জায়গায় xᵀβ বসে, আর Σ pinball(yi − xiᵀβ) কমাতে হয়। Loss টুকরো টুকরো রৈখিক আর convex, তাই সমস্যাটি একটি linear programming: প্রতিটি residual-কে দুটি অ-ঋণাত্মক অংশে (ওপরে ui, নিচে vi) ভাগ করে τΣu + (১ − τ)Σv কমানো, শর্ত y = Xβ + u − v. LP-র সমাধান একটি কোণে থাকে, তাই অন্তত p টি বিন্দু রেখার ঠিক ওপরে পড়ে। scikit-learn-এর QuantileRegressor এই LP সরাসরি SciPy-র HiGHS solver দিয়ে সমাধান করে (solver="highs", scikit-learn ১.৪ থেকে default). statsmodels-এর QuantReg iteratively reweighted least squares দিয়ে একই সর্বনিম্ন বিন্দুর কাছে পৌঁছায় আর standard error-ও দেয়। একটি ফাঁদ: QuantileRegressor-এর alpha default ১.০, একটি L1 শাস্তি (lasso-র মতো); শুধু quantile চাইলে alpha=0 দিতেই হবে।

import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from sklearn.linear_model import QuantileRegressor
from bazar_delivery import fan

km, hours = fan(n=400)                   # দূরে গেলে ছড়ানো বাড়ে
X = sm.add_constant(km)
taus = [0.1, 0.5, 0.9]
fits = {t: sm.QuantReg(hours, X).fit(q=t) for t in taus}
for t in taus:
    b0, b1 = fits[t].params
    below = np.mean(hours <= b0 + b1 * km)
    print(f"tau={t}: intercept={b0:6.3f} slope={b1:.4f} share below={below:.3f}")
ols = sm.OLS(hours, X).fit().params
print(f"OLS mean: intercept={ols[0]:6.3f} slope={ols[1]:.4f}")

# যাচাই: scikit-learn, প্রথম 120টি সারিতে, LP solver "highs"
qr = QuantileRegressor(quantile=0.9, alpha=0.0, solver="highs")
qr.fit(km[:120].reshape(-1, 1), hours[:120])
sm90 = sm.QuantReg(hours[:120], X[:120]).fit(q=0.9).params
print("n=120, tau=0.9  sklearn:", np.round([qr.intercept_, qr.coef_[0]], 4),
      " statsmodels:", np.round(sm90, 4))

grid = np.linspace(0, 80, 2)
plt.figure(figsize=(6, 3.6)).subplots_adjust(bottom=0.16)
plt.scatter(km, hours, s=8, alpha=0.5, label="orders")
for t, c in zip(taus, ["tab:blue", "k", "tab:red"]):
    b0, b1 = fits[t].params
    plt.plot(grid, b0 + b1 * grid, color=c, label=f"quantile {t}")
plt.plot(grid, ols[0] + ols[1] * grid, "g--", label="OLS mean")
plt.xlabel("distance from hub (km)"); plt.ylabel("delivery time (hours)")
plt.legend(fontsize=8); plt.show()

আসল আউটপুট (ব্রাউজারের Python; ছবিটি ব্লকের নিচে আঁকা হয়):

tau=0.1: intercept= 5.490 slope=0.1655 share below=0.102
tau=0.5: intercept= 5.600 slope=0.2485 share below=0.500
tau=0.9: intercept= 6.814 slope=0.3586 share below=0.897
OLS mean: intercept= 5.558 slope=0.2682
n=120, tau=0.9  sklearn: [6.4908 0.388 ]  statsmodels: [6.4908 0.388 ]

তিনটি ঢাল আলাদা: ১০তম percentile-এ প্রতি km-এ ০.১৬৫৫ ঘণ্টা, median-এ ০.২৪৮৫, ৯০তম-এ ০.৩৫৮৬. দূরত্ব বাড়লে দ্রুত ডেলিভারি একটু ধীর হয়, কিন্তু ধীর ডেলিভারি অনেক বেশি ধীর হয়: এটিই পাখা। ডেটা বানানোর সূত্র থেকে আসল quantile-রেখার ঢাল হিসাব করা যায় (০.১৬৬৯, ০.২৩১৮, ০.৩৫৬৯); অনুমান তার কাছে। share below দেখায়, প্রতিটি রেখার নিচে বা ওপরে কত ভাগ অর্ডার: ০.১০২, ০.৫০০, ০.৮৯৭, প্রায় τ-এর সমান। এটি অংশ ৫-এর সাম্যের শর্তেরই ফল, তবে এটি training ডেটায়; আসল যাচাই পরের অংশে নতুন ডেটায়। OLS-এর গড়-রেখা (ঢাল ০.২৬৮২) median-রেখার একটু ওপরে, কারণ ভুলের ডানে লম্বা লেজ গড়কে median-এর ওপরে টানে। শেষ লাইনে ১২০টি সারিতে scikit-learn-এর LP আর statsmodels-এর IRLS চার দশমিক পর্যন্ত একই উত্তর দিয়েছে।

৬ · Prediction interval, coverage আর quantile-এর কাটাকাটি

১০ আর ৯০তম quantile-এর রেখা মিলিয়ে একটি ৮০% prediction interval পাওয়া যায়: নতুন একটি অর্ডারের সময় প্রায় ৮০% ক্ষেত্রে এর ভেতরে পড়ার কথা। এটি গড়ের confidence interval নয়: CI বলে গড় রেখা কোথায় থাকতে পারে, prediction interval বলে একটি অর্ডারের সময় কোথায় পড়তে পারে, তাই এটি অনেক চওড়া। OLS থেকেও normal বণ্টন ধরে একটি ৮০% interval বানানো যায়, ŷ ± ১.২৮১৬·σ̂ (statsmodels-এর get_prediction দিয়ে এর পুরো রূপ পর্ব ২৩, Linear model গভীরে — basis function, interaction আর regularization path-এর বিষয়)। কিন্তু σ̂ সব দূরত্বে একই।

Interval-এর আসল পরীক্ষা coverage: training-এ কখনো না দেখা নতুন ডেটায় কত ভাগ মান interval-এর ভেতরে পড়ে। গড় coverage যথেষ্ট নয়; কাছে আর দূরে আলাদা করে দেখতে হয়, কারণ গড় ৮০% হতে পারে কাছে ৯৯% আর দূরে ৬০%-এর গড় হিসেবে। নিচের ব্লক আলাদা seed-এর ৪০০টি নতুন অর্ডারে দুটি interval মাপে, তারপর একটি দ্বিতীয় সমস্যা খোঁজে: quantile রেখার কাটাকাটি।

import numpy as np
import pandas as pd
import statsmodels.api as sm
from sklearn.linear_model import QuantileRegressor
from bazar_delivery import fan

km, hours = fan(n=400, seed=24)              # train
km_t, hours_t = fan(n=400, seed=99)          # held-out, অন্য seed
X, Xt = sm.add_constant(km), sm.add_constant(km_t)
lo = Xt @ sm.QuantReg(hours, X).fit(q=0.1).params
hi = Xt @ sm.QuantReg(hours, X).fit(q=0.9).params
ols = sm.OLS(hours, X).fit()
mid, s = Xt @ ols.params, np.sqrt(ols.scale)
olo, ohi = mid - 1.2816 * s, mid + 1.2816 * s    # normal ধরে 80% interval
band = pd.cut(km_t, [0, 27, 54, 81], labels=["near", "mid", "far"])
inside = lambda a, b: (hours_t >= a) & (hours_t <= b)
cov = pd.DataFrame({"quantile_10_90": inside(lo, hi), "ols_normal_80": inside(olo, ohi)})
tab = cov.groupby(band, observed=True).mean()
tab.loc["all"] = cov.mean()
print(tab.round(3))
print("mean width  quantile:", round((hi - lo).mean(), 2), "| OLS:", round(2 * 1.2816 * s, 2))

rng = np.random.default_rng(42)             # ছোট নমুনা: 100 বার, প্রতিবার 25 সারি
grid = np.linspace(2, 80, 40).reshape(-1, 1)
crossed = 0
for _ in range(100):
    i = rng.choice(400, 25, replace=False)
    q = np.column_stack([QuantileRegressor(quantile=t, alpha=0, solver="highs")
                         .fit(km[i, None], hours[i]).predict(grid)
                         for t in (0.1, 0.5, 0.9)])
    crossed += bool(np.any(np.diff(q, axis=1) < 0))
print("samples with crossing inside 2-80 km:", crossed, "of 100")
q_fix = np.sort(q, axis=1)                   # rearrangement: প্রতি km-এ সাজানো
cross = lambda a: bool(np.any(np.diff(a, axis=1) < 0))
print("last sample, crossing before / after sorting:", cross(q), "/", cross(q_fix))

আসল আউটপুট (ব্রাউজারের Python):

      quantile_10_90  ols_normal_80
near           0.818          0.991
mid            0.774          0.877
far            0.792          0.660
all            0.792          0.830
mean width  quantile: 9.65 | OLS: 10.89
samples with crossing inside 2-80 km: 48 of 100
last sample, crossing before / after sorting: True / False

Quantile interval নতুন ডেটায় সব মিলিয়ে ৭৯.২% ধরেছে, আর কাছে, মাঝে, দূরে তিন ভাগেই ৭৭ থেকে ৮২%: লক্ষ্য ৮০%-এর কাছে, প্রতিটি অংশে। OLS-এর interval সব মিলিয়ে ৮৩%, দেখতে আরও ভালো; কিন্তু কাছে ৯৯.১% (অযথা চওড়া, ক্রেতাকে অকারণে দেরির কথা বলা) আর দূরে মাত্র ৬৬% (প্রতি তিনটি দূরের অর্ডারের একটি প্রতিশ্রুতি ভাঙে)। গড় প্রস্থেও quantile interval সরু, ৯.৬৫ বনাম ১০.৮৯ ঘণ্টা, কারণ প্রস্থ যেখানে দরকার সেখানেই বাড়ে। ছোট নমুনায় (২৫টি সারি) ১০০ বারের ৪৮ বারে ২ থেকে ৮০ km-এর মধ্যে কোথাও তিনটি রেখার ক্রম ভেঙেছে: যেমন ১০তম percentile-এর অনুমান median-এর চেয়ে বড়, যা অসম্ভব। প্রতিটি দূরত্বে তিনটি অনুমান ছোট থেকে বড় সাজিয়ে নিলে ক্রম ঠিক হয়, শেষ নমুনায় যেমন হয়েছে।

এই কাটাকাটিকে বলে quantile crossing. কারণ সরল: প্রতিটি τ-এর রেখা আলাদাভাবে fit হয়, কেউ অন্যদের কথা জানে না। নমুনা ছোট হলে ঢালগুলো এলোমেলো হয়, আর দুটি সরলরেখার ঢাল আলাদা হলে কোথাও না কোথাও তারা কাটবেই; ডেটার পরিসরের বাইরে (extrapolation) এটি প্রায় নিশ্চিত। প্রতিকার তিনটি। (১) Rearrangement: প্রতিটি x-এ অনুমানগুলো সাজিয়ে নেওয়া; Chernozhukov, Fernández-Val আর Galichon ২০১০ সালে দেখান যে এতে অনুমান খারাপ হয় না, বরং সত্যের কাছে আসে। (২) Quantile-গুলো একসঙ্গে fit করা, ক্রমের শর্তসহ। (৩) Model-কে পরিসরের ভেতরে রাখা আর যথেষ্ট ডেটা নেওয়া।

Coverage-এর নিশ্চয়তা চাইলে: Quantile regression-এর ৮০% আসে শুধু model ঠিক আর নমুনা বড় হলে। Conformal prediction একটি আলাদা calibration set-এ ভুল মেপে interval সংশোধন করে আর coverage-এর গাণিতিক নিশ্চয়তা দেয়; quantile regression-এর সঙ্গে মিলিয়ে তার রূপ conformalized quantile regression, যা পর্ব ৫৮ (Conformal prediction — নিশ্চয়তাসহ অনুমানের পরিসর)-এ আসবে।

৭ · কোন পরিস্থিতিতে কোন পদ্ধতি

কোনো একটি পদ্ধতি সবসময় সেরা নয়। প্রশ্নটি আগে ঠিক করুন: আপনি গড় চান, median চান, নাকি পুরো পরিসর? ডেটার সমস্যা কি y-এ, x-এ, নাকি ছড়ানোয়?

পদ্ধতিকী অনুমান করেশক্তিদুর্বলতা
OLSশর্তসাপেক্ষ গড়পরিষ্কার Gaussian ভুলে সেরা, standard error সহজbreakdown point ০; একটি ভুল সারিই যথেষ্ট
Huber (HuberRegressor)গড়ের কাছাকাছি একটি কেন্দ্রy-এর outlier-এ ভালো; পরিষ্কার ডেটায় প্রায় ৯৫% efficiencyleverage-সহ ভুলে দুর্বল; ε বাছতে হয়
RANSACinlier-দের OLSঅনেক ভুলেও চলে, যদি ভালো জোড়া পাওয়া যায়threshold আপনাকে দিতে হয়; ফল এলোমেলো চেষ্টায় নির্ভরশীল
Theil–Senজোড়ার ঢালের medianbreakdown point প্রায় ২৯%; কোনো সীমা লাগে নাঅনেক feature-এ ধীর আর দুর্বল
Median regression (τ = ০.৫)শর্তসাপেক্ষ mediany-এর outlier আর লম্বা লেজে স্থিরx-এর leverage-এ এটিও টলে
Quantile regressionযেকোনো τ-quantileheteroscedasticity-তে interval, অসমান খরচের সিদ্ধান্তপ্রতিটি τ আলাদা; crossing; লেজের কাছে (τ = ০.৯৯) ডেটা কম

কয়েকটি কাজের নিয়ম। প্রথম, ভুল সারি চিনতে পারলে সংশোধনই সেরা প্রতিকার। দ্বিতীয়, OLS আর একটি robust পদ্ধতি পাশাপাশি চালান: দুটির উত্তর অনেক আলাদা হলে সেটিই সতর্কসংকেত, আর কারণ খুঁজুন। তৃতীয়, ছড়ানো বদলায় কি না residual বনাম fitted ছবিতে দেখুন; ফানেলের আকার পেলে interval-এর জন্য quantile regression বা log রূপান্তর ভাবুন, আর গড়ের standard error-এর জন্য robust SE (পর্ব ২০-এর sandwich SE). চতুর্থ, ব্যবসার খরচ অসমান হলে (দেরিতে পৌঁছানো আগে পৌঁছানোর চেয়ে খারাপ), গড় নয়, সেই খরচের অনুপাত থেকে τ বাছুন: দেরির দাম আগের দামের ৯ গুণ হলে τ = ৯/(৯ + ১) = ০.৯. পঞ্চম, Linear model-এর বাইরেও এই loss বাঁচে: gradient boosting-এ loss="quantile" বা loss="huber" দেওয়া যায়, যা পর্ব ৩৫ (Gradient boosting — residual থেকে functional gradient descent)-এ দেখবেন। আর outlier নিজেই যখন খোঁজার বিষয় (জালিয়াতি, যন্ত্রের ত্রুটি), তখন তা পর্ব ৪৩ (Anomaly detection — isolation forest, LOF আর one-class SVM)-এর কাজ।

আরও গভীরে: Robust পরিসংখ্যানের ভিত্তি Peter Huber-এর ১৯৬৪ সালের প্রবন্ধ (“Robust Estimation of a Location Parameter”, Annals of Mathematical Statistics) আর Frank Hampel-এর influence function ও breakdown point-এর কাজ; পাঠ্যবই হিসেবে Huber ও Ronchetti-র Robust Statistics আর Maronna, Martin ও Yohai-র বই। Peter Rousseeuw ১৯৮৪ সালে JASA-য় least median of squares দেন, যার breakdown point প্রায় ৫০%; তার উত্তরসূরি LTS আর MM-estimator উঁচু breakdown আর উঁচু efficiency একসঙ্গে চায়। Quantile regression-এর পূর্ণ তত্ত্ব আর প্রয়োগ Roger Koenker-এর Quantile Regression (২০০৫) বইয়ে।

পরীক্ষা ও ইন্টারভিউতে: (১) Outlier, leverage আর influence-এর পার্থক্য? y-তে দূরে; x-এ দূরে; দুটি মিলে fit বদলায়। (২) hii-এর যোগফল কত? p, parameter-এর সংখ্যা; গড় p/n, নিয়ম ২p/n. (৩) Cook's D কোন দুটি জিনিস মেলায়? residual-এর বর্গ আর h/(১ − h)²; উঁচু leverage কিন্তু ছোট residual-এর বিন্দুও প্রভাবশালী হতে পারে। (৪) τ = ০.৯, অনুমান ২০, আসল ১৪: pinball loss? ০.১ × ৬ = ০.৬; আসল ২৬ হলে ০.৯ × ৬ = ৫.৪. (৫) Huber কেন leverage-এ দুর্বল? ওজন residual দেখে কমে, আর leverage-এর বিন্দু রেখা টেনে নিজের residual ছোট করে ফেলে।

৮ · AI-এর যুগে — robust আর quantile কোড যাচাই

“Outlier সরিয়ে model বানাও” বা “ডেলিভারির সময়ের range দাও” বললে AI assistant প্রায়ই চলনসই কোড দেয়, কিন্তু এই বিষয়ের ভুলগুলো চুপচাপ থাকে। নিচের ভুলগুলো বারবার দেখা যায়।

AI প্রায়ই যা করেকেন সমস্যাকীভাবে ধরবেন
y-এর z-score > ৩ দেখে সারি মুছে দেয়, পুরো ডেটায়, split-এর আগেleverage-এর ভুল ধরা পড়ে না; সত্যিকারের বিরল ঘটনা হারায়; test set-এর তথ্য training-এ ঢোকেআগে split; hat value আর Cook's D দেখুন; মোছা সারি গুনে লিখে রাখুন
QuantileRegressor() default রাখেdefault alpha=1.0: L1 শাস্তি, ঢাল শূন্যের দিকেalpha=0; statsmodels QuantReg-এর সঙ্গে মেলান
পুরোনো solver="interior-point" লেখেSciPy ১.১১ থেকে নেই; error আসেনিজের version-এর docs; solver="highs"
RANSACRegressor-এ threshold বা seed দেয় নাdefault সীমা y-এর MAD, residual-এর নয়; প্রতি run-এ ফল বদলায়residual_threshold ডেটার এককে দিন, random_state দিন
Huber-এর outliers_-কে “ভুল সারির তালিকা” বলেএটি শুধু ওজন-কমানো সারি; অনেক ভালো সারিও থাকেচিহ্নিত সারি খুলে দেখুন; জানা ভুলের সঙ্গে মেলান
OLS ± ১.৯৬σ দিয়ে interval দিয়ে “৯৫% নিশ্চিত” বলেছড়ানো সমান না হলে দূরে coverage অনেক কমheld-out ডেটায়, x-এর ভাগে ভাগে coverage মাপুন

AI-কে শুধু কোড নয়, অনুমান আর যাচাইও চাইতে বলুন:

আমার কাছে ডেলিভারির ডেটা: km (hub থেকে দূরত্ব) আর hours (ডেলিভারির সময়)। কিছু সারিতে ভুল থাকতে পারে, আর দূরত্ব বাড়লে সময়ের ছড়ানো বাড়ে। ১. statsmodels OLSInfluence দিয়ে hat value, studentized residual আর Cook's distance বের করার কোড দাও; কোন সীমা দেখবে আর কেন সীমা পেরোলেই মোছা ঠিক নয়, ব্যাখ্যা করো। ২. OLS, HuberRegressor, RANSACRegressor আর TheilSenRegressor একই ডেটায় তুলনার কোড দাও; প্রতিটির default parameter (epsilon, alpha, residual_threshold) কী আর কেন বদলাতে পারি, লেখো। ৩. statsmodels QuantReg আর scikit-learn QuantileRegressor দিয়ে 10, 50 আর 90তম percentile; QuantileRegressor-এ alpha কত দিতে হবে আর কেন। ৪. আলাদা test set-এ 10–90 interval-এর coverage, দূরত্বের তিন ভাগে আলাদা করে, আর quantile crossing ধরার কোড দাও। ৫. তুমি যে API নাম আর default মান বলছ, সেগুলো কোন scikit-learn, statsmodels আর SciPy version-এর docs অনুযায়ী, তা লেখো।

AI-এর দেওয়া robust বা quantile কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১দুই শতাব্দী ধরে বিজ্ঞানীরা তর্ক করেছেন: সন্দেহজনক পর্যবেক্ষণ বাদ দেওয়া উচিত কি না। Peirce আর Chauvenet-এর নিয়ম থেকে Anscombe-এর চারটি ডেটা পর্যন্ত এই তর্ক কী শেখায়?

Least squares প্রকাশিত হয় উনিশ শতকের শুরুতে, Legendre-এর ১৮০৫ সালের লেখায় আর পরে Gauss-এর কাজে। প্রায় সঙ্গে সঙ্গে জ্যোতির্বিদেরা একটি বাস্তব সমস্যায় পড়েন: টেলিস্কোপের কয়েকটি পর্যবেক্ষণ বাকিদের থেকে অনেক দূরে, আর least squares সেগুলোকে বড় ওজন দেয়। Benjamin Peirce ১৮৫২ সালে আর William Chauvenet কয়েক বছর পরে সন্দেহজনক পর্যবেক্ষণ বাদ দেওয়ার আনুষ্ঠানিক নিয়ম প্রস্তাব করেন, normal বণ্টন ধরে নিয়ে। সেই সময়েই অনেকে আপত্তি তোলেন যে এমন নিয়ম ইচ্ছামতো ডেটা ছাঁটার সুযোগ দেয়, আর সত্যিকারের অস্বাভাবিক ঘটনা হারিয়ে যায়।

বিংশ শতকে প্রশ্নটি নতুন রূপ নেয়। Francis Anscombe ১৯৭৩ সালে চারটি ছোট ডেটা প্রকাশ করেন, যাদের গড়, variance, correlation আর OLS রেখা প্রায় হুবহু এক, অথচ ছবি একদম আলাদা। একটিতে একটি বিন্দু y-তে দূরে থেকে রেখা সামান্য সরায়; আরেকটিতে সব x একই, শুধু একটি বিন্দু x-এ দূরে, আর সেই একটি বিন্দুই পুরো ঢাল ঠিক করে। অর্থাৎ সংখ্যার সারাংশ এই পর্বের A আর C ধরনের বিন্দুর পার্থক্য দেখায় না; ছবি আর influence-এর মাপ দেখায়।

আধুনিক অবস্থান মোটামুটি তিন ধাপের। প্রথমে মাপুন (leverage, Cook's D, residual-এর ছবি)। তারপর কারণ খুঁজুন: লেখার ভুল, যন্ত্রের ভুল, ভিন্ন জনগোষ্ঠী (যেমন পাইকারি ক্রেতা), নাকি সত্যিকারের বিরল ঘটনা। শেষে সিদ্ধান্ত, যা লিখে রাখা হয় আর মোছার আগে-পরে দুটি ফলই দেখানো হয়। Robust পদ্ধতির একটি বড় সুবিধা এখানে: কোনো সারি মুছতে হয় না, শুধু তার ওজন কমে, তাই “কোন সারি মুছলাম” প্রশ্নটি ইচ্ছামতো উত্তরের সুযোগ কম দেয়।

Machine learning-এ এই প্রশ্ন আরও তীক্ষ্ণ, কারণ pipeline স্বয়ংক্রিয়। “z > ৩ হলে মুছে ফেলুন” নিয়ম একবার লিখলে লাখো সারিতে কেউ আর দেখে না। আর split-এর আগে পুরো ডেটায় এটি চালালে test set-এর তথ্যও training-এর সিদ্ধান্তে ঢোকে।

মূল উপলব্ধি: Outlier বাদ দেওয়া একটি বৈজ্ঞানিক সিদ্ধান্ত, যান্ত্রিক নিয়ম নয়; মাপুন, কারণ খুঁজুন, লিখে রাখুন, আর সম্ভব হলে মোছার বদলে robust পদ্ধতিতে ওজন কমান।

প্র ০২Tukey-র “contaminated normal” থেকে Huber-এর ১৯৬৪ সালের প্রবন্ধ: robustness আর efficiency-র দর কষাকষি কী, আর ε = ১.৩৫-এর মতো একটি সংখ্যা কোথা থেকে আসে?

John Tukey ১৯৬০ সালের দিকে একটি সরল কিন্তু ধাক্কা দেওয়া উদাহরণ নিয়ে লেখেন: ধরুন বেশিরভাগ পর্যবেক্ষণ একটি normal বণ্টন থেকে, কিন্তু অল্প কিছু (ধরুন কয়েক শতাংশ) একই কেন্দ্রের কিন্তু অনেক চওড়া একটি normal থেকে। এমন “দূষিত normal” চোখে normal-এর মতোই দেখায়, অথচ তাতে নমুনার গড় আর standard deviation-এর কার্যকারিতা দ্রুত নষ্ট হয়, আর median বা ছাঁটা গড় অনেক ভালো করে। শিক্ষাটি ছিল: সামান্য বিচ্যুতিতে “সর্বোত্তম” পদ্ধতি খুব খারাপ হতে পারে।

Peter Huber ১৯৬৪ সালে প্রশ্নটিকে গাণিতিক রূপ দেন: এমন একটি অনুমান খুঁজুন, যা দূষণের সবচেয়ে খারাপ অবস্থায়ও সবচেয়ে ভালো করে (minimax). উত্তর এল একটি loss, যা মাঝে বর্গ আর দুই পাশে সরলরৈখিক: আজকের Huber loss. কোথায় বর্গ থেকে সরলরেখায় যেতে হবে, তা দূষণের পরিমাণের ওপর নির্ভর করে।

বাস্তবে প্রশ্নটি অন্যভাবে করা হয়: দূষণ না থাকলে আমি কতটা দাম দিতে রাজি? এর মাপ efficiency: পরিষ্কার normal ডেটায় OLS-এর variance আর আমার পদ্ধতির variance-এর অনুপাত। Median-এর efficiency প্রায় ৬৪%, অর্থাৎ একই নির্ভুলতার জন্য প্রায় দেড় গুণ ডেটা লাগে। Huber-এর সীমা প্রায় ১.৩৪৫σ রাখলে efficiency ৯৫%; scikit-learn-এর ε = ১.৩৫ এখান থেকেই। অর্থাৎ পরিষ্কার ডেটায় ৫% দাম দিয়ে দূষিত ডেটায় বড় সুরক্ষা কেনা হয়: একটি বিমার প্রিমিয়ামের মতো।

১৯৭২ সালে Princeton-এ Andrews, Bickel, Hampel, Huber, Rogers আর Tukey একটি বড় simulation-গবেষণা প্রকাশ করেন, যেখানে অনেকগুলো location estimator নানা ধরনের দূষণে তুলনা হয়। মোটামুটি সিদ্ধান্ত ছিল, একটি সব-কাজের সেরা পদ্ধতি নেই, কিন্তু কিছু robust পদ্ধতি প্রায় সব অবস্থায় যুক্তিসংগত। এই পর্বের robust পদ্ধতির তুলনাও তাই একটি run-এ সিদ্ধান্ত দেয় না।

মূল উপলব্ধি: Robust পদ্ধতি একটি বিমা: পরিষ্কার ডেটায় সামান্য efficiency ছেড়ে দূষিত ডেটায় বিপর্যয় এড়ানো; ε-এর মতো সংখ্যা সেই প্রিমিয়ামের মাপ।

প্র ০৩Breakdown point কেন শুধু একটি সংখ্যা নয়, একটি দৃষ্টিভঙ্গি? Hampel, Rousseeuw আর RANSAC-এর computer vision থেকে কী শেখার আছে?

Frank Hampel ১৯৬০-৭০-এর দশকে robustness-কে দুটি প্রশ্নে ভাগ করেন। প্রথমটি ছোট দূষণের: একটি বিন্দু সামান্য সরালে অনুমান কতটা সরে? এর মাপ influence function, যার নমুনা-রূপ Cook's distance-এর আত্মীয়। দ্বিতীয়টি বড় দূষণের: কত ভাগ ডেটা নষ্ট হলে অনুমান পুরো ভেঙে পড়ে? এটিই breakdown point. গড়ের breakdown ০ (n বড় হলে ১/n প্রায় ০), median-এর ৫০%: অর্ধেকের বেশি নষ্ট না হলে median সীমার মধ্যে থাকে।

Regression-এ ছবিটি জটিল। Huber-এর মতো M-estimator y-এর দিকের দূষণে ভালো, কিন্তু x-এর দিকে একটি খারাপ leverage বিন্দুই তাকে টেনে নিতে পারে, তাই তার breakdown ০. Peter Rousseeuw ১৯৮৪ সালে least median of squares প্রস্তাব করেন: বর্গ-residual-এর যোগফল নয়, median কমানো। এর breakdown প্রায় ৫০%, কিন্তু efficiency কম আর হিসাব ভারী। পরে least trimmed squares আর MM-estimator দুটিকে মেলানোর চেষ্টা করে: উঁচু breakdown দিয়ে শুরু করে উঁচু efficiency-তে শেষ।

Computer vision আলাদা পথে একই সমস্যায় পৌঁছায়। Martin Fischler আর Robert Bolles ১৯৮১ সালে ছবি থেকে ক্যামেরার অবস্থান বের করার কাজে RANSAC প্রকাশ করেন। ছবির বিন্দু মেলানোর স্বয়ংক্রিয় পদ্ধতিতে অনেক মিলই ভুল, প্রায়ই অর্ধেকের বেশি। গড়-ভিত্তিক পদ্ধতি এখানে অচল; তাই তাঁরা উল্টো দিক থেকে ভাবেন: ছোট একটি নমুনা থেকে model, তারপর দেখা কত ডেটা তার সঙ্গে একমত। পরিষ্কার নমুনা আসার সম্ভাবনা থেকে কতবার চেষ্টা লাগবে তাও হিসাব করা যায়; scikit-learn-এর stop_probability সেই ভাবনা।

Theil–Sen মাঝামাঝি: breakdown প্রায় ২৯.৩%, কোনো threshold লাগে না, পরিষ্কার ডেটায় efficiency ভালো। কিন্তু feature অনেক হলে তার সাধারণ রূপ ধীর আর দুর্বল হয়।

মূল উপলব্ধি: “এই পদ্ধতি কত ভাগ খারাপ ডেটা সইতে পারে, আর কোন দিকের খারাপ ডেটা?” এই প্রশ্নটিই robust পদ্ধতি বাছাইয়ের মূল; y-এর দূষণ আর x-এর দূষণ আলাদা শত্রু।

প্র ০৪Koenker আর Bassett ১৯৭৮ সালে “regression quantiles” প্রকাশ করেন। গড়ের বদলে পুরো বণ্টন দেখার এই ধারণা অর্থনীতি, অর্থায়ন আর forecasting-এ কী বদলাল?

Absolute deviation কমিয়ে রেখা টানার ধারণা least squares-এর চেয়েও পুরোনো: আঠারো শতকের মাঝামাঝি Boscovich পৃথিবীর আকার মাপার ডেটায় এমন একটি পদ্ধতি ব্যবহার করেছিলেন বলে ইতিহাসে উল্লেখ আছে, আর Laplace-ও এ নিয়ে লেখেন। কিন্তু হিসাব কঠিন ছিল, আর Gauss-এর least squares-এর সূত্র সহজ, তাই প্রায় দেড় শতাব্দী median-ধাঁচের regression পেছনে পড়ে থাকে। বিংশ শতকের মাঝামাঝি linear programming আসার পর হিসাবটি সহজ হয়।

Roger Koenker আর Gilbert Bassett ১৯৭৮ সালে Econometrica-য় দেখান, absolute loss-কে অসমান করলে (আজকের pinball loss) যেকোনো quantile-এর regression পাওয়া যায়, আর তার পরিসংখ্যানগত তত্ত্বও দেন। ফল: শুধু “গড়ে কী হয়” নয়, “নিচের ১০%-এর কী হয়, ওপরের ১০%-এর কী হয়” প্রশ্ন করা যায়।

অর্থনীতিতে এর ব্যবহার ব্যাপক: শিক্ষা বা অভিজ্ঞতার প্রভাব কম আয়ের মানুষের ওপর আর বেশি আয়ের মানুষের ওপর আলাদা কি না, এমন প্রশ্নে। অর্থায়নে ঝুঁকির মাপ value-at-risk সংজ্ঞাতেই একটি quantile, আর Engle ও Manganelli ২০০৪ সালে quantile regression-এর ধারণায় সময়ের সঙ্গে বদলানো VaR model করেন। Forecasting-এ বড় প্রতিযোগিতাগুলো এখন প্রায়ই শুধু একটি সংখ্যা নয়, কয়েকটি quantile চায়; যেমন ২০২০ সালের M5 প্রতিযোগিতার uncertainty অংশে অংশগ্রহণকারীরা কয়েকটি quantile জমা দেন আর একটি scaled pinball loss দিয়ে মূল্যায়ন হয়।

তবে দুটি সতর্কতা। প্রথমত, quantile regression-এর βτ শর্তসাপেক্ষ বণ্টনের quantile বর্ণনা করে, কোনো নির্দিষ্ট মানুষের নয়: “নিচের ১০%-এর ওপর প্রভাব” মানে বণ্টনের নিচের অংশ কীভাবে সরে, একই মানুষগুলো নয়। দ্বিতীয়ত, লেজের কাছে (τ = ০.৯৯) ডেটা কম, তাই অনুমান অস্থির। বাজারঘরের ডেলিভারির প্রতিশ্রুতিতে τ = ০.৯ যুক্তিসংগত; τ = ০.৯৯৯ চাইলে কয়েক হাজার অর্ডারেও মাত্র কয়েকটি বিন্দু সেই লেজে।

মূল উপলব্ধি: সিদ্ধান্ত প্রায়ই গড়ের ওপর নয়, লেজের ওপর নির্ভর করে; quantile regression সেই লেজকে x-এর function হিসেবে সরাসরি অনুমান করার সবচেয়ে সরল উপায়।

প্র ০৫অ্যান্টার্কটিকার ওজোন গর্তের গল্পে বলা হয়, স্বয়ংক্রিয় যাচাই অস্বাভাবিক মাপকে আলাদা করে রেখেছিল। এই গল্প থেকে ML pipeline-এর outlier নিয়ম নিয়ে কী শেখা যায়, আর গল্পটির কোন অংশ নিয়ে সতর্ক থাকা উচিত?

১৯৮৫ সালে British Antarctic Survey-র Joseph Farman, Brian Gardiner আর Jonathan Shanklin Nature-এ মাটি থেকে মাপা ডেটায় অ্যান্টার্কটিকার বসন্তে ওজোনের বড় পতনের কথা প্রকাশ করেন। একটি বহুল প্রচলিত গল্প বলে, NASA-র উপগ্রহের software খুব কম মানগুলোকে “অসম্ভব” ধরে বাদ দিয়েছিল, তাই উপগ্রহের ডেটায় গর্তটি আগে ধরা পড়েনি।

গল্পটি পাঠ্যবই আর বক্তৃতায় অনেকবার এসেছে, কিন্তু এর বিবরণ নিয়ে বিতর্ক আছে। NASA-র বিজ্ঞানীদের পরের বিবরণ অনুযায়ী, কম মানগুলো চিহ্নিত হয়েছিল কিন্তু মুছে ফেলা হয়নি, আর একটি মাটির স্টেশনের ডেটার সঙ্গে মেলাতে গিয়ে সেই স্টেশনের ভুল সন্দেহ করা হয়েছিল বলে বর্ণনা পাওয়া যায়। তাই “software গর্ত মুছে দিয়েছিল” কথাটি সম্ভবত সরলীকরণ; নির্ভরযোগ্য অংশটি হলো যে স্বয়ংক্রিয় মান-যাচাই অস্বাভাবিক মানকে সন্দেহের তালিকায় রেখেছিল, আর মানুষের মনোযোগ পেতে দেরি হয়েছিল।

যে রূপেই হোক, শিক্ষাটি ML-এর জন্য সরাসরি প্রাসঙ্গিক। একটি pipeline-এ “৩ SD-এর বাইরে হলে বাদ” নিয়ম নীরবে চলে। যদি বাস্তবতা বদলায় (নতুন রাস্তা খোলায় ডেলিভারি হঠাৎ দ্রুত হলো, বা বন্যায় একটি জেলায় সব অর্ডার দেরি), তাহলে ঠিক সেই নতুন তথ্যটিই প্রথমে মুছে যায়। Model তখন পুরোনো পৃথিবী শেখে, আর monitoring-এর dashboard-ও সুন্দর দেখায়, কারণ অস্বাভাবিক সব কিছু আগেই ছাঁটা।

ভালো নকশায় outlier মুছে ফেলা হয় না, আলাদা করে রাখা হয়: কয়টি সারি বাদ পড়ল, তার হার সময়ের সঙ্গে বাড়ছে কি না, আর বাদ পড়া সারিগুলোর একটি নমুনা মানুষ নিয়মিত দেখেন। Robust পদ্ধতি এখানে সাহায্য করে, কারণ সে সারি মোছে না, শুধু ওজন কমায়; আর অস্বাভাবিকতা নিজেই যখন খোঁজার বিষয়, তখন তা anomaly detection-এর কাজ (পর্ব ৪৩)। ডেটার বণ্টন সময়ের সঙ্গে বদলানো ধরার কৌশল পর্ব ৫৯ (Distribution shift — covariate, label আর concept shift ধরা ও সামলানো)-এ।

মূল উপলব্ধি: স্বয়ংক্রিয় outlier নিয়ম একটি অনুমান করে যে পৃথিবী বদলায় না; বাদ পড়া ডেটা গুনুন, দেখুন আর রাখুন, কারণ পরের আবিষ্কার প্রায়ই সেখানেই থাকে। আর জনপ্রিয় বৈজ্ঞানিক গল্পও উৎস মিলিয়ে বলুন।

অনুশীলন

হিসাবগুলো খাতায় করুন, তারপর উত্তর মেলান। চাইলে পাতার ব্লকগুলোতে সংখ্যা বদলে যাচাই করুন।

  1. পূর্ণ numerical: পাঁচটি অর্ডারের দূরত্ব x = ১, ৩, ৫, ৭, ১৪ (১০ km এককে); model-এ intercept আর একটি ঢাল। (ক) পাঁচটি hat value বের করুন, যোগফল যাচাই করুন, আর ২p/n নিয়মে কোনটি চিহ্নিত হয়? (খ) পঞ্চম সারির residual −২ আর MSE = ৪ হলে তার Cook's D কত? (গ) τ = ০.১-এর একটি quantile অনুমান ৮ ঘণ্টা; আসল সময় ৫ আর ১২ হলে pinball loss কত?

    (ক) x̄ = ৩০/৫ = ৬; x − x̄ = −৫, −৩, −১, ১, ৮; বর্গ ২৫, ৯, ১, ১, ৬৪; Sxx = ১০০. h = ০.২ + বর্গ/১০০: ০.৪৫, ০.২৯, ০.২১, ০.২১, ০.৮৪. যোগফল ২.০০ = p. ২p/n = ০.৮, তাই শুধু পঞ্চমটি (০.৮৪) চিহ্নিত। (খ) D = e²/(p·MSE) × h/(১ − h)² = ৪/(২ × ৪) × ০.৮৪/০.১৬² = ০.৫ × ০.৮৪/০.০২৫৬ = ০.৫ × ৩২.৮১ = ১৬.৪১: residual মাত্র −২ হলেও সারিটি অত্যন্ত প্রভাবশালী। (গ) আসল ৫: r = ৫ − ৮ = −৩, loss (০.১ − ১)(−৩) = ২.৭. আসল ১২: r = +৪, loss ০.১ × ৪ = ০.৪. τ = ০.১-এ বেশি বলাই বেশি দামি, কারণ এই অনুমানের নিচে মাত্র ১০% পড়ার কথা।

  2. সমালোচনা ও নকশা: একজন বিশ্লেষক লিখেছেন: “পুরো ডেটায় hours-এর z-score > ৩-এর সারি মুছে দিয়েছি (৪%). তারপর train/test ভাগ করে LinearRegression চালিয়েছি, আর প্রতিটি অর্ডারের জন্য ŷ ± ১.৯৬ × RMSE দেখাচ্ছি: ‘৯৫% নিশ্চিত সময়’। Test set-এ coverage ৯৪%, তাই ঠিক আছে।” কী কী সমস্যা, আর আপনি কীভাবে নকশা করবেন?

    সমস্যা: (১) Split-এর আগে পুরো ডেটায় মোছা: test set-এর অস্বাভাবিক সারিও বাদ গেছে, তাই test coverage বাস্তবের চেয়ে ভালো দেখায়; বাস্তবে নতুন অর্ডার থেকে কেউ সারি মুছবে না। (২) শুধু y-এর z-score: ভুল লেখা দূরত্বের মতো leverage-এর ভুল ধরা পড়ে না। (৩) ৪% সারি মুছে ফেলা, কারণ না দেখে: সত্যিকারের দেরিও হয়তো হারিয়েছে, যা ক্রেতার কাছে সবচেয়ে জরুরি। (৪) ধ্রুব প্রস্থের interval: ছড়ানো দূরত্বের সঙ্গে বাড়লে কাছে অযথা চওড়া, দূরে সরু; মোট ৯৪% সেটি লুকিয়ে রাখে। নকশা: আগে split; training অংশে hat value আর Cook's D দেখে ভুল সারি সংশোধন বা কারণসহ বাদ; OLS-এর পাশে Huber বা Theil–Sen; interval-এর জন্য ২.৫ আর ৯৭.৫তম quantile regression (বা পর্ব ৫৮-এর conformal পদ্ধতি); test set-এ, সব সারি রেখে, দূরত্বের ভাগে ভাগে coverage আর গড় প্রস্থ রিপোর্ট।

  3. আউটপুট অনুমান করুন: চালানোর আগে চারটি লাইন লিখুন।
    import numpy as np
    y = np.array([3, 4, 4, 5, 6, 30.])
    for c in [np.mean(y), np.median(y)]:
        print(round(c, 2), round(np.mean((y - c) ** 2), 2), round(np.mean(np.abs(y - c)), 2))
    r = y - 5
    print(np.where(r >= 0, 0.9 * r, -0.1 * r))
    print(round(np.quantile(y, 0.9), 2))

    ডেস্কটপে সত্যিকারের run (Python 3.14, NumPy 2.5):

    8.67 91.89 7.11
    4.5 109.25 5.0
    [ 0.2  0.1  0.1  0.   0.9 22.5]
    18.0

    গড় ৮.৬৭: একটি ৩০ গড়কে বাকিদের সবার ওপরে টেনে তুলেছে। গড়ে mean squared error কম (৯১.৮৯ বনাম ১০৯.২৫), median-এ mean absolute error কম (৫.০ বনাম ৭.১১): প্রতিটি কেন্দ্র নিজের loss-এ জেতে। তৃতীয় লাইন অনুমান ৫ ধরে τ = ০.৯-এর pinball: ৩, ৪, ৪-এ বেশি বলা, দাম ০.১ × ২ = ০.২, ০.১, ০.১; ৫-এ ০; ৬-এ ০.৯ × ১ = ০.৯; ৩০-এ ০.৯ × ২৫ = ২২.৫. শেষ লাইন: ছয়টি মানে ৯০তম percentile-এর অবস্থান ০.৯ × ৫ = ৪.৫, অর্থাৎ ৫ম (৬) আর ৬ষ্ঠ (৩০) মানের মাঝপথে: ৬ + ০.৫ × ২৪ = ১৮.০. ছোট নমুনায় লেজের quantile একটি মানের ওপর কতটা নির্ভর করে, এটি তার উদাহরণ।

  4. কোডিং: একটি function theil_sen(x, y) লিখুন, যা সব জোড়ার ঢালের median আর intercept median(y − ঢাল·x) ফেরত দেয়। স্টেপারের পাঁচটি বিন্দুতে চালিয়ে SciPy-র scipy.stats.theilslopes আর np.polyfit-এর সঙ্গে তুলনা করুন।
    import numpy as np
    from itertools import combinations
    from scipy.stats import theilslopes
    
    def theil_sen(x, y):
        slopes = [(y[j] - y[i]) / (x[j] - x[i])
                  for i, j in combinations(range(len(x)), 2) if x[j] != x[i]]
        b1 = np.median(slopes)
        return np.median(y - b1 * x), b1
    
    x = np.array([10, 20, 30, 40, 50.])
    y = np.array([8.6, 11.0, 13.4, 16.2, 45.0])
    print(np.round(theil_sen(x, y), 4))
    res = theilslopes(y, x)
    print(round(res.intercept, 4), round(res.slope, 4))
    print(np.round(np.polyfit(x, y, 1)[::-1], 4))

    ডেস্কটপে সত্যিকারের run (Python 3.14, NumPy 2.5, SciPy 1.18) ছাপে:

    [5.6  0.27]
    5.3 0.27
    [-4.56  0.78]

    নিজের function আর SciPy দুটিই ঢাল ০.২৭ দেয়; intercept আলাদা (৫.৬ বনাম ৫.৩), কারণ SciPy-র সংজ্ঞা median(y) − ঢাল × median(x) = ১৩.৪ − ০.২৭ × ৩০ = ৫.৩. np.polyfit (OLS) ঢাল ০.৭৮ দিয়ে ভুল বিন্দুর টান দেখায়। x[j] != x[i] শর্তটি একই দূরত্বের দুটি অর্ডারে শূন্য দিয়ে ভাগ আটকায়।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমRANSAC দিয়ে একটি সরলরেখা fit করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ডেটা থেকে এলোমেলোভাবে ন্যূনতম সংখ্যক বিন্দু (সরলরেখায় ২টি) বাছা
  2. সেই বিন্দুগুলো দিয়ে একটি প্রার্থী রেখা বানানো
  3. রেখা থেকে threshold-এর ভেতরে থাকা বিন্দু (inlier) গোনা
  4. বহু চেষ্টার মধ্যে সবচেয়ে বেশি inlier-এর রেখাটি রেখে দেওয়া
  5. শেষে শুধু সেই inlier-দের নিয়ে OLS চালিয়ে চূড়ান্ত রেখা পাওয়া

পাঁচটি অর্ডারের দূরত্ব ১০, ২০, ৩০, ৪০, ১০০ km; model-এ intercept আর একটি ঢাল। ১০০ km-এর অর্ডারের hat value কত?

  • ০.২০
  • ০.৯২
  • ০.৭২
  • ১.৫০

τ = ০.৯-এর quantile model একটি অর্ডারের জন্য ২০ ঘণ্টা অনুমান করেছে, অর্ডারটি এসেছে ১৪ ঘণ্টায়। Pinball loss কত?

  • ০.৬
  • ৫.৪
  • ৬.০
  • ১৮.০

Huber loss-এ সীমা δ = ১.৩৫ (scale-এর এককে)। Residual r = ৩ হলে IRLS-এ সেই বিন্দুর ওজন w = ψ(r)/r কত?

  • ১
  • ০.০৪৫
  • ০.৪৫
  • −০.৪৫

একটি সরল regression-এ একটি বিন্দুর hat value ০.৯০, কিন্তু তার সাধারণ residual বাকিদের তুলনায় ছোট। কোন সিদ্ধান্তটি সবচেয়ে ঠিক?

  • Residual ছোট, তাই বিন্দুটি নিরাপদ
  • উঁচু leverage, তাই সঙ্গে সঙ্গে মুছে ফেলা উচিত
  • Hat value বেশি মানে y-এর মান ভুল
  • Studentized residual আর Cook's D দেখে, বিন্দুটি বাদ দিলে fit কতটা বদলায় তা যাচাই করা দরকার

পাঁচটি বিন্দুর ১০টি জোড়ার ঢাল ছোট থেকে বড়: ০.২৪, ০.২৪, ০.২৪, ০.২৫, ০.২৬, ০.২৮, ০.৯১, ১.১৩, ১.৫৮, ২.৮৮. Theil–Sen ঢাল কত?

  • প্রায় ০.৮০
  • ০.২৭
  • ০.২৪
  • ০.৯১

দূরত্ব বাড়লে ডেলিভারির সময়ের ছড়ানো বাড়ে। ১০ আর ৯০তম quantile regression দিয়ে বানানো interval-কে আলাদা test set-এ যাচাই করলে কী আশা করা উচিত?

  • প্রায় ৯০% মান interval-এর ভেতরে
  • সব দূরত্বে interval-এর প্রস্থ একই
  • মোটামুটি ৮০% coverage, কাছে আর দূরে দুই জায়গাতেই কাছাকাছি
  • ১০০% coverage, কারণ quantile regression outlier-এ robust

কোন পদ্ধতির breakdown point শূন্য, অর্থাৎ একটিমাত্র খারাপ বিন্দুই অনুমানকে যত খুশি দূরে সরাতে পারে?

  • OLS (সাধারণ least squares)
  • একটি সংখ্যার median
  • Theil–Sen
  • Least median of squares

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. তিনটি আলাদা জিনিস: outlier (y-তে দূরে), leverage (x-এ দূরে), influence (বাদ দিলে fit বদলায়)। খারাপ leverage-এর residual ছোট দেখায়।
  2. মাপ: hii = ১/n + (xi − x̄)²/Sxx, যোগফল p, নিয়ম ২p/n; Cook's D = e²/(p·MSE) × h/(১ − h)², নিয়ম ৪/n বা ১. সীমা মানে “খুলে দেখুন”, “মুছুন” নয়।
  3. Robust পথ: Huber বড় residual-এর টান δ-তে আটকায় (ওজন min(১, δ/∣r∣)), কিন্তু leverage-এ দুর্বল; RANSAC ভোটে inlier বাছে; Theil–Sen জোড়ার ঢালের median, breakdown প্রায় ২৯%.
  4. Quantile regression: pinball loss τ·r বা (τ − ১)·r; সমাধান একটি LP. statsmodels QuantReg, scikit-learn QuantileRegressor(alpha=0, solver="highs").
  5. Interval যাচাই: held-out ডেটায়, x-এর ভাগে ভাগে coverage; heteroscedasticity-তে OLS ± ধ্রুব σ কাছে বেশি আর দূরে কম ধরে। Quantile crossing পেলে প্রতিটি x-এ সাজিয়ে নিন।

পরের পর্বে: এতক্ষণ লক্ষ্য ছিল একটি সংখ্যা। লক্ষ্য যখন অনেকগুলো শ্রেণির একটি, বা একসঙ্গে কয়েকটি ট্যাগ? পর্ব ২৫ (Multiclass আর multilabel — OvR, OvO, softmax আর classifier chain) দেখাবে, দুই শ্রেণির classifier থেকে কীভাবে বহু শ্রেণি আর বহু ট্যাগের model বানানো আর মাপা যায়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান