فاز 9 · درس 8

آموزش الگوریتم PPO: بهینه‌سازی خط‌مشی نزدیک

نوع
ساختنی
زمان
حدود 75 دقیقه
زبان‌ها
Python
پیش‌نیاز
فاز 9 · درس 06 (REINFORCE)، فاز 9 · درس 07 (Actor-Critic)
نمودار هندسی برش نسبت با بازه‌های مشخص و مرزهای مسطح بالا و پایین
فهرست مطالب
  1. صورت مسئله
  2. مفاهیم کلیدی
  3. پیاده‌سازی
  4. گام 1: ثبت log π_old(a | s) در زمان جمع‌آوری داده‌ها
  5. گام 2: محاسبه‌ی مزیت‌های GAE (درس 07)
  6. گام 3: به‌روزرسانی با تابع جانشین برش‌خورده
  7. گام 4: ارزش و آنتروپی
  8. گام 5: ابزارهای تشخیصی
  9. دام‌های رایج
  10. کاربردهای واقعی
  11. بسته‌بندی نهایی
  12. تمرین‌ها
  13. اصطلاحات کلیدی
  14. برای مطالعه‌ی بیشتر

در الگوریتم‌های سنتی مبتنی بر گرادیان خط‌مشی (Policy Gradient) مانند A2C، هر دسته از داده‌های جمع‌آوری‌شده از تعامل با محیط (Rollout) بلافاصله پس از یک گام به‌روزرسانی دور ریخته می‌شود. از آنجا که تعامل با محیط‌های شبیه‌سازی یا تولید پاسخ در مدل‌های زبانی فرآیندی پرهزینه و زمان‌بر است، هدر رفتن این داده‌ها بازدهی نمونه‌ها را به شدت پایین می‌آورد و منابع پردازشی زیادی مصرف می‌کند.

الگوریتم بهینه‌سازی خط‌مشی نزدیک (Proximal Policy Optimization یا PPO) این مشکل اساسی را با ایده‌ای هوشمندانه حل می‌کند: تعریف یک تابع هدف برش‌خورده (Clipped Surrogate Objective) که از تغییرات ناگهانی و انفجار خط‌مشی جلوگیری می‌کند. با این ترفند، می‌توانید چندین دوره‌ی آموزشی (Epoch) را روی داده‌های یکسان اجرا کنید، بدون اینکه پایداری آموزش به خطر بیفتد.

امروزه PPO استاندارد اصلی یادگیری تقویتی در رباتیک، بازی‌های پیچیده و پایپ‌لاین‌های هم‌ترازی مدل‌های زبانی با بازخورد انسانی (RLHF) است. پیاده‌سازی‌های پایه و آزمایش‌های این درس روی محیط‌های ساده با پردازنده‌های معمولی (CPU) یا شتاب‌دهنده‌های سیستم‌های شخصی قابل اجرا هستند و نیازی به زیرساخت‌های ابری سنگین ندارند.

الگوریتم A2C داده‌های حاصل از اجرای هر دور (rollout) را پس از یک بار به‌روزرسانی دور می‌ریزد. در مقابل، PPO گرادیان خط‌مشی (policy gradient) را داخل یک نسبت اهمیت برش‌خورده (clipped importance ratio) قرار می‌دهد تا بتوانید بدون انفجار خط‌مشی، بیش از 10 دوره‌ی آموزشی (epoch) را روی همان داده‌ها اجرا کنید؛ شولمن و همکاران (2017). این روش در سال 2026 همچنان الگوریتم پیش‌فرض گرادیان خط‌مشی به شمار می‌رود.

صورت مسئله

الگوریتم A2C (درس 07) یک روش درون‌خط‌مشی (on-policy) است: گرادیان Eπθ[A⋅∇log⁡πθ]\mathbb E_{\pi_\theta}[A \cdot \nabla \log \pi_\theta] به داده‌هایی نیاز دارد که از خط‌مشی فعلی، یعنی πθ\pi_\theta نمونه‌برداری شده باشند. با یک بار به‌روزرسانی، πθ\pi_\theta تغییر می‌کند و داده‌هایی که استفاده کرده‌اید، برون‌خط‌مشی (off-policy) می‌شوند. اگر دوباره از آن‌ها استفاده کنید، گرادیان شما اریب خواهد شد.

جمع‌آوری داده‌ها هزینه دارد. در محیط‌های Atari، اجرای یک دور در 8 محیط موازی × 128 گام، برابر با 1024 گذار و ده‌ها ثانیه زمان در محیط بازی است. دور ریختن این همه داده فقط پس از یک گام گرادیان، به شدت اسراف‌آمیز است.

بهینه‌سازی خط‌مشی ناحیه اعتماد (TRPO، شولمن 2015) اولین راه‌حل این مشکل بود: هر به‌روزرسانی را طوری محدود کنید که واگرایی کولبک-لایبلر (KL Divergence) بین خط‌مشی قدیم و جدید زیر δ\delta بماند. از نظر تئوری ساختار تمیزی دارد، اما برای هر به‌روزرسانی نیاز به حل گرادیان مزدوج (conjugate gradient) دارد. در سال 2026 دیگر هیچ‌کس TRPO اجرا نمی‌کند.

الگوریتم PPO (شولمن و همکاران، 2017) آن قید سختِ ناحیه‌ی اعتماد را با یک تابع هدف برش‌خورده‌ی ساده جایگزین می‌کند؛ تنها با یک خط کد بیشتر، ده دوره‌ی آموزشی به ازای هر سری داده، بدون نیاز به گرادیان مزدوج و با تضمین‌های تئوری کاملاً رضایت‌بخش. بعد از گذشت نه سال، این روش هنوز هم الگوریتم پیش‌فرض گرادیان خط‌مشی برای هر کاری، از MuJoCo گرفته تا RLHF است.

مفاهیم کلیدی

تابع هدف جانشین و برش‌خورده‌ی PPO: برش نسبت در بازه‌ی 1 ± ε

نسبت اهمیت.

rt(θ)=πθ(at∣st)πθold(at∣st)r_t(\theta) = \frac{\pi_\theta(a_t \mid s_t)}{\pi_{\theta_{\text{old}}}(a_t \mid s_t)}

این عبارت، نسبت درست‌نمایی (likelihood ratio) خط‌مشی جدید به خط‌مشی جمع‌آوری‌کننده‌ی داده‌ها است. مقدار rt=1r_t = 1 یعنی هیچ تغییری رخ نداده است. مقدار rt=2r_t = 2 یعنی احتمال انتخاب عمل ata_t در خط‌مشی جدید، دو برابر خط‌مشی قبلی است.

تابع هدف جانشینِ برش‌خورده.

LCLIP(θ)=Et[min⁡(rt(θ)At,clip(rt(θ),1−ϵ,1+ϵ)At)]L^{\text{CLIP}}(\theta) = \mathbb{E}_t \left[ \min\left( r_t(\theta) A_t, \mathop{\mathrm{clip}}(r_t(\theta), 1-\epsilon, 1+\epsilon) A_t \right) \right]

این فرمول دو بخش دارد:

  • اگر مزیت مثبت باشد (At>0A_t > 0) و نسبت بخواهد از 1+ϵ1 + \epsilon فراتر رود، برش (clip) گرادیان را صاف و صفر می‌کند؛ یعنی احتمال انجام یک عمل خوب را بیش از +ϵ+\epsilon بالاتر از احتمال قبلی‌اش نبرید.
  • اگر مزیت منفی باشد (At<0A_t < 0) و نسبت بخواهد به زیر 1−ϵ1 - \epsilon سقوط کند (یعنی بخواهیم احتمال یک عمل بد را بسیار کمتر از خط‌مشی قبلی کنیم)، برش گرادیان را صاف می‌کند: احتمال یک عمل بد را بیش از −ϵ-\epsilon پایین‌تر از احتمال قبلی‌اش نبرید.

عملگر min جهت دیگر را کنترل می‌کند: اگر تغییرات نسبت در جهتی سودمند حرکت کرده باشد، همچنان گرادیان را دریافت می‌کنید (هیچ برشی در سمتی که به نفع شما باشد اعمال نمی‌شود).

معمولاً مقدار ϵ=0.2\epsilon = 0.2 در نظر گرفته می‌شود. اگر نمودار تابع هدف را برحسب rtr_t رسم کنید، یک تابع خطی تکه‌تکه با یک سقف مسطح در «سمت خوب» و یک کف مسطح در «سمت بد» خواهید داشت.

تابع زیان (loss) کامل PPO.

L(θ,ϕ)=LCLIP(θ)−cv⋅(Vϕ(st)−Vttarget)2+ce⋅H(πθ(⋅∣st))L(\theta, \phi) = L^{\text{CLIP}}(\theta) - c_v \cdot (V_\phi(s_t) - V_t^{\text{target}})^2 + c_e \cdot H(\pi_\theta(\cdot \mid s_t))

همان ساختار اکتور-کریتیکِ درس A2C در اینجا نیز برقرار است؛ با سه ضریب که معمولاً مقادیر آن‌ها cv=0.5c_v = 0.5، ce=0.01c_e = 0.01 و ϵ=0.2\epsilon = 0.2 است.

حلقه‌ی آموزش.

  1. جمع‌آوری N×TN \times T گذار در NN محیط موازی، هر کدام به اندازه‌ی TT گام.
  2. محاسبه‌ی مزیت‌ها (GAE) و فریز کردن آن‌ها به عنوان مقادیر ثابت.
  3. فریز کردن πθold\pi_{\theta_{\text{old}}} به عنوان یک اسنپ‌شات (snapshot) از πθ\pi_\theta فعلی.
  4. به ازای KK دوره‌ی آموزشی، برای هر مینی‌بچ از (s, a, A, V_target, log π_old(a|s)):
    • محاسبه‌ی rt(θ)=exp⁡(log⁡πθ(a∣s)−log⁡πold(a∣s))r_t(\theta) = \exp(\log \pi_\theta(a \mid s) - \log \pi_{\text{old}}(a \mid s)).
    • اِعمال LCLIPL^{\text{CLIP}} + تابع زیانِ ارزش + آنتروپی.
    • گام گرادیان.
  5. دور ریختن داده‌های این دور و بازگشت به گام 1.

مقدار K=10K = 10 به همراه مینی‌بچ‌های 64تایی، اَبَرپارامترهای استاندارد این روش هستند. الگوریتم PPO مقاومت بالایی دارد و تغییر جزئی این اعداد در بازه‌ی 50± درصد به‌ندرت اثری جدی می‌گذارد.

نسخه‌ی جریمه‌ی KL. مقاله‌ی اصلی روش دیگری هم با استفاده از جریمه‌ی تطبیقی KL پیشنهاد داد: L=LPG−β⋅KL(πθ∥πold)L = L^{\text{PG}} - \beta \cdot \mathrm{KL}(\pi_\theta \Vert \pi_{\text{old}}) که در آن β\beta بر اساس مقدار مشاهده‌شده‌ی KL تنظیم می‌شود. در عمل نسخه‌ی مبتنی بر برش به گزینه‌ی غالب تبدیل شد؛ اما نسخه‌ی KL همچنان در RLHF زنده است (جایی که مهار انحراف KL نسبت به مدل مرجع، در هر صورت یک قید پیش‌فرض و همیشگی است).

برش PPO (PPO CLIP)ضریب مزیت (advantage) و اپسیلون را بکشید
PPO CLIPPPO multiplies the advantage by the probability ratio r between the new and old policy. Grey is the raw product r·A; blue is the clipped surrogate. Once the ratio leaves the band 1-eps to 1+eps in the helpful direction, the clip flattens the objective so its gradient vanishes, keeping each update from moving the policy too far from the one that collected the data.
الگوریتم PPO مقدار مزیت (advantage) را در نسبت احتمال r میان خط‌مشی جدید و قدیم ضرب می‌کند. خط خاکستری حاصل‌ضرب خام r·A است؛ خط آبی تابع جانشین برش‌خورده (clipped surrogate) را نشان می‌دهد. به محض اینکه نسبت احتمال در جهت مثبت از بازه 1-eps تا 1+eps فراتر رود، عملیات برش تابع هدف را مسطح می‌کند تا گرادیان آن صفر شود؛ این کار مانع از آن می‌شود که هر به‌روزرسانی خط‌مشی، فاصله زیادی از سیاستی که داده‌ها را جمع‌آوری کرده بگیرد.

پیاده‌سازی

گام 1: ثبت log π_old(a | s) در زمان جمع‌آوری داده‌ها

for step in range(T):
    probs = softmax(logits(theta, state_features(s)))
    a = sample(probs, rng)
    s_next, r, done = env.step(s, a)
    buffer.append({
        "s": s, "a": a, "r": r, "done": done,
        "v_old": value(w, state_features(s)),
        "log_pi_old": log(probs[a] + 1e-12),
    })
    s = s_next

این اسنپ‌شات فقط یک بار در زمان جمع‌آوری داده‌ها ثبت می‌شود و در طول دوره‌های آموزشیِ به‌روزرسانی تغییری نمی‌کند.

گام 2: محاسبه‌ی مزیت‌های GAE (درس 07)

دقیقاً مشابه A2C عمل کرده و مقادیر را در کل بچ نرمال‌سازی کنید.

گام 3: به‌روزرسانی با تابع جانشین برش‌خورده

for _ in range(K_EPOCHS):
    for mb in minibatches(buffer, size=64):
        for rec in mb:
            x = state_features(rec["s"])
            probs = softmax(logits(theta, x))
            logp = log(probs[rec["a"]] + 1e-12)
            ratio = exp(logp - rec["log_pi_old"])
            adv = rec["advantage"]
            surrogate = min(
                ratio * adv,
                clamp(ratio, 1 - EPS, 1 + EPS) * adv,
            )
            # پس‌انتشار surrogate-، افزودن تابع زیان (loss) مقدار، کسر انتروپی
            grad_logpi = onehot(rec["a"]) - probs
            if (adv > 0 and ratio >= 1 + EPS) or (adv < 0 and ratio <= 1 - EPS):
                pg_grad = 0.0  # clipped
            else:
                pg_grad = ratio * adv
            for i in range(N_ACTIONS):
                for j in range(N_FEAT):
                    theta[i][j] += LR * pg_grad * grad_logpi[i] * x[j]

الگوی «برش → صفر شدن گرادیان» قلب تپنده‌ی PPO است. اگر خط‌مشی جدید بیش از حد در جهت سودمند حرکت کرده باشد، به‌روزرسانی متوقف می‌شود.

گام 4: ارزش و آنتروپی

مانند A2C، خطای استاندارد میانگین مربعات (MSE) را به هدف بخش نقاد (critic) اضافه کرده و یک ضریب تشویقی آنتروپی هم به بخش عملگر (actor) بدهید.

گام 5: ابزارهای تشخیصی

در هر به‌روزرسانی، این سه معیار را زیر نظر بگیرید:

  • میانگین KL E[log⁡πold−log⁡πθ]\mathbb{E}[\log \pi_{\text{old}} - \log \pi_\theta]: باید در بازه‌ی [0, 0.02] بماند. اگر از 0.1 فراتر رفت، K_EPOCHS یا LR را کم کنید.
  • کسر برش (Clip fraction) — سهم نمونه‌هایی که نسبت آن‌ها بیرون از [1−ϵ,1+ϵ][1-\epsilon, 1+\epsilon] قرار می‌گیرد: این عدد باید حدود 0.1-0.3 باشد. اگر نزدیک به 0 باشد، یعنی برش اصلاً فعال نمی‌شود؛ پس LR یا K_EPOCHS را افزایش دهید. اگر به بالای 0.5 رسید، نشانه‌ی بیش‌برازش روی داده‌های فعلی است و باید آن‌ها را کاهش دهید.
  • واریانس تبیین‌شده (Explained variance) 1−Var(Vtarget−Vpred)/Var(Vtarget)1 - \text{Var}(V_{\text{target}} - V_{\text{pred}}) / \text{Var}(V_{\text{target}}): معیاری برای کیفیت بخش نقاد است که همگام با یادگیری آن، باید به سمت عدد 1 بالا برود.

دام‌های رایج

  • تنظیم نبودن ضریب برش. مقدار ϵ=0.2\epsilon = 0.2 استانداردِ جاافتاده‌ی این حوزه است. کاهش آن به 0.1 آپدیت‌ها را بیش از حد محتاطانه می‌کند و افزایش آن به بالای 0.3 ناپایداری به بار می‌آورد.
  • دوره‌های آموزشی بیش از حد. مقدار K>20K > 20 معمولاً سیستم را ناپایدار می‌کند، چون خط‌مشی فاصله‌ی زیادی از πold\pi_{\text{old}} می‌گیرد. تعداد دوره‌ها را به‌خصوص برای شبکه‌های بزرگ، محدود نگه دارید.
  • عدم نرمال‌سازی پاداش‌ها. مقیاس‌های بزرگ پاداش، بازه‌ی برش را بی‌اثر می‌کنند. پیش از محاسبه‌ی مزیت‌ها، پاداش‌ها را با انحراف معیار متحرک (running std) نرمال‌سازی کنید.
  • فراموش کردن نرمال‌سازی مزیت. استانداردِ کار این است که در هر بچ، میانگین را صفر و انحراف معیار را یک کنید. نادیده گرفتن این مرحله PPO را در اکثر بنچمارک‌ها از کار می‌اندازد.
  • کاهش ندادن نرخ یادگیری (Learning rate). کاهش خطی نرخ یادگیری تا صفر به بهبود عملکرد PPO کمک می‌کند؛ ثابت نگه داشتن آن اغلب عملکرد ضعیف‌تری رقم می‌زند.
  • خطاهای محاسباتی در نسبت اهمیت. برای حفظ پایداری عددی (numerical stability)، همیشه از فرمول exp(log_new - log_old) استفاده کنید، نه new / old.
  • علامت اشتباه گرادیان. بیشینه‌سازی تابع هدف جانشین به معنی کمینه‌سازی −LCLIP-L^{\text{CLIP}} است. وارونه زدن این علامت، رایج‌ترین باگ پیاده‌سازی PPO به شمار می‌رود.

کاربردهای واقعی

در سال 2026، الگوریتم PPO انتخاب پیش‌فرض یادگیری تقویتی در گستره‌ی شگفت‌انگیزی از حوزه‌ها است:

کاربرد گونه‌ی PPO
MuJoCo / کنترل رباتیک PPO با خط‌مشی گاوسی و GAE(0.95)
Atari / بازی‌های گسسته PPO با خط‌مشی طبقه‌بندی‌شده (categorical) و دورهای 128گامی پیوسته
آموزش RLHF برای مدل‌های زبانی بزرگ PPO با جریمه‌ی KL نسبت به مدل مرجع، و محاسبه‌ی پاداش از مدل پاداش در انتهای پاسخ
ایجنت‌های بازی در مقیاس بزرگ ترکیب IMPALA + PPO (مانند AlphaStar و OpenAI Five)
مدل‌های زبانی استدلال‌گر GRPO (درس 12) — گونه‌ای از PPO بدون شبکه‌ی نقاد
داده‌های صرفاً ترجیحی DPO — خلاصه‌سازی فرم بسته (closed-form) از PPO+KL بدون نمونه‌برداری آنلاین

ساختار تابع زیان (loss) در PPO — یعنی تابع جانشین برش‌خورده + ارزش + آنتروپی — اسکلت اصلی DPO، GRPO و تقریباً تمام پایپ‌لاین‌های (pipeline) RLHF است.

بسته‌بندی نهایی

این کد را با نام outputs/skill-ppo-trainer.md ذخیره کنید:

---
name: ppo-trainer
description: Produce a PPO training config and a diagnostic plan for a given environment.
version: 1.0.0
phase: 9
lesson: 8
tags: [rl, ppo, policy-gradient]
---

Given an environment and training budget, output:

1. Rollout size. `N` envs × `T` steps.
2. Update schedule. `K` epochs, minibatch size, LR schedule.
3. Surrogate params. `ε` (clip), `c_v`, `c_e`, advantage normalization on.
4. Advantage. GAE(`λ`) with explicit `γ` and `λ`.
5. Diagnostics plan. KL, clip fraction, explained variance thresholds with alerts.

Refuse `K > 30` or `ε > 0.3` (unsafe trust region). Refuse any PPO run without advantage normalization or KL/clip monitoring. Flag clip fraction sustained above 0.4 as drift.

تمرین‌ها

  1. ساده. الگوریتم PPO را روی محیط 4×4 GridWorld با ϵ=0.2,K=4\epsilon=0.2, K=4 اجرا کنید. بازدهی نمونه‌ها (sample efficiency) را در گام‌های محیطی یکسان با A2C (که در هر دور یک دوره‌ی آموزشی دارد) مقایسه کنید.
  2. متوسط. مقادیر مختلف K∈{1,4,10,30}K \in \lbrace 1, 4, 10, 30 \rbrace را ارزیابی کنید. نمودار بازدهی برحسب گام‌های محیطی را رسم کرده و میانگین KL را در هر به‌روزرسانی ردیابی کنید. مقدار KL در این مسئله در چه نقطه‌ای از KK دچار انفجار می‌شود؟
  3. سخت. تابع جانشین برش‌خورده را با جریمه‌ی تطبیقی KL جایگزین کنید (اگر KL>2⋅target\mathrm{KL} > 2 \cdot \text{target} بود، β\beta دو برابر شود و اگر KL<target/2\mathrm{KL} < \text{target}/2 بود، نصف شود). بازدهی نهایی، پایداری و روند آموزش بدون برش را مقایسه کنید.

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

اصطلاح در گفتار عامیانه معنای دقیق فنی
نسبت اهمیت (Importance ratio) «r_t(θ)» مقدار πθ(a∣s)/πold(a∣s)\pi_\theta(a \mid s) / \pi_{\text{old}}(a \mid s)؛ میزان انحراف از خط‌مشی جمع‌آوری‌کننده‌ی داده‌ها.
تابع جانشین برش‌خورده (Clipped surrogate) «ترفند اصلی PPO» فرمول min⁡(r⋅A,clip(r,1−ϵ,1+ϵ)⋅A)\min(r \cdot A, \mathop{\mathrm{clip}}(r, 1-\epsilon, 1+\epsilon) \cdot A)؛ صفر شدن گرادیان بعد از مرز برش در جهت سودمند.
ناحیه اعتماد (Trust region) «نیت TRPO / PPO» محدود کردن مقدار KL در هر به‌روزرسانی برای تضمین بهبود یکنواخت.
جریمه‌ی KL (KL penalty) «ناحیه اعتماد نرم» گونه‌ی دیگر PPO با فرمول L−β⋅KL(πθ∥πold)L - \beta \cdot \mathrm{KL}(\pi_\theta \Vert \pi_{\text{old}}) به همراه β\beta تطبیقی.
کسر برش (Clip fraction) «میزان فعال شدن برش» یک ابزار تشخیصی که باید بین 0.1 تا 0.3 باشد؛ خارج از این بازه یعنی تنظیمات اشتباه است.
آموزش چند دوره‌ای (Multi-epoch training) «استفاده‌ی دوباره از داده‌ها» اجرای K دوره‌ی آموزشی روی هر دور داده؛ تبادل واریانس بیشتر در ازای بازدهی بالاتر نمونه‌ها.
شبه درون‌خط‌مشی (On-policy-ish) «تقریباً درون‌خط‌مشی» PPO اسماً روشی درون‌خط‌مشی است، اما اجرای دوره‌های K>1 با امنیت بالا از داده‌های اندکی برون‌خط‌مشی بهره می‌گیرد.
PPO-KL «آن یکی PPO» گونه‌ی مبتنی بر جریمه‌ی KL؛ کاربردی در RLHF که انحراف KL نسبت به مدل مرجع در هر صورت یک قید لازم است.

برای مطالعه‌ی بیشتر

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

چرا در الگوریتم PPO از تابع هدف برش‌خورده (Clipped Objective) استفاده می‌شود؟

برش نسبت اهمیت (Importance Ratio) مانع از به‌روزرسانی‌های بیش‌از‌حد بزرگ در خط‌مشی می‌شود و با صفر کردن گرادیان در صورت تغییرات فراتر از بازه مجاز، پایداری آموزش را حفظ می‌کند.

تفاوت اصلی الگوریتم PPO با روش‌های قدیمی‌تر مثل TRPO چیست؟

الگوریتم TRPO برای رعایت قید ناحیه اعتماد نیازمند محاسبات سنگین گرادیان مزدوج (Conjugate Gradient) بود، در حالی که PPO با یک برش ساده در تابع هدف به همان پایداری بدون محاسبات سنگین دست می‌یابد.

شاخص کسر برش (Clip Fraction) در آموزش PPO چه کاربردی دارد؟

کسر برش سهم نمونه‌هایی را نشان می‌دهد که از بازه برش فراتر رفته‌اند. مقدار نرمال آن بین 0.1 تا 0.3 است؛ مقادیر نزدیک به صفر نشانه عدم کارایی برش و مقادیر بالای 0.5 نشانه بیش‌برازش روی داده‌ها هستند.

ما رو توی گوگل بیشتر ببین

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