У меня есть текстовые данные в столбце панд. Обычно каждый документ является частью значения столбца. Каждый документ состоит из нескольких предложений.
Я хотел разбить каждый документ на предложения, а затем для каждого предложения получить список слов. Итак, если документ состоит из 5 предложений, у меня будет список слов длиной 5.
Я использовал функцию сопоставления, чтобы проделать с этим некоторые операции и получил список слов для каждого предложения текста. Вот код маппера:
def text_to_words(x):
""" This function converts sentences in a text to a list of words
"""
nlp=spacy.load('en')
txt_to_words= [str(doc).replace(".","").split(" ") for doc in nlp(x).sents]
return txt_to_words
Тогда я сделал это:
%%time
txt_to_words=map(text_to_words,pandas_df.log_text_cleaned)
Это было сделано за 70 микросекунд, и я получил итератор картографа.
Теперь, если я хочу добавить каждый список списка слов каждого документа как новое значение нового столбца в том же фрейме данных pandas.
Я могу просто сделать это:
txt_to_words=[*map(text_to_words,pandas_df.log_text_cleaned)]
Что расширит итератор карты и сохранит его в txt_to_words как список списка слов.
Но этот процесс идет очень медленно.
Я даже пробовал перебирать объект карты:
txt_to_words=map(text_to_words,pandas_df.log_text_cleaned)
txt_to_words_list=[]
for sent in txt_to_words:
txt_to_words_list.append(sent)
Но это аналогично медленно.
извлечение вывода из объекта сопоставления происходит очень медленно. И у меня только 67 КБ документов в столбце фрейма данных pandas.
Есть ли способ ускорить это?
Спасибо
Маловероятно, что составление списка на самом деле является медленным этапом. Когда вы создаете map, это происходит быстро, потому что он на самом деле мало что делает, кроме как настраивает ленивый итератор, чтобы вы могли использовать его позже. Когда вы конвертируете это в список, он должен оценить каждый элемент итератора, выполняя всю ленивую работу, которую вы отложили на потом, и на это уходит ваше время.
При этом: вызов list(…) вместо [*…] или оператора for будет значительно быстрее ... просто он ускорит ту часть, которая не имеет значения, сокращая микросекунды вашего общего процесса.
даже использование list () работает очень медленно. В этом нет большой разницы и в использовании цикла * или for. Есть ли другие предложения, которые могут ускорить это? Есть ли способ отправить каждое предложение по одной строке в текстовый файл, чтобы все предложения всех документов просто записывались в текстовый файл на диске одно под другим? Я пробовал сделать это, но он стирает все предыдущие предложения и сохраняет только последние предложения документов. Файл еще не создан, поэтому он должен быть в режиме 'w', чтобы он мог записать туда первый документ.
@Baktaawar Да. Как я только что вам объяснил, это потому, что вы оптимизируете не то. Составление списка занимает микросекунды, реальная работа занимает секунды. Так что ускорение построения списка не окажет заметного влияния.
Если вы мне не верите, попробуйте собрать collections.deque(map…, maxlen=0). Это самый быстрый способ использования итератора - ему не нужно выделять память или хранить что-либо. И вы увидите, что это по-прежнему не имеет видимого значения.
правильно, я понял. Так что в основном это не поможет в памяти. Можно как-то запихнуть на диск по txt файлу один под другим?
Доступ к диску не ускорит работу.
Я не совсем уверен, что находится в pandas_df.log_text_cleaned или что возвращает nlp(x).sents, но это те вещи, на которые вам нужно обратить внимание. См. Мой ответ, чтобы начать работу.
@abarnert: На самом деле, [*...] имеет точно такую же стоимость за единицу, что и list(...), и немного меньшую фиксированную стоимость. Оба они выполняют всю реальную работу на уровне C на CPython, а версия на основе синтаксиса имеет специальную поддержку байт-кода, которая позволяет избежать общих накладных расходов на диспетчеризацию вызовов list(...) (экономит колоссальные 100-150 нс для выполнения [*...]). Тем не менее, все остальное, что вы сказали, верно. Если для производства каждого элемента требуется 10 РС, не имеет значения, если вы сэкономите 10-50 нс на стоимости каждого элемента, чтобы добавить его в list. (*...,) работает медленнее, чем tuple(...) (создает list, а затем копирует на tuple).
@ShadowRanger У него более высокая фиксированная стоимость в быстром тесте %timeit с 3.6 на Linux и 3.7alpha на Mac - 137us против 129us (против 106us для deque(maxlen=0), хотя эта разница становится шире для гораздо больших итераторов, а другой - нет) т). Возможно, все было бы по-другому с большим количеством крошечных списков (где действительно имеет значение стоимость поиска глобального и вызова функции) вместо меньшего количества приличных списков.






Прямой ответ на ваш вопрос заключается в том, что самый быстрый способ преобразовать итератор в список, вероятно, - вызвать на нем list, хотя это может зависеть от размера ваших списков.
Однако это не имеет значения, за исключением незаметной, едва поддающейся измерению степени.
Разница между list(m), [*m] или даже явным выражением for составляет максимум микросекунды, но ваш код занимает секунды. Фактически, вы даже можете исключить почти всю работу, выполняемую list, используя collections.deque(m, maxlen=0) (который просто отбрасывает все значения, ничего не выделяя и не сохраняя), и вы по-прежнему не увидите разницы.
Ваша настоящая проблема в том, что работа над каждым элементом выполняется медленно.
Вызов map на самом деле не работает. Все, что он делает, это создает ленивый итератор, который настраивает работу, которую нужно выполнить позже. Когда позже? Когда вы конвертируете итератор в список (или используете его другим способом).
Итак, именно эту функцию text_to_words вам нужно ускорить.
И есть по крайней мере один очевидный кандидат на то, как это сделать:
def text_to_words(x):
""" This function converts sentences in a text to a list of words
"""
nlp=spacy.load('en')
txt_to_words= [str(doc).replace(".","").split(" ") for doc in nlp(x).sents]
return txt_to_words
Вы загружаете весь английский токенизатор / словарь / и т. д. для каждого предложения? Конечно, вы получите некоторую выгоду от кеширования после первого раза, но держу пари, что это все еще слишком медленно для каждого предложения.
Если вы пытались ускорить процесс, сделав его локальной переменной, а не глобальной (что, вероятно, не имеет значения, но может), это не способ сделать это; это:
nlp=spacy.load('en')
def text_to_words(x, *. _nlp=nlp):
""" This function converts sentences in a text to a list of words
"""
txt_to_words= [str(doc).replace(".","").split(" ") for doc in _nlp(x).sents]
return txt_to_words
Ну, у меня нет этой нагрузки nlp в моей реальной функции. Его снаружи. Я добавил его сюда, чтобы понять, что такое переменная nlp. Так что вышесказанное не помогает, так как это у меня уже есть снаружи.
«Это было сделано за 70 микросекунд, и я получил итератор картографа» - работа не выполнена. В этом суть
map, возвращающего итератор: он не выполняет свою работу, пока вы не зациклите его. Вы не ускорили работу. Вы только что сказали Python откладывать дела на потом.