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

কম label-এ শেখা — semi-supervised আর active learning

Learning with Few Labels — Self-Training, Label Spreading, When Unlabelled Data Hurts, Uncertainty Sampling and the Cost of Labels

Taught in Bangla, this lesson writes self-training by hand and matches scikit-learn's SelfTrainingClassifier, runs label spreading and shows how its default alpha leaves points unlabelled, and measures a case where unlabelled data hurts. It compares least confidence, margin and entropy sampling against random on the digits data over five seeds, with label costs and a stopping rule.

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

  • কখন label-হীন ডেটা সাহায্য করে: smoothness, cluster আর manifold অনুমান
  • নিশ্চয়তার সীমা দিয়ে self-training নিজে লেখা, আর scikit-learn-এর SelfTrainingClassifier-এর সঙ্গে হুবহু মেলানো
  • প্রতিবেশী-graph-এ label propagation আর label spreading, আর alpha-র একটি বাস্তব ফাঁদ
  • Semi-supervised কখন ক্ষতি করে, সত্যিকারের সংখ্যাসহ
  • Active learning-এর least confidence, margin, entropy আর committee, random-এর সঙ্গে learning curve, থামার নিয়ম আর টাকার হিসাব

মডিউল ১০-এর আগের চারটি পর্ব বিশেষ ধরনের ডেটা নিয়ে: পর্ব ৫০ (Imbalanced ডেটা — resampling, SMOTE, class weight আর threshold), পর্ব ৫১ (Time series forecasting — decomposition, ARIMA আর exponential smoothing), পর্ব ৫২ (ML দিয়ে forecasting — lag feature, backtesting আর অনেক series একসঙ্গে) আর পর্ব ৫৩ (Recommender system — collaborative filtering আর matrix factorization). এই পর্বের বিশেষত্ব ডেটায় নয়, label-এ: ডেটা প্রচুর, কিন্তু label অল্প। পর্ব ০৫-এ দেখেছেন label কীভাবে আসে, কত দামি আর কতটা শব্দযুক্ত; সেখানে weak supervision-ও এসেছে। এখানে দুটি ভিন্ন উত্তর। চারটি ব্লক পাতাতেই চলে (ব্রাউজারের Python, scikit-learn 1.8.0); প্রথমবার scikit-learn নামতে প্রায় দশ সেকেন্ড লাগে।

১ · Label কেন দামি — আর দুটি পথ

ধরুন একটি কোচিং সেন্টারে এক হাজার খাতা জমা পড়েছে, কিন্তু অভিজ্ঞ শিক্ষকের সময় আছে মাত্র কুড়িটি দেখার। দুটি বুদ্ধি আছে। এক, কুড়িটি দেখে বাকি খাতাগুলো গুছিয়ে রাখা: যে খাতাগুলো দেখতে প্রায় একই রকম (একই ভুল, একই উত্তর), তাদের নম্বরও কাছাকাছি হওয়ার কথা। দুই, এলোমেলো কুড়িটি না দেখে শিক্ষক বলুন কোন খাতাগুলো নিয়ে তিনি সবচেয়ে দ্বিধায়, শুধু সেগুলো দেখানো। প্রথমটি semi-supervised, দ্বিতীয়টি active learning.

বাজারঘরে প্রতিদিন কয়েকশো রিভিউ আসে। কোনটি আসল অভিযোগ (ফেরত, ভাঙা পণ্য, দেরি) আর কোনটি সাধারণ মন্তব্য, তা জানতে একজন মানুষকে পড়ে label দিতে হয়। এই পর্বে ধরে নেওয়া হচ্ছে প্রতিটি label-এর খরচ ৳৫ (কাল্পনিক), আর অভিজ্ঞ কর্মী দিনে কয়েকশোর বেশি পারেন না। রিভিউ জমা হয় হাজারে হাজারে, বিনা খরচে। তাই প্রশ্ন: label-হীন ডেটা দিয়ে কি কিছু শেখা যায়, আর label কিনতেই হলে কোনগুলো?

কম label-এ শেখার দুটি loop

ওপরে self-training: model নিজেই label বসায়। নিচে active learning: model বাছে, মানুষ label দেন।

Self-training loop and active learning loop Self-training (semi-supervised) ১. অল্প label দিয়ে model শেখানো ২. label-হীন ডেটায় অনুমান, p > সীমা? ৩. নিশ্চিতগুলো pseudo-label হিসেবে যোগ ঝুঁকি: ভুল pseudo-label নিজেকে নিশ্চিত করে Active learning (pool-based) ১. এ পর্যন্ত কেনা label দিয়ে শেখানো ২. pool থেকে সবচেয়ে অনিশ্চিতগুলো বাছা ৩. মানুষ label দেন (প্রতিটি ৳৫) থামা: বাজেট শেষ বা অনুমান স্থির
ছবি ৫৪.১ — দুই loop দেখতে প্রায় একই; পার্থক্য ধাপ ৩-এ। Self-training-এ label আসে model থেকে, বিনা খরচে কিন্তু ভুলের ঝুঁকিসহ; active learning-এ আসে মানুষ থেকে, খরচসহ কিন্তু সঠিক (মোটামুটি)।

দুটি শব্দ আগে পরিষ্কার করা যাক। Transductive শেখা মানে শুধু হাতে থাকা label-হীন বিন্দুগুলোর label অনুমান; inductive শেখা মানে এমন model, যা কালকের নতুন রিভিউতেও অনুমান দেবে। এই পর্বের প্রতিটি পদ্ধতিকে আমরা দুইভাবে মাপব: label-হীন ১,০০০টির ওপর, আর আলাদা ১,০০০টি নতুন test রিভিউতে।

২ · Label-হীন ডেটা কখন সাহায্য করে — তিনটি অনুমান

Label ছাড়া একটি বিন্দু বলে না সে কোন class-এর। তবু সে একটি জিনিস বলে: ডেটা কোথায় ঘন, কোথায় ফাঁকা। এই তথ্য label-এর অনুমানে কাজে লাগে কেবল তখন, যখন ঘনত্ব আর class-এর মধ্যে সম্পর্ক আছে। Olivier Chapelle, Bernhard Schölkopf আর Alexander Zien সম্পাদিত বই Semi-Supervised Learning (MIT Press, ২০০৬) এই সম্পর্কগুলোকে তিনটি অনুমানে সাজায়:

Smoothness

ঘন অঞ্চলে কাছাকাছি দুটি বিন্দুর label প্রায় সবসময় এক (smoothness assumption). দুই বিন্দুর মাঝে যদি ঘন বিন্দুর একটি “সেতু” থাকে, তারা এক class-এর।

kNN-এর মূল ধারণার আত্মীয়

Cluster

একই cluster-এর বিন্দু একই class-এর (cluster assumption). সমতুল্য রূপ: সীমারেখা যাবে ফাঁকা জায়গা দিয়ে (low-density separation).

ভাঙলে semi-supervised ক্ষতি করে

Manifold

উঁচু মাত্রার ডেটা আসলে কম মাত্রার একটি বাঁকা তলের ওপর থাকে (manifold hypothesis, পর্ব ৪২)। দূরত্ব মাপা উচিত সেই তল বরাবর।

graph-ভিত্তিক পদ্ধতির ভিত্তি

এই পর্বের রিভিউ ডেটা দুই মাত্রায় (দুটি সংখ্যায়) নামানো, যাতে ছবি আঁকা যায়। আকারটি scikit-learn-এর make_moons থেকে, দুটি বাঁকা চাঁদ: একটি “সাধারণ”, একটি “অভিযোগ”। এটি ধারণা বোঝানোর কৃত্রিম ডেটা, বাস্তবের embedding এত পরিষ্কার নয়। কিন্তু তিনটি অনুমানই এখানে সত্যি: প্রতিটি চাঁদ একটি ঘন, জোড়া লাগা অঞ্চল, আর দুই চাঁদের মাঝে ফাঁকা জায়গা। ১,০০০টি রিভিউর মধ্যে মাত্র ২০টির label জানা (প্রতি class-এ ১০টি)। একটি straight line এই সীমা টানতে পারে না, তাই base model হিসেবে k-nearest neighbors (AI-এর ভিত্তি কোর্সের পর্ব ৫৭)। পাতায় algorithm="kd_tree" দেওয়া, কারণ ডিফল্ট পথে ব্রাউজারের Python একটি অপ্রাসঙ্গিক সতর্কবার্তা ছাপে; ফল একই।

৩ · Self-training — নিজের নিশ্চিত অনুমানকে label বানানো

Self-training সবচেয়ে পুরোনো আর সরল পথ। ধারণাটি অন্তত ১৯৬০-এর দশকের (H. J. Scudder-এর ১৯৬৫ সালের কাজ প্রায়ই প্রথম উদাহরণ হিসেবে উদ্ধৃত হয়)। ধাপগুলো:

  1. Label-যুক্ত ডেটায় model শেখানো।
  2. Label-হীন প্রতিটি বিন্দুতে class-এর সম্ভাবনা অনুমান; সবচেয়ে বড় সম্ভাবনা max p.
  3. যেখানে max p একটি সীমার (threshold, এখানে ০.৯) চেয়ে বড়, সেখানে অনুমিত class-কে label ধরে যোগ করা।
  4. নতুন label-সেটে আবার শেখানো; কিছু যোগ না হলে, সব label পেলে বা সর্বোচ্চ দফা শেষে থামা।

scikit-learn-এর SelfTrainingClassifier ঠিক এটাই করে (ডিফল্ট threshold=0.75, max_iter=10; সীমার তুলনা কঠোরভাবে “বড়”)। একবার যোগ হওয়া pseudo-label আর ফেরত নেওয়া হয় না। নিচের ব্লকে loop নিজে লেখা; যেহেতু আমরা আসল label জানি, প্রতি দফায় নতুন pseudo-label-এর কতটা ঠিক তাও ছাপা হয়। বাস্তবে এই সংখ্যা জানা যায় না, শুধু শেখার জন্য দেখানো। kNN-এ ৭ জন প্রতিবেশীর ভোটই সম্ভাবনা, তাই p > ০.৯ মানে সাতজনই একমত।

import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.semi_supervised import SelfTrainingClassifier
from bazar_reviews import make_reviews

X, y, y_semi, Xt, yt = make_reviews()
def knn():                                   # kd_tree: পাতার Python-এ warning-মুক্ত
    return KNeighborsClassifier(n_neighbors=7, algorithm="kd_tree")
has = y_semi >= 0
print(f"label আছে {has.sum()}টির, নেই {(~has).sum()}টির")
print(f"শুধু 20টি label দিয়ে kNN: test accuracy {knn().fit(X[has], y[has]).score(Xt, yt):.3f}")

def self_train(X, y_semi, threshold=0.9, max_iter=10):
    yl = y_semi.copy()
    for r in range(1, max_iter + 1):
        known = yl >= 0
        if known.all():
            break
        m = knn().fit(X[known], yl[known])
        P = m.predict_proba(X[~known])
        sure = P.max(axis=1) > threshold     # শুধু খুব নিশ্চিত অনুমান
        if not sure.any():
            break
        idx = np.flatnonzero(~known)[sure]
        yl[idx] = m.classes_[P[sure].argmax(axis=1)]
        print(f"round {r}: +{sure.sum():3d} pseudo-label, ঠিক {np.mean(yl[idx] == y[idx]):.3f}, "
              f"মোট label {np.sum(yl >= 0)}")
    known = yl >= 0
    return knn().fit(X[known], yl[known]), yl

mine, yl = self_train(X, y_semi)
print(f"নিজে লেখা self-training: test accuracy {mine.score(Xt, yt):.3f}")
sk = SelfTrainingClassifier(knn(), threshold=0.9, max_iter=10).fit(X, y_semi)
print(f"SelfTrainingClassifier:  test accuracy {sk.score(Xt, yt):.3f}")
print("দুটির pseudo-label হুবহু এক?", np.array_equal(yl, sk.transduction_))

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

label আছে 20টির, নেই 980টির
শুধু 20টি label দিয়ে kNN: test accuracy 0.854
round 1: +322 pseudo-label, ঠিক 0.988, মোট label 342
round 2: +397 pseudo-label, ঠিক 0.887, মোট label 739
round 3: +117 pseudo-label, ঠিক 0.692, মোট label 856
round 4: + 72 pseudo-label, ঠিক 0.736, মোট label 928
round 5: + 31 pseudo-label, ঠিক 0.677, মোট label 959
round 6: + 14 pseudo-label, ঠিক 0.571, মোট label 973
round 7: +  3 pseudo-label, ঠিক 0.333, মোট label 976
round 8: +  1 pseudo-label, ঠিক 1.000, মোট label 977
নিজে লেখা self-training: test accuracy 0.862
SelfTrainingClassifier:  test accuracy 0.862
দুটির pseudo-label হুবহু এক? True

প্রথম দফা চমৎকার: ৩২২টি নতুন label, ৯৮.৮% ঠিক। এগুলো চাঁদের ঘন মাঝখানের বিন্দু, যেখানে সাতজন প্রতিবেশীই একই class-এর label-যুক্ত বিন্দু। তারপর মান নামে: দ্বিতীয় দফায় ৮৮.৭%, তৃতীয়তে ৬৯.২%, ষষ্ঠতে ৫৭.১%. সহজ বিন্দু শেষ, এখন বাকি দুই চাঁদের মাথা যেখানে কাছাকাছি আসে, সেই কঠিন অঞ্চল। সেখানে আগের দফার ভুল label প্রতিবেশী হয়ে নতুন ভুল ডেকে আনে। শেষে ৯৭৭টি label, test accuracy ০.৮৫৪ থেকে মাত্র ০.৮৬২. লাভ সামান্য; ২৩টি বিন্দু কখনো সীমা পেরোয়নি, আর নবম দফায় কেউ যোগ না হওয়ায় loop থেমেছে।

মিলেছে: নিজে লেখা loop আর SelfTrainingClassifier-এর ৯৮০টি pseudo-label হুবহু এক (transduction_-এ −১ মানে যে বিন্দু label পায়নি)। তাই library-র ভেতরে কোনো জাদু নেই।

দফায় দফায় pseudo-label-এর মান কেন নামে, তা একটি ছোট উদাহরণে ধাপে ধাপে দেখুন। সংখ্যাগুলো কাল্পনিক, শুধু যুক্তি বোঝানোর জন্য।

সীমা = 0.9কাল্পনিক ৬টি রিভিউ

            
  1. শুরু। কুড়িটি label-এ শেখা model ছয়টি label-হীন রিভিউতে “অভিযোগ”-এর সম্ভাবনা দেয়। আসল label আমরা জানি, model জানে না।
  2. বাছাই। দুই class-এ নিশ্চয়তা হলো max(p, ১ − p); r3-এর ০.০৮ মানে “সাধারণ”-এ ০.৯২. সীমা ০.৯ পেরোয় চারটি: r1, r2, r3, r5.
  3. Pseudo-label. চারটির তিনটি ঠিক। r5-এ model ০.৯১ নিশ্চিত, কিন্তু ভুল। উচ্চ নিশ্চয়তা মানেই ঠিক নয়, বিশেষ করে model যখন ঠিকমতো calibrated নয় (পর্ব ২৬)।
  4. ভুলের ছোঁয়াচ। পরের দফায় r5 সত্যিকারের label-এর মতো গণ্য। তার প্রতিবেশী r6-এর সম্ভাবনা ০.৪০ থেকে ০.৯৩-এ ওঠে, আরেকটি ভুল যোগ হয়। Model নিজের ভুলকে প্রমাণ হিসেবে ব্যবহার করছে: এটাই confirmation bias.
  5. প্রতিরোধ। সীমা বাড়ালে কম কিন্তু পরিষ্কার label; ছোট একটি মানুষের-label-যুক্ত validation সেটে pseudo-label-এর মান মাপা; প্রতি class-এ সংখ্যা বেঁধে দেওয়া, যাতে এক class গ্রাস না করে; আর প্রতি দফায় পুরোনো pseudo-label আবার যাচাই। scikit-learn-এর সংস্করণ শেষেরটি করে না।

৪ · Graph দিয়ে label ছড়ানো — label propagation আর label spreading

Self-training-এ model একবারে শুধু নিশ্চিত বিন্দুগুলো নেয়। Graph পদ্ধতি সব বিন্দুকে একসঙ্গে দেখে। প্রতিটি বিন্দুকে তার k জন নিকট প্রতিবেশীর সঙ্গে জোড়া হয় (একটি neighbour graph); তারপর label-এর সম্ভাবনা পাড়ার খবরের মতো প্রতিবেশী থেকে প্রতিবেশীতে ছড়ায়। চাঁদের ভেতরে বিন্দুগুলো ঘন সেতুতে যুক্ত, দুই চাঁদের মাঝে সেতু কম, তাই খবর চাঁদ বরাবর চলে, ফাঁক পেরোয় কম।

ধরুন n টি বিন্দু, C টি class. Y একটি n × C matrix: label-যুক্ত বিন্দুর সারিতে তার class-এর ঘরে ১, বাকি সব ০. W প্রতিবেশী-graph-এর ওজন (kNN-এ জোড়া থাকলে ১), D তার সারি-যোগফলের কর্ণ matrix.

  • Label propagation (Xiaojin Zhu আর Zoubin Ghahramani, ২০০২-এর প্রতিবেদন): F ← D−1WF, অর্থাৎ প্রতিটি বিন্দু প্রতিবেশীদের গড় নেয়; তারপর label-যুক্ত সারিগুলো আবার আসল label-এ বসানো (hard clamping). ভুল label কখনো শোধরায় না।
  • Label spreading (Dengyong Zhou ও সহকর্মীরা, NIPS ২০০৩-এ প্রকাশিত, প্রায়ই ২০০৪ উল্লেখ হয়): F ← αSF + (১ − α)Y, যেখানে S = D−1/2WD−1/2. প্রতি ধাপে α ভাগ প্রতিবেশীর খবর, (১ − α) ভাগ নিজের শুরুর label. এই α-কে বলে clamping factor; শুরুর label-ও একটু বদলাতে পারে বলে কিছু ভুল label সহ্য করে।

Iteration চলতে থাকলে F স্থির হয় F* = (১ − α)(I − αS)−1Y-এ; প্রতিটি বিন্দুর অনুমান হলো সারির সবচেয়ে বড় ঘর। এখন একটি সূক্ষ্ম কথা। শুরুতে label-হীন বিন্দুর সারি পুরো শূন্য। প্রথম ধাপে খবর পৌঁছায় label-যুক্ত বিন্দুর সরাসরি প্রতিবেশীদের কাছে, দ্বিতীয় ধাপে প্রতিবেশীর প্রতিবেশীর কাছে, আর প্রতিটি লাফে খবর মোটামুটি α গুণ দুর্বল হয়। scikit-learn-এর LabelSpreading-এ ডিফল্ট alpha=0.2 আর tol=0.001: পাঁচ লাফে ০.২৫ ≈ ০.০০০৩, যা tol-এর চেয়ে ছোট, তাই loop “বদল খুব কম” ভেবে থেমে যায়। তখনো যেসব বিন্দুতে খবর পৌঁছায়নি, তাদের সারি শূন্যই থাকে। দেখুন।

import numpy as np
import matplotlib.pyplot as plt
from sklearn.neighbors import KNeighborsClassifier
from sklearn.semi_supervised import LabelSpreading
from bazar_reviews import make_reviews

X, y, y_semi, Xt, yt = make_reviews()
has = y_semi >= 0
sup = KNeighborsClassifier(n_neighbors=7, algorithm="kd_tree").fit(X[has], y[has])
print(f"শুধু 20টি label (kNN): test accuracy {sup.score(Xt, yt):.3f}")
fits = {}
for alpha in (0.2, 0.8):
    ls = LabelSpreading(kernel="knn", n_neighbors=10, alpha=alpha, max_iter=100).fit(X, y_semi)
    empty = ls.label_distributions_.sum(axis=1) == 0      # কোনো label পৌঁছায়নি
    print(f"alpha={alpha}: {ls.n_iter_:2d} ধাপে থামল, label পৌঁছায়নি {empty.sum():3d}টিতে, "
          f"label-হীন 980টিতে accuracy {np.mean(ls.transduction_[~has] == y[~has]):.3f}")
    fits[alpha] = (ls, empty)
ls, empty = fits[0.8]
print(f"alpha=0.8, নতুন 1000টি test রিভিউ: accuracy {ls.score(Xt, yt):.3f}")

g1, g2 = np.meshgrid(np.linspace(-1.6, 2.6, 160), np.linspace(-1.1, 1.6, 110))
G = np.c_[g1.ravel(), g2.ravel()]
fig, axes = plt.subplots(1, 3, figsize=(12, 3.8), sharey=True)
titles = ["kNN, 20 labels only", "LabelSpreading, alpha=0.2", "LabelSpreading, alpha=0.8"]
for ax, title, m in zip(axes, titles, [sup, None, ls]):
    if m is not None:
        ax.contourf(g1, g2, m.predict_proba(G)[:, 1].reshape(g1.shape),
                    levels=[0, 0.5, 1], colors=["#bfdbfe", "#fed7aa"])
    ax.scatter(*X.T, c=y, s=5, cmap="coolwarm", alpha=0.5)
    ax.scatter(*X[has].T, c=y[has], s=90, marker="*", cmap="coolwarm", edgecolors="k")
    ax.set(title=title, xlabel="feature 1")
e = fits[0.2][1]
axes[1].scatter(*X[e].T, s=14, marker="x", color="k", label="no label reached")
axes[1].legend(loc="lower left", fontsize=8); axes[0].set_ylabel("feature 2")
fig.subplots_adjust(wspace=0.08)

আসল আউটপুট (ব্রাউজারের Python), নিচে ছবি:

শুধু 20টি label (kNN): test accuracy 0.854
alpha=0.2:  5 ধাপে থামল, label পৌঁছায়নি 176টিতে, label-হীন 980টিতে accuracy 0.843
alpha=0.8: 28 ধাপে থামল, label পৌঁছায়নি   0টিতে, label-হীন 980টিতে accuracy 0.908
alpha=0.8, নতুন 1000টি test রিভিউ: accuracy 0.900

ডিফল্টের ফাঁদ: alpha=0.2-এ পাঁচ ধাপেই থামা, আর ১৭৬টি বিন্দুতে কোনো খবর পৌঁছায়নি; মাঝের ছবিতে কালো ×. তাদের সারি শূন্য, তবু transduction_ তাদেরও একটি label দেয়: শূন্য সারির argmax প্রথম class, অর্থাৎ “সাধারণ”। কোনো সতর্কবার্তা আসে না। ফলে accuracy ০.৮৪৩, শুধু kNN-এর চেয়েও খারাপ। ডেস্কটপে এমন model দিয়ে নতুন বিন্দুর predict চালালে শূন্য দিয়ে ভাগের RuntimeWarning আসে (ব্রাউজারের Python-এ floating-point সতর্কবার্তা আসে না)। max_iter বাড়িয়ে লাভ নেই, কারণ loop নিজেই “স্থির” ভেবে থামছে।

alpha=0.8-এ খবর দূরে যায়: ২৮ ধাপ, সব বিন্দুতে পৌঁছায়, label-হীন ৯৮০টিতে ০.৯০৮ আর নতুন test-এ ০.৯০০, শুধু ২০টি label-এর kNN-এর (০.৮৫৪) চেয়ে স্পষ্ট ভালো, আর self-training-এর (০.৮৬২) চেয়েও। ডান ছবিতে সীমারেখা চাঁদের বাঁক অনুসরণ করে; বাঁ ছবিতে kNN-এর সীমা তারকা-চিহ্নিত কুড়িটি বিন্দুর মাঝামাঝি সোজাসুজি কাটে। তবু নিখুঁত নয়: বাঁ দিকের চাঁদের মাথায় একটি লাল তারকা নীল চাঁদের গায়ে বসে আছে (শব্দের কারণে সরে যাওয়া বিন্দু), আর তার আশেপাশে ভুল ছড়িয়েছে। পাঁচটি আলাদা ডেটা-seed-এ ডেস্কটপে একই সেটিংয়ে label-হীন অংশে accuracy ০.৯১ থেকে ০.৯৮৭; এই seed সবচেয়ে কঠিনগুলোর একটি।

বাস্তবের পরামর্শ: kernel="rbf" (ডিফল্ট) পুরো n × n ঘন matrix বানায়, বড় ডেটায় memory খায়; kernel="knn" sparse. n_neighbors খুব ছোট হলে graph টুকরো হয়, কোনো টুকরোয় label না থাকলে সেখানে খবর যায় না; খুব বড় হলে দুই চাঁদ জুড়ে যায়। সবসময় দেখুন কতগুলো সারি শূন্য (label_distributions_.sum(axis=1) == 0). Graph আর k-এর বাছাই পর্ব ৪২-এর Isomap আর UMAP-এর মতোই সংবেদনশীল।

৫ · যখন semi-supervised ক্ষতি করে

এতক্ষণ ডেটা semi-supervised-এর পক্ষে সাজানো ছিল। এবার উল্টো। বাজারঘরের ক্রেতারা দুটি স্পষ্ট দলে: ঢাকার ভেতরে আর বাইরে (প্রথম feature-এ বড় ফাঁক)। প্রশ্ন: ডেলিভারি দেরিতে হবে কি না। আসল নিয়মটি একটি হেলানো রেখা, ঢাল ০.৬: সে দুটি দলকেই মাঝখান দিয়ে কাটে। অর্থাৎ cluster অনুমান মিথ্যা: একই দলের ভেতরে দুই class-ই আছে, আর আসল সীমা ঘন অঞ্চল দিয়ে যায়। আসল সীমা সরলরেখা, তাই এখানে সঠিক model হলো logistic regression (AI-এর ভিত্তি কোর্সের পর্ব ৫৯)। দশটি seed-এ তিনটি পদ্ধতি তুলনা: শুধু ২০টি label-এ logistic regression, তার ওপর self-training, আর label spreading.

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.semi_supervised import SelfTrainingClassifier, LabelSpreading

def customers(n, rng):                       # দুটি স্পষ্ট দল, কিন্তু label-এর সীমা দুটিকেই কাটে
    group = rng.integers(0, 2, n)            # 0 = ঢাকার ভেতরে, 1 = ঢাকার বাইরে
    X = rng.normal(0, 1, (n, 2)) * [0.8, 1.5] + np.c_[np.where(group, 2.5, -2.5), np.zeros(n)]
    late = (X[:, 1] > 0.6 * X[:, 0]).astype(int)   # 1 = দেরি; আসল সীমার ঢাল 0.6
    return X, late
slope = lambda m: -m.coef_[0, 0] / m.coef_[0, 1]   # শেখা সীমারেখার ঢাল

rows = []
for seed in range(10):
    rng = np.random.default_rng(seed)
    X, y = customers(1000, rng); Xt, yt = customers(1000, rng)
    lab = np.r_[rng.choice(np.flatnonzero(y == 0), 10, replace=False),
                rng.choice(np.flatnonzero(y == 1), 10, replace=False)]
    y_semi = np.full(1000, -1); y_semi[lab] = y[lab]
    sup = LogisticRegression().fit(X[lab], y[lab])
    st = SelfTrainingClassifier(LogisticRegression(), threshold=0.9).fit(X, y_semi)
    ls = LabelSpreading(kernel="knn", n_neighbors=10, alpha=0.8, max_iter=100).fit(X, y_semi)
    rows.append([m.score(Xt, yt) for m in (sup, st, ls)])
    if seed < 3:
        print(f"seed {seed}: শুধু 20টি label {rows[-1][0]:.3f} | self-training {rows[-1][1]:.3f} | "
              f"LabelSpreading {rows[-1][2]:.3f} | ঢাল {slope(sup):.2f} -> {slope(st.estimator_):.2f}")
R = np.array(rows)
print("10টি seed-এর গড়:", np.round(R.mean(axis=0), 3), " sd:", np.round(R.std(axis=0, ddof=1), 3))
print("supervised-এর চেয়ে খারাপ কতবার: self-training", (R[:, 1] < R[:, 0]).sum(),
      "/10, LabelSpreading", (R[:, 2] < R[:, 0]).sum(), "/10")

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

seed 0: শুধু 20টি label 0.920 | self-training 0.876 | LabelSpreading 0.879 | ঢাল 0.84 -> 1.11
seed 1: শুধু 20টি label 0.955 | self-training 0.935 | LabelSpreading 0.929 | ঢাল 0.56 -> 0.77
seed 2: শুধু 20টি label 0.918 | self-training 0.858 | LabelSpreading 0.876 | ঢাল 1.01 -> 1.73
10টি seed-এর গড়: [0.954 0.922 0.907]  sd: [0.024 0.037 0.034]
supervised-এর চেয়ে খারাপ কতবার: self-training 9 /10, LabelSpreading 9 /10

দুটি semi-supervised পদ্ধতিই ক্ষতি করেছে: শুধু কুড়িটি label-এ গড় accuracy ০.৯৫৪, self-training-এ ০.৯২২, label spreading-এ ০.৯০৭; দশটির নয়টি seed-এ দুটিই খারাপ। কারণটি ঢালের সংখ্যায় দেখা যায়। আসল ঢাল ০.৬. Self-training-এর পরে seed ০-এ ঢাল ০.৮৪ থেকে ১.১১, seed ২-এ ১.০১ থেকে ১.৭৩: রেখা খাড়া হয়ে দুই দলের মাঝের ফাঁকের দিকে ঘোরে। নিশ্চিত pseudo-label আসে প্রতিটি দলের দূরের অংশ থেকে, যেখানে দলের পরিচয় আর class প্রায় এক হয়ে যায়; সেই শত শত বিন্দু রেখাকে “দল আলাদা করার” দিকে টানে। Label spreading-এ খবর দলের ভেতরে অবাধে ছড়ায়, যেখানে আসল সীমা আছে, তাই ভুল ছড়ায়।

শিক্ষা: label-হীন ডেটা নিরপেক্ষ নয়। সে সবসময় সীমাকে ফাঁকা জায়গার দিকে টানে। আপনার সমস্যায় সেই টান ঠিক দিকে কি না, তা আগে থেকে জানা যায় না, শুধু মাপা যায়।

গবেষণাও একই সতর্কতা দেয়। Avital Oliver ও সহকর্মীরা (NeurIPS ২০১৮, “Realistic Evaluation of Deep Semi-Supervised Learning Algorithms”) দেখান, ভালোভাবে tune করা supervised baseline-এর পাশে রাখলে অনেক পদ্ধতির দাবি করা লাভ ছোট হয়ে যায়, আর label-হীন ডেটায় এমন class থাকলে যা label-যুক্ত অংশে নেই, ফল খারাপও হতে পারে। তাই semi-supervised ব্যবহারের নিয়ম:

  • একই label দিয়ে শক্ত supervised baseline আগে; semi-supervised তার চেয়ে ভালো কি না, কয়েকটি seed-এ।
  • মানুষের দেওয়া label-যুক্ত একটি ছোট validation সেট রাখুন, যা কখনো pseudo-label হবে না; মূল্যায়ন শুধু সেখানে আর test-এ। pseudo-label-যুক্ত ডেটায় মূল্যায়ন নিজের খাতা নিজে দেখার মতো।
  • Label-হীন ডেটা যেন একই উৎস আর সময়ের হয়; অন্য মাসের বা অন্য অ্যাপের রিভিউ মেশালে বণ্টন বদলায় (পর্ব ৫৯, Distribution shift — covariate, label আর concept shift ধরা ও সামলানো)।

৬ · Active learning — কোন label কিনবেন, তা model বাছে

Semi-supervised বিনামূল্যের label খোঁজে; active learning মেনে নেয় label কিনতে হবে, শুধু কেনাকাটা বুদ্ধিমানভাবে। সবচেয়ে প্রচলিত রূপ pool-based: একটি বড় label-হীন pool, অল্প কিছু শুরুর label, আর প্রতি দফায় model pool থেকে কয়েকটি বাছে, মানুষ label দেন, model আবার শেখে (ছবি ৫৪.১-এর নিচের loop). বাছাইয়ের সবচেয়ে সরল আর জনপ্রিয় নিয়ম uncertainty sampling (David Lewis আর William Gale, SIGIR ১৯৯৪): যেখানে model সবচেয়ে দ্বিধায়, সেখানকার label সবচেয়ে বেশি শেখাবে। Model-এর class সম্ভাবনা p1 ≥ p2 ≥ … ≥ pC (বড় থেকে ছোট সাজানো) হলে তিনটি মাপ:

  • Least confidence: ১ − p1. শুধু সবচেয়ে বড় সম্ভাবনা দেখে; বড় মান মানে বেশি অনিশ্চিত।
  • Margin: p1 − p2. প্রথম দুইয়ের ব্যবধান; ছোট মান মানে দুই class-এর মধ্যে দোটানা।
  • Entropy: H = −∑ pk ln pk (entropy, AI-এর ভিত্তি কোর্সের পর্ব ৩৮)। সব class-এর ছড়ানো দেখে; বড় মান মানে বেশি অনিশ্চিত।

দুই class-এ তিনটি মাপ একই ক্রম দেয়: সবই শুধু p1-এর ওপর নির্ভর করে, আর p1 ০.৫-এর কাছে গেলে তিনটিই “বেশি অনিশ্চিত” বলে। পার্থক্য আসে তিন বা বেশি class-এ। রিভিউকে চারটি বিষয়ে ভাগ করার model-এর তিনটি অনুমান দিয়ে হাতে হিসাব দেখুন।

class: ডেলিভারি, পেমেন্ট, পণ্য, অন্যান্যln = স্বাভাবিক log

            
  1. তিনটি অনুমান। A তিনটি class-এ প্রায় সমান ভাগ। B দুই class-এ (ডেলিভারি না পেমেন্ট) প্রায় আধাআধি। C একটিতে একটু এগিয়ে, বাকি তিনে সমান ছড়ানো।
  2. Least confidence. শুধু p1 দেখে। A-র সবচেয়ে বড় সম্ভাবনাই মাত্র ০.৪০, তাই ১ − ০.৪০ = ০.৬০, সবচেয়ে বড়: A বাছা।
  3. Margin. প্রথম দুইয়ের ব্যবধান। B-তে ০.৫০ আর ০.৪৫, ব্যবধান মাত্র ০.০৫: B বাছা। মানুষের একটি label ঠিক এই দোটানা মেটায়।
  4. Entropy. প্রতিটি পদ −p ln p, যেমন −০.৪০ × ln ০.৪০ = ০.৩৬৬৫২. C-তে চারটি class-এই সম্ভাবনা থাকায় সবচেয়ে বড়, ১.১৮২৫১: C বাছা। চার class-এ সর্বোচ্চ সম্ভব মান ln ৪ ≈ ১.৩৮৬.
  5. তুলনা। তিন নিয়মে তিন বাছাই। Least confidence শুধু শীর্ষ দেখে, margin শীর্ষ দুই, entropy সব। Entropy অপ্রাসঙ্গিক ছোট class-গুলোর ছড়ানোতে টানা পড়ে, যা অনেক class-এ সমস্যা করতে পারে; সেটা পরের ব্লকে দেখবেন।

Model-এর সম্ভাবনা ছাড়াও “দ্বিধা” মাপার উপায় আছে। Query by committee (H. Sebastian Seung, Manfred Opper আর Haim Sompolinsky, ১৯৯২) কয়েকটি model-এর একটি দল বানায় (যেমন পর্ব ৩৩-এর bootstrap দিয়ে), আর যে বিন্দুতে তাদের ভোট সবচেয়ে বেশি ভাগ হয় (vote entropy বড়), সেটি বাছে। Uncertainty sampling-এর একটি চেনা দুর্বলতা: সে outlier ভালোবাসে, কারণ বিচ্ছিন্ন অদ্ভুত বিন্দুতে model সবসময় দ্বিধায়, অথচ সেই label অন্য কাউকে শেখায় না। Density-weighted sampling (Burr Settles আর Mark Craven, ২০০৮) তাই অনিশ্চয়তাকে বিন্দুটির আশেপাশের ঘনত্ব দিয়ে গুণ করে। আর একসঙ্গে দশটি কিনলে (batch active learning) দশটিই প্রায় একই জায়গা থেকে আসতে পারে; তখন বৈচিত্র্য দরকার, যেমন অনিশ্চিত প্রার্থীদের cluster করে প্রতিটি cluster থেকে একটি, বা Ozan Sener আর Silvio Savarese-এর (ICLR ২০১৮) core-set পদ্ধতি।

৭ · Random-এর সঙ্গে সৎ তুলনা — learning curve আর টাকার হিসাব

বাজারঘরের গুদামে হাতে লেখা অর্ডার স্লিপের অঙ্ক পড়তে একটি model দরকার, ধরা যাক। ডেটা: scikit-learn-এর সঙ্গে আসা load_digits (UCI-র হাতে লেখা অঙ্ক, ১,৭৯৭টি ৮ × ৮ ছবি, দশটি class). প্রতি seed-এ ১,০০০টি ছবি pool, বাকি ৭৯৭টি এলোমেলো test. শুরুতে প্রতি অঙ্কের ২টি label (মোট ২০), তারপর প্রতি দফায় ১০টি কেনা, ১৫০ পর্যন্ত। Model: pool-এ শেখা PCA দিয়ে ২০ মাত্রায় নামিয়ে logistic regression (PCA label দেখে না, তাই label-হীন pool-এ শেখানো বৈধ)। চার কৌশল, পাঁচটি seed, গড় ± sd. প্রতি label ৳৫ (কাল্পনিক)। ব্লকটি কয়েক সেকেন্ড নেয়।

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
from sklearn.linear_model import LogisticRegression

X, y = load_digits(return_X_y=True); X = X / 16   # 1797টি হাতে লেখা অঙ্ক, 8x8; pixel 0-1
def score(P, how):                           # বড় স্কোর = আগে কেনা হবে
    s = np.sort(P, axis=1)
    return {"least_conf": 1 - s[:, -1], "margin": s[:, -2] - s[:, -1],
            "entropy": -(P * np.log(P + 1e-12)).sum(axis=1)}[how]
def run(how, seed, step=10, budget=150):
    rng = np.random.default_rng(seed); p = rng.permutation(len(y))
    pool, test = p[:1000], p[1000:]
    pca = PCA(20, random_state=0).fit(X[pool]); Xp, Xt = pca.transform(X[pool]), pca.transform(X[test])
    lab = [i for c in range(10) for i in rng.choice(np.flatnonzero(y[pool] == c), 2, replace=False)]
    curve = []
    while True:
        m = LogisticRegression(max_iter=1000).fit(Xp[lab], y[pool][lab])
        curve.append(m.score(Xt, y[test]))
        if len(lab) >= budget: return curve
        rest = np.setdiff1d(np.arange(1000), lab)
        pick = (rng.choice(rest, step, replace=False) if how == "random"
                else rest[np.argsort(-score(m.predict_proba(Xp[rest]), how), kind="stable")[:step]])
        lab += list(pick)
n_lab = np.arange(20, 151, 10); fig, ax = plt.subplots(figsize=(7, 4))
print("label:      ", "  ".join(f"{n:5d}" for n in n_lab[::2]))
for how in ("random", "least_conf", "margin", "entropy"):
    A = np.array([run(how, s) for s in range(5)])          # 5টি seed
    mu, sd = A.mean(axis=0), A.std(axis=0, ddof=1)
    ax.plot(n_lab, mu, marker="o", ms=3, label=how); ax.fill_between(n_lab, mu - sd, mu + sd, alpha=0.15)
    need = n_lab[np.argmax(mu >= 0.90)] if (mu >= 0.90).any() else None
    print(f"{how:11s}", "  ".join(f"{a:.3f}" for a in mu[::2]), f"| 0.90-তে পৌঁছাতে {need} label, খরচ ৳{need * 5 if need else '-'}")
ax.set(xlabel="labels bought", ylabel="test accuracy", title="Active learning on digits: mean of 5 seeds, band = +/- 1 sd")
ax.legend()

আসল আউটপুট (ব্রাউজারের Python), নিচে ছবি:

label:          20     40     60     80    100    120    140
random      0.759  0.803  0.835  0.856  0.881  0.894  0.903 | 0.90-তে পৌঁছাতে 140 label, খরচ ৳700
least_conf  0.759  0.768  0.819  0.862  0.891  0.909  0.921 | 0.90-তে পৌঁছাতে 120 label, খরচ ৳600
margin      0.759  0.812  0.882  0.906  0.915  0.927  0.930 | 0.90-তে পৌঁছাতে 80 label, খরচ ৳400
entropy     0.759  0.741  0.814  0.850  0.868  0.886  0.896 | 0.90-তে পৌঁছাতে 150 label, খরচ ৳750

বিজয়ী margin: ৬০টি label-এ গড় ০.৮৮২, random-এর ০.৮৩৫; ০.৯০-তে পৌঁছায় ৮০টিতে, random ১৪০টিতে। ৳৫ দরে ৳৪০০ বনাম ৳৭০০: একই মানের model প্রায় ৪৩% কম খরচে। কিন্তু সব “বুদ্ধিমান” কৌশল জেতেনি: ৪০টি label-এ least confidence (০.৭৬৮) আর entropy (০.৭৪১) দুটিই random-এর (০.৮০৩) পেছনে; entropy শেষ পর্যন্ত random-এর চেয়ে ধীর থাকে (০.৯০-তে ১৫০টিতে)। দশ class-এ entropy সেই বিন্দুগুলো পছন্দ করে যেখানে সম্ভাবনা অনেক class-এ সামান্য করে ছড়ানো, যা প্রায়ই অস্পষ্ট বা বিকৃত লেখা; তার label কম শেখায়। শুরুতে model দুর্বল, তাই তার “অনিশ্চয়তা”-ও অবিশ্বস্ত: এটাই cold start সমস্যা।

ছড়ানো দেখুন: ছবির ছায়া ± ১ sd. শুরুর দিকে ব্যান্ডগুলো একে অপরের ওপর পড়ে; ৬০টির পরে margin-এর ব্যান্ড বাকিদের ওপরে আলাদা হয়। পাঁচটি seed কম; অন্য ডেটা বা model-এ ক্রম বদলাতে পারে, আর এটি একটি পরিবেশের এক run, সাধারণ নিয়ম নয়। এই ব্লক বানানোর সময় দুটি মাত্রার Gaussian ডেটায় linear model দিয়ে চেষ্টা করা হয়েছিল: সেখানে কৌশলগুলোর পার্থক্য seed-এর ছড়ানোর চেয়ে ছোট ছিল, আর বেশি মিশে থাকা class-এ least confidence ও entropy random-এর চেয়ে খারাপ।

লক্ষ করুন, test set প্রতিবার এলোমেলোভাবে আলাদা করা, আর loop কখনো তাকে দেখে না। Active learning-এ এটা বিশেষ জরুরি, কারণ কেনা label-সেট ইচ্ছাকৃতভাবে পক্ষপাতদুষ্ট (sampling bias): সে সীমার কাছের কঠিন উদাহরণে ভরা। সেই সেটে মাপা accuracy আসল চেহারার চেয়ে কম দেখাবে, আর cross-validation-ও আশাবাদী বা হতাশাবাদী দুই দিকেই বিকৃত হতে পারে। মূল্যায়নের জন্য আলাদা, এলোমেলো, মানুষের-label-যুক্ত একটি ছোট সেট আলাদা বাজেটে কিনুন।

৮ · Cold start, থামার নিয়ম আর বাস্তবের ঝামেলা

Cold start: প্রথম কয়েকটি label এলোমেলো (বা প্রতিটি class থেকে কয়েকটি, যদি class আগে জানা থাকে) কেনাই নিরাপদ, কারণ দুর্বল model-এর অনিশ্চয়তা অর্থহীন। একটি সাধারণ মিশ্রণ: প্রতি দফায় বেশিরভাগ uncertainty দিয়ে, কিছু এলোমেলো, যাতে model নিজের অন্ধ জায়গাও দেখে। পর্ব ০৫-এর labeling function দিয়েও শুরুর label আসতে পারে।

কখন থামবেন (stopping criterion)? বাস্তবে test-এর accuracy জানা নেই, তাই নির্ভর করতে হয় label ছাড়াই মাপা যায় এমন সংকেতে। Michael Bloodgood আর K. Vijay-Shanker (২০০৯) প্রস্তাব করেন “stabilizing predictions”: পরপর দুটি model একটি স্থির label-হীন সেটে কতটা একই অনুমান দেয়; মিল খুব বেশি হলে নতুন label আর model বদলাচ্ছে না। নিচের সংখ্যাগুলো অংশ ৭-এর margin loop-এর seed ০, ২০০ label পর্যন্ত চালানো (ডেস্কটপে সত্যিকারের run: Python 3.14.4, scikit-learn 1.8.0, NumPy 2.4.6). Test-এর কলাম শুধু শেখার জন্য।

margin sampling, seed 0প্রতি label ৳5

            
  1. শুরু। কুড়িটি label, ৳১০০. Pool-এর অর্ধেক ছবিতে margin ০.২-এর কম: model প্রায় সর্বত্র দ্বিধায়। এখানে থামার প্রশ্নই নেই।
  2. দ্রুত শেখা। ৫০টি label-এ test ০.৮৬৮: তিন দফায়, বাড়তি ৳১৫০ খরচে, ০.১৩৭ বেশি। পরপর দুই model-এর মিল ০.৮৮৬: প্রতি দফায় প্রায় ১১% অনুমান বদলাচ্ছে।
  3. লাভ কমছে। ৮০ থেকে ১১০-এ test ০.০৩০ বাড়ে, মিল ০.৯৭৬-এ ওঠে, দ্বিধার ভাগ ৯.২%. Learning curve চ্যাপ্টা হচ্ছে।
  4. স্থির। ১৫০-এ মিল ০.৯৯০, ১৬০-এ ০.৯৯১: পরপর দুবার ০.৯৯-এর ওপরে। নতুন label আর model প্রায় বদলাচ্ছে না।
  5. টাকার সিদ্ধান্ত। নিয়ম মানলে ১৬০টিতে থামা, মোট ৳৮০০. শেষ পঞ্চাশটি label-এ ৳২৫০ খরচে test ০.০১৭ বেড়েছে। এই বাড়তি নির্ভুলতার মূল্য ব্যবসার ক্ষতির হিসাব থেকে আসে (পর্ব ০২-এর খরচভিত্তিক কাঠামো); মূল্য বেশি হলে কেনা চালু।
  6. সতর্কতা। বাস্তবে test কলাম থাকে না; একটি ছোট এলোমেলো validation সেট আলাদা বাজেটে কিনে কাছাকাছি মান মাপুন। এখানে সংখ্যা একটি seed-এর এক run; ০.৯৯-এর মতো সীমা নিজের ডেটায় যাচাই করে বসাতে হয়।

বাস্তবের আরও ঝামেলা:

  • সব label-এর দাম সমান নয় (annotation cost): লম্বা রিভিউ পড়তে বেশি সময়, আর model যেগুলোয় দ্বিধায়, সেগুলোয় মানুষও প্রায়ই দ্বিধায়। দ্বিধার উদাহরণে label-দাতাদের মিল কম (পর্ব ০৫-এর kappa), তাই কেনা label-এ label noise বেশি থাকতে পারে।
  • Label-সেট model-এর জন্য বানানো: logistic regression-এর জন্য বাছা উদাহরণ পরে gradient boosting শেখাতে ব্যবহার করলে লাভ না-ও থাকতে পারে। David Lowell, Zachary Lipton আর Byron Wallace (EMNLP ২০১৯) টেক্সট শ্রেণিবিন্যাসে দেখান, active learning-এর লাভ সমস্যাভেদে অস্থির, আর এক model-এর জন্য কেনা ডেটা অন্য model-এ প্রায়ই random-এর চেয়ে ভালো নয়।
  • মানুষের সময়: প্রতি দফায় model আবার শেখাতে যদি কয়েক ঘণ্টা লাগে, label-দাতা বসে থাকেন। তখন বড় batch, বা পুরোনো model দিয়ে বাছাই।
  • দুই পথ একসঙ্গে: active learning দিয়ে কঠিন উদাহরণ কিনে, সহজগুলোর জন্য self-training বা label spreading, এই মিশ্রণ বাস্তবে চলে। তবে অংশ ৫-এর সতর্কতা তখনো খাটে।

আরও গভীরে: Burr Settles-এর “Active Learning Literature Survey” (University of Wisconsin–Madison-এর কারিগরি প্রতিবেদন, ২০০৯) এখনো সবচেয়ে উদ্ধৃত পরিচিতি। Semi-supervised-এ Xiaojin Zhu-র “Semi-Supervised Learning Literature Survey” আর Chapelle, Schölkopf ও Zien-এর বই ভিত্তি; Thorsten Joachims-এর transductive SVM (১৯৯৯) low-density separation-কে সরাসরি optimization-এ বসায়। Deep learning-এ pseudo-label-এর ধারণা Dong-Hyun Lee-র ২০১৩ সালের workshop paper থেকে জনপ্রিয় হয়; FixMatch (Kihyuk Sohn ও সহকর্মীরা, NeurIPS ২০২০) তাতে consistency regularization মেলায়: একটু বদলানো ছবিতেও অনুমান একই থাকুক। এগুলো পরের deep learning কোর্সের বিষয়।

পরীক্ষা ও ইন্টারভিউতে:

  • p = (০.৬, ০.৩, ০.১) হলে least confidence, margin, entropy? ১ − ০.৬ = ০.৪; ০.৬ − ০.৩ = ০.৩; H = ০.৩০৬৫ + ০.৩৬১২ + ০.২৩০৩ ≈ ০.৮৯৮ (ln-এ)।
  • দুই class-এ তিন uncertainty মাপের পার্থক্য? নেই: তিনটিই p1-এর একমুখী function, তাই একই ক্রম দেয়।
  • Self-training-এ threshold কমালে? বেশি pseudo-label, কিন্তু বেশি ভুল; confirmation bias বাড়ে। বাড়ালে কম কিন্তু পরিষ্কার, আর loop দ্রুত থেমে যেতে পারে।
  • Semi-supervised কখন ক্ষতি করে? যখন cluster বা smoothness অনুমান ভুল: আসল সীমা ঘন অঞ্চল দিয়ে যায়, বা label-হীন ডেটা অন্য বণ্টনের।
  • Active learning-এর মূল্যায়ন কোন ডেটায়? আলাদা, এলোমেলোভাবে বাছা test-এ; কেনা label-সেট পক্ষপাতদুষ্ট।

৯ · AI-এর যুগে — কম label-এর কোডে AI-কে যাচাই

AI assistant-কে “আমার ২০০টি label আর ৫০,০০০টি label-হীন রিভিউ আছে, semi-supervised দিয়ে model বানাও” বললে চলনসই কোড আসে, কিন্তু প্রায়ই এই ভুলগুলোসহ:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
SelfTrainingClassifier(base_estimator=...)scikit-learn 1.8.0-এ এই argument নেই, TypeError; নাম এখন estimatorনিজের version-এর docs; কোড চালানো
LabelSpreading ডিফল্টে রেখে দেয়kernel="knn"-এ alpha=0.2 হলে অনেক বিন্দুতে খবর পৌঁছায় না, নিঃশব্দে প্রথম classশূন্য সারি গোনা; n_iter_ দেখা
Pseudo-label-যুক্ত ডেটায় accuracy মাপেmodel নিজের অনুমানের সঙ্গে নিজেকে মেলায়; সংখ্যা অর্থহীন উঁচুমানুষের label-যুক্ত, আলাদা validation আর test
Supervised baseline ছাড়াই “উন্নতি” দাবিএকই label-এ ভালো tune করা supervised model প্রায়ই সমানএকই label, কয়েকটি seed, mean ± sd
Active learning-এ test set থেকে বাছাই, বা কেনা সেটে মূল্যায়নtest আর আলাদা থাকে না; কেনা সেট পক্ষপাতদুষ্টpool, কেনা সেট আর test আলাদা ফাইলে
এক seed-এ uncertainty বনাম randomশুরুর দিকে ছড়ানো পার্থক্যের চেয়ে বড়অনেক seed, learning curve-এ ব্যান্ড

AI-কে তৈরি উত্তর নয়, যাচাইযোগ্য পরিকল্পনা আর কোড চাইতে বলুন:

আমার কাছে বাজারঘরের ২০০টি label-যুক্ত আর ৫০,০০০টি label-হীন বাংলা রিভিউ আছে (অভিযোগ বনাম সাধারণ)। scikit-learn 1.8.0 ব্যবহার করছি। ১. আগে label-যুক্ত অংশ থেকে ৫০টি তালাবন্ধ test আলাদা করো; কোনো pseudo-label কখনো সেখানে যাবে না। ২. বাকি ১৫০টি দিয়ে একটি শক্ত supervised baseline (TF-IDF + logistic regression) দাও, ৫টি seed-এ mean ± sd. ৩. তারপর SelfTrainingClassifier আর LabelSpreading(kernel="knn") দিয়ে একই মাপ; threshold, alpha আর n_neighbors কেন বাছলে, এক লাইনে বলো; LabelSpreading-এ কতগুলো সারি শূন্য থাকল তাও ছাপো। ৪. প্রতি দফায় pseudo-label-এর মান একটি ছোট মানুষের-label-যুক্ত validation সেটে মাপার কোড দাও। ৫. যদি semi-supervised baseline-এর চেয়ে খারাপ হয়, সেটাও সরাসরি বলো; প্রতিটি function আর argument আমার version-এ আছে কি না যাচাই করো।

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

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

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

প্র ০১Self-training-এর ধারণা ছয় দশক ধরে বারবার ফিরে আসে কেন, আর প্রতিবার তাকে কী শিখতে হয়েছে?

নিজের অনুমান দিয়ে নিজেকে শেখানোর ধারণা পরিসংখ্যানের প্রাথমিক pattern recognition-এর সময় থেকেই আছে; H. J. Scudder-এর ১৯৬৫ সালের adaptive pattern-recognition machine নিয়ে কাজ প্রায়ই প্রথম উদাহরণ হিসেবে উদ্ধৃত হয়। ১৯৯৫ সালে David Yarowsky শব্দের অর্থ-নির্ণয়ে (যেমন ইংরেজি “plant” কারখানা না গাছ) এর একটি সফল রূপ দেখান: অল্প কয়েকটি বীজ-নিয়ম থেকে শুরু করে নিশ্চিত উদাহরণ যোগ করে নিয়ম বাড়ানো, সঙ্গে “এক লেখায় একটি অর্থ”-এর মতো বাড়তি সীমাবদ্ধতা। ১৯৯৮ সালে Avrim Blum আর Tom Mitchell co-training প্রস্তাব করেন: একটি ওয়েবপেজকে দুই দৃষ্টিতে দেখা যায়, তার নিজের লেখা আর অন্য পেজের লিংকের লেখা; একটি দৃষ্টির model অন্যটির জন্য label দেয়, ফলে একই ভুল নিজের কাছে ফিরে আসার সম্ভাবনা কমে।

Deep learning-এর যুগে Dong-Hyun Lee (২০১৩) neural network-এ pseudo-label জনপ্রিয় করেন, আর Qizhe Xie ও সহকর্মীদের Noisy Student (CVPR ২০২০) বড় image ডেটায় teacher model-এর pseudo-label দিয়ে student শেখান, student-কে noise (augmentation, dropout) দিয়ে কঠিন করে। প্রতিটি ধাপে একই শিক্ষা নতুন করে আসে: নিজের ভুল নিজে ধরা যায় না, তাই এমন কিছু লাগে যা ভুলকে ভাঙে। Yarowsky-র বাড়তি নিয়ম, co-training-এর আলাদা দৃষ্টি, Noisy Student-এর noise, আর এই পর্বের পরামর্শ (উঁচু সীমা, validation-এ মান মাপা, class-প্রতি সীমা) সবই সেই একই কাজ করে।

ধারণাটি টিকে থাকার কারণ সরল: label-হীন ডেটা সস্তা আর প্রচুর, আর self-training যেকোনো model-এর চারপাশে মোড়ানো যায়। এই পর্বের প্রথম ব্লকে দেখেছেন, প্রথম দফার pseudo-label প্রায় নিখুঁত (৯৮.৮%), কিন্তু তৃতীয় দফায় ৬৯.২%: সহজ উদাহরণ ফুরোলে বাকি থাকে ঠিক সেগুলো, যেখানে model-এর সাহায্য সবচেয়ে বেশি দরকার আর বিশ্বাস সবচেয়ে কম রাখা উচিত।

মূল উপলব্ধি: self-training-এর শক্তি তার সরলতা, দুর্বলতা তার আত্মবিশ্বাস; প্রতিটি সফল রূপ নিজের ভুল ভাঙার একটি বাইরের উপায় যোগ করেছে।

প্র ০২Label-হীন ডেটা কি সবসময় অন্তত ক্ষতি করে না? তত্ত্ব আর পরীক্ষা কী বলে?

স্বজ্ঞা বলে, বাড়তি তথ্য কখনো ক্ষতি করে না। কিন্তু label-হীন ডেটা তথ্য দেয় শুধু p(x) সম্পর্কে, অর্থাৎ বিন্দু কোথায় ঘন। আমাদের দরকার p(y | x). এই দুইয়ের মধ্যে সেতু হলো অনুমান: cluster, smoothness বা কোনো generative model. সেতু ভুল হলে বাড়তি তথ্য ভুল দিকে টানে। Fabio Cozman আর Ira Cohen ২০০০-এর দশকের শুরুর দিকে দেখান, generative model ভুলভাবে নির্দিষ্ট হলে (যেমন প্রতিটি class একটি Gaussian ধরা, অথচ আসলে নয়) label-হীন ডেটা যোগ করলে classifier খারাপ হতে পারে; তাঁদের বিশ্লেষণে এই ক্ষতি label-হীন ডেটা অনেক বাড়ালেও মেটে না। পর্ব ৩৯-এর EM দিয়ে semi-supervised শেখার (Kamal Nigam ও সহকর্মীদের ২০০০ সালের টেক্সট শ্রেণিবিন্যাসের কাজ) সাফল্য তাই নির্ভর করেছিল model-এর অনুমান কতটা খাটে তার ওপর।

Aarti Singh, Robert Nowak আর Xiaojin Zhu-র ২০০৮ সালের NIPS paper-এর শিরোনামই ব্যাপারটা বলে: “Unlabeled data: Now it helps, now it doesn't”. তাঁদের বিশ্লেষণে, label-যুক্ত ডেটা এত কম যে সে নিজে cluster-এর সীমা খুঁজে পায় না, অথচ label-হীন ডেটা পায়, তখনই লাভ। Label অনেক হলে supervised নিজেই সব শিখে নেয়, লাভ ফুরায়। Oliver ও সহকর্মীরা (২০১৮) পরীক্ষায় দেখান, ন্যায্য baseline আর একই tuning বাজেটে অনেক দাবি ছোট হয়।

এই পর্বের দুই পরীক্ষা এর ছোট প্রতিচ্ছবি। চাঁদের ডেটায় cluster অনুমান সত্যি: label spreading ০.৮৫৪ থেকে ০.৯০০-এ তোলে। দুই দলের ডেটায় অনুমান মিথ্যা: দশটির নয়টি seed-এ দুই পদ্ধতিই supervised-এর চেয়ে খারাপ, self-training-এর রেখা ফাঁকের দিকে ঘুরে যায়। বাস্তবে তাই একটি সিদ্ধান্তের নিয়ম: semi-supervised হলো একটি hypothesis, যা মানুষের label-যুক্ত validation-এ supervised-এর পাশে মেপে তবেই গ্রহণ।

মূল উপলব্ধি: label-হীন ডেটার মূল্য অনুমানের সত্যতার ওপর; সেই সত্যতা আগে জানা যায় না, শুধু মাপা যায়।

প্র ০৩Active learning-এর কেনা label-সেট পক্ষপাতদুষ্ট কেন, আর এর দীর্ঘমেয়াদি দাম কী?

Supervised learning-এর প্রায় সব তত্ত্ব ধরে নেয় training ডেটা এলোমেলো, iid নমুনা। Active learning ইচ্ছা করে এই অনুমান ভাঙে: সে সীমার কাছের, কঠিন, দ্বিধার উদাহরণ বাছে। এতে সুবিধা আছে, কারণ সীমার কাছের বিন্দুই সীমা ঠিক করে, যেমন support vector machine-এর শুধু support vector গুরুত্বপূর্ণ (পর্ব ২৭)। কিন্তু দাম তিনটি।

এক, মূল্যায়ন: কেনা সেট পুরো জনগোষ্ঠীর প্রতিনিধি নয়, তাই তার ওপর cross-validation বা accuracy বাস্তবের চেয়ে কম (কঠিন উদাহরণ বেশি) দেখায়, আর class-এর অনুপাতও বিকৃত। তাই এই পর্বের ব্লকে test সবসময় এলোমেলো। দুই, অন্ধ জায়গা: model শুরুতে কোনো অঞ্চলকে ভুলভাবে নিশ্চিত মনে করলে সেখান থেকে আর কখনো label কেনে না, ফলে ভুল কখনো ধরা পড়ে না। Sanjoy Dasgupta আর Daniel Hsu-র (ICML ২০০৮) মতো কাজ এই “নিশ্চিত কিন্তু ভুল” অঞ্চলের ঝুঁকি বিশ্লেষণ করে, আর ব্যবহারিক প্রতিকার হিসেবে কিছু এলোমেলো label মেশানো হয়। তিন, পুনর্ব্যবহার: logistic regression-এর জন্য বাছা ২০০টি label পরে gradient boosting বা নতুন neural network শেখাতে একই রকম কাজে না-ও আসতে পারে। Lowell, Lipton আর Wallace (২০১৯) টেক্সট শ্রেণিবিন্যাসে দেখান, এক model-এর জন্য কেনা ডেটা অন্য model-এ প্রায়ই random-এর চেয়ে ভালো ফল দেয় না।

ডেটাসেট প্রায়ই model-এর চেয়ে বেশি দিন বাঁচে। আজ কেনা label পাঁচ বছর পরের model-ও ব্যবহার করবে। তাই প্রতিষ্ঠানের ডেটা কৌশলে active learning-এর লাভের পাশে এই খরচও হিসাবে রাখা উচিত: একটি এলোমেলো অংশ সবসময় আলাদা করে কেনা, আর প্রতিটি label কোন নিয়মে কেনা হয়েছিল তা লিখে রাখা (পর্ব ০৫-এর data versioning).

মূল উপলব্ধি: active learning আজকের model-এর জন্য দ্রুত শেখে, কিন্তু ডেটাকে সেই model-এর ছাঁচে ঢালে; এলোমেলো একটি অংশ সেই পক্ষপাতের বিমা।

প্র ০৪Label-এর দাম শুধু টাকায় মাপা যায়? মানুষের সময়, ক্লান্তি আর মতভেদ হিসাবে আনলে active learning-এর ছবি কেমন বদলায়?

এই পর্বে প্রতিটি label-এর দাম ৳৫ ধরা হয়েছে, সব উদাহরণে সমান। বাস্তবে তা খুব কমই সত্যি। একটি দুই লাইনের রিভিউ পড়তে কয়েক সেকেন্ড, একটি দীর্ঘ অভিযোগ পড়তে এক মিনিট। চিকিৎসার ছবিতে একজন বিশেষজ্ঞের সময় সাধারণ label-দাতার চেয়ে বহু গুণ দামি। Burr Settles, Mark Craven আর Lewis Friedland ২০০৮ সালের “Active Learning with Real Annotation Costs” কাজে বাস্তব label-এর সময় মেপে দেখান, উদাহরণভেদে সময় অনেক বদলায়, আর শুধু উদাহরণের সংখ্যা গুনে তুলনা করলে কৌশলের আসল লাভ ভুল বোঝা যেতে পারে।

দ্বিতীয় সমস্যা আরও সূক্ষ্ম: model যেখানে দ্বিধায়, মানুষও প্রায়ই সেখানেই দ্বিধায়। “পণ্য ঠিক আছে, কিন্তু আর কিনব না” অভিযোগ, না সাধারণ মন্তব্য? Uncertainty sampling এমন উদাহরণে ভরা সেট কেনে, যেখানে label-দাতাদের মিল কম (পর্ব ০৫-এর Cohen's kappa). ফলে কেনা label-এ noise বেশি, আর model সেই noise থেকে শেখে। প্রতিকার: দ্বিধার উদাহরণে দুজন label-দাতা আর মতভেদে তৃতীয়জন (adjudication), যা খরচ বাড়ায়; বা “বুঝতে পারছি না” বিকল্প রাখা।

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

মূল উপলব্ধি: active learning-এর লাভ সবচেয়ে সৎভাবে মাপা যায় সময়, টাকা আর label-এর মান একসঙ্গে ধরে; গোনা label-এর সংখ্যা তার একটি অংশ মাত্র।

প্র ০৫Pretrained model আর বড় ভাষা model-এর যুগে কম label-এর সমস্যা কি মিটে গেছে?

ছবিটা অনেক বদলেছে, কিন্তু সমস্যা মেটেনি। আগে কুড়িটি label দিয়ে শূন্য থেকে model শেখাতে হতো। এখন একটি pretrained model-এর embedding (AI-এর ভিত্তি কোর্সের পর্ব ৬৯) নিয়ে তার ওপর ছোট logistic regression শেখানো যায়, আর সেই embedding-এ একই অর্থের রিভিউ কাছাকাছি থাকে বলে cluster অনুমান প্রায়ই আগের চেয়ে বেশি খাটে। অর্থাৎ pretraining নিজেই এক রকম বিশাল label-হীন ডেটা থেকে শেখা, আর semi-supervised-এর অনেক লাভ সেখানে আগেই নেওয়া হয়ে যায়।

দ্বিতীয় পরিবর্তন weak supervision. Alexander Ratner ও সহকর্মীদের Snorkel (VLDB ২০১৭) অনেকগুলো শব্দযুক্ত labeling function-এর ভোট মিলিয়ে বড় training সেট বানানোর একটি পদ্ধতি দেয়, যা পর্ব ০৫-এ দেখেছেন। এখন অনেক দল একটি বড় ভাষা model-কে labeling function বা label-দাতা হিসেবে ব্যবহার করে। এটা দ্রুত আর সস্তা হতে পারে, কিন্তু ঝুঁকিগুলো এই পর্বেরই পুরোনো ঝুঁকি নতুন পোশাকে: model-এর আত্মবিশ্বাসী ভুল (confirmation bias), বাংলা বা স্থানীয় প্রসঙ্গে পদ্ধতিগত পক্ষপাত, আর label-এর মান মাপার জন্য মানুষের তৈরি একটি সেটের দরকার। গ্রাহকের তথ্য বাইরের সেবায় পাঠানোর গোপনীয়তার প্রশ্নও আছে।

Active learning-ও প্রাসঙ্গিক থাকে, শুধু রূপ বদলায়: pretrained embedding-এর ওপর uncertainty বা diversity দিয়ে বাছাই, বা বড় model কোন উদাহরণে সবচেয়ে অনিশ্চিত তা দেখে মানুষকে পাঠানো। যে প্রশ্ন বদলায় না: শেষ বিচারের জন্য একটি ছোট, এলোমেলো, মানুষের-label-যুক্ত test সেট, আর একটি সৎ baseline. পরের deep learning আর NLP কোর্সে pseudo-label, consistency regularization আর বড় model দিয়ে label বানানো বিস্তারিত আসবে।

মূল উপলব্ধি: pretrained model কম label-এর সমস্যাকে ছোট করেছে, শেষ করেনি; label-এর মান মাপার দায় এখনো মানুষের তৈরি একটি এলোমেলো সেটের ওপর।

অনুশীলন

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

  1. পূর্ণ numerical: একটি তিন-class model দুটি label-হীন রিভিউতে দেয় D = (০.৫০, ০.২৫, ০.২৫) আর E = (০.৪৮, ০.৪৭, ০.০৫)। (ক) প্রতিটির least confidence, margin আর entropy (ln-এ; ln ০.৫ = −০.৬৯৩১, ln ০.২৫ = −১.৩৮৬৩, ln ০.৪৮ = −০.৭৩৩৯, ln ০.৪৭ = −০.৭৫৫০, ln ০.০৫ = −২.৯৯৫৭)। (খ) কোন নিয়ম কোনটি বাছে? (গ) একটি প্রকল্পে ০.৯০ accuracy-তে পৌঁছাতে random-এ ১৪০টি, margin-এ ৮০টি label লাগে; প্রতি label ৳৫, আর margin-এর জন্য প্রতি দফায় model আবার শেখাতে বাড়তি ৳২০ (৬টি দফা)। কত টাকা বাঁচে?

    (ক) D: LC = ১ − ০.৫০ = ০.৫০; margin = ০.৫০ − ০.২৫ = ০.২৫; H = ০.৫ × ০.৬৯৩১ + ২ × ০.২৫ × ১.৩৮৬৩ = ০.৩৪৬৬ + ০.৬৯৩১ = ১.০৩৯৭. E: LC = ০.৫২; margin = ০.০১; H = ০.৪৮ × ০.৭৩৩৯ + ০.৪৭ × ০.৭৫৫০ + ০.০৫ × ২.৯৯৫৭ = ০.৩৫২৩ + ০.৩৫৪৯ + ০.১৪৯৮ = ০.৮৫৭০.

    (খ) Least confidence: E (০.৫২ > ০.৫০)। Margin: E (০.০১ < ০.২৫)। Entropy: D (১.০৩৯৭ > ০.৮৫৭০)। E স্পষ্ট দুই class-এর দোটানা; D-র অনিশ্চয়তা তিন class-এ ছড়ানো।

    (গ) Random: ১৪০ × ৳৫ = ৳৭০০. Margin: ৮০ × ৳৫ + ৬ × ৳২০ = ৳৪০০ + ৳১২০ = ৳৫২০. সাশ্রয় ৳১৮০. বাড়তি প্রশিক্ষণ-খরচ গুনলে লাভ ৳৩০০ থেকে কমে যায়; label সস্তা আর প্রশিক্ষণ দামি হলে লাভ শূন্যও হতে পারে।

  2. সমালোচনা ও নকশা: একজন সহকর্মীর পরিকল্পনা: “১০০টি label-যুক্ত রিভিউ আর গত বছরের ২ লাখ label-হীন রিভিউ (অন্য একটি পুরোনো অ্যাপ থেকে) নিয়ে SelfTrainingClassifier(threshold=0.6) চালাব। শেষে পুরো ২ লাখের pseudo-label-এ accuracy মেপে রিপোর্ট দেব। পরে এই label দিয়ে নতুন একটি বড় model শেখাব।” কী কী সমস্যা, আর ভালো নকশা কী?

    সমস্যা: (১) Threshold ০.৬ খুব নিচু: দুই class-এ প্রায় সব অনুমান পেরোয়, প্রথম দফাতেই অনেক ভুল ঢোকে, confirmation bias. (২) Label-হীন ডেটা অন্য অ্যাপের, গত বছরের: বণ্টন আলাদা, ভাষা ও বিষয় বদলাতে পারে; label-হীন ডেটায় নতুন ধরনের রিভিউ থাকলে ক্ষতিও সম্ভব। (৩) Pseudo-label-এ accuracy মাপা অর্থহীন; model নিজের সঙ্গে নিজেকে মেলায়। (৪) কোনো supervised baseline নেই। (৫) Pseudo-label দিয়ে বড় model শেখালে প্রথম model-এর ভুল স্থায়ী হয়।

    ভালো নকশা: ১০০টি থেকে ২০–৩০টি তালাবন্ধ test, কিছু validation. একই সময় ও উৎসের label-হীন ডেটা। প্রথমে supervised baseline, কয়েকটি seed. Threshold উঁচু (যেমন ০.৯৫), প্রতি দফায় validation-এ pseudo-label-এর মান মাপা, class-প্রতি সীমা। Semi-supervised baseline-এর চেয়ে ভালো না হলে বাদ। বাজেট থাকলে কঠিন উদাহরণের জন্য active learning দিয়ে কয়েকশো আসল label কেনা।

  3. আউটপুট অনুমান করুন: চালানোর আগে লিখে ফেলুন নিচের কোড কী ছাপবে।
    import numpy as np
    P = np.array([[0.9, 0.1], [0.6, 0.4], [0.97, 0.03], [0.2, 0.8]])
    sure = P.max(axis=1) > 0.75
    print(sure.sum(), P.argmax(axis=1)[sure])
    s = np.sort(P, axis=1)
    print(np.argmin(s[:, -1] - s[:, -2]))
    3 [0 0 1]
    1

    সর্বোচ্চ সম্ভাবনা ০.৯, ০.৬, ০.৯৭, ০.৮; সীমা ০.৭৫ পেরোয় প্রথম, তৃতীয় আর চতুর্থটি, তাই ৩টি pseudo-label: class ০, ০, ১. Margin চারটির ০.৮, ০.২, ০.৯৪, ০.৬; সবচেয়ে ছোট সারি ১ (০.৬ বনাম ০.৪), তাই active learning সেটিই কিনবে। লক্ষ করুন, self-training যে সারি বাদ দেয় (সারি ১), active learning ঠিক সেটিই বাছে। (ডেস্কটপে NumPy 2.4.6-এ চালিয়ে মেলানো।)

  4. কোড লিখুন: অংশ ৭-এর ব্লকে একটি পঞ্চম কৌশল যোগ করুন: query by committee. প্রতি দফায় কেনা label থেকে ৫টি bootstrap নমুনা নিয়ে ৫টি logistic regression, আর pool-এ vote entropy সবচেয়ে বড় ১০টি কিনুন। পাঁচটি seed-এ গড় learning curve আর ০.৯০-তে পৌঁছানোর label সংখ্যা বের করুন।
    import numpy as np
    from sklearn.datasets import load_digits
    from sklearn.decomposition import PCA
    from sklearn.linear_model import LogisticRegression
    
    X, y = load_digits(return_X_y=True); X = X / 16
    def vote_entropy(models, Xr):                # committee-র ভোট কতটা ভাগ হয়েছে
        votes = np.stack([m.predict(Xr) for m in models])          # (5, n)
        frac = np.stack([(votes == c).mean(axis=0) for c in range(10)], axis=1)
        return -(frac * np.log(frac + 1e-12)).sum(axis=1)
    def run_qbc(seed, step=10, budget=150, size=5):
        rng = np.random.default_rng(seed); p = rng.permutation(len(y))
        pool, test = p[:1000], p[1000:]
        pca = PCA(20, random_state=0).fit(X[pool]); Xp, Xt = pca.transform(X[pool]), pca.transform(X[test])
        lab = [i for c in range(10) for i in rng.choice(np.flatnonzero(y[pool] == c), 2, replace=False)]
        curve = []
        while True:
            L = np.array(lab)
            curve.append(LogisticRegression(max_iter=1000).fit(Xp[L], y[pool][L]).score(Xt, y[test]))
            if len(lab) >= budget: return curve
            boots = [rng.choice(L, len(L)) for _ in range(size)]   # bootstrap: প্রতিটি সদস্য আলাদা নমুনায়
            models = [LogisticRegression(max_iter=1000).fit(Xp[b], y[pool][b]) for b in boots]
            rest = np.setdiff1d(np.arange(1000), lab)
            lab += list(rest[np.argsort(-vote_entropy(models, Xp[rest]), kind="stable")[:step]])
    A = np.array([run_qbc(s) for s in range(5)])
    n_lab = np.arange(20, 151, 10)
    print("label:", " ".join(f"{n:5d}" for n in n_lab[::2]))
    print("QBC:  ", " ".join(f"{a:.3f}" for a in A.mean(axis=0)[::2]))
    print("0.90-তে পৌঁছাতে:", n_lab[np.argmax(A.mean(axis=0) >= 0.90)], "label")

    ডেস্কটপে সত্যিকারের run (Python 3.14.4, scikit-learn 1.8.0, NumPy 2.4.6):

    label:    20    40    60    80   100   120   140
    QBC:   0.759 0.767 0.849 0.887 0.896 0.907 0.923
    0.90-তে পৌঁছাতে: 110 label

    Committee random-এর (১৪০টি) চেয়ে দ্রুত, কিন্তু margin-এর (৮০টি) চেয়ে ধীর: ১১০টি label, ৳৫৫০. প্রতি দফায় পাঁচটি বাড়তি model শেখাতে হয়, তাই হিসাবও পাঁচ গুণ। ছোট কেনা সেটের bootstrap-এ কোনো অঙ্ক বাদ পড়তে পারে, তখন সেই সদস্য সেই অঙ্ক কখনো ভোট দেয় না; committee-র বৈচিত্র্য আসে এই অসম্পূর্ণতা থেকেও। এক run, পাঁচ seed; অন্য ডেটায় ক্রম বদলাতে পারে।

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

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

ক্রমPool-based active learning-এর ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. এলোমেলো একটি test সেট আলাদা করে তালাবন্ধ রাখা
  2. Pool থেকে অল্প কিছু শুরুর label কেনা (cold start)
  3. কেনা label দিয়ে model শেখানো
  4. Pool-এর প্রতিটি উদাহরণে uncertainty স্কোর হিসাব করে সবচেয়ে অনিশ্চিতগুলো বাছা
  5. মানুষের কাছ থেকে সেগুলোর label কিনে কেনা সেটে যোগ
  6. থামার সংকেত মিললে শেষ model তালাবন্ধ test-এ একবার মাপা

একটি তিন-class model-এর অনুমান p = (০.৫০, ০.৪০, ০.১০)। Margin sampling-এর স্কোর (p1 − p2) আর least confidence (১ − p1) কত?

  • margin ০.৪০, least confidence ০.৫০
  • margin ০.১০, least confidence ০.৬০
  • margin ০.১০, least confidence ০.৫০
  • margin ০.৯০, least confidence ০.১০

Self-training-এ প্রতি দফায় নতুন pseudo-label-এর নির্ভুলতা কমতে থাকে (যেমন ০.৯৯, ০.৮৯, ০.৬৯)। এর সবচেয়ে সম্ভাব্য কারণ কী?

  • বেশি ডেটায় model overfit করে
  • সহজ উদাহরণ ফুরিয়ে কঠিনগুলো বাকি থাকে, আর আগের ভুল pseudo-label নতুন ভুল ডাকে
  • প্রতি দফায় threshold নিজে থেকে কমে যায়
  • আসল label-যুক্ত উদাহরণগুলো বাদ পড়ে যায়

কোন পরিস্থিতিতে label-হীন ডেটা যোগ করলে semi-supervised model সবচেয়ে বেশি ক্ষতি করতে পারে?

  • প্রতিটি class একটি আলাদা, ফাঁকা জায়গায় ঘেরা ঘন cluster
  • Label-হীন ডেটা label-যুক্ত ডেটার মতো একই উৎস আর সময়ের
  • Label-যুক্ত ডেটা ইতিমধ্যে প্রচুর
  • ডেটায় স্পষ্ট cluster আছে, কিন্তু আসল class-এর সীমা সেই cluster-গুলোর মাঝখান দিয়ে কাটে

scikit-learn 1.8.0-এ LabelSpreading(kernel="knn", alpha=0.2) চালানোর পরে কিছু বিন্দুর label_distributions_-এর সারি পুরো শূন্য। এর অর্থ কী?

  • Loop খবর পৌঁছানোর আগেই থেমেছে; এই বিন্দুগুলো নিঃশব্দে প্রথম class পায়
  • Model এই বিন্দুগুলোতে দুই class সমান সম্ভাব্য বলে নিশ্চিত
  • এগুলো outlier, তাই library তাদের বাদ দিয়েছে
  • কোড error দিয়ে থেমেছে, আবার চালালেই ঠিক হবে

পাঁচটি seed-এ active learning-এর তুলনায় ৪০টি label-এ random-এর গড় accuracy ০.৮০৩ আর entropy sampling-এর ০.৭৪১, কিন্তু margin sampling ০.৯০-তে ৮০টি label-এ পৌঁছায়, random ১৪০টিতে। সবচেয়ে যুক্তিসঙ্গত সিদ্ধান্ত কোনটি?

  • যেকোনো uncertainty কৌশল সবসময় random-এর চেয়ে ভালো
  • এই ডেটায় margin ভালো, কিন্তু কৌশলের ফল সমস্যাভেদে বদলায়; নিজের ডেটায় random-এর পাশে মাপা দরকার
  • Active learning অকেজো, সবসময় random কেনা উচিত
  • ভালো কৌশল বাছতে কেনা label-সেটের ওপর cross-validation যথেষ্ট

একটি দুই-class সমস্যায় least confidence, margin আর entropy sampling তুলনা করলে কী পাওয়া যায়?

  • Entropy সবসময় আলাদা উদাহরণ বাছে
  • Margin least confidence-এর উল্টো ক্রম দেয়
  • তিনটি একই ক্রম দেয়, তাই একই উদাহরণ বাছে
  • দুই class-এ uncertainty sampling সংজ্ঞায়িত নয়

শব্দকোষ

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

মনে রাখুন

  1. দুই পথ: semi-supervised label-হীন ডেটার গঠন কাজে লাগায়, active learning বাছে কোন label কিনবেন; দুটি একসঙ্গেও চলে।
  2. অনুমান: smoothness, cluster, manifold. সীমা ঘন অঞ্চল দিয়ে গেলে label-হীন ডেটা ক্ষতি করে (এখানে ১০-এ ৯ বার)।
  3. Self-training আর graph: threshold-এর ওপরের অনুমান pseudo-label; ভুল নিজেকে নিশ্চিত করে। LabelSpreading-এ alpha আর শূন্য সারি দেখুন।
  4. Uncertainty: least confidence ১ − p1, margin p1 − p2, entropy −∑p ln p; দুই class-এ তিনটি এক, বেশি class-এ আলাদা।
  5. সৎ মাপ: random-এর পাশে কয়েকটি seed-এর learning curve, এলোমেলো test, টাকা ও সময়ের হিসাব, আর আগে থেকে লেখা থামার নিয়ম।

পরের পর্বে: পর্ব ৫৫ (Survival analysis — ক্রেতা কখন চলে যাবে, Kaplan-Meier আর Cox): যখন প্রশ্ন “চলে যাবেন কি না” নয়, “কখন”, আর অনেক ক্রেতার গল্প এখনো শেষ হয়নি।

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

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