R: Вычислить расстояние косинуса от матрицы термина-документа с tm и прокси

Я хочу рассчитать косинус-расстояние между авторами корпуса. Давайте возьмем корпус из 20 документов.

require(tm)
data("crude")
length(crude)
# [1] 20

Я хочу узнать расстояние между косинусами (сходство) среди этих 20 документов. Я создаю матрицу term-document с

tdm <- TermDocumentMatrix(crude,
                          control = list(removePunctuation = TRUE,
                                         stopwords = TRUE))

то я должен преобразовать его в матрицу, чтобы передать его в dist() прокси-пакета

tdm <- as.matrix(tdm)
require(proxy)
cosine_dist_mat <- as.matrix(dist(t(tdm), method = "cosine"))

Наконец, я удаляю диагональ моей косинусной матрицы расстояния (поскольку меня не интересует расстояние между документом и собой) и вычисляет среднее расстояние между каждым документом и другим 19 документом корпуса

diag(cosine_dist_mat) <- NA
cosine_dist <- apply(cosine_dist_mat, 2, mean, na.rm=TRUE)

cosine_dist
# 127       144       191       194 
# 0.6728505 0.6788326 0.7808791 0.8003223 
# 211       236       237       242 
# 0.8218699 0.6702084 0.8752164 0.7553570 
# 246       248       273       349 
# 0.8205872 0.6495110 0.7064158 0.7494145 
# 352       353       368       489 
# 0.6972964 0.7134836 0.8352642 0.7214411 
# 502       543       704       708 
# 0.7294907 0.7170188 0.8522494 0.8726240

До сих пор так хорошо (с небольшими корпусами). Проблема заключается в том, что этот метод недостаточно масштабируется для больших корпусов документов. На этот раз кажется неэффективным из-за двух вызовов as.matrix(), чтобы передать tdm из tm в прокси-сервер и, наконец, вычислить среднее значение.

Можно ли представить более разумный способ получить тот же результат?

Ответ 1

Поскольку tm матрицы терминальных документов являются просто разреженными "простыми триплетными матрицами" из пакета slam, вы можете использовать функции для вычисления расстояний непосредственно из определения сходства косинусов:

library(slam)
cosine_dist_mat <- 1 - crossprod_simple_triplet_matrix(tdm)/(sqrt(col_sums(tdm^2) %*% t(col_sums(tdm^2))))

Это использует разреженное умножение матрицы. В моих руках tdm с 2963 терминами в 220 документах и ​​97% разрешающей способностью занимал всего пару секунд.

Я не профилировал это, поэтому я понятия не имею, если он быстрее, чем proxy::dist().

ПРИМЕЧАНИЕ: для этого нужно не принуждать tdm к регулярной матрице, т.е. не делать tdm <- as.matrix(tdm).

Ответ 2

Во-первых. Отличный код MAndrecPhD! Но я считаю, что он хотел написать:

cosine_dist_mat <- crossprod_simple_triplet_matrix(tdm)/(sqrt(col_sums(tdm^2) %*% t(col_sums(tdm^2))))

Его код в письменном виде возвращает оценку несходства. Мы хотим 1 на диагонали для подобия косинуса, а не 0. https://en.wikipedia.org/wiki/Cosine_similarity. Я мог ошибаться, и вы, ребята, действительно хотите получить несходство, но я подумал, что упомянул об этом, поскольку мне потребовалось немного подумать, чтобы разобраться.