So erstellen Sie mit PyTorch ein neuronales Netzwerk von Grund auf neu

In diesem Artikel werden wir unter die Haube neuronaler Netze gehen, um zu lernen, wie man eines von Grund auf aufbaut.

Das einzige, was mich am tiefsten beim Lernen begeistert, ist das Basteln mit Code, um etwas von Grund auf neu zu erstellen. Es ist jedoch keine leichte Aufgabe, und es ist noch schwieriger, jemand anderem beizubringen, wie man das macht.

Ich habe mich durch den Fast.ai-Kurs gearbeitet und dieser Blog ist stark von meiner Erfahrung inspiriert.

Beginnen wir ohne weitere Verzögerung unsere wunderbare Reise zur Entmystifizierung neuronaler Netze.

Wie funktioniert ein neuronales Netzwerk?

Beginnen wir mit dem Verständnis der Funktionsweise neuronaler Netze auf hoher Ebene.

Ein neuronales Netzwerk nimmt einen Datensatz auf und gibt eine Vorhersage aus. So einfach ist das.

Lassen Sie mich Ihnen ein Beispiel geben.

Nehmen wir an, einer Ihrer Freunde (der kein großer Fußballfan ist) zeigt auf ein altes Bild eines berühmten Fußballspielers - sagen wir Lionel Messi - und fragt Sie nach ihm.

Sie können den Fußballer in einer Sekunde identifizieren. Der Grund ist, dass Sie seine Bilder schon tausend Mal gesehen haben. So können Sie ihn auch dann identifizieren, wenn das Bild alt ist oder bei schwachem Licht aufgenommen wurde.

Aber was passiert, wenn ich Ihnen ein Bild eines berühmten Baseballspielers zeige (und Sie noch nie ein einziges Baseballspiel gesehen haben)? Sie können diesen Spieler nicht erkennen. Selbst wenn das Bild klar und hell ist, wissen Sie in diesem Fall nicht, wer es ist.

Dies ist das gleiche Prinzip, das für neuronale Netze verwendet wird. Wenn unser Ziel darin besteht, ein neuronales Netzwerk zur Erkennung von Katzen und Hunden aufzubauen, zeigen wir dem neuronalen Netzwerk nur eine Reihe von Bildern von Hunden und Katzen.

Insbesondere zeigen wir die neuronalen Netzwerkbilder von Hunden und sagen dann, dass es sich um Hunde handelt. Und dann zeigen Sie ihm Bilder von Katzen und identifizieren Sie diese als Katzen.

Sobald wir unser neuronales Netzwerk mit Bildern von Katzen und Hunden trainiert haben, kann es leicht klassifizieren, ob ein Bild eine Katze oder einen Hund enthält. Kurz gesagt, es kann eine Katze von einem Hund erkennen.

Wenn Sie unserem neuronalen Netzwerk jedoch ein Bild eines Pferdes oder eines Adlers zeigen, wird es niemals als Pferd oder Adler identifiziert. Dies liegt daran, dass es noch nie ein Bild von einem Pferd oder Adler gesehen hat, weil wir ihm diese Tiere noch nie gezeigt haben.

Wenn Sie die Fähigkeit des neuronalen Netzwerks verbessern möchten, müssen Sie lediglich Bilder aller Tiere anzeigen, die das neuronale Netzwerk klassifizieren soll. Ab sofort weiß es nur noch Katzen und Hunde und sonst nichts.

Der Datensatz, den wir für unser Training verwenden, hängt stark vom Problem unserer Hände ab. Wenn Sie klassifizieren möchten, ob ein Tweet eine positive oder negative Stimmung hat, möchten Sie wahrscheinlich einen Datensatz, der viele Tweets mit der entsprechenden Bezeichnung als positiv oder negativ enthält.

Nachdem Sie nun einen allgemeinen Überblick über Datensätze haben und wissen, wie ein neuronales Netzwerk aus diesen Daten lernt, wollen wir uns eingehender mit der Funktionsweise neuronaler Netzwerke befassen.

Neuronale Netze verstehen

Wir werden ein neuronales Netzwerk aufbauen, um die Ziffern drei und sieben aus einem Bild zu klassifizieren.

Bevor wir jedoch unser neuronales Netzwerk aufbauen, müssen wir tiefer gehen, um zu verstehen, wie sie funktionieren.

Jedes Bild, das wir an unser neuronales Netzwerk weitergeben, besteht nur aus einer Reihe von Zahlen. Das heißt, jedes unserer Bilder hat eine Größe von 28 × 28, was bedeutet, dass es wie eine Matrix 28 Zeilen und 28 Spalten hat.

Wir sehen jede der Ziffern als vollständiges Bild, aber für ein neuronales Netzwerk sind es nur eine Reihe von Zahlen zwischen 0 und 255.

Hier ist eine Pixeldarstellung der fünften Ziffer:

Wie Sie oben sehen können, haben wir 28 Zeilen und 28 Spalten (der Index beginnt bei 0 und endet bei 27), genau wie eine Matrix. Neuronale Netze sehen nur diese 28 × 28-Matrizen.

Um weitere Details anzuzeigen, habe ich gerade den Farbton zusammen mit den Pixelwerten angezeigt. Wenn Sie sich das Bild genauer ansehen, sehen Sie, dass die Pixelwerte nahe 255 dunkler sind, während die Werte näher an 0 heller im Schatten sind.

In PyTorch verwenden wir nicht den Begriff Matrix. Stattdessen verwenden wir den Begriff Tensor. Jede Zahl in PyTorch wird als Tensor dargestellt. Von nun an werden wir den Begriff Tensor anstelle der Matrix verwenden.

Visualisierung eines neuronalen Netzwerks

Ein neuronales Netzwerk kann eine beliebige Anzahl von Neuronen und Schichten aufweisen.

So sieht ein neuronales Netzwerk aus:

Lassen Sie sich nicht von den griechischen Buchstaben auf dem Bild verwirren. Ich werde es für Sie aufschlüsseln:

Nehmen Sie den Fall der Vorhersage, ob ein Patient überleben wird oder nicht, basierend auf einem Datensatz, der den Namen des Patienten, die Temperatur, den Blutdruck, den Herzzustand, das monatliche Gehalt und das Alter enthält.

In unserem Datensatz sind nur die Temperatur, der Blutdruck, der Herzzustand und das Alter von erheblicher Bedeutung für die Vorhersage, ob der Patient überleben wird oder nicht. Daher werden wir diesen Werten einen höheren Gewichtswert zuweisen, um eine höhere Bedeutung zu zeigen.

Merkmale wie der Name des Patienten und das monatliche Gehalt haben jedoch keinen oder nur geringen Einfluss auf die Überlebensrate des Patienten. Daher weisen wir diesen Merkmalen kleinere Gewichtswerte zu, um eine geringere Bedeutung zu zeigen.

In der obigen Abbildung sind x1, x2, x3 ... xn die Merkmale in unserem Datensatz, die im Fall von Bilddaten Pixelwerte oder Merkmale wie Blutdruck oder Herzerkrankung wie im obigen Beispiel sein können.

Die Merkmalswerte werden mit den entsprechenden Gewichtswerten multipliziert, die als w1j, w2j, w3j ... wnj bezeichnet werden. Die multiplizierten Werte werden summiert und an die nächste Schicht übergeben.

Die optimalen Gewichtswerte werden während des Trainings des neuronalen Netzwerks gelernt. Die Gewichtswerte werden kontinuierlich aktualisiert, um die Anzahl der korrekten Vorhersagen zu maximieren.

Die Aktivierungsfunktion ist in unserem Fall nichts anderes als die Sigmoidfunktion. Jeder Wert, den wir an das Sigmoid übergeben, wird in einen Wert zwischen 0 und 1 umgewandelt. Wir setzen einfach die Sigmoid-Funktion auf unsere Vorhersage des neuronalen Netzwerks, um einen Wert zwischen 0 und 1 zu erhalten.

You will understand the importance of the sigmoid layer once we start building our neural network model.

There are a lot of other activation functions that are even simpler to learn than sigmoid.

This is the equation for a sigmoid function:

The circular-shaped nodes in the diagram are called neurons. At each layer of the neural network, the weights are multiplied with the input data.

We can increase the depth of the neural network by increasing the number of layers. We can improve the capacity of a layer by increasing the number of neurons in that layer.

Understanding our data set

The first thing we need in order to train our neural network is the data set.

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.

Since our model is very small, it doesn't take much time to train for 2000 epochs or iterations. After 2000 epochs, our neural netwok has given a loss value of 0.6805 which is not bad from such a small model.

Conclusion

There is a huge space for improvement in the model that we've just created.

This is just a simple model, and you can experiment on it by increasing the number of layers, number of neurons in each layer, or increasing the number of epochs.

In short, machine learning is a whole lot of magic using math. Always learn the foundational concepts – they may be boring, but eventually you will understand that those boring math concepts created these cutting edge technologies like deepfakes.

You can get the complete code on GitHub or play with the code in Google colab.