La réponse de https://stackoverflow.com/users/1515832/generic-human est excellente. Mais la meilleure implémentation que j'ai jamais vue a été écrite par Peter Norvig lui-même dans son livre «Beautiful Data».
Avant de coller son code, permettez-moi d'expliquer pourquoi la méthode de Norvig est plus précise (bien qu'un peu plus lente et plus longue en termes de code).
1) Les données sont un peu meilleures - à la fois en termes de taille et de précision (il utilise un nombre de mots plutôt qu'un simple classement) 2) Plus important encore, c'est la logique derrière les n-grammes qui rend vraiment l'approche si précise .
L'exemple qu'il donne dans son livre est le problème de la division d'une chaîne «sitdown». Maintenant, une méthode non bigramme de division de chaîne considérerait p ('sit') * p ('down'), et si cela est inférieur au p ('sitdown') - ce qui sera le cas assez souvent - il ne sera PAS divisé mais nous le voudrions (la plupart du temps).
Cependant, lorsque vous avez le modèle bigramme, vous pouvez évaluer p («s'asseoir») comme un bigramme vs p («sitdown») et le premier gagne. En gros, si vous n'utilisez pas de bigrammes, cela traite la probabilité des mots que vous divisez comme indépendants, ce qui n'est pas le cas, certains mots sont plus susceptibles d'apparaître les uns après les autres. Malheureusement, ce sont aussi les mots qui sont souvent collés ensemble dans de nombreux cas et qui confondent le séparateur.
Voici le lien vers les données (il s'agit de données pour 3 problèmes distincts et la segmentation n'en est qu'un. Veuillez lire le chapitre pour plus de détails): http://norvig.com/ngrams/
et voici le lien vers le code: http://norvig.com/ngrams/ngrams.py
Ces liens existent depuis un certain temps, mais je vais quand même copier-coller la partie segmentation du code ici
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