Comment implémenter l'algorithme Perceptron à partir de zéro en Python
L'algorithme Perceptron est le type de réseau neuronal artificiel le plus simple.
Il s’agit d’un modèle de neurone unique qui peut être utilisé pour des problèmes de classification en deux classes et constitue la base du développement ultérieur de réseaux beaucoup plus vastes.
Dans ce tutoriel, vous découvrirez comment implémenter l'algorithme Perceptron à partir de zéro avec Python.
Après avoir terminé ce tutoriel, vous saurez :
- Comment entraîner les poids du réseau pour le Perceptron.
- Comment faire des prédictions avec le Perceptron.
- Comment implémenter l'algorithme Perceptron pour un problème de classification réel.
Démarrez votre projet avec mon nouveau livre Machine Learning Algorithms From Scratch, comprenant des tutoriels pas à pas et les fichiers code source Python pour tous les exemples.
Commençons.
- Mise à jour janvier 2017 : modification du calcul de Fold_size dans cross_validation_split() pour qu'il soit toujours un nombre entier. Résout les problèmes avec Python 3.
- Mise à jour d'août 2018 : testée et mise à jour pour fonctionner avec Python 3.6.
Description
Cette section fournit une brève introduction à l'algorithme Perceptron et à l'ensemble de données Sonar auquel nous l'appliquerons plus tard.
Algorithme Perceptron
Le Perceptron s’inspire du traitement de l’information d’une seule cellule neuronale appelée neurone.
Un neurone accepte les signaux d’entrée via ses dendrites, qui transmettent le signal électrique au corps cellulaire.
De la même manière, le Perceptron reçoit des signaux d'entrée provenant d'exemples de données d'entraînement que nous pondérons et combinons dans une équation linéaire appelée activation.
activation = sum(weight_i * x_i) + bias
L'activation est ensuite transformée en une valeur de sortie ou une prédiction à l'aide d'une fonction de transfert, telle que la fonction de transfert par étapes.
prediction = 1.0 if activation >= 0.0 else 0.0
De cette manière, le Perceptron est un algorithme de classification de problèmes à deux classes (0 et 1) où une équation linéaire (semblable ou hyperplan) peut être utilisée pour séparer les deux classes.
Elle est étroitement liée à la régression linéaire et à la régression logistique qui font des prédictions de la même manière (par exemple, une somme pondérée d'entrées).
Les poids de l'algorithme Perceptron doivent être estimés à partir de vos données d'entraînement en utilisant la descente de gradient stochastique.
Descente de gradient stochastique
La descente de gradient est le processus de minimisation d'une fonction en suivant les gradients de la fonction de coût.
Cela implique de connaître la forme du coût ainsi que la dérivée afin qu'à partir d'un point donné, vous connaissiez le gradient et puissiez vous déplacer dans cette direction, par ex. descente vers la valeur minimale.
En apprentissage automatique, nous pouvons utiliser une technique qui évalue et met à jour les poids à chaque itération appelée descente de gradient stochastique pour minimiser l'erreur d'un modèle sur nos données d'entraînement.
La façon dont fonctionne cet algorithme d'optimisation est que chaque instance de formation est présentée au modèle une par une. Le modèle effectue une prédiction pour une instance de formation, l'erreur est calculée et le modèle est mis à jour afin de réduire l'erreur pour la prédiction suivante.
Cette procédure peut être utilisée pour trouver l'ensemble de poids dans un modèle qui entraîne la plus petite erreur pour le modèle sur les données d'entraînement.
Pour l'algorithme Perceptron, à chaque itération les poids (w) sont mis à jour à l'aide de l'équation :
w = w + learning_rate * (expected - predicted) * x
Où w est le poids optimisé, learning_rate est un taux d'apprentissage que vous devez configurer (par exemple 0,01), (attendu – prédit) est la prédiction erreur du modèle sur les données d'entraînement attribuée au poids et x est la valeur d'entrée.
Ensemble de données sonar
L'ensemble de données que nous utiliserons dans ce didacticiel est l'ensemble de données Sonar.
Il s'agit d'un ensemble de données qui décrit les retours de gazouillis du sonar rebondissant sur différents services. Les 60 variables d’entrée représentent la force des rendements sous différents angles. Il s’agit d’un problème de classification binaire qui nécessite un modèle permettant de différencier les roches des cylindres métalliques.
Il s'agit d'un ensemble de données bien compris. Toutes les variables sont continues et généralement comprises entre 0 et 1. Nous n'aurons donc pas à normaliser les données d'entrée, ce qui est souvent une bonne pratique avec l'algorithme Perceptron. La variable de sortie est une chaîne « M » pour le mien et « R » pour le rock, qui devront être converties en entiers 1 et 0.
En prédisant la classe avec le plus d'observations dans l'ensemble de données (M ou mines), l'algorithme de la règle zéro peut atteindre une précision de 53 %.
Vous pouvez en savoir plus sur cet ensemble de données dans le référentiel UCI Machine Learning. Vous pouvez télécharger l'ensemble de données gratuitement et le placer dans votre répertoire de travail sous le nom de fichier sonar.all-data.csv.
Tutoriel
Ce tutoriel se décompose en 3 parties :
- Faire des prédictions.
- Poids du réseau de formation.
- Modélisation de l'ensemble de données Sonar.
Ces étapes vous donneront les bases pour implémenter et appliquer l'algorithme Perceptron à vos propres problèmes de modélisation prédictive de classification.
1. Faire des prédictions
La première étape consiste à développer une fonction capable de faire des prédictions.
Cela sera nécessaire à la fois pour l'évaluation des valeurs de poids candidates dans la descente de gradient stochastique et une fois le modèle finalisé et nous souhaitons commencer à faire des prédictions sur des données de test ou de nouvelles données.
Vous trouverez ci-dessous une fonction nommée predict() qui prédit une valeur de sortie pour une ligne en fonction d'un ensemble de pondérations.
Le premier poids est toujours le biais car il est autonome et n'est pas responsable d'une valeur d'entrée spécifique.
# Make a prediction with weights
def predict(row, weights):
activation = weights[0]
for i in range(len(row)-1):
activation += weights[i + 1] * row[i]
return 1.0 if activation >= 0.0 else 0.0
Nous pouvons créer un petit ensemble de données pour tester notre fonction de prédiction.
X1 X2 Y
2.7810836 2.550537003 0
1.465489372 2.362125076 0
3.396561688 4.400293529 0
1.38807019 1.850220317 0
3.06407232 3.005305973 0
7.627531214 2.759262235 1
5.332441248 2.088626775 1
6.922596716 1.77106367 1
8.675418651 -0.242068655 1
7.673756466 3.508563011 1
Nous pouvons également utiliser des poids préalablement préparés pour faire des prédictions pour cet ensemble de données.
En mettant tout cela ensemble, nous pouvons tester notre fonction predict() ci-dessous.
# Make a prediction with weights
def predict(row, weights):
activation = weights[0]
for i in range(len(row)-1):
activation += weights[i + 1] * row[i]
return 1.0 if activation >= 0.0 else 0.0
# test predictions
dataset = [[2.7810836,2.550537003,0],
[1.465489372,2.362125076,0],
[3.396561688,4.400293529,0],
[1.38807019,1.850220317,0],
[3.06407232,3.005305973,0],
[7.627531214,2.759262235,1],
[5.332441248,2.088626775,1],
[6.922596716,1.77106367,1],
[8.675418651,-0.242068655,1],
[7.673756466,3.508563011,1]]
weights = [-0.1, 0.20653640140000007, -0.23418117710000003]
for row in dataset:
prediction = predict(row, weights)
print("Expected=%d, Predicted=%d" % (row[-1], prediction))
Il existe deux valeurs d'entrée (X1 et X2) et trois valeurs de pondération (bias, w1 et w2). L'équation d'activation que nous avons modélisée pour ce problème est la suivante :
activation = (w1 * X1) + (w2 * X2) + bias
Ou, avec les valeurs de poids spécifiques que nous avons choisies manuellement comme :
activation = (0.206 * X1) + (-0.234 * X2) + -0.1
En exécutant cette fonction, nous obtenons des prédictions qui correspondent aux valeurs de sortie attendues (y).
Expected=0, Predicted=0
Expected=0, Predicted=0
Expected=0, Predicted=0
Expected=0, Predicted=0
Expected=0, Predicted=0
Expected=1, Predicted=1
Expected=1, Predicted=1
Expected=1, Predicted=1
Expected=1, Predicted=1
Expected=1, Predicted=1
Nous sommes maintenant prêts à mettre en œuvre la descente de gradient stochastique pour optimiser nos valeurs de poids.
2. Poids du réseau de formation
Nous pouvons estimer les valeurs de poids de nos données d'entraînement en utilisant la descente de gradient stochastique.
La descente de gradient stochastique nécessite deux paramètres :
- Taux d'apprentissage : utilisé pour limiter la quantité de correction de chaque poids à chaque fois qu'il est mis à jour.
- Époques : nombre de fois où parcourir les données d'entraînement tout en mettant à jour le poids.
Ceux-ci, ainsi que les données d’entraînement, constitueront les arguments de la fonction.
Il y a 3 boucles que nous devons effectuer dans la fonction :
- Parcourez chaque époque en boucle.
- Parcourez chaque ligne des données d’entraînement pour une époque.
- Parcourez chaque poids et mettez-le à jour pour une ligne dans une époque.
Comme vous pouvez le voir, nous mettons à jour chaque poids pour chaque ligne des données d'entraînement, à chaque époque.
Les poids sont mis à jour en fonction de l'erreur commise par le modèle. L'erreur est calculée comme la différence entre la valeur de sortie attendue et la prédiction faite avec les poids candidats.
Il existe un poids pour chaque attribut d'entrée, et ceux-ci sont mis à jour de manière cohérente, par exemple :
w(t+1)= w(t) + learning_rate * (expected(t) - predicted(t)) * x(t)
Le biais est mis à jour de la même manière, sauf sans entrée car il n'est pas associé à une valeur d'entrée spécifique :
bias(t+1) = bias(t) + learning_rate * (expected(t) - predicted(t))
Maintenant, nous pouvons mettre tout cela ensemble. Vous trouverez ci-dessous une fonction nommée train_weights() qui calcule les valeurs de poids pour un ensemble de données d'entraînement à l'aide de la descente de gradient stochastique.
# Estimate Perceptron weights using stochastic gradient descent
def train_weights(train, l_rate, n_epoch):
weights = [0.0 for i in range(len(train[0]))]
for epoch in range(n_epoch):
sum_error = 0.0
for row in train:
prediction = predict(row, weights)
error = row[-1] - prediction
sum_error += error**2
weights[0] = weights[0] + l_rate * error
for i in range(len(row)-1):
weights[i + 1] = weights[i + 1] + l_rate * error * row[i]
print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
return weights
Vous pouvez voir que nous gardons également une trace de la somme de l'erreur quadratique (une valeur positive) à chaque époque afin de pouvoir imprimer un joli message à chaque boucle externe.
Nous pouvons tester cette fonction sur le même petit ensemble de données artificiel ci-dessus.
# Make a prediction with weights
def predict(row, weights):
activation = weights[0]
for i in range(len(row)-1):
activation += weights[i + 1] * row[i]
return 1.0 if activation >= 0.0 else 0.0
# Estimate Perceptron weights using stochastic gradient descent
def train_weights(train, l_rate, n_epoch):
weights = [0.0 for i in range(len(train[0]))]
for epoch in range(n_epoch):
sum_error = 0.0
for row in train:
prediction = predict(row, weights)
error = row[-1] - prediction
sum_error += error**2
weights[0] = weights[0] + l_rate * error
for i in range(len(row)-1):
weights[i + 1] = weights[i + 1] + l_rate * error * row[i]
print('>epoch=%d, lrate=%.3f, error=%.3f' % (epoch, l_rate, sum_error))
return weights
# Calculate weights
dataset = [[2.7810836,2.550537003,0],
[1.465489372,2.362125076,0],
[3.396561688,4.400293529,0],
[1.38807019,1.850220317,0],
[3.06407232,3.005305973,0],
[7.627531214,2.759262235,1],
[5.332441248,2.088626775,1],
[6.922596716,1.77106367,1],
[8.675418651,-0.242068655,1],
[7.673756466,3.508563011,1]]
l_rate = 0.1
n_epoch = 5
weights = train_weights(dataset, l_rate, n_epoch)
print(weights)
Nous utilisons un taux d'apprentissage de 0,1 et entraînons le modèle pour seulement 5 époques, ou 5 expositions des poids à l'ensemble de données d'entraînement.
L'exécution de l'exemple imprime un message à chaque époque avec l'erreur de somme carrée pour cette époque et l'ensemble final de poids.
>epoch=0, lrate=0.100, error=2.000
>epoch=1, lrate=0.100, error=1.000
>epoch=2, lrate=0.100, error=0.000
>epoch=3, lrate=0.100, error=0.000
>epoch=4, lrate=0.100, error=0.000
[-0.1, 0.20653640140000007, -0.23418117710000003]
Vous pouvez voir comment le problème est appris très rapidement par l’algorithme.
Maintenant, appliquons cet algorithme sur un ensemble de données réel.
3. Modélisation de l'ensemble de données Sonar
Dans cette section, nous allons entraîner un modèle Perceptron en utilisant la descente de gradient stochastique sur l'ensemble de données Sonar.
L'exemple suppose qu'une copie CSV de l'ensemble de données se trouve dans le répertoire de travail actuel avec le nom de fichier sonar.all-data.csv.
L'ensemble de données est d'abord chargé, les valeurs de chaîne converties en valeurs numériques et la colonne de sortie est convertie des chaînes en valeurs entières de 0 à 1. Ceci est réalisé avec les fonctions d'assistance load_csv(), str_column_to_float.() et str_column_to_int() pour charger et préparer l'ensemble de données.
Nous utiliserons la validation croisée k-fold pour estimer les performances du modèle appris sur des données invisibles. Cela signifie que nous allons construire et évaluer k modèles et estimer les performances en tant qu'erreur moyenne du modèle. La précision de la classification sera utilisée pour évaluer chaque modèle. Ces comportements sont fournis dans les fonctions d'assistance cross_validation_split(), accuracy_metric() et evaluate_algorithm().
Nous utiliserons les fonctions predict() et train_weights() créées ci-dessus pour entraîner le modèle et une nouvelle fonction perceptron() pour les lier ensemble. .
Vous trouverez ci-dessous l'exemple complet.
# Perceptron Algorithm on the Sonar Dataset
from random import seed
from random import randrange
from csv import reader
# Load a CSV file
def load_csv(filename):
dataset = list()
with open(filename, 'r') as file:
csv_reader = reader(file)
for row in csv_reader:
if not row:
continue
dataset.append(row)
return dataset
# Convert string column to float
def str_column_to_float(dataset, column):
for row in dataset:
row[column] = float(row[column].strip())
# Convert string column to integer
def str_column_to_int(dataset, column):
class_values = [row[column] for row in dataset]
unique = set(class_values)
lookup = dict()
for i, value in enumerate(unique):
lookup[value] = i
for row in dataset:
row[column] = lookup[row[column]]
return lookup
# Split a dataset into k folds
def cross_validation_split(dataset, n_folds):
dataset_split = list()
dataset_copy = list(dataset)
fold_size = int(len(dataset) / n_folds)
for i in range(n_folds):
fold = list()
while len(fold) < fold_size:
index = randrange(len(dataset_copy))
fold.append(dataset_copy.pop(index))
dataset_split.append(fold)
return dataset_split
# Calculate accuracy percentage
def accuracy_metric(actual, predicted):
correct = 0
for i in range(len(actual)):
if actual[i] == predicted[i]:
correct += 1
return correct / float(len(actual)) * 100.0
# Evaluate an algorithm using a cross validation split
def evaluate_algorithm(dataset, algorithm, n_folds, *args):
folds = cross_validation_split(dataset, n_folds)
scores = list()
for fold in folds:
train_set = list(folds)
train_set.remove(fold)
train_set = sum(train_set, [])
test_set = list()
for row in fold:
row_copy = list(row)
test_set.append(row_copy)
row_copy[-1] = None
predicted = algorithm(train_set, test_set, *args)
actual = [row[-1] for row in fold]
accuracy = accuracy_metric(actual, predicted)
scores.append(accuracy)
return scores
# Make a prediction with weights
def predict(row, weights):
activation = weights[0]
for i in range(len(row)-1):
activation += weights[i + 1] * row[i]
return 1.0 if activation >= 0.0 else 0.0
# Estimate Perceptron weights using stochastic gradient descent
def train_weights(train, l_rate, n_epoch):
weights = [0.0 for i in range(len(train[0]))]
for epoch in range(n_epoch):
for row in train:
prediction = predict(row, weights)
error = row[-1] - prediction
weights[0] = weights[0] + l_rate * error
for i in range(len(row)-1):
weights[i + 1] = weights[i + 1] + l_rate * error * row[i]
return weights
# Perceptron Algorithm With Stochastic Gradient Descent
def perceptron(train, test, l_rate, n_epoch):
predictions = list()
weights = train_weights(train, l_rate, n_epoch)
for row in test:
prediction = predict(row, weights)
predictions.append(prediction)
return(predictions)
# Test the Perceptron algorithm on the sonar dataset
seed(1)
# load and prepare data
filename = 'sonar.all-data.csv'
dataset = load_csv(filename)
for i in range(len(dataset[0])-1):
str_column_to_float(dataset, i)
# convert string class to integers
str_column_to_int(dataset, len(dataset[0])-1)
# evaluate algorithm
n_folds = 3
l_rate = 0.01
n_epoch = 500
scores = evaluate_algorithm(dataset, perceptron, n_folds, l_rate, n_epoch)
print('Scores: %s' % scores)
print('Mean Accuracy: %.3f%%' % (sum(scores)/float(len(scores))))
Une valeur k de 3 a été utilisée pour la validation croisée, donnant à chaque pli 208/3=69,3 ou un peu moins de 70 enregistrements à évaluer à chaque itération. Un taux d'apprentissage de 0,1 et 500 époques de formation ont été choisis avec un peu d'expérimentation.
Vous pouvez essayer vos propres configurations et voir si vous pouvez battre mon score.
L'exécution de cet exemple imprime les scores pour chacun des 3 plis de validation croisée, puis imprime la précision moyenne de la classification.
Nous pouvons voir que la précision est d’environ 72 %, supérieure à la valeur de référence d’un peu plus de 50 % si nous avions uniquement prédit la classe majoritaire en utilisant l’algorithme de la règle zéro.
Scores: [76.81159420289855, 69.56521739130434, 72.46376811594203]
Mean Accuracy: 72.947%
Rallonges
Cette section répertorie les extensions de ce didacticiel que vous souhaiterez peut-être envisager d'explorer.
- Réglez l'exemple. Ajustez le taux d'apprentissage, le nombre d'époques et même la méthode de préparation des données pour obtenir un meilleur score sur l'ensemble de données.
- Descente de gradient stochastique par lots. Modifiez l'algorithme de descente de gradient stochastique pour accumuler les mises à jour à chaque époque et ne mettez à jour les poids que dans un lot à la fin de l'époque.
- Problèmes de régression supplémentaires. Appliquez la technique à d’autres problèmes de classification sur le référentiel d’apprentissage automatique UCI.
Avez-vous exploré l'une de ces extensions ?
Faites-le-moi savoir dans les commentaires ci-dessous.
Revoir
Dans ce didacticiel, vous avez découvert comment implémenter l'algorithme Perceptron en utilisant la descente de gradient stochastique à partir de zéro avec Python.
Vous avez appris.
- Comment faire des prédictions pour un problème de classification binaire.
- Comment optimiser un ensemble de poids à l'aide de la descente de gradient stochastique.
- Comment appliquer la technique à un problème réel de modélisation prédictive de classification.
Avez-vous des questions ?
Posez votre question dans les commentaires ci-dessous et je ferai de mon mieux pour y répondre.