基于上下文无关文法的句子生成算法

前言

算法来自国外大牛的一篇博客:点击此处可查看
算法不涉及任何人工智能领域知识,仅仅是针对上下文无关文法提出的生成句子的思路。

上下文无关文法

上下文无关文法仅与句子结构有关,与上下文语意无关。
属性|单词
--|--
S |NP VP
NP |Det N / Det N
NP |I / he / she / Joe
VP |V NP / VP
Det |a / the / my / his
N |elephant / cat / jeans / suit
V |kicked / followed / shot
上面是一个上下文无关文法规则的例子,S代表一个句子,从S开始,逐渐递归填充单词,便可以生成一个句子。

基本实现

import random
from collections import defaultdict

class CFG(object):
    def __init__(self):
        self.prod = defaultdict(list)  # 默认dict值为list,对于空键值对来说

    def add_prod(self, lhs, rhs):
        """ Add production to the grammar. 'rhs' can
            be several productions separated by '|'.
            Each production is a sequence of symbols
            separated by whitespace.

            Usage:
                grammar.add_prod('NT', 'VP PP')
                grammar.add_prod('Digit', '1|2|3|4')
        """
        prods = rhs.split('|')  # 按照|分割
        for prod in prods:
            self.prod[lhs].append(tuple(prod.split()))  # 默认split按空格进行分割,但是这里的分割是生成一个元组,整体添加到prod里

    def gen_random(self, symbol):
        """ Generate a random sentence from the
            grammar, starting with the given
            symbol.
        """
        sentence = ''

        # select one production of this symbol randomly
        rand_prod = random.choice(self.prod[symbol])  # 从符号列表中随机选择一个词组

        for sym in rand_prod:       #遍历词组中的单词
            # for non-terminals, recurse
            if sym in self.prod:        #如果这个位置的单词并不是一个确切的单词,而是一个词法结构,那么递归选择相应的符合条件的单词
                sentence += self.gen_random(sym)
            else:
                sentence += sym + ' '       #如果已经是一个确切的单词,那么直接连接到句子上即可

        return sentence

cfg1 = CFG()
cfg1.add_prod('S', 'NP VP')
cfg1.add_prod('NP', 'Det N | Det N')
cfg1.add_prod('NP', 'I | he | she | Joe')
cfg1.add_prod('VP', 'V NP | VP')
cfg1.add_prod('Det', 'a | the | my | his')
cfg1.add_prod('N', 'elephant | cat | jeans | suit')
cfg1.add_prod('V', 'kicked | followed | shot')

for i in range(10):
    print(cfg1.gen_random('S'))

这里给出了一个基于Python的基本实现,通过递归填充单词即可。

上下文无关文法导致无法终止的问题

上面的算法很简单,可以看起来很棒。但实际上存在一个问题,容易导致无法终止的问题。
属性|表达式
--|--
EXPR|TERM + EXPR
EXPR|TERM - EXPR
EXPR|TERM
TERM|FACTOR * TERM
TERM|FACTOR / TERM
TERM|FACTOR
FACTOR|ID // NUM // ( EXPR )
ID|x // y // z // w
NUM|0//1//2//3//4//5//6//7//8//9
例如上面一个生成算数表达式的例子,上面的规则都符合正常的数学知识,但是在生成表达式的过程中产生了不能终结的问题。EXPR->TERM + EXPR->TERM + EXPR,类似这样的无限循环。

解决无法终止问题

破解无法终止的问题,可以采用概率生成算法。

这里引用了作者原文中的图,由于TERM-EXPR的祖先已经使用过这个表达式,那么此时这个表达式的生成概率会相应地降低,例如图中的降低因子是0.5,也就是说使用过一次,那么下一次使用这个表达式的概率只有原来的50%。
上述算法使用代码实现如下

import random
from collections import defaultdict

# 概率选择算法
def weighted_choice(weights):
    rnd = random.random() * sum(weights)
    for i, w in enumerate(weights):
        rnd -= w
        if rnd < 0:
            return i

class CFG(object):
    def __init__(self):
        self.prod = defaultdict(list)  # 默认dict值为list,对于空键值对来说

    def add_prod(self, lhs, rhs):
        """ Add production to the grammar. 'rhs' can
            be several productions separated by '|'.
            Each production is a sequence of symbols
            separated by whitespace.

            Usage:
                grammar.add_prod('NT', 'VP PP')
                grammar.add_prod('Digit', '1|2|3|4')
        """
        prods = rhs.split('|')  # 按照|分割
        for prod in prods:
            self.prod[lhs].append(tuple(prod.split()))  # 默认split按空格进行分割,但是这里的分割是生成一个元组,整体添加到prod里

    def gen_random_convergent(self,
                              symbol,
                              cfactor=0.25,
                              pcount=defaultdict(int)
                              ):
        """ Generate a random sentence from the
            grammar, starting with the given symbol.

            Uses a convergent algorithm - productions
            that have already appeared in the
            derivation on each branch have a smaller
            chance to be selected.

            cfactor - controls how tight the
            convergence is. 0 < cfactor < 1.0

            pcount is used internally by the
            recursive calls to pass on the
            productions that have been used in the
            branch.
        """
        sentence = ''

        # The possible productions of this symbol are weighted
        # by their appearance in the branch that has led to this
        # symbol in the derivation
        #
        weights = []
        for prod in self.prod[symbol]:  # 对于满足某个要求的所有表达式,计算相应的生成概率
            if prod in pcount:
                weights.append(cfactor ** (pcount[prod]))  # 对于父节点已经引用过的表达式,此处需要根据因子减小生成概率
            else:
                weights.append(1.0)  #

        rand_prod = self.prod[symbol][weighted_choice(weights)]  # 根据概率选择新生成的表达式

        # pcount is a single object (created in the first call to
        # this method) that's being passed around into recursive
        # calls to count how many times productions have been
        # used.
        # Before recursive calls the count is updated, and after
        # the sentence for this call is ready, it is rolled-back
        # to avoid modifying the parent's pcount.
        #
        pcount[rand_prod] += 1

        for sym in rand_prod:
            # for non-terminals, recurse
            if sym in self.prod:  # 如果不是一个确切的单词,那么递归填充表达式
                sentence += self.gen_random_convergent(
                    sym,
                    cfactor=cfactor,
                    pcount=pcount)
            else:
                sentence += sym + ' '  # 如果是一个确切的单词,那么直接添加到句子后面即可

        # backtracking: clear the modification to pcount
        pcount[rand_prod] -= 1  # 由于pcount是引用传值,因此需要恢复原来状态
        return sentence

cfg1 = CFG()
cfg1.add_prod('S', 'NP VP')
cfg1.add_prod('NP', 'Det N | Det N')
cfg1.add_prod('NP', 'I | he | she | Joe')
cfg1.add_prod('VP', 'V NP | VP')
cfg1.add_prod('Det', 'a | the | my | his')
cfg1.add_prod('N', 'elephant | cat | jeans | suit')
cfg1.add_prod('V', 'kicked | followed | shot')

for i in range(10):
    print(cfg1.gen_random_convergent('S'))

cfg2 = CFG()
cfg2.add_prod('EXPR', 'TERM + EXPR')
cfg2.add_prod('EXPR', 'TERM - EXPR')
cfg2.add_prod('EXPR', 'TERM')
cfg2.add_prod('TERM', 'FACTOR * TERM')
cfg2.add_prod('TERM', 'FACTOR / TERM')
cfg2.add_prod('TERM', 'FACTOR')
cfg2.add_prod('FACTOR', 'ID | NUM | ( EXPR )')
cfg2.add_prod('ID', 'x | y | z | w')
cfg2.add_prod('NUM', '0|1|2|3|4|5|6|7|8|9')
for i in range(10):
    print(cfg2.gen_random_convergent('EXPR'))

小结

通过递归,可以很容易地实现基于上下文无关文法生成句子的算法。但是需要注意的是,普通算法会导致无法终止的问题,针对这个问题,有人提出了基于概率的句子生成算法,很好地解决了无法终止的问题。

原文地址:https://www.cnblogs.com/zhenlingcn/p/9210540.html

时间: 2024-10-11 19:42:03

基于上下文无关文法的句子生成算法的相关文章

句法模式识别(二)-正规文法、上下文无关文法

正规文法的特性 1.所有长度有限的语言都是正规的. 2.用正规文法当然能产生无限长串,其中周期重复部分的长度不大于非终止符的长度. 举个例子 在此规则之下,能生成句子 其中周期重复部分为ab,这个例子的非终止符的元素个数为2,故满足2不大于2. 自嵌入特性 我们把上下文无关文法中的正规文法去掉,剩下的那部分我们叫做真正的上下文无关文法. 自嵌入特性是区分真正的上下文无关文法与正规文法的判定标准. 即一个真正的上下文无关文法一定具有自嵌入特性,正规文法具有非自嵌入特性.亦即非自嵌入的上下文无关文法

上下文无关文法

1.上下文无关文法定义 文法:它描述语言语法结构的一组形式规则.  上下文无关文法:它定义的语法范畴(或语法单位)是完全独立于这种范畴可能出现的环境.例如,在程序设计语言中,当碰到一个算术表达式时,我们完全可以"就事论事"处理,而不必考虑它所处的上下文.然而,在自然语言中,随便一个词,甚至一个字的意思在不同的上下文中都有可能有不同的意思.幸运的是,当今的程序设计语言都是上下文无关的 . 好像有点抽象,来个例子 "→"表示箭头左边的由箭头右边的定义 把He gave

上下文无关文法的表示与存储

[问题描述]把输入的文法存储在计算机内. [基本要求] 1.输入上下文无关文法的一组产生式. 2.将文法按顺序或链式结构存储在计算机内. 3.输出文法的四要素:终极符集合.非终极符集合.规则式集合和开始符. 4.开始符在输入时指明,否则将所输入第一条规则式的左部符号视为开始符. 例如输入如下文法: E->E+T| T T- >T*F| F F->(E)|i 下面是C++代码: #include<iostream> using namespace std; const int

上下文有关文法

上下文有关文法(CSG,英语:context-sensitive grammar)是一种形式文法,其中任何产生式规则的左手端和右手端都可以被终结符和非终结符构成的上下文所围绕.上下文有关文法比上下文无关文法更一般性,但仍足够有秩序得可以被线性有界自动机所解析. 上下文有关文法的概念是诺姆·乔姆斯基在1950年代介入的,被作为描述自然语言的语法的一种方式,在自然语言中一个单词是否可以出现在特定位置上,要依赖于上下文.可以被上下文有关文法描述的形式语言叫做上下文有关语言. 形式定义 形式文法 G =

基于字典序的组合生成算法

基于字典序的组合生成算法 2010-12-02 01:22:52|  分类: 离散数学 |  标签:离散数学  排列组合   |举报 |字号大中小 订阅 一. 问题描述 给定非空集合A,按字典序的方法生成集合A的所有组合.关于字典序的概念,这里不做严格定义,只是做一简单解释. 字典序是字符串比较的一种方法.例如两个字符串 abcd,abef,这两个字符串谁大? 显然,abef>abcd:如何得出这个结论的呢? 从左至右依次比较每一个字符,首先比较两个串的第一个字符,都是a,相等:其次比较两个串的

04(2) 基于上下文相关的GMM-HMM声学模型2之参数共享

1.三音素建模存在的问题 问题一:很多三音素在训练数据中没有出现(尤其跨词三音素) 问题二:在训练数据中出现过的三音素有相当一部分出现的频次较少 因此,三音素模型训练时存在较严重的数据不足问题 2.参数共享 1)何为参数共享? 对于一个HMM模型来说,有如下参数: 两个模型之间参数共享,意为: 如: 共享转移概率: 共享状态输出分布: 2)共享可以在不同的层次上进行 (1)共享高斯---tied mixtures 所有分布共享相同的高斯集合,但拥有不同的混合权重 (2)共享状态---state

基于Spark MLlib平台的协同过滤算法---电影推荐系统

基于Spark MLlib平台的协同过滤算法---电影推荐系统 又好一阵子没有写文章了,阿弥陀佛...最近项目中要做理财推荐,所以,回过头来回顾一下协同过滤算法在推荐系统中的应用. 说到推荐系统,大家可能立马会想到协同过滤算法.本文基于Spark MLlib平台实现一个向用户推荐电影的简单应用.其中,主要包括三部分内容: 协同过滤算法概述 基于模型的协同过滤应用---电影推荐 实时推荐架构分析     一.协同过滤算法概述 本人对算法的研究,目前还不是很深入,这里简单的介绍下其工作原理. 通常,

[计算机图形学] 基于C#窗口的Bresenham直线扫描算法、种子填充法、扫描线填充法模拟软件设计(一)

一.首先说明: 这是啥? —— 这是利用C#FORM写的一个用来演示计算机图形学中 ①Bresenham直线扫描算法(即:连点成线):②种子填充法(即:填充多边形):③扫描线填充法 有啥用? ——  无论是连点成线还是区域填充在高级编程中基本上都提供很高效的库函数来调用.这里拿出这些算法一方面有利于大家理解那些封装的函数底层是实现:另一方面是方便嵌入式TFT屏幕底层驱动开发时借鉴的. 是啥样? ——  如下面的操作,不言而喻. 二.进入正题: 2-1.直线的扫描转换 图形的扫描转换实质就是在光栅

时空上下文视觉跟踪(STC)算法的解读与代码复现(转)

本文转载自zouxy09博客,原文地址为http://blog.csdn.net/zouxy09/article/details/16889905:在此,仅当笔记mark一下及给大家分享一下. 时空上下文视觉跟踪(STC)算法的解读与代码复现 [email protected] http://blog.csdn.net/zouxy09 本博文主要是关注一篇视觉跟踪的论文.这篇论文是Kaihua Zhang等人今年投稿到一个会议的文章,因为会议还没有出结果,所以作者还没有发布他的Matlab源代码