Есть ли способ нормализовать строки в fcm (т.е. преобразовать счетчики в значения от 0 до 1)? - PullRequest
0 голосов
/ 07 мая 2018

Добрый день,

У меня есть совместное местоположение элемента (fcm, из пакета quanteda в R) размеров 94966 x 94966 (для иллюстрации называется fcm2). Я могу выбрать строки (класс: объект fcm) по имени объекта или номеру строки, например ::

a1 <- fcm2[1,]

и выполнить расчет нормализации для этой конкретной строки:

a2 <- a1/(max(a1)-min(a1))

Моя цель состоит в том, чтобы нормализовать каждую строку в моем оригинальном fcm. Стратегия, которую я попытался, состояла в том, чтобы инициализировать пустую матрицу, а затем использовать цикл for, чтобы пройти по строкам и выполнить вычисление. Сбой инициализации из-за проблем с памятью (Windows 10, 12 ГБ ОЗУ, версия R 3.4.4):

fcm3 <- matrix(data = NA, nrow = 94966, ncol = 94966)
Error: cannot allocate vector of size 33.6 Gb

Мне удалось выполнить нормализацию с помощью структуры данных, но недостаточно памяти для хранения всего fcm2 в качестве кадра данных:

Шаг 1. Извлечение «подматрицы» на основе списка ключевых слов, преобразование в фрейм данных, удаление ненужных столбцов

m <- fcm2[keywords(),]
df_m1 <- as.data.frame(m)
df_m1 <- subset(df_m1, select = -c(document, rt))

Шаг 2: Нормализация

k <- 0 # initialize counter
df2 <- data.frame() # initialize
n4 <- nrow(df_m1) # count rows of the extracted sub-matrix as df (df_m1)

for(k in 1:n4){
  a1 <- df_m1[k,] # store the (n4)th row 
  max_k <- max(a1)
  min_k <- min(a1)
  a2 <- a1/(max_k-min_k) # normalize so max is 1, 0s are still 0s
  df2 <- rbind(df2, a2) # append normalized results into a row of a data.frame
 }

Есть ли более эффективный способ нормализации каждой строки для всего fcm?

Доброе спасибо!

Ответы [ 3 ]

0 голосов
/ 07 мая 2018

Я могу понять, что у OP есть ограничение на память, и он не может выделить память для хранения другой копии этого большого matrix.

Если память позволяет, то решение может быть:

mat1 = t(apply(mat1, 1, function(x) x/(max(x)-min(x))))

При ограничении памяти можно написать функцию для нормализации вектора и применить ее ко всем строкам в for-loop. Это должно быть эффективным способом в данном сценарии.

# Function to normalize a vector
normalise <- function(x){
  x/(max(x)-min(x))
}

#Apply over all rows of matrix
for(i in 1:nrow(mat1)){
  mat1[i,] = normalise(mat1[i,])
}

mat1
#           [,1]       [,2]      [,3]      [,4]
# [1,] 0.5454545 1.27272727 0.2727273 0.6363636
# [2,] 0.6153846 1.15384615 0.1538462 0.9230769
# [3,] 1.0000000 0.81818182 1.8181818 1.5454545
# [4,] 1.7777778 2.11111111 1.1111111 2.0000000
# [5,] 0.3333333 0.08333333 0.4166667 1.0833333

Данные: Используется @ Onyambu

# Data
set.seed(1)
mat1=matrix(sample(20),5)
0 голосов
/ 07 мая 2018

Наиболее эффективный способ заключается в непосредственном воздействии на разреженные значения объекта fcm, избегая любого преобразования в плотный объект, такой как матрица или data.frame. Вот как функции манипулирования и вычисления dfm и fcm определены в quanteda и почему они способны выполнять быстро и в пределах ограниченной памяти.

Чтобы определить такую ​​функцию для вашего типа нормализации, вы можете использовать следующую функцию, которую я продемонстрировал здесь на простом fcm.

library("quanteda")
library("Matrix")

myfcm <- fcm(data_char_sampletext, window = 5)
myfcm
## Feature co-occurrence matrix of: 244 by 244 features.

Теперь мы определим функцию, которая (для удобства) преобразует fcm в разреженное триплетное представление (класс dgTMatrix) и извлекает ненулевые значения, используя split(). Каждый элемент результирующего списка будет представлять строку вашего fcm, но только для ненулевых значений. (Из-за этого мы также должны возвращать ноль для пустых строк.)

fcm_customnorm <- function(x) {
    x <- as(x, "dgTMatrix")
    split_x <- split(x@x, x@i)
    norm_x <- lapply(split_x, function(y) {
        result <- y/(max(y) - min(y))
        # transform any divisions by zero into zero
        result[is.nan(result)] <- 0
        result
    })
    x@x <- unlist(norm_x, use.names = FALSE)
    quanteda:::as.fcm(x)
}

Применяя это к подмножеству, мы видим, что оно работает:

myfcm[1:5, 1:5]
## Feature co-occurrence matrix of: 5 by 5 features.
## 5 x 5 sparse Matrix of class "fcm"
##          features
## features  Instead we have  a Fine
##   Instead       0  5    1  4    1
##   we            0 10    5 20    5
##   have          0  0    0  4    1
##   a             0  0    0  6    4
##   Fine          0  0    0  0    0

fcm_customnorm(myfcm[1:5, 1:5])
## Feature co-occurrence matrix of: 5 by 5 features.
## 5 x 5 sparse Matrix of class "fcm"
##          features
## features  Instead  we      have         a Fine
##   Instead       0 1.0 0.8000000 0.3333333 1.00
##   we            0 0.2 0.2000000 1.3333333 0.25
##   have          0 0   0.6666667 0.3333333 3.00
##   a             0 0   0         0.0000000 2.00
##   Fine          0 0   0         0         0.00

Другим вариантом было бы извлечь простое триплетное представление в data.table (из пакета data.table ) и затем выполнить вычисления с использованием функций группировки и :=. Но этот подход проще и дает желаемый результат, который является нормализованным fcm.

0 голосов
/ 07 мая 2018

Йо может написать функцию:

norm=function(mat){
  mx=mat[cbind(1:nrow(mat),max.col(mat))]
  mn=mat[cbind(1:nrow(mat),max.col(-mat))]
  mat/(mx-mn)
}

И затем использовать ее.

Пример

set.seed(1)
mat1=matrix(sample(20),5)
mat1
     [,1] [,2] [,3] [,4]
[1,]    6   14    3    7  #max is 14, min is 3 thus divide by 11
[2,]    8   15    2   12
[3,]   11    9   20   17
[4,]   16   19   10   18
[5,]    4    1    5   13

norm(mat)
          [,1]       [,2]      [,3]      [,4]
[1,] 0.5454545 1.27272727 0.2727273 0.6363636
[2,] 0.6153846 1.15384615 0.1538462 0.9230769
[3,] 1.0000000 0.81818182 1.8181818 1.5454545
[4,] 1.7777778 2.11111111 1.1111111 2.0000000
[5,] 0.3333333 0.08333333 0.4166667 1.0833333

Вы можете распечатать в виде дроби, чтобы увидетьдействительно ли результаты представляют то, что было необходимо:

MASS::fractions(norm(mat))
     [,1]  [,2]  [,3]  [,4] 
[1,]  6/11 14/11  3/11  7/11
[2,]  8/13 15/13  2/13 12/13
[3,]     1  9/11 20/11 17/11
[4,]  16/9  19/9  10/9     2
[5,]   1/3  1/12  5/12 13/12
...