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

问题描述

我发现 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. 比较两组令牌,发现它们不同。

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() 不适用于此),LDA 的结果是恒定的。

我想知道这是否只发生在我身上(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,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 年 2 月 18 日创建

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...