Cluster SOM para variáveis ​​nominais / circulares

11

Basta saber se alguém está familiarizado com o agrupamento de entradas nominais. Eu estive olhando o SOM como uma solução, mas aparentemente ele só funciona com recursos numéricos. Existem extensões para recursos categóricos? Especificamente, eu estava pensando em "Dias da semana" como possíveis recursos. Obviamente, é possível convertê-lo em um recurso numérico (por exemplo, seg - dom correspondente aos n. 1-7), mas a distância euclidiana entre sol e seg (1 e 7) não seria a mesma que a distância de seg a ter (1 e 2). ) Todas as sugestões ou idéias serão muito apreciadas.

Michael
fonte
(+1) uma pergunta muito interessante
steffen
2
As variáveis ​​cíclicas são melhor consideradas como elementos do círculo unitário no plano Complex. Assim, seria natural mapear os dias da semana para (digamos) os pontos , j = 0 , ... , 6 ; ou seja , ( cos ( 0 ) , sin ( 0 ) ) , ( cos ( 2 π / 7 ) , sin ( 2 π / 7exp(2jπi/7)j=0,,6(cos(0),sin(0)) , ... ( cos ( 12 π / 7 ) , sin ( 12 π / 7 ) ) . (cos(2π/7),sin(2π/7))(cos(12π/7),sin(12π/7))
whuber
1
eu teria que codificar minha própria matriz de distância específica para variáveis ​​cíclicas? apenas imaginando se já havia algoritmos para esse tipo de cluster. thx
Michael
@ Michael: Eu acredito que você desejará especificar sua própria métrica de distância que seja apropriada para sua aplicação e que seja definida em todas as dimensões dos seus dados, não apenas na DOW. Formalmente, deixando x, y denotar pontos em seu espaço de dados, você precisa definir uma função métrica d (x, y) com as propriedades usuais: d (x, x) = 0, d (x, y) = d (y , x) ed (x, z) <= d (x, y) + d (y, z). Depois de fazer isso, criar o SOM é mecânico. O desafio criativo é definir d () de uma maneira que capture a noção de "similaridade" apropriada para sua aplicação.
Arthur pequeno

Respostas:

7

Fundo:

A maneira mais lógica de transformar a hora é em duas variáveis ​​que oscilam para frente e para trás fora de sincronia. Imagine a posição do ponteiro de fim de hora de um relógio de 24 horas. A xposição oscila para frente e para trás fora de sincronia com a yposição. Para um relógio de 24 horas você pode fazer isso com x=sin(2pi*hour/24), y=cos(2pi*hour/24).

Você precisa de ambas as variáveis ​​ou o movimento adequado ao longo do tempo é perdido. Isso se deve ao fato de que a derivada do pecado ou cos muda no tempo, enquanto a (x,y)posição varia suavemente à medida que viaja ao redor do círculo unitário.

Por fim, considere se vale a pena adicionar um terceiro recurso para rastrear o tempo linear, que pode ser construído como horas (ou minutos ou segundos) desde o início do primeiro registro ou um carimbo de data / hora do Unix ou algo semelhante. Esses três recursos fornecem proxies para a progressão cíclica e linear do tempo, por exemplo, você pode retirar fenômenos cíclicos como ciclos de sono no movimento das pessoas e também crescimento linear como população versus tempo.

Exemplo de se está sendo realizado:

# 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

insira a descrição da imagem aqui

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

Agora vamos tentar:

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

insira a descrição da imagem aqui

Você mal consegue ver que há alguns horários após a meia-noite incluídos no cluster verde antes da meia-noite. Agora vamos reduzir o número de clusters e mostrar que antes e depois da meia-noite podem ser conectados em um único cluster com mais detalhes:

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

insira a descrição da imagem aqui

Veja como o cluster azul contém horários anteriores e posteriores à meia-noite agrupados no mesmo cluster ...

Você pode fazer isso por hora, dia da semana, semana do mês, dia do mês, estação do ano ou qualquer outra coisa.

user1745038
fonte
Útil (+1). Esta é uma aplicação em que os gráficos sendo quadrados e não oblongos são realmente importantes. Não conheço o seu software, mas imagino que você possa definir a proporção para 1, longe do padrão.
Nick Cox
Isso é verdade @NickCox. Ou você pode apenas executar a transformação linear em sua cabeça ;-) #
user1745038 18 /
2

As variáveis ​​nominais comuns são codificadas quando usadas no SOM (por exemplo, uma variável com 1 na segunda-feira 0 para não segunda-feira, outra na terça-feira etc.).

Você pode incorporar informações adicionais criando categorias combinadas de dias adjacentes. Por exemplo: segunda e terça-feira, terça e quarta-feira, etc. No entanto, se seus dados estiverem relacionados ao comportamento humano, geralmente será mais útil usar o dia da semana e o fim de semana como categorias.

Tim
fonte
2

Para variáveis ​​nominais, a codificação típica em uma rede neural ou em um contexto de engenharia elétrica é chamada de "um quente" - um vetor de todos os 0s, com um 1 na posição apropriada para o valor da variável. Para os dias da semana, por exemplo, há sete dias, portanto seus vetores de um calor seriam de sete. Então, segunda-feira seria representada como [1 0 0 0 0 0 0], terça-feira como [0 1 0 0 0 0 0]] etc.

Como Tim sugeriu, essa abordagem pode ser generalizada facilmente para abranger vetores de recursos booleanos arbitrários, em que cada posição no vetor corresponde a um recurso de interesse em seus dados, e a posição é definida como 1 ou 0 para indicar a presença ou ausência dos mesmos. característica.

Depois de ter vetores binários, a distância de Hamming se torna uma métrica natural, embora a distância euclidiana também seja usada. Para vetores binários muito quentes, o SOM (ou outro aproximador de função) naturalmente interpola entre 0 e 1 para cada posição do vetor. Nesse caso, esses vetores são frequentemente tratados como parâmetros de uma distribuição Boltzmann ou softmax no espaço da variável nominal; esse tratamento também fornece uma maneira de usar os vetores em algum tipo de cenário de divergência de KL.

Variáveis ​​cíclicas são muito mais complicadas. Como Arthur disse nos comentários, você precisa definir uma métrica de distância que incorpore a natureza cíclica da variável.

lmjohns3
fonte
1

Supondo que o dia da semana (dow) vá de [0, 6], em vez de projetar dados em um círculo, outra opção é usar:

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

Para entender o porquê, considere o dow como um relógio

  6  0
5      1
4      2
    3

diff entre 6 e 1 pode ser 6 - 1 = 5 (indo no sentido horário de 1 a 6) ou 7 - (6 - 1) = 2. Tomar o mínimo de ambas as opções deve ser suficiente.

Em geral, você pode usar: min(abs(diff), range - abs(diff))

ragha
fonte
0

Eu codifiquei com sucesso Dias da semana (e Meses do ano) como tupla de (cos, sin) como whuber destacado em seu comentário. Do que a distância euclidiana utilizada.

Este é um exemplo de código em 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))
}

A distância euclidiana entre 0 e 6 é igual a 0 e 1.

Mario F.
fonte