Как эффективно рассчитать PPMI на разреженной матрице в R?

Я бы подумал, что между R пакеты text2vec, tm, quanteda, svs, qlcMatrix а также wordspace была бы функция для вычисления PPMI (положительная точечная взаимная информация) между терминами и контекстами (основанная на матрице совпадений термин-термин (контекст)) - но, очевидно, нет, поэтому я решил написать ее сам. Проблема в том, что он медленный, как патока, возможно потому, что я не очень хорош с разреженными матрицами - и мои tcms имеют порядок 10k*20k, поэтому они должны быть разреженными.

Из того, что я понимаю, PMI = log( p(word, context) / (p(word)*p(context)) )следовательно, я считаю, что:

           count(word_context_co-occurrence) / N
PMI = log( -------------------------------------  )
             count(word)/N * count(context)/N 

куда N является суммой всех совпадений в матрице совпадений. И PPMI просто заставляет все значения <0 быть равными 0. (Это пока правильно, верно?)

Имея это в виду, вот попытка реализации:

library(Matrix)
set.seed(1)
pmat = matrix(sample(c(0,0,0,0,0,0,1,10),5*10,T), 5,10, byrow=T) # tiny example matrix; 
# rows are words, columns are contexts (words the row-words co-occur with, in a certain window in the text)
pmat = Matrix(pmat, sparse=T) # make it sparse

# calculate some things beforehand to make it faster
N = sum(pmat)
contextp = Matrix::colSums(pmat)/N # probabilities of contexts
wordp = Matrix::rowSums(pmat)/N    # probabilities of terms

# here goes nothing...
pmat2 = pmat
for(r in 1:nrow(pmat)){ # go term by term, calculate PPMI association with each of its contexts
  not0 = which(pmat[r, ] > 0)  # no need to consider 0 values (no co-occurrence)
  tmp = log( (pmat[r,not0] / N) / (wordp[r] * contextp[not0] )) # PMI
  tmp = ifelse(tmp < 0, 0, tmp)  # PPMI
  pmat2[r, not0] = tmp  # <-- THIS here is the slow part, replacing the old frequency values with the new PPMI weighted ones. 
}
# take a look:
round(pmat2,2)

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

Как эффективнее заменить старые значения в такой разреженной матрице новыми взвешенными значениями PPMI? Либо предложения изменить этот код, либо использовать какую-то существующую функцию в каком-то пакете, который я как-то пропустил - все в порядке.

1 ответ

Тем временем, вроде как, это работает довольно быстро. Я оставлю это здесь на случай, если кто-нибудь еще столкнется с той же проблемой. Также, похоже, очень похоже на подход, связанный в комментарии к вопросу (спасибо!).

# this is for a column-oriented sparse matrix; transpose if necessary
tcmrs = Matrix::rowSums(pmat)
tcmcs = Matrix::colSums(pmat)
N = sum(tcmrs)
colp = tcmcs/N
rowp = tcmrs/N
pp = pmat@p+1
ip = pmat@i+1
tmpx = rep(0,length(pmat@x)) # new values go here, just a numeric vector
# iterate through sparse matrix:
for(i in 1:(length(pmat@p)-1) ){ 
  ind = pp[i]:(pp[i+1]-1)
  not0 = ip[ind]
  icol = pmat@x[ind]
  tmp = log( (icol/N) / (rowp[not0] * colp[i] )) # PMI
  tmpx[ind] = tmp    
}
pmat@x = tmpx
# to convert to PPMI, replace <0 values with 0 and do a Matrix::drop0() on the object.
Другие вопросы по тегам