When I wrote this blog post (this Pytorch tutorial), I remembered the challenge I set for myself at the beginning of the year to learn deep learning, I did not even know Python at the time. What makes things difficult is not necessarily the complexity of the concepts, but it starts with questions like: What framework to use for deep learning? Which activation function should I choose? Which cost function is best suited for my problem?
My personal experience has been to study the PyTorch framework and especially for the theory the online course made available for free by Yan LeCun whom I thank (link here Website fr.wikipedia.org) . I still have to learn and work in the field but through this blog post, I would like to share and give you an overview of what I have learned about deep learning this year.
The objective of this article is to sweep through this central topic in DeepLearning of choosing the activation and cost (loss) function according to the problem you are looking to solve by means of a DeepLearning algorithm.
We are talking about the activation function of the last layer of your model, that is to say the one that gives you the result.
This result is used in the algorithm which checks for each learning the difference between the result predicted by the neural network and the real result (gradient descent algorithm), for this we apply to the result a cost function (loss function) which represents this difference and which you seek to minimize as you practice in order to reduce the error. (See this article here to learn more : Website machinelearnia.com)
As a reminder, the machine learns by minimizing the cost function, iteratively by successive training steps, the result of the cost function and taken into account for the adjustment of the parameters of the neurons (weight and bias for example for linear layers) .
This choice of activation function on the last layer and the cost function to be minimized (loss function) is therefore crucial since these two elements combined will determine how you are going to solve a problem using your neural network.
This article also presents simple examples that use the Pytorch framework in my opinion a very good tool for machine learning.
The question here to ask is the following:
You are trying to predict by means of your DeepLearning algorithm whether a result is true or false, and more precisely it is a probability that a result of 1 or that of a result of 0 that you will get.
The output size of your neural network is 1 (final layer) and you seek to obtain a result between 0 and 1 which will be assimilated to the probability that the result is 1 (example at the output of the network if you obtain 0.65 this will correspond to 65% chance that the result is true).
Application example: Predicting the probability that the home team in a football match will win. The closer the value is to 1 the more the home team has a chance of winning, and conversely the closer the score is to 0 the more chance the home team has of losing.
It is possible to qualify this result using a threshold, for example by admitting that if the output is> 0.5 then the result is true if not false.
The final activation function should return a result between 0 and 1, the correct choice in this case may be the sigmoid function. The sigmoid function will easily translate a result between 0 and 1 and therefore is ideal for translating the probability that we are trying to predict.
If you want to plot the sigmoid function in Python here is some code that should help you (see alsoWebsite squall0032.tumblr.com) :
import math import numpy as np import matplotlib.pyplot as plt def sigmoid(x): a = [] for item in x: a.append(1/(1+math.exp(-item))) return a x = np.arange(-10., 10., 0.2) sig = sigmoid(x) plt.plot(x,sig) plt.show()
You have to determine during training the difference between the probability that the model predicts (translated via the final sigmoid function) and the true and known response (0 or 1). The function to use is Binary Cross Entrropy because this function allows to calculate the difference between 2 probability.
The optimizer will then use this result to adjust the weights and biases in your model (or other parameters depending on the architecture of your model).
In this example I will create a neural network with 1 linear layer and a final sigmoid activation function.
First, I perform all the imports that we will use in this post:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from collections import OrderedDict import math from random import randrange import torch from torch.autograd import Variable import torch.nn as nn from torch.autograd import Function from torch.nn.parameter import Parameter from torch import optim import torch.nn.functional as F from torchvision import datasets, transforms from echoAI.Activation.Torch.bent_id import BentID from sklearn.model_selection import train_test_split import sklearn.datasets from sklearn.metrics import accuracy_score import hiddenlayer as hl import warnings warnings.filterwarnings("ignore") from torchviz import make_dot, make_dot_from_trace
The following lines allow you not to display the warnings in python:
import warnings warnings.filterwarnings("ignore")
I will generate 1000 samples generated by the sklearn library allowing me to have a set of tests for a binary classification problem. The test set covers 2 decimal inputs and a binary output (0 or 1). I also display the result as a point cloud:
inputData,outputData = sklearn.datasets.make_moons(1000,noise=0.3) plt.scatter(inputData[:,0],inputData[:,1],s=40,c=outputData,cmap = plt.cm.get_cmap("Spectral"))
The test set generated here corresponds to a data set with 2 classes (class 0 and 1).
The goal of my neural network is therefore a binary classification of the input.
I will take 20% of this test set as test data and the remaining 80% as training data. I split the test set again to create a validation set on the training dataset.
X_train, X_test, y_train, y_test = train_test_split(inputData, outputData, test_size=0.20, random_state=42)
In my example I’m going to create a network with any architecture what interests us is to show how the last layer works:
fc1 = nn.Linear(2, 2) fc2 = nn.Linear(2, 1) model = nn.Sequential(OrderedDict([ ('lin1', fc1), ('BentID1', BentID()), ('lin2', fc2), ('sigmoid', nn.Sigmoid()) ])) model = model.double()
I added a line to transform the pytorch model to use the double type. This avoids an error of the type:
Expected object of scalar type Double but got scalar type Float for argument
Also it will be necessary to use dtype = torch.double when creating torch.tensor at each training.
As indicated in my documents above the cost function is the Binary Cross Entropy function in this case (BCELoss : Website pytorch.org) :
loss = nn.BCELoss()
I am using the Adam optimizer with a learning rate of 0.01:
Deep learning optimizers are algorithms or methods used to modify attributes of your neural network such as weights and bias in such a way that the loss function is minimized.
The learning rate is a hyperparameter that controls how much the model should be changed in response to the estimated error each time the model weights are updated.
Here I am using the Adam optimization algorithm. In Deep Learning Adam is a stochastic gradient descent method that calculates individual adaptive learning rates for different parameters from estimates of the first and second order moments of the gradients.
More information on optimizing Adam: Website machinelearningmastery.com
and in PyToch doc : Website pytorch.org
optimizer = optim.Adam(model.parameters(), lr=0.01)
I previously split the training data a second time to take 20% of the data as validation data. They allow us at each training to validate that we are not doing over-training (over-adjustment, or over-interpretation, or simply in English overfitting).
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.20, random_state=42)
In this example I have chosen to implement the EarlyStopping algorithm with a patience of 5. This means that if the cost function of the validation data increases during 15 training sessions (ie the distance between the prediction and the true data). After 15 training sessions with an increasing distance, we reload the previous data which represents the configuration of the neural network producing a distance of the minimum cost function (in the case of our example this consists in reloading the weight and the bias for the 2 layers linear). As long as the function decreases, the configuration of the neural network is saved (weight and bias of the linear layers in our example).
Here I have chosen to display the weights and bias of the 2 linear layers every 10 workouts, to give you an idea of how the Adam optimization algorithm works:
I used Website github.com to display various graphs around training and validation metrics. The graphics are thus refreshed during use.
history1 = hl.History() canvas1 = hl.Canvas()
Here is the main code of the learning loop:
bestvalidloss = np.inf saveWeight1 = fc1.weight saveWeight2 = fc2.weight saveBias1 = fc1.bias saveBias2 = fc2.bias wait = 0 for epoch in range(1000): model.train() optimizer.zero_grad() result = model(torch.tensor(X_train,dtype=torch.double)) lossoutput = loss(result, torch.tensor(y_train,dtype=torch.double)) lossoutput.backward() optimizer.step() print("EPOCH " + str(epoch) + "- train loss = " + str(lossoutput.item())) if((epoch+1)%10==0): #AFFICHE LES PARAMETRES DU RESEAU TOUT LES 10 entrainements print("*************** PARAMETERS WEIGHT & BIAS *********************") print("weight linear1= " + str(fc1.weight)) print('Bias linear1=' + str(fc1.bias)) print("weight linear2= " + str(fc2.weight)) print('bias linear2=' + str(fc2.bias)) print("**************************************************************") model.eval() validpred = model(torch.tensor(X_valid,dtype=torch.double)) validloss = loss(validpred, torch.tensor(y_valid,dtype=torch.double)) print("EPOCH " + str(epoch) + "- valid loss = " + str(validloss.item())) # Store and plot train and valid loss. history1.log(epoch, trainloss=lossoutput.item(),validloss=validloss.item()) canvas1.draw_plot([history1["trainloss"], history1["validloss"]]) if(validloss.item() < bestvalidloss): bestvalidloss = validloss.item() # saveWeight1 = fc1.weight saveWeight2 = fc2.weight saveBias1 = fc1.bias saveBias2 = fc2.bias wait = 0 else: wait += 1 if(wait > 15): #Restauration des mailleurs parametre et early stopping fc1.weight = saveWeight1 fc2.weight = saveWeight2 fc1.bias = saveBias1 fc2.bias = saveBias2 print("##############################################################") print("stop valid because loss is increasing (EARLY STOPPING) afte EPOCH=" + str(epoch)) print("BEST VALID LOSS = " + str(bestvalidloss)) print("BEST PARAMETERS WHEIGHT AND BIAS = ") print("FIRST LINEAR : WEIGHT=" + str(saveWeight1) + " BIAS = " + str(saveBias1)) print("SECOND LINEAR : WEIGHT=" + str(saveWeight2) + " BIAS = " + str(saveBias2)) print("##############################################################") break else: continue
Here is an overview of the result, training stops after about 200 epochs (an « epoch » is a term used in machine learning to refer to a passage of the complete training data set). In my example I didn’t use a batch to load the data so the epoch count is the iteration count.
Evolution of the cost function on the training test and the validation test
Here’s a look at the prediction results:
result = model(torch.tensor(X_test,dtype=torch.double)) plt.scatter(X_test[:,0],X_test[:,1],s=40,c=y_test,cmap = plt.cm.get_cmap("Spectral")) plt.title("True") plt.colorbar() plt.show() plt.scatter(X_test[:,0],X_test[:,1],s=40,c=result.data.numpy(),cmap = plt.cm.get_cmap("Spectral")) plt.title("predicted") plt.colorbar() plt.show()
To display the architecture of the neural network I used the hidden layer library which requires the installation of graphviz to avoid the following error:
RuntimeError: Make sure the Graphviz executables are on your system's path
To resolve this error see Website graphviz.org
On Mac OS X, for example, I installed via Homebrew:
brew install graphviz
Here is an overview of the hidden layer graph:
# HiddenLayer graph hl.build_graph(model, torch.zeros(2,dtype=torch.double))
I also used PyTorchViz Website github.com to display a graph of Pytorch operations during the forward of an entry and thus we can clearly see what will happen pass during the backward (ie the application the calculation of the dols / dx differential for all the parameters x of the network which has requires_grad = True).
make_dot(model(torch.ones(2,dtype=torch.double)), params=dict(model.named_parameters()))
In this case, you are trying to predict a continuous numerical quantity using your DeepLearning algorithm.
In this case, the gradient descent algorithm consists in comparing the difference between the numerical value predicted by the network and the true value.
The output size of the network will be 1 since there is only one value to predict.
The activation function to use in this case depends on the range in which your data is located.
For a data between -infinite and + infinite then you can use a linear function at the output of your network.
Linear function graph function. Website pytorch.org. The particularity is that this linear function (of the type y = ax + b) contains learnable parameters (weight and bias which are modified by the optimizer over the training sessions, i.e. y = weight * x + bias).
You can also use the Relu function if your value to predict is strictly positive
the output of ReLu is the maximum value between zero and the input value. An output is zero when the input value is negative and the input value when the input is positive.
Note that unlike the rectified linear activation function Relu does not have an adjustable parameter (learnable).
Another possible function is PReLu (parametric linear rectification unit):
Also another possible final activation function is Bent identity.
Finally I recommend the possible use of Parametric Soft Exponential, I use the echoAi implementation because it is not natively in PyTorch
Website en.wikipedia.org
The cost function which makes it possible to determine the distance between the predicted value and the real value is the average of the squared distance between the 2 predictions. The function to use is mean squared error (MSE): Website pytorch.org
Here is a simple example that illustrates the use of each of the final functions:
I started off with the example of house prices which I then adapted to test with each of the functions.
First, I import the necessary libraries:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from collections import OrderedDict import math from random import randrange import torch from torch.autograd import Variable import torch.nn as nn from torch.autograd import Function from torch.nn.parameter import Parameter from torch import optim import torch.nn.functional as F from torchvision import datasets, transforms from echoAI.Activation.Torch.bent_id import BentID from echoAI.Activation.Torch.soft_exponential import SoftExponential from sklearn.model_selection import train_test_split import sklearn.datasets from sklearn.metrics import accuracy_score import hiddenlayer as hl import warnings warnings.filterwarnings("ignore") from torchviz import make_dot, make_dot_from_trace from sklearn.datasets import make_regression from torch.autograd import Variable
I create the dataset which is quite simple and display it:
house_prices_array = [30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140] house_price_np = np.array(house_prices_array, dtype=np.float32) house_price_np = house_price_np.reshape(-1,1) house_price_tensor = Variable(torch.from_numpy(house_price_np)) house_size = [ 7.5, 7, 6.5, 6.0, 5.5, 5.0, 4.5,3.5,3.2,2.8,3.0,2.5] house_size_np = np.array(house_size, dtype=np.float32) house_size_np = house_size_np.reshape(-1, 1) house_size_tensor = Variable(torch.from_numpy(house_size_np)) import matplotlib.pyplot as plt plt.scatter(house_prices_array, house_size_np) plt.xlabel("House Price $") plt.ylabel("House Sizes") plt.title("House Price $ VS House Size") plt.show()
In the example, we see that the function to find is close to
f (x) = – 0.05 * x + 9
Example: – 0.05 * 40 + 9 = 7 and -0.05 * 30 + 9 = 7.5
I set the bias and the weight to -0.01 and 8 to limit the training time.
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
I declare MSELoss and an Adam optimizer tuned with a learning rate 0.01
loss = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=0.01)
Here is the training loop:
history1 = hl.History() canvas1 = hl.Canvas() for epoch in range(100): model.train() optimizer.zero_grad() result = model(house_price_tensor) lossoutput = loss(result, house_size_tensor) lossoutput.backward() optimizer.step() print("EPOCH " + str(epoch) + "- train loss = " + str(lossoutput.item())) history1.log(epoch, trainloss=lossoutput.item()) canvas1.draw_plot([history1["trainloss"]])
After a hundred training sessions we obtain an MSELoss = 0.13
If I display the weights and biases found by the model I get in my example:
print("weight" + str(fc1.weight)) print("bias" + str(fc1.bias))
The network therefore executes here the function f (x) = -0.0408 * x + 8.1321
I then display the predicted result to compare it with the actual result:
result = model(house_price_tensor) plt.scatter(house_prices_array, house_size_np) plt.title("True") plt.show() plt.scatter(house_prices_array,result.detach().numpy()) plt.title("predicted") plt.show()
Here is a preview of the architecture diagram for a simple Pytorch neuron network with Linear function, we see the multiplication operator and the addition operator to execute y = ax + b.
hl.build_graph(model, torch.ones(1,dtype=torch.float))
ReLu is not an activation function with « learnable » parameters (modified by the optimizer) so I add a linear layer upstream for the test:
The result is identical, which is logical since in my case reLu only passes the result which is strictly positive
In the previous example, a ReLu layer must be added at the output after the linear layer:
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('relu',nn.ReLU()) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
The same for PrRelu in my case:
These two functions simply make a flat pass between the linear function and the output. ReLu remains interesting if you want to set to 0 all the outputs concerning a negative input.
Bent identity there is no learning parameter but the function does not just forward the input it applies the curved identity function to it which slightly modifies the result:
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('BentID',BentID()) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
In our case after 100 training sessions the network found the following weights for the linear function upstream of bent identity the final loss is 0.78 so less good than with the linear function (the network converges less quickly):
In our case, the function applied by the network will therefore be:
-0.0481 ((math.sqt(x**2 + 1) -1)/2 + x) + 7.7386
Result obtained with Bent Identity:
hl.build_graph(model, torch.ones(1,dtype=torch.float))
I display the architecture of the Pytorch network which is now linear layer + bent identity layer :
Also :
make_dot(model(torch.ones(1,dtype=torch.float)), params=dict(model.named_parameters()))
Soft Exponential has a trainable alpha parameter.We are going to place a linear function upstream and softExponential in output and check the approximation performed in this case:
fc1 = nn.Linear(1, 1) fse = SoftExponential(1,torch.tensor([-0.9],dtype=torch.float)) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('SoftExponential',fse) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
After 100 training sessions the results on the linear layer parameters and the soft exponential layer parameters are as follows:
print("weight" + str(fc1.weight)) print("bias" + str(fc1.bias)) print("fse alpha" + str(fse.alpha))
The result is not really good since the approximation is not done in the right direction,
We therefore notice that we could invert the function in our case to define a Soft Exponential customize activation function with a bias criterion and that is what we will do here.
We declare a custom activation function to PyTorch compared to the original soft exponential I added the beta bias criterion as well as the torch.div inversion (1, …)
class SoftExponential2(nn.Module): def __init__(self, in_features, alpha=None,beta=None): super(SoftExponential2, self).__init__() self.in_features = in_features # initialize alpha if alpha is None: self.alpha = Parameter(torch.tensor(0.0)) # create a tensor out of alpha else: self.alpha = Parameter(torch.tensor(alpha)) # create a tensor out of alpha if beta is None: self.beta = Parameter(torch.tensor(0.0)) # create a tensor out of alpha else: self.beta = Parameter(torch.tensor(beta)) # create a tensor out of alpha self.alpha.requiresGrad = True # set requiresGrad to true! self.beta.requiresGrad = True # set requiresGrad to true! def forward(self, x): if self.alpha == 0.0: return x if self.alpha < 0.0: return torch.add(torch.div(1,(-torch.log(1 - self.alpha * (x + self.alpha)) / self.alpha)),self.beta) if self.alpha > 0.0: return torch.add(torch.div(1,((torch.exp(self.alpha * x) - 1) / self.alpha + self.alpha)),self.beta)
We now have 2 parameters that can be trained in this custom function in Pytorch.
By also lowering the learning rate to 0.01 after 100 training sessions and initializing alpha = 0 .1 and beta = 0.7 I arrive at a loss <5
fse = SoftExponential2(1,torch.tensor([0.1],dtype=torch.float),torch.tensor([7.0],dtype=torch.float)) model = nn.Sequential(OrderedDict([ ('SoftExponential',fse) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
print("fse alpha" + str(fse.alpha)) print("fse beta" + str(fse.beta))
You are looking to predict the probability that your entry will match a single class and exit betting multiple possible classes.
For example you want to predict the type of vehicle on an image allowed the classes: car, truck, train
The dimensioning of your output network corresponds to n neurons for n classes. And for each output neurons corresponding to a possible class to be predicted, we will obtain a probability between 0 and 1 which will represent the probability that the input corresponds to this class at the output. So for each class this comes down to solving a binary classification problem in part 1 but in addition to which it must be considered that an input can only correspond to a single output class.
The activation function to be used on the final layer is a Softfmax function with n dimensions corresponding to the number of classes to be predicted.
Softmax is a mathematical function that converts a vector of numbers (tensor) into a vector of probabilities, where the probabilities of each value are proportional to the relative scale of each value in the vector.
(Cf Website machinelearningmastery.com)
In other words, for an output tensor it will return a probability for each class by scaling each of them so that their sum is equal to one.
The cost function to be used is close to that used in the case of binary classification but with this notion of probability vector.
Cross Entropy will evaluate the difference between 2 probability distributions. We will therefore use it to compare the predicted value and the true value.
Based on the tutorial and the data set on this page we have:Website pytorch.org
For info if you get the following error:
ImportError: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
You need to setup Iprogress on jupyter lab
pip install ipywidgets jupyter nbextension enable --py widgetsnbextension
In our example we will first retrieve the input dataset
The dataset used is CIFAR-10, more information here:
This is already integrated with Pytorch to allow us to perform certain tests.
Preparation of the dataset:
import torch import torchvision import torchvision.transforms as transforms transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
We then define a function which allows to visualize the imshow image and we display for each image the unique label associated with it:
import matplotlib.pyplot as plt import numpy as np # functions to show an image def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) plt.show() # get some random training images dataiter = iter(trainloader) images, labels = dataiter.next() # show images imshow(torchvision.utils.make_grid(images)) # print labels print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
Create a convolutional neural network (more information here: Website towardsdatascience.com)
A convolutional neural network (ConvNet or CNN) is a DeepLearning algorithm that can take an image as input, it sets a score (weight and bias which are learnable parameters) to various aspects / objects of the image and be able to differentiate one from the other.
The architecture of a convolutional neuron network is close to that of the model of neuron connectivity in the human brain and was inspired by the organization of the visual cortex.
Individual neurons respond to stimuli only in a restricted region of the visual field known as the receptive field. A collection of these fields overlap to cover the entire visual area.
In our case we are going to use with Pytorch a Conv2d layer and a pooling layer.
Website pytorch.org : The Conv2d layer is the 2D convolution layer.
A filter in a conv2D layer has a height and a width. They are often smaller than the input image. This filter therefore moves over the entire image during training (this area is called the receptive field).
The Max Pooling layer is a sampling process. The objective is to sub-sample an input representation (image for example), by reducing its size and by making assumptions on the characteristics contained in the grouped sub-regions.
In my example with PyTorch the declaration is made :
import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x net = Net()
Define the criterion of the cost function with CrossEntropyLoss, here we use the SGD opimizer rather than adam (more info here on the comparison between optimizer Website ruder.io)
import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
The training loop:
for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # get the inputs; data is a list of [inputs, labels] inputs, labels = data # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 2000 == 1999: # print every 2000 mini-batches print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)) running_loss = 0.0 print('Finished Training')
The Pytorch neuron network is saved
PATH = './cifar_net.pth' torch.save(net.state_dict(), PATH)
We load a test and use the network to predict the outcome.
dataiter = iter(testloader) images, labels = dataiter.next() # print images imshow(torchvision.utils.make_grid(images)) print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
In this example the « true » labels are « Cat, ship, ship, plane », we then launch the network to make a prediction:
net = Net() net.load_state_dict(torch.load(PATH)) outputs = net(images) _, predicted = torch.max(outputs, 1) print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
Here is an overview of the architecture of this convolution network.
import hiddenlayer as hl from torchviz import make_dot, make_dot_from_trace make_dot(net(images), params=dict(net.named_parameters()))
Overall, it is about predicting several probabilities for each of the classes to indicate their probabilities of presence in the entry. One possible use is to indicate the presence of an object in an image.
The problem then comes back to a problem of binary classification for n classes.
The final activation function is sigmoid and the loss function is Binary cross entropy
This internet example perfectly illustrates the use of BCELoss in the case of the prediction of several classes among several possible classes.
Website medium.com
In this example: The image dataset used is the CelebFaces Large Scale Attribute Dataset (CelebA).
Website mmlab.ie.cuhk.edu.hk
In this data dataset there are 200K images with 40 different class labels and each image has a different background footprint and there are a lot of different variations making it difficult for a model to classify each label effectively class.
I suggest you follow the tutorial from the articleWebsite medium.com:
Here is the results:
Load of the dataset :
Use of the imshow function (see previous example for single label prediction)
Architecture of the convolutional neuron network:
import torch.nn.functional as F class MultiClassifier(nn.Module): def __init__(self): super(MultiClassifier, self).__init__() self.ConvLayer1 = nn.Sequential( nn.Conv2d(3, 64, 3), # 3, 256, 256 nn.MaxPool2d(2), # op: 16, 127, 127 nn.ReLU(), # op: 64, 127, 127 ) self.ConvLayer2 = nn.Sequential( nn.Conv2d(64, 128, 3), # 64, 127, 127 nn.MaxPool2d(2), #op: 128, 63, 63 nn.ReLU() # op: 128, 63, 63 ) self.ConvLayer3 = nn.Sequential( nn.Conv2d(128, 256, 3), # 128, 63, 63 nn.MaxPool2d(2), #op: 256, 30, 30 nn.ReLU() #op: 256, 30, 30 ) self.ConvLayer4 = nn.Sequential( nn.Conv2d(256, 512, 3), # 256, 30, 30 nn.MaxPool2d(2), #op: 512, 14, 14 nn.ReLU(), #op: 512, 14, 14 nn.Dropout(0.2) ) self.Linear1 = nn.Linear(512 * 14 * 14, 1024) self.Linear2 = nn.Linear(1024, 256) self.Linear3 = nn.Linear(256, 40) def forward(self, x): x = self.ConvLayer1(x) x = self.ConvLayer2(x) x = self.ConvLayer3(x) x = self.ConvLayer4(x) x = x.view(x.size(0), -1) x = self.Linear1(x) x = self.Linear2(x) x = self.Linear3(x) return F.sigmoid(x)
An overview of the network architecture:
Here is a summary of my pytorch tutorial : sheet that I created to allow you to choose the right activation function and the right cost function more quickly according to your problem to be solved.
Website fr.wikipedia.org : Dree courses Yann LeCun
Website pytorch.org : Official web site PyToch
Website machinelearningmastery.com : Advanced on deep learning
Website www.fun-mooc.fr : Free MOOC
https://128mots.com/index.php/en/category/non-classe-en/
https://128mots.com/index.php/category/python/
Quand j’ai écris ce billet de blog à propos du pytorch tutorial, je me suis souvenu du challenge que je me suis fixé en début d’année d’apprendre le deep learning, je ne connaissais alors même pas Python. Ce qui rends les choses difficiles ce n’est pas forcément la complexité des concepts, mais cela commence par des questions du type : Quel framework utiliser pour le Deep learning ? Quel fonction d’activation est ce que je dois choisir ? Quel est la fonction de côut la mieux adaptée pour mon problème ?
Mon expérience personnelle a été d’étudier le framework PyTorch et surtout pour la théorie le cours en ligne mis à disposition gratuitement par Yan LeCun que je remercie (lien ici Website fr.wikipedia.org). Il me reste encore à apprendre et travailler sur le domaine mais par ce billet de blog, j’aimerais partager et vous donner un panorama de ce que j’ai appris sur le deep learning cette année.
L’objectif de cet article est de balayer ce sujet central en DeepLearning du choix la fonction d’activation et de coût (loss) en fonction du problème que vous cherchez à résoudre au moyen d’un algorithme de DeepLearning.
On parle ici de la fonction d’activation de la dernière couche de votre modèle c’est à dire celle qui vous donne le résultat.
Ce résultat est utilisé dans l’algorithme qui vérifie à chaque apprentissage la différence entre le résultat prédit par le réseau de neurones et le résultat réel (algorithme de descente du gradient), pour cela on applique au résultat une fonction de coût (loss function) qui représente cette différence et que vous cherchez à minimiser au fil des entrainement afin de réduire l’erreur. (Voir cet article ici pour en savoir plus : Website machinelearnia.com)
Pour rappel la machine apprends en minimisant la fonction de coût, itérativement par étapes d’entrainement successif, le résultat de la fonction de coût et pris en compte pour l’ajustement des paramètres des neurones (poids et biais par exemple pour les couches linéaires).
Ce choix de fonction d’activation sur la dernière couche et la fonction de coût à minimiser (loss function) est donc crucial puisque ces deux éléments combinés vont déterminer comment vous allez résoudre un problème au moyen de votre réseau de neurones.
Cet article présente également des exemples simples qui utilisent le framework Pytorch d’après moi un très bon outil pour le machine learning.
La question ici à se poser est la suivante :
Vous cherchez à prédire au moyen de votre algorithme de DeepLearning qu’un résultat est vrai ou faux, et plus précisément c’est une probabilité qu’un résultat soit 1 ou qu’un résultat soit 0 que vous allez obtenir.
La taille de sortie de votre réseau de neurone est 1 (couche finale) et vous cherchez à obtenir un résultat entre 0 et 1 qui sera assimilé à la probabilité que le résultat soit 1 (exemple en sortie du réseau si vous obtenez 0.65 cela correspondra à 65% de chance que le résultat soit vrai).
Exemple d’application : Prédiction de la probabilité que l’équipe à domicile d’un match de football gagne. Plus la valeur est proche de 1 plus l’équipe à domicile a des chances de gagner, et inversement plus le résultat se rapproche de 0 plus l’équipe à domicile a des chance de perdre.
Il est possible de qualifier ce résultat en utilisant un seuil, par exemple en admettant que si la sortie est > 0.5 alors le résultat est vrai sinon faux.
La fonction d’activation finale doit retourner un résultat entre 0 et 1, le bon choix dans ce cas peut être la fonction sigmoïde. La fonction sigmoïde permettra de traduire facilement un résultat entre 0 et 1 et donc est idéal pour traduire la probabilité qu’on cherche.à prédire.
Si vous souhaitez tracer la fonction sigmoïde en Python voici un code qui devrait vous aider (voir également Website squall0032.tumblr.com) :
import math import numpy as np import matplotlib.pyplot as plt def sigmoid(x): a = [] for item in x: a.append(1/(1+math.exp(-item))) return a x = np.arange(-10., 10., 0.2) sig = sigmoid(x) plt.plot(x,sig) plt.show()
Il faut déterminer lors de l’entrainement la différence entre la probabilité que le modèle prédit (traduite via la fonction finale sigmoid) et la réponse vraie et connue (0 ou 1). La fonction à utiliser est Binary Cross Entrropy car cette fonction permet de calculer la différence entre 2 probabilité.
L’optimiseur va ensuite utiliser ce résultat pour ajuster les poids et biais dans votre modèle (ou les autres paramètres selon l’architecture de votre modèle).
Dans cet exemple je vais créer un réseau de neurones avec 1 couche linéaire et une fonction d’activation finale sigmoïde.
Dans un premier temps, au travers de ce pytorch tutorial, j’effectue tous les imports que nous allons utiliser dans ce billet :
import numpy as np import pandas as pd import matplotlib.pyplot as plt from collections import OrderedDict import math from random import randrange import torch from torch.autograd import Variable import torch.nn as nn from torch.autograd import Function from torch.nn.parameter import Parameter from torch import optim import torch.nn.functional as F from torchvision import datasets, transforms from echoAI.Activation.Torch.bent_id import BentID from sklearn.model_selection import train_test_split import sklearn.datasets from sklearn.metrics import accuracy_score import hiddenlayer as hl import warnings warnings.filterwarnings("ignore") from torchviz import make_dot, make_dot_from_trace
Les lignes suivantes permettent de ne pas afficher les warning en python :
import warnings warnings.filterwarnings("ignore")
Je vais générer 1000 échantillons générés par la librairie sklearn me permettant d’avoir un jeu de tests pour un problème de classification binaire. Le jeu de tests porte sur 2 entrées décimales et une sortie binaire (0 ou 1). J’affiche également le résultat sous forme d’un nuage de point :
inputData,outputData = sklearn.datasets.make_moons(1000,noise=0.3) plt.scatter(inputData[:,0],inputData[:,1],s=40,c=outputData,cmap = plt.cm.get_cmap("Spectral"))
Le jeu de test ici généré correspond à un jeu de données avec 2 classes (classe 0 et 1).
L’objectif de mon réseau de neurone est donc une classification binaire de l’input.
Je vais prendre 20% de ce jeu de test comme donnée de test et les 80% restant comme données d’entrainement. Je split à nouveau le jeu de test pour créer un jeu de validation sur le jeu de données d’entrainement.
X_train, X_test, y_train, y_test = train_test_split(inputData, outputData, test_size=0.20, random_state=42)
Dans mon exemple je vais créer un réseau avec une architecture quelconque ce qui nous intéresse c’est de montrer le fonctionnement de la dernière couche :
fc1 = nn.Linear(2, 2) fc2 = nn.Linear(2, 1) model = nn.Sequential(OrderedDict([ ('lin1', fc1), ('BentID1', BentID()), ('lin2', fc2), ('sigmoid', nn.Sigmoid()) ])) model = model.double()
J’ai ajouté une ligne permettant de transformer le modèle pytorch pour qu’il utilise le type double. Cela évite une erreur du type :
Expected object of scalar type Double but got scalar type Float for argument
Également il faudra utiliser dtype=torch.double lors de la création des torch.tensor à chaque entrainement.
Comme indiqué dans mon documents ci-desssus la fonction de coût est la fonction Binary Cross Entropy dans ce cas (BCELoss : Website pytorch.org) :
loss = nn.BCELoss()
Dans le cadre de ce pytorch tutorial, J’utilise l’optomizer Adam avec un learning rate de 0.01 :
Les optimiseurs en deep learning sont des algorithmes ou des méthodes utilisés pour modifier les attributs de votre réseau neuronal tels que les poids et le biais de tel manière que la fonction de cout (loss function) soit minimisée.
Le taux d’apprentissage est un hyperparamètre qui contrôle dans quelle mesure le modèle doit être modifié en réponse à l’erreur estimée chaque fois que les poids du modèle sont mis à jour.
Ici j’utilise l’algorithme d’optimisation Adam. En Deep Learning Adam est une méthode de descente de gradient stochastique qui calcule les taux d’apprentissage adaptatif individuels pour différents paramètres à partir d’estimations des moments de premier et de second ordre des gradients.
Plus d’information sur l’optimiser Adam : Website machinelearningmastery.com
Implémentation PyTorch de l’algorithme d’optimisation Adam : Website pytorch.org
optimizer = optim.Adam(model.parameters(), lr=0.01)
J’ai préalablement splits une seconde fois les données d’entrainement pour prendre 20% des données comme des données de validations. Elle nous permettent à chaque entrainement de valider que nous ne faisons pas de surraprentissage (sur-ajustement, ou encore surinterprétation, ou simplement en anglais overfitting) .
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.20, random_state=42)
Dans cet exemple j’ai choisi d’implémenter le EarlyStopping algorithme avec une patience de 5. Cela signifie que si la fonction de cout des données de validation augmente durant 15 entrainements (soit la distance entre la prédiction et les données vraies). Après 15 entrainements avec une distance qui augmente on recharge les données précédentes qui représente la configuration du réseau de neurone produisant une distance de la fonction de cout minimale (dans le cas de notre exemple cela consiste à recharger le poids et le biais pour les 2 couches linéaires). Tant que la fonction diminue on sauvegarde la configuration du réseau de neurones (poids et biais des couches linéaire dans notre exemple).
Ici j’ai choisi d’afficher les poids et le biais des 2 couches linéaire tous les 10 entrainements, pour vous donner une idée de comment fonctionne l’algorithme d’optimisation Adam :
J’ai utilisé Website github.com pour afficher divers graphique autour des métriques de training et validation. Les graphiques sont ainsi rafraîchi en cours d’utilisation.
history1 = hl.History() canvas1 = hl.Canvas()
Ici le code principal de la boucle d’apprentissage :
bestvalidloss = np.inf saveWeight1 = fc1.weight saveWeight2 = fc2.weight saveBias1 = fc1.bias saveBias2 = fc2.bias wait = 0 for epoch in range(1000): model.train() optimizer.zero_grad() result = model(torch.tensor(X_train,dtype=torch.double)) lossoutput = loss(result, torch.tensor(y_train,dtype=torch.double)) lossoutput.backward() optimizer.step() print("EPOCH " + str(epoch) + "- train loss = " + str(lossoutput.item())) if((epoch+1)%10==0): #AFFICHE LES PARAMETRES DU RESEAU TOUT LES 10 entrainements print("*************** PARAMETERS WEIGHT & BIAS *********************") print("weight linear1= " + str(fc1.weight)) print('Bias linear1=' + str(fc1.bias)) print("weight linear2= " + str(fc2.weight)) print('bias linear2=' + str(fc2.bias)) print("**************************************************************") model.eval() validpred = model(torch.tensor(X_valid,dtype=torch.double)) validloss = loss(validpred, torch.tensor(y_valid,dtype=torch.double)) print("EPOCH " + str(epoch) + "- valid loss = " + str(validloss.item())) # Store and plot train and valid loss. history1.log(epoch, trainloss=lossoutput.item(),validloss=validloss.item()) canvas1.draw_plot([history1["trainloss"], history1["validloss"]]) if(validloss.item() < bestvalidloss): bestvalidloss = validloss.item() # saveWeight1 = fc1.weight saveWeight2 = fc2.weight saveBias1 = fc1.bias saveBias2 = fc2.bias wait = 0 else: wait += 1 if(wait > 15): #Restauration des mailleurs parametre et early stopping fc1.weight = saveWeight1 fc2.weight = saveWeight2 fc1.bias = saveBias1 fc2.bias = saveBias2 print("##############################################################") print("stop valid because loss is increasing (EARLY STOPPING) afte EPOCH=" + str(epoch)) print("BEST VALID LOSS = " + str(bestvalidloss)) print("BEST PARAMETERS WHEIGHT AND BIAS = ") print("FIRST LINEAR : WEIGHT=" + str(saveWeight1) + " BIAS = " + str(saveBias1)) print("SECOND LINEAR : WEIGHT=" + str(saveWeight2) + " BIAS = " + str(saveBias2)) print("##############################################################") break else: continue
Voici un aperçu du résultat, l’entraînement s’arrête au bout d’environ 200 epoch (une « epoch » est un terme utilisé en machine learning pour désigner un passage du jeu de donnée d’entrainement complet). Dans mon exemple je n’ai pas utilisé de batch pour charger les données le nombre d’epoch est donc le nombre d’itérations.
Evolution de la fonction de cout sur le test d’entrainement et le test de validation (pytorch tutorial)
Voici un aperçu des résultats de la prédiction :
result = model(torch.tensor(X_test,dtype=torch.double)) plt.scatter(X_test[:,0],X_test[:,1],s=40,c=y_test,cmap = plt.cm.get_cmap("Spectral")) plt.title("True") plt.colorbar() plt.show() plt.scatter(X_test[:,0],X_test[:,1],s=40,c=result.data.numpy(),cmap = plt.cm.get_cmap("Spectral")) plt.title("predicted") plt.colorbar() plt.show()
Pour afficher l’architecture du réseau de neurone j’ai utilisé la librairie hidden layer qui nécessite l’installation de graphviz pour éviter l’erreur suivante :
RuntimeError: Make sure the Graphviz executables are on your system's path
Pour résoudre cette erreur voir Website graphviz.org
Sur Mac OS X j’ai par exemple installé via Homebrew :
brew install graphviz
Voici un aperçu du graphe de la hidden layer :
# HiddenLayer graph hl.build_graph(model, torch.zeros(2,dtype=torch.double))
J’ai également utilisé PyTorchViz Website github.com pour afficher un graphe des opérations de Pytorch pendant le forward d’une entrée et on peut voir ainsi clairement ce qui va se passer lors du backward (c’est à dire l’application le calcul de la différentiel dols/dx pour tout les paramètres x du réseau qui a requires_grad=True).
make_dot(model(torch.ones(2,dtype=torch.double)), params=dict(model.named_parameters()))
Vous cherchez dans ce cas à prédire au moyen de votre algorithme de DeepLearning une grandeur numérique continue.
Dans ce cas l’algorithme de descente du gradient consiste a comparer la différence entre la valeur numérique prédite par le réseau et la valeur vraie.
La taille de sortie du réseau sera 1 puisqu’il y a une seule valeur à prédire.
La fonction d’activation à utiliser dans ce cas dépend de la plage dans laquelle votre donnée est située.
Pour une donnée entre -infinie et + infinie alors vous pouvez utiliser une fonction linéaire en sortie de votre réseau.
Fonction graphe de fonction Linear. Website pytorch.org. La particularité est que cette fonction linéaire (du type y = ax + b)contient des paramètres apprenable (weight and bias qui sont modifiés par l’optimizer au fil des entrainements soit y = weight * x + bias).
Vous pouvez également utiliser la fonction Relu si votre valeur à prédire est strictement positive
la sortie de ReLu est la valeur maximale entre zéro et la valeur d’entrée. Une sortie est égale à zéro lorsque la valeur d’entrée est négative et la valeur d’entrée lorsque l’entrée est positive.
A noter que contrairement à la fonction d’activation linéaire rectifiée Relu ne possède pas de paramètre ajustable (learnable).
Une autre fonction possible est PReLu (unité de rectification linéaire paramétrique) :
Egalement une autre fonction d’activation finale possible est Bent identity (identité courbée).
Enfin je recommande l’utilisation possible de Exponentielle douce paramétrique (Soft exponential), j’utilise l’implément de echoAi car elle n’est pas nativement dans PyTorch
Website en.wikipedia.org
La fonction de cout qui permet de déterminer la distance entre la valeur prédite et la valeur réelle est la moyenne de la distance au carré entre les 2 prédictions. La fonction à utiliser est mean squared error (MSE):Website pytorch.org
Voici un exemple simple qui illustre l’utilisation de chacune des fonctions finales :
Je suis parti au départ de l’exemple du prix des maisons que j’ai ensuite adapté pour faire le test avec chacune des fonctions.
Dans un premier temps j’importe les librairies nécessaires :
import numpy as np import pandas as pd import matplotlib.pyplot as plt from collections import OrderedDict import math from random import randrange import torch from torch.autograd import Variable import torch.nn as nn from torch.autograd import Function from torch.nn.parameter import Parameter from torch import optim import torch.nn.functional as F from torchvision import datasets, transforms from echoAI.Activation.Torch.bent_id import BentID from echoAI.Activation.Torch.soft_exponential import SoftExponential from sklearn.model_selection import train_test_split import sklearn.datasets from sklearn.metrics import accuracy_score import hiddenlayer as hl import warnings warnings.filterwarnings("ignore") from torchviz import make_dot, make_dot_from_trace from sklearn.datasets import make_regression from torch.autograd import Variable
Je crée le jeu de données qui est assez simple et je l’affiche :
house_prices_array = [30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140] house_price_np = np.array(house_prices_array, dtype=np.float32) house_price_np = house_price_np.reshape(-1,1) house_price_tensor = Variable(torch.from_numpy(house_price_np)) house_size = [ 7.5, 7, 6.5, 6.0, 5.5, 5.0, 4.5,3.5,3.2,2.8,3.0,2.5] house_size_np = np.array(house_size, dtype=np.float32) house_size_np = house_size_np.reshape(-1, 1) house_size_tensor = Variable(torch.from_numpy(house_size_np)) import matplotlib.pyplot as plt plt.scatter(house_prices_array, house_size_np) plt.xlabel("House Price $") plt.ylabel("House Sizes") plt.title("House Price $ VS House Size") plt.show()
Dans l’exemple, on voit que que la fonction à trouver est proche de
f(x) = – 0.05 * x + 9
Exemple : – 0.05 * 40 + 9 = 7 et -0.05 * 30 + 9 = 7.5
J’initialise le biais et le poids à -0.01 et 8 pour limiter le temps d’entrainement.
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
Je déclare MSELoss et un optimizer Adam réglé avec un taux d’apprentissage 0.01
loss = nn.MSELoss() optimizer = optim.Adam(model.parameters(), lr=0.01)
Voici la boucle d’entrainement :
history1 = hl.History() canvas1 = hl.Canvas() for epoch in range(100): model.train() optimizer.zero_grad() result = model(house_price_tensor) lossoutput = loss(result, house_size_tensor) lossoutput.backward() optimizer.step() print("EPOCH " + str(epoch) + "- train loss = " + str(lossoutput.item())) history1.log(epoch, trainloss=lossoutput.item()) canvas1.draw_plot([history1["trainloss"]])
Au bout d’une centaine d’entrainement on obtient un MSELoss = 0.13
Si j’affiche les poids et biais trouvé par le modèle j’obtiens dans mon exemple :
print("weight" + str(fc1.weight)) print("bias" + str(fc1.bias))
Le réseau éxecute donc ici la fonction f(x) = -0.0408 * x + 8.1321
J’affiche ensuite le résultat prédit pour le comparer avec le résultat réel :
result = model(house_price_tensor) plt.scatter(house_prices_array, house_size_np) plt.title("True") plt.show() plt.scatter(house_prices_array,result.detach().numpy()) plt.title("predicted") plt.show()
Voici un aprerçu du diagramme d’architecture pour un simple réseau de neurone Pytorch avec fonction Linéaire, on voit l’opérateur de multiplication et l’opérateur d’addition pour executer y = ax + b.
hl.build_graph(model, torch.ones(1,dtype=torch.float))
ReLu n’étant pas une fonction d’activation avec des paramètres « apprenables » (modifié par l’optimiseur) j’ajoute donc une couche linéaire en amont pour le test :
Le résultat est identique, ce qui est logique puisque dans mon cas reLu ne fait que passer le résultat qui est strictement positif
Dans l’exemple précédent il faut ajouter une couche ReLu en sortie après la couche linéaire :
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('relu',nn.ReLU()) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
De même pour PrRelu dans mon cas:
Ces Deux fonctions font simplement un passe plat entre la fonction linéaire et la sortie. ReLu reste intéressant si vous souhaitez mettre à 0 tout les sortie concernant une entrée négative.
Bent identity il n’y a pas de paramètre apprenante mais la fonction ne fait pas que forwarder l’entrée elle y applique la fonction identité courbée qui modifie légèrement le résultat :
fc1 = nn.Linear(1, 1) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('BentID',BentID()) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
Dans notre cas après 100 entrainements le réseau a trouvé les poids suivant pour la fonction linéaire en amont de bent identity le loss final est de 0.78 donc moin bon qu’avec la fonction linéaire (le réseau converge moins vite) :
Dans notre cas la fonction appliquée par le réseau sera donc :
-0.0481 ((math.sqt(x**2 + 1) -1)/2 + x) + 7.7386
Résultat obtenu avec Bent Identity :
hl.build_graph(model, torch.ones(1,dtype=torch.float))
J’affiche l’architécture du réseau Pytorch qui fait maintenant couche linéaire + couche bent identity :
Egalement :
make_dot(model(torch.ones(1,dtype=torch.float)), params=dict(model.named_parameters()))
Soft Exponential possède un paramètre entrainable alpha nous allons placer une fonction linéaire en amont et softExponential en sortie et vérifier l’approximation effectuée dans ce cas :
fc1 = nn.Linear(1, 1) fse = SoftExponential(1,torch.tensor([-0.9],dtype=torch.float)) model = nn.Sequential(OrderedDict([ ('lin', fc1), ('SoftExponential',fse) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
Après 100 entrainements le résultats sur les paramètre de la couche linéaire et les paramètre de la couche soft exponential sont les suivants :
print("weight" + str(fc1.weight)) print("bias" + str(fc1.bias)) print("fse alpha" + str(fse.alpha))
Le résultat n’est pas vraiment bon puisque l’approximation ne se fait pas dans le bon sens,
On remarque donc qu’on pourrait inverser la fonction dans notre cas définir une fonction d’activation customize de Soft Exponential avec un critère de biais et c’est ce que nous allons faire ici.
On déclare une fonction d’activation custom à PyTorch par rapport au soft exponential d’origine j’ai ajouté le critère de biais beta ainsi que l’inversion torch.div(1,…)
class SoftExponential2(nn.Module): def __init__(self, in_features, alpha=None,beta=None): super(SoftExponential2, self).__init__() self.in_features = in_features # initialize alpha if alpha is None: self.alpha = Parameter(torch.tensor(0.0)) # create a tensor out of alpha else: self.alpha = Parameter(torch.tensor(alpha)) # create a tensor out of alpha if beta is None: self.beta = Parameter(torch.tensor(0.0)) # create a tensor out of alpha else: self.beta = Parameter(torch.tensor(beta)) # create a tensor out of alpha self.alpha.requiresGrad = True # set requiresGrad to true! self.beta.requiresGrad = True # set requiresGrad to true! def forward(self, x): if self.alpha == 0.0: return x if self.alpha < 0.0: return torch.add(torch.div(1,(-torch.log(1 - self.alpha * (x + self.alpha)) / self.alpha)),self.beta) if self.alpha > 0.0: return torch.add(torch.div(1,((torch.exp(self.alpha * x) - 1) / self.alpha + self.alpha)),self.beta)
On a maintenant 2 paramètres entrainable dans cette fonction custom à Pytorch.
En abaissant également le taux d’apprentissage learning rate à 0.01 au bout de 100 entrainement et en initialisant alpha = 0 .1 et beta = 0.7 j’arrive à un loss < 5
fse = SoftExponential2(1,torch.tensor([0.1],dtype=torch.float),torch.tensor([7.0],dtype=torch.float)) model = nn.Sequential(OrderedDict([ ('SoftExponential',fse) ])) model = model.float() fc1.weight.data.fill_(-0.01) fc1.bias.data.fill_(8)
print("fse alpha" + str(fse.alpha)) print("fse beta" + str(fse.beta))
Vous cherchez à prédire la probabilité que votre entrée corresponde à une classe unique en sortie paris plusieurs classes possible.
Par exemple vous souhaitez prédire le type de véhicule sur une image permis les classes : voiture, camion, train
Le dimensionnement de votre réseau en sortie correspond à n neurones pour n classes. Et pour chaque neurones de sortie correspondant à une classe possible à prédire on obtiendra une probabilité entre 0 et 1 qui représentera la probabilité que l’entrée correspondent à cette classe en sortie. Ainsi pour chaque classe cela revient à une résolution d’un problème de classification binaire dans la partie 1 mais en plus duquel il faut considérer qu’une entrée ne peut correspondre qu’à une seule classe en sortie.
La fonction d’activation à utiliser sur la couche finale est une fonction Softfmax avec n dimensions correspondant au nombre de classe à prédire.
Softmax est une fonction mathématique qui convertit un vecteur de nombres (tensor) en un vecteur de probabilités, où les probabilités de chaque valeur sont proportionnelles à l’échelle relative de chaque valeur dans le vecteur.
(Cf Website machinelearningmastery.com)
En d’autres terme pour un tensor de sortie il va retourner une probabilité pour chaque classe en échelonnant chacune d’elle pour que leur somme soit égale à un.
La fonction de cout à utiliser est proche de celle utilisée dans le cas de la classification binaire mais avec cette notion de vecteur de probabilité.
Cross Entropy va evéluer la différence entre 2 distributions de probabilité. On va donc l’utiliser pour comparer la valeur prédite et la valeur vraie.
En me basant sur le tutorial et le jeu de donnée présent sur cette page on a :Website pytorch.org
Pour info si vous obtenez l’erreur suivante :
ImportError: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
il suffit d’executer l’installation de Iprogress sur jupyter lab
pip install ipywidgets jupyter nbextension enable --py widgetsnbextension
Dans notre exemple on va dans un premier temps récupérer le jeu de données en entrée
Le dataset utilisé est CIFAR-10, plus d’information ici : Website www.cs.toronto.edu
Celui-ci est déjà intégré à Pytorch pour nous permettre d’effectuer certain test.
Préparation du dataset :
import torch import torchvision import torchvision.transforms as transforms transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2) testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2) classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
On définit alors une fonction qui permet de visualiser l’image imshow et on affiche pour chaque image l’unique étiquette qui lui est associé :
import matplotlib.pyplot as plt import numpy as np # functions to show an image def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) plt.show() # get some random training images dataiter = iter(trainloader) images, labels = dataiter.next() # show images imshow(torchvision.utils.make_grid(images)) # print labels print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
Créer un réseau de neurones de convolution (plus d’information ici : Website towardsdatascience.com)
Un réseau de neurones convolutifs (ConvNet ou CNN) est un algorithme de DeepLearning qui peut prendre une image en entrée, il fixe un score (poids et biais qui sont des paramètres apprenables) à divers aspects / objets de l’image et être capable de différencier l’un de l’autre.
L’architecture d’un réseau de neurone convolutif est proche de celle du modèle de connectivité des neurones dans le cerveau humain et a été inspirée par l’organisation du cortex visuel.
Les neurones individuels ne répondent aux stimuli que dans une région restreinte du champ visuel connue sous le nom de champ récepteur. Une collection de ces champs se chevauchent pour couvrir toute la zone visuelle.
Dans notre cas nous allons utiliser avec Pytorch une couche Conv2d et une couche de pooling.
Website pytorch.org : La couche Conv2d est la couche de convolution 2D.
Un filtre dans une couche conv2D a une hauteur et une largeur. Ils sont souvent plus petits que l’image d’entrée. Ce filtre se déplace donc sur toute l’image au fil de l’entrainement (cette zone est appelée champ réceptif).
La couche de Max Pooling est un processus d’échantillonage. L’objectif est de sous-échantillonner une représentation d’entrée (image par exemple), en réduisant sa dimension et en faisant des hypothèses sur les caractéristiques contenues dans les sous-régions regroupées.
Dans mon exemple avec PyTorch la déclaration se fait :
import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) x = self.fc3(x) return x net = Net()
Définir le critère de la fonction de cout avec CrossEntropyLoss, ici on utilise l’opimizer SGD plutot que adam (plus d’info ici sur les comparaison entre optimizer Website ruder.io)
import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
La boucle d’entrainement :
for epoch in range(2): # loop over the dataset multiple times running_loss = 0.0 for i, data in enumerate(trainloader, 0): # get the inputs; data is a list of [inputs, labels] inputs, labels = data # zero the parameter gradients optimizer.zero_grad() # forward + backward + optimize outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics running_loss += loss.item() if i % 2000 == 1999: # print every 2000 mini-batches print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000)) running_loss = 0.0 print('Finished Training')
Le réseau de neurone Pytorch est sauvegardé
PATH = './cifar_net.pth' torch.save(net.state_dict(), PATH)
On charge un test et on utilise le réseau pour prédire le résultat.
dataiter = iter(testloader) images, labels = dataiter.next() # print images imshow(torchvision.utils.make_grid(images)) print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
Dans cet exemple les labels « vrai » sont « Cat, ship, ship, plane », on lance alors le réseau pour faire une prédiction :
net = Net() net.load_state_dict(torch.load(PATH)) outputs = net(images) _, predicted = torch.max(outputs, 1) print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
Voici un aperçu de l’architecture de ce réseau de convolution.
import hiddenlayer as hl from torchviz import make_dot, make_dot_from_trace make_dot(net(images), params=dict(net.named_parameters()))
Globalement il s’agit de prédire plusieurs probabilités pour chacune des classes pour indiquer leurs probabilités de présence dans l’entrée. Une utilisation possible est d’indiquer la présence d’un objet dans une image.
Le problème revient alors à un problème de classification binaire pour n classes.
La fonction d’activation finale est sigmoid et la fonction loss est Binary cross entropy
Cette exemple sur internet illustre parfaitement l’utilisation de BCELoss dans le cas de la prédiction de plusieurs classes parmi plusieurs classe possible.
Website medium.com
Dans cet exemple : L’ensemble de données d’image utilisé est l’ensemble de données d’attributs CelebFaces à grande échelle (CelebA).
Website mmlab.ie.cuhk.edu.hk
Dans ce dataset de données, il y a 200K images avec 40 étiquettes de classe différentes et chaque image a un encombrement d’arrière-plan différent et il y a beaucoup de variations différentes, ce qui rend difficile pour un modèle de classer efficacement chaque étiquette de classe.
Je vous propose de suivre le tutorial issu de l’article Website medium.com:
Voici le résultat que j’obtiens :
Chargement du dataset :
Utilisation de la fonction imshow (cf exemple précédent pour la prédiction single label)
Architecture du réseau de neurone convolutif :
import torch.nn.functional as F class MultiClassifier(nn.Module): def __init__(self): super(MultiClassifier, self).__init__() self.ConvLayer1 = nn.Sequential( nn.Conv2d(3, 64, 3), # 3, 256, 256 nn.MaxPool2d(2), # op: 16, 127, 127 nn.ReLU(), # op: 64, 127, 127 ) self.ConvLayer2 = nn.Sequential( nn.Conv2d(64, 128, 3), # 64, 127, 127 nn.MaxPool2d(2), #op: 128, 63, 63 nn.ReLU() # op: 128, 63, 63 ) self.ConvLayer3 = nn.Sequential( nn.Conv2d(128, 256, 3), # 128, 63, 63 nn.MaxPool2d(2), #op: 256, 30, 30 nn.ReLU() #op: 256, 30, 30 ) self.ConvLayer4 = nn.Sequential( nn.Conv2d(256, 512, 3), # 256, 30, 30 nn.MaxPool2d(2), #op: 512, 14, 14 nn.ReLU(), #op: 512, 14, 14 nn.Dropout(0.2) ) self.Linear1 = nn.Linear(512 * 14 * 14, 1024) self.Linear2 = nn.Linear(1024, 256) self.Linear3 = nn.Linear(256, 40) def forward(self, x): x = self.ConvLayer1(x) x = self.ConvLayer2(x) x = self.ConvLayer3(x) x = self.ConvLayer4(x) x = x.view(x.size(0), -1) x = self.Linear1(x) x = self.Linear2(x) x = self.Linear3(x) return F.sigmoid(x)
Un aperçu de l’architecture du réseau (pytorch tutorial) :
Voici une fiche de synthèse que j’ai créé pour vous permettre de plus rapidement choisir la bonne fonction d’activation et la bonne fonction de coût en fonction de votre problème à résoudre.
Website fr.wikipedia.org : Cours gratuit de Yann LeCun
Website pytorch.org : Le site officiel pour PyToch
Website machinelearningmastery.com : Un site qui traite de sujet avancé sur le machine learning et le deep learning
Website www.fun-mooc.fr : Un MOOC gratuit sur le deep learning
Website dataanalyticspost.com : Un site de veille intéressant autour du machine learning.
https://128mots.com/index.php/en/category/non-classe-en/
https://128mots.com/index.php/category/python/