n-gramów w Pythonie, cztery, pięć, sześć gramów?

142

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!

Shifu
źródło
Czy chcesz, aby tekst został podzielony na grupy o rozmiarze n według słowa lub znaku? Czy możesz podać przykład, jak powinien wyglądać wynik w przypadku powyższego?
ChrisProsser,
4
Nigdy nie robiono nltk, ale wygląda na to, że istnieje funkcja, ingramsktó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 ma ngramsi ingramstam, ingramsjako generator.
Brian,
W tym wątku jest też odpowiedź, która może się przydać: stackoverflow.com/questions/7591258/fast-n-gram-calculation
ChrisProsser.

Odpowiedzi:

220

Świetne odpowiedzi oparte na natywnym Pythonie udzielane przez innych użytkowników. Ale oto nltkpodejście (na wszelki wypadek OP zostanie ukarany za ponowne wymyślenie tego, co już istnieje w nltkbibliotece).

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
alvas
źródło
4
Aby uzyskać ngramy postaci, spójrz również na: stackoverflow.com/questions/22428020/…
alvas
Czy istnieje sposób na użycie N-gramów do sprawdzenia całego dokumentu, takiego jak txt? Nie znam Pythona, więc nie wiem, czy może otworzyć plik txt, a następnie użyć analizy N-gramowej, aby sprawdzić?
maoyi
1
Czy ktoś może skomentować, jak sprawdzić dokładność sixgrams?
LYu
65

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.']
inspectorG4dget
źródło
To jest dokładnie to, co robi pierwsza odpowiedź, pomniejszona o liczenie częstotliwości i konwersję krotek.
Brian,
Jednak przyjemniej jest zobaczyć to przepisane jako zrozumienie.
Brian,
@amirouche: dobry chwyt. Dzięki za zgłoszenia błędów. To zostało już naprawione
inspectorG4dget
17

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
15

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')]
MAHassan
źródło
1
Musiałem zrobić nltk.download ('punkt'), aby użyć funkcji nltk.word_tokenize (). Aby wydrukować wyniki, trzeba było przekonwertować obiekt generatora, taki jak bigramy, trygramy i fourgramy, na listę przy użyciu listy (<genrator_object>).
im_bhatman,
13

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.

im_bhatman
źródło
6

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')]
tzaman
źródło
1
Czy możesz wyjaśnić izip(*(islice(seq, index, None) for index, seq in enumerate(tee(s, N)))), że nie do końca to rozumiem.
TomazStoiljkovic,
4

Bardziej eleganckie podejście do budowania bigramów z wbudowanym językiem Pythona zip(). Po prostu przekonwertuj oryginalny ciąg na listę o split(), 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.')]
Serendipity
źródło
2

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ć. Dda 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
Nik
ź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ątkiem
inspectorG4dget
2

Dla 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.

sel
źródło
2

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_sizedowolną dodatnią liczbę całkowitą. To znaczy możesz podzielić tekst na cztery gramy, pięć lub nawet sto gramów.

Franck Dernoncourt
źródło
2

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 .

Yann Dubois
źródło
0

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')]
Daniel Pérez Rada
źródło
0

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

Joe Zhow
źródło
0

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
inspectorG4dget
źródło
0

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']
James McGuigan
źródło