Phase 02: ML Fundamentals

ارزیابی مدل

یک مدل فقط به اندازه اندازه گیریش خوب است.

Type: Build

Languages: Python

Prerequisites: Phase 1 (Probability & Distributions, Statistics for ML), Phase 2 Lessons 1-8

Time: ~90 minutes

اهداف یادگیری

  • از ابتدا اعتبارسنجی های متقاطع K-fold و K-fold را پیاده سازی کنید و توضیح دهید که چرا طبقه بندی برای داده های بی تعادل مهم است
  • دقت محاسبه، بازپس گرفتن، F1، AUC-ROC و متریک های بازپسین (MSE، RMSE، MAE، R-squared) از ابتدا
  • تفسیر منحنیات یادگیری برای تشخیص اینکه آیا یک مدل از تعصب بالا یا تفاوت بالا رنج می برد
  • شناسایی اشتباهات ارزیابی رایج از جمله خروجی داده ها، انتخاب متریک اشتباه و آلودگی مجموعه آزمایش

مشکل

تو يه مدل رو آموزش دادي، 95 درصد دقت داده ها رو داره

شاید شايد نه اگر 95 درصد از داده های شما متعلق به یک کلاس باشد، یک مدل که همیشه پیش بینی می کند که این کلاس 95 درصد دقت دارد در حالی که کاملا بی فایده است. اگر شما بر اساس همان داده هایی که روی آن آموزش داده اید ارزیابی کنید، ۹۵ درصد عدد بی معنی است زیرا مدل فقط پاسخ ها را به یاد گرفته است. اگر مجموعه داده شما دارای یک عنصر زمان باشد و قبل از تقسیم آن به طور تصادفی مخلوط شده باشد، مدل شما ممکن است از داده های آینده برای پیش بینی گذشته استفاده کند.

ارزیابی مدل جایی است که اکثر پروژه های ML اشتباه می شوند. متریک اشتباه باعث می شود که یک مدل بد به نظر خوب برسد. تقسیم اشتباه باعث می شود که یک مدل فریب دهد. مقایسه اشتباه باعث می شود که شما مدل بدتر را انتخاب کنید. ارزیابی صحیح را انتخاب کنید. این تفاوت بین یک مدل است که در تولید کار می کند و یکی که در لحظه ای شکست می خورد که داده های واقعی را می بیند.

مفهوم

آموزش، اعتبار، آزمون

flowchart LR
    A[Full Dataset] --> B[Train Set 60-70%]
    A --> C[Validation Set 15-20%]
    A --> D[Test Set 15-20%]
    B --> E[Fit Model]
    E --> C
    C --> F[Tune Hyperparameters]
    F --> E
    F --> G[Final Model]
    G --> D
    D --> H[Report Performance]

سه قسمت، سه هدف:

  • Training set: مدل از این داده ها یاد می گیرد. این مثال ها را در طول آموزش می بیند.
  • Validation set: برای تنظیم پارامترهای بالا و انتخاب بین مدل ها استفاده می شود. مدل هرگز بر این داده ها آموزش نمی دهد، اما تصمیمات شما تحت تاثیر آن قرار می گیرد.
  • Test set: دقیقا یک بار، در پایان، برای گزارش عملکرد نهایی لمس شده است. اگر شما به عملکرد آزمون نگاه کنید و سپس به تغییر مدل خود برگردید، دیگر یک مجموعه آزمایش نیست. این یک مجموعه تایید دوم شده است.

مجموعه آزمایش تضمین شما است که عملکرد گزارش شده نشان دهنده عملکرد مدل در داده های واقعاً ندیده شده است.

اعتبارسنجی کراس K-Fold

با مجموعه داده های کوچک، یک قطار/تثبیت به صورت جداگانه داده های زباله را از بین می برد و تخمین های شور را ارائه می دهد. اعتبارسنجی کراس K-fold از تمام داده ها برای آموزش و اعتبار استفاده می کند:

flowchart TB
    subgraph Fold1["Fold 1"]
        direction LR
        V1["Val"] --- T1a["Train"] --- T1b["Train"] --- T1c["Train"] --- T1d["Train"]
    end
    subgraph Fold2["Fold 2"]
        direction LR
        T2a["Train"] --- V2["Val"] --- T2b["Train"] --- T2c["Train"] --- T2d["Train"]
    end
    subgraph Fold3["Fold 3"]
        direction LR
        T3a["Train"] --- T3b["Train"] --- V3["Val"] --- T3c["Train"] --- T3d["Train"]
    end
    subgraph Fold4["Fold 4"]
        direction LR
        T4a["Train"] --- T4b["Train"] --- T4c["Train"] --- V4["Val"] --- T4d["Train"]
    end
    subgraph Fold5["Fold 5"]
        direction LR
        T5a["Train"] --- T5b["Train"] --- T5c["Train"] --- T5d["Train"] --- V5["Val"]
    end
    Fold1 --> R["Average scores"]
    Fold2 --> R
    Fold3 --> R
    Fold4 --> R
    Fold5 --> R
  1. داده ها را به کالا های مساوی اندازه تقسیم کنید
  2. برای هر طناب، روی طناب K-1 حرکت کنید و روی طناب باقیمانده تأیید کنید
  3. میانگین نمره های اعتبار K

K=5 یا K=10 انتخاب های استاندارد هستند. هر نقطه داده برای اعتبارسنجی دقیقاً یک بار استفاده می شود. نمره متوسط یک تخمین پایدار تر از هر تقسیم واحد است.

Stratified K-fold: توزیع کلاس را در هر لایه حفظ می کند. اگر مجموعه داده شما 70٪ کلاس A و 30٪ کلاس B باشد، هر لایه تقریباً نسبت مشابهی دارد. این برای مجموعه داده های نامتناسب مهم است که در آن تقسیم تصادفی می تواند تمام نمونه های اقلیت را در یک لایه قرار دهد.

متریک های طبقه بندی

Confusion matrix: پایه. برای طبقه بندی دوگانه:

Predicted PositivePredicted Negative
Actually PositiveTrue Positive (TP)False Negative (FN)
Actually NegativeFalse Positive (FP)True Negative (TN)

از این ماتریکس، تمام متریک های دیگر عبارتند از:

  • Accuracy= (TP + TN) / (TP + TN + FP + FN). بخش از پیش بینی های صحیح. گمراه کننده در زمانی که کلاس ها بی تعادل هستند.
  • Precision= TP / (TP + FP). از همه چیز پیش بینی شده مثبت، چند مورد در واقع بودند؟ استفاده کنید زمانی که مثبت های غلط گران هستند (به عنوان مثال، فیلتر اسپام که ایمیل واقعی را به عنوان اسپم نشان می دهد).
  • Recall(حساسیت) = TP / (TP + FN). از همه مثبت های واقعی، چند تا را گرفتیم؟ استفاده کنید وقتی منفی های غلط گران هستند (به عنوان مثال، غربالگری سرطان از وجود تومور).
  • F1 score= 2 دقت یادآوری / (درست + یادآوری) میانگین هماهنگ دقت و یادآوری.
  • AUC-ROC: منطقه زیر منحنی ویژگی های عملیاتی گیرنده. نرخ مثبت واقعی در مقابل نرخ مثبت نادرست در حدودی های مختلف طبقه بندی را نشان می دهد. AUC = 0.5 به معنای حدس تصادفی است، AUC = 1.0 به معنای جداسازی کامل است. مستقل از حد: این اندازه گیری می کند که مدل چقدر مثبت را بالاتر از منفی قرار می دهد، صرف نظر از کسری که انتخاب می کنید.

متریک های بازپسین

  • MSE(وسط خطای مربع) = متوسط (((y_true - y_pred) ^ 2). خطاهای بزرگ را به صورت مربع مجازات می کند. حساس به غیر معمول است.
  • RMSE(سرمایه اشتباه مربع) = sqrt(MSE). واحد های مشابه متغیر هدف. تفسیر آن آسان تر از MSE است.
  • MAE(متوسط خطا مطلق) = متوسط ((y_true - y_pred_the)) تمام اشتباهات را خطیانه درمان می کند. قوی تر تا غیر قابل ملاحظه از MSE است.
  • R-squared= 1 - SS_res / SS_tot، جایی که SS_res = مجموع (((y_true - y_pred) ^2) و SS_tot = مجموع ((((y_true - y_mean) ^2).

منحنیات یادگیری

نمرات آموزش و اعتبارسنجی به عنوان یک تابع از اندازه مجموعه آموزش:

  • High bias (underfitting): هر دو منحنی به نمره پایین نزدیک می شوند. اضافه کردن اطلاعات بیشتر کمک نخواهد کرد. شما به یک مدل پیچیده تر نیاز دارید.
  • High variance (overfitting)در این زمینه، در نظر گرفته می شود که این موضوع در مورد این موضوع بسیار مهم است.

منحنیات اعتبار

نمرات آموزش و اعتبارسنجی نقشه به عنوان تابع یک پارامتر:

  • در پیچیدگی پایین: هر دو نمره کم هستند (کم مناسب)
  • در پیچیدگی درست: هر دو نمره بالا و نزدیک به هم هستند
  • در شرایط پیچیده: نمره آموزش بالا باقی می ماند اما نمره اعتبار (ترافق) کاهش می یابد

ارزش مطلوب هیپر پارامتر جایی است که نمره اعتبار سنجی بالا می رود.

اشتباهات رایج در ارزیابی

Data leakage: اطلاعات از مجموعه آزمایش در آموزش نفوذ می کند. نمونه ها: نصب یک مقیاس در کل مجموعه داده قبل از تقسیم، از جمله داده های آینده در پیش بینی سری زمان، با استفاده از یک ویژگی که از هدف حاصل شده است. همیشه تقسیم اول، سپس پیش پردازش.

Class imbalance: 99 درصد از معاملات مشروع هستند، 1 درصد از معاملات کلاهبرداری است. یک مدل که همیشه "مشروع" را پیش بینی می کند 99 درصد دقت را می گیرد. درست بودن، بازپس گرفتن، F1 یا AUC-ROC را به جای آن استفاده کنید.

Wrong metric: بهینه سازی دقت زمانی که شما باید بهینه سازی بازپس (تشخیص پزشکی) را انجام دهید، یا بهینه سازی RMSE زمانی که داده های شما دارای انحرافات سنگین است (به جای آن MAE را استفاده کنید).

Not using stratified splits: با داده های نامتناسب، یک تقسیم تصادفی ممکن است نمونه های اقلیت بسیار کمی را در فولد تأیید قرار دهد و تخمین های نامناسب را ارائه دهد.

Testing too oftenهر بار که به عملکرد تست نگاه می کنید و تنظیم می کنید، شما به مجموعه تست بیش از حد متناسب می شوید.

آن را بسازید

مرحله ی اول: تقسیم قطار/مطابق/مطابق آزمون

pythonimport random
import math


def train_val_test_split(X, y, train_ratio=0.6, val_ratio=0.2, seed=42):
    random.seed(seed)
    n = len(X)
    indices = list(range(n))
    random.shuffle(indices)

    train_end = int(n * train_ratio)
    val_end = int(n * (train_ratio + val_ratio))

    train_idx = indices[:train_end]
    val_idx = indices[train_end:val_end]
    test_idx = indices[val_end:]

    X_train = [X[i] for i in train_idx]
    y_train = [y[i] for i in train_idx]
    X_val = [X[i] for i in val_idx]
    y_val = [y[i] for i in val_idx]
    X_test = [X[i] for i in test_idx]
    y_test = [y[i] for i in test_idx]

    return X_train, y_train, X_val, y_val, X_test, y_test

مرحله دوم: اعتبارسنجی کثیر و لایه بندی کثیر

pythondef kfold_split(n, k=5, seed=42):
    random.seed(seed)
    indices = list(range(n))
    random.shuffle(indices)

    fold_size = n // k
    folds = []

    for i in range(k):
        start = i * fold_size
        end = start + fold_size if i < k - 1 else n
        val_idx = indices[start:end]
        train_idx = indices[:start] + indices[end:]
        folds.append((train_idx, val_idx))

    return folds


def stratified_kfold_split(y, k=5, seed=42):
    random.seed(seed)

    class_indices = {}
    for i, label in enumerate(y):
        class_indices.setdefault(label, []).append(i)

    for label in class_indices:
        random.shuffle(class_indices[label])

    folds = [{"train": [], "val": []} for _ in range(k)]

    for label, indices in class_indices.items():
        fold_size = len(indices) // k
        for i in range(k):
            start = i * fold_size
            end = start + fold_size if i < k - 1 else len(indices)
            val_part = indices[start:end]
            train_part = indices[:start] + indices[end:]
            folds[i]["val"].extend(val_part)
            folds[i]["train"].extend(train_part)

    return [(f["train"], f["val"]) for f in folds]


def cross_validate(X, y, model_fn, k=5, metric_fn=None, stratified=False):
    n = len(X)

    if stratified:
        folds = stratified_kfold_split(y, k)
    else:
        folds = kfold_split(n, k)

    scores = []
    for train_idx, val_idx in folds:
        X_train = [X[i] for i in train_idx]
        y_train = [y[i] for i in train_idx]
        X_val = [X[i] for i in val_idx]
        y_val = [y[i] for i in val_idx]

        model = model_fn()
        model.fit(X_train, y_train)
        predictions = [model.predict(x) for x in X_val]

        if metric_fn:
            score = metric_fn(y_val, predictions)
        else:
            score = sum(1 for yt, yp in zip(y_val, predictions) if yt == yp) / len(y_val)
        scores.append(score)

    return scores

مرحله 3: ماتریس سردرگمی و متریک های طبقه بندی

pythondef confusion_matrix(y_true, y_pred):
    tp = sum(1 for yt, yp in zip(y_true, y_pred) if yt == 1 and yp == 1)
    tn = sum(1 for yt, yp in zip(y_true, y_pred) if yt == 0 and yp == 0)
    fp = sum(1 for yt, yp in zip(y_true, y_pred) if yt == 0 and yp == 1)
    fn = sum(1 for yt, yp in zip(y_true, y_pred) if yt == 1 and yp == 0)
    return tp, tn, fp, fn


def accuracy(y_true, y_pred):
    tp, tn, fp, fn = confusion_matrix(y_true, y_pred)
    total = tp + tn + fp + fn
    return (tp + tn) / total if total > 0 else 0.0


def precision(y_true, y_pred):
    tp, tn, fp, fn = confusion_matrix(y_true, y_pred)
    return tp / (tp + fp) if (tp + fp) > 0 else 0.0


def recall(y_true, y_pred):
    tp, tn, fp, fn = confusion_matrix(y_true, y_pred)
    return tp / (tp + fn) if (tp + fn) > 0 else 0.0


def f1_score(y_true, y_pred):
    p = precision(y_true, y_pred)
    r = recall(y_true, y_pred)
    return 2 * p * r / (p + r) if (p + r) > 0 else 0.0


def roc_curve(y_true, y_scores):
    thresholds = sorted(set(y_scores), reverse=True)
    tpr_list = []
    fpr_list = []

    total_positives = sum(y_true)
    total_negatives = len(y_true) - total_positives

    for threshold in thresholds:
        y_pred = [1 if s >= threshold else 0 for s in y_scores]
        tp = sum(1 for yt, yp in zip(y_true, y_pred) if yt == 1 and yp == 1)
        fp = sum(1 for yt, yp in zip(y_true, y_pred) if yt == 0 and yp == 1)

        tpr = tp / total_positives if total_positives > 0 else 0.0
        fpr = fp / total_negatives if total_negatives > 0 else 0.0

        tpr_list.append(tpr)
        fpr_list.append(fpr)

    return fpr_list, tpr_list, thresholds


def auc_roc(y_true, y_scores):
    fpr_list, tpr_list, _ = roc_curve(y_true, y_scores)

    pairs = sorted(zip(fpr_list, tpr_list))
    fpr_sorted = [p[0] for p in pairs]
    tpr_sorted = [p[1] for p in pairs]

    area = 0.0
    for i in range(1, len(fpr_sorted)):
        width = fpr_sorted[i] - fpr_sorted[i - 1]
        height = (tpr_sorted[i] + tpr_sorted[i - 1]) / 2
        area += width * height

    return area

مرحله چهارم: متریک های بازپسین

pythondef mse(y_true, y_pred):
    n = len(y_true)
    return sum((yt - yp) ** 2 for yt, yp in zip(y_true, y_pred)) / n


def rmse(y_true, y_pred):
    return math.sqrt(mse(y_true, y_pred))


def mae(y_true, y_pred):
    n = len(y_true)
    return sum(abs(yt - yp) for yt, yp in zip(y_true, y_pred)) / n


def r_squared(y_true, y_pred):
    mean_y = sum(y_true) / len(y_true)
    ss_res = sum((yt - yp) ** 2 for yt, yp in zip(y_true, y_pred))
    ss_tot = sum((yt - mean_y) ** 2 for yt in y_true)
    if ss_tot == 0:
        return 0.0
    return 1.0 - ss_res / ss_tot

مرحله 5: منحنیات یادگیری

pythondef learning_curve(X, y, model_fn, metric_fn, train_sizes=None, val_ratio=0.2, seed=42):
    random.seed(seed)
    n = len(X)
    indices = list(range(n))
    random.shuffle(indices)

    val_size = int(n * val_ratio)
    val_idx = indices[:val_size]
    pool_idx = indices[val_size:]

    X_val = [X[i] for i in val_idx]
    y_val = [y[i] for i in val_idx]

    if train_sizes is None:
        train_sizes = [int(len(pool_idx) * r) for r in [0.1, 0.2, 0.4, 0.6, 0.8, 1.0]]

    train_scores = []
    val_scores = []

    for size in train_sizes:
        subset = pool_idx[:size]
        X_train = [X[i] for i in subset]
        y_train = [y[i] for i in subset]

        model = model_fn()
        model.fit(X_train, y_train)

        train_pred = [model.predict(x) for x in X_train]
        val_pred = [model.predict(x) for x in X_val]

        train_scores.append(metric_fn(y_train, train_pred))
        val_scores.append(metric_fn(y_val, val_pred))

    return train_sizes, train_scores, val_scores

مرحله 6: یک طبقه بندی ساده برای آزمایش، به علاوه نمایش کامل

pythonclass SimpleLogistic:
    def __init__(self, lr=0.1, epochs=100):
        self.lr = lr
        self.epochs = epochs
        self.weights = None
        self.bias = 0.0

    def sigmoid(self, z):
        z = max(-500, min(500, z))
        return 1.0 / (1.0 + math.exp(-z))

    def fit(self, X, y):
        n_features = len(X[0])
        self.weights = [0.0] * n_features
        self.bias = 0.0

        for _ in range(self.epochs):
            for xi, yi in zip(X, y):
                z = sum(w * x for w, x in zip(self.weights, xi)) + self.bias
                pred = self.sigmoid(z)
                error = yi - pred
                for j in range(n_features):
                    self.weights[j] += self.lr * error * xi[j]
                self.bias += self.lr * error

    def predict_proba(self, x):
        z = sum(w * xi for w, xi in zip(self.weights, x)) + self.bias
        return self.sigmoid(z)

    def predict(self, x):
        return 1 if self.predict_proba(x) >= 0.5 else 0


class SimpleLinearRegression:
    def __init__(self, lr=0.001, epochs=200):
        self.lr = lr
        self.epochs = epochs
        self.weights = None
        self.bias = 0.0

    def fit(self, X, y):
        n_features = len(X[0])
        self.weights = [0.0] * n_features
        self.bias = 0.0
        n = len(X)

        for _ in range(self.epochs):
            for xi, yi in zip(X, y):
                pred = sum(w * x for w, x in zip(self.weights, xi)) + self.bias
                error = yi - pred
                for j in range(n_features):
                    self.weights[j] += self.lr * error * xi[j] / n
                self.bias += self.lr * error / n

    def predict(self, x):
        return sum(w * xi for w, xi in zip(self.weights, x)) + self.bias


def standardize(values):
    n = len(values)
    mean = sum(values) / n
    var = sum((v - mean) ** 2 for v in values) / n
    std = math.sqrt(var) if var > 0 else 1.0
    return [(v - mean) / std for v in values], mean, std


def make_classification_data(n=300, seed=42):
    random.seed(seed)
    X = []
    y = []
    for _ in range(n):
        x1 = random.gauss(0, 1)
        x2 = random.gauss(0, 1)
        label = 1 if (x1 + x2 + random.gauss(0, 0.5)) > 0 else 0
        X.append([x1, x2])
        y.append(label)
    return X, y


def make_regression_data(n=200, seed=42):
    random.seed(seed)
    X = []
    y = []
    for _ in range(n):
        x1 = random.uniform(0, 10)
        x2 = random.uniform(0, 5)
        target = 3 * x1 + 2 * x2 + random.gauss(0, 2)
        X.append([x1, x2])
        y.append(target)
    return X, y


def make_imbalanced_data(n=300, minority_ratio=0.05, seed=42):
    random.seed(seed)
    X = []
    y = []
    for _ in range(n):
        if random.random() < minority_ratio:
            x1 = random.gauss(3, 0.5)
            x2 = random.gauss(3, 0.5)
            label = 1
        else:
            x1 = random.gauss(0, 1)
            x2 = random.gauss(0, 1)
            label = 0
        X.append([x1, x2])
        y.append(label)
    return X, y


if __name__ == "__main__":
    X_clf, y_clf = make_classification_data(300)

    print("=== Train/Validation/Test Split ===")
    X_train, y_train, X_val, y_val, X_test, y_test = train_val_test_split(X_clf, y_clf)
    print(f"  Train: {len(X_train)}, Val: {len(X_val)}, Test: {len(X_test)}")
    print(f"  Train class distribution: {sum(y_train)}/{len(y_train)} positive")
    print(f"  Val class distribution: {sum(y_val)}/{len(y_val)} positive")

    model = SimpleLogistic(lr=0.1, epochs=200)
    model.fit(X_train, y_train)

    print("\n=== Classification Metrics ===")
    y_pred = [model.predict(x) for x in X_test]
    tp, tn, fp, fn = confusion_matrix(y_test, y_pred)
    print(f"  Confusion matrix: TP={tp}, TN={tn}, FP={fp}, FN={fn}")
    print(f"  Accuracy:  {accuracy(y_test, y_pred):.4f}")
    print(f"  Precision: {precision(y_test, y_pred):.4f}")
    print(f"  Recall:    {recall(y_test, y_pred):.4f}")
    print(f"  F1 Score:  {f1_score(y_test, y_pred):.4f}")

    y_scores = [model.predict_proba(x) for x in X_test]
    auc = auc_roc(y_test, y_scores)
    print(f"  AUC-ROC:   {auc:.4f}")

    print("\n=== K-Fold Cross-Validation (K=5) ===")
    cv_scores = cross_validate(
        X_clf, y_clf,
        model_fn=lambda: SimpleLogistic(lr=0.1, epochs=200),
        k=5,
        metric_fn=accuracy,
    )
    mean_cv = sum(cv_scores) / len(cv_scores)
    std_cv = math.sqrt(sum((s - mean_cv) ** 2 for s in cv_scores) / len(cv_scores))
    print(f"  Fold scores: {[round(s, 4) for s in cv_scores]}")
    print(f"  Mean: {mean_cv:.4f} (+/- {std_cv:.4f})")

    print("\n=== Stratified K-Fold Cross-Validation (K=5) ===")
    strat_scores = cross_validate(
        X_clf, y_clf,
        model_fn=lambda: SimpleLogistic(lr=0.1, epochs=200),
        k=5,
        metric_fn=accuracy,
        stratified=True,
    )
    strat_mean = sum(strat_scores) / len(strat_scores)
    strat_std = math.sqrt(sum((s - strat_mean) ** 2 for s in strat_scores) / len(strat_scores))
    print(f"  Fold scores: {[round(s, 4) for s in strat_scores]}")
    print(f"  Mean: {strat_mean:.4f} (+/- {strat_std:.4f})")

    print("\n=== Imbalanced Data: Why Accuracy Lies ===")
    X_imb, y_imb = make_imbalanced_data(300, minority_ratio=0.05)
    positives = sum(y_imb)
    print(f"  Class distribution: {positives} positive, {len(y_imb) - positives} negative ({positives/len(y_imb)*100:.1f}% positive)")

    always_negative = [0] * len(y_imb)
    print(f"  Always-negative baseline:")
    print(f"    Accuracy:  {accuracy(y_imb, always_negative):.4f}")
    print(f"    Precision: {precision(y_imb, always_negative):.4f}")
    print(f"    Recall:    {recall(y_imb, always_negative):.4f}")
    print(f"    F1 Score:  {f1_score(y_imb, always_negative):.4f}")

    X_tr_i, y_tr_i, X_v_i, y_v_i, X_te_i, y_te_i = train_val_test_split(X_imb, y_imb)
    model_imb = SimpleLogistic(lr=0.5, epochs=500)
    model_imb.fit(X_tr_i, y_tr_i)
    y_pred_imb = [model_imb.predict(x) for x in X_te_i]
    print(f"\n  Trained model on imbalanced data:")
    print(f"    Accuracy:  {accuracy(y_te_i, y_pred_imb):.4f}")
    print(f"    Precision: {precision(y_te_i, y_pred_imb):.4f}")
    print(f"    Recall:    {recall(y_te_i, y_pred_imb):.4f}")
    print(f"    F1 Score:  {f1_score(y_te_i, y_pred_imb):.4f}")

    print("\n=== Regression Metrics ===")
    X_reg, y_reg = make_regression_data(200)

    col0 = [x[0] for x in X_reg]
    col1 = [x[1] for x in X_reg]
    col0_s, m0, s0 = standardize(col0)
    col1_s, m1, s1 = standardize(col1)
    X_reg_scaled = [[col0_s[i], col1_s[i]] for i in range(len(X_reg))]

    X_tr_r, y_tr_r, X_v_r, y_v_r, X_te_r, y_te_r = train_val_test_split(X_reg_scaled, y_reg)
    reg_model = SimpleLinearRegression(lr=0.01, epochs=500)
    reg_model.fit(X_tr_r, y_tr_r)
    y_pred_r = [reg_model.predict(x) for x in X_te_r]

    print(f"  MSE:       {mse(y_te_r, y_pred_r):.4f}")
    print(f"  RMSE:      {rmse(y_te_r, y_pred_r):.4f}")
    print(f"  MAE:       {mae(y_te_r, y_pred_r):.4f}")
    print(f"  R-squared: {r_squared(y_te_r, y_pred_r):.4f}")

    mean_baseline = [sum(y_tr_r) / len(y_tr_r)] * len(y_te_r)
    print(f"\n  Mean baseline:")
    print(f"    MSE:       {mse(y_te_r, mean_baseline):.4f}")
    print(f"    R-squared: {r_squared(y_te_r, mean_baseline):.4f}")

    print("\n=== Learning Curve ===")
    sizes, train_sc, val_sc = learning_curve(
        X_clf, y_clf,
        model_fn=lambda: SimpleLogistic(lr=0.1, epochs=200),
        metric_fn=accuracy,
    )
    print(f"  {'Size':>6} {'Train':>8} {'Val':>8}")
    for s, tr, va in zip(sizes, train_sc, val_sc):
        print(f"  {s:>6} {tr:>8.4f} {va:>8.4f}")

    print("\n=== Statistical Model Comparison ===")
    model_a_scores = cross_validate(
        X_clf, y_clf,
        model_fn=lambda: SimpleLogistic(lr=0.1, epochs=100),
        k=5, metric_fn=accuracy,
    )
    model_b_scores = cross_validate(
        X_clf, y_clf,
        model_fn=lambda: SimpleLogistic(lr=0.1, epochs=500),
        k=5, metric_fn=accuracy,
    )
    diffs = [a - b for a, b in zip(model_a_scores, model_b_scores)]
    mean_diff = sum(diffs) / len(diffs)
    std_diff = math.sqrt(sum((d - mean_diff) ** 2 for d in diffs) / len(diffs))
    t_stat = mean_diff / (std_diff / math.sqrt(len(diffs))) if std_diff > 0 else 0.0
    print(f"  Model A (100 epochs) mean: {sum(model_a_scores)/len(model_a_scores):.4f}")
    print(f"  Model B (500 epochs) mean: {sum(model_b_scores)/len(model_b_scores):.4f}")
    print(f"  Mean difference: {mean_diff:.4f}")
    print(f"  Paired t-statistic: {t_stat:.4f}")
    print(f"  (|t| > 2.78 for significance at p<0.05 with df=4)")

ازش استفاده کن

با scikit-learn، ارزیابی به جریان کار ساخته شده است:

pythonfrom sklearn.model_selection import cross_val_score, StratifiedKFold, learning_curve
from sklearn.metrics import (
    accuracy_score, precision_score, recall_score, f1_score,
    roc_auc_score, confusion_matrix, mean_squared_error, r2_score,
)
from sklearn.linear_model import LogisticRegression

model = LogisticRegression()
scores = cross_val_score(model, X, y, cv=StratifiedKFold(5), scoring="f1")

نسخه های از ابتدا دقیقاً نشان می دهد که اعتبارسنجی چه کاری انجام می دهد (هیچ جادویی، فقط برای حلقه ها و ردیابی شاخص) ، چگونه هر متریک محاسبه می شود (فقط TP /FP /TN / FN را می شماریم) و چرا طبقه بندی مهم است (حفاظ نسبت های کلاس در هر لایه). نسخه های کتابخانه موازی، گزینه های نمره گذاری بیشتر و ادغام با لوله ها را اضافه می کنند.

-باده

این درس نتیجه می دهد:

  • outputs/skill-evaluation.md- یک مهارت که شامل استراتژی ارزیابی برای مدل های طبقه بندی و بازپسین می شود

تمرینات

  1. منحنیات دقیق بازیابی را پیاده سازی کنید: دقت نقشه مقابل بازیابی در حدودی های مختلف. دقت متوسط را محاسبه کنید (جاهای زیر منحنی PR). منحنی PR را با منحنی ROC در مجموعه داده های نامتناسب مقایسه کنید و توضیح دهید که هر کدام اطلاعات بیشتری دارند.
  2. یک حلقه اعتبارسنجی متقابل را ایجاد کنید: حلقه بیرونی عملکرد مدل را ارزیابی می کند، حلقه داخلی پارامترهای را تنظیم می کند. از آن برای مقایسه دو مدل به طور عادلانه بدون اینکه داده های اعتبارسنجی را به ارزیابی برساند استفاده کنید.
  3. یک آزمایش تغییر جهت مقایسه مدل را اجرا کنید: برچسب ها را مخلوط کنید، دوباره آموزش دهید و عملکرد را اندازه گیری کنید. 100 بار تکرار کنید تا توزیع صفر را ایجاد کنید. ارزش p عملکرد مشاهده شده مدل را با این توزیع محاسبه کنید.

اصطلاحات کلیدی

TermWhat people sayWhat it actually means
Overfitting"Memorizing the training data"The model captures noise in the training data, performing well on training but poorly on unseen data
Cross-validation"Testing on different subsets"Systematically rotating which portion of data is used for validation, averaging results across all rotations
Precision"How many predicted positives are correct"TP / (TP + FP): the fraction of positive predictions that are actually positive
Recall"How many actual positives we found"TP / (TP + FN): the fraction of actual positives that were correctly identified
AUC-ROC"How well the model separates classes"The area under the curve of true positive rate vs false positive rate across all thresholds, from 0.5 (random) to 1.0 (perfect)
R-squared"How much variance is explained"1 - (sum of squared residuals / total sum of squares): the fraction of target variance captured by the model
Data leakage"The model cheated"Using information during training that would not be available at prediction time, leading to optimistic evaluation
Learning curve"How performance changes with more data"A plot of training and validation scores vs training set size, revealing underfitting or overfitting
Stratified split"Keeping class ratios balanced"Splitting data so each subset has the same proportion of each class as the full dataset

خواندن بیشتر

This free lesson is part of the AI Engineering from Scratch curriculum. Read the full explanation, run the lesson code, and verify the result in the interactive reader or from the repository source.

Browse the complete course catalog or open this lesson on GitHub.