محاسبه خطای شبکه با ضرر
با یک مدل که به طور تصادفی مقداردهی اولیه شده است، یا حتی مدلی که با رویکردهای پیچیده تر مقداردهی اولیه شده است، هدف ما آموزش یا آموزش یک مدل در طول زمان است. برای آموزش یک مدل، وزن ها و سوگیری ها را تغییر می دهیم تا دقت و اطمینان مدل را بهبود بخشیم. برای این کار محاسبه می کنیم که مدل چقدر خطا دارد. تابع ضرر که به آن تابع هزینه نیز گفته می شود، الگوریتمی است که میزان اشتباه بودن یک مدل را تعیین می کند. ضرر معیار این معیار است. از آنجایی که ضرر خطای مدل است، ما در حالت ایده آل می خواهیم 0 باشد.
ممکن است تعجب کنید که چرا ما خطای یک مدل را بر اساس دقت argmax محاسبه نمی کنیم.
مثال قبلی ما از اعتماد به نفس را به یاد بیاورید: [0.22، 0.6، 0.18] در مقابل [0.32، 0.36، 0.32]. اگر کلاس صحیح در واقع کلاس میانی (شاخص 1) باشد، دقت مدل بین دو مورد بالا یکسان خواهد بود. اما آیا این دو مثال واقعا به اندازه یکدیگر دقیق هستند؟ آنها اینطور نیستند، زیرا دقت به سادگی اعمال یک argmax به خروجی برای یافتن شاخص بزرگترین مقدار است. خروجی یک شبکه عصبی در واقع اعتماد به نفس است و اطمینان بیشتر به پاسخ صحیح بهتر است. به همین دلیل، ما در تلاش هستیم تا اعتماد به نفس صحیح را افزایش دهیم و اعتماد به نفس نابجا را کاهش دهیم.
از دست دادن آنتروپی متقاطع طبقه بندی شده
اگر با رگرسیون خطی آشنا هستید، یکی از توابع ضرر مورد استفاده در شبکه های عصبی که رگرسیون را انجام می دهند می دانید: خطای مربع (یا میانگین مربعات خطا با شبکه های عصبی).
ما در این مثال رگرسیون را انجام نمی دهیم؛ ما در حال طبقه بندی هستیم، بنابراین به یک تابع ضرر متفاوت نیاز داریم. این مدل دارای یک تابع فعال سازی softmax برای لایه خروجی است، به این معنی که یک توزیع احتمال را خروجی می دهد. آنتروپی متقابل طبقه بندی شده است که به صراحت برای مقایسه احتمال “حقیقت زمینی” استفاده می شود (Y یا “اهداف“) و برخی از توزیع پیش بینی شده (y – کلاه یا “پیش بینی“), بنابراین آن را حس می کند به استفاده از آنتروپی متقابل در اینجا. همچنین یکی از رایج ترین توابع از دست دادن با فعال سازی softmax در لایه خروجی است.
فرمول محاسبه آنتروپی متقاطع طبقه ای y (توزیع واقعی/مطلوب) و y-hat (توزیع پیش بینی شده) به شرح زیر است:
جایی که Li مقدار از دست دادن نمونه را نشان می دهد، i نمونه i-th در مجموعه، j شاخص برچسب/خروجی، y مقادیر هدف و y-hat مقادیر پیش بینی شده را نشان می دهد.
هنگامی که شروع به کدنویسی راه حل کردیم، آن را ساده تر می کنیم تا فرمول آن این است: –log(correct_class_confidence)
جایی که Li مقدار از دست دادن نمونه را نشان می دهد، i نمونه i-th در یک مجموعه، k شاخص برچسب هدف (برچسب زمینی واقعی)، y مقادیر هدف را نشان می دهد و y-hat مقادیر پیش بینی شده را نشان می دهد.
ممکن است بپرسید که چرا ما این را آنتروپی متقابل می نامیم و نه از دست دادن ورود به سیستم، که آن هم نوعی از دست دادن است. اگر نمی دانید از دست دادن ورود به سیستم چیست، ممکن است تعجب کنید که چرا چنین فرمول فانتزی برای آنچه به نظر می رسد یک توصیف نسبتا ابتدایی است، وجود دارد.
به طور کلی، تابع خطای از دست دادن لگاریتم همان چیزی است که ما برای خروجی یک مدل رگرسیون لجستیک باینری اعمال می کنیم (که در فصل 16 توضیح خواهیم داد) – تنها دو کلاس در توزیع وجود دارد که هر کدام از آنها برای یک خروجی واحد (نورون) اعمال می شوند که به عنوان 0 یا 1 هدف قرار می گیرد. در مورد ما، ما یک مدل طبقه بندی داریم که توزیع احتمال را بر روی تمام خروجی ها برمی گرداند. آنتروپی متقابل دو توزیع احتمال را مقایسه می کند. در مورد ما، ما یک خروجی softmax داریم، فرض کنید که:
softmax_output = [0.7, 0.1, 0.2]
قصد داریم این را با کدام توزیع احتمال مقایسه کنیم؟ ما 3 اطمینان کلاس در خروجی بالا داریم و فرض کنیم که پیش بینی مورد نظر کلاس اول است (شاخص 0 که در حال حاضر 0.7 است). اگر این پیش بینی مورد نظر باشد، توزیع احتمال مورد نظر [1، 0، 0] است. آنتروپی متقاطع همچنین می تواند روی توزیع های احتمالاتی مانند [0.2، 0.5، 0.3] کار کند؛ لازم نیست مانند آنچه در بالا به نظر می رسد. گفته می شود، احتمالات مورد نظر شامل 1 در کلاس مورد نظر و 0 در کلاس های نامطلوب باقی مانده خواهد بود. آرایه ها یا بردارهایی مانند این one-hot نامیده می شوند، به این معنی که یکی از مقادیر “داغ” (روشن) است، با مقدار 1 و بقیه “سرد” (خاموش) با مقادیر 0 هستند. هنگام مقایسه نتایج مدل با یک بردار تک داغ با استفاده از آنتروپی متقاطع، سایر قسمت های معادله صفر می شوند و از دست دادن لگاریتم احتمال هدف در 1 ضرب می شود و محاسبه آنتروپی متقاطع را نسبتا ساده می کند. این نیز یک مورد خاص از محاسبه آنتروپی متقابل است که آنتروپی متقابل طبقه ای نامیده می شود. برای مثال این – اگر خروجی softmax [0.7، 0.1، 0.2] و تارگت های [1، 0، 0] را در نظر بگیریم، می توانیم محاسبات را به صورت زیر اعمال کنیم:
بیایید کد پایتون را برای این کار ببینیم:
import math
# An example output from the output layer of the neural network
softmax_output = [0.7, 0.1, 0.2]
# Ground truth
target_output = [1, 0, 0]
loss = -(math.log(softmax_output[0])*target_output[0] +
math.log(softmax_output[1])*target_output[1] +
math.log(softmax_output[2])*target_output[2])
print(loss)
>>>
0.35667494393873245
این محاسبه کامل آنتروپی متقاطع طبقه بندی شده است، اما با توجه به بردارهای هدف یک داغ می توانیم چند فرض داشته باشیم. اول، مقادیر target_output[1] و target_output[2] در این مورد چیست؟ هر دو 0 هستند و هر چیزی که در 0 ضرب شود 0 است. بنابراین، نیازی به محاسبه این شاخص ها نداریم. در مرحله بعد، ارزش target_output[0] در این مورد چقدر است؟ این 1 است. بنابراین می توان این را حذف کرد زیرا هر عددی که در 1 ضرب شود ثابت می ماند. همان خروجی را می توان با موارد زیر محاسبه کرد:
loss = -math.log(softmax_output[0])
که هنوز به ما می دهد:
>>>
0.35667494393873245
همانطور که می بینید با اهداف برداری تک داغ یا مقادیر اسکالر که آنها را نشان می دهد، می توانیم فرضیات ساده تری داشته باشیم و از یک محاسبه ابتدایی تر استفاده کنیم – آنچه زمانی یک فرمول درگیر بود به لگاریتم منفی امتیاز اطمینان کلاس هدف کاهش می یابد – فرمول دوم ارائه شده در ابتدای این فصل.
همانطور که قبلا بحث کردیم، سطح اطمینان مثال ممکن است مانند [0.22، 0.6، 0.18] یا [0.32، 0.36، 0.32] باشد. در هر دو مورد، argmax این بردارها کلاس دوم را به عنوان پیش بینی برمی گرداند، اما اطمینان مدل در مورد این پیش بینی ها فقط برای یکی از آنها زیاد است. از دست دادن آنتروپی متقابل طبقه بندی شده آن را به حساب می آورد و هر چه اطمینان کمتر باشد، ضرر بیشتری را تولید می کند:
import math
print(math.log(1.))
print(math.log(0.95))
print(math.log(0.9))
print(math.log(0.8))
print(‘…’)
print(math.log(0.2))
print(math.log(0.1))
print(math.log(0.05))
print(math.log(0.01))
>>>
0.0
-0.05129329438755058
-0.10536051565782628
-0.2231435513142097
…
-1.6094379124341003
-2.3025850929940455
-2.995732273553991
-4.605170185988091
ما مقادیر ورود به سیستم های مختلف را برای چند مثال اطمینان چاپ کرده ایم. هنگامی که سطح اطمینان برابر با 1 باشد، به این معنی که مدل 100٪ در مورد پیش بینی خود “مطمئن” است، مقدار ضرر برای این نمونه برابر با 0 است. مقدار ضرر با سطح اطمینان افزایش می یابد و به 0 نزدیک می شود. همچنین ممکن است تعجب کنید که چرا ما نتیجهlog (0) را چاپ نکردیم – به زودی توضیح خواهیم داد.
تا کنون، ما() log را برای خروجی softmax اعمال کرده ایم، اما نه توضیح داده ایم که “log” چیست و نه چرا از آن استفاده می کنیم.
ما بحث “چرا” را تا فصل بعدی که مشتقات، گرادیان ها و بهینه سازی ها را پوشش می دهد، ذخیره خواهیم کرد؛ کافی است بگوییم که تابع log دارای برخی ویژگی های مطلوب است. Log مخفف لگاریتم است و به عنوان راه حل برای عبارت x در معادله ای به شکل تعریف می شودax = b.
مثلا، 10x = 100 با یک گزارش قابل حل است: log10(100),
که به 2 ارزیابی می شود. این ویژگی تابع log به ویژه زمانی مفید است که e (عدد اویلر یا ~2.71828) در پایه استفاده شود (جایی که 10 در مثال است). لگاریتم با e به عنوان پایه آن به عنوان لگاریتم طبیعی، لگاریتم طبیعی یا به سادگی log نامیده می شود – همچنین ممکن است این نوشته شده را به این صورت : ln(x) = log(x) = loge(x)
تنوع قراردادها می تواند این موضوع را گیج کننده کند، بنابراین برای ساده کردن چیزها، هر گونه اشاره به log همیشه یک لگاریتم طبیعی در سراسر این کتاب خواهد بود. ورود به سیستم طبیعی نشان دهنده راه حل برای اصطلاح x در معادله است
ex = b; for example, ex = 5.2 is solved by log(5.2).
در کد پایتون:
import numpy as np
b = 5.2
print(np.log(b))
>>>
1.6486586255873816
ما می توانیم این را با تسریع نتیجه خود تأیید کنیم:
import math
print(math.e ** 1.6486586255873816)
>>>
5.199999999999999
تفاوت کوچک نتیجه دقت ممیز شناور در پایتون است. با بازگشت به محاسبه ضرر، باید خروجی خود را به دو روش دیگر تغییر دهیم. ابتدا، فرآیند خود را برای کار بر روی دسته هایی از توزیع های خروجی softmax به روز می کنیم. و دوم، محاسبه لگاریتم منفی را برای شاخص هدف پویا کنید (شاخص هدف تاکنون کدگذاری شده است).
سناریویی را با یک شبکه عصبی در نظر بگیرید که طبقه بندی بین سه کلاس را انجام می دهد و شبکه عصبی در دسته های سه تایی طبقه بندی می شود. پس از اجرای عملکرد فعال سازی softmax با دسته ای از 3 نمونه و 3 کلاس، لایه خروجی شبکه به دست می دهد:
# Probabilities for 3 samples
softmax_outputs = np.array([[0.7, 0.1, 0.2],
[0.1, 0.5, 0.4],
[0.02, 0.9, 0.08]])
ما به راهی برای محاسبه پویا آنتروپی متقاطع طبقه ای نیاز داریم، که اکنون می دانیم یک محاسبه ورود به سیستم منفی است. برای تعیین اینکه کدام مقدار در خروجی softmax را از آن محاسبه کنیم، فقط باید مقادیر هدف خود را بدانیم. در این مثال، 3 کلاس وجود دارد. فرض کنید سعی می کنیم چیزی را به عنوان “سگ”، “گربه” یا “انسان” طبقه بندی کنیم. یک سگ کلاس 0 (در شاخص 0)، یک گربه کلاس 1 (شاخص 1) و یک کلاس انسانی 2 (شاخص 2) است. بیایید فرض کنیم دسته ای از سه ورودی نمونه به این شبکه عصبی به مقادیر هدف یک سگ، گربه و گربه نگاشت می شود. بنابراین اهداف (مانند
لیستی از شاخص های هدف) [0، 1، 1] خواهد بود.
softmax_outputs = [[0.7, 0.1, 0.2],
[0.1, 0.5, 0.4],
[0.02, 0.9, 0.08]]
class_targets = [0, 1, 1] # dog, cat, cat
مقدار اول، 0، در class_targets به این معنی است که پیش بینی مورد نظر اولین توزیع خروجی softmax در شاخص 0 [0.7، 0.1، 0.2] بود؛ مدل دارای امتیاز اطمینان 0.7 است که این مشاهده یک سگ است. این امر در سراسر دسته ادامه می یابد، جایی که هدف مورد نظر توزیع دوم softmax، [0.1، 0.5، 0.4]، در شاخص 1 بود؛ مدل فقط دارای امتیاز اطمینان 0.5 است که این یک گربه است – مدل در مورد این مشاهده اطمینان کمتری دارد. در آخرین نمونه، این شاخص دومین شاخص از توزیع softmax است، مقدار 0.9 در این مورد – اطمینان بسیار بالا.
با مجموعه ای از خروجی های softmax و اهداف مورد نظر آنها، می توانیم این شاخص ها را برای بازیابی مقادیر از توزیع های softmax ترسیم کنیم:
softmax_outputs = [[0.7, 0.1, 0.2],
[0.1, 0.5, 0.4],
[0.02, 0.9, 0.08]]
class_targets = [0, 1, 1]
for targ_idx, distribution in zip(class_targets, softmax_outputs):
print(distribution[targ_idx])
>>>
0.7
0.5
0.9
تابع () zip دوباره به ما اجازه می دهد تا چندین تکرار را به طور همزمان در پایتون تکرار کنیم. این را می توان با استفاده از NumPy ساده تر کرد (ما این بار در حال ایجاد یک آرایه NumPy از خروجی های Softmax هستیم):
softmax_outputs = np.array([[0.7, 0.1, 0.2],
[0.1, 0.5, 0.4],
[0.02, 0.9, 0.08]])
class_targets = [0, 1, 1]
print(softmax_outputs[[0, 1, 2], class_targets])
>>>
[0.7 0.5 0.9]
مقادیر 0، 1 و 2 چیست؟ NumPy به ما امکان می دهد یک آرایه را به روش های مختلف فهرست بندی کنیم. یکی از آنها استفاده از لیستی پر از شاخص ها است و این برای ما راحت است – ما می توانیم از class_targets برای این منظور استفاده کنیم زیرا در حال حاضر حاوی لیستی از شاخص های مورد علاقه ما است. مشکل این است که این باید ردیف های داده را در آرایه فیلتر کند – بعد دوم. برای انجام این کار، باید به صراحت این آرایه را در بعد اول آن فیلتر کنیم. این بعد شامل پیش بینی ها است و البته ما می خواهیم همه آنها را حفظ کنیم. ما می توانیم با استفاده از لیستی حاوی اعداد از 0 از طریق همه شاخص ها به این هدف دست یابیم. ما می دانیم که به اندازه توزیع ها در کل دسته خود شاخص خواهیم داشت، بنابراین می توانیم به جای تایپ هر مقدار خودمان، از ()range استفاده کنیم:
print(softmax_outputs[
range(len(softmax_outputs)), class_targets
])
>>>
[0.7 0.5 0.9]
این لیستی از اطمینان ها را در شاخص های هدف برای هر یک از نمونه ها برمی گرداند. اکنون گزارش منفی را در این لیست اعمال می کنیم:
print(-np.log(softmax_outputs[
range(len(softmax_outputs)), class_targets
]))
>>>
[0.35667494 0.69314718 0.10536052]
در نهایت، ما می خواهیم میانگین ضرر در هر دسته ایده ای در مورد نحوه عملکرد مدل ما در طول آموزش داشته باشیم. روش های زیادی برای محاسبه میانگین در پایتون وجود دارد. ابتدایی ترین شکل یک میانگین میانگین حسابی است: sum(قابل تکرار) / len(قابل تکرار). NumPy متدی دارد که این میانگین را روی آرایه ها محاسبه می کند، بنابراین به جای آن از آن استفاده خواهیم کرد. میانگین NumPy را به کد اضافه می کنیم:
neg_log = -np.log(softmax_outputs[
range(len(softmax_outputs)), class_targets
])
average_loss = np.mean(neg_log)
print(average_loss)
>>>
0.38506088005216804
ما قبلا یاد گرفته ایم که اهداف را می توان یک داغ کدگذاری کرد، جایی که همه مقادیر، به جز یکی، صفر هستند و موقعیت برچسب صحیح با 1 پر می شود. آنها همچنین می توانند پراکنده باشند، به این معنی که اعدادی که در آنها وجود دارد اعداد کلاس صحیح هستند – ما آنها را از این طریق با تابع spiral_data() تولید می کنیم و می توانیم اجازه دهیم محاسبه ضرر هر یک از این اشکال را بپذیرد. از آنجایی که ما این را برای کار با برچسب های پراکنده پیاده سازی کردیم (مانند داده های آموزشی ما)، باید یک بررسی اضافه کنیم که آیا آنها یک داغ کدگذاری شده اند یا خیر و در این مورد جدید کمی متفاوت از آن استفاده کنیم. بررسی را می توان با شمارش ابعاد انجام داد – اگر اهداف تک بعدی باشند (مانند یک لیست)، پراکنده هستند، اما اگر 2 بعد وجود داشته باشد (مانند لیستی از لیست ها)، مجموعه ای از بردارهای رمزگذاری شده یک داغ وجود دارد. در این حالت دوم، ما یک راه حل را با استفاده از معادله اول این فصل پیاده سازی می کنیم، به جای فیلتر کردن اطمینان ها در برچسب های هدف. ما باید اطمینان ها را در تارگت ها ضرب کنیم، همه مقادیر به جز مقادیر موجود در برچسب های صحیح را صفر کنیم، و مجموع را در امتداد محور ردیف (محور 1) انجام دهیم. ما باید یک تست به کدی که به تازگی برای تعداد ابعاد نوشتیم اضافه کنیم، محاسبات مقادیر لاگ را به خارج از این دستور if جدید منتقل کنیم و راه حل برچسب های رمزگذاری شده یک داغ را پس از معادله اول پیاده سازی کنیم:
import numpy as np
softmax_outputs = np.array([[0.7, 0.1, 0.2],
[0.1, 0.5, 0.4],
[0.02, 0.9, 0.08]])
class_targets = np.array([[1, 0, 0],
[0, 1, 0],
[0, 1, 0]])
# احتمالات برای مقادیر هدف –
# فقط اگر برچسب های طبقه بندی شده باشد
if len(class_targets.shape) == 1:
correct_confidences = softmax_outputs[
range(len(softmax_outputs)),
class_targets
]
# Mask values – only for one-hot encoded labels
elif len(class_targets.shape) == 2:
correct_confidences = np.sum(
softmax_outputs * class_targets,
axis=1
)
# ضرر و زیان
neg_log = -np.log(correct_confidences)
average_loss = np.mean(neg_log)
print(average_loss)
قبل از اینکه ادامه دهیم، یک مشکل اضافی وجود دارد که باید حل شود. خروجی softmax ، که همچنین ورودی این تابع از دست دادن است ، از اعدادی در محدوده 0 تا 1 تشکیل شده است – لیستی از اطمینان ها. این امکان وجود دارد که مدل اطمینان کامل به یک برچسب داشته باشد و تمام اطمینان های باقی مانده را صفر کند. به طور مشابه، این امکان نیز وجود دارد که مدل اطمینان کامل را به مقداری اختصاص دهد که هدف نبوده است. اگر سعی کنیم از دست دادن این اطمینان 0 را محاسبه کنیم:
import numpy as np
-np.log(0)
>>>
__main__:1: RuntimeWarning: divide by zero encountered in log
inf
قبل از اینکه این موضوع را توضیح دهیم، باید در مورد log(0) صحبت کنیم. از نقطه نظر ریاضی، log(0) تعریف نشده است. ما قبلا وابستگی زیر را می دانیم: اگر y=log(x)، پس ey=x. این سوال که y حاصل در y=log(0) چیست همان سوال این است که y در ey=0 چیست. به عبارت ساده، ثابت e به هر توان همیشه یک عدد مثبت است و هیچ y وجود ندارد که منجر به ey=0 شود. این بدان معناست که log(0) تعریف نشده است. ما باید بدانیم که log(0) چیست و “undefined” به این معنی نیست که ما چیزی در مورد آن نمی دانیم. از آنجایی که log(0) تعریف نشده است، نتیجه مقداری بسیار نزدیک به 0 چیست؟ ما می توانیم حد یک تابع را محاسبه کنیم. نحوه محاسبه دقیق آن از این کتاب فراتر رفته است، اما راه حل این است:
ما آن را به عنوان حد لگاریتم طبیعی x می خوانیم ، با x نزدیک شدن به 0 از مثبت (این است
محاسبه لگاریتم طبیعی یک مقدار منفی غیرممکن است) برابر با بی نهایت منفی است. این بدان معناست که حد برای یک x بی نهایت کوچک بی نهایت منفی است ، جایی که x هرگز به 0 نمی رسد.
وضعیت در زبان های برنامه نویسی کمی متفاوت است. ما در اینجا محدودیتی نداریم، فقط تابعی است که با توجه به یک پارامتر، مقداری مقدار را برمی گرداند. لگاریتم طبیعی منفی 0، در پایتون با NumPy برابر با یک عدد بی نهایت بزرگ است، نه تعریف نشده، و هشداری در مورد تقسیم بر 0 چاپ می کند (که نتیجه نحوه انجام این محاسبه است). اگر -np.log(0) برابر با inf باشد، آیا می توان e را به توان بی نهایت منفی با پایتون محاسبه کرد؟
np.e**(-np.inf)
>>>
0.0
در برنامه نویسی، هر چه چیزهای تعریف نشده کمتر باشند، بهتر است. بعدا، ساده سازی های مشابهی را خواهیم دید، به عنوان مثال هنگام محاسبه مشتق تابع مقدار مطلق، که برای ورودی 0 وجود ندارد و باید تصمیماتی برای دور زدن آن بگیریم.
بازگشت به نتیجه inf برای-np.log(0) —
تا آنجا که منطقی است، از آنجایی که مدل کاملا اشتباه است، این مشکل برای ما خواهد بود که محاسبات بیشتری را با آن انجام دهیم. بعدا ، با بهینه سازی ، ما همچنین در محاسبه گرادیان ها مشکل خواهیم داشت ، که با مقدار میانگین تمام تلفات نمونه شروع می شود زیرا یک مقدار بی نهایت در یک لیست باعث می شود که میانگین آن لیست نیز بی نهایت باشد:
import numpy as np
np.mean([1, 2, 3, -np.log(0)])
>>>
__main__:1: RuntimeWarning: divide by zero encountered in log
inf
ما می توانیم مقدار بسیار کوچکی به اعتماد به نفس اضافه کنیم تا از صفر شدن آن جلوگیری کنیم، به عنوان مثال، 1e-7:
-np.log(1e-7)
>>>
16.11809565095832
افزودن یک مقدار بسیار کوچک، یک دهم میلیون، به اعتماد به نفس در لبه دور آن تأثیر ناچیزی بر نتیجه خواهد داشت، اما این روش 2 مسئله اضافی را به همراه دارد. اول، در موردی که مقدار اطمینان 1 است:
-np.log(1+1e-7)
>>>
-9.999999505838704e-08
هنگامی که مدل در یک پیش بینی کاملا صحیح باشد و تمام اعتماد به نفس را در برچسب صحیح قرار دهد، ضرر به جای 0 بودن به یک مقدار منفی تبدیل می شود. مشکل دیگر در اینجا تغییر اعتماد به سمت 1 است، حتی اگر با مقدار بسیار کوچکی باشد. برای جلوگیری از هر دو مشکل، بهتر است مقادیر را از هر دو طرف با یک عدد برش دهیم، در مورد ما 1e-7. این بدان معناست که کمترین مقدار ممکن به 1e-7 تبدیل می شود (مانند نمایشی که ما انجام دادیم) اما بالاترین مقدار ممکن، به جای اینکه 1+1e-7 باشد، 1-1e-7 می شود (بنابراین کمی کمتر از 1):
-np.log(1-1e-7)
>>>
1.0000000494736474e-07
این از ضرر جلوگیری می کند که دقیقا 0 باشد، در عوض آن را به یک مقدار بسیار کوچک تبدیل می کند، اما آن را به یک مقدار منفی تبدیل نمی کند و ضرر کلی را به سمت 1 سوق نمی دهد. در کد خود و با استفاده از numpy، ما این کار را با استفاده از متد np.clip() انجام خواهیم داد:
y_pred_clipped = np.clip(y_pred, 1e-7, 1 – 1e-7)
این روش می تواند برش را روی آرایه ای از مقادیر انجام دهد، بنابراین می توانیم آن را مستقیما روی پیش بینی ها اعمال کنیم و آن را به عنوان یک آرایه جداگانه ذخیره کنیم، که به زودی از آن استفاده خواهیم کرد.
کلاس از دست دادن آنتروپی متقابل طبقه بندی شده
در فصل های بعدی، توابع از دست دادن بیشتری را اضافه خواهیم کرد و برخی از عملیاتی که انجام خواهیم داد برای همه آنها مشترک است. یکی از این عملیات نحوه محاسبه ضرر کلی است – مهم نیست که از کدام تابع ضرر استفاده کنیم، ضرر کلی همیشه مقدار میانگین تمام تلفات نمونه است. بیایید کلاس Loss را ایجاد کنیم که حاوی روش محاسبه است که متد فوروارد شی ضرر ما را فراخوانی می کند و مقدار میانگین تلفات نمونه برگشتی را محاسبه می کند:
# کلاس ضرر مشترک
class Loss:
# داده ها و تلفات منظم سازی را محاسبه می کند
# خروجی مدل داده شده و مقادیر حقیقت زمین
def calculate(self, output, y):
# تلفات نمونه را محاسبه کنید
sample_losses = self.forward(output, y)
# میانگین ضرر را محاسبه کنید
data_loss = np.mean(sample_losses)
# ضرر برگشتی
return data_loss
در فصل های بعدی، کد بیشتری به این کلاس اضافه خواهیم کرد و دلیل وجود آن واضح تر خواهد شد. در حال حاضر، ما از آن برای این هدف استفاده خواهیم کرد.
بیایید کد ضرر خود را برای راحتی در آینده به یک کلاس تبدیل کنیم:
# Cross-entropy loss
class Loss_CategoricalCrossentropy(Loss):
# Forward pass
def forward(self, y_pred, y_true):
# تعداد نمونه ها در یک دسته
samples = len(y_pred)
# کلیپ داده ها برای جلوگیری از تقسیم بر 0
# هر دو طرف را کلیپ کنید تا میانگین را به سمت هیچ مقداری نکشید
y_pred_clipped = np.clip(y_pred, 1e-7, 1 – 1e-7)
# احتمالات برای مقادیر هدف –
# فقط اگر برچسب های طبقه بندی شده باشد
if len(y_true.shape) == 1:
correct_confidences = y_pred_clipped[
range(samples),
y_true
]
# Mask values – only for one-hot encoded labels
elif len(y_true.shape) == 2:
correct_confidences = np.sum(
y_pred_clipped * y_true,
axis=1
)
# Losses
negative_log_likelihoods = -np.log(correct_confidences)
return negative_log_likelihoods
This class inherits the Loss class and performs all the error calculations that we derived throughout this chapter and can be used as an object. For example, using the manually-created output and targets:
loss_function = Loss_CategoricalCrossentropy()
loss = loss_function.calculate(softmax_outputs, class_targets)
print(loss)
>>>
0.38506088005216804
ترکیب همه چیز تا این مرحله:
import numpy as np
import nnfs
from nnfs.datasets import spiral_data
nnfs.init()
# لایه متراکم
class Layer_Dense:
# مقداردهی اولیه لایه
def __init__(self, n_inputs, n_neurons):
# Initialize weights and biases
self.weights = 0.01 * np.random.randn(n_inputs, n_neurons)
self.biases = np.zeros((1, n_neurons))
# Forward pass
def forward(self, inputs):
# مقادیر خروجی را از ورودی ها، وزن ها و biases محاسبه کنید
self.output = np.dot(inputs, self.weights) + self.biases
# فعال سازی ReLU
class Activation_ReLU:
# Forward pass
def forward(self, inputs):
# Calculate output values from inputs
self.output = np.maximum(0, inputs)
# فعال سازی Softmax
class Activation_Softmax:
# Forward pass
def forward(self, inputs):
# احتمالات غیرعادی را دریافت کنید
exp_values = np.exp(inputs – np.max(inputs, axis=1,
keepdims=True))
# آنها را برای هر نمونه عادی کنید
probabilities = exp_values / np.sum(exp_values, axis=1,
keepdims=True)
self.output = probabilities
# Common loss class
class Loss:
# داده ها و تلفات منظم سازی را محاسبه می کند
# خروجی مدل داده شده و مقادیر حقیقت زمین
def calculate(self, output, y):
# تلفات نمونه را محاسبه کنید
sample_losses = self.forward(output, y)
# میانگین ضرر را محاسبه کنید
data_loss = np.mean(sample_losses)
# ضرر برگشتی
return data_loss
# Cross-entropy loss
class Loss_CategoricalCrossentropy(Loss):
# Forward pass
def forward(self, y_pred, y_true):
# تعداد نمونه ها در یک دسته
samples = len(y_pred)
# clip داده ها برای جلوگیری از تقسیم بر 0
# هر دو طرف را clip کنید تا میانگین را به سمت هیچ مقداری نکشید
y_pred_clipped = np.clip(y_pred, 1e-7, 1 – 1e-7)
# احتمالات برای مقادیر هدف –
# فقط اگر برچسب های طبقه بندی شده باشد
if len(y_true.shape) == 1:
correct_confidences = y_pred_clipped[
range(samples),
y_true
]
# Mask values – only for one-hot encoded labels
elif len(y_true.shape) == 2:
correct_confidences = np.sum(
y_pred_clipped * y_true,
axis=1
)
# ضرر و زیان
negative_log_likelihoods = -np.log(correct_confidences)
return negative_log_likelihoods
# مجموعه داده ایجاد کنید
X, y = spiral_data(samples=100, classes=3)
# ایجاد لایه متراکم با 2 ویژگی ورودی و 3 مقدار خروجی
dense1 = Layer_Dense(2, 3)
# ایجاد فعال سازی ReLU (برای استفاده با لایه متراکم):
activation1 = Activation_ReLU()
# ایجاد لایه دوم Dense با 3 ویژگی ورودی (همانطور که خروجی می گیریم)
# لایه قبلی در اینجا) و 3 مقدار خروجی
dense2 = Layer_Dense(3, 3)
# ایجاد فعال سازی Softmax (برای استفاده با لایه Dense):
activation2 = Activation_Softmax()
# ایجاد عملکرد از دست دادن
loss_function = Loss_CategoricalCrossentropy()
# یک پاس رو به جلو از داده های آموزشی خود را از طریق این لایه انجام دهید
dense1.forward(X)
# عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید
# خروجی اولین لایه متراکم را در اینجا می گیرد
activation1.forward(dense1.output)
# یک عبور رو به جلو از لایه دوم Dense انجام دهید
# خروجی های تابع فعال سازی لایه اول را به عنوان ورودی می گیرد
dense2.forward(activation1.output)
# عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید
# خروجی لایه متراکم دوم را در اینجا می گیرد
activation2.forward(dense2.output)
# بیایید خروجی چند نمونه اول را ببینیم:
print(activation2.output[:5])
# عملکرد فعال سازی را از طریق عبور رو به جلو انجام دهید
# خروجی لایه متراکم دوم را در اینجا می گیرد و ضرر را برمی گرداند
loss = loss_function.calculate(activation2.output, y)
# مقدار از دست دادن پرینت
print(‘loss:’, loss)
>>>
[[0.33333334 0.33333334 0.33333334]
[0.33333316 0.3333332 0.33333364]
[0.33333287 0.3333329 0.33333418]
[0.3333326 0.33333263 0.33333477]
[0.33333233 0.3333324 0.33333528]]
loss: 1.0986104
باز هم، ما مقادیر ~0.33 را دریافت می کنیم زیرا مدل تصادفی است، و میانگین تلفات آن نیز برای این داده ها زیاد نیست، زیرا ما هنوز مدل خود را در مورد نحوه تصحیح خطاهای آن آموزش نداده ایم.
محاسبه دقت
در حالی که از دست دادن یک معیار مفید برای بهینه سازی یک مدل است، معیاری که معمولا در عمل همراه با ضرر استفاده می شود، دقت است که توضیح می دهد که هر چند وقت یکبار بیشترین اطمینان کلاس صحیح از نظر کسر است. به راحتی، می توانیم از تعاریف متغیر موجود برای محاسبه معیار دقت استفاده کنیم. ما از مقادیر argmax از خروجی های softmax استفاده می کنیم و سپس آنها را با تارگت ها مقایسه می کنیم. این کار به سادگی انجام دادن است (توجه داشته باشید که ما softmax_outputs را برای این مثال کمی اصلاح کردیم):
import numpy as np
# احتمالات 3 نمونه
softmax_outputs = np.array([[0.7, 0.2, 0.1],
[0.5, 0.1, 0.4],
[0.02, 0.9, 0.08]])
# برچسب های هدف (حقیقت زمینی) برای 3 نمونه
class_targets = np.array([0, 1, 1])
# مقادیر را در امتداد محور دوم محاسبه کنید (محور شاخص 1)
predictions = np.argmax(softmax_outputs, axis=1)
# اگر تارگت ها یک داغ کدگذاری شده اند – آنها را تبدیل کنید
if len(class_targets.shape) == 2:
class_targets = np.argmax(class_targets, axis=1)
# درست به 1 ارزیابی می شود. نادرست به 0
accuracy = np.mean(predictions==class_targets)
print(‘acc:’, accuracy)
>>>
acc: 0.6666666666666666
ما همچنین با تبدیل آنها به مقادیر پراکنده با استفاده از np.argmax()، اهداف کدگذاری شده یک داغ را مدیریت می کنیم.
برای محاسبه دقت آن می توانیم موارد زیر را به انتهای اسکریپت کامل خود در بالا اضافه کنیم:
# محاسبه دقت از خروجی فعال سازی2 و اهداف
# مقادیر را در امتداد محور اول محاسبه کنید
predictions = np.argmax(activation2.output, axis=1)
if len(y.shape) == 2:
y = np.argmax(y, axis=1)
accuracy = np.mean(predictions==y)
# Print accuracy
print(‘acc:’, accuracy)
>>>
acc: 0.34
اکنون که یاد گرفتید که چگونه یک پاس رو به جلو از طریق شبکه ما انجام دهید و معیارها را برای سیگنال دادن در صورت عملکرد ضعیف مدل محاسبه کنید، در فصل بعدی بهینه سازی را آغاز خواهیم کرد!
Supplementary Material: https://nnfs.io/ch5
Chapter code, further resources, and errata for this chapter.