Pas à pas sur la théorie, l'implémentation en python et l'analyse à l'aide de scikit-learn sur l'algorithme précédemment repris dans "Classification of Machine Learning" J'étudierai avec. Je l'écris pour un apprentissage personnel, alors j'aimerais que vous oubliez toute erreur.
Dernière fois, la classification à 2 classes a été étendue à la classification multi-classes. Cette fois, je vais l'implémenter en Python.
J'ai fait référence aux sites suivants. Merci beaucoup.
Je voudrais étendre Régression logistique précédemment implémentée à plusieurs classes. La méthode est
Je vais essayer.
Les données Ayame sont utilisées pour la classification. Il utilise 4 quantités de caractéristiques (sepal_length, sepal_width, petal_length, petal_width) et les classe en 3 classes (setosa, versicolor, virginica).
Ci-dessous, nous allons implémenter la classification en utilisant sepal_length et sepal_width pour plus de clarté.
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.datasets import load_iris
sns.set()
iris = sns.load_dataset("iris")
ax = sns.scatterplot(x=iris.sepal_length, y=iris.sepal_width,
hue=iris.species, style=iris.species)
One-vs-Rest One-vs-Rest crée un classificateur à deux classes pour chaque classe d'étiquette à apprendre, et utilise enfin la valeur la plus plausible. Puisque la régression logistique produit une valeur de probabilité, la classification du classificateur avec la probabilité la plus élevée est adoptée.
Utilisez la classe LogisticRegression, qui est une version légèrement modifiée du code de régression logistique que nous avons utilisé la dernière fois. J'ai fait une méthode predict_proba
car elle détermine quelle valeur utiliser avec une probabilité.
from scipy import optimize
class LogisticRegression:
def __init__(self):
self.w = None
def sigmoid(self, a):
return 1.0 / (1 + np.exp(-a))
def predict_proba(self, x):
x = np.hstack([1, x])
return self.sigmoid(self.w.T @ x)
def predict(self, x):
return 1 if self.predict_proba(x)>=0.5 else -1
def cross_entropy_loss(self, w, *args):
def safe_log(x, minval=0.0000000001):
return np.log(x.clip(min=minval))
t, x = args
loss = 0
for i in range(len(t)):
ti = 1 if t[i] > 0 else 0
h = self.sigmoid(w.T @ x[i])
loss += -ti*safe_log(h) - (1-ti)*safe_log(1-h)
return loss/len(t)
def grad_cross_entropy_loss(self, w, *args):
t, x = args
grad = np.zeros_like(w)
for i in range(len(t)):
ti = 1 if t[i] > 0 else 0
h = self.sigmoid(w.T @ x[i])
grad += (h - ti) * x[i]
return grad/len(t)
def fit(self, x, y):
w0 = np.ones(len(x[0])+1)
x = np.hstack([np.ones((len(x),1)), x])
self.w = optimize.fmin_cg(self.cross_entropy_loss, w0, fprime=self.grad_cross_entropy_loss, args=(y, x))
@property
def w_(self):
return self.w
Implémentez la classe One-vs-Rest. J'ai également implémenté la méthode ʻaccuracy_score` pour calculer à quel point la réponse est correcte, car je l'utiliserai plus tard pour la comparaison d'algorithmes.
from sklearn.metrics import accuracy_score
class OneVsRest:
def __init__(self, classifier, labels):
self.classifier = classifier
self.labels = labels
self.classifiers = [classifier() for _ in range(len(self.labels))]
def fit(self, x, y):
y = np.array(y)
for i in range(len(self.labels)):
y_ = np.where(y==self.labels[i], 1, 0)
self.classifiers[i].fit(x, y_)
def predict(self, x):
probas = [self.classifiers[i].predict_proba(x) for i in range(len(self.labels))]
return np.argmax(probas)
def accuracy_score(self, x, y):
pred = [self.labels[self.predict(i)] for i in x]
acc = accuracy_score(y, pred)
return acc
Classifiez en fait en utilisant les données précédentes.
model = OneVsRest(LogisticRegression, np.unique(iris.species))
x = iris[['sepal_length', 'sepal_width']].values
y = iris.species
model.fit(x, y)
print("accuracy_score: {}".format(model.accuracy_score(x,y)))
accuracy_score: 0.8066666666666666
Le taux de réponse correcte de 81% n'est pas très bon. Visualisons comment il a été classé.
Utilisez la méthode contourf
de matplotlib pour la visualisation. Couleurs selon lesquelles la valeur du point de grille est classée
from matplotlib.colors import ListedColormap
x_min = iris.sepal_length.min()
x_max = iris.sepal_length.max()
y_min = iris.sepal_width.min()
y_max = iris.sepal_width.max()
x = np.linspace(x_min, x_max, 100)
y = np.linspace(y_min, y_max, 100)
data = []
for i in range(len(y)):
data.append([model.predict([x[j], y[i]]) for j in range(len(x))])
xx, yy = np.meshgrid(x, y)
cmap = ListedColormap(('blue', 'orange', 'green'))
plt.contourf(xx, yy, data, alpha=0.25, cmap=cmap)
ax = sns.scatterplot(x=iris.sepal_length, y=iris.sepal_width,
hue=iris.species, style=iris.species)
plt.show()
Comme vous pouvez le voir, setosa est correctement classé, mais les deux classes restantes sont mixtes, il semble donc que le taux de réponse correct soit un peu faible. Pour le moment, ce sera comme ça.
Implémentez la classe LogisticRegressionMulti pour la classification softmax dans la régression logistique.
L'erreur d'entropie croisée a été utilisée comme fonction d'erreur pour l'évaluation, et les paramètres ont été calculés en utilisant la méthode de descente de gradient la plus raide. Je l'ai fait assez correctement, je suis désolé
from sklearn.metrics import accuracy_score
class LogisticRegressionMulti:
def __init__(self, labels, n_iter=1000, eta=0.01):
self.w = None
self.labels = labels
self.n_iter = n_iter
self.eta = eta
self.loss = np.array([])
def softmax(self, a):
if a.ndim==1:
return np.exp(a)/np.sum(np.exp(a))
else:
return np.exp(a)/np.sum(np.exp(a), axis=1)[:, np.newaxis]
def cross_entropy_loss(self, w, *args):
x, y = args
def safe_log(x, minval=0.0000000001):
return np.log(x.clip(min=minval))
p = self.softmax(x @ w)
loss = -np.sum(y*safe_log(p))
return loss/len(x)
def grad_cross_entropy_loss(self, w, *args):
x, y = args
p = self.softmax(x @ w)
grad = -(x.T @ (y-p))
return grad/len(x)
def fit(self, x, y):
self.w = np.ones((len(x[0])+1, len(self.labels)))
x = np.hstack([np.ones((len(x),1)), x])
for i in range(self.n_iter):
self.loss = np.append(self.loss, self.cross_entropy_loss(self.w, x, y))
grad = self.grad_cross_entropy_loss(self.w, x, y)
self.w -= self.eta * grad
def predict(self, x):
x = np.hstack([1, x])
return np.argmax(self.softmax(x @ self.w))
def accuracy_score(self, x, y):
pred = [self.predict(i) for i in x]
y_ = np.argmax(y, axis=1)
acc = accuracy_score(y_, pred)
return acc
@property
def loss_(self):
return self.loss
L'entrée dans LogisticRegressionMulti
utilise une étiquette One-Hot-Encoded. C'est facile avec les pandas 'get_dummies'. (J'ai pensé après l'avoir fait, mais j'aurais dû utiliser get_dummies dans la classe)
model = LogisticRegressionMulti(np.unique(iris.species), n_iter=10000, eta=0.1)
x = iris[['sepal_length', 'sepal_width']].values
y = pd.get_dummies(iris['species']).values
model.fit(x, y)
print("accuracy_score: {}".format(model.accuracy_score(x, y)))
accuracy_score: 0.8266666666666667
Le taux de réponse correcte est d'environ 83%. En regardant l'historique des erreurs, il semble qu'il a convergé, donc cela semble être le cas.
Aussi, colorions comment il est classé de la même manière qu'avant.
Enfin, en utilisant toutes les fonctionnalités, comparez le classificateur créé cette fois avec la classe LogisticRegression de scikit-learn.
Méthode | accuracy_score |
---|---|
OneVsRest | 0.98 |
LogisticRegressionMulti | 0.98 |
sklearn LogisticRegression | 0.973 |
Même avec cette implémentation, il semble que vous puissiez obtenir un bon score s'il s'agit de la classification d'Ayame.
Implémentation de la classification multi-classes à l'aide de la régression logistique. Je pense que d'autres classificateurs peuvent être utilisés de la même manière. Surtout dans les réseaux de neurones, le softmax multi-classes est une méthode courante, j'ai donc pensé qu'il serait utile de comprendre la partie théorique plus tard.
Recommended Posts