Regroupement SOM pour les variables nominales / circulaires


11

Je me demande simplement si quelqu'un est familier avec le regroupement des entrées nominales. J'ai regardé SOM comme une solution mais apparemment, cela ne fonctionne qu'avec des fonctionnalités numériques. Existe-t-il des extensions pour les fonctionnalités catégorielles? Plus précisément, je me posais des questions sur les «jours de la semaine» comme fonctionnalités possibles. Bien sûr, il est possible de le convertir en une caractéristique numérique (c.-à-d. Du lundi au dimanche correspondant aux n ° 1 à 7), mais la distance euclidienne entre le soleil et le lundi (1 et 7) ne serait pas la même que la distance du lundi au mardi (1 et 2). ). Toutes suggestions ou idées seraient très appréciées.


(+1) une question très intéressante
steffen

2
Les variables cycliques sont mieux considérées comme des éléments du cercle unitaire dans le plan complexe. Ainsi, il serait naturel de cartographier les jours de la semaine avec (disons) les points , j = 0 , , 6 ; c'est-à - dire , ( cos ( 0 ) , sin ( 0 ) ) , ( cos ( 2 π / 7 ) , sin ( 2 π / 7exp(2jπje/sept)j=0,,6(cos(0),péché(0)) , ... ( cos ( 12 π / 7 ) , sin ( 12 π / 7 ) ) . (cos(2π/sept),péché(2π/sept))(cos(12π/sept),péché(12π/sept))
whuber

1
devrais-je coder ma propre matrice de distance, puis spécifique aux variables cycliques? je me demandais simplement s'il existait déjà des algorithmes pour ce type de clustering. thx
Michael

@Michael: Je pense que vous voudrez spécifier votre propre métrique de distance qui est appropriée pour votre application, et qui est définie sur toutes les dimensions de vos données, pas seulement le DOW. Formellement, en laissant x, y désigner des points dans votre espace de données, vous devez définir une fonction métrique d (x, y) avec les propriétés habituelles: d (x, x) = 0, d (x, y) = d (y , x) et d (x, z) <= d (x, y) + d (y, z). Une fois que vous avez fait cela, la création du SOM est mécanique. Le défi créatif consiste à définir d () d'une manière qui capture la notion de «similitude» appropriée à votre application.
Arthur Small

Réponses:


7

Contexte:

Le moyen le plus logique de transformer l'heure est en deux variables qui oscillent dans les deux sens hors de la synchronisation. Imaginez la position de la fin de l'aiguille des heures d'une horloge de 24 heures. La xposition oscille d'avant en arrière par rapport à la yposition. Pour une horloge de 24 heures , vous pouvez y arriver avec x=sin(2pi*hour/24), y=cos(2pi*hour/24).

Vous avez besoin des deux variables ou le bon mouvement dans le temps est perdu. Cela est dû au fait que la dérivée de sin ou cos change dans le temps tandis que la (x,y)position varie en douceur lorsqu'elle se déplace autour du cercle unitaire.

Enfin, examinez s'il vaut la peine d'ajouter une troisième fonctionnalité pour tracer le temps linéaire, qui peut être construite en heures (ou minutes ou secondes) à partir du début du premier enregistrement ou un horodatage Unix ou quelque chose de similaire. Ces trois caractéristiques fournissent ensuite des indicateurs pour la progression cyclique et linéaire du temps, par exemple, vous pouvez extraire des phénomènes cycliques tels que les cycles de sommeil dans le mouvement des personnes et également une croissance linéaire comme la population en fonction du temps.

Exemple de réalisation:

# Enable inline plotting
%matplotlib inline

#Import everything I need...

import numpy as np
import matplotlib as mp

import matplotlib.pyplot as plt
import pandas as pd

# Grab some random times from here: https://www.random.org/clock-times/
# put them into a csv.
from pandas import DataFrame, read_csv
df = read_csv('/Users/angus/Machine_Learning/ipython_notebooks/times.csv',delimiter=':')
df['hourfloat']=df.hour+df.minute/60.0
df['x']=np.sin(2.*np.pi*df.hourfloat/24.)
df['y']=np.cos(2.*np.pi*df.hourfloat/24.)

df

entrez la description de l'image ici

def kmeansshow(k,X):

    from sklearn import cluster
    from matplotlib import pyplot
    import numpy as np

    kmeans = cluster.KMeans(n_clusters=k)
    kmeans.fit(X)

    labels = kmeans.labels_
    centroids = kmeans.cluster_centers_
    #print centroids

    for i in range(k):
        # select only data observations with cluster label == i
        ds = X[np.where(labels==i)]
        # plot the data observations
        pyplot.plot(ds[:,0],ds[:,1],'o')
        # plot the centroids
        lines = pyplot.plot(centroids[i,0],centroids[i,1],'kx')
        # make the centroid x's bigger
        pyplot.setp(lines,ms=15.0)
        pyplot.setp(lines,mew=2.0)
    pyplot.show()
    return centroids

Essayons maintenant:

kmeansshow(6,df[['x', 'y']].values)

entrez la description de l'image ici

Vous pouvez à peine voir qu'il y a des heures après minuit incluses avec le cluster vert avant minuit. Réduisons maintenant le nombre de clusters et montrons qu'avant et après minuit peuvent être connectés en un seul cluster plus en détail:

kmeansshow(3,df[['x', 'y']].values)

entrez la description de l'image ici

Voyez comment le cluster bleu contient des heures antérieures et postérieures à minuit regroupées dans le même cluster ...

Vous pouvez le faire pour l'heure, le jour de la semaine ou la semaine du mois, le jour du mois ou la saison, etc.


Utile (+1). Il s'agit d'une application où les graphiques étant carrés et non oblongs sont vraiment importants. Je ne connais pas votre logiciel mais j'imagine que vous pouvez régler le rapport d'aspect à 1, loin de la valeur par défaut.
Nick Cox

C'est vrai @NickCox. Ou vous pouvez simplement effectuer la transformation linéaire dans votre tête ;-)
user1745038

2

Les variables nominales courantes sont codées de manière factice lorsqu'elles sont utilisées dans SOM (par exemple, une variable pour avec 1 pour lundi 0 pour pas lundi, une autre pour mardi, etc.).

Vous pouvez incorporer des informations supplémentaires en créant des catégories combinées de jours adjacents. Par exemple: lundi et mardi, mardi et mercredi, etc. Cependant, si vos données se rapportent au comportement humain, il est souvent plus utile d'utiliser Weekday et Weekend comme catégories.


2

Pour les variables nominales, l'encodage typique dans un réseau de neurones ou dans un contexte d'ingénierie électrique est appelé "one-hot" - un vecteur de tous les 0, avec un 1 dans la position appropriée pour la valeur de la variable. Pour les jours de la semaine, par exemple, il y a sept jours, donc vos vecteurs one-hot auraient une longueur de sept. Lundi serait alors représenté comme [1 0 0 0 0 0 0], mardi comme [0 1 0 0 0 0 0], etc.

Comme l'a laissé entendre Tim, cette approche peut être facilement généralisée pour englober des vecteurs de caractéristiques booléennes arbitraires, où chaque position dans le vecteur correspond à une caractéristique d'intérêt dans vos données, et la position est définie sur 1 ou 0 pour indiquer la présence ou l'absence de cette fonctionnalité.

Une fois que vous avez des vecteurs binaires, la distance de Hamming devient une métrique naturelle, bien que la distance euclidienne soit également utilisée. Pour les vecteurs binaires à chaud, le SOM (ou un autre approximateur de fonction) interpolera naturellement entre 0 et 1 pour chaque position de vecteur. Dans ce cas, ces vecteurs sont souvent traités comme les paramètres d'une distribution de Boltzmann ou softmax sur l'espace de la variable nominale; ce traitement permet également d'utiliser les vecteurs dans une sorte de scénario de divergence KL.

Les variables cycliques sont beaucoup plus délicates. Comme Arthur l'a dit dans les commentaires, vous devez définir vous-même une mesure de distance qui incorpore la nature cyclique de la variable.


1

En supposant que le jour de la semaine (dow) passe de [0, 6], au lieu de projeter des données sur un cercle, une autre option consiste à utiliser:

dist = min(abs(dow_diff), 7 - abs(dow_diff))

Pour comprendre pourquoi, considérez le Dow comme une horloge

  6  0
5      1
4      2
    3

le différentiel entre 6 et 1 pourrait être 6 - 1 = 5 (aller dans le sens horaire de 1 à 6) ou 7 - (6 - 1) = 2. Prendre min des deux options devrait faire l'affaire.

En général, vous pouvez utiliser: min(abs(diff), range - abs(diff))


0

J'ai réussi à encoder les jours de la semaine (et les mois de l'année) en tant que tuple de (cos, sin) comme le souligne Whuber dans son commentaire. Que la distance euclidienne utilisée.

Voici un exemple de code dans r:

circularVariable = function(n, r = 4){
 #Transform a circular variable (e.g. Month so the year or day of the week) into two new variables (tuple).
 #n = upper limit of the sequence. E.g. for days of the week this is 7.
 #r =  number of digits to round generated variables.
 #Return
 #
 coord = function(y){
   angle = ((2*pi)/n) *y
   cs = round(cos(angle),r)
   s = round(sin(angle),r)
   c(cs,s)
 }
 do.call("rbind", lapply((0:(n-1)), coord))
}

La distance euclidienne entre 0 et 6 est égale à 0 et 1.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.