📜  Cocke–Younger–Kasami(CYK)算法

📅  最后修改于: 2021-04-27 23:22:52             🧑  作者: Mango

语法表示自然语言对话的句法规则。但是在形式语言理论中,语法被定义为一组可以生成字符串的规则。可以从语法生成的所有字符串的集合称为语法语言。

上下文无关语法:
给我们一个上下文无关语法G =(V,X,R,S)和一个字符串w,其中:

  • V是变量或非终结符号的有限集合,
  • X是一组有限的终端符号,
  • R是一组有限的规则,
  • S是开始符号,是V的不同元素,并且
  • 假定VX是不交集。

成员资格问题定义为:语法G生成语言L(G)。给定的字符串是L(G)的成员吗?

乔姆斯基范式:
如果每个规则(如果G的每个规则都具有以下形式),则上下文无关文法G可以采用Chomsky范式(CNF):

  • A –> BC ,[在RHS上最多具有两个非终端符号]
  • A –> a或[RHS上的一个终端符号]
  • S –>空字符串 [空字符串]

Cocke-Younger-Kasami算法
它用于通过动态编程方法解决成员资格问题。该算法基于以下原理:问题[i,j]的解决方案可以从子问题[i,k]的解决方案和子问题[k,j]的解决方案构建该算法要求语法G为Chomsky范式(CNF)。注意,任何上下文无关的语法都可以系统地转换为CNF。采用此限制是为了使每个问题只能分为两个子问题,而不能分为多个子问题,以限制时间复杂度。

CYK算法如何工作?

对于长度为N的字符串,构造大小为N x N的表T。T [i,j]中的每个单元格都是可以产生从位置i到j的子字符串的所有成分的集合。该过程涉及用自底向上解析过程中遇到的子问题的解决方案填写表格。因此,单元格将从左到右,从下到上填充。

 

1

2

3

4

5

1 [1, 1] [1, 2] [1, 3] [1, 4] [1, 5]
2   [2, 2] [2, 3] [2, 4] [2, 5]
3     [3, 3] [3, 4] [3, 5]
4       [4, 4] [4, 5]
5         [5, 5]

在T [i,j]中,行号i表示开始索引,列号j表示结束索引。

A \in T[i, j] \text{ if and only if } B \in T[i, k],  C \in T[k, j] \text{ and } A \rightarrow BC \text{ is a rule of G}

如果可以从非终端K生成从ij的字母序列,该算法将考虑每个可能的字母子序列,并将K添加到T [i,j] 。对于长度为2或更大的子序列,它将考虑子序列的每个可能的划分为两部分,并检查是否存在A形式的规则。语法中的BC ,其中B和C可以基于T中已经存在的条目分别生成两个部分。仅当整个字符串都与起始符号匹配时,即,如果ST [1,n]的成员,才可以通过语法生成该句子

考虑Chomsky范式的示例语法:

NP   -->  Det | Nom
Nom  -->  AP | Nom
AP  -->  Adv | A
Det  -->  a | an
Adv  -->  very | extremely
AP   -->  heavy | orange | tall
A   -->  heavy | orange | tall | muscular
Nom -->  book | orange | man

现在考虑短语“一本非常沉重的橙皮书”:

a(1) very(2) heavy (3) orange(4) book(5)

让我们根据上述规则从左至右,从下至上填充表格:

 

1
a

2
very

3
heavy

4
orange

5
book

1
a

Det

NP

NP

2
very

 

Adv

AP

Nom

Nom

3
heavy

   

A, AP

Nom

Nom

4
orange

      Nom, A, AP

Nom

5
book

       

Nom

该表以以下方式填充:

  1. T [1,1] = {Det} as Det –> a是语法规则之一。
  2. T [2,2] = {Adv} as Adv –>确实是语法规则之一。
  3. T [1,2] = {},因为没有观察到匹配规则。
  4. T [3,3] = {A,AP},因为A –>非常和AP –>非常是语法规则。
  5. T [2,3] = {AP},因为AP –> Adv(T [2,2])A(T [3,3])是语法规则。
  6. T [1,3] = {},因为没有观察到匹配规则。
  7. T [4,4] = {Nom,A,AP},因为Nom –>橙色和A –>橙色和AP –>橙色是语法规则。
  8. T [3,4] = {Nom},因为Nom –> AP(T [3,3])Nom(T [3,4])是语法规则。
  9. T [2,4] = {Nom},因为Nom –> AP(T [2,3])Nom(T [4,4])是语法规则。
  10. T [1,4] = {NP},因为NP –> Det(T [1,1])Nom(T [2,4])是语法规则。
  11. T [5,5] = {Nom},因为Nom –> book是语法规则。
  12. T [4,5] = {Nom},因为Nom –> AP(T [4,4])Nom(T [5,5])是语法规则。
  13. T [3,5] = {Nom},因为Nom –> AP(T [3,3])Nom(T [4,5])是语法规则。
  14. T [2,5] = {Nom},因为Nom –> AP(T [2,3])Nom(T [4,5])是语法规则。
  15. T [1,5] = {NP},因为NP –> Det(T [1,1])Nom(T [2,5])是语法规则。

我们看到T [1] [5]具有NP (开始符号),这意味着该短语是语法G的语言的成员。

此短语的分析树如下所示:

让我们看另一个示例短语,“一个非常高大,极有肌肉的男人”:

a(1) very(2) tall(3) extremely(4) muscular(5) man(6)

现在,我们将使用CYK算法查找此字符串是否为语法G的成员

 

1
a

2
very

3
tall

4
extremely

5
muscular

6
man

1
a

Det

NP

2
very

 

Adv

AP

Nom

3
tall

   

AP, A

Nom

4
extremely

     

Adv

AP

Nom

5
muscular

       

A

6
man

         

Nom

我们看到T [1] [6]具有NP ,即开始符号,这意味着该短语是语法G语言的成员。

下面是上述算法的实现:

Python3
# Python implementation for the
# CYK Algorithm
  
# Non-terminal symbols
non_terminals = ["NP", "Nom", "Det", "AP", 
                  "Adv", "A"]
terminals = ["book", "orange", "man", 
             "tall", "heavy", 
             "very", "muscular"]
  
# Rules of the grammar
R = {
     "NP": [["Det", "Nom"]],
     "Nom": [["AP", "Nom"], ["book"], 
             ["orange"], ["man"]],
     "AP": [["Adv", "A"], ["heavy"], 
            ["orange"], ["tall"]],
     "Det": [["a"]],
     "Adv": [["very"], ["extremely"]],
     "A": [["heavy"], ["orange"], ["tall"], 
           ["muscular"]]
    }
  
# Function to perform the CYK Algorithm
def cykParse(w):
    n = len(w)
      
    # Initialize the table
    T = [[set([]) for j in range(n)] for i in range(n)]
  
    # Filling in the table
    for j in range(0, n):
  
        # Iterate over the rules
        for lhs, rule in R.items():
            for rhs in rule:
                  
                # If a terminal is found
                if len(rhs) == 1 and \
                rhs[0] == w[j]:
                    T[j][j].add(lhs)
  
        for i in range(j, -1, -1):   
               
            # Iterate over the range i to j + 1   
            for k in range(i, j + 1):     
  
                # Iterate over the rules
                for lhs, rule in R.items():
                    for rhs in rule:
                          
                        # If a terminal is found
                        if len(rhs) == 2 and \
                        rhs[0] in T[i][k] and \
                        rhs[1] in T[k + 1][j]:
                            T[i][j].add(lhs)
  
    # If word can be formed by rules 
    # of given grammar
    if len(T[0][n-1]) != 0:
        print("True")
    else:
        print("False")
      
# Driver Code
  
# Given string
w = "a very heavy orange book".split()
  
# Function Call
cykParse(w)


输出:
True

时间复杂度: O(N 3 )
辅助空间: O(N 2 )