还有一种语言建模方法,那就是利用语法规则(grammar) 来生成符合要求的句子。在小
学的时候,我们就已经知道了词的词类及其组合方式。例如,如果你有一个非常糟糕的英
语老师,你必定会认为句子都是由名词后面跟动词构成的。这样的话,如果给你一个由名
词和动词组成的列表,你就可以根据这种规则来造句了。
下面,我们将定义一个稍微复杂的语法:
grammar = {
"_S" : ["_NP _VP"],
"_NP" : ["_N",
"_A _NP _P _A _N"],
"_VP" : ["_V",
"_V _NP"],
"_N" : ["data science", "Python", "regression"],
"_A" : ["big", "linear", "logistic"],
"_P" : ["about", "near"],
"_V" : ["learns", "trains", "tests", "is"]
}
我们约定,以下划线开头的名称表示语法规则,它们需要进一步展开; 而其他名称是不需
要进一步处理的终端符号。
例如, "_S" 是“句子”规则,其产生一个 "_NP"(“名词短语”)规则,后面紧跟一个 "_VP"
(“动词短语”)规则。
动词短语规则可能会产生一个 "_V"(“动词”)规则,也可能会产生一个动词规则继之以名
词短语规则。
请注意, "_NP" 规则所生成的规则中也包括其自身。我们知道,语法是可以递归的,因此,
尽管这里这些语法非常有限,但是照样能够产生无穷多不同的句子。
那么,我们如何通过这些语法来生成句子呢? 我们不妨从一个包含句子规则的列表 ["_S"]
着手。然后,我们将不断展开每一项规则,即从该规则的产物中随机选择一个来代替它。
当我们的列表元素全部变成终端符号时,我们就可以停下来了。
举例来说,上述过程可能像下面这样:
['_S']
['_NP','_VP']
['_N','_VP']
['Python','_VP']
['Python','_V','_NP']
['Python','trains','_NP']
['Python','trains','_A','_NP','_P','_A','_N']
['Python','trains','logistic','_NP','_P','_A','_N']
['Python','trains','logistic','_N','_P','_A','_N']
['Python','trains','logistic','data science','_P','_A','_N']
['Python','trains','logistic','data science','about','_A', '_N']
['Python','trains','logistic','data science','about','logistic','_N']
['Python','trains','logistic','data science','about','logistic','Python']
我们如何实现它呢? 首先,我们需要创建一个简单的辅助函数来识别终端符号:
def is_terminal(token):
return token[0] != "_"
接下来,我们需要编写一个函数,将一个标记列表变成一个句子。首先,我们需要找到第
一个非终结符号标记。 如果我们找不到这种标记,那就意味着我们已经有一个完整的句
子,可以收工了。
如果我们真的找到了一个非终端符号, 那么就在其产物中随机选择一个。如果选中的是个
终端符号(即一个单词),那么直接用它替换相应的标记即可。除此之外,如果选中的是
一个由空格符分隔的非终端符标记, 那么则需要进行拆分,并将其拼接到当前标记中。总
之,我们的工作就是在一组新标记上不断重复这个过程。
上述过程可以通过下列代码实现:
def expand(grammar, tokens):
for i, token in enumerate(tokens):
# 跳过终端符号
if is_terminal(token): continue
# 如果这一步我们发现了一个非终端符号
# 需要随机选择一个替代者
replacement = random.choice(grammar[token])
if is_terminal(replacement):
tokens[i] = replacement
else:
tokens = tokens[:i] + replacement.split() + tokens[(i+1):]
# 现在展开新的符号列表
return expand(grammar, tokens)
# 如果到达这一步,就找出了所有的终端符号,可以收工了
return tokens
现在我们可以生成句子了:
def generate_sentence(grammar):
return expand(grammar, ["_S"])
只要我们不断改变语法——例如添加更多的单词、添加更多的规则、添加各种词类等——
就能得到足够多的网页来满足公司的需要。








暂无数据