Как предотвратить разделение определенных слов или фраз и чисел в NLTK?

У меня проблема с сопоставлением текста, когда я токенизирую текст, который разбивает определенные слова, даты и числа. Как я могу предотвратить разделение некоторых фраз, таких как «бег в моей семье», «30-минутная прогулка» или «4 раза в день» во время токенизации слов в NLTK?

Они не должны приводить к:

['runs','in','my','family','4x','a','day']

Например:

Да, 20-30 минут в день на моем велосипеде, это отлично работает!

дает:

['yes','20-30','minutes','a','day','on','my','bike',',','it','works','great']

Я хочу, чтобы «20-30 минут» рассматривались как одно слово. Как я могу добиться такого поведения>?


person mm7    schedule 10.04.2019    source источник
comment
Блин первый вопрос! Я думаю, что было бы целесообразно немного почистить этот вопрос с пунктуацией и грамматикой, потому что я не думаю, что это простая задача. Я предложил решение, но боюсь, что оно может быть очень затратным с вычислительной точки зрения. Привлечение других пользователей к этому может очень помочь.   -  person bart cubrich    schedule 10.04.2019
comment
Хороший вопрос! И есть функции, написанные на nltk, они работают немного иначе, чем spacy лингвистические шаги / шаблоны регулярных выражений.   -  person alvas    schedule 12.04.2019


Ответы (2)


Насколько мне известно, вам будет сложно сохранить n-граммы различной длины одновременно с токенизацией, но вы можете найти эти n-граммы, как показано здесь. Затем вы можете заменить элементы в корпусе, которые вы хотите, в виде n-грамм с некоторым присоединяющимся символом, таким как тире.

Это пример решения, но, вероятно, есть много способов добраться туда. Важное примечание: я предоставил способ найти энграммы, которые часто встречаются в тексте (вам, вероятно, понадобится больше, чем 1, поэтому я поместил туда переменную, чтобы вы могли решить, сколько энграмм собирать Вы можете захотеть разные числа для каждого типа, но я дал только 1 переменную на данный момент.) Это может пропустить ngrams, которые вы считаете важными. Для этого вы можете добавить те, которые хотите найти, в user_grams. Они будут добавлены в поиск.

import nltk 

#an example corpus
corpus='''A big tantrum runs in my family 4x a day, every week. 
A big tantrum is lame. A big tantrum causes strife. It runs in my family 
because of our complicated history. Every week is a lot though. Every week
I dread the tantrum. Every week...Here is another ngram I like a lot'''.lower()

#tokenize the corpus
corpus_tokens = nltk.word_tokenize(corpus)

#create ngrams from n=2 to 5
bigrams = list(nltk.ngrams(corpus_tokens,2))
trigrams = list(nltk.ngrams(corpus_tokens,3))
fourgrams = list(nltk.ngrams(corpus_tokens,4))
fivegrams = list(nltk.ngrams(corpus_tokens,5))

В этом разделе можно найти распространенные ngrams до Five_grams.

#if you change this to zero you will only get the user chosen ngrams
n_most_common=1 #how many of the most common n-grams do you want.

fdist_bigrams = nltk.FreqDist(bigrams).most_common(n_most_common) #n most common bigrams
fdist_trigrams = nltk.FreqDist(trigrams).most_common(n_most_common) #n most common trigrams
fdist_fourgrams = nltk.FreqDist(fourgrams).most_common(n_most_common) #n most common four grams
fdist_fivegrams = nltk.FreqDist(fivegrams).most_common(n_most_common) #n most common five grams

#concat the ngrams together
fdist_bigrams=[x[0][0]+' '+x[0][1] for x in fdist_bigrams]
fdist_trigrams=[x[0][0]+' '+x[0][1]+' '+x[0][2] for x in fdist_trigrams]
fdist_fourgrams=[x[0][0]+' '+x[0][1]+' '+x[0][2]+' '+x[0][3] for x in fdist_fourgrams]
fdist_fivegrams=[x[0][0]+' '+x[0][1]+' '+x[0][2]+' '+x[0][3]+' '+x[0][4]  for x in fdist_fivegrams]

#next 4 lines create a single list with important ngrams
n_grams=fdist_bigrams
n_grams.extend(fdist_trigrams)
n_grams.extend(fdist_fourgrams)
n_grams.extend(fdist_fivegrams)

Этот раздел позволяет вам добавлять свои собственные ngrams в список

#Another option here would be to make your own list of the ones you want
#in this example I add some user ngrams to the ones found above
user_grams=['ngram1 I like', 'ngram 2', 'another ngram I like a lot']
user_grams=[x.lower() for x in user_grams]    

n_grams.extend(user_grams)

И эта последняя часть выполняет обработку, чтобы вы могли снова токенизировать и получать ngrams как токены.

#initialize the corpus that will have combined ngrams
corpus_ngrams=corpus

#here we go through the ngrams we found and replace them in the corpus with
#version connected with dashes. That way we can find them when we tokenize.
for gram in n_grams:
    gram_r=gram.replace(' ','-')
    corpus_ngrams=corpus_ngrams.replace(gram, gram.replace(' ','-'))

#retokenize the new corpus so we can find the ngrams
corpus_ngrams_tokens= nltk.word_tokenize(corpus_ngrams)

print(corpus_ngrams_tokens)

Out: ['a-big-tantrum', 'runs-in-my-family', '4x', 'a', 'day', ',', 'every-week', '.', 'a-big-tantrum', 'is', 'lame', '.', 'a-big-tantrum', 'causes', 'strife', '.', 'it', 'runs-in-my-family', 'because', 'of', 'our', 'complicated', 'history', '.', 'every-week', 'is', 'a', 'lot', 'though', '.', 'every-week', 'i', 'dread', 'the', 'tantrum', '.', 'every-week', '...']

Я думаю, что это действительно очень хороший вопрос.

person bart cubrich    schedule 10.04.2019
comment
Спасибо. Если я хочу сопоставить n-граммы, которые я нашел в своем наборе данных, тогда я должен составить свой собственный список, чтобы соответствовать этому, и сохранить только те n-граммы, которые есть в списке, но это займет больше времени? - person mm7; 11.04.2019
comment
Я включил эту опцию в код. Если вы не хотите находить самые распространенные, просто измените n_most_common=1 на n_most_common=0. Я хотел, чтобы мое решение было автономным и поддающимся проверке. Я отредактирую это в комментарии. Затем вы можете просто добавить нужные n-граммы в список user_gram. - person bart cubrich; 11.04.2019
comment
Кроме того, кажется, что не может быть так много энграмм, которые могут быть одновременно необычными и важными. Другими словами, если вы собираетесь токенизировать эти ngram, то это должно быть потому, что они важны для вас, но если они встречаются не часто, это само по себе делает их не такими уж важными. Вы должны получить все важные и общие с помощью начального метода, и вам просто нужно добавить несколько специфичных для вашего исследования, но это только моя догадка. - person bart cubrich; 11.04.2019
comment
Кроме того, если это ответило на ваш вопрос, рассмотрите возможность голосования и проверки его как ответа. См. Что делать, если кто-то ответит на мой вопрос? - person bart cubrich; 11.04.2019

Вы можете использовать MWETokenizer:

from nltk import word_tokenize
from nltk.tokenize import MWETokenizer

tokenizer = MWETokenizer([('20', '-', '30', 'minutes', 'a', 'day')])
tokenizer.tokenize(word_tokenize('Yes 20-30 minutes a day on my bike, it works great!!'))

[вне]:

['Yes', '20-30_minutes_a_day', 'on', 'my', 'bike', ',', 'it', 'works', 'great', '!', '!']

Более принципиальный подход, поскольку вы не знаете, как `word_tokenize разделит слова, которые вы хотите сохранить:

from nltk import word_tokenize
from nltk.tokenize import MWETokenizer

def multiword_tokenize(text, mwe):
    # Initialize the MWETokenizer
    protected_tuples = [word_tokenize(word) for word in mwe]
    protected_tuples_underscore = ['_'.join(word) for word in protected_tuples]
    tokenizer = MWETokenizer(protected_tuples)
    # Tokenize the text.
    tokenized_text = tokenizer.tokenize(word_tokenize(text))
    # Replace the underscored protected words with the original MWE
    for i, token in enumerate(tokenized_text):
        if token in protected_tuples_underscore:
            tokenized_text[i] = mwe[protected_tuples_underscore.index(token)]
    return tokenized_text

mwe = ['20-30 minutes a day', '!!']
print(multiword_tokenize('Yes 20-30 minutes a day on my bike, it works great!!', mwe))

[вне]:

['Yes', '20-30 minutes a day', 'on', 'my', 'bike', ',', 'it', 'works', 'great', '!!']
person alvas    schedule 12.04.2019