La respuesta de https://stackoverflow.com/users/1515832/generic-human es excelente. Pero la mejor implementación de esto que he visto nunca fue escrita por el propio Peter Norvig en su libro 'Beautiful Data'.
Antes de pegar su código, permítanme explicarme por qué el método de Norvig es más preciso (aunque un poco más lento y más largo en términos de código).
1) Los datos son un poco mejores, tanto en términos de tamaño como en términos de precisión (usa un recuento de palabras en lugar de una clasificación simple) 2) Más importante aún, es la lógica detrás de los n-gramas lo que realmente hace que el enfoque sea tan preciso .
El ejemplo que proporciona en su libro es el problema de dividir una cuerda 'sentada'. Ahora, un método de división de cadenas que no es bigrama consideraría p ('sit') * p ('down'), y si es menor que p ('sitdown'), que será el caso con bastante frecuencia, NO se dividirá , pero nos gustaría que lo hiciera (la mayor parte del tiempo).
Sin embargo, cuando tiene el modelo de bigrama, puede valorar p ('sentarse') como un bigrama frente a p ('sentarse') y el primero gana. Básicamente, si no usa bigrams, trata la probabilidad de las palabras que está dividiendo como independientes, lo cual no es el caso, es más probable que algunas palabras aparezcan una tras otra. Desafortunadamente, esas son también las palabras que a menudo se juntan en muchos casos y confunden al divisor.
Aquí está el enlace a los datos (son datos para 3 problemas separados y la segmentación es solo uno. Lea el capítulo para obtener más detalles): http://norvig.com/ngrams/
y aquí está el enlace al código: http://norvig.com/ngrams/ngrams.py
Estos enlaces han estado activos por un tiempo, pero copiaré y pegaré la parte de segmentación del código aquí de todos modos.
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