n-gram dalam python, empat, lima, enam gram?

137

Saya mencari cara untuk membagi teks menjadi n-gram. Biasanya saya akan melakukan sesuatu seperti:

import nltk
from nltk import bigrams
string = "I really like python, it's pretty awesome."
string_bigrams = bigrams(string)
print string_bigrams

Saya sadar bahwa nltk hanya menawarkan bigrams dan trigram, tetapi apakah ada cara untuk membagi teks saya menjadi empat gram, lima gram atau bahkan seratus gram?

Terima kasih!

Shifu
sumber
Apakah Anda ingin teks dipecah menjadi kelompok-kelompok n ukuran berdasarkan kata atau karakter? Bisakah Anda memberi contoh output seperti apa di atas?
ChrisProsser
4
Tidak pernah dilakukan nltk tetapi sepertinya ada fungsi ingramsyang parameter kedua adalah derajat ngram yang Anda inginkan. Apakah INI versi NLTK yang Anda gunakan? Bahkan jika tidak, inilah sumbernya EDIT: Ada ngramsdan ingramsdi sana, ingramsmenjadi generator.
Brian
Ada juga jawaban di bawah utas ini yang mungkin berguna: stackoverflow.com/questions/7591258/fast-n-gram-calculation
ChrisProsser

Jawaban:

212

Jawaban berdasarkan python asli yang bagus diberikan oleh pengguna lain. Tapi inilah nltkpendekatannya (untuk berjaga-jaga, OP akan dihukum karena menemukan kembali apa yang sudah ada di nltkperpustakaan).

Ada modul ngram yang jarang digunakan orang nltk. Ini bukan karena sulit membaca ngram, tetapi melatih model berdasarkan ngrams di mana n> 3 akan menghasilkan banyak data yang jarang.

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
alva
sumber
4
Untuk karakter ngram, silakan juga melihat: stackoverflow.com/questions/22428020/…
alvas
Apakah ada cara untuk menggunakan N-gram untuk memeriksa seluruh dokumen seperti txt? Saya tidak terbiasa dengan Python jadi saya tidak tahu apakah itu bisa membuka file txt dan kemudian menggunakan analisis N-gram untuk memeriksa?
maoyi
1
Dapatkah seseorang mengomentari cara menguji keakuratan sixgrams?
LYu
64

Saya terkejut bahwa ini belum muncul:

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
sumber
Itulah yang dilakukan jawaban pertama dikurangi penghitungan frekuensi dan konversi tuple.
Brian
Lebih baik melihatnya ditulis ulang sebagai sebuah pemahaman.
Brian
@amirouche: tangkapan yang bagus. Terima kasih atas laporan bugnya. Sudah diperbaiki sekarang
inspectorG4dget
16

Hanya menggunakan alat 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]

Contoh output

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']

Untuk menjaga agar ngrams dalam format array cukup hapus ' '.join

Δημητρης Παππάς
sumber
15

di sini adalah cara sederhana untuk melakukan n-gram

>>> 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
sumber
1
Harus melakukan nltk.download ('punkt') untuk menggunakan fungsi nltk.word_tokenize (). Juga untuk mencetak hasilnya harus mengkonversi objek generator seperti bigrams, trigram, dan empatgram ke daftar menggunakan daftar (<genrator_object>).
bhatman
11

Orang-orang sudah menjawab dengan cukup baik untuk skenario di mana Anda membutuhkan bigrams atau trigram tetapi jika Anda membutuhkan everygram untuk kalimat dalam hal itu Anda dapat menggunakannltk.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')]

Jika Anda memiliki batas seperti dalam kasus trigram di mana panjang maks harus 3 maka Anda dapat menggunakan param max_len untuk menentukannya.

>>> list(everygrams(msg_split, max_len=2))
[('who',), ('let',), ('the',), ('dogs',), ('out',), ('who', 'let'), ('let', 'the'), ('the', 'dogs'), ('dogs', 'out')]

Anda hanya dapat memodifikasi param max_len untuk mencapai apa pun gram yaitu empat gram, lima gram, enam atau bahkan seratus gram.

Solusi yang disebutkan sebelumnya dapat dimodifikasi untuk mengimplementasikan solusi yang disebutkan di atas tetapi solusi ini jauh lebih maju dari itu.

Untuk bacaan lebih lanjut klik di sini

Dan ketika Anda hanya membutuhkan gram spesifik seperti bigram atau trigram dll, Anda dapat menggunakan nltk.util.ngrams sebagaimana disebutkan dalam jawaban MAHassan.

bhatman
sumber
6

Anda dapat dengan mudah menyiapkan fungsi Anda sendiri untuk melakukan ini menggunakan 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
sumber
1
Bisakah Anda jelaskan izip(*(islice(seq, index, None) for index, seq in enumerate(tee(s, N))))saya tidak terlalu memahaminya.
TomazStoiljkovic
4

Pendekatan yang lebih elegan untuk membangun bigrams dengan built-in python zip(). Cukup konversikan string asli menjadi daftar split(), lalu sampaikan daftar secara normal dan sekali diimbangi oleh satu elemen.

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
sumber
2

Saya tidak pernah berurusan dengan nltk tetapi melakukan N-gram sebagai bagian dari beberapa proyek kelas kecil. Jika Anda ingin menemukan frekuensi semua N-gram yang terjadi dalam string, berikut adalah cara untuk melakukannya. Dakan memberi Anda histogram kata-N Anda.

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
sumber
collections.Counter(tuple(strparts[i:i+N]) for i in xrange(len(strparts)-N))akan bekerja lebih cepat daripada coba-kecuali
inspectorG4dget
2

Untuk four_grams sudah ada di NLTK , berikut adalah sepotong kode yang dapat membantu Anda menuju ini:

 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

Saya harap ini membantu.

sel
sumber
2

Anda dapat menggunakan 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))

output:

4-grams: [u'like python it pretty', u'python it pretty awesome', u'really like python it']

Anda dapat mengatur ke ngram_sizebilangan bulat positif. Yaitu Anda dapat membagi teks menjadi empat gram, lima gram atau bahkan seratus gram.

Franck Dernoncourt
sumber
2

Jika efisiensi adalah masalah dan Anda harus membangun beberapa n-gram yang berbeda (hingga seratus seperti yang Anda katakan), tetapi Anda ingin menggunakan python murni saya akan lakukan:

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)))

Penggunaan:

>>> 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')]

~ Kecepatan yang sama dengan 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)

Repost dari jawaban saya sebelumnya .

Yann Dubois
sumber
0

Nltk hebat, tetapi kadang-kadang merupakan overhead untuk beberapa proyek:

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)]

Contoh penggunaan:

>> 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
sumber
0

Anda bisa mendapatkan semua 4-6gram menggunakan kode tanpa paket lain di bawah ini:

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)

output di bawah ini:

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

Anda dapat menemukan lebih banyak detail di blog ini

Joe Zhow
sumber
0

Setelah sekitar tujuh tahun, inilah jawaban yang lebih elegan menggunakan 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']

Keluaran:

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
sumber
0

Jika Anda menginginkan solusi iterator murni untuk string besar dengan penggunaan memori konstan:

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

Uji:

input = "If you want a pure iterator solution for large strings with constant memory usage"
list(ngrams_iter(input, 5))

Keluaran:

['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
sumber