web-dev-qa-db-fra.com

Générer des Ngrams (Unigrams, Bigrams, etc.) à partir d'un grand corpus de fichiers .txt et de leur fréquence

Je dois écrire un programme en NLTK qui divise un corpus (une grande collection de fichiers txt) en unigrammes, bigrammes, trigrammes, quatre grammes et cinq grammes. J'ai déjà écrit du code pour entrer mes fichiers dans le programme.

L'entrée est 300 fichiers .txt écrits en anglais et je veux la sortie sous forme de Ngrams et spécialement le nombre de fréquences. 

Je sais que NLTK propose des modules Bigram et Trigram: http://www.nltk.org/_modules/nltk/model/ngram.html

mais je ne suis pas si avancé pour les entrer dans mon programme. 

entrée: fichiers txt PAS des phrases simples

exemple de sortie:

Bigram [('Hi', 'How'), ('How', 'are'), ('are', 'you'), ('you', '?'), ('?', 'i'), ('i', 'am'), ('am', 'fine'), ('fine', 'and'), ('and', 'you')] 

Trigram: [('Hi', 'How', 'are'), ('How', 'are', 'you'), ('are', 'you', '?'), ('you', '?', 'i'), ('?', 'i', 'am'), ('i', 'am', 'fine'), ('am', 'fine', 'and'), ('fine', 'and', 'you')]

Mon code jusqu'à présent est: 

from nltk.corpus import PlaintextCorpusReader
corpus = 'C:/Users/jack3/My folder'
files = PlaintextCorpusReader(corpus, '.*')
ngrams=2

def generate(file, ngrams):
    for gram in range(0, ngrams):
    print((file[0:-4]+"_"+str(ngrams)+"_grams.txt").replace("/","_"))


for file in files.fileids():
generate(file, ngrams)

Toute aide que faut-il faire ensuite? 

15
Arash

Il suffit d'utiliser ntlk.ngrams.

import nltk
from nltk import Word_tokenize
from nltk.util import ngrams
from collections import Counter

text = "I need to write a program in NLTK that breaks a corpus (a large collection of \
txt files) into unigrams, bigrams, trigrams, fourgrams and fivegrams.\ 
I need to write a program in NLTK that breaks a corpus"
token = nltk.Word_tokenize(text)
bigrams = ngrams(token,2)
trigrams = ngrams(token,3)
fourgrams = ngrams(token,4)
fivegrams = ngrams(token,5)

print Counter(bigrams)

Counter({('program', 'in'): 2, ('NLTK', 'that'): 2, ('that', 'breaks'): 2,
 ('write', 'a'): 2, ('breaks', 'a'): 2, ('to', 'write'): 2, ('I', 'need'): 2,
 ('a', 'corpus'): 2, ('need', 'to'): 2, ('a', 'program'): 2, ('in', 'NLTK'): 2,
 ('and', 'fivegrams'): 1, ('corpus', '('): 1, ('txt', 'files'): 1, ('unigrams', 
','): 1, (',', 'trigrams'): 1, ('into', 'unigrams'): 1, ('trigrams', ','): 1,
 (',', 'bigrams'): 1, ('large', 'collection'): 1, ('bigrams', ','): 1, ('of',
 'txt'): 1, (')', 'into'): 1, ('fourgrams', 'and'): 1, ('fivegrams', '.'): 1,
 ('(', 'a'): 1, (',', 'fourgrams'): 1, ('a', 'large'): 1, ('.', 'I'): 1, 
('collection', 'of'): 1, ('files', ')'): 1})

UPDATE (avec du python pur): 

import os

corpus = []
path = '.'
for i in os.walk(path).next()[2]:
    if i.endswith('.txt'):
        f = open(os.path.join(path,i))
        corpus.append(f.read())
frequencies = Counter([])
for text in corpus:
    token = nltk.Word_tokenize(text)
    bigrams = ngrams(token, 2)
    frequencies += Counter(bigrams)
23
hellpanderr

Si l'efficacité est un problème et que vous devez créer plusieurs n-grammes différents, mais que vous souhaitez utiliser du python pur, je le ferais: 

from itertools import chain

def n_grams(tokens, n=1):
    """Returns an iterator over the n-grams given a list of tokens"""
    shiftToken = lambda i: (el for j,el in enumerate(tokens) 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(tokens, ngramRange=(1,2)):
    """Returns an itirator over all n-grams for n in range(ngramRange) given a list of tokens."""
    return chain(*(n_grams(tokens, i) for i in range(*ngramRange)))

Utilisation: 

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

~ Même vitesse que 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)

Republier de ma réponse précédente .

4
Yann Dubois

Voici un exemple simple d'utilisation de Python pur pour générer une variable ngram:

>>> def ngrams(s, n=2, i=0):
...     while len(s[i:i+n]) == n:
...         yield s[i:i+n]
...         i += 1
...
>>> txt = 'Python is one of the awesomest languages'

>>> unigram = ngrams(txt.split(), n=1)
>>> list(unigram)
[['Python'], ['is'], ['one'], ['of'], ['the'], ['awesomest'], ['languages']]

>>> bigram = ngrams(txt.split(), n=2)
>>> list(bigram)
[['Python', 'is'], ['is', 'one'], ['one', 'of'], ['of', 'the'], ['the', 'awesomest'], ['awesomest', 'languages']]

>>> trigram = ngrams(txt.split(), n=3)
>>> list(trigram)
[['Python', 'is', 'one'], ['is', 'one', 'of'], ['one', 'of', 'the'], ['of', 'the', 'awesomest'], ['the', 'awesomest',
'languages']]
2
Aziz Alto

Ok, puisque vous avez demandé une solution NLTK, il se peut que ce ne soit pas exactement ce que vous cherchiez, mais avez-vous envisagé TextBlob ? Il a un backend NLTK mais sa syntaxe est plus simple. Cela ressemblerait à ceci:

from textblob import TextBlob

text = "Paste your text or text-containing variable here" 
blob = TextBlob(text)
ngram_var = blob.ngrams(n=3)
print(ngram_var)

Output:
[WordList(['Paste', 'your', 'text']), WordList(['your', 'text', 'or']), WordList(['text', 'or', 'text-containing']), WordList(['or', 'text-containing', 'variable']), WordList(['text-containing', 'variable', 'here'])]

Bien entendu, vous devez toujours utiliser Counter ou une autre méthode pour ajouter un nombre par gramme. 

Cependant, l’approche la plus rapide (de loin) que j’ai été capable de créer à la fois n’importe quel ngram et que vous comptiez dans une seule fonction provient de this post de 2012 et utilise Itertools. C'est bien.

2
Montmons

peut-être que ça aide. voir lien

import spacy  
nlp_en = spacy.load("en_core_web_sm")
[x.text for x in doc]
0
madjardi

La réponse de @hellpander ci-dessus est correcte, mais pas efficace pour un très gros corpus (j'ai rencontré des difficultés avec ~ 650K documents). Le code ralentirait considérablement à chaque mise à jour des fréquences, en raison de la coûteuse consultation du dictionnaire au fur et à mesure de la croissance du contenu. Vous aurez donc besoin de variables tampons supplémentaires pour vous aider à mettre en cache le compteur de fréquences de @hellpander answer. Par conséquent, il est préférable de rechercher des clés très volumineuses (dictionnaire) à chaque fois qu’un nouveau document est itéré, vous l’ajouteriez au compteur temporaire plus petit Counter. Ensuite, après quelques itérations, il sera ajouté aux fréquences globales. De cette façon, ce sera beaucoup plus rapide car la recherche dans le dictionnaire est beaucoup moins fréquente.

import os

corpus = []
path = '.'
for i in os.walk(path).next()[2]:
    if i.endswith('.txt'):
        f = open(os.path.join(path,i))
        corpus.append(f.read())
frequencies = Counter([])

for i in range(0, len(corpus)):
    token = nltk.Word_tokenize(corpus[i])
    bigrams = ngrams(token, 2)
    f += Counter(bigrams)
    if (i%10000 == 0):
        # store to global frequencies counter and clear up f every 10000 docs.
        frequencies += Counter(bigrams)
        f = Counter([])
0
A. Dew