【发布时间】:2014-01-27 00:18:42
【问题描述】:
上下文
我一直在尝试实现最近在this paper 中提出的算法。给定大量文本(语料库),该算法应该返回语料库的特征 n-grams(即 n 个单词的序列)。用户可以决定适当的 n,目前我正在尝试使用 n = 2-6,就像在原始论文中一样。换句话说,使用该算法,我想提取表征语料库的 2 到 6 克。
我能够实现根据识别出的特征 n-gram 来计算分数的部分,但一直在努力消除非特征性的部分。
数据
我有一个名为token.df 的列表,其中包含五个数据帧,其中包括出现在语料库中的所有 n-gram。每个数据帧对应于 n-grams 中的每个 n。例如,token.df[[2]] 包括按字母顺序排列的所有二元组(2 元组)及其分数(以下称为 mi)。
> head(token.df[[2]])
w1 w2 mi
_ eos 17.219346
_ global 7.141789
_ what 8.590394
0 0 2.076421
0 00 5.732846
0 000 3.426785
这里,二元组 0 0(尽管它们本身并不完全是单词)的得分为 2.076421。由于数据帧包含所有出现在语料库中的 n-gram,它们每个都有超过一百万行。
> sapply(token.df, nrow)
[[1]]
NULL
[[2]]
[1] 1006059 # number of unique bigrams in the corpus
[[3]]
[1] 2684027 # number of unique trigrams in the corpus
[[4]]
[1] 3635026 # number of unique 4-grams in the corpus
[[5]]
[1] 3965120 # number of unique 5-grams in the corpus
[[6]]
[1] 4055048 # number of unique 6-grams in the corpus
任务
我想确定哪些 n-gram 要保留,哪些要丢弃。为此,该算法执行以下操作。
- 二元组
- 它保留了得分高于前两个单词匹配bigrams的trigrams的bigrams。
- 3-5克
- 对于每个 n-gram 其中 n = {3, 4, 5},它查看
- 与 n-gram 的前 n-1 个单词匹配的 n-1 个词和
- 前 n 个单词与 n-gram 匹配的 n+1 个gram。
- 仅当 n-gram 的分数高于 n-1 个和 n+1 个的分数时,该算法才会保留> 克以上。
- 对于每个 n-gram 其中 n = {3, 4, 5},它查看
- 6克
- 保留与 6-gram 的前五个单词匹配的分数高于 5-gram 的 6-gram。
示例
> token.df[[2]][15, ]
w1 w2 mi
0 001 10.56292
> token.df[[3]][33:38, ]
w1 w2 w3 mi
0 001 also 3.223091
0 001 although 5.288097
0 001 and 2.295903
0 001 but 4.331710
0 001 compared 6.270625
0 001 dog 11.002312
> token.df[[4]][46:48, ]
w1 w2 w3 w4 mi
0 001 compared to 5.527626
0 001 dog walkers 10.916028
0 001 environmental concern 10.371769
这里,不保留二元组0 001,因为前两个词与二元组匹配的三元组之一(0 001 dog)的得分高于二元组(11.002312 > 10.56292)。 trigram 0 001 dog 被保留,因为它的分数 (11.002312) 高于匹配 trigram 前两个单词的 bigram (0 001; score = 10.56292 ) 和前三个词与三元组匹配的 4-gram 的结果 (0 001 dog walkers; score = 10.916028)。
问题和失败尝试
我想知道的是实现上述目标的有效方法。例如,为了确定要保留哪些二元组,我需要为token.df[[2]] 的每一行找出token.df[[3]] 中的哪些行的前两个单词与所关注的二元组相同。但是,由于行数很大,我下面的迭代方法需要很长时间才能运行。他们专注于二元组的情况,因为这项任务看起来比 3-5 克的情况更简单。
-
for循环方法。
由于下面的代码在每次迭代中都会遍历token.df[[3]]的所有行,因此估计需要几个月的时间才能运行。虽然稍微好一点,但by()的情况类似。# for loop retain <- numeric(nrow(token.df[[2]])) for (i in 1:nrow(token.df[[2]])) { mis <- token.df[[3]]$mi[token.df[[2]][i, ]$w1 == token.df[[3]][ , 1] & token.df[[2]][i, ]$w2 == token.df[[3]][ , 2]] retain[i] <- ifelse(token.df[[2]]$mi[i] > max(mis), TRUE, FALSE) } # by mis <- by(token.df[[2]], 1:nrow(token.df[[2]]), function(x) token.df[[3]]$mi[x$w1 == token.df[[3]]$w1 & x$w2 == token.df[[3]]$w2]) retain <- sapply(seq(mis), function(i) token.df[[2]]$mi[i] > max(mis[[i]])) -
指针方法。
上述问题是(垂直)长数据帧上的大量迭代。为了缓解这个问题,我想我可以利用 n-gram 在每个数据帧中按字母顺序排序的事实,并使用一种指针来指示从哪一行开始查找。但是,这种方法也需要很长时间才能运行(至少几天)。retain <- numeric(nrow(token.df[[2]])) nrow <- nrow(token.df[[3]]) # number of rows of the trigram data frame pos <- 1 # pointer for (i in seq(nrow(token.df[[2]]))) { j <- 1 target.rows <- numeric(10) while (TRUE) { if (pos == nrow + 1 || !all(token.df[[2]][i, 1:2] == token.df[[3]][pos, 1:2])) break target.rows[j] <- pos pos <- pos + 1 if (j %% 10 == 0) target.rows <- c(target.rows, numeric(10)) j <- j + 1 } target.rows <- target.rows[target.rows != 0] retain[i] <- ifelse(token.df[[2]]$mi[i] > max(token.df[[3]]$mi[target.rows]), TRUE, FALSE) }
有没有办法在合理的时间内(例如,一夜之间)完成这项任务?既然迭代方法是徒劳的,我想知道是否可以进行任何矢量化。但我愿意采取任何方式加快这一进程。
数据具有树形结构,即一个二元组被划分为一个或多个三元组,每个三元组又被划分为一个或多个四元组,依此类推。我不确定如何最好地处理此类数据。
可重现的示例
我曾想过将我正在使用的部分真实数据放上去,但减少数据会破坏问题的全部意义。我假设人们不想为此下载整个 250MB 的数据集,我也无权上传它。下面是随机数据集,它仍然比我使用的要小,但有助于解决问题。使用上面的代码(指针方法),我的计算机需要 4-5 秒来处理下面的 token.df[[2]] 的前 100 行,而处理所有的二元组大概需要 12 小时。
token.df <- list()
types <- combn(LETTERS, 4, paste, collapse = "")
set.seed(1)
data <- data.frame(matrix(sample(types, 6 * 1E6, replace = TRUE), ncol = 6), stringsAsFactors = FALSE)
colnames(data) <- paste0("w", 1:6)
data <- data[order(data$w1, data$w2, data$w3, data$w4, data$w5, data$w6), ]
set.seed(1)
for (n in 2:6) token.df[[n]] <- cbind(data[ , 1:n], mi = runif(1E6))
非常感谢任何加快代码速度的想法。
【问题讨论】: