【问题标题】:Produce a sentence from a grammar with a given number of terminals从具有给定数量终端的语法中产生一个句子
【发布时间】:2009-06-08 19:36:38
【问题描述】:

假设你有一个玩具语法,比如:(更新后输出看起来更自然)

S -> ${NP} ${VP} | ${S} and ${S} | ${S}, after which ${S}

NP -> the ${N} | the ${A} ${N} | the ${A} ${A} ${N}

VP -> ${V} ${NP}

N -> dog | fish | bird | wizard

V -> kicks | meets | marries

A -> red | striped | spotted

例如,“狗踢红巫师”,“鸟遇到斑点鱼或巫师嫁给条纹狗”

根据这个语法必须包含总共 n 个 Vs + As + Ns 的约束,你如何从这个语法中产生一个句子。给定一个整数,句子必须包含那么多终结符。 (当然,在这个语法中,最小可能的 n 是 3)。

【问题讨论】:

  • 您是否正在尝试在具有 n 个终端的所有句子中实现特定的随机分布?
  • 最少是5个,不能做10个或11个。
  • 这些句子可能很奇怪。 “要么狗踢鸟,要么狗踢鸟,要么狗踢鸟”。你想限制语法更像英语吗?
  • @ucleo#1:如果我错了,请纠正我,但我认为最小值是 N + V + N,3。我不在乎“the”确定器,我在数作为 NP 非终结符的一部分(也许我用错了这个词)。
  • 我同意最小值是 3,但是给定 S 的规则我没有看到最大值?除非终端复制被禁止?

标签: algorithm language-agnostic parsing nlp grammar


【解决方案1】:

以下 Python 代码将生成具有给定终端数的随机句子。 它的工作原理是计算产生给定长度的句子的方法数量,生成一个大的随机数,然后计算指示的句子。 计数是递归完成的,带有记忆。 如果 n 为 0,则空的右手边产生 1 个句子,否则产生 0 个句子。 要计算由非空右手边产生的句子数量,请将 i 相加,即右手边第一个符号使用的终端数。 对于每个 i,将右侧其余部分的可能性数量乘以第一个符号的可能性数量。 如果第一个符号是终结符,则 i 为 1 的可能性为 1,否则为 0。 如果第一个符号是非终结符,则对每个备选方案的可能性求和。 为了避免无限循环,我们必须小心在数量为 0 时修剪递归调用。 如果语法对一个句子有无限多的推导,这仍然可能无限循环。 例如,在语法中

S -> S S
S ->

空句有无限多的推导: S => , S => S S => , S => S S => S S S => 等。 查找特定句子的代码是对代码的直接修改以计算它们。 这段代码相当高效,可以在不到一秒的时间内生成 100 个句子,每个句子有 100 个终端。

import collections
import random

class Grammar:
    def __init__(self):
        self.prods = collections.defaultdict(list)
        self.numsent = {}
        self.weight = {}

    def prod(self, lhs, *rhs):
        self.prods[lhs].append(rhs)
        self.numsent.clear()

    def countsent(self, rhs, n):
        if n < 0:
            return 0
        elif not rhs:
            return 1 if n == 0 else 0
        args = (rhs, n)
        if args not in self.numsent:
            sym = rhs[0]
            rest = rhs[1:]
            total = 0
            if sym in self.prods:
                for i in xrange(1, n + 1):
                    numrest = self.countsent(rest, n - i)
                    if numrest > 0:
                        for rhs1 in self.prods[sym]:
                            total += self.countsent(rhs1, i) * numrest
            else:
                total += self.countsent(rest, n - self.weight.get(sym, 1))
            self.numsent[args] = total
        return self.numsent[args]

    def getsent(self, rhs, n, j):
        assert 0 <= j < self.countsent(rhs, n)
        if not rhs:
            return ()
        sym = rhs[0]
        rest = rhs[1:]
        if sym in self.prods:
            for i in xrange(1, n + 1):
                numrest = self.countsent(rest, n - i)
                if numrest > 0:
                    for rhs1 in self.prods[sym]:
                        dj = self.countsent(rhs1, i) * numrest
                        if dj > j:
                            j1, j2 = divmod(j, numrest)
                            return self.getsent(rhs1, i, j1) + self.getsent(rest, n - i, j2)
                        j -= dj
            assert False
        else:
            return (sym,) + self.getsent(rest, n - self.weight.get(sym, 1), j)

    def randsent(self, sym, n):
        return self.getsent((sym,), n, random.randrange(self.countsent((sym,), n)))

if __name__ == '__main__':
    g = Grammar()
    g.prod('S', 'NP', 'VP')
    g.prod('S', 'S', 'and', 'S')
    g.prod('S', 'S', 'after', 'which', 'S')
    g.prod('NP', 'the', 'N')
    g.prod('NP', 'the', 'A', 'N')
    g.prod('NP', 'the', 'A', 'A', 'N')
    g.prod('VP', 'V', 'NP')
    g.prod('N', 'dog')
    g.prod('N', 'fish')
    g.prod('N', 'bird')
    g.prod('N', 'wizard')
    g.prod('V', 'kicks')
    g.prod('V', 'meets')
    g.prod('V', 'marries')
    g.prod('A', 'red')
    g.prod('A', 'striped')
    g.prod('A', 'spotted')
    g.weight.update({'and': 0, 'after': 0, 'which': 0, 'the': 0})
    for i in xrange(100):
        print ' '.join(g.randsent('S', 3))

【讨论】:

  • 如果是“空范围”错误,则表示没有该长度的句子。
  • 啊,我明白了……这很接近但不太正确。看起来它正在输出的单词总数,而不是终端,N's,V's 和 A's only。 'the' 和 'and' 是非终结符的一部分,不应该计算在内。
  • 我更新了代码以允许指定权重。为简单起见,我假设没有非终结符可以为空。
  • 嗯,我以前见过“rhs”和“lhs”这两个词……这里:tinyurl.com/l75l48。我能问你在哪里可以了解更多关于这些东西的信息吗?我还在学习这些东西,我不一定总是觉得很直观。
【解决方案2】:

也许不是最好的解决方案,但我会递归地遍历语法的每个规则,直到超出约束,然后弹出并沿着语法探索另一条路径。保留所有符合约束的句子,丢弃所有不符合的句子。

例如,n = 3:

S -> (${NP} ${VP}) -> ( (${N}) ${VP}) -> ( ((狗) ${VP}) -> ... - > ( ((the (dog)) ( (kicks) (the ${NP} ) ) ) -> ( (the (dog) ( (kicks) (the (dog) ) ) ) )

然后弹回来

( ((the (dog) ( (kicks) (the ${N} ) ) ) ) -> ( (the (dog) ( (kicks) (the (fish) ) ) ) )

过了一会儿……

( ( (狗) ( ${V} ${N} ) ) ) -> ( ( (狗) ( (满足) ${N} ) ) ) -> ( ( (狗) ( (遇见) (狗) ) ) )

等等

本质上是深度优先的图搜索,只有您在搜索时构建图(并且您停止构建超出约束的部分)。

【讨论】:

    【解决方案3】:

    此问题包含类别错误。您指定的语法具有上下文无关语法的外观,但要求有特定数量的终端节点需要递归可枚举语法。

    【讨论】:

    • 你可能是对的,但我只能在 google 上找到一些关于“递归可枚举语法”的热门文章,这些文章并不是晦涩难懂的会议论文
    猜你喜欢
    • 1970-01-01
    • 2016-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-18
    • 1970-01-01
    • 2012-03-15
    • 1970-01-01
    相关资源
    最近更新 更多