چیتشیت
دیباگ آموزش شبکه عصبی
Debugging Neural Network Training
کشف باگهای خاموش آموزش از روی نشانه. بررسیها، آستانهها، قواعد مقداردهی، ترتیب برش و AMP، پروفایلرها.
فهرست مطالب
- نشانه، علت، اولین راهکار
- باگهای متداول PyTorch
- عدم کاهش تابع زیان: ترتیب بررسی
- توابع عیبیابی
- متصل کردن دیباگر با هوک به مدل
- آستانههای مورد استفاده در بررسیها
- مقداردهی اولیه بر اساس اکتیویشن
- ترتیب گامهای برش و مقیاسبندی
- یک گام آموزش AMP
- پروفایل و بازرسی
- اعدادی که باید زیر نظر داشت
- چکلیست قبل از آموزش کامل
نشانه، علت، اولین راهکار
عیبیابیگیر کردن تابع زیان روی -log(1/num_classes)
- علت
- مدل توزیع یکنواخت پیشبینی میکند
- راهحل
- پایپلاین داده و برچسبها را بررسی کنید
NaN شدن تابع زیان پس از چند گام
- علت
- نرخ یادگیری بیش از حد بالاست
- راهحل
- کاهش نرخ یادگیری به میزان 10x
NaN شدن بلافاصلهی تابع زیان
- علت
- log(0) یا تقسیم بر صفر
- راهحل
- افزودن اپسیلون به لگاریتم و تقسیم
نوسان شدید تابع زیان
- علت
- نرخ یادگیری بسیار بالا یا دسته بسیار کوچک
- راهحل
- کاهش نرخ یادگیری، افزایش اندازهی دسته
کاهش تابع زیان و سپس رسیدن به حالت یکنواخت
- علت
- نرخ یادگیری برای فاز فاینتیونینگ زیادی بالاست
- راهحل
- افزودن برنامهی کاهش کسینوسی یا پلهای
دقت آموزش بالا، دقت آزمون پایین
- علت
- بیشبرازش
- راهحل
- افزودن دراپاوت، کاهش وزن، دادهی بیشتر
دقت آموزش = دقت آزمون = شانس تصادفی
- علت
- مدل چیزی یاد نمیگیرد
- راهحل
- اجرای آزمون بیشبرازش روی یک بچ
صفر بودن همهی گرادیانها
- علت
- نورونهای مردهی ReLU یا گراف جداشده
- راهحل
- تغییر به LeakyReLU، بررسی
.requires_grad
باگهای متداول PyTorch
عیبیابینوسان تابع زیان، تجمع گرادیانها
- علت
- عدم استفاده از
optimizer.zero_grad() - راهحل
- فراخوانی آن قبل از
loss.backward()
تغییر دقت آزمون بین اجراهای مختلف
- علت
- عدم استفاده از
model.eval()حین آزمون - راهحل
- افزودن
model.eval()وtorch.no_grad()
نتایج نادرست بدون خطا
- علت
- برادکستینگ خاموش ابعاد
- راهحل
- چاپ ابعاد بعد از هر عملیات
RuntimeError: expected CUDA tensor- علت
- عدم تطابق CPU و GPU
- راهحل
- استفاده از
.to(device)هم برای مدل و هم داده
رشد نامحدود گراف، OOM
- علت
- تنسورها جدا (detach) نشدهاند
- راهحل
- استفاده از
.detach()یاwith torch.no_grad()
RuntimeError: modified by in-place operation- علت
- عملیات درجا مانع مشتقگیری خودکار میشود
- راهحل
- جایگزینی
x += 1باx = x + 1
گیر کردن تابع زیان در سطح شانس تصادفی
- علت
- ورودیها نرمالسازی نشدهاند
- راهحل
- نرمالسازی ورودیها به میانگین 0 و انحراف معیار 1
انتظار
Longدر آنتروپی متقاطع و دریافتFloat- علت
- dtype نامناسب برچسبها
- راهحل
- تغییر نوع برچسبها:
labels.long()
عدم کاهش تابع زیان: ترتیب بررسی
گامبهگام- نرخ یادگیریاگر زیاد باشد، تابع زیان نوسان کرده یا منفجر میشود. اگر کم باشد، بهسختی تغییر میکند.
- نرخ یادگیریسه نرخ با فاصلهی 10x را امتحان کنید؛ مثلاً 1e-2، 1e-3، 1e-4.
- گرادیانهاصفر بودن همه یعنی نورون مردهی ReLU یا محوشدگی گرادیان.
- گرادیانهاNaN یا Inf یعنی انفجار گرادیان.
- پایپلاین دادهبُر خوردن اشتباه برچسبها دقت را در حد شانس تصادفی نگه میدارد.
- پایپلاین دادهباگ پیشپردازش باعث یادگیری نویز توسط مدل میشود.
- معماریمدل بسیار کوچک دچار کمبرازش میشود؛ مدل بیش از حد عمیق سخت بهینهسازی میشود.
توابع عیبیابی
کار و کد| وظیفه | کد |
|---|---|
| ابعاد، dtype، دستگاه، و NaN بودن تنسور | debug_print(name, tensor) |
| نگاشت ابعاد تمام لایهها | check_shapes(model, sample_input) |
| یافتن گرادیانهای NaN یا Inf | detect_nan(model, loss, step) |
| یافتن تنسورهای روی دستگاه دیگر | check_devices(model, *tensors) |
| نُرم کلی، گرادیانهای بزرگ یا صفر | check_gradient_health(model) |
| اثبات توانایی یادگیری حلقه آموزش | overfit_one_batch(model, x_batch, y_batch, criterion) |
| پیمایش نرخ یادگیری | find_learning_rate(model, x_data, y_data, criterion) |
| مقایسه پسانتشار با تفاضل متناهی | gradient_check(model, x, y, criterion, eps=1e-4) |
متصل کردن دیباگر با هوک به مدل
کدpython
debugger1 = NetworkDebugger(model1) # هوک زدن به Linear، Conv2d، ReLU، LeakyReLU
debugger1.record_loss(loss.item()) # یک بار فراخوانی در هر گام
debugger1.print_report() # بررسیهای تابع زیان، اکتیویشن و گرادیان
debugger1.remove_hooks()
with torch.autograd.detect_anomaly(): # تشخیص توکار NaN و Inf
loss.backward()
آستانههای مورد استفاده در بررسیها
پیشفرضها| تنظیم | پیشفرض | یادداشت |
|---|---|---|
fraction_zero | > 0.5 | نورونهای مرده. استفاده از LeakyReLU یا نرخ یادگیری پایینتر |
Activation `std` | < 1e-6 | فروپاشی اکتیویشنها |
Gradient `abs_mean` | < 1e-7 | محوشدگی گرادیان در آن لایه |
Gradient `abs_mean` | > 100 | انفجار گرادیان در آن لایه |
First / last grad ratio | > 100 | محوشدگی گرادیان در عمق شبکه |
rel_diff | < 1e-5 | تأیید بررسی گرادیان. بالاتر از 1e-3 باگ است |
Overfit `final_loss` | > 0.1 | ناموفق: مدل یا حلقه آموزش خراب است |
start_lr | 1e-7 | پیمایش ابزار LR finder تا end_lr=10 |
مقداردهی اولیه بر اساس اکتیویشن
مفهومها- مقداردهی صفر
- همه نورونها یکسان میمانند. عملاً یک نورون مؤثر در هر پهنا.
- قاعده واریانس
- $\mathrm{Var}(z) = \text{fan}_{\text{in}} \cdot \mathrm{Var}(w) \cdot \mathrm{Var}(x)$
- Xavier
- $\mathrm{Var}(w) = 2/(\text{fan}_{\text{in}} + \text{fan}_{\text{out}})$ برای sigmoid و tanh.
- Kaiming
- $\mathrm{Var}(w) = 2/\text{fan}_{\text{in}}$ برای ReLU، Leaky ReLU، GELU و Swish.
- مقیاسبندی باقیمانده
- مقیاس لایههای باقیمانده با $1/\sqrt{2N}$ برای $N$ لایه.
- تصادفی N(0,1)
- تا لایه 5 منفجر میشود. N(0,0.01): تا لایه 10 محو میشود.
- بررسی
- بزرگی اکتیویشنها در تمام لایهها بین 0.5 و 2.0 باقی بماند.
- PyTorch
- مقدار پیشفرض
nn.Linear(512, 256)توزیع Kaiming uniform است.
ترتیب گامهای برش و مقیاسبندی
معمارییک گام آموزش AMP
کدpython
self.optimizer.zero_grad(set_to_none=True)
with torch.amp.autocast(device_type=self.device_type, dtype=self.amp_dtype):
loss = self._loss_fn(self.model(inputs), targets)
if not torch.isfinite(loss).all().item(): # رد شدن، بدون اجرای scaler.update()
...
self.scaler.scale(loss).backward()
self.scaler.unscale_(self.optimizer) # پیش از برش
if has_non_finite_grad(self.model.parameters()):
self.scaler.update() # نصف کردن مقیاس
pre_clip, post_clip = clip_global_l2_norm(list(self.model.parameters()), self.max_norm)
self.scaler.step(self.optimizer)
self.scaler.update() # در هر گام
scale = self.scaler.get_scale() # ثبت کنار lr و نُرم
پروفایل و بازرسی
دستورها| دستور | عملکرد |
|---|---|
python -m cProfile -s cumtime train.py | هر فراخوانی تابع، مرتبشده بر اساس زمان تجمعی |
kernprof -l -v train.py | زمان خطبهخط برای توابع @profile |
python -m memory_profiler your_script.py | مصرف حافظه خطبهخط |
tracemalloc.take_snapshot() | حافظه CPU خطبهخط بعد از tracemalloc.start() |
torch.cuda.memory_summary() | حافظه GPU تخصیصیافته و کششده |
tensorboard --logdir=runs | منحنیهای زیان، هیستوگرامهای وزن و گرادیان |
breakpoint() | توقف در pdb. بررسی با p outputs.shape، ادامه با c، خروج با q |
python3 code/main.py | دموی برش و AMP. حداقل یک ردیف SKIP است |
اعدادی که باید زیر نظر داشت
پرسش و پاسخ- رفتار طبیعی مقیاس GradScaler چگونه است؟
- با توانهای 2 تا حدود
2^17یا2^18بالا میرود. - چه زمانی نرخ رد کردن هشداردهنده است؟
- بالای 5 درصد در یک پنجره متحرک 1000 گامی.
- مقیاس اصلاً تغییر نمیکند؟
- مسیر رد شدن
scaler.update()را فراموش کرده است. - بارگذاری داده 60٪ زمان را میگیرد؟
- مقدار
num_workers > 0را در DataLoader تنظیم کنید. - خطای کمبود حافظه (OOM)؟
- ابتدا اندازهی دسته را کم کنید. سپس از
torch.cuda.empty_cache()استفاده کنید. - دقت آزمون 99٪ است؟
- نشت داده را بررسی کنید. اول تفکیک داده، سپس پیشپردازش.
- کاهش زیان آموزش همزمان با افزایش زیان اعتبارسنجی؟
- مدل دچار بیشبرازش شده است.
چکلیست قبل از آموزش کامل
نکتهها- اجرای آزمون بیشبرازش روی یک بچ؛ در صورت عدم موفقیت، متوقف شوید.
- چاپ خلاصهی مدل؛ بررسی تعداد پارامترها.
- اجرای یک گذر رو به جلو با داده تصادفی؛ بررسی ابعاد خروجی.
- آموزش برای 5 دوره (epoch)؛ اطمینان از کاهش تابع زیان.
- بررسی آمارههای اکتیویشن و جریان گرادیان.
- چاپ 5 نمونه تصادفی همراه با برچسبها.
- ثبت لاگ تابع زیان، نرخ یادگیری و نُرم گرادیان حین آموزش.