اسلاید قبلی
اسلاید بعدی

فصل پنجم-محاسبه خطای شبکه با ضرر

محاسبه خطای شبکه با ضرر

با یک مدل که به طور تصادفی مقداردهی اولیه شده است، یا حتی مدلی که با رویکردهای پیچیده تر مقداردهی اولیه شده است، هدف ما آموزش یا آموزش یک مدل در طول زمان است. برای آموزش یک مدل، وزن ها و سوگیری ها را تغییر می دهیم تا دقت و اطمینان مدل را بهبود بخشیم. برای این کار محاسبه می کنیم که مدل چقدر خطا دارد. تابع ضرر که به آن تابع هزینه نیز گفته می شود، الگوریتمی است که میزان اشتباه بودن یک مدل را تعیین می کند. ضرر معیار این معیار است. از آنجایی که ضرر خطای مدل است، ما در حالت ایده آل می خواهیم 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.

Chapter 6 – Optimization

فصل دهم-Optimizers

Optimizers هنگامی که شیب را محاسبه کردیم، می توانیم از این اطلاعات برای تنظیم وزن ها و بایاس ها برای کاهش اندازه گیری ضرر استفاده کنیم. در یک مثال اسباب بازی قبلی، ما نشان دادیم که چگونه می توانیم با موفقیت خروجی تابع فعال سازی نورون (ReLU) را به این روش کاهش دهیم. به یاد بیاورید که ما کسری از گرادیان را برای هر پارامتر وزن و بایاس کم کردیم. در حالی که بسیار ابتدایی است، اما هنوز هم یک بهینه ساز رایج به نام Stochastic Gradient Descent (SGD) است. همانطور که به زودی متوجه خواهید شد، اکثر بهینه سازها

فصل نهم-پس انتشار

پس انتشار اکنون که ایده ای در مورد نحوه اندازه گیری تأثیر متغیرها بر خروجی یک تابع داریم، می توانیم شروع به نوشتن کد برای محاسبه این مشتقات جزئی کنیم تا نقش آنها را در به حداقل رساندن تلفات مدل ببینیم. قبل از اعمال این در یک شبکه عصبی کامل، بیایید با یک پاس رو به جلو ساده شده تنها با یک نورون شروع کنیم. به جای پس انتشار از تابع از دست دادن برای یک شبکه عصبی کامل، بیایید تابع ReLU را برای یک نورون واحد پس انتشار کنیم و طوری عمل کنیم که گویی قصد داریم خروجی این

فصل هشتم-گرادیان ها، مشتقات جزئی و قانون زنجیره ای

گرادیان ها، مشتقات جزئی و قانون زنجیره ای دو تا از آخرین قطعات پازل، قبل از ادامه کدنویسی شبکه عصبی، مفاهیم مرتبط گرادیان ها و مشتقات جزئی هستند. مشتقاتی که تاکنون حل کرده ایم مواردی بوده اند که فقط یک متغیر مستقل در تابع وجود دارد – یعنی نتیجه فقط به x بستگی  دارد. با این حال ، شبکه عصبی ما به عنوان مثال از نورون ها تشکیل شده است که دارای ورودی های متعدد هستند. هر ورودی در وزن مربوطه ضرب می شود (تابعی از 2 پارامتر) ، و آنها با بایاس جمع می شوند (تابعی از پارامترهای به

فصل هفتم-مشتقات

مشتقات تغییر تصادفی و جستجوی وزن ها و سوگیری های بهینه به یک دلیل اصلی مثمر ثمر نبود: تعداد ترکیبات احتمالی وزن ها و سوگیری ها بی نهایت است و برای دستیابی به هر موفقیتی به چیزی هوشمندانه تر از شانس خالص نیاز داریم. هر وزن و سوگیری نیز ممکن است درجات مختلفی از تأثیر بر کاهش داشته باشد – این تأثیر به خود پارامترها و همچنین به نمونه فعلی بستگی دارد که ورودی لایه اول است. سپس این مقادیر ورودی در وزن ها ضرب می شوند، بنابراین داده های ورودی بر خروجی نورون تأثیر می گذارند و بر تأثیری

فصل ششم-معرفی بهینه سازی

معرفی بهینه سازی اکنون که شبکه عصبی ساخته شده است، می تواند داده ها را از آن عبور دهد و قادر به محاسبه ضرر باشد، گام بعدی تعیین نحوه تنظیم وزن ها و بایاس ها برای کاهش تلفات است. یافتن روشی هوشمندانه برای تنظیم وزن و سوگیری ورودی نورون ها برای به حداقل رساندن ضرر، مشکل اصلی شبکه های عصبی است. اولین گزینه ای که ممکن است به آن فکر کنید این است که به طور تصادفی وزن ها را تغییر دهید، ضرر را بررسی کنید و این کار را تکرار کنید تا زمانی که با کمترین ضرر یافت شده

فصل چهارم-توابع فعال سازی

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

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *