R, SOM, пакет Кохонена, обнаружение выбросов

С SOM я немного поэкспериментировал. Сначала я использовал MiniSOM в Python, но меня это не впечатлило, и я перешел на пакет kohonen в R, который предлагает больше функций, чем предыдущий. По сути, я применил SOM для трех случаев использования: (1) кластеризация в 2D с сгенерированными данными, (2) кластеризация с более многомерными данными: встроенный набор данных вина и (3) обнаружение выбросов. Я решил все три варианта использования, но хотел бы поднять вопрос в связи с примененным мною обнаружением выбросов. Для этой цели я использовал вектор som $ distance, который содержит расстояние для каждой строки входного набора данных. Значения с превосходными расстояниями могут быть выбросами. Однако я не знаю, как рассчитывается это расстояние. Описание пакета (https://cran.r-project.org/web/packages/kohonen/kohonen.pdf) указывает для этой метрики: расстояние до ближайшей единицы.

  1. Подскажите, пожалуйста, как рассчитывается это расстояние?
  2. Не могли бы вы прокомментировать используемое мной обнаружение выбросов? Как бы вы это сделали? (В сгенерированном наборе данных он действительно находит выбросы. В реальном наборе данных по вину есть четыре относительно превосходных значения среди 177 сортов вин. См. Диаграммы ниже. Идея, которая пришла мне в голову, использовать гистограммы для отображения этого, я действительно нравиться.)

Графики:

  • Сгенерированные данные, 100 точек в 2D в 5 отдельных кластерах и 2 выброса (Категория 6 показывает выбросы): введите описание изображения здесь

  • Расстояния показаны для всех 102 точек данных, две последние - выбросы, которые были правильно идентифицированы. Я повторил тест с 500 и 1000 точками данных и добавил только 2 выброса. В этих случаях также были обнаружены выбросы. введите описание изображения здесь

  • Расстояния для реального набора данных вина с потенциальными выбросами:  введите описание изображения здесь

Идентификатор строки потенциальных выбросов:

# print the row id of the outliers
# the threshold 10 can be taken from the bar chart,
# below which the vast majority of the values fall
df_wine[df_wine$value > 10, ]

it produces the following output:
    index    value
59     59 12.22916
110   110 13.41211
121   121 15.86576
158   158 11.50079

Мой аннотированный фрагмент кода:

        data(wines)

        scaled_wines <- scale(wines)

        # creating and training SOM
        som.wines <- som(scaled_wines, grid = somgrid(5, 5, "hexagonal"))
        summary(som.wines)

        #looking for outliers, dist = distance to the closest unit
        som.wines$distances

        len <- length(som.wines$distances)
        index_in_vector <- c(1:len)
        df_wine<-data.frame(cbind(index_in_vector, som.wines$distances))
        colnames(df_wine) <-c("index", "value")

        po <-ggplot(df_wine, aes(index, value)) + geom_bar(stat = "identity") 
        po <- po + ggtitle("Outliers?") + theme(plot.title = element_text(hjust = 0.5)) + ylab("Distances in som.wines$distances") + xlab("Number of Rows in the Data Set")
        plot(po)

        # print the row id of the outliers
        # the threshold 10 can be taken from the bar chart,
        # below which the vast majority of the values fall
        df_wine[df_wine$value > 10, ]

Дополнительные образцы кода

Что касается обсуждения в комментариях, я также публикую запрошенные фрагменты кода. Насколько я помню, строки кода, отвечающие за кластеризацию, я построил на основе примеров, которые я нашел в описании пакета Kohonen (https://cran.r-project.org/web/packages/kohonen/kohonen.pdf). Однако я не совсем уверен, это было больше года назад. Код предоставляется как есть, без каких-либо гарантий :-). Имейте в виду, что конкретный подход к кластеризации может работать с разными данными с разной точностью. Я также рекомендовал бы сравнить его с t-SNE в наборе данных по винам (data(wines) доступно в R). Кроме того, внедрите тепловые карты, чтобы продемонстрировать, как расположены данные по отдельным переменным. (В случае приведенного выше примера с двумя переменными это не важно, но было бы неплохо для набора данных вина).

Генерация данных с пятью кластерами и двумя выбросами и построение графиков

            library(stats)
            library(ggplot2)

            library(kohonen)


            generate_data <- function(num_of_points, num_of_clusters, outliers=TRUE){
              num_of_points_per_cluster <- num_of_points/num_of_clusters
              cat(sprintf("#### num_of_points_per_cluster = %s, num_of_clusters = %s \n", num_of_points_per_cluster, num_of_clusters))
              arr<-array()
              
              standard_dev_y <- 6000
              standard_dev_x <- 2
              
              # for reproducibility setting the random generator
              set.seed(10)
              
              for (i in 1:num_of_clusters){
                centroid_y <- runif(1, min=10000, max=200000)
                centroid_x <- runif(1, min=20, max=70)
                cat(sprintf("centroid_x = %s \n, centroid_y = %s", centroid_x, centroid_y ))
                
                vector_y <- rnorm(num_of_points_per_cluster, mean=centroid_y, sd=standard_dev_y)
                vector_x <- rnorm(num_of_points_per_cluster, mean=centroid_x, sd=standard_dev_x)
                cluster <- array(c(vector_y, vector_x), dim=c(num_of_points_per_cluster, 2))
                cluster <- cbind(cluster, i)
                
                arr <- rbind(arr, cluster)
              }
              
              if(outliers){
                #adding two outliers
                arr <- rbind(arr, c(10000, 30, 6))
                arr <- rbind(arr, c(150000, 70, 6))
              }
              
              colnames(arr) <-c("y", "x", "Cluster")
              # WA to remove the first NA row
              arr <- na.omit(arr)
              return(arr)
            }

            scatter_plot_data <- function(data_in, couloring_base_indx, main_label){
              
              df <- data.frame(data_in)
              colnames(df) <-c("y", "x", "Cluster")

              pl <- ggplot(data=df, aes(x = x,y=y)) + geom_point(aes(color=factor(df[, couloring_base_indx]))) 
              pl <- pl + ggtitle(main_label) + theme(plot.title = element_text(hjust = 0.5))
              print(pl)
              
            }

            ##################
            # generating data
            data <- generate_data(100, 5, TRUE)
            print(data)
            scatter_plot_data(data, couloring_base_indx<-3, "Original Clusters without Outliers \n 102 Points")

Подготовка, кластеризация и построение графиков

Я использовал подход иерархической кластеризации с картой Кохонена (SOM).

            normalising_data <- function(data){
              # normalizing data points not the cluster identifiers
              mtrx <- data.matrix(data)
              umtrx <- scale(mtrx[,1:2])
              umtrx <- cbind(umtrx, factor(mtrx[,3]))
              colnames(umtrx) <-c("y", "x", "Cluster")
              return(umtrx)
            }

            train_som <- function(umtrx){
              # unsupervised learning
              set.seed(7)
              g <- somgrid(xdim=5, ydim=5, topo="hexagonal")
              #map<-som(umtrx[, 1:2], grid=g, alpha=c(0.005, 0.01), radius=1, rlen=1000)
              map<-som(umtrx[, 1:2], grid=g)
              summary(map)
              
              return(map)
            }

            plot_som_data <- function(map){
              par(mfrow=c(3,2))
              # to plot some charactristics of the SOM map
              plot(map, type='changes')
              plot(map, type='codes', main="Mapping Data")
              plot(map, type='count')
              plot(map, type='mapping') # how many data points are held by each neuron
              plot(map, type='dist.neighbours') # the darker the colours are, the closer the point are; the lighter the colours are, the more distant the points are
              
              #to switch the plot config to the normal
              par(mfrow=c(1,1))
            }

            plot_disstances_to_the_closest_point <- function(map){
              
              # to see which neuron is assigned to which value 
              map$unit.classif
              
              #looking for outliers, dist = distance to the closest unit
              map$distances
              max(map$distances)
              
              len <- length(map$distances)
              index_in_vector <- c(1:len)
              df<-data.frame(cbind(index_in_vector, map$distances))
              colnames(df) <-c("index", "value")
              
              po <-ggplot(df, aes(index, value)) + geom_bar(stat = "identity") 
              po <- po + ggtitle("Outliers?") + theme(plot.title = element_text(hjust = 0.5)) + ylab("Distances in som$distances") + xlab("Number of Rows in the Data Set")
              plot(po)
              
              return(df)
              
            }


            ###################
            # unsupervised learning

            umtrx <- normalising_data(data)

            map<-train_som(umtrx)
            plot_som_data(map)

            #####################
            # creating the dendogram and then the clusters for the neurons
            dendogram <- hclust(object.distances(map, "codes"), method = 'ward.D')
            plot(dendogram)

            clusters <- cutree(dendogram, 7)
            clusters
            length(clusters)

            #visualising the clusters on the map
            par(mfrow = c(1,1))
            plot(map, type='dist.neighbours', main="Mapping Data")
            add.cluster.boundaries(map, clusters)

Сюжеты с кластерами

Вы также можете создавать хорошие тепловые карты для выбранных переменных, но я не реализовал их для кластеризации с двумя переменными, это не имеет смысла. Если вы реализуете его для набора данных вина, добавьте код и диаграммы в этот пост.

            #see the predicted clusters with the data set
            # 1. add the vector of the neuron ids to the data
            mapped_neurons <- map$unit.classif
            umtrx <- cbind(umtrx, mapped_neurons)

            # 2. taking the predicted clusters and adding them the the original matrix
            # very good description of the apply functions:
            # https://www.guru99.com/r-apply-sapply-tapply.html
            get_cluster_for_the_row <- function(x, cltrs){
              return(cltrs[x])
            }

            predicted_clusters <- sapply (umtrx[,4], get_cluster_for_the_row, cltrs<-clusters)

            mtrx <- cbind(mtrx, predicted_clusters)
            scatter_plot_data(mtrx, couloring_base_indx<-4, "Predicted Clusters with Outliers \n 100 points")

См. Прогнозируемые кластеры ниже в случае, если были выбросы, а в случае, если их не было.

введите описание изображения здесь  введите описание изображения здесь  введите описание изображения здесь


person Tamas    schedule 14.05.2019    source источник
comment
Отличный вопрос, Тамас! Как вы построили график 5 кластеров с двумя выбросами?   -  person Noob    schedule 23.01.2021
comment
Привет, Нуб, насколько я помню, я случайным образом выбирал координаты x и y для 2D-пространства в заданном диапазоне для центроидов кластеров. Затем вокруг каждого центроида (x, y) я сгенерировал случайные значения x 'и y' из нормального распределения со средним x и средним y. Стандартное отклонение я выбрал как мне понравилось :-). Когда у меня были точки в 5 кластерах, я произвольно добавил 2 точки, которые не принадлежали ни одному сгенерированному кластеру. Если нужно, я попробую поискать написанный мной код ....   -  person Tamas    schedule 24.01.2021
comment
Большое спасибо! Дайте мне знать, сможете ли вы найти код   -  person Noob    schedule 24.01.2021
comment
Я также работаю над аналогичным вопросом здесь: stackoverflow.com/questions/ 65864333 /   -  person Noob    schedule 24.01.2021
comment
Просто вопрос: вы когда-нибудь использовали сети коонен на каких-то реальных данных? Как прошло?   -  person Noob    schedule 24.01.2021
comment
Я поищу код, думаю, он у меня где-то есть :-). Я вернусь к вам через 1-2 дня.   -  person Tamas    schedule 25.01.2021
comment
Привет, Нуб, я добавил аннотированный код, который вы просили. Комментарии дают подсказки. Было бы неплохо сравнить производительность SOM с t-SNE на разных наборах данных. См. Выше. Если у вас более двух переменных, было бы неплохо реализовать тепловые карты для пар переменных. Если да, пожалуйста, добавьте его в сообщение. Я желаю вам всего наилучшего в вашем анализе.   -  person Tamas    schedule 26.01.2021
comment
Привет, Тамас, я только что попробовал твой код. Все нормально работает: неужели вы имели ввиду umtrx вместо mtrx? mtrx нигде не определен   -  person Noob    schedule 19.02.2021


Ответы (1)


  1. Я не совсем уверен, но я часто обнаруживаю, что измерение расстояния между двумя объектами, находящимися в определенном пространственном измерении, в основном использует евклидово расстояние. Например, две точки A и B в двумерном пространстве с местоположением A (x = 3, y = 4) и B (x = 6, y = 8) находятся на расстоянии 5 единиц расстояния друг от друга. Это результат вычисления квадратного корня ((3-6) ^ 2 + (4-8) ^ 2). Это также применяется к данным, имеющим большее измерение, путем добавления конечной степени двойки разницы значений двух точек в конкретном измерении. Если A (x = 3, y = 4, z = 5) и B (x = 6, y = 8, z = 7), то расстояние равно квадратному корню ((3-6) ^ 2 + (4-8) ^ 2 + (5-7) ^ 2) и так далее. В kohonen, я думаю, что после того, как модель завершит этап обучения, алгоритм затем вычисляет расстояния от каждого элемента данных до всех узлов, а затем назначает его ближайшему узлу (узлу, который имеет наименьшее расстояние до него). В конце концов, значения внутри переменной «distance», возвращаемые моделью, представляют собой расстояние от каждой точки отсчета до ближайшего узла. Из вашего сценария следует отметить, что алгоритм не измеряет расстояние непосредственно от исходных значений свойств, которые имеют данные, потому что они были масштабированы до подачи данных в модель. Измерение расстояния применяется к масштабированной версии данных. Масштабирование - это стандартная процедура для устранения преобладания одной переменной над другой.
  2. Я считаю, что ваш метод приемлем, потому что значения внутри переменной «distance» - это расстояние от каждого элемента данных до ближайшего к нему узла. Таким образом, если значение расстояния между датумом и ближайшим к нему узлом высокое, это также означает: расстояние между датумом и другими узлами, очевидно, намного больше.
person h45    schedule 24.08.2019