web-dev-qa-db-fra.com

Modèles thématiques: validation croisée avec log vraisemblance et perplexité

Je regroupe des documents à l'aide de la modélisation de sujet. Je dois trouver les numéros de sujet optimaux. J'ai donc décidé de faire une validation croisée décuplée avec les sujets 10, 20, ... 60.

J'ai divisé mon corpus en dix lots et mis de côté un lot pour un jeu d'exclusion. J'ai exécuté l'allocation de dirichlet latente (LDA) en utilisant neuf lots (180 documents au total) avec les sujets 10 à 60. Maintenant, je dois calculer la perplexité ou la probabilité de journalisation pour l'ensemble d'exclusion.

J'ai trouvé ce code lors d'une des sessions de discussion de CV. Je ne comprends vraiment pas plusieurs lignes de codes ci-dessous. J'ai la matrice dtm en utilisant le jeu d'exclusion (20 documents). Mais je ne sais pas comment calculer la perplexité ou la probabilité de journalisation de cet ensemble d'exclusion.


Des questions:

  1. Quelqu'un peut-il m'expliquer ce que seq (2, 100, par = 1) signifie ici? Aussi, que signifie AssociatedPress [21:30]? Quelle fonction (k) fait ici?

    best.model <- lapply(seq(2, 100, by=1), function(k){ LDA(AssociatedPress[21:30,], k) })
    
  2. Si je veux calculer la perplexité ou la probabilité de journalisation de l'ensemble d'exclusion appelé dtm, y a-t-il un meilleur code? Je sais qu'il existe des fonctions perplexity() et logLik() mais comme je suis nouveau, je ne peux pas comprendre comment l'implémenter avec ma matrice d'exclusion, appelée dtm.

  3. Comment puis-je effectuer une validation croisée décuplée avec mon corpus contenant 200 documents? Existe-t-il un code existant que je peux invoquer? J'ai trouvé caret à cet effet, mais encore une fois, je ne peux pas le comprendre non plus.

22
user37874

La réponse acceptée à cette question est bonne dans la mesure où elle va, mais elle ne traite pas réellement de la façon d'estimer la perplexité sur un ensemble de données de validation et comment utiliser la validation croisée.

Utiliser la perplexité pour une validation simple

Perplexité est une mesure de l'adéquation d'un modèle de probabilité avec un nouvel ensemble de données. Dans le package topicmodels R, il est simple de s'adapter à la fonction perplexity, qui prend comme arguments un modèle de rubrique précédemment adapté et un nouvel ensemble de données, et renvoie un seul numéro. Le plus bas sera le mieux.

Par exemple, diviser les données AssociatedPress en un ensemble d'apprentissage (75% des lignes) et un ensemble de validation (25% des lignes):

# load up some R packages including a few we'll need later
library(topicmodels)
library(doParallel)
library(ggplot2)
library(scales)

data("AssociatedPress", package = "topicmodels")

burnin = 1000
iter = 1000
keep = 50

full_data  <- AssociatedPress
n <- nrow(full_data)
#-----------validation--------
k <- 5

splitter <- sample(1:n, round(n * 0.75))
train_set <- full_data[splitter, ]
valid_set <- full_data[-splitter, ]

fitted <- LDA(train_set, k = k, method = "Gibbs",
                          control = list(burnin = burnin, iter = iter, keep = keep) )
perplexity(fitted, newdata = train_set) # about 2700
perplexity(fitted, newdata = valid_set) # about 4300

La perplexité est plus élevée pour l'ensemble de validation que pour l'ensemble de formation, car les sujets ont été optimisés en fonction de l'ensemble de formation.

Utiliser la perplexité et la validation croisée pour déterminer un bon nombre de sujets

L'extension de cette idée à la validation croisée est simple. Divisez les données en différents sous-ensembles (disons 5), et chaque sous-ensemble obtient un tour en tant qu'ensemble de validation et quatre tours en tant qu'élément de l'ensemble d'apprentissage. Cependant, c'est vraiment beaucoup de calcul, en particulier lorsque vous essayez le plus grand nombre de sujets.

Vous pourrez peut-être utiliser caret pour ce faire, mais je soupçonne qu'il ne gère pas encore la modélisation de sujet. En tout cas, c'est le genre de chose que je préfère faire moi-même pour être sûr de comprendre ce qui se passe.

Le code ci-dessous, même avec un traitement parallèle sur 7 CPU logiques, a pris 3,5 heures pour s'exécuter sur mon ordinateur portable:

#----------------5-fold cross-validation, different numbers of topics----------------
# set up a cluster for parallel processing
cluster <- makeCluster(detectCores(logical = TRUE) - 1) # leave one CPU spare...
registerDoParallel(cluster)

# load up the needed R package on all the parallel sessions
clusterEvalQ(cluster, {
   library(topicmodels)
})

folds <- 5
splitfolds <- sample(1:folds, n, replace = TRUE)
candidate_k <- c(2, 3, 4, 5, 10, 20, 30, 40, 50, 75, 100, 200, 300) # candidates for how many topics

# export all the needed R objects to the parallel sessions
clusterExport(cluster, c("full_data", "burnin", "iter", "keep", "splitfolds", "folds", "candidate_k"))

# we parallelize by the different number of topics.  A processor is allocated a value
# of k, and does the cross-validation serially.  This is because it is assumed there
# are more candidate values of k than there are cross-validation folds, hence it
# will be more efficient to parallelise
system.time({
results <- foreach(j = 1:length(candidate_k), .combine = rbind) %dopar%{
   k <- candidate_k[j]
   results_1k <- matrix(0, nrow = folds, ncol = 2)
   colnames(results_1k) <- c("k", "perplexity")
   for(i in 1:folds){
      train_set <- full_data[splitfolds != i , ]
      valid_set <- full_data[splitfolds == i, ]

      fitted <- LDA(train_set, k = k, method = "Gibbs",
                    control = list(burnin = burnin, iter = iter, keep = keep) )
      results_1k[i,] <- c(k, perplexity(fitted, newdata = valid_set))
   }
   return(results_1k)
}
})
stopCluster(cluster)

results_df <- as.data.frame(results)

ggplot(results_df, aes(x = k, y = perplexity)) +
   geom_point() +
   geom_smooth(se = FALSE) +
   ggtitle("5-fold cross-validation of topic modelling with the 'Associated Press' dataset",
           "(ie five different models fit for each candidate number of topics)") +
   labs(x = "Candidate number of topics", y = "Perplexity when fitting the trained model to the hold-out set")

Nous voyons dans les résultats que 200 sujets sont trop nombreux et ont un sur-ajustement, et 50 est trop peu. Sur le nombre de sujets essayés, 100 est le meilleur, avec la perplexité moyenne la plus faible sur les cinq différents ensembles d'expositions.

enter image description here

20
Peter Ellis

J'ai écrit la réponse sur le CV auquel vous vous référez, voici un peu plus de détails:

  1. seq(2, 100, by =1) crée simplement une séquence de nombres de 2 à 100 par unités, donc 2, 3, 4, 5, ... 100. Ce sont les nombres de sujets que je veux utiliser dans les modèles. Un modèle avec 2 sujets, un autre avec 3 sujets, un autre avec 4 sujets et ainsi de suite jusqu'à 100 sujets.

  2. AssociatedPress[21:30] est simplement un sous-ensemble des données intégrées dans le package topicmodels. J'ai juste utilisé un sous-ensemble dans cet exemple pour qu'il s'exécute plus rapidement.

En ce qui concerne la question générale des nombres optimaux de sujets, je suis maintenant l'exemple de Martin Ponweiser sur la sélection de modèles par moyenne harmonique (4.3.3 dans sa thèse, qui est ici: http://epub.wu.ac.at /3558/1/main.pdf ). Voici comment je le fais en ce moment:

library(topicmodels)
#
# get some of the example data that's bundled with the package
#
data("AssociatedPress", package = "topicmodels")

harmonicMean <- function(logLikelihoods, precision=2000L) {
  library("Rmpfr")
  llMed <- median(logLikelihoods)
  as.double(llMed - log(mean(exp(-mpfr(logLikelihoods,
                                       prec = precision) + llMed))))
}

# The log-likelihood values are then determined by first fitting the model using for example
k = 20
burnin = 1000
iter = 1000
keep = 50

fitted <- LDA(AssociatedPress[21:30,], k = k, method = "Gibbs",control = list(burnin = burnin, iter = iter, keep = keep) )

# where keep indicates that every keep iteration the log-likelihood is evaluated and stored. This returns all log-likelihood values including burnin, i.e., these need to be omitted before calculating the harmonic mean:

logLiks <- fitted@logLiks[-c(1:(burnin/keep))]

# assuming that burnin is a multiple of keep and

 harmonicMean(logLiks)

Donc, pour ce faire sur une séquence de modèles de sujets avec différents nombres de sujets ...

# generate numerous topic models with different numbers of topics
sequ <- seq(2, 50, 1) # in this case a sequence of numbers from 1 to 50, by ones.
fitted_many <- lapply(sequ, function(k) LDA(AssociatedPress[21:30,], k = k, method = "Gibbs",control = list(burnin = burnin, iter = iter, keep = keep) ))

# extract logliks from each topic
logLiks_many <- lapply(fitted_many, function(L)  L@logLiks[-c(1:(burnin/keep))])

# compute harmonic means
hm_many <- sapply(logLiks_many, function(h) harmonicMean(h))

# inspect
plot(sequ, hm_many, type = "l")

# compute optimum number of topics
sequ[which.max(hm_many)]
## 6

enter image description here Voici la sortie, avec un nombre de sujets le long de l'axe des x, indiquant que 6 sujets sont optimaux.

La validation croisée des modèles de sujet est assez bien documentée dans les documents fournis avec le package, voir ici par exemple: http://cran.r-project.org/web/packages/topicmodels/vignettes/topicmodels. pdf Essayez, puis revenez avec une question plus spécifique sur le codage de CV avec des modèles de sujet.

22
Ben