5. TÌM HIỂU MỘT SỐ CHỨC NĂNG TRONG NLTK
5.4.5. Chart Parsing: Well-Formed Substring Tables
Những phương pháp đã đề cập ở trên bị giới hạn bởi tính hoàn chỉnh và tính hiệu quả. Để khác phục tình trạng này, chúng ta sẽ áp dụng các kỹ thuật thiết kế thuật toán lập trình động đến vấn đề phân tích cú pháp này.
Như chúng ta đã biết, lập trình động có thể lưu trữ kết quả trung gian và tái sử dụng chúng khi thích hợp, đạt được lợi ích hiệu quả đáng kể. Kỹ thuật này có thể được áp dụng để phân tích cú pháp, cho phép chúng ta lưu trữ các giải pháp một phần(partial) của công việc phân tích cú pháp và sau đó sử dụng chúng khi có nhu cầu để cho ra một giải pháp hoàn chỉnh. Cách tiếp cận được biết tới như là chart parsing, cũng là phần chính của nội dung phần này.
Phân tích câu “I shot an elephant in my pajamas” với văn phạm sau:
>>> groucho_grammar = nltk.parse_cfg(""" S -> NP VP
PP -> P NP
NP -> Det N | Det N PP | 'I'
VP -> V NP | VP PP
Det -> 'an' | 'my'
N -> 'elephant' | 'pajamas' V -> 'shot'
P -> 'in' """)
Rõ ràng trường hợp này không thể sử dụng top-down parser được do luật đệ quy tồn tại: VP NP PP. Để kiểm tra ta có thể sử dụng lệnh nltk.app.rdparser() nhập văn phạm và câu đầu vào để test, kết quả cho ra là vô tận ngay nhánh VP VP PP
Lập trình động cho phép xây dựng các cụm từ PP “in my pajamas”chỉ một lần, trước tiên chúng ta xây dựng và lưu nó trong một bảng, tra nó khi cần sử dụng như là một thành phần con của đối tượng NP hoặc đối tượng VP cao hơn. Bảng này được gọi là Bảng chuỗi con đúng văn phạm (Well-Formed Substring Tables), viết tắt là WFST. (Từ "substring" đề cập đến một chuỗi liền kề nhau của từ bên trong một câu). Chúng ta sẽ tìm hiểu làm thế nào xây dựng các WFST bottom-up để ghi lại các một cách có hệ thống những gì các thành phần cú pháp được tìm thấy.
Hãy hình dung cấu trúc câu như một chart sau: trong đó mỗi một cung nối trực tiếp chính là từ loại của từ vựng.
Cấu trúc này thể hiện mỗi một từ (nhóm từ) sẽ được bắt đầu bằng vị trí của nó trong chart và kết thúc cụm từ bởi vị trí kết thúc. Đều này dựa vào bản chất hình thành các cụm từ(câu) là tuần tự. Theo cấu trúc này, để truy xuất dễ dàng, ta sẽ tổ chức chúng thành một ma trận, vì vậy mà cung nối (1) đến (2) sẽ là một động từ (v). Một cách tổng quát chuỗi câu của ta gồm các từ vựng a1,a2…,an và văn phạm chúng ta chứa một luật có dạng A ai, thì ta sẽ cập nhật giá trị A vào cung (i-1,i).
Việc đầu tiên là cần phải xác định từ loại cho các từ vựng cơ bản dựa vào văn phạm
>>> text = ['I', 'shot', 'an', 'elephant', 'in', 'my', 'pajamas'] >>> groucho_grammar.productions(rhs=text[1])
[V -> 'shot']
a2 = shot và cung luật V shot thể hiện bằng giá trị cung (1,2).
Định nghĩa cấu trúc này như một list of list trong python,với hàm khởi tạo như sau:
def init_wfst(tokens, grammar): numtokens = len(tokens)
wfst = [[None for i in range(numtokens+1)] for j in range(numtokens+1)]
for i in range(numtokens):
productions = grammar.productions(rhs=tokens[i]) wfst[i][i+1] = productions[0].lhs()
return wfst
Định nghĩa hàm hiển thị:
def display(wfst, tokens):
print '\nWFST ' + ' '.join([("%-4d" % i) for i in range(1, len(wfst))])
for i in range(len(wfst)-1): print "%d " % i,
for j in range(1, len(wfst)):
print "%-4s" % (wfst[i][j] or '.'), print >>> wfst0 = init_wfst(tokens, groucho_grammar) >>> display(wfst0, tokens) WFST 1 2 3 4 5 6 7 0 NP . . . . 1 . V . . . . . 2 . . Det . . . . 3 . . . N . . . 4 . . . . P . . 5 . . . Det . 6 . . . N
Để có thể tra ra từ loại của từ vựng trong câu, truy xuất vào phần tử (i-1,i) với i>=1. Dựa vào quy tắc tuần tự của các từ sắp xếp trong câu, có thể dễ dàng cập nhật nội dung cụm từ loại cho các cụm từ bắt đầu từ các từ đơn ban đầu.
Xét từ “an” tại vị trí 2, ta có từ loại của nó là (2,3) = Det, từ “elephant” vị trí 3 có giá trị từ loại là (3,4)=N. Như vậy nếu nối chúng lại “an elephant” (2,4) ta sẽ có giá trị là gì ? hãy quay lại với bottom-up để tìm luật có dạng khớp với “an elephant”, kết quả cho ra A Det N= NP Det N. Kết quả giá trị (2,4) sẽ là NP chính là cung nối từ 2 đến 4 cho cụm từ “an elephant”. Một cách tổng quát, (i,j) có giá trị A khi và chỉ khi:
- A B C
- (i,k) có giá trị B
- (k,j) có giá trị C
Đoạn code hoàn tất ma trận như sau:
def complete_wfst(wfst, tokens, grammar, trace=False):
index = dict((p.rhs(), p.lhs()) for p in grammar.productions()) numtokens = len(tokens)
for span in range(2, numtokens+1):
for start in range(numtokens+1-span): end = start + span
nt1, nt2 = wfst[start][mid], wfst[mid][end] if nt1 and nt2 and (nt1,nt2) in index: wfst[start][end] = index[(nt1,nt2)] if trace:
print "[%s] %3s [%s] %3s [%s] ==> [%s] %3s [%s]" % \
(start, nt1, mid, nt2, end, start, index[(nt1,nt2)], end)
return wfst
Debug
>>> wfst1 = complete_wfst(wfst0, tokens, groucho_grammar, trace=True) [2] Det [3] N [4] ==> [2] NP [4] [5] Det [6] N [7] ==> [5] NP [7] [1] V [2] NP [4] ==> [1] VP [4] [4] P [5] NP [7] ==> [4] PP [7] [0] NP [1] VP [4] ==> [0] S [4] [1] VP [4] PP [7] ==> [1] VP [7] [0] NP [1] VP [7] ==> [0] S [7] >>> display(wfst1, tokens) WFST 1 2 3 4 5 6 7 0 NP . . S . . S 1 . V . VP . . VP 2 . . Det NP . . . 3 . . . N . . . 4 . . . . P . PP 5 . . . Det NP 6 . . . N
Hình thể hiện các cụm từ loại bằng các cung bên ngoài
Chúng ta có thể kết luận cho việc phân tích cú pháp của cả câu văn với nút S. Cấu trúc này bao gồm toàn bộ câu văn.
Lưu ý rằng toàn bộ đoạn cai đặt này không sử dụng bất cứ một hàm có sẳn nào mà xây dựng chúng ngay từ đầu và cấu trúc văn phạm có sẳn.
Một số nhược điểm:
- WFST không phải là một cây cú pháp, do đó đầu vào của nó phải dựa trên một câu phải đúng văn phạm.
- Yêu cầu các luật sinh không thuộc từ vựng phài là nhị phân. Mặc dù nó có thể chuyển đổi văn phạm CFG bất kì về dạng này, nhưng chúng ta nên sử dụng đúng với yêu cầu như vậy.
- Cũng như bottom-up, nó tiềm ẩn những xử lý phí phạm cho những tình huống không đúng văn phạm.
- Không giải quyết được sự mơ hồ trong câu, ví dụ 2 cụm từ VP(1,7): VP V NP và VP VP PP.
Chart parser sẽ sử dụng một số cấu trúc và thuật toán đặc trưng để giải quyết những vấn đề này.