2015-11-06 15 views
15

Ho insegnato alcune classi introduttive al text mining con Python e la classe ha provato il metodo simile con i testi di esercitazione forniti. Alcuni studenti hanno ottenuto risultati diversi per text1.similar() rispetto ad altri.Il metodo simile del modulo nltk produce risultati diversi su macchine diverse. Perché?

Tutte le versioni e così via erano uguali.

Qualcuno sa perché queste differenze si verifichino? Grazie.

Codice utilizzato alla riga di comando.

python 
>>> import nltk 
>>> nltk.download() #here you use the pop-up window to download texts 
>>> from nltk.book import * 
*** Introductory Examples for the NLTK Book *** 
Loading text1, ..., text9 and sent1, ..., sent9 
Type the name of the text or sentence to view it. 
Type: 'texts()' or 'sents()' to list the materials. 
text1: Moby Dick by Herman Melville 1851 
text2: Sense and Sensibility by Jane Austen 1811 
text3: The Book of Genesis 
text4: Inaugural Address Corpus 
text5: Chat Corpus 
text6: Monty Python and the Holy Grail 
text7: Wall Street Journal 
text8: Personals Corpus 
text9: The Man Who Was Thursday by G . K . Chesterton 1908 
>>>>>> text1.similar("monstrous") 
mean part maddens doleful gamesome subtly uncommon careful untoward 
exasperate loving passing mouldy christian few true mystifying 
imperial modifies contemptible 
>>> text2.similar("monstrous") 
very heartily so exceedingly remarkably as vast a great amazingly 
extremely good sweet 

Tali elenchi di termini restituiti dal metodo simile differire da utente a utente, hanno molte parole comuni, ma non sono elenchi identici. Tutti gli utenti utilizzavano lo stesso sistema operativo e le stesse versioni di python e nltk.

Spero che chiarisca la domanda. Grazie.

+0

Potete fornire il testo di input e il frammento di codice che avete utilizzato? Quindi, possiamo provare a esaminare il codice e vedere come spiegare la differenza. – alvas

+0

Ho appena seguito le istruzioni che fanno parte di questa pagina del libro NLTK. http://www.nltk.org/book/ch01.html –

+0

Qual è il bit della macchina che stai utilizzando per il tuo snippet di codice nella domanda? http://stackoverflow.com/questions/9964396/python-check-if-a-system-is-32-or-64-bit-to-determine-whether-to-run-the-funct, qual è il tuo output per la struttura di importazione 'python -c"; print struct.calcsize ('P') * 8 "' – alvas

risposta

16

Nel tuo esempio ci sono 40 altre parole che hanno esattamente un contesto in comune con la parola 'monstrous'. Nella funzione similar un oggetto Counter viene utilizzato per contare le parole con contesti simili e quindi vengono stampate le più comuni (predefinito 20). Poiché tutti i 40 hanno la stessa frequenza, l'ordine può essere diverso.

Dalle doc di Counter.most_common:

elementi con uguali conteggi sono ordinate arbitrariamente


ho controllato la frequenza delle parole simili con questo codice (che è essenzialmente una copia la parte pertinente del codice funzione):

from nltk.book import * 
from nltk.util import tokenwrap 
from nltk.compat import Counter 

word = 'monstrous' 
num = 20 

text1.similar(word) 

wci = text1._word_context_index._word_to_contexts 

if word in wci.conditions(): 
      contexts = set(wci[word]) 
      fd = Counter(w for w in wci.conditions() for c in wci[w] 
          if c in contexts and not w == word) 
      words = [w for w, _ in fd.most_common(num)] 
      # print(tokenwrap(words)) 

print(fd) 
print(len(fd)) 
print(fd.most_common(num)) 

uscita: (sedute diverse danno output diverso per me)

Counter({'doleful': 1, 'curious': 1, 'delightfully': 1, 'careful': 1, 'uncommon': 1, 'mean': 1, 'perilous': 1, 'fearless': 1, 'imperial': 1, 'christian': 1, 'trustworthy': 1, 'untoward': 1, 'maddens': 1, 'true': 1, 'contemptible': 1, 'subtly': 1, 'wise': 1, 'lamentable': 1, 'tyrannical': 1, 'puzzled': 1, 'vexatious': 1, 'part': 1, 'gamesome': 1, 'determined': 1, 'reliable': 1, 'lazy': 1, 'passing': 1, 'modifies': 1, 'few': 1, 'horrible': 1, 'candid': 1, 'exasperate': 1, 'pitiable': 1, 'abundant': 1, 'mystifying': 1, 'mouldy': 1, 'loving': 1, 'domineering': 1, 'impalpable': 1, 'singular': 1}) 
6

In breve:

Ha qualcosa a che fare con il modo python3 hash chiavi quando la funzione similar() utilizza il dizionario contatore. Vedi http://pastebin.com/ysAF6p6h

Vedi How and why is the dictionary hashes different in python2 and python3?


a Long:

Cominciamo con:

from nltk.book import * 

L'importazione qui viene da https://github.com/nltk/nltk/blob/develop/nltk/book.py che importano l'oggetto nltk.text.Text e leggere diversi corpora nell'oggetto Text objec t.

E.g. Questo è come la variabile text1 è stato letto da nltk.book:

>>> import nltk.corpus 
>>> from nltk.text import Text 
>>> moby = Text(nltk.corpus.gutenberg.words('melville-moby_dick.txt')) 

Ora, se andiamo giù per il codice per la funzione similar() a https://github.com/nltk/nltk/blob/develop/nltk/text.py#L377, vediamo questo l'inizializzazione se è la prima istanza di accesso self._word_context_index:

def similar(self, word, num=20): 
    """ 
    Distributional similarity: find other words which appear in the 
    same contexts as the specified word; list most similar words first. 
    :param word: The word used to seed the similarity search 
    :type word: str 
    :param num: The number of words to generate (default=20) 
    :type num: int 
    :seealso: ContextIndex.similar_words() 
    """ 
    if '_word_context_index' not in self.__dict__: 
     #print('Building word-context index...') 
     self._word_context_index = ContextIndex(self.tokens, 
               filter=lambda x:x.isalpha(), 
               key=lambda s:s.lower()) 


    word = word.lower() 
    wci = self._word_context_index._word_to_contexts 
    if word in wci.conditions(): 
     contexts = set(wci[word]) 
     fd = Counter(w for w in wci.conditions() for c in wci[w] 
         if c in contexts and not w == word) 
     words = [w for w, _ in fd.most_common(num)] 
     print(tokenwrap(words)) 
    else: 
     print("No matches") 

In modo che ci punti all'oggetto nltk.text.ContextIndex, che è supporre di raccogliere tutte le parole con la finestra di contesto simile e memorizzarle. La docstring dice:

Un indice bidirezionale tra le parole e i loro "contesti" in un testo. Il contesto di una parola viene in genere definito come le parole che si verificano in una finestra fissa attorno alla parola; ma altre definizioni possono anche essere usate fornendo una funzione di contesto personalizzata.

Per impostazione predefinita, se si sta chiamando la funzione similar(), sarà inizializzare la _word_context_index con le impostazioni di default del contesto vale a direla finestra di token a sinistra ea destra, vedere https://github.com/nltk/nltk/blob/develop/nltk/text.py#L40

@staticmethod 
def _default_context(tokens, i): 
    """One left token and one right token, normalized to lowercase""" 
    left = (tokens[i-1].lower() if i != 0 else '*START*') 
    right = (tokens[i+1].lower() if i != len(tokens) - 1 else '*END*') 
    return (left, right) 

Dalla funzione similar(), vediamo che esegue un'iterazione parola nel contesto memorizzata nel word_context_index, cioè wci = self._word_context_index._word_to_contexts.

In sostanza, _word_to_contexts è un dizionario, dove le chiavi sono le parole del corpus ed i valori sono le parole a destra ea sinistra da https://github.com/nltk/nltk/blob/develop/nltk/text.py#L55:

self._word_to_contexts = CFD((self._key(w), self._context_func(tokens, i)) 
           for i, w in enumerate(tokens)) 

E qui vediamo che si tratta di un CFD, che è un nltk.probability.ConditionalFreqDist oggetto, che non include il livellamento della probabilità di token, vedere il codice completo a https://github.com/nltk/nltk/blob/develop/nltk/probability.py#L1646.


L'unico eventualmente di ottenere il risultato diverso è quando la funzione similar() passanti attraverso le parole most_common a https://github.com/nltk/nltk/blob/develop/nltk/text.py#L402

Dato che due chiavi in ​​un oggetto Counter hanno gli stessi conteggi, la parola con un inferiore hash ordinata stamperà prima e l'hash della chiave dipende dal bit-dimensioni della CPU, vedere http://www.laurentluce.com/posts/python-dictionary-implementation/


L'intero processo di trovare le si parole simili è deterministico, dal:

  • corpus/ingresso è fisso Text(gutenberg.words('melville-moby_dick.txt'))
  • contesto di default per ogni parola è anche corretto, cioè self._word_context_index
  • calcolo della distribuzione di frequenza condizionale per _word_context_index._word_to_contexts è discreto

Salvo quando la funzione genera l'elenco most_common, che quando c'è un pareggio nei valori Counter, verrà emesso l'elenco delle chiavi in ​​base ai loro hash.

In python2, non c'è ragione per ottenere un output diverso da diverse istanze dello stessa macchina con il seguente codice:

$ python 
>>> from nltk.book import * 
>>> text1.similar('monstrous') 
>>> exit() 
$ python 
>>> from nltk.book import * 
>>> text1.similar('monstrous') 
>>> exit() 
$ python 
>>> from nltk.book import * 
>>> text1.similar('monstrous') 
>>> exit() 

Ma in Python3, dà un output diverso ogni volta che si esegue text1.similar('monstrous') , vedi http://pastebin.com/ysAF6p6h


Ecco un semplice esperimento per dimostrare che le differenze di hashing eccentrici tra python2 e python3:

[email protected]:~$ python -c "from collections import Counter; x = Counter({'foo': 1, 'bar': 1, 'foobar': 1, 'barfoo': 1}); print(x.most_common())" 
[('foobar', 1), ('foo', 1), ('bar', 1), ('barfoo', 1)] 
[email protected]:~$ python -c "from collections import Counter; x = Counter({'foo': 1, 'bar': 1, 'foobar': 1, 'barfoo': 1}); print(x.most_common())" 
[('foobar', 1), ('foo', 1), ('bar', 1), ('barfoo', 1)] 
[email protected]:~$ python -c "from collections import Counter; x = Counter({'foo': 1, 'bar': 1, 'foobar': 1, 'barfoo': 1}); print(x.most_common())" 
[('foobar', 1), ('foo', 1), ('bar', 1), ('barfoo', 1)] 


[email protected]:~$ python3 -c "from collections import Counter; x = Counter({'foo': 1, 'bar': 1, 'foobar': 1, 'barfoo': 1}); print(x.most_common())" 
[('barfoo', 1), ('foobar', 1), ('bar', 1), ('foo', 1)] 
[email protected]:~$ python3 -c "from collections import Counter; x = Counter({'foo': 1, 'bar': 1, 'foobar': 1, 'barfoo': 1}); print(x.most_common())" 
[('foo', 1), ('barfoo', 1), ('bar', 1), ('foobar', 1)] 
[email protected]:~$ python3 -c "from collections import Counter; x = Counter({'foo': 1, 'bar': 1, 'foobar': 1, 'barfoo': 1}); print(x.most_common())" 
[('bar', 1), ('barfoo', 1), ('foobar', 1), ('foo', 1)] 
+0

I ran il tuo ultimo frammento di codice (3x il caricamento e la chiamata 'simile') su un Windows (64 bit) e ottengo output diversi per ogni corsa. – b3000

+0

@ b3000 Freddo. Puoi condividere la tua uscita da qualche parte? Anche sistemi operativi diversi hanno cose diverse in corso. – alvas

+1

Oh UBER cool, questo succede solo in python3! vedi http://pastebin.com/ysAF6p6h – alvas

Problemi correlati