名义/循环变量的 SOM 聚类

机器算法验证 聚类 无监督学习 自组织图
2022-03-21 05:52:19

只是想知道是否有人熟悉对标称输入进行聚类。我一直将 SOM 视为一种解决方案,但显然它仅适用于数字特征。分类特征是否有任何扩展?具体来说,我想知道“星期几”是一种可能的功能。当然可以将其转换为数字特征(即 Mon - Sun 对应于 1-7 号)但是那么 Sun 和 Mon (1&7) 之间的欧几里得距离将与 Mon 到 Tues 的距离 (1&2) 不同)。任何建议或想法将不胜感激。

4个回答

背景:

转换小时的最合乎逻辑的方法是转换成两个来回摆动不同步的变量。想象一下 24 小时制时针末端的位置。x位置前后摆动,与位置不同步y对于 24 小时制,您可以使用x=sin(2pi*hour/24),来完成此操作y=cos(2pi*hour/24)

您需要这两个变量,否则会丢失正确的时间运动。这是因为 sin 或 cos 的导数随时间变化,而(x,y)位置在绕单位圆行进时平滑变化。

最后,考虑是否值得添加第三个特征来跟踪线性时间,它可以构造为从第一条记录开始的小时(或分钟或秒)或 Unix 时间戳或类似的东西。然后,这三个特征为时间的循环和线性进程提供了代理,例如,您可以提取循环现象,如人们运动中的睡眠周期,以及人口与时间的线性增长。

如果完成的例子:

# 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

在此处输入图像描述

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

现在让我们尝试一下:

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

在此处输入图像描述

您几乎看不到午夜之前的绿色集群中包含一些午夜之后的时间。现在让我们减少集群的数量,并更详细地展示午夜之前和之后可以连接到一个集群中:

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

在此处输入图像描述

看看蓝色集群如何包含从午夜之前和之后聚集在同一个集群中的时间......

您可以针对时间、一周中的某天、一个月中的一周、一个月中的某天、季节或任何时间执行此操作。

在 SOM 中使用时,通常名义变量是虚拟编码的(例如,一个变量代表星期一,0 代表非星期一,另一个代表星期二,等等)。

您可以通过创建相邻日期的组合类别来合并其他信息。例如:Monday&Tuesday、Tuesday&Wednesday 等。但是,如果您的数据与人类行为相关,则使用 Weekday 和 Weekend 作为类别通常更有用。

对于标称变量,神经网络或电气工程环境中的典型编码称为“one-hot” ——一个全为 0 的向量,其中一个 1 在变量值的适当位置。例如,对于一周中的几天,有 7 天,因此您的 one-hot 向量的长度为 7。然后星期一将表示为 [1 0 0 0 0 0 0],星期二表示为 [0 1 0 0 0 0 0],等等。

正如 Tim 暗示的那样,这种方法可以很容易地推广到包含任意布尔特征向量,其中向量中的每个位置对应于数据中感兴趣的特征,并且位置设置为 1 或 0 以指示存在或不存在该特征。

一旦有了二进制向量,汉明距离就成为一个自然度量,尽管也使用了欧几里得距离。对于 one-hot 二元向量,SOM(或其他函数逼近器)自然会针对每个向量位置在 0 和 1 之间进行插值。在这种情况下,这些向量通常被视为标称变量空间上的玻尔兹曼或 softmax 分布的参数;这种处理也提供了一种在某种 KL 散度场景中使用向量的方法。

循环变量要复杂得多。正如 Arthur 在评论中所说,您需要自己定义一个包含变量循环性质的距离度量。

假设星期几(dow)从 [0, 6] 开始,而不是将数据投影到圆圈上,另一个选项是使用:

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

要了解原因,请将道琼斯指数视为时钟

  6  0
5      1
4      2
    3

6 和 1 之间的差异可能是 6 - 1 = 5(从 1 到 6 顺时针方向)或 7 - (6 - 1) = 2。取两个选项中的最小值应该可以解决问题。

一般来说,您可以使用:min(abs(diff), range - abs(diff))