Непоследовательное поведение с функциями преобразования tm_map при использовании нескольких ядер.

Другим потенциальным заголовком для этого поста может быть «При параллельной обработке в R отношение между количеством ядер, размером фрагмента петли и размером объекта?

У меня есть корпус, в котором я выполняю некоторые преобразования с использованием пакета tm. Поскольку корпус большой, я использую параллельную обработку с допараллельным пакетом.

Иногда преобразования выполняют задачу, а иногда нет. Например, tm::removeNumbers(). Самый первый документ в корпусе имеет значение содержимого n417. Итак, если предварительная обработка прошла успешно, этот документ будет преобразован только в n.

Образец корпуса показан ниже для воспроизведения. Вот блок кода:

library(tidyverse)
library(qdap)
library(stringr)
library(tm)
library(textstem)
library(stringi)
library(foreach)
library(doParallel)
library(SnowballC)

  corpus <- (see below)
  n <- 100 # This is the size of each chunk in the loop

  # Split the corpus into pieces for looping to get around memory issues with transformation
  nr <- length(corpus)
  pieces <- split(corpus, rep(1:ceiling(nr/n), each=n, length.out=nr))
  lenp <- length(pieces)

  rm(corpus) # Save memory

  # Save pieces to rds files since not enough RAM
  tmpfile <- tempfile()
  for (i in seq_len(lenp)) {
    saveRDS(pieces[[i]],
            paste0(tmpfile, i, ".rds"))
  }

  rm(pieces) # Save memory

  # Doparallel
  registerDoParallel(cores = 12)
  pieces <- foreach(i = seq_len(lenp)) %dopar% {
    piece <- readRDS(paste0(tmpfile, i, ".rds"))
    # Regular transformations
    piece <- tm_map(piece, content_transformer(removePunctuation), preserve_intra_word_dashes = T)
    piece <- tm_map(piece, content_transformer(function(x, ...)
      qdap::rm_stopwords(x, stopwords = tm::stopwords("english"), separate = F)))
    piece <- tm_map(piece, removeNumbers)
    saveRDS(piece, paste0(tmpfile, i, ".rds"))
    return(1) # Hack to get dopar to forget the piece to save memory since now saved to rds
  }

  stopImplicitCluster()

  # Combine the pieces back into one corpus
  corpus <- list()
  corpus <- foreach(i = seq_len(lenp)) %do% {
    corpus[[i]] <- readRDS(paste0(tmpfile, i, ".rds"))
  }
  corpus_done <- do.call(function(...) c(..., recursive = TRUE), corpus)

А здесь находится ссылка на образцы данных. Мне нужно вставить достаточно большой образец документов размером 2 КБ для воссоздания, и это не позволит мне вставить столько, поэтому, пожалуйста, смотрите связанный документ для данных.

corpus <- VCorpus(VectorSource([paste the chr vector from link above]))

Если я запущу свой блок кода, как указано выше, с n до 200, посмотрите на результаты.

Я вижу, что цифры остались там, где их должен был убрать tm::removeNumbers():

> lapply(1:10, function(i) print(corpus_done[[i]]$content)) %>% unlist
[1] "n417"
[1] "disturbance"
[1] "grand theft auto"

Однако, если я изменю размер блока (значение переменной n) на 100:

> lapply(1:10, function(i) print(corpus_done[[i]]$content)) %>% unlist
[1] "n"
[1] "disturbance"
[1] "grand theft auto"

Номера удалены.

Но это непоследовательно. Я попытался сузить его, протестировав 150, затем 125 ... и обнаружил, что он будет / не будет работать между 120 и 125 размерами фрагментов. Затем, после повторения функции между 120:125, она иногда работала, а затем не для того же размера фрагмента.

Я думаю, что, возможно, есть связь с этой проблемой между тремя переменными: размером корпуса, размером чанка и количеством ядер в registerdoparallel(). Я просто не знаю, что это такое.

Каково решение? Можно ли воспроизвести эту проблему с помощью связанного корпуса образцов? Я обеспокоен тем, что иногда я могу воспроизвести ошибку, а иногда нет. Изменение размера чанка дает как бы возможность увидеть ошибку с удалением номеров, но не всегда.


Обновлять

Сегодня я возобновил сеанс и не смог воспроизвести ошибку. Я создал документ Google Docs и экспериментировал с различными значениями для корпуса размер, количество ядер и размеры блоков. В каждом случае все прошло успешно. Итак, я попробовал запустить на полных данных, и все сработало. Однако, для моего здравомыслия, я попытался снова запустить на полных данных, и это не удалось. Теперь я снова там, где был вчера.

Похоже, что запуск функции на большом наборе данных что-то изменил... не знаю что! Возможно, какая-то переменная сеанса?

Итак, новая информация заключается в том, что эта ошибка возникает только после запуска функции на очень большом наборе данных. Перезапуск моего сеанса не решил проблему, но возобновил сеансы после нескольких часов отсутствия.


Новая информация:

Возможно, было бы проще воспроизвести проблему в большом корпусе, так как это, по-видимому, вызывает проблему corpus <- do.call(c, replicate(250, corpus, simplify = F)) создаст корпус документов объемом 500 000 на основе предоставленного мной образца. Функция может работать в первый раз, когда вы ее вызываете, но мне кажется, что во второй раз она не работает.

Эта проблема сложна, потому что, если бы я мог воспроизвести проблему, я, вероятно, смог бы идентифицировать и исправить ее.


Новая информация:

Поскольку с этой функцией происходит несколько вещей, было трудно понять, на чем сосредоточить усилия по отладке. Я рассматривал как тот факт, что я использую несколько временных файлов RDS для экономии памяти, так и тот факт, что я выполняю параллельную обработку. Я написал две альтернативные версии скрипта: одна по-прежнему использует файлы rds и разбивает корпус, но не выполняет параллельную обработку (%dopar% заменена просто на %do%, а также удалена строка registerDoParallel), а другая использует параллельную обработку. но не использует временные файлы RDS для разделения небольшого корпуса образцов.

Мне не удалось вызвать ошибку с одноядерной версией скрипта, только с версией, использующей %dopar%, я смог воссоздать проблему (хотя проблема носит периодический характер, она не всегда дает сбой с dopar).

Итак, эта проблема возникает только при использовании %dopar%. Тот факт, что я использую временные файлы RDS, не является частью проблемы.


person Doug Fir    schedule 25.08.2017    source источник
comment
Я не понимаю, что вы называете корпусом. Вы даете нам только вектор символов.   -  person F. Privé    schedule 25.08.2017
comment
См. этот блок в моем посте: docs <- (copy from link above) corpus <- VCorpus(VectorSource(docs)) он берет вектор и превращает его в корпус. Так что просто оберните все в связанном документе внутри VCorpus(VectorSource([character vector goes here]))   -  person Doug Fir    schedule 25.08.2017
comment
@DougFir, несмотря на все мое уважение к tm, я бы просто рекомендовал 1) создать собственную функцию предварительной обработки (выучить некоторые регулярные выражения) 2) перейти к другому пакету - text2vec или quanteda - будет намного быстрее и проще   -  person Dmitriy Selivanov    schedule 29.08.2017
comment
@DmitriySelivanov только что просмотрел документацию по Quanteda, на самом деле выглядит очень интересно, и я мог бы попробовать. Это похоже на допараллельную проблему при использовании с tm. Если бы я использовал qunateda, если бы он работал быстрее, это могло бы означать, что мне не нужно использовать параллельную обработку.   -  person Doug Fir    schedule 29.08.2017
comment
@DougFir тоже дает шанс text2vec (я автор :-)). Ознакомьтесь с руководствами на text2vec.org.   -  person Dmitriy Selivanov    schedule 29.08.2017
comment
@DmitriySelivanov Хорошо, спасибо за подсказку! я тоже там посмотрю   -  person Doug Fir    schedule 29.08.2017
comment
Я бы попробовал помочь, но я не совсем уверен, какой конечный результат вы хотите от входного вектора символов в вашей ссылке выше. Это просто удалить числа из символьных данных, но таким образом, чтобы они были распараллелены?   -  person Ken Benoit    schedule 31.08.2017
comment
@KenBenoit ввод в приведенный выше блок кода должен быть корпусом tm. Ссылка в моем Gdoc — это просто вектор символов corpus <- VCorpus(VectorSource([paste the chr vector from link above])) . Удалите числа и другие преобразования. В двух словах проблема в том, что все работает нормально при использовании одного ядра. Преобразования с использованием tm_map в корпусе работают. Однако при использовании нескольких ядер преобразования tm_map ИНОГДА работают. Его трудно воссоздать, так как он выглядит случайным. Я заметил, что проблема появляется после запуска блока кода на очень...   -  person Doug Fir    schedule 31.08.2017
comment
(продолжение) ... на очень большом корпусе. Итак, если вы присоединитесь к корпусу примеров, предоставленному самому себе, чтобы сделать его, например. 500 КБ или даже 1 миллион, вы можете обнаружить, что это работает с первого раза. Однако, если вы попытаетесь запустить его во второй раз, блок кода может не сработать, и преобразования отобразятся так, как будто их не было. Эта проблема особенно сложна, поскольку ее воспроизведение непоследовательно. Только иногда не работает. Однако это проблема только при использовании нескольких ядер, в остальном все работает нормально (только медленно).   -  person Doug Fir    schedule 31.08.2017
comment
Я спросил, потому что считаю, что есть гораздо более простые, быстрые и масштабируемые методы достижения желаемого, чем тот подход, который вы используете. Пожалуйста, сформулируйте просто, что вы хотите сделать: Удалить цифры из текста, понятно. Что-нибудь еще?   -  person Ken Benoit    schedule 31.08.2017
comment
Привет @Кен. Хорошо, я хотел бы удалить цифры, знаки препинания и стоп-слова из моего корпуса. (На самом деле, с тех пор, как я опубликовал это, я начал использовать Quanteda, который, похоже, использует параллельную обработку (я наблюдал за терминалом во время работы), и все работало прекрасно без проблем. Так что на самом деле моя непосредственная проблема решена благодаря Quanteda. Тем не менее, я бы мне понравилось понимать, что происходит выше, но оцените, что это, вероятно, очень сложно отладить, поскольку проблема появлялась несколько спорадически и непоследовательно. Из любопытства, какое решение вы бы предложили?   -  person Doug Fir    schedule 31.08.2017
comment
Согласитесь с приведенными выше комментариями, что tm_map очень сложно отлаживать, имеет много плохо задокументированных особенностей и что использование подхода apply с вашей собственной пользовательской функцией по сравнению с другим пакетом, вероятно, намного лучше как в краткосрочной, так и в долгосрочной перспективе.   -  person Gary Weissman    schedule 24.04.2018
comment
Я голосую за то, чтобы закрыть этот вопрос, потому что этому вопросу уже более 3 лет, и на него нет ответов, а автору были даны четкие альтернативы в комментариях, и он явно больше не заинтересован в ответе.   -  person jamesc    schedule 22.07.2020
comment
Я наблюдал противоречивые результаты от R с использованием Apple Accelerate BLAS, а также с использованием parallel одновременно с некоторыми более старыми версиями OpenBLAS. Обновление OpenBLAS устранило эту проблему для меня. Вполне вероятно, что tm использует некоторые функции BLAS, так что это также возможная причина.   -  person webb    schedule 12.06.2021
comment
Я голосую за то, чтобы закрыть этот вопрос, потому что этому вопросу уже более 3 лет, и на него нет ответов, а автору были даны четкие альтернативы в комментариях, и он явно больше не заинтересован в ответе.   -  person webb    schedule 12.06.2021