Szukam sposobu na podzielenie tekstu na n-gramów. Normalnie zrobiłbym coś takiego:
import nltk
from nltk import bigrams
string = "I really like python, it's pretty awesome."
string_bigrams = bigrams(string)
print string_bigrams
Zdaję sobie sprawę, że nltk oferuje tylko bigramy i trygramy, ale czy istnieje sposób na podzielenie mojego tekstu na cztery gramy, pięć lub nawet sto gramów?
Dzięki!
ingrams
której drugim parametrem jest żądany stopień ngramów. Czy TO jest wersja nltk, której używasz? Nawet jeśli nie, tutaj jest źródło EDIT: Nie mangrams
iingrams
tam,ingrams
jako generator.Odpowiedzi:
Świetne odpowiedzi oparte na natywnym Pythonie udzielane przez innych użytkowników. Ale oto
nltk
podejście (na wszelki wypadek OP zostanie ukarany za ponowne wymyślenie tego, co już istnieje wnltk
bibliotece).Istnieje moduł ngram, z którego ludzie rzadko korzystają
nltk
. Nie dlatego, że trudno jest odczytać ngramy, ale uczenie modelu bazującego na ngramach, gdzie n> 3 spowoduje dużą rzadkość danych.from nltk import ngrams sentence = 'this is a foo bar sentences and i want to ngramize it' n = 6 sixgrams = ngrams(sentence.split(), n) for grams in sixgrams: print grams
źródło
sixgrams
?Dziwię się, że to się jeszcze nie pokazało:
In [34]: sentence = "I really like python, it's pretty awesome.".split() In [35]: N = 4 In [36]: grams = [sentence[i:i+N] for i in xrange(len(sentence)-N+1)] In [37]: for gram in grams: print gram ['I', 'really', 'like', 'python,'] ['really', 'like', 'python,', "it's"] ['like', 'python,', "it's", 'pretty'] ['python,', "it's", 'pretty', 'awesome.']
źródło
Korzystanie tylko z narzędzi nltk
from nltk.tokenize import word_tokenize from nltk.util import ngrams def get_ngrams(text, n ): n_grams = ngrams(word_tokenize(text), n) return [ ' '.join(grams) for grams in n_grams]
Przykładowe dane wyjściowe
get_ngrams('This is the simplest text i could think of', 3 ) ['This is the', 'is the simplest', 'the simplest text', 'simplest text i', 'text i could', 'i could think', 'could think of']
Aby zachować ngramy w formacie tablicy, po prostu usuń
' '.join
źródło
tutaj jest inny prosty sposób na zrobienie n-gramów
>>> from nltk.util import ngrams >>> text = "I am aware that nltk only offers bigrams and trigrams, but is there a way to split my text in four-grams, five-grams or even hundred-grams" >>> tokenize = nltk.word_tokenize(text) >>> tokenize ['I', 'am', 'aware', 'that', 'nltk', 'only', 'offers', 'bigrams', 'and', 'trigrams', ',', 'but', 'is', 'there', 'a', 'way', 'to', 'split', 'my', 'text', 'in', 'four-grams', ',', 'five-grams', 'or', 'even', 'hundred-grams'] >>> bigrams = ngrams(tokenize,2) >>> bigrams [('I', 'am'), ('am', 'aware'), ('aware', 'that'), ('that', 'nltk'), ('nltk', 'only'), ('only', 'offers'), ('offers', 'bigrams'), ('bigrams', 'and'), ('and', 'trigrams'), ('trigrams', ','), (',', 'but'), ('but', 'is'), ('is', 'there'), ('there', 'a'), ('a', 'way'), ('way', 'to'), ('to', 'split'), ('split', 'my'), ('my', 'text'), ('text', 'in'), ('in', 'four-grams'), ('four-grams', ','), (',', 'five-grams'), ('five-grams', 'or'), ('or', 'even'), ('even', 'hundred-grams')] >>> trigrams=ngrams(tokenize,3) >>> trigrams [('I', 'am', 'aware'), ('am', 'aware', 'that'), ('aware', 'that', 'nltk'), ('that', 'nltk', 'only'), ('nltk', 'only', 'offers'), ('only', 'offers', 'bigrams'), ('offers', 'bigrams', 'and'), ('bigrams', 'and', 'trigrams'), ('and', 'trigrams', ','), ('trigrams', ',', 'but'), (',', 'but', 'is'), ('but', 'is', 'there'), ('is', 'there', 'a'), ('there', 'a', 'way'), ('a', 'way', 'to'), ('way', 'to', 'split'), ('to', 'split', 'my'), ('split', 'my', 'text'), ('my', 'text', 'in'), ('text', 'in', 'four-grams'), ('in', 'four-grams', ','), ('four-grams', ',', 'five-grams'), (',', 'five-grams', 'or'), ('five-grams', 'or', 'even'), ('or', 'even', 'hundred-grams')] >>> fourgrams=ngrams(tokenize,4) >>> fourgrams [('I', 'am', 'aware', 'that'), ('am', 'aware', 'that', 'nltk'), ('aware', 'that', 'nltk', 'only'), ('that', 'nltk', 'only', 'offers'), ('nltk', 'only', 'offers', 'bigrams'), ('only', 'offers', 'bigrams', 'and'), ('offers', 'bigrams', 'and', 'trigrams'), ('bigrams', 'and', 'trigrams', ','), ('and', 'trigrams', ',', 'but'), ('trigrams', ',', 'but', 'is'), (',', 'but', 'is', 'there'), ('but', 'is', 'there', 'a'), ('is', 'there', 'a', 'way'), ('there', 'a', 'way', 'to'), ('a', 'way', 'to', 'split'), ('way', 'to', 'split', 'my'), ('to', 'split', 'my', 'text'), ('split', 'my', 'text', 'in'), ('my', 'text', 'in', 'four-grams'), ('text', 'in', 'four-grams', ','), ('in', 'four-grams', ',', 'five-grams'), ('four-grams', ',', 'five-grams', 'or'), (',', 'five-grams', 'or', 'even'), ('five-grams', 'or', 'even', 'hundred-grams')]
źródło
Ludzie już całkiem ładnie odpowiedzieli na scenariusz, w którym potrzebujesz bigramów lub trygramów, ale jeśli potrzebujesz każdego gramu do zdania, w takim przypadku możesz użyć
nltk.util.everygrams
>>> from nltk.util import everygrams >>> message = "who let the dogs out" >>> msg_split = message.split() >>> list(everygrams(msg_split)) [('who',), ('let',), ('the',), ('dogs',), ('out',), ('who', 'let'), ('let', 'the'), ('the', 'dogs'), ('dogs', 'out'), ('who', 'let', 'the'), ('let', 'the', 'dogs'), ('the', 'dogs', 'out'), ('who', 'let', 'the', 'dogs'), ('let', 'the', 'dogs', 'out'), ('who', 'let', 'the', 'dogs', 'out')]
Jeśli masz limit jak w przypadku trygramów, w których maksymalna długość powinna wynosić 3, możesz użyć parametru max_len, aby go określić.
>>> list(everygrams(msg_split, max_len=2)) [('who',), ('let',), ('the',), ('dogs',), ('out',), ('who', 'let'), ('let', 'the'), ('the', 'dogs'), ('dogs', 'out')]
Możesz po prostu zmodyfikować parametr max_len, aby uzyskać dowolny gram, tj. Cztery gramy, pięć gramów, sześć lub nawet sto gramów.
Wcześniej wspomniane rozwiązania można zmodyfikować, aby wdrożyć powyższe rozwiązanie, ale to rozwiązanie jest znacznie prostsze.
Aby przeczytać więcej, kliknij tutaj
A kiedy potrzebujesz tylko określonego gramu, takiego jak bigram lub trygram itp., Możesz użyć nltk.util.ngrams, jak wspomniano w odpowiedzi MAHassana.
źródło
Możesz łatwo uruchomić swoją własną funkcję, aby to zrobić, używając
itertools
:from itertools import izip, islice, tee s = 'spam and eggs' N = 3 trigrams = izip(*(islice(seq, index, None) for index, seq in enumerate(tee(s, N)))) list(trigrams) # [('s', 'p', 'a'), ('p', 'a', 'm'), ('a', 'm', ' '), # ('m', ' ', 'a'), (' ', 'a', 'n'), ('a', 'n', 'd'), # ('n', 'd', ' '), ('d', ' ', 'e'), (' ', 'e', 'g'), # ('e', 'g', 'g'), ('g', 'g', 's')]
źródło
izip(*(islice(seq, index, None) for index, seq in enumerate(tee(s, N))))
, że nie do końca to rozumiem.Bardziej eleganckie podejście do budowania bigramów z wbudowanym językiem Pythona
zip()
. Po prostu przekonwertuj oryginalny ciąg na listę osplit()
, a następnie przekaż listę raz normalnie i raz z przesunięciem o jeden element.string = "I really like python, it's pretty awesome." def find_bigrams(s): input_list = s.split(" ") return zip(input_list, input_list[1:]) def find_ngrams(s, n): input_list = s.split(" ") return zip(*[input_list[i:] for i in range(n)]) find_bigrams(string) [('I', 'really'), ('really', 'like'), ('like', 'python,'), ('python,', "it's"), ("it's", 'pretty'), ('pretty', 'awesome.')]
źródło
Nigdy nie zajmowałem się nltk, ale robiłem N-gram jako część jakiegoś małego projektu klasowego. Jeśli chcesz znaleźć częstotliwość wszystkich N-gramów występujących w strunie, oto sposób, aby to zrobić.
D
da ci histogram twoich N-słów.D = dict() string = 'whatever string...' strparts = string.split() for i in range(len(strparts)-N): # N-grams try: D[tuple(strparts[i:i+N])] += 1 except: D[tuple(strparts[i:i+N])] = 1
źródło
collections.Counter(tuple(strparts[i:i+N]) for i in xrange(len(strparts)-N))
będzie działać szybciej niż try-z wyjątkiemDla four_grams jest już w NLTK , oto fragment kodu, który może ci w tym pomóc:
from nltk.collocations import * import nltk #You should tokenize your text text = "I do not like green eggs and ham, I do not like them Sam I am!" tokens = nltk.wordpunct_tokenize(text) fourgrams=nltk.collocations.QuadgramCollocationFinder.from_words(tokens) for fourgram, freq in fourgrams.ngram_fd.items(): print fourgram, freq
Mam nadzieję, że to pomoże.
źródło
Możesz użyć sklearn.feature_extraction.text.CountVectorizer :
import sklearn.feature_extraction.text # FYI http://scikit-learn.org/stable/install.html ngram_size = 4 string = ["I really like python, it's pretty awesome."] vect = sklearn.feature_extraction.text.CountVectorizer(ngram_range=(ngram_size,ngram_size)) vect.fit(string) print('{1}-grams: {0}'.format(vect.get_feature_names(), ngram_size))
wyjścia:
4-grams: [u'like python it pretty', u'python it pretty awesome', u'really like python it']
Możesz ustawić
ngram_size
dowolną dodatnią liczbę całkowitą. To znaczy możesz podzielić tekst na cztery gramy, pięć lub nawet sto gramów.źródło
Jeśli wydajność jest problemem i musisz zbudować wiele różnych n-gramów (do stu, jak mówisz), ale chcesz użyć czystego Pythona, zrobiłbym:
from itertools import chain def n_grams(seq, n=1): """Returns an itirator over the n-grams given a listTokens""" shiftToken = lambda i: (el for j,el in enumerate(seq) if j>=i) shiftedTokens = (shiftToken(i) for i in range(n)) tupleNGrams = zip(*shiftedTokens) return tupleNGrams # if join in generator : (" ".join(i) for i in tupleNGrams) def range_ngrams(listTokens, ngramRange=(1,2)): """Returns an itirator over all n-grams for n in range(ngramRange) given a listTokens.""" return chain(*(n_grams(listTokens, i) for i in range(*ngramRange)))
Stosowanie :
>>> input_list = input_list = 'test the ngrams generator'.split() >>> list(range_ngrams(input_list, ngramRange=(1,3))) [('test',), ('the',), ('ngrams',), ('generator',), ('test', 'the'), ('the', 'ngrams'), ('ngrams', 'generator'), ('test', 'the', 'ngrams'), ('the', 'ngrams', 'generator')]
~ Ta sama prędkość co NLTK:
import nltk %%timeit input_list = 'test the ngrams interator vs nltk '*10**6 nltk.ngrams(input_list,n=5) # 7.02 ms ± 79 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %%timeit input_list = 'test the ngrams interator vs nltk '*10**6 n_grams(input_list,n=5) # 7.01 ms ± 103 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %%timeit input_list = 'test the ngrams interator vs nltk '*10**6 nltk.ngrams(input_list,n=1) nltk.ngrams(input_list,n=2) nltk.ngrams(input_list,n=3) nltk.ngrams(input_list,n=4) nltk.ngrams(input_list,n=5) # 7.32 ms ± 241 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) %%timeit input_list = 'test the ngrams interator vs nltk '*10**6 range_ngrams(input_list, ngramRange=(1,6)) # 7.13 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Opublikuj ponownie z mojej poprzedniej odpowiedzi .
źródło
Nltk jest świetny, ale czasami jest narzutem w przypadku niektórych projektów:
import re def tokenize(text, ngrams=1): text = re.sub(r'[\b\(\)\\\"\'\/\[\]\s+\,\.:\?;]', ' ', text) text = re.sub(r'\s+', ' ', text) tokens = text.split() return [tuple(tokens[i:i+ngrams]) for i in xrange(len(tokens)-ngrams+1)]
Przykładowe zastosowanie:
>> text = "This is an example text" >> tokenize(text, 2) [('This', 'is'), ('is', 'an'), ('an', 'example'), ('example', 'text')] >> tokenize(text, 3) [('This', 'is', 'an'), ('is', 'an', 'example'), ('an', 'example', 'text')]
źródło
Możesz zdobyć wszystkie 4-6 gramów, używając kodu bez innego pakietu poniżej:
from itertools import chain def get_m_2_ngrams(input_list, min, max): for s in chain(*[get_ngrams(input_list, k) for k in range(min, max+1)]): yield ' '.join(s) def get_ngrams(input_list, n): return zip(*[input_list[i:] for i in range(n)]) if __name__ == '__main__': input_list = ['I', 'am', 'aware', 'that', 'nltk', 'only', 'offers', 'bigrams', 'and', 'trigrams', ',', 'but', 'is', 'there', 'a', 'way', 'to', 'split', 'my', 'text', 'in', 'four-grams', ',', 'five-grams', 'or', 'even', 'hundred-grams'] for s in get_m_2_ngrams(input_list, 4, 6): print(s)
dane wyjściowe są poniżej:
I am aware that am aware that nltk aware that nltk only that nltk only offers nltk only offers bigrams only offers bigrams and offers bigrams and trigrams bigrams and trigrams , and trigrams , but trigrams , but is , but is there but is there a is there a way there a way to a way to split way to split my to split my text split my text in my text in four-grams text in four-grams , in four-grams , five-grams four-grams , five-grams or , five-grams or even five-grams or even hundred-grams I am aware that nltk am aware that nltk only aware that nltk only offers that nltk only offers bigrams nltk only offers bigrams and only offers bigrams and trigrams offers bigrams and trigrams , bigrams and trigrams , but and trigrams , but is trigrams , but is there , but is there a but is there a way is there a way to there a way to split a way to split my way to split my text to split my text in split my text in four-grams my text in four-grams , text in four-grams , five-grams in four-grams , five-grams or four-grams , five-grams or even , five-grams or even hundred-grams I am aware that nltk only am aware that nltk only offers aware that nltk only offers bigrams that nltk only offers bigrams and nltk only offers bigrams and trigrams only offers bigrams and trigrams , offers bigrams and trigrams , but bigrams and trigrams , but is and trigrams , but is there trigrams , but is there a , but is there a way but is there a way to is there a way to split there a way to split my a way to split my text way to split my text in to split my text in four-grams split my text in four-grams , my text in four-grams , five-grams text in four-grams , five-grams or in four-grams , five-grams or even four-grams , five-grams or even hundred-grams
więcej szczegółów znajdziesz na tym blogu
źródło
Po około siedmiu latach, oto bardziej elegancka odpowiedź, używając
collections.deque
:def ngrams(words, n): d = collections.deque(maxlen=n) d.extend(words[:n]) words = words[n:] for window, word in zip(itertools.cycle((d,)), words): print(' '.join(window)) d.append(word) words = ['I', 'am', 'become', 'death,', 'the', 'destroyer', 'of', 'worlds']
Wynik:
In [15]: ngrams(words, 3) I am become am become death, become death, the death, the destroyer the destroyer of In [16]: ngrams(words, 4) I am become death, am become death, the become death, the destroyer death, the destroyer of In [17]: ngrams(words, 1) I am become death, the destroyer of In [18]: ngrams(words, 2) I am am become become death, death, the the destroyer destroyer of
źródło
Jeśli potrzebujesz czystego iteratora dla dużych ciągów znaków ze stałym użyciem pamięci:
from typing import Iterable import itertools def ngrams_iter(input: str, ngram_size: int, token_regex=r"[^\s]+") -> Iterable[str]: input_iters = [ map(lambda m: m.group(0), re.finditer(token_regex, input)) for n in range(ngram_size) ] # Skip first words for n in range(1, ngram_size): list(map(next, input_iters[n:])) output_iter = itertools.starmap( lambda *args: " ".join(args), zip(*input_iters) ) return output_iter
Test:
input = "If you want a pure iterator solution for large strings with constant memory usage" list(ngrams_iter(input, 5))
Wynik:
['If you want a pure', 'you want a pure iterator', 'want a pure iterator solution', 'a pure iterator solution for', 'pure iterator solution for large', 'iterator solution for large strings', 'solution for large strings with', 'for large strings with constant', 'large strings with constant memory', 'strings with constant memory usage']
źródło