تخطَّ إلى المحتوى
2/30الفصل 2 من 30

من أين تأتي دالة الخسارة: من الأرجحية، لا من العرف

الخطأ التربيعي ليس عرفًا؛ إنه فرضية عن الضجيج. تعرّف كيف تربط الأرجحية بين القياس ودوال الخسارة.

في هذه الصفحة

تتآكل الشفرة التي تقطع القطع. خلال وردية من عشر ساعات تفقد حدّها بما يكفي لتخرج القطع من السير أعرض مما بدأت به بجزء من المليمتر، وما إن تتجاوز 23.5 مليمترًا حتى يرفضها التفتيش. لا أحد في المصنع يعرف متى يحدث ذلك. ما لديهم هو قدمة قياس، ودفتر ملاحظات، وعشرون قراءة من الثلاثاء الماضي: الساعات منذ تغيير الشفرة، وعرض القطعة المقاس في تلك اللحظة.

يرسم أحدهم خطًا عبر النقاط. ويرسم شخص آخر خطًا مختلفًا قليلًا. ويرسم ثالث خطًا ثالثًا. تبدو الخطوط الثلاثة معقولة على الورق، لكنها تختلف في موعد تغيير الشفرة بعدة ساعات — وفي هذا المصنع، هذا هو الفرق بين أسبوع هادئ ودفعة تالفة.

أي خط أفضل؟

بهذه الصياغة، لا إجابة لهذا السؤال. ليست إجابة صعبة — بل لا إجابة إطلاقًا. «أفضل» ليست خاصية في الخط كما أن ميله خاصية؛ إنها خاصية لخط مع قاعدة لتقييم الخطوط، وما لم يكتب أحد تلك القاعدة فلا يوجد شيء يُحسب. يأخذ هذا الفصل هذه الجملة بجدية، وينتهي باكتشاف أن القاعدة الأكثر شيوعًا في machine learning ليست عرفًا، بل نتيجة لادعاء عن العالم — ادعاء يمكنك اختباره، ويكون أحيانًا خاطئًا.

اعتراف واحد قبل أول سطر من الكود. هذه القراءات العشرون ليست من مصنع حقيقي: لقد ولّدتها من خط اخترته، width=20.00+0.30h\text{width} = 20.00 + 0.30 \cdot h، مضافًا إليه ضجيج عشوائي بانتشار يقارب عُشر مليمتر. هذا مهم، لأن كل ما يلي يدور حول ما إذا كانت طريقة ما تستعيد حقيقة، والطريقة الوحيدة للتحقق من ذلك هي معرفة الحقيقة مسبقًا. إذن: 0.30 مليمتر في الساعة هي الإجابة في آخر الكتاب. لا يُسمح لك باستخدامها، بل فقط بالمقارنة معها.

إليك القراءات والخطوط الثلاثة، وقد قُيّمت بثلاث طرق: الخطأ التربيعي، الذي يلجأ إليه الجميع؛ والخطأ المطلق، الذي قد يختاره إحصائي؛ وأسوأ خطأ، الذي سيختاره عامل التشغيل، لأن المفتش لا يهتم بمتوسطك — إنه يرفض القطعة الواحدة الخارجة عن السماحية.

يصل NumPy هنا، بعد فصل واحد من perceptron المكتوب بـ pure-Python، لسبب واحد: بحلول نهاية هذا الفصل سنقيّم أربعمئة ألف خط مرشح مقابل عشرين قراءة لكل منها، وحلقة Python ليست الأداة المناسبة لذلك. وهي أيضًا الصياغة التي كُتب بها كل مصدر مذكور أدناه.

loss.pyPYTHON
import numpy as np

# Hours since the blade was changed, and the width of the part measured then.
SHIFT = np.array([
    (0.5, 20.17), (1.0, 20.28), (1.5, 20.53), (2.0, 20.61), (2.5, 20.69),
    (3.0, 20.94), (3.5, 21.21), (4.0, 21.31), (4.5, 21.27), (5.0, 21.35),
    (5.5, 21.58), (6.0, 21.80), (6.5, 21.67), (7.0, 22.07), (7.5, 22.10),
    (8.0, 22.31), (8.5, 22.48), (9.0, 22.66), (9.5, 22.90), (10.0, 23.13),
])
h, y = SHIFT[:, 0], SHIFT[:, 1]

LINES = {"A": (20.10, 0.26), "B": (20.20, 0.28), "C": (20.30, 0.26)}

for name, (a, b) in LINES.items():
    r = y - (a + b * h)                                      
    print(f"{name}   mean square {np.mean(r**2):.5f}"
          f"   mean absolute {np.mean(np.abs(r)):.5f}"
          f"   worst {np.max(np.abs(r)):.3f}")               

الكمية في الأسطر المظللة هي الباقي: ما قاله الخط ناقص ما قالته القدمة، رقم واحد لكل قراءة. كل قاعدة تقييم في هذا الفصل، وكل loss function في الفصول الثمانية والعشرين التي تليه، ليست إلا طريقة لضغط قائمة من البواقي إلى رقم واحد. لا تختلف إلا في طريقة الضغط.

TEXT
A   mean square 0.02699   mean absolute 0.12600   worst 0.430
B   mean square 0.02524   mean absolute 0.13700   worst 0.350
C   mean square 0.03179   mean absolute 0.15000   worst 0.320

اقرأ الأعمدة، لا الصفوف. يقول الخطأ التربيعي إن B هو الأفضل، ويقول الخطأ المطلق إن A هو الأفضل، ويقول أسوأ خطأ إن C هو الأفضل: ثلاث قواعد، ثلاثة فائزين، على النقاط العشرين نفسها.

اخترت هذه الخطوط الثلاثة بحيث تختلف، وينبغي أن أقول ذلك صراحة. المغزى هو مدى سهولة ذلك — بضع دقائق من البحث بين تقاطعات وميلان تبدو معقولة تكشف مئات الثلاثيات من هذا النوع. الترتيب خاصية للقاعدة التي اخترتها، لا حقيقة عن الخطوط، ولذلك فالقاعدة ليست تفصيلًا تنفيذيًا: إنها تعريف المشكلة. وهذا يطرح السؤال الذي وُجد هذا الفصل للإجابة عنه: على أي أساس تختارها؟

أولًا مسألة أصغر، لأن الأمر لا يتعلق بثلاثة خطوط بل بعدد لا نهائي منها. خذ الخطأ التربيعي حاليًا، بما أنه ما يأخذه الجميع، وقلّص المشكلة إلى رقم واحد باستخدام الحيلة التي أنقذت perceptron أحد عشر ألف epoch في الفصل 1: اطرح المتوسط من كلا العمودين. ما إن تتمركز سحابة النقاط حول الأصل، حتى يمر أفضل خط وفق الخطأ التربيعي عبر الأصل تمامًا — وهكذا يُحسم التقاطع ولا يبقى إلا الميل للاختيار.

loss.py (continued)PYTHON
u, v = h - h.mean(), y - y.mean()        # 5.25 hours, 21.553 mm

def mse(theta):
    return np.mean((v - theta * u) ** 2)

grid = np.arange(0.0, 0.6001, 0.001)
curve = np.array([mse(t) for t in grid])
print(grid.size, "candidates ->", f"theta={grid[curve.argmin()]:.3f}", f"mse={curve.min():.6f}")
TEXT
601 candidates -> theta=0.293 mse=0.010115

ستمئة وواحد من الميلان المرشحة، وفائز واحد: 0.293 مليمتر في الساعة مقابل حقيقة قدرها 0.300. عشرون قراءة مشوشة وحلقة for وصلتا إلى أقل من جزء من مئة من المليمتر في الساعة — اثنان وثلث في المئة.

الجزء المثير للاهتمام ليس الفائز بل شكل البحث. اطبع المنحنى كله، مُدارًا بحيث تمتد الخسارة من اليسار إلى اليمين:

loss.py (continued)PYTHON
ts = np.arange(0.0, 0.6001, 0.04)
ls = np.array([mse(t) for t in ts])
for t, l in zip(ts, ls):
    col = round(l / ls.max() * 50)
    print(f"theta={t:.2f} |{' ' * col}*{' ' * (50 - col)}| mse={l:7.4f}")
TEXT
theta=0.00 |                                              *    | mse= 0.7244
theta=0.04 |                                  *                | mse= 0.5428
theta=0.08 |                        *                          | mse= 0.3878
theta=0.12 |                *                                  | mse= 0.2593
theta=0.16 |          *                                        | mse= 0.1575
theta=0.20 |     *                                             | mse= 0.0822
theta=0.24 |  *                                                | mse= 0.0336
theta=0.28 | *                                                 | mse= 0.0116
theta=0.32 | *                                                 | mse= 0.0161
theta=0.36 |   *                                               | mse= 0.0473
theta=0.40 |       *                                           | mse= 0.1050
theta=0.44 |            *                                      | mse= 0.1894
theta=0.48 |                   *                               | mse= 0.3004
theta=0.52 |                            *                      | mse= 0.4379
theta=0.56 |                                      *            | mse= 0.6021
theta=0.60 |                                                  *| mse= 0.7928

هذا وادٍ، منظور إليه من الجانب. له قاع واحد، وترتفع جدرانه بسلاسة على الجانبين، و— وهذا هو الجزء الذي لم يكن درج الفصل 1 قادرًا على تقديمه — في كل نقطة عليه يوجد اتجاه محدد جيدًا لـ «النزول». تذكّر هذا الشكل. الفصل 3 كله يدور حول السير نزولًا فيه من دون زيارة النقاط الستمئة والواحدة كلها، وحول ما يتغير عندما يكون للوادي أكثر من قاع واحد.

لدينا وادٍ لأننا قمنا بالتربيع. كان الخطأ المطلق سيعطيه زاوية حادة عند القاع؛ وكان أسوأ خطأ سيعطيه مقاطع مسطحة لا يغير فيها تحريك الخط شيئًا على الإطلاق. التربيع مريح بلا شك — والراحة هي تقريبًا السبب الذي تقدمه معظم المقررات، في أربع صيغ مزخرفة: يجعل الأخطاء موجبة (والقيمة المطلقة تفعل ذلك أيضًا)؛ يعاقب الأخطاء الكبيرة أكثر (ولماذا ينبغي له ذلك؟)؛ قابل للاشتقاق (والقوة الرابعة كذلك)؛ إنه ما يستخدمه الجميع (صحيح، وليس ذلك حجة).

هذا هو الموقف الصريح. اختار الخطأ التربيعي الخط B، واختار الخطأ المطلق الخط A. أحدهما صحيح لهذا المصنع والآخر خاطئ، ولا شيء مما قيل حتى الآن يخبرك أيهما. لاختيار القاعدة تحتاج إلى معرفة شيء عن كيف اختلفت القراءات عن الخط، وهذا سؤال عن العالم، لا عن الرياضيات. للإجابة عنه نحتاج إلى قطعة صغيرة من العتاد.

إليك الادعاء الذي يحوّل «أي خط أفضل» إلى سؤال له إجابة.

افترض أن عرض قطعة ما هو الخط مضافًا إليه خطأ عشوائي، وافترض أن ذلك الخطأ مسحوب من Gaussian — منحنى الجرس — بمتوسط صفر وانحراف معياري σ\sigma:

yi=θxi+εi,εiN(0,σ2)y_i = \theta x_i + \varepsilon_i, \qquad \varepsilon_i \sim \mathcal{N}(0, \sigma^2)

كثافة Gaussian هي

p(ε)=1σ2πexp ⁣(ε22σ2)p(\varepsilon) = \frac{1}{\sigma\sqrt{2\pi}} \exp\!\left(-\frac{\varepsilon^2}{2\sigma^2}\right)

والآن افعل شيئًا لم يستطع perceptron فعله. بالنسبة إلى ميل مرشح معيّن θ\theta، لكل قراءة باقٍ، والصيغة أعلاه تحوّل ذلك الباقي إلى رقم: ما مدى معقولية خطأ بهذا الحجم بالضبط، إذا كان هذا الميل هو الحقيقة؟ القراءة الواقعة على الخط تحصل على رقم كبير، والقراءة البعيدة نصف مليمتر تحصل على رقم صغير.

القراءات مستقلة — فالقدمة لا تتذكر القطعة السابقة — لذلك تقول قاعدة الضرب إن معقولية الدفتر كله هي حاصل ضرب الكثافات الفردية. ذلك الحاصل هو أرجحية θ\theta.1 انتبه إلى الاتجاه، لأنه هو الاتجاه الذي تتحدث عنه قاعدة بايز: البيانات ثابتة ومعروفة، والمعامل هو الذي يتغير. هذا ليس «احتمال الميل». إنه الاحتمال الذي يسنده النموذج إلى البيانات التي حصلت عليها فعلًا، مقروءًا كدالة في الميل.

likelihood.pyPYTHON
SIGMA = 0.12

def gaussian(r, sigma):
    return np.exp(-r ** 2 / (2 * sigma ** 2)) / (sigma * np.sqrt(2 * np.pi))

def likelihood(theta):
    return np.prod(gaussian(v - theta * u, SIGMA))          

for t in (0.25, 0.293, 0.35):
    print(f"theta={t}   likelihood = {likelihood(t):.6g}")
TEXT
theta=0.25   likelihood = 521.952
theta=0.293   likelihood = 2.42028e+07
theta=0.35   likelihood = 0.190312

ميل 0.293 يجعل هذا الدفتر أكثر معقولية بستة وأربعين ألف مرة من 0.25، وأكثر معقولية بمئة وسبعة وعشرين مليون مرة من 0.35. Maximum likelihood هو المبدأ الذي يقول إنك تختار المعامل الذي يجعل ما لاحظته فعلًا أقل إثارة للدهشة قدر الإمكان. ليس نظرية بل اقتراحًا عما ينبغي أن تعنيه «الأفضل» — اقتراحًا ذا مضمون، لأنه يجبرك على التصريح بافتراضك عن الضجيج قبل أن يُسمح لك بتقييم أي شيء.

شغّل الأسطر الثلاثة نفسها من الكود على شهر من الورديات بدلًا من وردية واحدة، فتنهار الطريقة.

likelihood.py (continued)PYTHON
rng = np.random.default_rng(7)
u_big = rng.uniform(-5.25, 5.25, 2000)                     # 2000 readings, not 20
v_big = 0.30 * u_big + 0.12 * rng.standard_normal(2000)

print("2000 readings, sigma = 0.12 mm :", np.prod(gaussian(v_big - 0.30 * u_big, 0.12)))
noisy = 0.30 * u_big + 2.0 * rng.standard_normal(2000)
print("2000 readings, sigma = 2.00 mm :", np.prod(gaussian(noisy - 0.30 * u_big, 2.0)))
print("largest float64 :", np.finfo(np.float64).max)
TEXT
RuntimeWarning: overflow encountered in reduce
2000 readings, sigma = 0.12 mm : inf
2000 readings, sigma = 2.00 mm : 0.0
largest float64 : 1.7976931348623157e+308

ألفا عملية ضرب والإجابة هي inf. غيّر ثابتًا واحدًا — قدمة أقل دقة، بحيث تخرج الكثافات أصغر من 1 بدلًا من أكبر — ويعيد الكود نفسه 0.0. كلتا الإجابتين خاطئتان، في اتجاهين متعاكسين، ولا ترفع أي منهما استثناء يمكنك التقاطه، والثانية لا تطبع حتى تحذيرًا.

لا خطأ في الرياضيات. الأرجحية عند تلك الإعدادات عدد منتهٍ ومحدد تمامًا: لوغاريتمها الطبيعي 1400.91، فالعدد نفسه يقارب 1060810^{608}. المشكلة أن حاسوبك لا يملك ذلك العدد، ويستحق الأمر أن تفهم بالضبط أي أعداد يملك، لأن هذه لن تكون آخر مرة يقرر فيها النتيجة.

إصلاح حاصل الضرب المنفجر هو الإصلاح المعتاد: خذ اللوغاريتمات. يحوّل اللوغاريتم حواصل الضرب إلى مجاميع، وهو متزايد بصرامة فلا يستطيع نقل موضع القيمة العظمى، ومجموع ألفي عدد معتدل شيء يتعامل معه float64 بلا شكوى. وفق العرف نأخذ سالب لوغاريتم الأرجحية، بحيث يعني الأفضل الأصغر. الآن عوّض كثافة Gaussian وشاهد ما يحدث.

  1. ابدأ من حاصل الضرب. الأرجحية هي L(θ)=i=1Np(yiθxi)\mathcal{L}(\theta) = \prod_{i=1}^{N} p(y_i - \theta x_i)، حيث pp هي كثافة Gaussian أعلاه.

  2. خذ سالب اللوغاريتم. يصبح حاصل الضرب مجموعًا، ويلغي الأس في الكثافة نفسه مباشرة أمام اللوغاريتم:

logL(θ)=N2log ⁣(2πσ2)+12σ2i=1N(yiθxi)2-\log \mathcal{L}(\theta) = \frac{N}{2}\log\!\left(2\pi\sigma^2\right) + \frac{1}{2\sigma^2}\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2
  1. تخلّص من كل ما لا يحتوي على θ\theta. الحد الأول ثابت. والـ 1/2σ21/2\sigma^2 أمام المجموع ثابت موجب، وضرب دالة في ثابت موجب لا يستطيع نقل موضع حدها الأدنى. ما يتبقى هو
i=1N(yiθxi)2\sum_{i=1}^{N}\left(y_i - \theta x_i\right)^2

وهو مجموع البواقي التربيعية — الشيء الذي بدأنا به الفصل لأنه أول ما يخطر ببال أي شخص.

هذه هي النتيجة التي وُجد الفصل لأجلها، وتستحق أن تُقال بلا تحفظ: الخطأ التربيعي ليس عرفًا. إنه سالب log-likelihood لـ Gaussian، بعد حذف الثوابت. تقليل الخطأ التربيعي هو الفعل نفسه تمامًا مثل تأكيد أن أخطاءك Gaussian والسؤال عن المعامل الذي يجعل بياناتك أقل إثارة للدهشة. كنت تقدّم هذا التأكيد طوال الوقت؛ فقط لم يكن أحد يخبرك.

التكافؤ قابل للتحقق، لذا تحقق منه: امسح الميلان الستمئة والواحد نفسها باستخدام سالب log-likelihood الكامل، بكل ثوابته، وباستخدام الخطأ التربيعي الصافي.

likelihood.py (continued)PYTHON
N = v.size

def nll(theta):
    r = v - theta * u
    return N * np.log(SIGMA * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * SIGMA ** 2)

nlls = np.array([nll(t) for t in grid])
mses = np.array([mse(t) for t in grid])
print(f"argmin of the negative log-likelihood : theta={grid[nlls.argmin()]:.3f}  nll={nlls.min():.6f}")
print(f"argmin of the mean squared error      : theta={grid[mses.argmin()]:.3f}  mse={mses.min():.6f}")
print("same index:", nlls.argmin() == mses.argmin())
TEXT
argmin of the negative log-likelihood : theta=0.293  nll=-17.001977
argmin of the mean squared error      : theta=0.293  mse=0.010115
same index: True

أرقام مختلفة على المحور الرأسي، وأحدها سالب، وهذا ما لا يمكن لمجموع مربعات أن يكونه: قد ينخفض سالب log-likelihood دون الصفر، لأن الكثافة يمكن أن تتجاوز 1. القاع نفسه للوادي نفسه، حتى آخر نقطة على الشبكة.

عرض الاشتقاق الكامل

أي عمليات الحذف آمنة بالضبط؟ تظهر المناورة نفسها في كل فصل يشتق loss، وليست بريئة دائمًا.

حذف ثابت مضاف آمن متى لم يعتمد على المعامل الذي تحسّنه، وحذف ثابت ضربي موجب آمن لأن argminθcf(θ)=argminθf(θ)\arg\min_\theta c\,f(\theta) = \arg\min_\theta f(\theta) لأي c>0c > 0. يفشل الاثنان لحظة أن تكون σ\sigma قيد الملاءمة أيضًا: عندها لا يكون N2log(2πσ2)\frac{N}{2}\log(2\pi\sigma^2) ثابتًا إطلاقًا، بل هو الحد الذي يمنع النموذج من ادعاء σ=0\sigma = 0 وأرجحية لا نهائية. وهذا بالضبط هو القسم التالي.

ويفشلان بطريقة أخرى في الفصل 3: الثابت الضربي لا ينقل الحد الأدنى، لكنه يغير مقياس التدرج، والتدرج يُضرب في معدل التعلم. القسمة على NN للحصول على الخطأ التربيعي المتوسط بدلًا من المجموع غير مرئية في الإجابة، لكنها شديدة الوضوح في تشغيل التدريب — مع المجموع، مضاعفة حجم batch تضاعف كل خطوة تخطوها.

ثبتنا σ\sigma عند 0.12 بقرار تعسفي، ولا أحد في المصنع يعرف انتشار خطأ القدمة. عاملها كمجهول ثانٍ ودع maximum likelihood يقررها أيضًا. هنا يعود الحد الثابت الذي حذفناه للتو، لأنه الشيء الوحيد الواقف بين النموذج وادعاء الدقة الكاملة.

likelihood.py (continued)PYTHON
r = v - 0.293 * u
sigmas = np.arange(0.01, 1.0001, 0.0001)
nll_sigma = N * np.log(sigmas * np.sqrt(2 * np.pi)) + np.sum(r ** 2) / (2 * sigmas ** 2)

print("best sigma on the grid       :", round(float(sigmas[nll_sigma.argmin()]), 4))
print("sqrt(mean squared residual)  :", round(float(np.sqrt(np.mean(r ** 2))), 4))
TEXT
best sigma on the grid       : 0.1006
sqrt(mean squared residual)  : 0.1006

يتفق الاثنان حتى أربع خانات عشرية، وليس ذلك مصادفة: اشتقاق هذا التعبير وجعله صفرًا يعطي σ^2=1Nri2\hat{\sigma}^2 = \frac{1}{N}\sum r_i^2 تمامًا. إذن متوسط الخطأ التربيعي ليس مجرد شيء يشبه التباين. تحت هذا النموذج هو تقدير maximum-likelihood لتباين الضجيج — فالعدد الذي كنت تقلله طوال الوقت كان تقديرًا لمدى ضجيج حساسك.

تعقيد صغير، رخيص في قوله ومكلف في إعادة اكتشافه لاحقًا: ذلك التقدير منحاز إلى الأسفل، لأن البواقي قيسَت مقابل ملاءمة اختيرت هي نفسها لجعلها صغيرة. حاكِ ذلك — مئتا ألف دفتر من عشرين قراءة لكل منها، مسحوبة من توزيع تباينه الحقيقي يساوي 1 بالضبط، مع تقدير معامل الملاءمة الوحيد من القراءات نفسها. قسمة مجموع المربعات على NN تعطي متوسطًا قدره 0.9501؛ والقسمة على N1N-1 تعطي 1.0001؛ و(N1)/N(N-1)/N هو 0.95 تمامًا. كل معامل تلائمه يكلف درجة حرية واحدة، وهذه أصغر حالة مرئية لمشكلة أكبر بكثير: النموذج يبدو دائمًا أفضل على البيانات التي لائم نفسه عليها. يحوّل الفصل 4 ذلك إلى انضباط حجز البيانات جانبًا، ويعطي الفصل 6 الأثر اسمه.

إذا كان الخطأ التربيعي يؤكد أن الضجيج Gaussian، فالسؤال التالي هو ماذا يحدث عندما يكون هذا التأكيد خاطئًا. ليس خاطئًا قليلًا — بل خاطئًا بالطريقة التي تكون بها القياسات الحقيقية خاطئة.

على أرض المصنع، معظم قراءات القدمة جيدة حتى عُشر مليمتر، ومرة أو مرتين في الوردية تدخل شظية رايش تحت الفك فتخطئ القراءة بعدة مليمترات. أخطاء كهذه ثقيلة الذيل: صغيرة معظم الوقت، وهائلة أحيانًا، وهائلة أكثر بكثير مما يسمح به منحنى الجرس. توزيع Cauchy هو النموذج النظيف القياسي لهذا السلوك، وكثافته بسيطة مثل كثافة Gaussian:

p(ε)=1πs(1+(ε/s)2)p(\varepsilon) = \frac{1}{\pi s \left(1 + (\varepsilon/s)^2\right)}

الفرق في الذيل: يتناقص Gaussian مثل eε2e^{-\varepsilon^2}، بسرعة قاسية، ويتناقص Cauchy مثل 1/ε21/\varepsilon^2، بالكاد. النتيجة أسهل في الرؤية منها في القول:

PYTHON
rng = np.random.default_rng(3)
g = 0.12 * rng.standard_normal(10 ** 6)          # Gaussian noise
c = 0.12 * rng.standard_cauchy(10 ** 6)          # Cauchy noise, same scale
for k in (10 ** 2, 10 ** 3, 10 ** 4, 10 ** 5, 10 ** 6):
    print(f"{k:>9,} samples   gaussian var {g[:k].var():.4f}   cauchy var {c[:k].var():10.2f}")
TEXT
      100 samples   gaussian var 0.0164   cauchy var       0.26
    1,000 samples   gaussian var 0.0146   cauchy var      59.88
   10,000 samples   gaussian var 0.0145   cauchy var     358.17
  100,000 samples   gaussian var 0.0144   cauchy var    3097.98
1,000,000 samples   gaussian var 0.0144   cauchy var   32886.10

يستقر تباين عينة Gaussian على 0.0144، وهو 0.1220.12^2، ويبقى هناك. أما Cauchy فيصعد، ويواصل الصعود ما دمت تأخذ عينات، لأنه لا يوجد شيء يتقارب إليه: توزيع Cauchy لا تباين له، ولا متوسط أيضًا. الخطأ التربيعي، الذي تدور مهمته كلها حول تقليل متوسط المربعات، يُطلب منه كمية غير موجودة.

إليك إذن وردية واحدة خُدعت فيها القدمة. العشرون ساعة نفسها، والشفرة نفسها، والانجراف نفسه البالغ 0.30 مليمتر في الساعة — فقط الضجيج الآن Cauchy. لائمها مرتين: مرة بتقليل البواقي التربيعية، ومرة بتقليل سالب log-likelihood للضجيج الذي ولّد البيانات فعلًا. حيلة التمركز لا تساعد هنا — فهي تثبّت التقاطع للخطأ التربيعي فقط — لذلك تتم الملاءمتان بالقوة الغاشمة عبر شبكة من التقاطعات و الميلان، لأننا ما زلنا لا نملك طريقة للعثور على قاع وادٍ إلا بزيارته.

swarf.pyPYTHON
SWARF = np.array([
    (0.5, 20.08), (1.0, 21.95), (1.5, 20.86), (2.0, 27.51), (2.5, 20.64),
    (3.0, 20.75), (3.5, 21.01), (4.0, 21.03), (4.5, 21.37), (5.0, 20.60),
    (5.5, 22.03), (6.0, 21.95), (6.5, 21.98), (7.0, 22.01), (7.5, 21.73),
    (8.0, 22.97), (8.5, 22.60), (9.0, 22.66), (9.5, 22.44), (10.0, 22.78),
])
hs, ys = SWARF[:, 0], SWARF[:, 1]

A = np.arange(18.0, 22.001, 0.005)      # 801 intercepts
B = np.arange(-0.20, 0.8001, 0.002)     # 501 slopes
R = ys - (A[:, None, None] + B[None, :, None] * hs)      # every line against every point

SCALE = 0.12
square = np.sum(R ** 2, axis=2)                          # least squares          
cauchy = np.sum(np.log(1 + (R / SCALE) ** 2), axis=2)    # Cauchy likelihood      

for name, surface in (("least squares", square), ("Cauchy likelihood", cauchy)):
    i, j = np.unravel_index(surface.argmin(), surface.shape)
    print(f"{name:>18}:  width = {A[i]:.3f} + {B[j]:.4f} * hours"
          f"   -> 23.5 mm at hour {(23.5 - A[i]) / B[j]:.2f}")
print(f"{'the truth':>18}:  width = 20.000 + 0.3000 * hours"
      f"   -> 23.5 mm at hour {(23.5 - 20.0) / 0.30:.2f}")
print(f"{A.size * B.size:,} candidate lines evaluated")

السطران المظلان هما الفرق الكامل بين الملاءمتين. خذ لوغاريتم كثافة Cauchy، واحذف الثوابت تمامًا كما فعلنا من قبل، وما يتبقى هو log(1+(r/s)2)\sum \log\left(1 + (r/s)^2\right). الوصفة نفسها، وادعاء مختلف عن الضجيج.

TEXT
     least squares:  width = 21.380 + 0.1080 * hours   -> 23.5 mm at hour 19.63
 Cauchy likelihood:  width = 19.935 + 0.3020 * hours   -> 23.5 mm at hour 11.80
         the truth:  width = 20.000 + 0.3000 * hours   -> 23.5 mm at hour 11.67
401,301 candidate lines evaluated

تبلغ المربعات الصغرى عن انجراف قدره 0.108 مليمتر في الساعة، أي نحو ثلث المعدل الحقيقي، وتستنتج أن الشفرة صالحة حتى الساعة 19.6. الإجابة الحقيقية هي الساعة 11.7. إذا تصرف المصنع بناءً على تلك الملاءمة، فسيشغل المكبس ثماني ساعات إضافية منتجًا قطعًا خارج السماحية، بسلطة أكثر loss function معيارية في المجال. أما ملاءمة Cauchy، باستخدام القراءات العشرين نفسها، والشبكة نفسها، وفرق سطر واحد في الكود، فتصل إلى الساعة 11.8.

اعتراضان يستحقان إجابات، لأن كليهما أول ما يقوله مهندس جيد.

القيمة الشاذة واضحة — احذفها فقط. يمكنك ذلك، وهذا يساعد، لكنه ليس كافيًا. حذف أسوأ قراءة منفردة ينقل ميل المربعات الصغرى من 0.108 إلى 0.239، وهو ما يزال يضع تغيير الشفرة عند الساعة 13.1، متأخرًا ساعة ونصف؛ حذف الأسوأ، ثم إعادة الملاءمة، ثم حذف ما أصبح هو الأسوأ الآن يوصلك إلى 0.286 — ولاحظ أن هذا أصبح بالفعل إجراءً، لا ملاحظة: احذف أكبر باقيين في الملاءمة الأصلية بدلًا من ذلك وستصل إلى 0.223. لكنك الآن اتخذت أحكامًا لا تستطيع كتابتها أو الدفاع عنها، وأتمتة القاعدة لا تنقذها: قاعدة حذف أكبر باقي ثم إعادة الملاءمة، عند تشغيلها على ألف وردية محاكاة، لها خطأ ميل وسيطي 0.0177 مقابل 0.0100 لملاءمة الأرجحية، وتبتعد بأكثر من 0.05 في 14.7% من الورديات مقابل 1.3%. الحذف رقعة فوق افتراض خاطئ. الأرجحية لا تحتاج إلى رقعة، لأنها لم تفترض قط أن القيمة الشاذة مستحيلة.

لقد اخترت مجموعة بيانات محظوظة. هذا الاعتراض صحيح تمامًا، ولهذا تحاكي التجربة الأخيرة ألف وردية مستقلة وتعيد الملاءمة بالطريقتين في كل منها.

swarf.py (continued)PYTHON
A = np.arange(18.0, 22.001, 0.02)        # a coarser grid: a thousand fits to do
B = np.arange(-0.20, 0.8001, 0.005)
lines = A[:, None, None] + B[None, :, None] * hs
rng = np.random.default_rng(2026)
err_sq, err_ca = [], []

for _ in range(1000):                                        # 1000 independent shifts
    ys = 20.00 + 0.30 * hs + SCALE * rng.standard_cauchy(hs.size)
    R = ys - lines
    _, j = np.unravel_index(np.sum(R ** 2, axis=2).argmin(), (A.size, B.size))
    _, q = np.unravel_index(np.sum(np.log1p((R / SCALE) ** 2), axis=2).argmin(), (A.size, B.size))
    err_sq.append(abs(B[j] - 0.30))
    err_ca.append(abs(B[q] - 0.30))

err_sq, err_ca = np.array(err_sq), np.array(err_ca)
for name, e in (("least squares", err_sq), ("Cauchy likelihood", err_ca)):
    print(f"{name:>18}: median slope error {np.median(e):.4f} mm/h"
          f"   off by more than 0.05 in {100 * np.mean(e > 0.05):4.1f}% of shifts"
          f"   worst {e.max():.3f}")
print(f"the likelihood fit is the closer of the two in {100 * np.mean(err_ca < err_sq):.1f}% of shifts")
TEXT
     least squares: median slope error 0.0350 mm/h   off by more than 0.05 in 40.4% of shifts   worst 0.500
 Cauchy likelihood: median slope error 0.0100 mm/h   off by more than 0.05 in  1.3% of shifts   worst 0.090
the likelihood fit is the closer of the two in 75.6% of shifts

الوسيط، لا المتوسط، للسبب نفسه في كل هذا القسم: أخطاء المربعات الصغرى تقودها Cauchy، لذلك ليس متوسطها شيئًا مستقرًا للإبلاغ عنه. المربعات الصغرى خاطئة بشدة في ورديتين من كل خمس؛ وملاءمة الأرجحية خاطئة بشدة في وردية واحدة من كل سبع وسبعين، وأسوأ فشل لها عبر ألف وردية أقل من خُمس أسوأ فشل للمربعات الصغرى.

لا يجعل أي من هذا الخطأ التربيعي سيئًا. بل يجعله محددًا، والحساب يشرح بالضبط لماذا. خذ باقيًا قدره 0.1 mm وآخر قدره 7 mm. عند التربيع، تسهم القراءة السيئة في المجموع بمقدار 4,900 مرة مساهمة القراءة الجيدة، لذلك يُجرّ الخط بجسده نحوها؛ أما تحت log-likelihood لـ Cauchy فتسهم البواقي نفسها بـ 0.527 و8.133، بنسبة 15.4. القراءة السيئة ما تزال تُحسب، لكنها لا تحصل على حق القرار. هذه بداية الإحصاء المتين، حيث تفصل loss الخاصة بـ Huber عام 1964 بين الأمرين بالسلوك تربيعيًا للبواقي الصغيرة وخطيًا للبواقي الكبيرة،7 وحيث كان Tukey قد أظهر بالفعل قلة التلوث اللازمة لجعل تباين العينة أداة أسوأ من متوسط الانحراف المطلق.8

وملاحظة تاريخية، أجمل من أن تُترك. نُشرت المربعات الصغرى أولًا على يد Legendre عام 1805، كأداة جبرية مريحة بلا تبرير سوى أنها نجحت.9 وبعد أربع سنوات قلب Gauss الحجة: أخذ كمعطى أن المتوسط الحسابي هو الطريقة الصحيحة لدمج القياسات المتكررة، وسأل أي توزيع للأخطاء يجعل المتوسط هو القيمة الأكثر احتمالًا، وأظهر أن واحدًا فقط تقريبًا يفعل ذلك — وهو الذي يحمل اسمه الآن.10 الاشتقاق في هذا الفصل هو اشتقاقه، وعمره أكثر من قرنين، وما يزال هو الجزء الذي تتركه معظم المقررات خارجًا.

ما يمكنك قوله الآن، وما لا تستطيع فعله بعد

رابط إلى القسم: ما يمكنك قوله الآن، وما لا تستطيع فعله بعد

ما اكتسبته. loss function هي قاعدة تقييم، والترتيب الذي تنتجه خاصية للقاعدة، لا للمرشحين. كل loss في هذا المقرر هي سالب log-likelihood لافتراض ما عن الضجيج، بعد حذف الثوابت — Gaussian يعطي الخطأ التربيعي هنا، وBernoulli يعطي cross-entropy في الفصل 4، وتوزيع فئوي على مفردات يعطي next-token loss في الفصل 8. الوصفة لا تتغير أبدًا: صرّح بالضجيج، واكتب الأرجحية، وخذ سالب اللوغاريتم. وعندما يكون الافتراض خاطئًا، لا يكون النموذج غير دقيق فحسب، بل يكون خاطئًا في اتجاه يمكنك التنبؤ به.

ما يزال ناقصًا. وجدنا قاع الوادي بزيارة كل نقطة فيه. نجح ذلك لمعامل واحد وستمئة مرشح، ونجا مع معاملين عند 401,301 مرشحًا في خُمس ثانية. ثلاثة معاملات بالدقة نفسها تعني 201,051,801 مرشحًا ولم تعد تلائم مصفوفة واحدة؛ وشبكة صغيرة في الفصل 5 لديها آلاف المعاملات، والنماذج التي يضع الفصل 10 سعرًا لها لديها مليارات. القوة الغاشمة هنا ليست بطيئة، بل مستحيلة حسابيًا، ولا شيء في هذا الفصل يقترح بديلًا.

لكن انظر إلى الوادي مرة أخرى. وأنت واقف عند θ=0.20\theta = 0.20 بخسارة 0.0822، فإن اتجاه «النزول» ليس لغزًا — يمكنك رؤيته على الصفحة، فالمنحنى ينحدر إلى اليمين. إذا كان بإمكانك أن تسأل loss function عن اتجاه ميلها عند النقطة التي تقف عليها، من دون تقييمها في أي مكان آخر، لاستطعت أن تخطو خطوة في ذلك الاتجاه، ثم تسأل مجددًا، وتكرر حتى تصبح الأرض مستوية.

لهذا السؤال اسم. ميل دالة عند نقطة هو مشتقتها، وبالنسبة إلى دالة ذات معاملات كثيرة فإن مجموعة الميلان في كل اتجاه دفعة واحدة هي التدرج. لم يستطع الفصل 1 استخدام واحد، لأن خطأ perceptron كان درجًا بلا ميل يمكن السؤال عنه. بنى هذا الفصل شيئًا أفضل: loss ناعمة في كل مكان وجاءت من افتراض مصرح به بدلًا من تفضيل.

لذلك لم يعد سؤال الفصل 3 هو ما إذا كان الميل موجودًا. بل كيف نحسبه، ولماذا التحرك عكسه ينزل بنا لا يصعد — وهي إشارة يطلب منك كل مقرر تقريبًا أن تقبلها إيمانًا — وكم ينبغي أن نخطو قبل السؤال مرة أخرى، وهو ما يتبين أنه الرقم الواحد الذي يقرر ما إذا كان تشغيل التدريب يتقارب، أو يتذبذب حول الإجابة إلى الأبد، أو يهرب إلى اللانهاية.


ومن المفيد أيضًا القراءة إلى جانب هذا الفصل: Prince, Understanding Deep Learning §5.1–5.2 والملحق C، الذي يبني كل loss في الكتاب من maximum likelihood بالترتيب المستخدم هنا؛ Goodfellow وBengio وCourville، Deep Learning §3.1–3.11 و§5.5، حيث يشتق قسم maximum-likelihood أيضًا KL divergence الذي يحتاجه الفصل 4؛ Murphy, Probabilistic Machine Learning: An Introduction الفصل 2 و§4.2، حول ما يضمنه maximum likelihood وما لا يضمنه؛ Deisenroth وFaisal وOng، Mathematics for Machine Learning §6.1–6.4 من أجل قاعدة الجمع وقاعدة الضرب وقاعدة بايز كما ينبغي؛ مذكرة Tom Mitchell القصيرة من CMU بعنوان Estimating Probabilities: MLE and MAP (2016)؛ و§22.7 من Dive into Deep Learning، الذي يصل إلى النتيجة نفسها في كود قابل للتشغيل.

  1. Fisher, R. A. On the mathematical foundations of theoretical statistics. Philosophical Transactions of the Royal Society A 222، ص. 309–368 (1922). حيث تُعرض الأرجحية كطريقة عامة، إلى جانب «parameter» و«statistic» والكفاية والكفاءة. أما التسمية نفسها، والفصل عن الاحتمال، فقد سبقا ذلك بعام: Fisher, R. A., On the “probable error” of a coefficient of correlation deduced from a small sample, Metron 1، ص. 3–32 (1921)، ص. 24–25.

  2. IEEE Standard for Floating-Point Arithmetic, IEEE 754-2019. يعرّف binary32 وbinary16، وقواعد التقريب التي تجعل تجربة الجمع تخرج كما خرجت.

  3. Kalamkar, D. et al. A Study of BFLOAT16 for Deep Learning Training. arXiv:1905.12322 (2019). معاملات التنسيق، والحجة لصالح مقايضة بتات mantissa ببتات الأس.

  4. Micikevicius, P. et al. Mixed Precision Training. ICLR 2018، arXiv:1710.03740. scaling للخسارة، ومقادير التدرج المقاسة التي تجعله ضروريًا في float16.

  5. Goldberg, D. What Every Computer Scientist Should Know About Floating-Point Arithmetic. ACM Computing Surveys 23(1)، ص. 5–48 (1991). ما يزال أفضل شرح منفرد لسبب اختلاف ترتيبي الجمع.

  6. Kahan, W. Pracniques: further remarks on reducing truncation errors. Communications of the ACM 8(1)، ص. 40 (1965). الجمع المعوّض في نصف صفحة.

  7. Huber, P. J. Robust estimation of a location parameter. The Annals of Mathematical Statistics 35(1)، ص. 73–101 (1964). loss التي تكون تربيعية قرب الصفر وخطية في الأذيال، مشتقة لا مرقعة.

  8. Tukey, J. W. A survey of sampling from contaminated distributions، في Contributions to Probability and Statistics (Stanford University Press, 1960)، ص. 448–485.

  9. Legendre, A. M. Nouvelles méthodes pour la détermination des orbites des comètes (Paris, 1805)، الملحق Sur la méthode des moindres quarrés. أول نشر للمربعات الصغرى، كأداة حسابية.

  10. Gauss, C. F. Theoria Motus Corporum Coelestium (Hamburg, 1809)، الكتاب II، §§175–179. الحجة من المتوسط الحسابي إلى قانون الخطأ الطبيعي، ومنه إلى المربعات الصغرى.

هل أنت مستعد لتترك الاختيار لـ LIA؟

ابنِ بكل نماذج الذكاء الاصطناعي في مكان واحد — ابدأ مجانًا اليوم.