La risposta di https://stackoverflow.com/users/1515832/generic-human è fantastica. Ma la migliore implementazione di questo che abbia mai visto è stata scritta dallo stesso Peter Norvig nel suo libro "Beautiful Data".
Prima di incollare il suo codice, lasciatemi spiegare perché il metodo di Norvig è più accurato (anche se un po 'più lento e più lungo in termini di codice).
1) I dati sono un po 'migliori, sia in termini di dimensioni che in termini di precisione (usa un conteggio di parole piuttosto che una semplice classifica) 2) Ancora più importante, è la logica dietro n-grammi che rende l'approccio così accurato .
L'esempio che fornisce nel suo libro è il problema della divisione di una stringa "sitdown". Ora un metodo di suddivisione delle stringhe non Bigram prenderebbe in considerazione p ('sit') * p ('down'), e se questo è inferiore a p ('sitdown') - che sarà il caso abbastanza spesso - NON verrà diviso esso, ma lo vorremmo (la maggior parte delle volte).
Tuttavia, quando hai il modello bigram potresti valutare p ('sit down') come bigram vs p ('sitdown') e il primo vince. Fondamentalmente, se non usi bigram, tratta la probabilità delle parole che stai dividendo come indipendente, il che non è il caso, alcune parole hanno maggiori probabilità di apparire una dopo l'altra. Sfortunatamente quelle sono anche le parole che spesso sono incollate insieme in molti casi e confondono lo splitter.
Ecco il collegamento ai dati (sono i dati per 3 problemi separati e la segmentazione è solo uno. Si prega di leggere il capitolo per i dettagli): http://norvig.com/ngrams/
ed ecco il link al codice: http://norvig.com/ngrams/ngrams.py
Questi collegamenti sono attivi da un po ', ma copierò comunque la parte di segmentazione del codice qui
import re, string, random, glob, operator, heapq
from collections import defaultdict
from math import log10
def memo(f):
"Memoize function f."
table = {}
def fmemo(*args):
if args not in table:
table[args] = f(*args)
return table[args]
fmemo.memo = table
return fmemo
def test(verbose=None):
"""Run some tests, taken from the chapter.
Since the hillclimbing algorithm is randomized, some tests may fail."""
import doctest
print 'Running tests...'
doctest.testfile('ngrams-test.txt', verbose=verbose)
################ Word Segmentation (p. 223)
@memo
def segment(text):
"Return a list of words that is the best segmentation of text."
if not text: return []
candidates = ([first]+segment(rem) for first,rem in splits(text))
return max(candidates, key=Pwords)
def splits(text, L=20):
"Return a list of all possible (first, rem) pairs, len(first)<=L."
return [(text[:i+1], text[i+1:])
for i in range(min(len(text), L))]
def Pwords(words):
"The Naive Bayes probability of a sequence of words."
return product(Pw(w) for w in words)
#### Support functions (p. 224)
def product(nums):
"Return the product of a sequence of numbers."
return reduce(operator.mul, nums, 1)
class Pdist(dict):
"A probability distribution estimated from counts in datafile."
def __init__(self, data=[], N=None, missingfn=None):
for key,count in data:
self[key] = self.get(key, 0) + int(count)
self.N = float(N or sum(self.itervalues()))
self.missingfn = missingfn or (lambda k, N: 1./N)
def __call__(self, key):
if key in self: return self[key]/self.N
else: return self.missingfn(key, self.N)
def datafile(name, sep='\t'):
"Read key,value pairs from file."
for line in file(name):
yield line.split(sep)
def avoid_long_words(key, N):
"Estimate the probability of an unknown word."
return 10./(N * 10**len(key))
N = 1024908267229 ## Number of tokens
Pw = Pdist(datafile('count_1w.txt'), N, avoid_long_words)
#### segment2: second version, with bigram counts, (p. 226-227)
def cPw(word, prev):
"Conditional probability of word, given previous word."
try:
return P2w[prev + ' ' + word]/float(Pw[prev])
except KeyError:
return Pw(word)
P2w = Pdist(datafile('count_2w.txt'), N)
@memo
def segment2(text, prev='<S>'):
"Return (log P(words), words), where words is the best segmentation."
if not text: return 0.0, []
candidates = [combine(log10(cPw(first, prev)), first, segment2(rem, first))
for first,rem in splits(text)]
return max(candidates)
def combine(Pfirst, first, (Prem, rem)):
"Combine first and rem results into one (probability, words) pair."
return Pfirst+Prem, [first]+rem