LightGBM چیست؟ آموزش کامل ساخت مدل یادگیری ماشین سریع با پایتون

در این آموزش عملی LightGBM، نصب کتابخانه، ساخت دیتاست، پردازش ویژگی‌های عددی و دسته‌ای، آموزش مدل، Early Stopping، انتخاب Threshold، تحلیل اهمیت ویژگی‌ها و ذخیره مدل را قدم‌به‌قدم یاد می‌گیرید.

Share
LightGBM چیست؟ آموزش کامل ساخت مدل یادگیری ماشین سریع با پایتون

LightGBM یکی از محبوب‌ترین کتابخانه‌های یادگیری ماشین برای آموزش مدل روی داده‌های جدولی است. اگر اطلاعات پروژه شما در فایل CSV، دیتابیس، جدول کاربران، گزارش فروش، داده‌های استفاده از نرم‌افزار یا رکوردهای عملیاتی ذخیره شده باشد، LightGBM می‌تواند گزینه‌ای سریع و قدرتمند برای ساخت مدل طبقه‌بندی، رگرسیون یا رتبه‌بندی باشد.

این کتابخانه برای پردازش دیتاست‌های بزرگ، مصرف بهینه حافظه، آموزش سریع و پشتیبانی مستقیم از ویژگی‌های دسته‌ای طراحی شده است. به همین دلیل در بسیاری از پروژه‌های Data Science، سیستم‌های پیشنهاددهنده، پیش‌بینی رفتار کاربران و مسابقات یادگیری ماشین استفاده می‌شود.

در این آموزش فقط با یک مثال کوتاه و آزمایشی روبه‌رو نیستید. یک پروژه کامل می‌سازیم که مراحل زیر را پوشش می‌دهد:

  • نصب و راه‌اندازی LightGBM
  • ساخت داده آزمایشی قابل اجرا
  • مدیریت ستون‌های عددی و دسته‌ای
  • تقسیم صحیح Train، Validation و Test
  • آموزش مدل با LGBMClassifier
  • استفاده از Early Stopping
  • ارزیابی داده‌های نامتوازن
  • انتخاب Threshold مناسب
  • بررسی Feature Importance
  • ذخیره و بارگذاری مدل
  • انجام پیش‌بینی روی داده جدید
  • اتصال خروجی مدل به API درواره
  • آماده‌سازی مدل برای استفاده در Production

LightGBM چیست؟

LightGBM مخفف Light Gradient Boosting Machine است. این ابزار یک فریم‌ورک Gradient Boosting مبتنی بر درخت تصمیم محسوب می‌شود.

در Gradient Boosting چندین درخت تصمیم به‌صورت مرحله‌ای ساخته می‌شوند. هر درخت جدید تلاش می‌کند خطاهای مدل فعلی را کاهش دهد. نتیجه نهایی ترکیبی از خروجی تمام درخت‌ها است:

Prediction =
Initial prediction
+ Learning rate × Tree 1
+ Learning rate × Tree 2
+ ...
+ Learning rate × Tree N

LightGBM با استفاده از روش‌های بهینه برای ساخت درخت و مدیریت داده، تلاش می‌کند آموزش مدل‌های Gradient Boosting را سریع‌تر و کم‌هزینه‌تر کند.

طبق مستندات رسمی LightGBM، این کتابخانه برای آموزش کارآمد، مصرف حافظه کمتر و پشتیبانی از پردازش موازی، توزیع‌شده و GPU طراحی شده است.

تفاوت ساخت درخت در LightGBM

یکی از مهم‌ترین ویژگی‌های LightGBM، رشد Leaf-wise درخت‌ها است.

در بعضی الگوریتم‌ها، درخت به‌صورت Level-wise رشد می‌کند؛ یعنی گره‌های یک سطح تقریباً با هم گسترش پیدا می‌کنند. LightGBM معمولاً برگی را انتخاب می‌کند که تقسیم آن بیشترین کاهش خطا را ایجاد کند.

به‌صورت ساده:

Level-wise:
تمام شاخه‌های یک سطح توسعه پیدا می‌کنند.

Leaf-wise:
برگی توسعه پیدا می‌کند که بیشترین Gain را ایجاد کند.

رشد Leaf-wise می‌تواند مدل را سریع‌تر به خطای کمتر برساند، اما اگر پارامترها کنترل نشوند، احتمال Overfitting نیز افزایش پیدا می‌کند.

به همین دلیل پارامترهای زیر در LightGBM اهمیت زیادی دارند:

  • num_leaves
  • max_depth
  • min_child_samples
  • min_split_gain
  • reg_alpha
  • reg_lambda

LightGBM برای چه پروژه‌هایی مناسب است؟

LightGBM بیشتر برای داده‌های ساختاریافته و جدولی مناسب است. نمونه کاربردهای متداول آن عبارت‌اند از:

  • طبقه‌بندی رکوردها
  • پیش‌بینی احتمال انجام یک رویداد
  • تحلیل رفتار کاربران
  • پیش‌بینی تقاضا
  • امتیازدهی به سرنخ‌های فروش
  • رتبه‌بندی نتایج جست‌وجو
  • پیش‌بینی مقدار یک متغیر عددی
  • ساخت مدل روی داده‌های دارای ستون‌های دسته‌ای
  • ساخت مدل پایه برای مقایسه با شبکه عصبی
  • تحلیل داده‌های استخراج‌شده از متن یا تصویر

LightGBM مستقیماً برای درک متن خام، تصویر، صدا یا ویدئو طراحی نشده است. برای این داده‌ها معمولاً ابتدا Feature یا Embedding استخراج می‌شود و سپس مدل LightGBM روی مقادیر عددی آموزش می‌بیند.

چه زمانی از LightGBM استفاده نکنیم؟

LightGBM در تمام مسائل بهترین انتخاب نیست. در شرایط زیر باید گزینه‌های دیگر را نیز بررسی کنید:

  • تعداد رکوردها بسیار کم است.
  • داده اصلی از نوع تصویر، ویدئو یا صوت خام است.
  • مسئله به تولید متن یا محتوای جدید نیاز دارد.
  • روابط داده بسیار ساده است و یک مدل خطی کافی خواهد بود.
  • توضیح‌پذیری کامل و مستقیم از دقت بیشتر مهم‌تر است.
  • داده ماهیت زمانی دارد، اما بدون طراحی درست از مدل جدولی استفاده می‌شود.
  • کیفیت داده پایین است و هنوز Pipeline پاک‌سازی مشخصی وجود ندارد.

در یادگیری ماشین، الگوریتم پیچیده‌تر همیشه مدل بهتری تولید نمی‌کند. ساخت یک Baseline ساده قبل از LightGBM کمک می‌کند ارزش واقعی آن را اندازه‌گیری کنید.

مقایسه LightGBM با XGBoost، CatBoost و Random Forest

ویژگیLightGBMXGBoostCatBoostRandom Forest
روش اصلیGradient BoostingGradient BoostingGradient BoostingBagging
شیوه رشد رایج درختLeaf-wiseمعمولاً Level-wise یا Depth-wiseSymmetric Treesدرخت‌های مستقل
سرعت روی داده بزرگمعمولاً بالابالابالامتوسط تا بالا
ویژگی دسته‌ایپشتیبانی مستقیمپشتیبانی مستقیم در نسخه‌های جدیدپشتیبانی قویمعمولاً نیازمند Encoding
حساسیت به تنظیم پارامترنسبتاً زیادنسبتاً زیادمتوسطکمتر
خطر Overfitting روی داده کوچکقابل‌توجهقابل‌کنترلقابل‌کنترلمعمولاً کمتر
کاربرد مناسبداده جدولی بزرگداده جدولی عمومیداده دارای Category زیادBaseline پایدار

بهترین گزینه باید با آزمایش روی داده واقعی و معیار مناسب انتخاب شود. نتیجه یک Benchmark عمومی الزاماً روی پروژه شما تکرار نمی‌شود.

نصب LightGBM

روش پیشنهادی نصب در Python استفاده از Pip است:

pip install lightgbm

این روش در راهنمای رسمی Python در LightGBM نیز پیشنهاد شده است.

برای اجرای کامل پروژه این آموزش، کتابخانه‌های زیر را نصب کنید:

pip install lightgbm scikit-learn pandas numpy matplotlib requests

برای بررسی نسخه نصب‌شده:

python -c "import lightgbm; print(lightgbm.__version__)"

در زمان نگارش این مقاله، نسخه 4.7.0 در PyPI منتشر شده است. برای مشاهده نسخه فعلی می‌توانید صفحه LightGBM در PyPI را بررسی کنید.

ساخت Virtual Environment

بهتر است وابستگی‌های پروژه را در یک محیط مجازی نصب کنید:

python -m venv .venv

فعال‌سازی در Windows PowerShell:

.venv\Scripts\Activate.ps1

فعال‌سازی در Linux و macOS:

source .venv/bin/activate

سپس وابستگی‌ها را نصب کنید:

python -m pip install --upgrade pip
python -m pip install lightgbm scikit-learn pandas numpy matplotlib requests

ساختار پروژه

ساختار پیشنهادی پروژه:

lightgbm-project/
├── .venv/
├── artifacts/
├── train.py
├── predict.py
└── requirements.txt

محتوای requirements.txt:

lightgbm
scikit-learn
pandas
numpy
matplotlib
requests

نصب وابستگی‌ها:

pip install -r requirements.txt

پروژه عملی: پیش‌بینی انجام یک رویداد با LightGBM

در این پروژه، رفتار گروهی از کاربران فرضی را شبیه‌سازی می‌کنیم. هدف مدل این است که بر اساس ویژگی‌های عددی و دسته‌ای، احتمال انجام یک رویداد مشخص را پیش‌بینی کند.

این داده کاملاً مصنوعی است و صرفاً برای آموزش استفاده می‌شود. مزیت این روش آن است که خواننده می‌تواند پروژه را بدون دانلود دیتاست یا استفاده از اطلاعات واقعی اجرا کند.

ویژگی‌های پروژه شامل موارد زیر است:

  • تعداد Sessionهای ماهانه
  • میانگین زمان هر Session
  • تعداد روز از آخرین فعالیت
  • تعداد قابلیت‌های استفاده‌شده
  • تعداد درخواست‌های پشتیبانی
  • نوع پلن
  • نوع دستگاه
  • منطقه فرضی
  • کانال ورود کاربر

مرحله اول: Import کردن کتابخانه‌ها

فایل train.py را ایجاد کنید:

from pathlib import Path
import json

import lightgbm as lgb
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from sklearn.metrics import (
    accuracy_score,
    average_precision_score,
    classification_report,
    confusion_matrix,
    f1_score,
    precision_score,
    recall_score,
    roc_auc_score,
)
from sklearn.model_selection import train_test_split

مرحله دوم: ساخت دیتاست مصنوعی

تابع زیر ۸۰۰۰ رکورد مصنوعی تولید می‌کند:

RANDOM_STATE = 42


def create_dataset(
    number_of_rows: int = 8000,
) -> tuple[pd.DataFrame, pd.Series]:
    rng = np.random.default_rng(
        RANDOM_STATE
    )

    data = pd.DataFrame(
        {
            "monthly_sessions": rng.poisson(
                lam=12,
                size=number_of_rows,
            ),
            "average_session_minutes": rng.gamma(
                shape=2.5,
                scale=5.0,
                size=number_of_rows,
            ),
            "days_since_last_activity": rng.integers(
                low=0,
                high=61,
                size=number_of_rows,
            ),
            "used_features": rng.integers(
                low=1,
                high=16,
                size=number_of_rows,
            ),
            "support_requests": rng.poisson(
                lam=1.2,
                size=number_of_rows,
            ),
            "plan": rng.choice(
                ["basic", "pro", "team"],
                size=number_of_rows,
                p=[0.60, 0.28, 0.12],
            ),
            "device": rng.choice(
                ["desktop", "mobile", "tablet"],
                size=number_of_rows,
                p=[0.50, 0.42, 0.08],
            ),
            "region": rng.choice(
                ["north", "south", "east", "west"],
                size=number_of_rows,
            ),
            "source": rng.choice(
                ["organic", "direct", "referral", "campaign"],
                size=number_of_rows,
                p=[0.35, 0.30, 0.15, 0.20],
            ),
        }
    )

    plan_effect = (
        data["plan"]
        .map(
            {
                "basic": 0.0,
                "pro": 0.55,
                "team": 0.85,
            }
        )
        .astype(float)
    )

    source_effect = (
        data["source"]
        .map(
            {
                "organic": 0.15,
                "direct": 0.25,
                "referral": 0.45,
                "campaign": 0.0,
            }
        )
        .astype(float)
    )

    noise = rng.normal(
        loc=0.0,
        scale=0.75,
        size=number_of_rows,
    )

    score = (
        -2.8
        + 0.075 * data["monthly_sessions"]
        + 0.035 * data["average_session_minutes"]
        - 0.055 * data["days_since_last_activity"]
        + 0.10 * data["used_features"]
        - 0.18 * data["support_requests"]
        + plan_effect
        + source_effect
        + noise
    )

    probability = 1 / (
        1 + np.exp(-score)
    )

    target = rng.binomial(
        n=1,
        p=probability,
    )

    categorical_columns = [
        "plan",
        "device",
        "region",
        "source",
    ]

    for column in categorical_columns:
        data[column] = data[column].astype(
            "category"
        )

    return data, pd.Series(
        target,
        name="target",
    )

دیتاست را ایجاد و بررسی می‌کنیم:

X, y = create_dataset()

print(X.head())
print(X.dtypes)

print("\nDataset shape:")
print(X.shape)

print("\nTarget distribution:")
print(y.value_counts())

print("\nTarget percentage:")
print(y.value_counts(normalize=True))

در این مثال، Target با ترکیبی از ویژگی‌ها و مقداری نویز ساخته شده است. این کار باعث می‌شود مسئله بیش‌ازحد ساده یا کاملاً قابل‌پیش‌بینی نباشد.

تقسیم داده به Train، Validation و Test

داده را به سه قسمت تقسیم می‌کنیم:

  • Train برای آموزش
  • Validation برای Early Stopping و انتخاب Threshold
  • Test برای ارزیابی نهایی

ابتدا Test را جدا می‌کنیم:

(
    X_train_valid,
    X_test,
    y_train_valid,
    y_test,
) = train_test_split(
    X,
    y,
    test_size=0.20,
    stratify=y,
    random_state=RANDOM_STATE,
)

سپس Validation را از بخش باقی‌مانده جدا می‌کنیم:

(
    X_train,
    X_valid,
    y_train,
    y_valid,
) = train_test_split(
    X_train_valid,
    y_train_valid,
    test_size=0.20,
    stratify=y_train_valid,
    random_state=RANDOM_STATE,
)

بررسی اندازه مجموعه‌ها:

print("Train:", X_train.shape)
print("Validation:", X_valid.shape)
print("Test:", X_test.shape)

استفاده از stratify کمک می‌کند نسبت کلاس‌های صفر و یک در هر مجموعه تقریباً حفظ شود.

در این تقسیم‌بندی، حدود ۶۴ درصد داده برای Train، حدود ۱۶ درصد برای Validation و ۲۰ درصد برای Test استفاده می‌شود.

چرا نباید Test را برای تنظیم مدل استفاده کرد؟

مجموعه Test باید تا پایان پروژه کنار گذاشته شود. اگر پارامترها، Threshold یا ویژگی‌ها را بر اساس نتیجه Test تغییر دهید، مدل به‌طور غیرمستقیم از اطلاعات Test استفاده خواهد کرد.

این موضوع باعث می‌شود نتیجه نهایی خوش‌بینانه باشد و عملکرد واقعی Production را به‌درستی نشان ندهد.

چرخه صحیح به‌صورت زیر است:

Train:
یادگیری پارامترهای مدل

Validation:
Early Stopping، انتخاب Hyperparameter و Threshold

Test:
فقط ارزیابی نهایی

مدیریت داده‌های نامتوازن

اگر کلاس مثبت کمتر از کلاس منفی باشد، Accuracy به‌تنهایی معیار کافی نیست. برای نمونه، اگر فقط ۱۰ درصد رکوردها مثبت باشند، مدلی که همیشه صفر پیش‌بینی کند Accuracy برابر با ۹۰ درصد خواهد داشت؛ اما در عمل هیچ نمونه مثبتی را تشخیص نمی‌دهد.

برای محاسبه وزن کلاس مثبت:

negative_count = int(
    (y_train == 0).sum()
)

positive_count = int(
    (y_train == 1).sum()
)

scale_pos_weight = (
    negative_count / positive_count
)

print(
    "scale_pos_weight:",
    scale_pos_weight,
)

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

همچنین توجه کنید که وزن‌دهی به کلاس‌ها می‌تواند روی Calibration احتمالات اثر بگذارد. اگر مقدار خروجی باید به‌عنوان احتمال دقیق تفسیر شود، علاوه بر معیارهای طبقه‌بندی باید Probability Calibration را نیز ارزیابی کنید. مستندات LGBMClassifier نیز درباره اثر Class Weight بر برآورد احتمالات هشدار می‌دهد.

ساخت مدل LGBMClassifier

مدل را با تنظیمات اولیه زیر می‌سازیم:

model = lgb.LGBMClassifier(
    objective="binary",
    boosting_type="gbdt",
    n_estimators=1500,
    learning_rate=0.03,
    num_leaves=31,
    max_depth=-1,
    min_child_samples=30,
    subsample=0.85,
    subsample_freq=1,
    colsample_bytree=0.85,
    reg_alpha=0.05,
    reg_lambda=1.0,
    scale_pos_weight=scale_pos_weight,
    importance_type="gain",
    random_state=RANDOM_STATE,
    n_jobs=-1,
    verbosity=-1,
)

معنی مهم‌ترین پارامترها

پارامترکاربرد
objectiveتابع هدف مدل
boosting_typeنوع الگوریتم Boosting
n_estimatorsحداکثر تعداد درخت‌ها
learning_rateمیزان اثر هر درخت جدید
num_leavesحداکثر تعداد برگ‌های هر درخت
max_depthمحدودیت عمق درخت
min_child_samplesحداقل تعداد رکورد در هر برگ
subsampleسهم رکوردهای انتخاب‌شده
subsample_freqفاصله اجرای Row Sampling
colsample_bytreeسهم ویژگی‌های انتخاب‌شده برای هر درخت
reg_alphaRegularization نوع L1
reg_lambdaRegularization نوع L2
scale_pos_weightوزن کلاس مثبت
importance_typeنوع محاسبه Feature Importance
n_jobsتعداد Threadهای پردازنده

فهرست کامل گزینه‌ها در مستندات پارامترهای LightGBM در دسترس است.

آموزش مدل با Early Stopping در LightGBM 4.7

در LightGBM 4.7 می‌توان داده Validation را با eval_X و eval_y به مدل داد:

callbacks = [
    lgb.early_stopping(
        stopping_rounds=50,
        verbose=False,
    ),
    lgb.log_evaluation(
        period=0,
    ),
]

model.fit(
    X_train,
    y_train,
    eval_X=X_valid,
    eval_y=y_valid,
    eval_names=["validation"],
    eval_metric=[
        "binary_logloss",
        "auc",
    ],
    categorical_feature="auto",
    callbacks=callbacks,
)

پارامتر قدیمی eval_set در LightGBM 4.7 منسوخ شده و در نسخه جدید، eval_X و eval_y جایگزین آن شده‌اند.

اگر از نسخه قدیمی‌تر LightGBM استفاده می‌کنید و eval_X شناخته نمی‌شود، می‌توانید از ساختار زیر استفاده کنید:

model.fit(
    X_train,
    y_train,
    eval_set=[
        (X_valid, y_valid)
    ],
    eval_names=[
        "validation"
    ],
    eval_metric=[
        "binary_logloss",
        "auc",
    ],
    categorical_feature="auto",
    callbacks=callbacks,
)

پس از آموزش:

print(
    "Best iteration:",
    model.best_iteration_,
)

print(
    "Best scores:",
    model.best_score_,
)

Early Stopping زمانی آموزش را متوقف می‌کند که معیار Validation برای تعداد مشخصی Iteration بهتر نشود.

مزیت این روش آن است که می‌توان n_estimators را نسبتاً بزرگ انتخاب کرد، اما مدل فقط تا جایی آموزش ببیند که عملکرد Validation در حال بهبود است.

ارزیابی اولیه مدل

احتمال کلاس مثبت را دریافت می‌کنیم:

valid_probabilities = (
    model.predict_proba(
        X_valid,
        validate_features=True,
    )[:, 1]
)

پیش‌بینی با Threshold پیش‌فرض:

valid_predictions = (
    valid_probabilities >= 0.50
).astype(int)

محاسبه معیارها:

print(
    "Accuracy:",
    accuracy_score(
        y_valid,
        valid_predictions,
    ),
)

print(
    "Precision:",
    precision_score(
        y_valid,
        valid_predictions,
    ),
)

print(
    "Recall:",
    recall_score(
        y_valid,
        valid_predictions,
    ),
)

print(
    "F1:",
    f1_score(
        y_valid,
        valid_predictions,
    ),
)

print(
    "ROC-AUC:",
    roc_auc_score(
        y_valid,
        valid_probabilities,
    ),
)

print(
    "Average Precision:",
    average_precision_score(
        y_valid,
        valid_probabilities,
    ),
)

معیارهای مهم برای طبقه‌بندی

معیارکاربرد
Accuracyنسبت تمام پیش‌بینی‌های درست
Precisionدرصد پیش‌بینی‌های مثبت که واقعاً مثبت بوده‌اند
Recallدرصد نمونه‌های مثبت که شناسایی شده‌اند
F1-scoreمیانگین موزون Precision و Recall
ROC-AUCکیفیت رتبه‌بندی کلاس مثبت و منفی
Average Precisionخلاصه عملکرد منحنی Precision-Recall
Log Lossکیفیت احتمالات پیش‌بینی‌شده

در پروژه‌های نامتوازن بهتر است چند معیار را هم‌زمان بررسی کنید. انتخاب یک مدل فقط بر اساس Accuracy می‌تواند نتیجه گمراه‌کننده‌ای ایجاد کند.

انتخاب Threshold مناسب

متد predict_proba احتمال کلاس‌ها را برمی‌گرداند. برای تبدیل احتمال به کلاس باید یک Threshold انتخاب کنیم:

اگر Probability >= Threshold:
    Class = 1

در غیر این صورت:
    Class = 0

Threshold پیش‌فرض 0.5 است، اما ممکن است بهترین گزینه برای پروژه شما نباشد.

برای انتخاب Threshold بر اساس F1 روی Validation:

thresholds = np.arange(
    0.10,
    0.91,
    0.01,
)

f1_scores = []

for threshold in thresholds:
    predictions = (
        valid_probabilities >= threshold
    ).astype(int)

    score = f1_score(
        y_valid,
        predictions,
    )

    f1_scores.append(score)

best_index = int(
    np.argmax(f1_scores)
)

best_threshold = float(
    thresholds[best_index]
)

best_validation_f1 = float(
    f1_scores[best_index]
)

print(
    "Best threshold:",
    best_threshold,
)

print(
    "Best validation F1:",
    best_validation_f1,
)

رسم نمودار:

plt.figure(
    figsize=(9, 5)
)

plt.plot(
    thresholds,
    f1_scores,
)

plt.axvline(
    best_threshold,
    color="red",
    linestyle="--",
    label=(
        f"Best threshold = "
        f"{best_threshold:.2f}"
    ),
)

plt.xlabel("Threshold")
plt.ylabel("F1-score")
plt.title(
    "Validation F1 by Threshold"
)
plt.legend()
plt.tight_layout()
plt.show()

Threshold باید روی Validation انتخاب شود، نه روی Test.

اگر جلوگیری از False Positive مهم‌تر است، می‌توانید Threshold را بر اساس Precision انتخاب کنید. اگر پیداکردن تعداد بیشتری از نمونه‌های مثبت مهم‌تر باشد، Recall اهمیت بیشتری خواهد داشت.

ارزیابی نهایی روی Test

اکنون Threshold انتخاب شده و می‌توانیم ارزیابی نهایی را انجام دهیم:

test_probabilities = (
    model.predict_proba(
        X_test,
        validate_features=True,
    )[:, 1]
)

test_predictions = (
    test_probabilities >= best_threshold
).astype(int)

نمایش معیارها:

print(
    "Test Accuracy:",
    accuracy_score(
        y_test,
        test_predictions,
    ),
)

print(
    "Test Precision:",
    precision_score(
        y_test,
        test_predictions,
    ),
)

print(
    "Test Recall:",
    recall_score(
        y_test,
        test_predictions,
    ),
)

print(
    "Test F1:",
    f1_score(
        y_test,
        test_predictions,
    ),
)

print(
    "Test ROC-AUC:",
    roc_auc_score(
        y_test,
        test_probabilities,
    ),
)

print(
    "Test Average Precision:",
    average_precision_score(
        y_test,
        test_probabilities,
    ),
)

نمایش Classification Report:

print(
    classification_report(
        y_test,
        test_predictions,
    )
)

نمایش Confusion Matrix:

matrix = confusion_matrix(
    y_test,
    test_predictions,
)

print(matrix)

ساختار Confusion Matrix در طبقه‌بندی دودویی:

[[True Negative,  False Positive],
 [False Negative, True Positive]]

رسم Confusion Matrix

برای نمایش تصویری:

from sklearn.metrics import (
    ConfusionMatrixDisplay,
)

display = ConfusionMatrixDisplay(
    confusion_matrix=matrix,
    display_labels=[0, 1],
)

display.plot(
    cmap="Blues",
)

plt.title(
    "LightGBM Confusion Matrix"
)

plt.tight_layout()
plt.show()

بررسی روند آموزش مدل

LightGBM نتیجه معیارهای هر Iteration را در evals_result_ نگه می‌دارد:

evaluation_results = (
    model.evals_result_
)

print(
    evaluation_results.keys()
)

برای رسم AUC:

validation_auc = (
    evaluation_results[
        "validation"
    ]["auc"]
)

plt.figure(
    figsize=(9, 5)
)

plt.plot(
    validation_auc,
)

plt.axvline(
    model.best_iteration_,
    color="red",
    linestyle="--",
    label="Best iteration",
)

plt.xlabel(
    "Boosting iteration"
)

plt.ylabel(
    "Validation AUC"
)

plt.title(
    "LightGBM Validation AUC"
)

plt.legend()
plt.tight_layout()
plt.show()

این نمودار کمک می‌کند بفهمید مدل در چه مرحله‌ای به بهترین عملکرد رسیده و آیا آموزش طولانی‌تر مفید بوده است.

تحلیل Feature Importance

LightGBM می‌تواند اهمیت ویژگی‌ها را بر اساس تعداد Split یا میزان Gain محاسبه کند.

چون هنگام ساخت مدل importance_type="gain" را انتخاب کرده‌ایم:

importance_df = pd.DataFrame(
    {
        "feature": (
            model.feature_name_
        ),
        "importance": (
            model.feature_importances_
        ),
    }
).sort_values(
    "importance",
    ascending=False,
)

print(
    importance_df.head(15)
)

رسم نمودار:

top_features = (
    importance_df
    .head(12)
    .sort_values(
        "importance",
        ascending=True,
    )
)

plt.figure(
    figsize=(9, 6)
)

plt.barh(
    top_features["feature"],
    top_features["importance"],
)

plt.xlabel(
    "Total gain"
)

plt.ylabel(
    "Feature"
)

plt.title(
    "LightGBM Feature Importance"
)

plt.tight_layout()
plt.show()

تفاوت Split و Gain

نوع اهمیتمعنی
splitتعداد دفعات استفاده از ویژگی برای تقسیم
gainمجموع بهبود ایجادشده توسط تقسیم‌های ویژگی

ویژگی‌ای ممکن است تعداد Split زیادی داشته باشد، اما Gain کمی ایجاد کند. برای همین بررسی هر دو دیدگاه می‌تواند مفید باشد.

Feature Importance نشان‌دهنده رابطه علت و معلولی نیست. این مقدار فقط نحوه استفاده مدل از ویژگی‌ها را نشان می‌دهد.

توضیح یک پیش‌بینی با Feature Contribution

LightGBM می‌تواند سهم هر ویژگی در یک پیش‌بینی را برگرداند:

row = X_test.iloc[[0]]

contributions = model.predict(
    row,
    pred_contrib=True,
    validate_features=True,
)

print(
    contributions
)

خروجی برای یک مسئله دودویی شامل سهم هر ویژگی و یک مقدار پایه است. آخرین ستون معمولاً Expected Value یا مقدار پایه مدل محسوب می‌شود.

برای تبدیل خروجی به جدول:

contribution_names = (
    list(model.feature_name_)
    + ["expected_value"]
)

contribution_df = pd.DataFrame(
    contributions,
    columns=contribution_names,
)

print(
    contribution_df.T.sort_values(
        by=0,
        key=abs,
        ascending=False,
    )
)

Contribution مثبت، امتیاز خام مدل را به سمت کلاس مثبت می‌برد و Contribution منفی آن را کاهش می‌دهد.

این مقادیر نیز نباید به‌عنوان علت قطعی تفسیر شوند. آن‌ها فقط توضیح می‌دهند مدل چگونه به خروجی خود رسیده است.

تنظیم پارامترهای LightGBM

پارامتر num_leaves

num_leaves یکی از مهم‌ترین پارامترهای LightGBM است. تعداد بیشتر برگ‌ها به مدل اجازه می‌دهد روابط پیچیده‌تری یاد بگیرد، اما خطر Overfitting را افزایش می‌دهد.

نقاط شروع قابل آزمایش:

15
31
63
127

این اعداد قانون ثابت نیستند. مقدار مناسب باید با Validation یا Cross-validation انتخاب شود.

طبق راهنمای رسمی تنظیم پارامترهای LightGBM، رشد Leaf-wise می‌تواند درخت‌هایی عمیق‌تر از روش Depth-wise ایجاد کند؛ بنابراین کنترل num_leaves و max_depth اهمیت زیادی دارد.

پارامتر max_depth

مقدار -1 یعنی محدودیت مستقیمی برای عمق وجود ندارد.

برای داده کوچک می‌توان مقادیری مانند زیر را آزمایش کرد:

4
6
8
10

اگر max_depth را تنظیم می‌کنید، بهتر است num_leaves نیز متناسب با آن انتخاب شود.

پارامتر min_child_samples

این پارامتر حداقل تعداد رکوردهای هر Leaf را مشخص می‌کند.

مقدار بزرگ‌تر می‌تواند مدل را محافظه‌کارتر کند:

20
30
50
100

اگر دیتاست کوچک است و مدل Overfit می‌شود، افزایش min_child_samples می‌تواند مؤثر باشد.

پارامتر learning_rate

نرخ یادگیری کمتر معمولاً به تعداد درخت بیشتری نیاز دارد:

0.01
0.03
0.05
0.10

کاهش learning_rate بدون افزایش n_estimators ممکن است باعث Underfitting شود.

پارامترهای Sampling

برای نمونه‌برداری از ردیف‌ها:

subsample=0.80
subsample_freq=1

برای نمونه‌برداری از ستون‌ها:

colsample_bytree=0.80

این تنظیمات می‌توانند به کاهش Overfitting و زمان آموزش کمک کنند.

Regularization

برای Regularization نوع L1:

reg_alpha=0.1

برای Regularization نوع L2:

reg_lambda=1.0

افزایش بیش‌ازحد این پارامترها ممکن است مدل را بیش‌ازحد ساده کند.

جست‌وجوی پارامترها با RandomizedSearchCV

برای جست‌وجوی اولیه:

from sklearn.model_selection import (
    RandomizedSearchCV,
)

search_model = lgb.LGBMClassifier(
    objective="binary",
    boosting_type="gbdt",
    scale_pos_weight=scale_pos_weight,
    random_state=RANDOM_STATE,
    n_jobs=-1,
    verbosity=-1,
)

parameter_distributions = {
    "n_estimators": [
        200,
        400,
        700,
    ],
    "learning_rate": [
        0.02,
        0.05,
        0.10,
    ],
    "num_leaves": [
        15,
        31,
        63,
    ],
    "max_depth": [
        -1,
        5,
        8,
        12,
    ],
    "min_child_samples": [
        20,
        40,
        80,
    ],
    "subsample": [
        0.70,
        0.85,
        1.0,
    ],
    "subsample_freq": [
        1,
    ],
    "colsample_bytree": [
        0.70,
        0.85,
        1.0,
    ],
    "reg_alpha": [
        0.0,
        0.05,
        0.5,
    ],
    "reg_lambda": [
        0.5,
        1.0,
        3.0,
    ],
}

search = RandomizedSearchCV(
    estimator=search_model,
    param_distributions=(
        parameter_distributions
    ),
    n_iter=25,
    scoring="average_precision",
    cv=5,
    random_state=RANDOM_STATE,
    n_jobs=-1,
    verbose=1,
)

search.fit(
    X_train_valid,
    y_train_valid,
    categorical_feature="auto",
)

print(
    "Best parameters:"
)

print(
    search.best_params_
)

print(
    "Best CV score:",
    search.best_score_,
)

در این مثال برای ساده‌ترشدن Cross-validation از Early Stopping استفاده نشده است.

روش مرحله‌ای مناسب‌تر:

  1. یک مدل Baseline بسازید.
  2. محدوده پارامترها را با Cross-validation بررسی کنید.
  3. بهترین تنظیمات را روی Train آموزش دهید.
  4. از Validation برای Early Stopping استفاده کنید.
  5. Threshold را روی Validation انتخاب کنید.
  6. فقط یک‌بار روی Test ارزیابی نهایی انجام دهید.

کار با داده‌های دسته‌ای

LightGBM می‌تواند ستون‌های دسته‌ای Pandas را مستقیماً شناسایی کند:

categorical_columns = [
    "plan",
    "device",
    "region",
    "source",
]

for column in categorical_columns:
    X[column] = X[column].astype(
        "category"
    )

سپس هنگام آموزش:

model.fit(
    X_train,
    y_train,
    categorical_feature="auto",
)

این قابلیت باعث می‌شود در بسیاری از پروژه‌ها نیازی به ساخت دستی صدها ستون One-hot نباشد.

بااین‌حال باید Categoryهای زمان آموزش ذخیره شوند. اگر هنگام پیش‌بینی دسته‌ها متفاوت باشند، ممکن است نوع ستون یا تفسیر داده تغییر کند.

ذخیره Categoryهای زمان آموزش

Categoryهای هر ستون را استخراج می‌کنیم:

categorical_columns = [
    "plan",
    "device",
    "region",
    "source",
]

categories = {
    column: (
        X_train[column]
        .cat.categories
        .tolist()
    )
    for column in categorical_columns
}

print(categories)

این اطلاعات را باید همراه مدل ذخیره کنیم.

ذخیره مدل LightGBM

ایجاد پوشه خروجی:

artifacts_dir = Path(
    "artifacts"
)

artifacts_dir.mkdir(
    parents=True,
    exist_ok=True,
)

ذخیره Booster:

model.booster_.save_model(
    artifacts_dir
    / "lightgbm_model.txt",
    num_iteration=(
        model.best_iteration_
    ),
)

ذخیره Metadata:

metadata = {
    "threshold": best_threshold,
    "feature_names": (
        model.feature_name_
    ),
    "categorical_columns": (
        categorical_columns
    ),
    "categories": categories,
    "best_iteration": int(
        model.best_iteration_
    ),
    "target_name": "target",
}

with (
    artifacts_dir
    / "metadata.json"
).open(
    "w",
    encoding="utf-8",
) as file:
    json.dump(
        metadata,
        file,
        ensure_ascii=False,
        indent=2,
    )

بهتر است اطلاعات زیر نیز در یک Model Registry یا فایل Metadata نگهداری شود:

  • نسخه LightGBM
  • نسخه Python
  • تاریخ آموزش
  • نسخه دیتاست
  • پارامترهای مدل
  • معیارهای Validation
  • معیارهای Test
  • نام و ترتیب ویژگی‌ها
  • Categoryهای مجاز
  • Threshold
  • منطق Preprocessing

بارگذاری مدل و پیش‌بینی

فایل predict.py:

from pathlib import Path
import json

import lightgbm as lgb
import pandas as pd


artifacts_dir = Path(
    "artifacts"
)

booster = lgb.Booster(
    model_file=(
        artifacts_dir
        / "lightgbm_model.txt"
    )
)

with (
    artifacts_dir
    / "metadata.json"
).open(
    "r",
    encoding="utf-8",
) as file:
    metadata = json.load(file)

feature_names = (
    metadata["feature_names"]
)

threshold = float(
    metadata["threshold"]
)

new_record = {
    "monthly_sessions": 18,
    "average_session_minutes": 14.5,
    "days_since_last_activity": 3,
    "used_features": 8,
    "support_requests": 1,
    "plan": "pro",
    "device": "desktop",
    "region": "north",
    "source": "organic",
}

input_df = pd.DataFrame(
    [new_record]
)

for column in (
    metadata["categorical_columns"]
):
    input_df[column] = pd.Categorical(
        input_df[column],
        categories=(
            metadata["categories"][
                column
            ]
        ),
    )

input_df = input_df[
    feature_names
]

probability = float(
    booster.predict(
        input_df
    )[0]
)

predicted_class = int(
    probability >= threshold
)

result = {
    "probability": round(
        probability,
        6,
    ),
    "threshold": threshold,
    "predicted_class": (
        predicted_class
    ),
}

print(result)

اعتبارسنجی ویژگی‌ها قبل از پیش‌بینی

برای جلوگیری از خطا:

missing_features = (
    set(feature_names)
    - set(input_df.columns)
)

unexpected_features = (
    set(input_df.columns)
    - set(feature_names)
)

if missing_features:
    raise ValueError(
        "Missing features: "
        f"{sorted(missing_features)}"
    )

if unexpected_features:
    raise ValueError(
        "Unexpected features: "
        f"{sorted(unexpected_features)}"
    )

برای Categoryهای ناشناخته:

for column in (
    metadata["categorical_columns"]
):
    allowed_values = set(
        metadata["categories"][
            column
        ]
    )

    current_values = set(
        input_df[column]
        .dropna()
        .astype(str)
    )

    unknown_values = (
        current_values
        - allowed_values
    )

    if unknown_values:
        raise ValueError(
            f"Unknown values in "
            f"{column}: "
            f"{sorted(unknown_values)}"
        )

این روش باید پیش از تبدیل ستون به pd.Categorical اجرا شود؛ زیرا Pandas ممکن است مقدار ناشناخته را به NaN تبدیل کند.

ساخت تابع پیش‌بینی قابل‌استفاده

def predict_record(
    booster: lgb.Booster,
    record: dict,
    metadata: dict,
) -> dict:
    feature_names = (
        metadata["feature_names"]
    )

    missing_features = (
        set(feature_names)
        - set(record.keys())
    )

    if missing_features:
        raise ValueError(
            "Missing features: "
            f"{sorted(missing_features)}"
        )

    input_df = pd.DataFrame(
        [record]
    )

    for column in (
        metadata[
            "categorical_columns"
        ]
    ):
        allowed_categories = (
            metadata["categories"][
                column
            ]
        )

        if (
            record[column]
            not in allowed_categories
        ):
            raise ValueError(
                f"Unknown category "
                f"for {column}: "
                f"{record[column]}"
            )

        input_df[column] = (
            pd.Categorical(
                input_df[column],
                categories=(
                    allowed_categories
                ),
            )
        )

    input_df = input_df[
        feature_names
    ]

    probability = float(
        booster.predict(
            input_df
        )[0]
    )

    threshold = float(
        metadata["threshold"]
    )

    return {
        "probability": round(
            probability,
            6,
        ),
        "threshold": threshold,
        "predicted_class": int(
            probability >= threshold
        ),
    }

استفاده:

prediction = predict_record(
    booster=booster,
    record=new_record,
    metadata=metadata,
)

print(prediction)

کد کامل آموزش LightGBM

نسخه یکپارچه train.py:

from pathlib import Path
import json

import lightgbm as lgb
import numpy as np
import pandas as pd

from sklearn.metrics import (
    accuracy_score,
    average_precision_score,
    classification_report,
    confusion_matrix,
    f1_score,
    precision_score,
    recall_score,
    roc_auc_score,
)
from sklearn.model_selection import (
    train_test_split,
)


RANDOM_STATE = 42


def create_dataset(
    number_of_rows=8000,
):
    rng = np.random.default_rng(
        RANDOM_STATE
    )

    X = pd.DataFrame(
        {
            "monthly_sessions": rng.poisson(
                12,
                number_of_rows,
            ),
            "average_session_minutes": rng.gamma(
                2.5,
                5.0,
                number_of_rows,
            ),
            "days_since_last_activity": rng.integers(
                0,
                61,
                number_of_rows,
            ),
            "used_features": rng.integers(
                1,
                16,
                number_of_rows,
            ),
            "support_requests": rng.poisson(
                1.2,
                number_of_rows,
            ),
            "plan": rng.choice(
                [
                    "basic",
                    "pro",
                    "team",
                ],
                number_of_rows,
                p=[
                    0.60,
                    0.28,
                    0.12,
                ],
            ),
            "device": rng.choice(
                [
                    "desktop",
                    "mobile",
                    "tablet",
                ],
                number_of_rows,
                p=[
                    0.50,
                    0.42,
                    0.08,
                ],
            ),
            "region": rng.choice(
                [
                    "north",
                    "south",
                    "east",
                    "west",
                ],
                number_of_rows,
            ),
            "source": rng.choice(
                [
                    "organic",
                    "direct",
                    "referral",
                    "campaign",
                ],
                number_of_rows,
                p=[
                    0.35,
                    0.30,
                    0.15,
                    0.20,
                ],
            ),
        }
    )

    plan_effect = X["plan"].map(
        {
            "basic": 0.0,
            "pro": 0.55,
            "team": 0.85,
        }
    ).astype(float)

    source_effect = X["source"].map(
        {
            "organic": 0.15,
            "direct": 0.25,
            "referral": 0.45,
            "campaign": 0.0,
        }
    ).astype(float)

    noise = rng.normal(
        0.0,
        0.75,
        number_of_rows,
    )

    score = (
        -2.8
        + 0.075 * X["monthly_sessions"]
        + 0.035 * X[
            "average_session_minutes"
        ]
        - 0.055 * X[
            "days_since_last_activity"
        ]
        + 0.10 * X["used_features"]
        - 0.18 * X["support_requests"]
        + plan_effect
        + source_effect
        + noise
    )

    probability = 1 / (
        1 + np.exp(-score)
    )

    y = pd.Series(
        rng.binomial(
            1,
            probability,
        ),
        name="target",
    )

    categorical_columns = [
        "plan",
        "device",
        "region",
        "source",
    ]

    for column in categorical_columns:
        X[column] = X[column].astype(
            "category"
        )

    return (
        X,
        y,
        categorical_columns,
    )


def find_best_threshold(
    y_true,
    probabilities,
):
    thresholds = np.arange(
        0.10,
        0.91,
        0.01,
    )

    scores = [
        f1_score(
            y_true,
            (
                probabilities
                >= threshold
            ).astype(int),
        )
        for threshold in thresholds
    ]

    best_index = int(
        np.argmax(scores)
    )

    return (
        float(
            thresholds[best_index]
        ),
        float(
            scores[best_index]
        ),
    )


def print_metrics(
    title,
    y_true,
    probabilities,
    threshold,
):
    predictions = (
        probabilities >= threshold
    ).astype(int)

    print(f"\n{title}")

    print(
        "Accuracy:",
        accuracy_score(
            y_true,
            predictions,
        ),
    )

    print(
        "Precision:",
        precision_score(
            y_true,
            predictions,
        ),
    )

    print(
        "Recall:",
        recall_score(
            y_true,
            predictions,
        ),
    )

    print(
        "F1:",
        f1_score(
            y_true,
            predictions,
        ),
    )

    print(
        "ROC-AUC:",
        roc_auc_score(
            y_true,
            probabilities,
        ),
    )

    print(
        "Average Precision:",
        average_precision_score(
            y_true,
            probabilities,
        ),
    )

    print(
        "Confusion matrix:\n",
        confusion_matrix(
            y_true,
            predictions,
        ),
    )

    print(
        "Classification report:\n",
        classification_report(
            y_true,
            predictions,
        ),
    )


def main():
    (
        X,
        y,
        categorical_columns,
    ) = create_dataset()

    (
        X_train_valid,
        X_test,
        y_train_valid,
        y_test,
    ) = train_test_split(
        X,
        y,
        test_size=0.20,
        stratify=y,
        random_state=RANDOM_STATE,
    )

    (
        X_train,
        X_valid,
        y_train,
        y_valid,
    ) = train_test_split(
        X_train_valid,
        y_train_valid,
        test_size=0.20,
        stratify=y_train_valid,
        random_state=RANDOM_STATE,
    )

    negative_count = int(
        (y_train == 0).sum()
    )

    positive_count = int(
        (y_train == 1).sum()
    )

    scale_pos_weight = (
        negative_count
        / positive_count
    )

    model = lgb.LGBMClassifier(
        objective="binary",
        boosting_type="gbdt",
        n_estimators=1500,
        learning_rate=0.03,
        num_leaves=31,
        max_depth=-1,
        min_child_samples=30,
        subsample=0.85,
        subsample_freq=1,
        colsample_bytree=0.85,
        reg_alpha=0.05,
        reg_lambda=1.0,
        scale_pos_weight=(
            scale_pos_weight
        ),
        importance_type="gain",
        random_state=RANDOM_STATE,
        n_jobs=-1,
        verbosity=-1,
    )

    model.fit(
        X_train,
        y_train,
        eval_X=X_valid,
        eval_y=y_valid,
        eval_names=[
            "validation"
        ],
        eval_metric=[
            "binary_logloss",
            "auc",
        ],
        categorical_feature="auto",
        callbacks=[
            lgb.early_stopping(
                50,
                verbose=False,
            ),
            lgb.log_evaluation(
                0
            ),
        ],
    )

    valid_probabilities = (
        model.predict_proba(
            X_valid,
            validate_features=True,
        )[:, 1]
    )

    (
        best_threshold,
        best_validation_f1,
    ) = find_best_threshold(
        y_valid,
        valid_probabilities,
    )

    print(
        "Best iteration:",
        model.best_iteration_,
    )

    print(
        "Best threshold:",
        best_threshold,
    )

    print(
        "Best validation F1:",
        best_validation_f1,
    )

    print_metrics(
        "Validation metrics",
        y_valid,
        valid_probabilities,
        best_threshold,
    )

    test_probabilities = (
        model.predict_proba(
            X_test,
            validate_features=True,
        )[:, 1]
    )

    print_metrics(
        "Test metrics",
        y_test,
        test_probabilities,
        best_threshold,
    )

    artifacts_dir = Path(
        "artifacts"
    )

    artifacts_dir.mkdir(
        parents=True,
        exist_ok=True,
    )

    model.booster_.save_model(
        artifacts_dir
        / "lightgbm_model.txt",
        num_iteration=(
            model.best_iteration_
        ),
    )

    categories = {
        column: (
            X_train[column]
            .cat.categories
            .tolist()
        )
        for column in (
            categorical_columns
        )
    }

    metadata = {
        "threshold": best_threshold,
        "feature_names": (
            model.feature_name_
        ),
        "categorical_columns": (
            categorical_columns
        ),
        "categories": categories,
        "best_iteration": int(
            model.best_iteration_
        ),
        "target_name": "target",
    }

    with (
        artifacts_dir
        / "metadata.json"
    ).open(
        "w",
        encoding="utf-8",
    ) as file:
        json.dump(
            metadata,
            file,
            ensure_ascii=False,
            indent=2,
        )

    print(
        "\nModel and metadata saved."
    )


if __name__ == "__main__":
    main()

اجرای پروژه:

python train.py

ترکیب LightGBM با مدل‌های زبانی

LightGBM و مدل‌های زبانی وظایف متفاوتی دارند.

LightGBM برای تحلیل داده جدولی و تولید یک امتیاز یا احتمال مناسب است. مدل زبانی می‌تواند نتیجه را به زبان طبیعی توضیح دهد، گزارش تولید کند یا اطلاعات ساختاریافته را از متن استخراج کند.

یک معماری ترکیبی مناسب:

  1. اطلاعات متنی با مدل زبانی پردازش می‌شود.
  2. ویژگی‌های ساختاریافته از متن استخراج می‌شوند.
  3. ویژگی‌ها وارد LightGBM می‌شوند.
  4. LightGBM امتیاز یا احتمال را تولید می‌کند.
  5. مدل زبانی نتیجه را بدون تغییر مقدار اصلی توضیح می‌دهد.
  6. برنامه خروجی نهایی را به کاربر نمایش می‌دهد.

مدل زبانی نباید مقدار احتمال LightGBM را تغییر دهد یا اطلاعات جدیدی به‌عنوان واقعیت اضافه کند.

توضیح خروجی LightGBM با API درواره

برای دسترسی به مدل‌های هوش مصنوعی از طریق API می‌توانید از درواره استفاده کنید.

متغیرهای محیطی را تنظیم کنید:

در Linux و macOS:

export DARVAREH_API_KEY="YOUR_DARVAREH_API_KEY"
export DARVAREH_MODEL_ID="YOUR_MODEL_ID"

در Windows PowerShell:

$env:DARVAREH_API_KEY="YOUR_DARVAREH_API_KEY"
$env:DARVAREH_MODEL_ID="YOUR_MODEL_ID"

ارسال نتیجه مدل برای تولید توضیح:

import os
import requests


api_key = os.environ[
    "DARVAREH_API_KEY"
]

model_id = os.environ[
    "DARVAREH_MODEL_ID"
]

prediction_result = {
    "probability": 0.72,
    "threshold": 0.56,
    "predicted_class": 1,
}

response = requests.post(
    "https://api.darvareh.ir/v1/chat/completions",
    headers={
        "Authorization": (
            f"Bearer {api_key}"
        ),
        "Content-Type": (
            "application/json"
        ),
    },
    json={
        "model": model_id,
        "messages": [
            {
                "role": "system",
                "content": (
                    "نتیجه مدل یادگیری "
                    "ماشین را دقیق، کوتاه "
                    "و بدون افزودن اطلاعات "
                    "جدید توضیح بده."
                ),
            },
            {
                "role": "user",
                "content": f"""
نتیجه مدل LightGBM:

{prediction_result}

به فارسی توضیح بده:
- احتمال محاسبه‌شده چقدر است؟
- چرا کلاس نهایی مثبت شده است؟
- تأکید کن که خروجی قطعی نیست.
""",
            },
        ],
        "temperature": 0.2,
    },
    timeout=60,
)

response.raise_for_status()

result = response.json()

explanation = result[
    "choices"
][0]["message"]["content"]

print(explanation)

شناسه مدل را از صفحه مدل‌ها و قیمت‌های درواره دریافت کنید.

کلید API را داخل کد، فایل عمومی، مخزن Git یا Frontend قرار ندهید. درخواست API باید از Backend ارسال شود.

استقرار LightGBM در Production

برای استفاده واقعی از مدل:

  • مدل را یک‌بار هنگام شروع برنامه بارگذاری کنید.
  • برای هر درخواست دوباره مدل را از دیسک نخوانید.
  • Feature Schema را اعتبارسنجی کنید.
  • ترتیب ستون‌ها را ثابت نگه دارید.
  • Categoryهای زمان آموزش را ذخیره کنید.
  • Threshold را همراه مدل نسخه‌بندی کنید.
  • زمان پاسخ را اندازه‌گیری کنید.
  • خطاهای ورودی را مدیریت کنید.
  • Drift داده را پایش کنید.
  • نسخه مدل را در خروجی یا Log داخلی ثبت کنید.
  • اطلاعات حساس را در Log ذخیره نکنید.

خطاهای رایج LightGBM

خطای No module named 'lightgbm'

کتابخانه در Python فعال نصب نشده است:

python -m pip install lightgbm

بررسی محل نصب:

python -m pip show lightgbm

خطای مربوط به eval_X

احتمالاً نسخه LightGBM شما قدیمی‌تر از 4.7 است.

راه‌حل اول:

python -m pip install --upgrade lightgbm

راه‌حل دوم، استفاده از API قدیمی‌تر:

model.fit(
    X_train,
    y_train,
    eval_set=[
        (X_valid, y_valid)
    ],
    callbacks=[
        lgb.early_stopping(50)
    ],
)

خطای مربوط به ستون‌های Object

ستون دسته‌ای را به نوع category تبدیل کنید:

X["plan"] = X["plan"].astype(
    "category"
)

مدل خیلی سریع Overfit می‌شود

موارد زیر را بررسی کنید:

  • کاهش num_leaves
  • تعیین max_depth
  • افزایش min_child_samples
  • کاهش learning_rate
  • استفاده از Early Stopping
  • فعال‌کردن Row Sampling
  • فعال‌کردن Column Sampling
  • افزایش Regularization
  • بررسی Data Leakage

subsample اثری ندارد

برای فعال‌شدن Bagging معمولاً باید subsample_freq بزرگ‌تر از صفر باشد:

subsample=0.80
subsample_freq=1

هنگام پیش‌بینی Category جدید دریافت می‌شود

Categoryهای آموزش را ذخیره و ورودی را قبل از پیش‌بینی اعتبارسنجی کنید. باید برای Category جدید سیاست مشخصی مانند رد ورودی، نگاشت به unknown یا آموزش مجدد مدل داشته باشید.

مدل Accuracy خوبی دارد اما نمونه‌های مثبت را پیدا نمی‌کند

  • Recall را بررسی کنید.
  • Confusion Matrix را ببینید.
  • scale_pos_weight را آزمایش کنید.
  • Threshold را روی Validation تنظیم کنید.
  • Average Precision را گزارش دهید.
  • کیفیت و تعداد نمونه‌های مثبت را بررسی کنید.

اشتباهات متداول

انتخاب مدل فقط بر اساس سرعت

سرعت مهم است، اما معیار نهایی باید کیفیت مدل، پایداری، هزینه نگهداری و زمان پاسخ Production باشد.

استفاده از تنظیمات پیش‌فرض بدون Baseline

مقادیر پیش‌فرض برای همه دیتاست‌ها مناسب نیستند. در عین حال، جست‌وجوی گسترده بدون Baseline نیز منابع را هدر می‌دهد.

تنظیم Threshold روی Test

این کار استقلال Test را از بین می‌برد.

نادیده‌گرفتن داده‌های زمانی

اگر رکوردها وابسته به زمان هستند، تقسیم تصادفی ممکن است اطلاعات آینده را وارد Train کند. در این حالت Split زمانی مناسب‌تر است.

تفسیر Feature Importance به‌عنوان علت

اهمیت ویژگی فقط رفتار مدل را توضیح می‌دهد و اثبات نمی‌کند تغییر آن ویژگی باعث تغییر واقعی نتیجه خواهد شد.

استفاده مستقیم از احتمال بدون Calibration

اگر وزن کلاس‌ها یا Sampling استفاده شده باشد، احتمال خروجی ممکن است به Calibration نیاز داشته باشد.

ذخیره مدل بدون Metadata

بدون Feature Schema، Categoryها، Threshold و نسخه‌ها، بازتولید پیش‌بینی دشوار خواهد بود.

چک‌لیست نهایی پروژه LightGBM

  • مسئله و Target دقیق تعریف شده است.
  • یک Baseline ساده وجود دارد.
  • Train، Validation و Test جدا هستند.
  • Data Leakage بررسی شده است.
  • نوع ستون‌ها مشخص است.
  • Categoryها ذخیره شده‌اند.
  • معیار مناسب انتخاب شده است.
  • Early Stopping فعال است.
  • Threshold روی Validation انتخاب شده است.
  • Test فقط برای ارزیابی نهایی استفاده شده است.
  • مدل و Metadata با هم ذخیره شده‌اند.
  • ورودی Production اعتبارسنجی می‌شود.
  • عملکرد و Drift مدل قابل‌پایش است.
  • نسخه قبلی مدل قابل‌بازیابی است.
  • خروجی مدل به‌عنوان نتیجه قطعی نمایش داده نمی‌شود.

سؤالات متداول

LightGBM چیست؟

LightGBM یک فریم‌ورک Gradient Boosting مبتنی بر درخت تصمیم است که برای آموزش سریع و کارآمد مدل‌های طبقه‌بندی، رگرسیون و رتبه‌بندی طراحی شده است.

LightGBM بهتر است یا XGBoost؟

هیچ پاسخ ثابتی وجود ندارد. LightGBM معمولاً روی داده‌های بزرگ بسیار سریع است، اما XGBoost نیز عملکرد و اکوسیستم قدرتمندی دارد. هر دو باید روی داده واقعی پروژه ارزیابی شوند.

LightGBM بهتر است یا CatBoost؟

اگر داده دارای ستون‌های دسته‌ای متعدد باشد، CatBoost گزینه مهمی برای مقایسه است. LightGBM نیز از داده دسته‌ای پشتیبانی می‌کند و ممکن است روی دیتاست‌های بزرگ سریع‌تر باشد. نتیجه به ساختار داده و تنظیمات بستگی دارد.

آیا LightGBM برای متن فارسی مناسب است؟

LightGBM متن فارسی خام را مستقیماً درک نمی‌کند. ابتدا باید متن را به ویژگی‌های عددی، TF-IDF یا Embedding تبدیل کنید.

آیا LightGBM به StandardScaler نیاز دارد؟

مدل‌های درختی LightGBM معمولاً به StandardScaler نیاز ندارند. بااین‌حال، کیفیت داده، نوع ستون‌ها و Preprocessing همچنان اهمیت دارند.

آیا LightGBM از داده‌های گمشده پشتیبانی می‌کند؟

بله، LightGBM می‌تواند مقادیر گمشده را مدیریت کند؛ اما دلیل Missing Value و تفاوت الگوی آن در Train و Production باید بررسی شود.

آیا LightGBM از GPU استفاده می‌کند؟

LightGBM از آموزش با GPU پشتیبانی می‌کند، اما برای بسیاری از پروژه‌ها CPU کافی است. استفاده از GPU به اندازه دیتاست، نوع ویژگی‌ها و نحوه نصب کتابخانه بستگی دارد.

تفاوت num_leaves و max_depth چیست؟

num_leaves تعداد برگ‌های درخت را محدود می‌کند و max_depth حداکثر عمق آن را تعیین می‌کند. در LightGBM معمولاً کنترل num_leaves اهمیت ویژه‌ای دارد.

چرا Early Stopping لازم است؟

Early Stopping آموزش را زمانی متوقف می‌کند که عملکرد Validation بهتر نمی‌شود. این قابلیت می‌تواند زمان آموزش و احتمال Overfitting را کاهش دهد.

آیا می‌توان LightGBM را داخل API استفاده کرد؟

بله. مدل را می‌توان داخل FastAPI، Flask، Django یا سایر Backendها بارگذاری و از طریق یک Endpoint فراخوانی کرد.

آیا LightGBM یک مدل مولد است؟

خیر. LightGBM متن، تصویر یا ویدئوی جدید تولید نمی‌کند. این مدل برای پیش‌بینی، طبقه‌بندی و رتبه‌بندی داده‌های ساختاریافته استفاده می‌شود.

جمع‌بندی

LightGBM یکی از قدرتمندترین ابزارهای یادگیری ماشین برای کار با داده‌های جدولی است. سرعت آموزش، مصرف مناسب حافظه، رشد Leaf-wise درخت‌ها، پشتیبانی از ویژگی‌های دسته‌ای و سازگاری با Scikit-learn، آن را به گزینه‌ای جدی برای پروژه‌های واقعی تبدیل کرده است.

بااین‌حال، کیفیت یک پروژه فقط به انتخاب LightGBM وابسته نیست. تقسیم درست داده، جلوگیری از Data Leakage، انتخاب معیار مناسب، استفاده از Early Stopping، تنظیم Threshold و نگهداری Metadata برای رسیدن به یک مدل قابل‌اعتماد ضروری هستند.

بهترین رویکرد این است که ابتدا یک Baseline ساده بسازید، سپس LightGBM را با XGBoost، CatBoost یا Random Forest مقایسه کنید و فقط بر اساس نتایج Test مستقل و نیازهای Production تصمیم بگیرید.

برای افزودن قابلیت‌های مدل‌های زبانی به پروژه‌های Python و یادگیری ماشین می‌توانید از API هوش مصنوعی درواره استفاده کنید. برای انتخاب مدل مناسب و مشاهده قیمت به‌روز نیز صفحه مدل‌های درواره را ببینید.

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more