Как эффективно получить верхние K-подобные векторы по косинусному сходству с помощью R?

Я работаю над проблемой большой размерности (~ 4k членов) и хотел бы получить верхнее k-подобное (по косинусному сходству) и не могу позволить себе выполнить попарные вычисления.

Мой обучающий набор представляет собой матрицу размером 6 миллионов x 4k, и я хотел бы сделать прогнозы для матрицы 600k x 4k.

Каков наиболее эффективный способ получить k-похожих элементов для каждого элемента в моей матрице 600k x 4k?

В идеале я хотел бы получить матрицу размером 600 тыс. X 10 (т. Е. 10 лучших похожих элементов для каждого из 600 тыс. Элементов).

ps: я исследовал веб-сайт SO и обнаружил, что почти все вопросы «косинусное сходство в R» относятся к cosine_sim(vector1, vector2). Но этот вопрос относится к cosine_sim(matrix1, matrix2).

Обновить В следующем коде используется простой метод определения косинусного сходства между каждой строкой в ​​тестовом наборе и каждой строкой в ​​обучающем наборе.

set.seed(123)
train<-matrix(round(runif(30),0),nrow=6,ncol=5)
set.seed(987)
test<-matrix(round(runif(20),0),nrow=4,ncol=5)
train

[1,]    0    1    1    0    1    
[2,]    1    1    1    1    1    
[3,]    0    1    0    1    1    
[4,]    1    0    1    1    1    
[5,]    1    1    0    1    0    
[6,]    0    0    0    1    0

test

[1,]    0    1    1    0    0
[2,]    1    0    1    0    1
[3,]    1    0    0    0    0
[4,]    1    0    0    1    1

coSim<-function(mat1, mat2, topK){
require(plyr)
#mat2: is the testset
#mat1: is the training set. We will find cosine similarity between each row in testset and every row in trainingset.
#topK: user-input. for each row in testset we will return 'topk' similar rows(index) from the testset

#set up an empty result matrix. nrow(result) will be the same as the cartesian product between mat1 & mat2.
result<-matrix(rep(NA, nrow(mat1)*nrow(mat2)), nrow=nrow(mat1)*nrow(mat2), ncol=3)
k=1
for(i in 1:nrow(mat2)){
    for(j in 1:nrow(mat1)){
    result[k,1]<-i
    result[k,2]<-j
    result[k,3]<-crossprod(mat1[j,], mat2[i,])/sqrt(crossprod(mat1[j,]) * crossprod(mat2[i,]))
    k<-k+1
        }
    }
#sort the result matrix by cosine similarity found for each row in testset. not sure how to keep topK from each group so convert to df
result<-as.data.frame(result)
colnames(result)<-c("testRowId", "trainRowId","CosineSimilarity")
result<-ddply(result, "testRowId", function(x) head(x[order(x$CosineSimilarity, decreasing = TRUE) , ], topK))
resultMat<-matrix(result$trainRowId, nrow=nrow(mat2), ncol=topK,byrow=T)
finalResult<-list(similarity=result, index=resultMat)
}

system.time(cosineSim<-coSim(train, test, topK=2)) #0.12 secs
cosineSim
$similarity
  testRowId trainRowId CosineSimilarity
1         1          1        0.8164966
2         1          2        0.6324555
3         2          4        0.8660254
4         2          2        0.7745967
5         3          5        0.5773503
6         3          4        0.5000000
7         4          4        0.8660254
8         4          2        0.7745967

$index
     [,1] [,2]
[1,]    1    2
[2,]    4    2
[3,]    5    4
[4,]    4    2


set.seed(123)
train<-matrix(round(runif(1000000),0),nrow=5000,ncol=200)
set.seed(987)
test<-matrix(round(runif(400000),0),nrow=2000,ncol=200)
system.time(cosineSim<-coSim(train, test, topK=50)) #380secs

Когда я запускаю ту же функцию с матрицей 5000x200 для обучения и матрицей 2000x200 для тестирования, это заняло более 380 секунд.

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


person Community    schedule 22.09.2013    source источник
comment
@ человеку, который проголосовал против этого вопроса: если вы собираетесь проголосовать против, возможно, было бы полезно, если бы вы могли добавить комментарий о том, почему вы это делаете.   -  person    schedule 22.09.2013
comment
Я не голосовал против. Но вы должны прочитать это, прежде чем размещать вопрос.   -  person Metrics    schedule 22.09.2013
comment
@Metrics: спасибо, я обновил свой вопрос кодом. Надеюсь, теперь вопрос ясен.   -  person    schedule 23.09.2013


Ответы (1)


Нет необходимости вычислять сходство для каждой строки. Вместо этого вы можете использовать это:

coSim2<-function(mat1, mat2, topK){
    #similarity computation:

    xy <- tcrossprod(mat1, mat2)
    xx <- rowSums(mat1^2)
    yy <- rowSums(mat2^2)
    result <- xy/sqrt(outer(xx,yy))

    #top similar rows from train (per row in test):

    top <- apply(result, 2, order, decreasing=TRUE)[1:topK,]
    result_df <- data.frame(testRowId=c(col(top)), trainRowId=c(top))
    result_df$CosineSimilarity <- result[as.matrix(result_df[,2:1])]
    list(similarity=result_df, index=t(top))
}

Тестовые данные (я уменьшил вашу train матрицу)

set.seed(123)
train<-matrix(round(runif(100000),0),nrow=500,ncol=200)
set.seed(987)
test<-matrix(round(runif(400000),0),nrow=2000,ncol=200)

Результат:

> system.time(cosineSim<-coSim(train, test, topK=50)) #380secs
   user  system elapsed 
  41.71    1.59   43.72 

> system.time(cosineSim2<-coSim2(train, test, topK=50)) #380secs
   user  system elapsed 
   0.46    0.02    0.49 

Используя вашу полную матрицу 5000 x 200 train, coSim2 выполняется за 7,8 секунды.

Также обратите внимание:

> any(cosineSim$similarity != cosineSim2$similarity)
[1] FALSE
> any(cosineSim$index != cosineSim2$index)
[1] FALSE

Вы не можете использовать identical, потому что моя функция возвращает целые числа вместо удвоений для идентификаторов строк.

person Ferdinand.kraft    schedule 22.09.2013
comment
Выглядит отлично. После работы присмотрюсь и отчитаюсь (скорее всего, приму ваш ответ!). - person ; 23.09.2013
comment
Мои извинения. Я проголосовал за, вместо того, чтобы нажимать на галочку. Теперь все готово. - person ; 24.09.2013
comment
Привет, у меня та же проблема, я использую python, а не R, не могли бы вы объяснить свой код? благодаря. - person user1024; 24.12.2015