Câu trả lời của https://stackoverflow.com/users/1515832/generic-human là tuyệt vời. Nhưng cách thực hiện điều này tốt nhất mà tôi từng thấy là do chính Peter Norvig viết trong cuốn sách 'Dữ liệu đẹp' của anh ấy.
Trước khi tôi dán mã của anh ấy, hãy để tôi mở rộng lý do tại sao phương pháp của Norvig chính xác hơn (mặc dù chậm hơn một chút và lâu hơn về mặt mã).
1) Dữ liệu tốt hơn một chút - cả về kích thước và độ chính xác (anh ấy sử dụng số từ thay vì xếp hạng đơn giản) 2) Quan trọng hơn, đó là logic đằng sau n-gram thực sự làm cho cách tiếp cận trở nên chính xác .
Ví dụ mà anh ấy cung cấp trong cuốn sách của mình là vấn đề tách một chuỗi 'sitdown'. Bây giờ, một phương pháp tách chuỗi không phải bigram sẽ xem xét p ('sit') * p ('down') và nếu điều này nhỏ hơn p ('sitdown') - trường hợp này thường xảy ra - nó sẽ KHÔNG chia nó, nhưng chúng tôi muốn nó (hầu hết thời gian).
Tuy nhiên, khi bạn có mô hình bigram, bạn có thể đánh giá p ('sit down') là bigram so với p ('sitdown') và chiến thắng trước đó. Về cơ bản, nếu bạn không sử dụng bigrams, nó sẽ coi xác suất các từ bạn đang tách là độc lập, điều này không đúng, một số từ có nhiều khả năng xuất hiện lần lượt hơn. Thật không may, đó cũng là những từ thường bị dính với nhau trong rất nhiều trường hợp và gây nhầm lẫn cho bộ chia.
Đây là liên kết đến dữ liệu (đó là dữ liệu cho 3 vấn đề riêng biệt và phân đoạn chỉ là một. Vui lòng đọc chương để biết chi tiết): http://norvig.com/ngrams/
và đây là liên kết đến mã: http://norvig.com/ngrams/ngrams.py
Các liên kết này đã hoạt động một thời gian, nhưng tôi vẫn sao chép, dán phần phân đoạn của mã vào đây
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