quanteda 中的 tokens_compound() 改变了特征的顺序

tokens_compound() in quanteda changes the order of features

我发现 quanteda 中的 tokens_compound() 更改了不同 R 会话中标记的顺序。也就是说,即使种子值是固定的,每次重新启动会话后结果都会有所不同,尽管它在单个会话中不会改变。

复制过程如下:

  1. 查找搭配、复合标记并保存。
library(quanteda)

set.seed(12345)

data(data_corpus_inaugural)

toks <- data_corpus_inaugural %>% 
  tokens(remove_punct = TRUE,
         remove_symbol = TRUE, 
         padding = TRUE) %>% 
  tokens_tolower()

col <- toks %>% 
  textstat_collocations()

toks.col <- toks %>%
  tokens_compound(pattern = col[col$z > 3])

write(attr(toks.col, "types"), "col1.txt")
  1. 结束并重新启动 R 会话并再次 运行 上述代码,将“col1.txt”替换为“col2.txt”。

  2. 比较两组token,发现不一样

col1 <- read.table("col1.txt")
col2 <- read.table("col2.txt")

identical(col1$V1, col2$V1) # This should return FALSE.

col1$V1[head(which(col1$V1 != col2$V1))]
col2$V1[head(which(col1$V1 != col2$V1))]

这在很多情况下并不重要,但 LDA(通过 {topicmodels})的结果在不同的会话中会发生变化。我猜是这样,因为如果我将 tokens 中的特征顺序重置为 as.list() 然后 as.tokens()dfm_sort() 不适用于此)。

我想知道这是否只发生在我身上(Ubuntu 18.04.5、R 4.0.4 和 quanteda 2.1.2),很高兴听到另一个(更简单的)解决方案。

2 月 20 日更新

比如LDA的输出没有复现

lis <- list()
for (i in seq_len(2)) {
  set.seed(123)
  lis[[i]] <- tokens_compound(toks, pattern = col[col$z > 3]) %>% 
    dfm() %>% 
    convert(to = "topicmodels") %>% 
    LDA(k = 5,
        method = "Gibbs",
        control = list(seed = 12345,
                       iter = 100))
}

head(lis[[1]]@gamma)
head(lis[[2]]@gamma)

一项有趣的调查,但这既不是错误,也不是什么值得关注的事情。在 quanteda 令牌对象中,在 textstat_compound() 等处理步骤之后,类型的顺序是不确定的。这是因为此函数在 C++ 中是并行化的,并且这些线程的操作方式并未由 R 中的 set.seed() 确定。但这不会影响重要部分,即类型集或有关标记本身的任何内容。如果你希望你提取的类型的顺序相同,那么你应该在提取时对它们进行排序。

library("quanteda")
## Package version: 2.1.2

toks <- data_corpus_inaugural %>%
  tokens(
    remove_punct = TRUE,
    remove_symbol = TRUE,
    padding = TRUE
  ) %>%
  tokens_tolower()
col <- quanteda.textstats::textstat_collocations(toks)

事实证明,您不需要保存输出或重新启动 R - 这会在单个会话中发生。

# types are differently indexed, but are the same set
lis <- list()
for (i in seq_len(2)) {
  set.seed(123)
  toks.col <- tokens_compound(toks, pattern = col[col$z > 3])
  lis <- c(lis, list(types = types(toks.col)))
}
dframe <- data.frame(lis)

sum(dframe$types != dframe$types.1)
## [1] 19898
head(dframe[dframe$types != dframe$types.1, ])
##                                            types              types.1
## 8897                              at_this_second   my_fellow_citizens
## 8898 to_take_the_oath_of_the_presidential_office            no_people
## 8899                                    there_is             on_earth
## 8900                                occasion_for cause_to_be_thankful
## 8901                                 an_extended         this_is_said
## 8902                                   there_was            spirit_of

但是(无序的)类型集是相同的:

# but
setequal(dframe$types, dframe$types.1)
## [1] TRUE

更重要的是,当我们比较有序的每个标记的值时,它们是相同的:

# tokens are the same
lis <- list()
for (i in seq_len(2)) {
  set.seed(123)
  toks.col <- tokens_compound(toks, pattern = col[col$z > 3])
  lis <- c(lis, list(toks = as.character(toks.col)))
}
dframe <- data.frame(lis)
all.equal(dframe$toks, dframe$toks.1)
## [1] TRUE

reprex package (v1.0.0)

于 2021-02-18 创建

附加评论,此分析强调了其重要性:我们强烈反对直接访问对象属性。如上所述使用 types(x),而不是 attr(x, "types")。前者将永远有效。后者依赖于我们对对象的实现,它可能会随着我们改进包而改变。