大家晚安,
这个问题让我有点尴尬,因为我知道我应该能够独自得到答案。但是,我对Python的了解只是一点点,所以我需要一些比我更有经验的人的帮助......
以下代码来自最近编辑过的书中的Norvig's "Natural Language Corpus Data"章节,它是关于将句子“likethisone”转换为“[like,this,one]”(这意味着,正确地分割单词)。
我已将所有代码移植到C#(事实上,我自己重新编写了程序),除了函数segment
,我甚至在尝试理解它的语法时遇到了很多麻烦。有人可以帮我翻译一下C#中更易阅读的形式吗?
非常感谢你。
################ 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)
答案 0 :(得分:2)
让我们先解决第一个问题:
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)
它需要一个单词并返回最可能的单词列表,因此其签名将为static IEnumerable<string> segment(string text)
。显然,如果text
是一个空字符串,其结果应该是一个空列表。否则,它会创建一个递归列表推导,定义可能的单词候选列表,并根据其概率返回最大值。
static IEnumerable<string> segment(string text)
{
if (text == "") return new string[0]; // C# idiom for empty list of strings
var candidates = from pair in splits(text)
select new[] {pair.Item1}.Concat(segment(pair.Item2));
return candidates.OrderBy(Pwords).First();
}
当然,现在我们必须翻译splits
功能。它的工作是返回一个单词开头和结尾的所有可能元组的列表。翻译相当简单:
static IEnumerable<Tuple<string, string>> splits(string text, int L = 20)
{
return from i in Enumerable.Range(1, Math.Min(text.Length, L))
select Tuple.Create(text.Substring(0, i), text.Substring(i));
}
接下来是Pwords
,只需在其输入列表中的每个单词的product
结果上调用Pw
函数:
static double Pwords(IEnumerable<string> words)
{
return product(from w in words select Pw(w));
}
product
非常简单:
static double product(IEnumerable<double> nums)
{
return nums.Aggregate((a, b) => a * b);
}
查看完整的源代码,很明显Norvig打算将segment
函数的结果记忆以提高速度。这是一个提供此加速的版本:
static Dictionary<string, IEnumerable<string>> segmentTable =
new Dictionary<string, IEnumerable<string>>();
static IEnumerable<string> segment(string text)
{
if (text == "") return new string[0]; // C# idiom for empty list of strings
if (!segmentTable.ContainsKey(text))
{
var candidates = from pair in splits(text)
select new[] {pair.Item1}.Concat(segment(pair.Item2));
segmentTable[text] = candidates.OrderBy(Pwords).First().ToList();
}
return segmentTable[text];
}
答案 1 :(得分:1)
我根本不知道C#,但我可以解释Python代码是如何工作的。
@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)
第一行,
@memo
是decorator。这导致后续行中定义的函数被包装在另一个函数中。装饰器通常用于过滤输入和输出。在这种情况下,根据它包装的函数的名称和角色,我收集到这个函数memoizes调用segment
。
下一步:
def segment(text):
"Return a list of words that is the best segmentation of text."
if not text: return []
声明函数正确,给出docstring,并设置此函数递归的终止条件。
接下来是最复杂的一行,也许是给你带来麻烦的那一行:
candidates = ([first]+segment(rem) for first,rem in splits(text))
外括号与for..in
构造相结合,创建generator expression。这是迭代序列的有效方式,在本例中为splits(text)
。生成器表达式是一种紧凑的for循环,可以产生值。在这种情况下,值将成为迭代candidates
的元素。 “Genexps”类似于list comprehensions,但是通过不保留它们产生的每个值来实现更高的内存效率。
因此,对于splits(text)
返回的迭代中的每个值,生成器表达式都会生成一个列表。
splits(text)
中的每个值都是(first, rem)
对。
每个生成的列表都以对象first
开头;这是通过将first
放在列表文字中表示的,即[first]
。然后添加另一个列表;第二个列表由递归调用segment
确定。在Python中添加列表会将它们连接起来,即[1, 2] + [3, 4]
给出[1, 2, 3, 4]
。
最后,在
return max(candidates, key=Pwords)
递归确定的列表iteration
和密钥函数被传递给max
。在迭代中的每个值上调用关键函数,以获取用于确定该列表在迭代中是否具有最高值的值。