كيفية بناء شبكة عصبية من الصفر باستخدام PyTorch

في هذه المقالة ، سنخفي الشبكات العصبية لمعرفة كيفية بناء واحدة من الألف إلى الياء.

الشيء الوحيد الذي يثيرني أكثر في التعلم العميق هو العبث بالكود لبناء شيء ما من الصفر. إنها ليست مهمة سهلة ، وتعليم شخص آخر كيفية القيام بذلك أكثر صعوبة.

لقد كنت أعمل في طريقي من خلال دورة Fast.ai وهذه المدونة مستوحاة بشكل كبير من تجربتي.

بدون مزيد من التأخير ، لنبدأ رحلتنا الرائعة لإزالة الغموض عن الشبكات العصبية.

كيف تعمل الشبكة العصبية؟

لنبدأ بفهم طريقة عمل الشبكات العصبية عالية المستوى.

تأخذ الشبكة العصبية مجموعة بيانات وتخرج تنبؤًا. إنها بهذه السهولة.

اسمحوا لي أن أقدم لكم مثالا.

لنفترض أن أحد أصدقائك (وهو ليس من مشجعي كرة القدم) يشير إلى صورة قديمة للاعب كرة قدم مشهور - قل ليونيل ميسي - ويسألك عنه.

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

ولكن ماذا يحدث إذا عرضت عليك صورة لاعب بيسبول مشهور (ولم ترَ مباراة بيسبول واحدة من قبل)؟ لن تتمكن من التعرف على هذا اللاعب. في هذه الحالة ، حتى لو كانت الصورة واضحة ومشرقة ، فلن تعرف من تكون.

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

وبشكل أكثر تحديدًا ، نعرض صور الشبكة العصبية للكلاب ثم نخبرها أنها كلاب. ثم اعرض عليها صور القطط ، وحددها على أنها قطط.

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

ولكن إذا عرضت على شبكتنا العصبية صورة حصان أو نسر ، فلن تحدده أبدًا على أنه حصان أو نسر. هذا لأنه لم يسبق له أن رأى صورة حصان أو نسر من قبل لأننا لم نعرضها أبدًا على تلك الحيوانات.

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

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

الآن بعد أن أصبح لديك نظرة عامة عالية المستوى على مجموعات البيانات وكيف تتعلم الشبكة العصبية من تلك البيانات ، دعنا نتعمق أكثر في كيفية عمل الشبكات العصبية.

فهم الشبكات العصبية

سنقوم ببناء شبكة عصبية لتصنيف الرقمين ثلاثة وسبعة من الصورة.

لكن قبل أن نبني شبكتنا العصبية ، نحتاج إلى التعمق أكثر لفهم كيفية عملها.

كل صورة نمررها إلى شبكتنا العصبية هي مجرد مجموعة من الأرقام. أي أن حجم كل صورة من صورنا 28 × 28 مما يعني أنها تحتوي على 28 صفًا و 28 عمودًا ، تمامًا مثل المصفوفة.

نرى كل رقم كصورة كاملة ، لكن بالنسبة للشبكة العصبية ، فهو مجرد مجموعة من الأرقام تتراوح من 0 إلى 255.

فيما يلي تمثيل البكسل للرقم خمسة:

كما ترى أعلاه ، لدينا 28 صفًا و 28 عمودًا (يبدأ الفهرس من 0 وينتهي عند 27) تمامًا مثل المصفوفة. ترى الشبكات العصبية فقط هذه المصفوفات 28 × 28.

لإظهار المزيد من التفاصيل ، لقد أظهرت للتو الظل مع قيم البكسل. إذا نظرت عن قرب إلى الصورة ، يمكنك أن ترى أن قيم البكسل القريبة من 255 أغمق بينما القيم الأقرب إلى 0 تكون أفتح في الظل.

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

تصور الشبكة العصبية

يمكن أن تحتوي الشبكة العصبية على أي عدد من الخلايا العصبية والطبقات.

هكذا تبدو الشبكة العصبية:

لا ترتبك من الحروف اليونانية في الصورة. سوف أقسمها لك:

خذ حالة التنبؤ بما إذا كان المريض سينجو أم لا بناءً على مجموعة بيانات تحتوي على اسم المريض ودرجة الحرارة وضغط الدم وحالة القلب والراتب الشهري والعمر.

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

لكن سمات مثل اسم المريض والراتب الشهري لها تأثير ضئيل أو معدوم على معدل بقاء المريض. لذلك قمنا بتعيين قيم وزن أصغر لهذه الميزات لإظهار أهمية أقل.

في الشكل أعلاه ، x1 ، x2 ، x3 ... xn هي الميزات الموجودة في مجموعة البيانات الخاصة بنا والتي قد تكون قيم بكسل في حالة بيانات الصورة أو ميزات مثل ضغط الدم أو حالة القلب كما في المثال أعلاه.

يتم ضرب قيم السمات في قيم الوزن المقابلة المشار إليها باسم w1j ، w2j ، w3j ... يتم جمع القيم المضاعفة معًا وتمريرها إلى الطبقة التالية.

يتم التعرف على قيم الوزن المثلى أثناء تدريب الشبكة العصبية. يتم تحديث قيم الوزن بشكل مستمر بطريقة تزيد من عدد التنبؤات الصحيحة.

وظيفة التنشيط ليست سوى وظيفة السيني في حالتنا. يتم تحويل أي قيمة نمررها إلى السيني إلى قيمة بين 0 و 1. نحن فقط نضع الدالة السينية فوق تنبؤ الشبكة العصبية لدينا للحصول على قيمة بين 0 و 1.

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

هناك الكثير من وظائف التنشيط الأخرى التي يسهل تعلمها أكثر من السيني.

هذه هي معادلة الدالة السينية:

تسمى العقد ذات الشكل الدائري في الرسم البياني بالخلايا العصبية. في كل طبقة من طبقات الشبكة العصبية ، يتم ضرب الأوزان ببيانات الإدخال.

يمكننا زيادة عمق الشبكة العصبية عن طريق زيادة عدد الطبقات. يمكننا تحسين قدرة الطبقة عن طريق زيادة عدد الخلايا العصبية في تلك الطبقة.

فهم مجموعة البيانات لدينا

أول شيء نحتاجه لتدريب شبكتنا العصبية هو مجموعة البيانات.

Since the goal of our neural network is to classify whether an image contains the number three or seven, we need to train our neural network with images of threes and sevens. So, let's build our data set.

Luckily, we don't have to create the data set from scratch. Our data set is already present in PyTorch. All we have to do is just download it and do some basic operations on it.

We need to download a data set called MNIST(Modified National Institute of Standards and Technology) from the torchvision library of PyTorch.

Now let's dig deeper into our data set.

What is the MNIST data set?

The MNIST data set contains handwritten digits from zero to nine with their corresponding labels as shown below:

So, what we do is simply feed the neural network the images of the digits and their corresponding labels which tell the neural network that this is a three or seven.

How to prepare our data set

The downloaded MNIST data set has images and their corresponding labels.

We just write the code to index out only the images with a label of three or seven. Thus, we get a data set of threes and sevens.

First, let's import all the necessary libraries.

import torch from torchvision import datasets import matplotlib.pyplot as plt

We import the PyTorch library for building our neural network and the torchvision library for downloading the MNIST data set, as discussed before. The Matplotlib library is used for displaying images from our data set.

Now, let's prepare our data set.

mnist = datasets.MNIST('./data', download=True) threes = mnist.data[(mnist.targets == 3)]/255.0 sevens = mnist.data[(mnist.targets == 7)]/255.0 len(threes), len(sevens)

As we learned above, everything in PyTorch is represented as tensors. So our data set is also in the form of tensors.

We download the data set in the first line. We index out only the images whose target value is equal to 3 or 7 and normalize them by dividing with 255 and store them separately.

We can check whether our indexing was done properly by running the code in the last line which gives the number of images in the threes and sevens tensor.

Now let's check whether we've prepared our data set correctly.

def show_image(img): plt.imshow(img) plt.xticks([]) plt.yticks([]) plt.show() show_image(threes[3]) show_image(sevens[8])

Using the Matplotlib library, we create a function to display the images.

Let's do a quick sanity check by printing the shape of our tensors.

print(threes.shape, sevens.shape)

If everything went right, you will get the size of threes and sevens as ([6131, 28, 28]) and ([6265, 28, 28]) respectively. This means that we have 6131 28×28 sized images for threes and 6265 28×28 sized images for sevens.

We've created two tensors with images of threes and sevens. Now we need to combine them into a single data set to feed into our neural network.

combined_data = torch.cat([threes, sevens]) combined_data.shape

We will concatenate the two tensors using PyTorch and check the shape of the combined data set.

Now we will flatten the images in the data set.

flat_imgs = combined_data.view((-1, 28*28)) flat_imgs.shape

We will flatten the images in such a way that each of the 28×28 sized images becomes a single row with 784 columns (28×28=784). Thus the shape gets converted to ([12396, 784]).

We need to create labels corresponding to the images in the combined data set.

target = torch.tensor([1]*len(threes)+[2]*len(sevens)) target.shape

We assign the label 1 for images containing a three, and the label 0 for images containing a seven.

How to train your Neural Network

To train your neural network, follow these steps.

Step 1: Building the model

Below you can see the simplest equation that shows how neural networks work:

                                y = Wx + b

Here, the term 'y' refers to our prediction, that is, three or seven. 'W' refers to our weight values, 'x' refers to our input image, and 'b' is the bias (which, along with weights, help in making predictions).

In short, we multiply each pixel value with the weight values and add them to the bias value.

The weights and bias value decide the importance of each pixel value while making predictions.  

We are classifying three and seven, so we have only two classes to predict.

So, we can predict 1 if the image is three and 0 if the image is seven. The prediction we get from that step may be any real number, but we need to make our model (neural network) predict a value between 0 and 1.

This allows us to create a threshold of 0.5. That is, if the predicted value is less than 0.5 then it is a seven. Otherwise it is a three.

We use a sigmoid function to get a value between 0 and 1.

We will create a function for sigmoid using the same equation shown earlier. Then we pass in the values from the neural network into the sigmoid.

We will create a single layer neural network.

We cannot create a lot of loops to multiply each weight value with each pixel in the image, as it is very expensive. So we can use a magic trick to do the whole multiplication in one go by using matrix multiplication.

def sigmoid(x): return 1/(1+torch.exp(-x)) def simple_nn(data, weights, bias): return sigmoid(([email protected]) + bias)

Step 2: Defining the loss

Now, we need a loss function to calculate by how much our predicted value is different from that of the ground truth.

For example, if the predicted value is 0.3 but the ground truth is 1, then our loss is very high. So our model will try to reduce this loss by updating the weights and bias so that our predictions become close to the ground truth.

We will be using mean squared error to check the loss value. Mean squared error finds the mean of the square of the difference between the predicted value and the ground truth.

def error(pred, target): return ((pred-target)**2).mean()

Step 3: Initialize the weight values

We just randomly initialize the weights and bias. Later, we will see how these values are updated to get the best predictions.

w = torch.randn((flat_imgs.shape[1], 1), requires_grad=True) b = torch.randn((1, 1), requires_grad=True)

The shape of the weight values should be in the following form:

(Number of neurons in the previous layer, number of neurons in the next layer)

We use a method called gradient descent to update our weights and bias to make the maximum number of correct predictions.

Our goal is to optimize or decrease our loss, so the best method is to calculate gradients.

We need to take the derivative of each and every weight and bias with respect to the loss function. Then we have to subtract this value from our weights and bias.

In this way, our weights and bias values are updated in such a way that our model makes a good prediction.

Updating a parameter for optimizing a function is not a new thing – you can optimize any arbitrary function using gradients.

We've set a special parameter (called requires_grad) to true to calculate the gradient of weights and bias.

Step 4: Update the weights

If our prediction does not come close to the ground truth, that means that we've made an incorrect prediction. This means that our weights are not correct. So we need to update our weights until we get good predictions.

For this purpose, we put all of the above steps inside a for loop and allow it to iterate any number of times we wish.

At each iteration, the loss is calculated and the weights and biases are updated to get a better prediction on the next iteration.

Thus our model becomes better after each iteration by finding the optimal weight value suitable for our task in hand.

Each task requires a different set of weight values, so we can't expect our neural network trained for classifying animals to perform well on musical instrument classification.

This is how our model training looks like:

for i in range(2000): pred = simple_nn(flat_imgs, w, b) loss = error(pred, target.unsqueeze(1)) loss.backward() w.data -= 0.001*w.grad.data b.data -= 0.001*b.grad.data w.grad.zero_() b.grad.zero_() print("Loss: ", loss.item())

We will calculate the predictions and store it in the 'pred' variable by calling the function that we've created earlier. Then we calculate the mean squared error loss.

Then, we will calculate all the gradients for our weights and bias and update the value using those gradients.

We've multiplied the gradients by 0.001, and this is called learning rate. This value decides the rate at which our model will learn, if it is too low, then the model will learn slowly, or in other words, the loss will be reduced slowly.

If the learning rate is too high, our model will not be stable, jumping between a wide range of loss values. This means it will fail to converge.

We do the above steps for 2000 times, and each time our model tries to reduce the loss by updating the weights and bias values.

We should zero out the gradients at the end of each loop or epoch so that there is no accumulation of unwanted gradients in the memory which will affect our model's learning.

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

استنتاج

هناك مساحة كبيرة للتحسين في النموذج الذي أنشأناه للتو.

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

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

يمكنك الحصول على الكود الكامل على GitHub أو اللعب بالرمز في Google colab.