معاينة مختبر آمنة
Perceptron
هذي معاينة منقّحة للقراءة فقط؛ ما فيه أي شيء يشتغل داخل الصفحة.
قراءة فقط
معاينة الدفتر
Perceptron
> **ملاحظة بيئة التشغيل المدمجة:** هالمعاينة تستخدم عيّنة صغيرة وثابتة وآمنة من ناحية الحقوق عشان تكون النتايج قابلة للتكرار. النتايج بالحجم الكامل تحتاج مجموعة البيانات أو النموذج الموثّق بالدرس داخل بيئة خارجية معتمدة.
## البيرسيبترون (Perceptron)
> هالدفتر جزء من منهج AI for Beginners. زوروا المستودع عشان تلقون مواد التعلّم كاملة.
مثل ما ناقشنا، يخلّينا البيرسيبترون نحل **مسألة تصنيف ثنائي** (binary classification)، يعني نصنّف أمثلة الإدخال إلى فئتين نسمّيها **إيجابية** و**سلبية**.
خلّونا أول شي نستورد المكتبات اللي نحتاجها.
import pylab
from matplotlib import gridspec
from sklearn.datasets import load_digits, make_classification
from types import SimpleNamespace
import numpy as np
from ipywidgets import interact, interactive, fixed
import ipywidgets as widgets
# pick the seed for reproducability - change it to explore the effects of random variations
np.random.seed(1)
import random## مشكلة بسيطة
نبدأ بمسألة تجريبية بسيطة عندنا فيها سمتان للإدخال. مثلًا في الطب، قد نبي نصنّف الأورام إلى حميدة وخبيثة بحسب حجم الورم وعمره.
بنسوي مجموعة البيانات العشوائية للتصنيف باستخدام الدالة `make_classification` من مكتبة SciKit Learn:
n = 50
X, Y = make_classification(n_samples = n, n_features=2,
n_redundant=0, n_informative=2, flip_y=0)
Y = Y*2-1 # convert initial 0/1 values into -1/1
X = X.astype(np.float32); Y = Y.astype(np.int32) # features - float, label - int
# Split the dataset into training and test
train_x, test_x = np.split(X, [ n*8//10])
train_labels, test_labels = np.split(Y, [n*8//10])
print("Features:\n",train_x[0:4])
print("Labels:\n",train_labels[0:4])وخلّونا نرسم مجموعة البيانات بعد:
def plot_dataset(suptitle, features, labels):
# prepare the plot
fig, ax = pylab.subplots(1, 1)
#pylab.subplots_adjust(bottom=0.2, wspace=0.4)
fig.suptitle(suptitle, fontsize = 16)
ax.set_xlabel('$x_i[0]$ -- (feature 1)')
ax.set_ylabel('$x_i[1]$ -- (feature 2)')
colors = ['r' if l>0 else 'b' for l in labels]
ax.scatter(features[:, 0], features[:, 1], marker='o', c=colors, s=100, alpha = 0.5)
fig.show()
plot_dataset('Training data', train_x, train_labels)## بيرسيبترون
بما إن البيرسيبترون مصنّف ثنائي، فمخرجه لكل متجه إدخال $x$ يكون إمّا +1 أو -1 بحسب الفئة. نحسب المخرج بالصيغة:
$$y(\mathbf{x}) = f(\mathbf{w}^{\mathrm{T}}\mathbf{x})$$
وهنا $\mathbf{w}$ هو متجه الأوزان، و$f$ هي **دالة تنشيط درجية** (step activation function):
$$
f(x) = \begin{cases}
+1 & x \geq 0 \\
-1 & x < 0
\end{cases} \\
$$
لكن النموذج الخطي العام يحتاج بعد إلى انحياز (bias)؛ يعني الأفضل نحسب $y$ بالشكل $y=f(\mathbf{w}^{\mathrm{T}}\mathbf{x}+\mathbf{b})$. وعشان نبسّط النموذج، نقدر نستغني عن حد الانحياز بإضافة بُعد جديد إلى سمات الإدخال تكون قيمته دائمًا 1:
pos_examples = np.array([ [t[0], t[1], 1] for i,t in enumerate(train_x)
if train_labels[i]>0])
neg_examples = np.array([ [t[0], t[1], 1] for i,t in enumerate(train_x)
if train_labels[i]<0])
print(pos_examples[0:3])## خوارزمية التدريب
عشان ندرّب البيرسيبترون، نحتاج نلقى الأوزان $\mathbf{w}$ اللي تقلّل الخطأ. نعرّف الخطأ باستخدام **معيار البيرسيبترون** (perceptron criterion):
$$E(\mathbf{w}) = -\sum_{n \in \mathcal{M}}\mathbf{w}^{\mathrm{T}}\mathbf{x}_{n}t_{n}$$
* $t_{n} \in \{-1, +1\}$ تكون للعينة السلبية وللعينة الإيجابية، بالترتيب
* $\mathcal{M}$ هي مجموعة الأمثلة اللي تصنيفها طلع خطأ
بنستخدم **الانحدار المتدرّج** (gradient descent). نبدأ بأوزان عشوائية أولية $\mathbf{w}^{(0)}$، ونعدّلها في كل خطوة تدريب باستخدام تدرّج $E$:
$$\mathbf{w}^{\tau + 1}=\mathbf{w}^{\tau} - \eta \nabla E(\mathbf{w}) = \mathbf{w}^{\tau} + \eta\sum_{n \in \mathcal{M}}\mathbf{x}_{n} t_{n}$$
وهنا $\eta$ هو **معدل التعلّم** (learning rate)، و$\tau\in\mathbb{N}$ هو رقم التكرار.
خلّونا نعرّف الخوارزمية في Python:
def train(positive_examples, negative_examples, num_iterations = 100, learning_rate = 0.01):
num_dims = positive_examples.shape[1]
# Initialize weights.
# We initialize with 0 for simplicity, but random initialization is also a good idea
weights = np.zeros((num_dims,1))
pos_count = positive_examples.shape[0]
neg_count = negative_examples.shape[0]
report_frequency = 10
for i in range(num_iterations):
# Pick one positive and one negative example
pos = random.choice(positive_examples)
neg = random.choice(negative_examples)
z = np.dot(pos, weights)
if z < 0: # positive example was classified as negative
weights = weights + learning_rate * pos.reshape(weights.shape)
z = np.dot(neg, weights)
if z >= 0: # negative example was classified as positive
weights = weights - learning_rate * neg.reshape(weights.shape)
# Periodically, print out the current accuracy on all examples
if i % report_frequency == 0:
pos_out = np.dot(positive_examples, weights)
neg_out = np.dot(negative_examples, weights)
pos_correct = (pos_out >= 0).sum() / float(pos_count)
neg_correct = (neg_out < 0).sum() / float(neg_count)
print("Iteration={}, pos correct={}, neg correct={}".format(i,pos_correct,neg_correct))
return weights**ملاحظة عن معدل التعلّم:** يتحكّم المعامل `learning_rate`، وقيمته الافتراضية `0.01`، بمقدار تعديل الأوزان في كل خطوة تدريب. وهذا يطبّق صيغة تحديث الانحدار المتدرج:
$$\mathbf{w}^{\tau + 1}=\mathbf{w}^{\tau} + \eta \mathbf{x}_{n} t_{n}$$
- معدل تعلّم أكبر، مثل `1.0`، يخلّي البيرسيبترون يتعلّم أسرع، لكنه قد يتجاوز الحل الأمثل.
- معدل تعلّم أصغر، مثل `0.001`، يكون أبطأ لكنه قد يتقارب بدقة أعلى.
- تقدرون تجرّبون باستدعاء: `train(pos_examples, neg_examples, learning_rate=0.1)`
الحين خلّونا نشغّل التدريب على مجموعة البيانات:
wts = train(pos_examples,neg_examples)
print(wts.transpose())مثل ما تشوفون، تبدأ الدقة حول 50%، وبعدها ترتفع بسرعة إلى قرابة 90%.
خلّونا نعرض فصل الفئتين بصريًا. دالة التصنيف عندنا هي $\mathbf{w}^Tx$؛ قيمتها أكبر من 0 لفئة، وأقل من 0 للفئة الثانية. لذلك يحدّد المعادلة $\mathbf{w}^Tx = 0$ خط الفصل بينهما. ولأن عندنا بُعدين بس، $x_0$ و$x_1$، تكون معادلة الخط $w_0x_0+w_1x_1+w_2 = 0$؛ تذكّروا إننا أضفنا البُعد $x_2=1$ صراحةً. خلّونا نرسم الخط:
def plot_boundary(positive_examples, negative_examples, weights):
if np.isclose(weights[1], 0):
if np.isclose(weights[0], 0):
x = y = np.array([-6, 6], dtype = 'float32')
else:
y = np.array([-6, 6], dtype='float32')
x = -(weights[1] * y + weights[2])/weights[0]
else:
x = np.array([-6, 6], dtype='float32')
y = -(weights[0] * x + weights[2])/weights[1]
pylab.xlim(-6, 6)
pylab.ylim(-6, 6)
pylab.plot(positive_examples[:,0], positive_examples[:,1], 'bo')
pylab.plot(negative_examples[:,0], negative_examples[:,1], 'ro')
pylab.plot(x, y, 'g', linewidth=2.0)
pylab.show()plot_boundary(pos_examples,neg_examples,wts)## تجربة معدلات التعلّم
الحين بنستكشف وشلون تأثّر معدلات التعلّم المختلفة على التدريب. معدل التعلّم يضبط حجم الخطوة في الانحدار المتدرج، وهو **معامل فائق** (hyperparameter) مهم يأثّر على سرعة التقارب واستقراره.
# Compare different learning rates
learning_rates = [0.001, 0.01, 0.1, 1.0]
fig, axes = pylab.subplots(2, 2, figsize=(12, 10))
fig.suptitle('Effect of Different Learning Rates', fontsize=16)
for idx, lr in enumerate(learning_rates):
ax = axes[idx // 2, idx % 2]
# Train with this learning rate
weights_lr = train(pos_examples, neg_examples, num_iterations=100, learning_rate=lr)
# Plot decision boundary
if np.isclose(weights_lr[1], 0):
if np.isclose(weights_lr[0], 0):
x = y = np.array([-6, 6], dtype='float32')
else:
y = np.array([-6, 6], dtype='float32')
x = -(weights_lr[1] * y + weights_lr[2])/weights_lr[0]
else:
x = np.array([-6, 6], dtype='float32')
y = -(weights_lr[0] * x + weights_lr[2])/weights_lr[1]
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.plot(pos_examples[:, 0], pos_examples[:, 1], 'bo', label='Positive', alpha=0.7)
ax.plot(neg_examples[:, 0], neg_examples[:, 1], 'ro', label='Negative', alpha=0.7)
ax.plot(x, y, 'g-', linewidth=2)
ax.set_title(f'Learning Rate = {lr}')
ax.set_xlabel('Feature 1')
ax.set_ylabel('Feature 2')
ax.legend()
ax.grid(True, alpha=0.3)
pylab.tight_layout()
pylab.show()### تجربة تفاعلية لمعدل التعلّم
استخدموا شريط التمرير تحت عشان تجرّبون معدلات تعلّم مختلفة وتشوفون أثرها على حد القرار:
def train_and_plot_with_lr(learning_rate=0.01):
"""Train perceptron with specified learning rate and plot results"""
weights_lr = train(pos_examples, neg_examples, num_iterations=100, learning_rate=learning_rate)
fig, (ax1, ax2) = pylab.subplots(1, 2, figsize=(14, 5))
# Plot 1: Decision boundary
if np.isclose(weights_lr[1], 0):
if np.isclose(weights_lr[0], 0):
x = y = np.array([-6, 6], dtype='float32')
else:
y = np.array([-6, 6], dtype='float32')
x = -(weights_lr[1] * y + weights_lr[2])/weights_lr[0]
else:
x = np.array([-6, 6], dtype='float32')
y = -(weights_lr[0] * x + weights_lr[2])/weights_lr[1]
ax1.set_xlim(-6, 6)
ax1.set_ylim(-6, 6)
ax1.plot(pos_examples[:, 0], pos_examples[:, 1], 'bo', label='Positive', s=100, alpha=0.6)
ax1.plot(neg_examples[:, 0], neg_examples[:, 1], 'ro', label='Negative', s=100, alpha=0.6)
ax1.plot(x, y, 'g-', linewidth=3, label='Decision Boundary')
ax1.set_title(f'Decision Boundary (lr={learning_rate})', fontsize=14)
ax1.set_xlabel('Feature 1')
ax1.set_ylabel('Feature 2')
ax1.legend()
ax1.grid(True, alpha=0.3)
# Plot 2: Weight values
ax2.bar(['w0', 'w1', 'bias'], weights_lr.flatten(), color=['blue', 'green', 'red'], alpha=0.7)
ax2.set_title('Final Weight Values', fontsize=14)
ax2.set_ylabel('Weight Value')
ax2.grid(True, alpha=0.3, axis='y')
ax2.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
pylab.tight_layout()
pylab.show()
print(f"Final weights: {weights_lr.flatten()}")
# Create interactive widget
interact(train_and_plot_with_lr,
learning_rate=widgets.FloatSlider(value=0.01, min=0.001, max=1.0, step=0.001,
description='Learning Rate:', continuous_update=False))## التقييم على مجموعة الاختبار
في البداية فصلنا جزءًا من مجموعة البيانات للاختبار. خلّونا نشوف دقة المصنّف على مجموعة البيانات هذي. نضيف لها بُعدًا جديدًا، ونضربها في مصفوفة الأوزان، ثم نتأكّد إن إشارة القيمة الناتجة تطابق التسمية (+1 أو -1). بعدها نجمع القيم المنطقية ونقسمها على عدد عينات الاختبار عشان نحصل على الدقة:
def accuracy(weights, test_x, test_labels):
res = np.dot(np.c_[test_x,np.ones(len(test_x))],weights)
return (res.reshape(test_labels.shape)*test_labels>=0).sum()/float(len(test_labels))
accuracy(wts, test_x, test_labels)## متابعة عملية التدريب
شفنا قبل وشلون ترتفع الدقة أثناء التدريب. والحين بنشوف وشلون يتغيّر خط الفصل خلاله. الكود تحت يعرض كل شي في رسم بياني واحد، وتقدرون تحرّكون شريط التمرير كأنكم «تسافرون عبر الزمن» بين مراحل التدريب.
def train_graph(positive_examples, negative_examples, num_iterations = 100, learning_rate = 0.01):
num_dims = positive_examples.shape[1]
weights = np.zeros((num_dims,1)) # initialize weights
pos_count = positive_examples.shape[0]
neg_count = negative_examples.shape[0]
report_frequency = 15;
snapshots = []
for i in range(num_iterations):
pos = random.choice(positive_examples)
neg = random.choice(negative_examples)
z = np.dot(pos, weights)
if z < 0:
weights = weights + learning_rate * pos.reshape(weights.shape)
z = np.dot(neg, weights)
if z >= 0:
weights = weights - learning_rate * neg.reshape(weights.shape)
if i % report_frequency == 0:
pos_out = np.dot(positive_examples, weights)
neg_out = np.dot(negative_examples, weights)
pos_correct = (pos_out >= 0).sum() / float(pos_count)
neg_correct = (neg_out < 0).sum() / float(neg_count)
snapshots.append([np.copy(weights).flatten(), (pos_correct+neg_correct)/2.0])
return np.array(snapshots, dtype=object)
snapshots = train_graph(pos_examples,neg_examples)
def plotit(pos_examples,neg_examples,snapshots,step):
fig = pylab.figure(figsize=(10,4))
fig.add_subplot(1, 2, 1)
plot_boundary(pos_examples, neg_examples, snapshots[step][0])
fig.add_subplot(1, 2, 2)
pylab.plot(np.arange(len(snapshots[:,1])), snapshots[:,1])
pylab.ylabel('Accuracy')
pylab.xlabel('Iteration')
pylab.plot(step, snapshots[step,1], "bo")
pylab.show()
def pl1(step): plotit(pos_examples,neg_examples,snapshots,step)interact(pl1, step=widgets.IntSlider(value=0, min=0, max=len(snapshots)-1))## قيود بيرسيبترون
مثل ما شفتوا فوق، البيرسيبترون **مصنّف خطي**. يقدر يفرّق بين فئتين زين إذا كانت الفئتان **قابلتين للفصل خطيًا**، يعني نقدر نفصل بينهما بخط مستقيم. وإذا ما كانت كذا، فلن تتقارب عملية تدريب البيرسيبترون.
أوضح مثال لمسألة ما يقدر البيرسيبترون يحلّها هو **مسألة XOR**. نبي البيرسيبترون يتعلّم الدالة المنطقية XOR، واللي لها جدول الحقيقة التالي:
| | 0 | 1 |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 0 |
خلّونا نجرّب! بنجهّز عينات التدريب الإيجابية والسلبية يدويًا، وبعدها نستدعي دالة التدريب اللي عرّفناها فوق:
pos_examples_xor = np.array([[1,0,1],[0,1,1]])
neg_examples_xor = np.array([[1,1,1],[0,0,1]])
snapshots_xor = train_graph(pos_examples_xor,neg_examples_xor,1000)
def pl2(step): plotit(pos_examples_xor,neg_examples_xor,snapshots_xor,step)interact(pl2, step=widgets.IntSlider(value=0, min=0, max=len(snapshots)-1))مثل ما تشوفون في الرسم فوق، ما تتعدّى الدقة 75%؛ لأن ما فيه خط مستقيم يقدر يصنّف كل الأمثلة صح.
مسألة XOR مثال كلاسيكي على قيود البيرسيبترون. أشار لها مارفن مينسكي وسيمور بابيرت سنة 1969 في كتابهما [Perceptrons](https://en.wikipedia.org/wiki/Perceptrons_(book)). هالملاحظة حدّت من أبحاث الشبكات العصبية قرابة 10 سنوات، مع إن البيرسيبترون متعدد الطبقات يقدر يحل مسائل مثلها بكل كفاءة، وهذا اللي بنشوفه في القسم الجاي من المنهج.
## مثال أعقد: MNIST
مع إن البيرسيبترون ما يقدر يحل مسألة XOR، فهو يقدر يحل مسائل أعقد بكثير، مثل التعرّف على المحارف المكتوبة بخط اليد.
من مجموعات البيانات اللي تُستخدم كثير في تعلم الآلة مجموعة [MNIST](https://en.wikipedia.org/wiki/MNIST_database). أنشأها المعهد الوطني المعدّل للمعايير والتقنية، وفيها مجموعة تدريب من 60000 رقم مكتوب بخط اليد، جُمعت من قرابة 250 طالبًا وموظفًا في المعهد. وفيها بعد مجموعة اختبار من 10000 رقم جُمعت من أشخاص مختلفين.
كل رقم ممثّل بصورة بتدرّج رمادي مقاسها 28x28 بكسل.
> مجموعة MNIST متاحة كتحدّي تدريبي على [Kaggle](https://www.kaggle.com/c/digit-recognizer)، وهو موقع يستضيف مسابقات تعلّم الآلة. عقب ما تتعلّمون تصنيف أرقام MNIST، تقدرون ترفعون حلّكم على Kaggle وتشوفون ترتيبه بين حلول المشاركين.
نبدأ بتهيئة عيّنة الأرقام المدمجة:
> تستخدم هالنسخة عيّنة الأرقام المكتوبة بخط اليد المدمجة مع scikit-learn، وتكبّر صورها من 8×8 إلى 28×28 بطريقة ثابتة. العيّنة أصغر من MNIST وما تحتاج اتصال بالشبكة؛ لذلك تختلف أعداد العينات والنتائج عن تمرين MNIST الأصلي.
_course_digits = load_digits()
_course_images = np.kron(_course_digits.images.astype(np.float32) / 16.0, np.ones((3, 3), dtype=np.float32))
_course_images = np.pad(_course_images, ((0, 0), (2, 2), (2, 2)))
mnist = SimpleNamespace(
data=(_course_images.reshape(len(_course_images), -1) * 255.0).astype(np.float32),
target=np.asarray(_course_digits.target, dtype=np.int64),
)
all_features = np.asarray(mnist.data, dtype=np.float32)
all_labels = np.asarray(mnist.target, dtype=np.int64)
train_size = int(0.8 * len(all_features))
MNIST = {
"Train": {
"Features": all_features[:train_size],
"Labels": all_labels[:train_size],
},
"Test": {
"Features": all_features[train_size:],
"Labels": all_labels[train_size:],
},
}الحين خلّونا نرسم مجموعة البيانات:
print(MNIST['Train']['Features'][0][130:180])
print(MNIST['Train']['Labels'][0])
features = MNIST['Train']['Features'].astype(np.float32) / 255.0
labels = MNIST['Train']['Labels']
fig = pylab.figure(figsize=(10,5))
for i in range(10):
ax = fig.add_subplot(1,10,i+1)
pylab.imshow(features[i].reshape(28,28))
pylab.show()لأن البيرسيبترون مصنّف ثنائي، بنحصر المسألة في التعرّف على رقمين بس. الدالة تحت تملأ مصفوفتَي العينات الإيجابية والسلبية بالرقمين المحددين، وتعرض عينات منهما للتوضيح.
def set_mnist_pos_neg(positive_label, negative_label):
positive_indices = [i for i, j in enumerate(MNIST['Train']['Labels'])
if j == positive_label]
negative_indices = [i for i, j in enumerate(MNIST['Train']['Labels'])
if j == negative_label]
positive_images = MNIST['Train']['Features'][positive_indices]
negative_images = MNIST['Train']['Features'][negative_indices]
fig = pylab.figure()
ax = fig.add_subplot(1, 2, 1)
pylab.imshow(positive_images[0].reshape(28,28), cmap='gray', interpolation='nearest')
ax.set_xticks([])
ax.set_yticks([])
ax = fig.add_subplot(1, 2, 2)
pylab.imshow(negative_images[0].reshape(28,28), cmap='gray', interpolation='nearest')
ax.set_xticks([])
ax.set_yticks([])
pylab.show()
return positive_images, negative_imagesبنبدأ بمحاولة التفريق بين 0 و 1:
pos1,neg1 = set_mnist_pos_neg(1,0)def plotit2(snapshots_mn,step):
fig = pylab.figure(figsize=(10,4))
ax = fig.add_subplot(1, 2, 1)
pylab.imshow(snapshots_mn[step][0].reshape(28, 28), interpolation='nearest')
ax.set_xticks([])
ax.set_yticks([])
pylab.colorbar()
ax = fig.add_subplot(1, 2, 2)
ax.set_ylim([0,1])
pylab.plot(np.arange(len(snapshots_mn[:,1])), snapshots_mn[:,1])
pylab.plot(step, snapshots_mn[step,1], "bo")
pylab.show()
def pl3(step): plotit2(snapshots_mn,step)
def pl4(step): plotit2(snapshots_mn2,step)snapshots_mn = train_graph(pos1,neg1,1000)
interact(pl3, step=widgets.IntSlider(value=0, min=0, max=len(snapshots_mn) - 1))لاحظوا وشلون ترتفع الدقة بسرعة إلى قرابة 100%.
حرّكوا شريط التمرير إلى نقطة قريبة من نهاية التدريب، وراقبوا مصفوفة الأوزان المرسومة على اليسار. هالمصفوفة توضّح لكم وشلون يشتغل البيرسيبترون فعليًا. بتشوفون أوزانًا عالية وسط الحقل، وهي تقابل البكسلات اللي تظهر غالبًا في الرقم 1، وأوزانًا سالبة على الجوانب حيث تظهر أجزاء الرقم 0. فإذا كان الرقم المعروض على البيرسيبترون هو 1 فعلًا، تُضرب بكسلات وسطه بقيم عالية ويطلع الناتج موجبًا. والعكس يوم يشوف البيرسيبترون رقم 0: تُضرب البكسلات المقابلة بأرقام سالبة.
> قد تلاحظون إننا لو أعطينا البيرسيبترون رقم 1 مزاحًا شوي أفقيًا، بحيث تجي بكسلاته في مواقع الأجزاء العمودية للرقم 0، فقد يعطينا نتيجة خطأ. طبيعة مجموعة البيانات MNIST إن الأرقام فيها متمركزة وموضوعة بطريقة منتظمة، والبيرسيبترون يعتمد على هالترتيب عشان يفرّق بينها.
الحين خلّونا نجرّب رقمين مختلفين:
pos2,neg2 = set_mnist_pos_neg(2,5)snapshots_mn2 = train_graph(pos2,neg2,1000)
interact(pl4, step=widgets.IntSlider(value=0, min=0, max=len(snapshots_mn2) - 1))## مناقشة النتائج
فصل الرقمين 2 و 5 مب سهل. ومع إن الدقة عالية نسبيًا، فوق 85%، نقدر نشوف بوضوح إن البيرسيبترون يوقف تعلّم عند نقطة معيّنة.
عشان نفهم السبب، نقدر نستخدم [تحليل المكوّنات الرئيسية](https://en.wikipedia.org/wiki/Principal_component_analysis) (Principal Component Analysis — PCA). هذي تقنية من تعلم الآلة تقلّل أبعاد مجموعة البيانات المدخلة بطريقة تحافظ قدر الإمكان على قابلية الفصل بين الفئات.
في حالتنا، صورة الإدخال فيها 784 بكسل، وهي سمات الإدخال، ونبي نستخدم PCA عشان نقلّل عدد المعاملات إلى 2 بس فنقدر نرسمهما. المعاملان تركيب خطي من السمات الأصلية. ونقدر نتخيّل العملية كأننا «ندوّر» الفضاء الأصلي ذي 784 بُعدًا ونراقب إسقاطه على فضاء 2D إلى أن نلقى المنظور اللي يفصل الفئات بأفضل شكل.
from sklearn.decomposition import PCA
def pca_analysis(positive_label, negative_label):
positive_images, negative_images = set_mnist_pos_neg(positive_label, negative_label)
M = np.append(positive_images, negative_images, 0)
mypca = PCA(n_components=2)
mypca.fit(M)
pos_points = mypca.transform(positive_images[:200])
neg_points = mypca.transform(negative_images[:200])
pylab.plot(pos_points[:,0], pos_points[:,1], 'bo')
pylab.plot(neg_points[:,0], neg_points[:,1], 'ro')pca_analysis(1,0)pca_analysis(2,5)مثل ما تشوفون، نقدر نفصل الرقمين 0 و 1 بوضوح بخط مستقيم. وهذا يدل إن النقاط اللي تمثّل الرقمين قابلة للفصل خطيًا بعد في الفضاء الأصلي ذي 784 بُعدًا. أما مع 2 و 5، فما نلقى إسقاطًا يفصل الرقمين بوضوح، ولذلك تطلع بعض حالات التصنيف خطأ.
> لاحقًا بالمنهج بنتعلّم وشلون نبني مصنّفات غير خطية باستخدام الشبكات العصبية، ووشلون نتعامل مع الأرقام إذا ما كانت محاذاتها مضبوطة. وقريب بنوصل إلى دقة تتجاوز 99% في تصنيف أرقام MNIST إلى 10 فئات مختلفة.
## الزبدة
* تعرّفنا على أبسط بنية للشبكات العصبية: بيرسيبترون بطبقة وحدة.
* طبّقنا البيرسيبترون «يدويًا» بإجراء تدريب بسيط يعتمد على الانحدار المتدرج.
* مع بساطته، يقدر البيرسيبترون ذو الطبقة الوحدة يحل مسائل معقّدة نسبيًا مثل التعرّف على الأرقام المكتوبة بخط اليد.
* البيرسيبترون ذو الطبقة الوحدة مصنّف خطي، ولذلك عنده قوة التصنيف نفسها اللي عند الانحدار اللوجستي.
* في فضاء العينات، يقدر البيرسيبترون يفصل فئتين من بيانات الإدخال باستخدام مستوى فائق (hyperplane).
## الشكر والتقدير
هالدفتر جزء من منهج AI for Beginners.
حذفنا المخرجات وعدّادات التشغيل والودجات والمحتوى النشط وقت الاستيراد. شغّل الدفاتر بس في بيئة خارجية تثق فيها.
سجّل تطبيقك
التسجيل اختياري، يفيدك تتذكر وش طبّقت، ولا يمنع إكمال الدورة.