【问题标题】:R Optimizing double loop that uses stri_extractR优化使用stri_extract的双循环
【发布时间】:2016-12-08 22:24:43
【问题描述】:

我一直在进行一些文本抓取/分析。我做的一件事是从文档中提取最热门的单词来比较和了解不同的指标。这是快速和容易的。但是,定义要使用的分隔符并提取单个单词而不是短语会从分析中删除信息,这成为了一个问题。例如 .Net Developer 转换后成为 net 和 developer。我已经有一个其他人放弃的旧项目中的固定短语/单词列表。下一步是从多个文档的多行中提取特定关键字。

我一直在研究几种技术,包括矢量化、并行处理、在 R 中使用 C++ 代码等。展望未来,我将尝试所有这些技术,并尝试加快我的流程,并为我未来的项目提供这些工具。同时(没有实验)我想知道哪些调整是显而易见的,这将显着减少所花费的时间,例如将部分代码移到循环外,使用更好的包等 我也有一个进度条,但如果它显着减慢了我的循环,我可以将其删除。

这是我的代码:

words <- read.csv("keyphrases.csv")
df <- data.frame(x=(list.files("sec/new/")))
total = length(df$x)
pb <- txtProgressBar(title = "Progress Bar", min = 0, max =total , width = 300, style=3)

for (i in df$x){
          s <- read.csv(paste0("sec/new/",i))
          u <- do.call(rbind, pblapply(words$words, function(x){
              t <- data.frame(ref= s[,2], words = stri_extract(s[,3], coll=x))
              t<-na.omit(t)
          }))
          write.csv(u,paste0("sec/new_results/new/",i), row.names = F)
          setTxtProgressBar(pb, i, title=paste( round(which(df$x== i)/total*100, 2),"% done"))
      }

所以words 有 60,000 行单词/短语 - 每行不超过 30 个字符。长度 i 约为 4000,其中每个 i 有 100 到 5000 行,每行有 1 到 5000 个字符。如果我的问题需要重现,可以使用任何随机字符/字符串。

我只使用 lapply 是因为将它与 rbind 和 do.call 结合使用效果非常好,循环中的循环也可能会显着减慢进程。

所以我可以立即做一些事情,对吗?将 data.frame 交换为 data.table 或使用向量。以某种方式在循环外进行读写?也许写成这样一个循环不是嵌套的?

提前致谢

编辑

需要加速的关键元素是提取。我是使用上面的 lapply 还是将其缩减为:

for(x in words$words){t<-data.table(words=stri_extract(s[,3], coll=x))}

这仍然需要很长时间。在这种情况下,技能和 t 是数据表。

EDIT2

尝试创建可重现的数据:

set.seed(42)    
words <- data.frame(words=rnorm(1:60000))
    words$wwords <- as.String(words$words)

set.seed(42)
     file1 <- data.frame(x=rnorm(1:5000))
     file1$x<-as.String(file1$x)

     pblapply(words$words, function(x){
         t <- data.frame(words = stri_extract(file1$x, coll=x))
     })

【问题讨论】:

  • 如果你真的提供一些玩具数据来执行你的代码会很有帮助,请参阅stackoverflow.com/questions/5963269/…
  • 我无法提供我的数据,我将文件的大小放在我的问题中,能回答这个问题的人在创建随机字符串/文件方面会比我好得多。也许它就像制作数字的随机向量一样简单,我以前没有尝试过。
  • 我说的是准备玩具数据。经验表明,如果人们可以先运行您的代码,他们更有可能直接提出问题 - 而无需自己模拟数据。
  • 在第二次编辑中尝试过此操作。虽然不知道如何给人们文件夹/文件。只是一个读取一个文件的基本示例。
  • @OliPaul 例如通过rnorm等函数生成数据时请使用set.seed

标签: r performance loops optimization


【解决方案1】:

首先要做的事情。是的,我肯定会从 data.frame 切换到 data.table。它不仅更快更容易使用,当您开始合并数据集时,data.table 会做一些合理的事情,而 data.frame 会给您带来意想不到的结果。

其次,使用 R 来处理分隔符是否有优势?您提到了许多您正在考虑使用的不同技术。如果分隔符只是为了分析目的而产生的噪音,为什么不将工作分成两个工具并使用更擅长处理分隔符和延续线等的工具呢?对我来说,Python 是执行诸如将一堆文本解析为关键字之类的自然选择——包括剥离分隔符和其他您在分析中不关心的“噪音”词。将 Python 解析的结果输入 R,并利用 R 的优势。

有几种不同的方法可以将 Python 的输出导入 R。我建议从简单的东西开始:CSV 文件。它们是你开始使用的东西,它们在 Python 中易于读写,在 R 中也易于阅读。稍后你可以处理 Python 和 R 之间的直接管道,但在你有一个工作之前它不会给你带来太多优势原型,一开始还有很多工作要做。让 Python 读取您的原​​始数据并生成一个 CSV 文件,R 可以直接将其放入 data.table 中而无需进一步处理。

至于stri_extract,这次真的不是你需要的工具。你当然可以匹配一堆不同的词,但这并不是它真正优化的目标。我同意@Chris 的观点,即在 data.tables 上使用 merge() 是一种更有效且更快的搜索关键字的方法。

【讨论】:

  • 不能同意 - stringi 不会比用于文本清理/处理的基础 python 差。当您将正确标记文本 - 将其提供给 text2vec 包时 - 它会轻松完成剩下的工作。
  • 那么 Rcpp 包不是用来在 R 中运行 c++ 的。只是不确定我的代码是否可以翻译成 C++,从未使用过。
【解决方案2】:

单字版

当您在每次查找中只有单个单词时,这很容易通过合并来完成:

library(data.table)

#Word List
set.seed(42)
WordList <- data.table(ID = 1:60000, words = sapply(1:60000, function(x) paste(sample(letters, 5), collapse = '')))

#A list of dictionaries
set.seed(42)
Dicts <- list(
  Dict1 = sapply(1:15000, function(x) {
    paste(sample(letters, 5), collapse = '')
  }),
  Dict2 = sapply(1:15000, function(x) {
    paste(sample(letters, 5), collapse = '')
  }),
  Dict3 = sapply(1:15000, function(x) {
    paste(sample(letters, 5), collapse = '')
  })
)

#Create Dictionary Data.table and add Identifier
Dicts <- rbindlist(lapply(Dicts, function(x){data.table(ref = x)}), use.names = T, idcol = T)

# set key for joining
setkey(WordList, "words")
setkey(Dicts, "ref")

现在我们有一个包含所有字典单词的 data.table 和一个包含单词列表中所有单词的 data.table。现在我们可以合并了:

merge(WordList, Dicts, by.x = "words", by.y = "ref", all.x = T, allow.cartesian = T)
       words    ID   .id
    1: abcli 30174 Dict3
    2: abcrg 26210 Dict2
    3: abcsj  8487 Dict1
    4: abczg 24311 Dict2
    5: abdgl  1326 Dict1
   ---                  
60260: zyxeb 52194    NA
60261: zyxfg 57359    NA
60262: zyxjw 19337 Dict2
60263: zyxoq  5771 Dict1
60264: zyxqa 24544 Dict2

所以我们可以看到abcli 出现在Dict3 中,而zyxeb 没有出现在任何字典中。看起来有 264 个重复项(出现在 >1 个字典中的单词),因为结果 data.table 大于我们的单词列表(60264 > 60000)。如下所示:

merge(WordList, Dicts, by.x = "words", by.y = "ref", all.x = T, allow.cartesian = T)[words == "ahlpk"]
   words    ID   .id
1: ahlpk  7344 Dict1
2: ahlpk  7344 Dict2
3: ahlpk 28487 Dict1
4: ahlpk 28487 Dict2

我们在这里还看到,单词列表中的重复单词将创建多个结果行。

这运行起来非常非常快

短语+句子

如果您在句子中搜索短语,则需要执行字符串匹配。但是,您仍然需要进行n(Phrases) * n(Sentences) 比较,这将很快达到大多数 R 数据结构中的内存限制。幸运的是,这是一个embarrassingly parallel 操作:

相同的设置:

library(data.table)
library(foreach)
library(doParallel)


# Sentence List
set.seed(42)
Sentences <- data.table(ID = 1:60000, Sentence = sapply(1:60000, function(x) paste(sample(letters, 10), collapse = '')))

# A list of phrases
set.seed(42)
Phrases <- list(
  Phrases1 = sapply(1:15000, function(x) {
    paste(sample(letters, 5), collapse = '')
  }),
  Phrases2 = sapply(1:15000, function(x) {
    paste(sample(letters, 5), collapse = '')
  }),
  Phrases3 = sapply(1:15000, function(x) {
    paste(sample(letters, 5), collapse = '')
  })
)

# Create Dictionary Data.table and add Identifier
Phrases <- rbindlist(lapply(Phrases, function(x){data.table(Phrase = x)}), use.names = T, idcol = T)

# Full Outer Join
Sentences[, JA := 1]
Phrases[, JA := 1]

# set key for joining
setkey(Sentences, "JA")
setkey(Phrases, "JA")

我们现在想将 Phrases 表分解为可管理的批次

cl<-makeCluster(4)
registerDoParallel(cl)

nPhrases <- as.numeric(nrow(Phrases))
nSentences <- as.numeric(nrow(Sentences))

batch_size <- ceiling(nPhrases*nSentences / 2^30) #Max data.table allocation is 2^31. Lower this if you are hitting memory allocation limits
seq_s <- seq(1,nrow(Phrases), by = floor(nrow(Phrases)/batch_size))
ln_s <- length(seq_s)
if(ln_s > 1){
  str_seq <- paste0(seq_s,":",c(seq_s[2:ln_s],nrow(Phrases) + 1) - 1)
} else {
  str_seq <- paste0(seq_s,":",nrow(Phrases))
}
  

我们现在可以发送我们的工作了。下面的grepl 行正在做这项工作——测试哪些短语与每个句子匹配。然后我们过滤掉所有不匹配的内容。

ls<-foreach(i = 1:ln_s) %dopar% {
  
  library(data.table)
  TEMP_DT <- merge(Sentences,Phrases[eval(parse(text = str_seq[1]))], by = "JA", allow.cartesian = T)
  TEMP_DT <- TEMP_DT[, match_test := grepl(Phrase,Sentence), by = .(Phrase,Sentence)][match_test == 1]
  return(TEMP_DT)
  
}

stopCluster(cl)


DT_OUT <- unique(do.call(rbind,ls))

DT_OUT 现在总结了匹配的句子,以及它所在的短语 + 短语列表。

这仍然需要一些时间(因为需要进行大量处理),但还不到一年。

【讨论】:

  • 主要问题是@oil-paul 不知道如何将文本拆分为单词/短语的确切方法。
  • 其中一个是短语,另一个可以是句子甚至段落。此方法仅在字符串匹配时有效,如果一个字符串包含在另一个字符串中则无效
  • @OliPaul 几个问题:两个列表中是否只需要匹配一个词,还是列表 1 中的短语需要完全包含在列表 2 中的句子中?在我的示例中,列表 1(带有短语的列表)WordListDict 1-3?
  • 例如,我希望将短语“努力工作”从带有句子“你努力工作吗?”的一行中提取(或计算)
  • 我会测试这个!快速提及我可能错过的一些事情。当我说句子时,我指的是其他人写入的空闲空间。可能有新行、制表符、空格和所有类型的特殊字符。我在哪里清理数据并提取发现的最重要的单词,我可以拆分数据,使其始终每行一行,或者您的技术是否适用于任何可用文本空间?
猜你喜欢
  • 1970-01-01
  • 2016-07-14
  • 2018-12-16
  • 1970-01-01
  • 2021-07-28
  • 1970-01-01
  • 2015-07-24
  • 2016-12-07
相关资源
最近更新 更多