Optuna یک چارچوب متنباز پایتونی برای بهینهسازی خودکار فراپارامترهاست که با الگوریتم بیزی Tree-structured Parzen Estimator (TPE) فضای جستوجو را هوشمندانهتر از GridSearch و RandomSearch کاوش میکند. در Optuna 4.x (منتشرشده در ۲۰۲۵ تا ۲۰۲۶)، شما فضای جستوجو را بهصورت پویا و ضروری (define-by-run) داخل تابع هدف تعریف میکنید، آزمایشهای ضعیف را زودتر هرس (pruning) میکنید و میتوانید صدها آزمایش را بهصورت موازی روی چند پردازنده یا ماشین اجرا کنید. راستش را بخواهید، در پروژهٔ آخری که روی آن کار میکردم، جایگزین کردن GridSearchCV با Optuna زمان تنظیم مدل را از دو روز به چهار ساعت رساند. در این راهنما، از نصب تا الگوهای پیشرفتهٔ تولیدی را با کد اجراشدنی پوشش میدهم.
Optuna 4.x بر مبنای الگوریتم TPE (Bergstra و همکاران، ۲۰۱۱) کار میکند و برای بیش از ۹۰٪ کاربردهای عملی، مؤثرتر از GridSearchCV است.
الگوی define-by-run اجازه میدهد فضای جستوجو شامل انشعابهای شرطی باشد؛ چیزی که در scikit-learn GridSearch ممکن نیست.
هرس زودهنگام با MedianPruner یا HyperbandPruner زمان بهینهسازی را معمولاً ۳ تا ۱۰ برابر کاهش میدهد.
ذخیرهسازی مطالعه در RDB (SQLite/PostgreSQL) از دست رفتن نتایج جلوگیری میکند و اجرای موازی چندنودی را ممکن میسازد.
ادغام رسمی با XGBoost، LightGBM، PyTorch، TensorFlow و scikit-learn از طریق ماژول optuna.integration در دسترس است.
بهینهسازی چندهدفه (Multi-objective) با الگوریتم NSGA-II پشتیبانی میشود و برای موازنهٔ دقت/سرعت یا دقت/سایز مدل مفید است.
Optuna چیست و چه کاری انجام میدهد؟
Optuna یک چارچوب بهینهسازی فراپارامتر (Hyperparameter Optimization – HPO) است که ابتدا در سال ۲۰۱۹ توسط پژوهشگران Preferred Networks منتشر شد و از آن زمان به یکی از پرکاربردترین ابزارهای صنعتی برای تنظیم مدلهای یادگیری ماشین تبدیل شده است. برخلاف رویکرد سنتی جستوجوی شبکهای (Grid Search) که تمام ترکیبهای ممکن را میآزماید، Optuna از یک الگوریتم بیزی به نام TPE استفاده میکند تا از نتایج آزمایشهای قبلی برای پیشنهاد فراپارامترهای امیدبخشتر در آزمایشهای آینده بهره ببرد.
سه ویژگی معماری Optuna را از رقبا متمایز میکند. نخست، الگوی define-by-run؛ فضای جستوجو داخل تابع هدف (objective) و در زمان اجرای هر trial تعریف میشود. این یعنی میتوانید فضای شرطی داشته باشید (مثلاً اگر optimizer=Adam انتخاب شد، پارامتر beta1 را هم بسنجید). دوم، هرس زودهنگام (early stopping): آزمایشهایی که در میانهٔ آموزش نتایج ضعیفی نشان میدهند قطع میشوند و منابع محاسباتی به کاندیداهای بهتر اختصاص مییابد. سوم، معماری توزیعشده: یک study میتواند در چند فرآیند یا حتی چند ماشین بهصورت همزمان اجرا شود و همهٔ نتایج در یک پایگاه دادهٔ مشترک ذخیره گردد.
در سال ۲۰۲۶، Optuna به نسخهٔ ۴.x رسیده و ویژگیهایی مانند JournalStorage برای ذخیرهسازی سبک بدون نیاز به RDBMS، پشتیبانی بهبودیافته از الگوریتم CMA-ES برای فضاهای پیوستهٔ بزرگ، و ادغام رسمی با فریمورکهای جدیدتر مانند PyTorch Lightning 2.x را ارائه میدهد. اطلاعات دقیق در مستندات رسمی Optuna در دسترس است.
نصب و اولین تابع هدف
نصب Optuna از طریق pip یا conda ساده است. برای پروژههای تولیدی توصیه میکنم بستهٔ اضافی [optional] را برای نصب کتابخانههای مصورسازی نصب کنید:
# نصب پایه
pip install optuna
# نصب همراه با ابزارهای مصورسازی و ادغام
pip install "optuna[optional]"
# نصب داشبورد اختیاری
pip install optuna-dashboard
یک تابع هدف در Optuna باید یک آرگومان trial دریافت کند و یک عدد اسکالر بازگرداند که Optuna تلاش میکند آن را کمینه یا بیشینه کند. مثال زیر یک تابع سادهٔ ریاضی است تا مفهوم را نشان دهد:
import optuna
def objective(trial: optuna.Trial) -> float:
# پیشنهاد یک متغیر پیوسته در بازه [-10, 10]
x = trial.suggest_float("x", -10.0, 10.0)
# پیشنهاد یک متغیر گسسته
y = trial.suggest_int("y", -5, 5)
# پیشنهاد یک متغیر دستهای
operator = trial.suggest_categorical("op", ["add", "mul"])
if operator == "add":
return (x - 2) ** 2 + (y + 1) ** 2
else:
return (x * y - 6) ** 2
study = optuna.create_study(direction="minimize")
study.optimize(objective, n_trials=100, show_progress_bar=True)
print("بهترین مقدار:", study.best_value)
print("بهترین پارامترها:", study.best_params)
سه متد اصلی پیشنهاد پارامتر عبارتاند از suggest_float، suggest_int و suggest_categorical. برای پارامترهای در مقیاس لگاریتمی (مانند نرخ یادگیری) از آرگومان log=True استفاده کنید. این تصمیم برای پارامترهایی که چند مرتبهٔ بزرگی تغییر میکنند حیاتی است.
الگوریتم TPE چگونه کار میکند؟
TPE مخفف Tree-structured Parzen Estimator است و در مقالهٔ کلاسیک Bergstra و همکاران (NIPS 2011) معرفی شد. برخلاف بهینهسازی بیزی مبتنی بر فرآیندهای گاوسی (GP) که پیچیدگی محاسباتی درجه سوم دارد، TPE با پیچیدگی خطی نسبت به تعداد آزمایشها مقیاسپذیر میماند و برای فضاهایی با ۲۰ تا ۱۰۰ پارامتر عملی است.
ایدهٔ اصلی TPE ساده اما هوشمندانه است. پس از چند آزمایش اولیه (پیشفرض ۱۰ آزمایش تصادفی)، الگوریتم آزمایشها را بر اساس مقدار هدف به دو گروه تقسیم میکند: گروه «خوب» شامل بهترین γ درصد (پیشفرض ۲۵٪) و گروه «بد» شامل بقیه. سپس دو توزیع چگالی احتمال مجزا ساخته میشود، l(x) برای گروه خوب و g(x) برای گروه بد، با تخمینگر پارزن. آزمایش بعدی نقطهای است که نسبت l(x)/g(x) را بیشینه میکند، یعنی جایی که احتمال «خوب بودن» نسبت به «بد بودن» بیشترین است.
در Optuna، نمونهگیر پیشفرض TPESampler است اما میتوانید آن را به سلیقهٔ خود تغییر دهید:
from optuna.samplers import TPESampler, CmaEsSampler, RandomSampler
# TPE با تنظیمات سفارشی
sampler = TPESampler(
n_startup_trials=20, # آزمایشهای تصادفی اولیه
n_ei_candidates=24, # تعداد کاندیداها برای انتخاب بعدی
multivariate=True, # مدلسازی همبستگی بین پارامترها
seed=42,
)
study = optuna.create_study(sampler=sampler, direction="maximize")
گزینهٔ multivariate=True از Optuna 2.2 به بعد در دسترس است و همبستگی میان پارامترها را نیز مدل میکند. این ویژگی روی مسائل با پارامترهای وابسته (مانند اندازهٔ لایههای شبکهٔ عصبی) عملکرد را بهشکل ملموسی بهبود میبخشد. من در یکی از پروژههای بینایی ماشین با فعال کردن این گزینه، تعداد آزمایشهای لازم برای رسیدن به همان دقت را نزدیک به ۳۰٪ کاهش دادم.
Optuna در برابر GridSearchCV و RandomizedSearchCV
مقایسهٔ زیر بر اساس تجربهٔ من در چندین پروژهٔ تنظیم مدل و همچنین بنچمارکهای منتشرشده توسط تیم Optuna جمعآوری شده است. برای درک تفاوتهای عملی، جدول زیر جنبههای کلیدی را کنار هم میگذارد:
ویژگی
GridSearchCV
RandomizedSearchCV
Optuna (TPE)
الگوریتم جستوجو
پیمایش کامل
نمونهگیری تصادفی
بیزی (TPE / CMA-ES / NSGA-II)
فضای شرطی
ندارد
ندارد
پشتیبانی کامل
هرس آزمایشهای ضعیف
ندارد
ندارد
MedianPruner، Hyperband، ASHA
مقیاسپذیری موازی
Joblib (تکماشین)
Joblib (تکماشین)
چندماشین با RDBStorage
پایداری میان اجراها
ندارد
ندارد
ذخیره در SQLite/PostgreSQL
ادامهٔ مطالعه
ندارد
ندارد
با load_study
مصورسازی داخلی
ندارد
ندارد
Plotly + Matplotlib
سربار برای مسائل کوچک
کم
کم
متوسط
در عمل، اگر فضای جستوجو کمتر از ۲۰ ترکیب دارد، GridSearchCV هنوز انتخاب معقولی است و سربار Optuna توجیهپذیر نیست. اما به محض اینکه فضای جستوجو به بیش از ۵۰ ترکیب برسد یا زمان هر آزمایش از چند ثانیه فراتر رود، Optuna عملاً بیرقیب است. اگر با scikit-learn Pipeline کار میکنید، مطالعهٔ راهنمای Pipeline و ColumnTransformer در scikit-learn به شما کمک میکند تا هر دو ابزار را بهدرستی در کنار Optuna ترکیب کنید.
ادغام Optuna با scikit-learn
در پروژههای واقعی، تابع هدف معمولاً یک مدل scikit-learn را آموزش میدهد و امتیاز اعتبارسنجی متقاطع (cross-validation) را برمیگرداند. الگوی زیر یک الگوی تولیدی برای تنظیم RandomForestClassifier است:
سه نکتهٔ مهم در این کد وجود دارد. اول، direction="maximize" چون ROC-AUC معیاری است که باید بیشینه شود؛ برای معیارهایی مثل log-loss یا MSE از "minimize" استفاده کنید. دوم، آرگومان timeout=600 بهینهسازی را پس از ۱۰ دقیقه متوقف میکند حتی اگر n_trials تکمیل نشده باشد. این برای پروژههای تولیدی که بودجهٔ زمانی محدود دارید حیاتی است. سوم، step=50 در n_estimators باعث میشود پارامتر روی مقادیر گسسته با فاصلهٔ ۵۰ نمونهگیری شود، که نتایج قابلتفسیرتری میدهد.
تنظیم فراپارامتر XGBoost و LightGBM
برای مدلهای Gradient Boosting که آموزش هر دور شامل چندین صد epoch است، بازدهی هرس زودهنگام بسیار چشمگیر است. Optuna از طریق ماژول optuna.integration کالبکهای آمادهای برای این کتابخانهها فراهم میکند:
ترکیب LightGBMPruningCallback با MedianPruner نتایج شگفتانگیزی دارد؛ در بنچمارکهای تیم Optuna، این ترکیب بین ۵ تا ۱۰ برابر سریعتر از GridSearchCV به نتیجهٔ مشابه یا بهتر میرسد. برای XGBoost کد تقریباً یکسان است، فقط LightGBMPruningCallback را با XGBoostPruningCallback جایگزین کنید.
هرس آزمایشهای ناامیدکننده
هرس (pruning) در Optuna یعنی متوقف کردن زودهنگام آزمایشهایی که در گزارشهای میانی نشان میدهند احتمالاً بهتر از بهترین مقدار فعلی نخواهند شد. برای فعالسازی هرس، تابع هدف باید در پایان هر epoch یا هر مرحلهٔ آموزش، مقدار میانی را با trial.report() اعلام کند و سپس trial.should_prune() را بررسی نماید:
import optuna
import numpy as np
from sklearn.linear_model import SGDClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X, y = load_digits(return_X_y=True)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.25, random_state=0)
def objective(trial: optuna.Trial) -> float:
alpha = trial.suggest_float("alpha", 1e-6, 1e-1, log=True)
clf = SGDClassifier(loss="log_loss", alpha=alpha, random_state=0)
for epoch in range(50):
clf.partial_fit(X_train, y_train, classes=np.unique(y))
acc = accuracy_score(y_val, clf.predict(X_val))
trial.report(acc, step=epoch)
if trial.should_prune():
raise optuna.TrialPruned()
return acc
study = optuna.create_study(
direction="maximize",
pruner=optuna.pruners.HyperbandPruner(min_resource=1, max_resource=50),
)
study.optimize(objective, n_trials=80)
print(f"آزمایشهای هرسشده: {sum(1 for t in study.trials if t.state == optuna.trial.TrialState.PRUNED)}")
Optuna چهار هرسگر اصلی ارائه میدهد. MedianPruner سادهترین است؛ اگر امتیاز میانی آزمایش فعلی از میانهٔ آزمایشهای قبلی در همان step بدتر باشد، هرس میشود. SuccessiveHalvingPruner و HyperbandPruner بر اساس مقالهٔ ASHA کار میکنند و برای مسائل با تعداد آزمایش بالا (بیش از ۲۰۰) بازدهی بهتری دارند. PatientPruner اجازه میدهد آزمایش برای تعداد مشخصی از گامها بدون بهبود ادامه یابد.
اجرای موازی و ذخیرهسازی پایدار
در تنظیمات پیشفرض، Optuna آزمایشها را بهصورت متوالی در یک فرآیند اجرا میکند و مطالعه را در حافظه نگه میدارد. برای پروژههای واقعی این کافی نیست. باید نتایج پایدار باشند و از چند هستهٔ پردازنده استفاده کنیم. راهحل، ذخیرهسازی مطالعه در RDB است:
import optuna
storage_url = "sqlite:///optuna_studies.db"
# ایجاد یا بارگذاری مطالعه
study = optuna.create_study(
study_name="xgb_tuning_v2",
storage=storage_url,
load_if_exists=True,
direction="maximize",
)
# اجرای موازی داخل یک فرآیند با joblib
study.optimize(objective, n_trials=200, n_jobs=4)
برای اجرای چندماشین، همان کد را روی چند سرور با URL یکسان storage اجرا کنید. همه به یک PostgreSQL یا MySQL مشترک متصل میشوند و بهصورت هماهنگ آزمایش میگیرند. یا از رابط خط فرمان استفاده کنید:
# روی هر ماشین/کارگر جداگانه:
optuna study optimize --study-name xgb_tuning_v2 --storage "postgresql://user:pass@db-host/optuna" --n-trials 50 objective_script.py objective
در Optuna 3.1+ گزینهای سبکتر به نام JournalStorage اضافه شده که فقط به یک فایل مشترک روی سیستم فایل نیاز دارد و برای تیمهای کوچک ایدهآل است. برای پردازش نتایج، من اغلب مطالعه را به یک DataFrame تبدیل میکنم و در Pandas 3.0 تحلیل میکنم:
ماژول optuna.visualization شش نوع نمودار Plotly تعاملی برای بررسی نتایج ارائه میدهد. من در بازبینی هر پروژه از این سه نمودار بیشترین استفاده را میکنم:
from optuna.visualization import (
plot_optimization_history,
plot_param_importances,
plot_parallel_coordinate,
plot_slice,
plot_contour,
plot_edf,
)
# روند بهبود مقدار هدف در طول آزمایشها
fig1 = plot_optimization_history(study)
# اهمیت هر فراپارامتر بر اساس fANOVA
fig2 = plot_param_importances(study)
# رابطهٔ چندبعدی بین پارامترها
fig3 = plot_parallel_coordinate(study, params=["learning_rate", "num_leaves"])
fig1.show(); fig2.show(); fig3.show()
plot_param_importances از الگوریتم fANOVA (Hutter و همکاران، ۲۰۱۴) استفاده میکند و به شما نشان میدهد کدام فراپارامترها بیشترین تأثیر را بر عملکرد مدل دارند. صادقانه بگویم، این تحلیل اغلب باعث میشود در دور بعدی بهینهسازی، فضای جستوجو را به سه یا چهار پارامتر مهم محدود کنید و بازدهی را چند برابر افزایش دهید. اگر میخواهید نمودارهای سبکتر و آفلاین بسازید، Optuna از Matplotlib هم پشتیبانی میکند: کافی است از optuna.visualization.matplotlib ایمپورت کنید. برای الگوهای عمومی مصورسازی داده، مطالعهٔ راهنمای Matplotlib و Seaborn پیشنهاد میشود.
بهینهسازی چندهدفه
در بسیاری از پروژههای صنعتی، فقط دقت مدل مهم نیست. سرعت استنتاج، مصرف حافظه، یا اندازهٔ مدل هم مهم است. بهینهسازی چندهدفه (Multi-objective Optimization) به شما اجازه میدهد جبههٔ پرتو (Pareto Front) از راهحلهای غیرقابلغلبه را پیدا کنید:
import time
import optuna
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X, y = load_digits(return_X_y=True)
X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=1)
def objective(trial: optuna.Trial):
n_estimators = trial.suggest_int("n_estimators", 50, 500)
max_depth = trial.suggest_int("max_depth", 3, 30)
clf = RandomForestClassifier(
n_estimators=n_estimators, max_depth=max_depth,
random_state=0, n_jobs=-1,
)
clf.fit(X_train, y_train)
accuracy = accuracy_score(y_val, clf.predict(X_val))
start = time.perf_counter()
for _ in range(100):
clf.predict(X_val[:1])
latency_ms = (time.perf_counter() - start) * 10 # میانگین ms
return accuracy, latency_ms
study = optuna.create_study(
directions=["maximize", "minimize"], # دقت بالا، تأخیر کم
sampler=optuna.samplers.NSGAIISampler(seed=42),
)
study.optimize(objective, n_trials=80)
# استخراج جبههٔ پرتو
pareto_trials = study.best_trials
for t in pareto_trials[:5]:
print(f"دقت={t.values[0]:.4f} تأخیر={t.values[1]:.2f}ms params={t.params}")
در حالت چندهدفه، بهجای یک «بهترین» راهحل، مجموعهای از راهحلها دارید که هیچکدام از دیگری در همهٔ اهداف بهتر نیست، و تصمیم نهایی به کسبوکار برمیگردد. Optuna از الگوریتم NSGA-II استفاده میکند که استاندارد صنعتی برای بهینهسازی چندهدفه محسوب میشود. برای اطلاعات بیشتر و نمونههای پیشرفتهتر، مخزن Optuna در GitHub منابع فراوانی دارد.
پرسشهای متداول
آیا Optuna از GridSearchCV بهتر است؟
در اکثر موارد بله. اگر فضای جستوجو بیش از ۲۰ ترکیب دارد یا هر آزمایش بیش از چند ثانیه طول میکشد، Optuna معمولاً با تعداد آزمایش کمتر به نتیجهٔ بهتر میرسد. برای فضاهای بسیار کوچک (کمتر از ۱۰ ترکیب)، GridSearchCV هنوز سادهتر و کافی است.
Optuna از چه الگوریتمی برای پیشنهاد فراپارامتر استفاده میکند؟
الگوریتم پیشفرض TPE (Tree-structured Parzen Estimator) است. الگوریتمهای دیگری مانند CMA-ES برای فضاهای پیوستهٔ بزرگ، NSGA-II برای بهینهسازی چندهدفه، و GP-BO برای فضاهای کوچک نیز پشتیبانی میشوند.
آیا Optuna میتواند روی GPU اجرا شود؟
خود Optuna به GPU نیاز ندارد چون کار بهینهسازی سبک است. اما تابع هدف شما میتواند مدلهای PyTorch یا TensorFlow را روی GPU آموزش دهد و Optuna آزمایشهای موازی روی چند GPU را از طریق n_jobs یا پایگاه دادهٔ مشترک هماهنگ میکند.
چگونه یک مطالعهٔ Optuna را از سر بگیرم؟
اگر مطالعه را در RDBStorage ذخیره کردهاید، کافی است با optuna.create_study(study_name="...", storage="...", load_if_exists=True) آن را بارگذاری و study.optimize() را دوباره فراخوانی کنید. آزمایشهای قبلی حفظ و از همانجا ادامه داده میشود.
هرس (pruning) دقیقاً چه کاری انجام میدهد؟
هرس آزمایشهایی را که در گزارشهای میانی نشان میدهند احتمالاً بهتر از بهترین مقدار فعلی نخواهند شد، زودتر متوقف میکند. با MedianPruner یا HyperbandPruner، معمولاً ۳۰ تا ۷۰ درصد آزمایشها قبل از تکمیل هرس میشوند و زمان کل بهینهسازی بهشکل چشمگیری کاهش مییابد.
آیا Optuna برای شبکههای عصبی عمیق مناسب است؟
بله؛ Optuna ادغام رسمی با PyTorch، TensorFlow، PyTorch Lightning و Keras دارد. برای شبکههای عمیق حتماً از HyperbandPruner استفاده کنید تا آزمایشهای ضعیف بعد از چند epoch اول قطع شوند. این کار میتواند زمان تنظیم را از هفته به روز کاهش دهد.