Văn phạm tiếng Việt

Một phần của tài liệu Hoàn thiện hạch toán chi phí sản xuất và tính giá thành sản phẩm tại Công ty (Trang 47)

Tổng kết lại, ta xét văn phạm sinh một lớp các câu đơn giản hai thành phần của tiếng Việt như sau:

1. [Câu] → [Chủ ngữ] + [Vị ngữ] 2. [Chủ ngữ] → [Tên riêng] 3. [Chủ ngữ] → [Đại từ] 4. [Chủ ngữ] → [Đại từ] + [Phụ tố chỉ định] 5. [Chủ ngữ] → [Danh ngữ] 6. [Vị ngữ] → [Tính ngữ] 7. [Vị ngữ] → [Động ngữ] 8. [Vị ngữ] → [Từ quan hệ] + [Danh ngữ] 9. [Vị ngữ] → [Từ quan hệ] + [Động ngữ] 10. [Danh ngữ] → [Danh từ]

11. [Danh ngữ] → [Phụ tố số lượng] + [Danh từ] 12. [Danh ngữ] → [Phụ tố số lượng] + [Phụ tổ loại

thể, đơn vị] + [Danh từ] 13. [Danh ngữ] → [Danh từ] + [Phụ tố chỉ định]

14. [Danh ngữ] → [Phụ tổ loại thể, đơn vị] + [Danh từ]

15. [Danh ngữ] → [Phụ tổ loại thể, đơn vị] + [Danh từ] + [Phụ tố chỉ định]

16. [Danh ngữ] → [Phụ tố số lượng] + [Phụ tổ loại thể, đơn vị] + [Danh từ] + [Phụ tố chỉ định] 17. [Động ngữ] → [Động từ] 18. [Động ngữ] → [Động từ] + [Danh từ] 19. [Động ngữ] → [Động từ] + [Phụ từ] 20. [Động ngữ] → [Động từ] + [Tính từ] 21. [Động ngữ] → [Phụ từ] + [Động từ] 22. [Động ngữ] → [Phụ từ] + [Động từ] + [Phụ từ] 23. [Động ngữ] → [Phụ từ] + [Động từ] + [Danh từ] 24. [Động ngữ] → [Phụ từ] + [Động từ] + [Tính từ] 25. [Tính ngữ] → [Tính từ] 26. [Tính ngữ] → [Tính từ] + [Phụ từ]

27. [Tính ngữ] → [Tính từ] + [Danh từ] 28. [Tính ngữ] → [Phụ từ] + [Tính từ]

29. [Tính ngữ] → [Phụ từ] + [Tính từ] + [Danh từ] Bảng 5. Tập luật của văn phạm tiếng Việt

Bây giờ em xin trình bày cấu trúc dữ liệu được sử dụng trong chương trình phân tích cú pháp từ trên xuống cho văn phạm phi ngữ cảnh, chỉ nêu lên phần cài đặt cấu trúc dữ liệu và thuật toán phân tích mà không liệt kê chi tiết toàn bộ mã nguồn của chương trình. Cấu trúc dữ liệu và thuật toán được trình bày trong ngôn ngữ lập trình Java. Với tiếng Việt, trước khi phân tích cú pháp, ta cần tách các đơn vị từ vựng trong câu, sau đó áp dụng tương tự mô hình tiếng Anh. Vì vậy, trong phần này em chỉ trình bày cấu trúc dữ liệu và cài đặt thuật toán cho bài toán phân tích cú pháp tiếng Anh. Trong phần phụ lục của khoá luận em sẽ trình bày chi tiết bài toán tách từ vựng tiếng Việt.

Chương 5. Cài đặt chương trình 5.1. Cấu trúc dữ liệu

Từ điển là một danh sách tuyến tính các mục từ trong từ điển. Mỗi mục từ là một đối tượng của lớp Lexicon. Lớp Lexicon gồm hai thành phần dữ liệu: biến kiểu xâu ký tự word chứa nội dung của mục từ và một vector partOfSpeech biểu diễn các từ loại tương ứng với từ đó. Các phương thức thao tác trên hai thành phần dữ liệu này là:

¾ boolean isPos(String c) kiểm tra xem từ này có kiểu từ loại là c hay không

¾ String getWord() lấy ra nội dung của từ

¾ Vector getPos() lấy ra vector các từ loại của từ Lớp Lexicon có dạng như sau:

class Lexicon { String word;

Vector partOfSpeech;

Lexicon(String w, Vector pos) {}

boolean isPOS(String c) {} public String getWord() {} public Vector getPOS() {} public String toString() {} }

Để tăng tốc độ tính toán trong quá trình phân tích, ta biểu diễn mỗi kiểu từ loại bởi một ký tự. Để thực hiện điều này, ta dùng một bảng băm (hashtable) chứa các cặp khoá-trị thể hiện các cặp kiểu từ loại -- mã ký tự tương ứng. Trong chương trình phân tích cú pháp tiếng Anh, ta dùng bảng băm sau:

Hashtable ht = new Hashtable();

String keys[] = {"S", "N", "V", "a","n", "v", "i", "j", "P", "p", "x", "o", "u"

};

String values[] = {"S", "NP", "VP", "ART", "NOUN", "VERB", "NAME", "ADJ", "PP", "PREP", "AUX", "PRONOUN", "NUMBER"

};

for (int i = 0; i < keys.length; i++) ht.put(keys[i], values[i]);

Bảng băm này cũng tiện dụng khi biểu diễn các nút của cây chứa kết quả phân tích. Để biểu diễn các nút của cây phân tích, ta sử dụng lớp ParsingTreeNode. Lớp này gồm hai thành phần dữ liệu value chứa tên từ loại và key chứa mã của từ loại này. Lớp ParsingTreeNode có dạng như sau:

private String key;

public ParsingTreeNode() {}

public ParsingTreeNode(String k, String v) {} public String getKey() {}

public String toString() {} } (adsbygoogle = window.adsbygoogle || []).push({});

Với bảng băm nêu trên, ta có văn phạm đơn giản của tiếng Anh gồm 11 quy tắc như sau:

STT Dạng đầy đủ Dạng biểu diễn

1. S → NP VP S → NV

2. NP → ART NOUN N → an 3. NP → ART ADJ NOUN N → ajn

4. NP → NAME N → i 5. NP → PRONOUN N → o 6. PP → PREP NP P → pN 7. VP → VERB V → v 8. VP → AUX V NP V → xvN 9. VP → VERB NP V → vN 10. VP → VERB NP PP V → vNP 11. VP → VERB PP V → vP Bảng 6. Tập luật của văn phạm tiếng Anh

Để lưu các vế trái và vế phải của các quy tắc sinh trong văn phạm, ta sử dụng hai mảng một chiều left, right:

int MAX_RULES = 50; // số quy tắc cực đại String left[] = new String[MAX_RULES]; String right[] = new String[MAX_RULES];

Các từ của câu cần phân tích được chứa trong mảng tokens. Với tiếng Anh, ta chỉ cần dựa vào các ký tự trắng trong câu để cắt ra các từ.

String tokens[];

Để chứa các trạng thái phân tích được sao lưu và vị trí hiện tại đang xét trong câu cần phân tích, ta dùng các ngăn xếp

Stack backupStates = new Stack(); Stack positions = new Stack();

5.2. Cài đặt thuật toán

Để dựng được cây phân tích, bao giờ ta cũng cần tìm dẫn xuất trái của quá trình phân tích. Dẫn xuất trái là một dẫn xuất tại mỗi bước luôn luôn chọn chữ phụ đầu tiên của xâu trung gian để thực hiện phép thế. Như vậy, một xâu ω bất kỳ thuộc vào ngôn ngữ sinh bởi văn phạm G khi và chỉ khi có tồn tại dẫn xuất trái để từ ký hiệu khởi đầu S suy dẫn được ra ω. Ta gọi dãy các số hiệu của các quy tắc suy dẫn i1i2...in là dãy trái. Ta quy bài toán phân tích câu về bài toán tìm dãy trái của ω.

Với những cấu trúc dữ liệu và thuật toán đã trình bày, ta có phương thức tìm dãy trái của một xâu như sau:

¾ Input: Một câu tiếng Anh ω, các từ cách nhau ít nhất một ký tự trắng, không có các dấu chấm câu

¾ Output: Dãy trái của ω nếu phân tích được, hoặc trả lời không đoán nhận được

public Vector parse(String w) { int i,j;

boolean recognizable = false; // đã đoán nhận được?

Vector parsingOrder = new Vector();// dãy trái của phân tích // Lấy ra các từ trong câu

StringTokenizer stk = new StringTokenizer(w); int n = stk.countTokens();

tokens = new String[n]; i = 0;

while (stk.hasMoreTokens()) { tokens[i] = stk.nextToken(); i++;

}

String curState = ""; // trạng thái hiện tại

int curPos = 0; // vị trí hiện tại

Stack backupStates = new Stack(); // các trạng thái sao lưu

Stack positions = new Stack(); // các vị trí sao lưu

Stack ruleIdxs = new Stack(); // chỉ số các quy tắc được chọn

backupStates.push(startSymbol); // startSymbol = S - câu

ruleIdxs.push(new Vector()); positions.push(new Integer(0)); (adsbygoogle = window.adsbygoogle || []).push({});

// vòng lặp phân tích

while ((!backupStates.isEmpty()) && (!recognizable)) { curState = (String)backupStates.pop();

parsingOrder = (Vector)ruleIdxs.pop();

curPos = ((Integer)positions.pop()).intValue(); j = 0;

while ((curState.length() > 0) &&

(Character.isLowerCase(curState.charAt(j)))) {

// lo•i b• nh•ng ph••ng án không th• ••a t•i k•t qu•… while ((curState.length() > n-curPos) &&

(!backupStates.isEmpty())) { curState = (String)backupStates.pop(); curPos = ((Integer)positions.pop()).intValue(); parsingOrder = (Vector)ruleIdxs.pop(); } // nếu từ hiện tại thuộc kiểu từ loại này…

curState = (curState.length() == 1)? "" : curState.substring(1);

j = 0;

if ((curPos >= tokens.length) && curState.compareTo("") == 0)){ recognizable = true;

break; }

} else { // n•u không thì xét v• trí và tr•ng thái sao l•u… if (!positions.isEmpty()) curPos = ((Integer)positions.pop()).intValue(); if (!backupStates.isEmpty()) { curState = (String)backupStates.pop(); parsingOrder = (Vector)ruleIdxs.pop(); } else break; } }

// Áp d•ng các suy d•n: ••a các ph••ng án ti•p theo vào các ng•n x•p, // ch• ••a vào các ph••ng án có kh• n•ng cho k•t qu•

if ((!recognizable) && (curState.compareTo("") != 0)) for (i = 0; i < numberOfRules; i++)

if ((left[i].compareTo("" + curState.charAt(0)) == 0) && (curState.length() <= tokens.length-curPos+1)) { backupStates.push(right[i]+

((curState.length() >= 1) ? curState.substring(1) : ""));

positions.push(new Integer(curPos)); Vector tempVec = new Vector();

for (Enumeration e = parsingOrder.elements(); e.hasMoreElements();)

tempVec.addElement((Integer)e.nextElement()); tempVec.addElement(new Integer(i+1));

ruleIdxs.push(tempVec); }

} // kết thúc vòng lặp phân tích

if (!recognizable) return new Vector(); else return parsingOrder;

}

Phương thức parse nêu trên tìm dãy trái của ω và đưa nó vào một vector các số nguyên, mỗi phần tử của vector này là số hiệu của các quy tắc suy dẫn tương ứng.

5.3. Thể hiện kết quả phân tích

Để thể hiện kết quả phân tích ở dạng cây, ta dùng cấu trúc JTree của Java. Cây phân tích được xây dựng từ dãy trái, trong quá trình chèn các nút mới, ta sử dụng thuật toán tìm kiếm theo chiều sâu để tìm nút cha của nút mới cần chèn vào. Phương thức xây dựng cây phân tích như sau:

public JTree createParsingTree(Vector parsingOrder) { JTree tree;

rootNode = new DefaultMutableTreeNode(new ParsingTreeNode("S", "S"));

int q; (adsbygoogle = window.adsbygoogle || []).push({});

Enumeration e; String s;

for (int i = 0; i < parsingOrder.size(); i++) {

q = ((Integer)parsingOrder.elementAt(i)).intValue()-1; e = rootNode.depthFirstEnumeration(); DefaultMutableTreeNode treeNode; while (e.hasMoreElements()) { treeNode = (DefaultMutableTreeNode)e.nextElement(); s = ((ParsingTreeNode)treeNode.getUserObject()).getKey(); if ((s.compareTo(left[q]) == 0) && (treeNode.isLeaf())) { DefaultMutableTreeNode tempNode = null;

for (int j = 0; j < right[q].length(); j++) { String key = "" + right[q].charAt(j);

tempNode = new DefaultMutableTreeNode(new

ParsingTreeNode(key, (String)partOfSpeechMap.get(key))); treeNode.add(tempNode); } break; } } } e = rootNode.depthFirstEnumeration(); DefaultMutableTreeNode treeNode; int j = 0; while (e.hasMoreElements()) { treeNode = (DefaultMutableTreeNode)e.nextElement(); if (treeNode.isLeaf()) {

DefaultMutableTreeNode tempNode = null;

tempNode = new DefaultMutableTreeNode(tokens[j]); treeNode.add(tempNode);

j++; }

}

tree = new JTree(rootNode); tree.setEditable(true); tree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION); tree.setShowsRootHandles(true); return tree; }

Hình 14 là giao diện của chương trình và thể hiện cây phân tích của câu "The green water evaporated" nhập từ một hộp soạn thảo.

Hình 14. Giao diện chương trình phân tích cú pháp tiếng Anh

được. Dưới đây là kết quả phân tích của chương trình với một số câu nhập vào khác nhau:

Để thử nghiệm chương trình phân tích cú pháp tiếng Việt, ta có thể nhập vào một số câu có cấu trúc khác nhau. Nếu dùng ký hiệu [w*] để chỉ từ w có thể xuất hiện hoặc không xuất hiện trong câu thì những câu có dạng:

1. Cô [ấy*] [rất*] tốt [nết*]

2. Cô [ấy*] tốt [cực kỳ*]

đều được phân tích đúng. Để thử nghiệm danh ngữ làm chủ ngữ, ta có thể thay đại từ “cô” bằng các tổ hợp: mèo, mèo ấy, con mèo, con mèo ấy, những con mèo ấy và chọn tính ngữ làm vị ngữ cho phù hợp.

Có thể nhập vào một số câu như sau để thử nghiệm động ngữ làm vị ngữ:

Anh [này*] [đang*] ăn [cơm* | ngon*]

Tương tự, có thể thay đại từ “anh” bằng một danh ngữ. Để thử nghiệm từ quan hệ, ta có thể dùng một số câu như:

1. Tôi là sinh viên

2. Nó bằng nhôm

3. Anh ấy là sinh viên

4. Họ là các sinh viên

5.4. Đánh giá kết quả

Khoá luận trình bày việc vận dụng các mô hình văn phạm phi ngữ cảnh và các mạng chuyển vào bài toán phân tích cú pháp tiếng Anh và tiếng Việt. Trong phân tích cú pháp có những đặc điểm chính sau:

• Tách riêng việc giải quyết phân tách từ vựng và phân tích cú pháp cho tiếng Việt. Sử dụng kết quả phân tích cú pháp để hỗ trợ quyết định chọn phương án phân tách từ vựng của câu (Phần Phụ lục).

• Giải quyết được vấn đề độ dài từ ghép hoặc cụm từ cố định.

• Chương trình hiện tại chỉ xử lý những câu đơn hai thành phần đơn giản của tiếng Việt (adsbygoogle = window.adsbygoogle || []).push({});

• Với câu nhập nhằng thì đưa ra mọi phương án phân tích có thể.

• Chương trình cung cấp các giao diện nhập liệu và xử lý trong môi trường hệ điều hành Windows, thuận tiện cho việc sử dụng.

Mọi câu đưa vào đều được thực hiện theo hai bước, gồm tách từ vựng và phân tích cú pháp. Mặc dù em chưa đưa ra được những số liệu thống kê chính xác nhưng quá trình thử nghiệm bước đầu cho thấy chương trình chạy khá nhanh và cho kết quả tương đối chính xác.

Ph lc

Bài toán tách từ vựng tiếng Việt

Trước khi thực hiện phân tích cú pháp của một câu tiếng Việt, ta cần một bước tiền xử lý quan trọng, đó là tách câu thành các đơn vị từ vựng và gán cho chúng những nhãn từ loại tương ứng (chẳng hạn danh từ, động từ...).

1. Đặt bài toán

Nhập vào một câu tiếng Việt bất kỳ, hãy tách câu đó thành những đơn vị từ vựng (từ), hoặc chỉ ra những âm tiết nào không có trong từ điển (phát hiện đơn vị từ vựng mới).

Để giải quyết bài toán đặt ra, ta cần tập dữ liệu gồm từ điển âm tiết (khoảng 6700 âm tiết) và từ điển từ vựng tiếng Việt (khoảng 30.000 từ). Việc sử dụng hai bộ từ điển nêu trên trong phạm vi đề tài nghiên cứu đã được sự đồng ý của Trung tâm từ điển học. Các từ điển được lưu dưới dạng các tệp văn bản có định dạng mã TCVN hoặc Unicode (UTF-8).

2. Các bước giải quyết

1. Xây dựng ôtôtmát âm tiết đoán nhận tất cả các âm tiết tiếng Việt 2. Xây dựng ôtôtmát từ vựng đoán nhận tất cả các từ vựng tiếng Việt.

3. Dựa trên các ôtômát nêu trên, xây dựng đồ thị tương ứng với câu cần phân tích và sử dụng thuật toán tìm kiếm trên đồ thị để liệt kê các cách phân tích có thể. Tư tưởng của phương pháp xây dựng các ôtômát là: xây dựng dần dần dựa trên ôtômát đã có ở bước trước và âm tiết (hoặc từ vựng) mới đọc được từ tệp dữ liệu ở bước hiện tại.

Bảng chữ cái của ôtômát âm tiết là bảng chữ cái tiếng Việt, mỗi cung chuyển được ghi trên đó một ký tự, ban đầu ôtômát âm tiết chỉ gồm một trạng thái khởi đầu được đánh số hiệu 0. Giả sử tại bước nào đó ta đọc được âm tiết a có độ dài n (tính bằng số ký tự) từ tệp dữ liệu. Xuất phát từ trạng thái khởi đầu p = q0 ta lấy ra từng ký

tự ci của a và tìm xem từ p có cung chuyển đến trạng thái q nào đó mà trên đó ghi ký tự ci hay không. Nếu có trạng thái q như thế, ta chuyển p thành q và lặp lại bước trên với ký tự ci+1 tiếp theo. Nếu không có q nào như thế, ta ra khỏi vòng lặp và xây dựng mới các trạng thái và cung chuyển tương ứng trên đó ghi các ký tự ci, ci+1,..., cn-1 theo sơ đồ sau (ô vuông chỉ rằng đó là trạng thái kết).

Ví dụ, với ba âm tiết phương, pháp, trình ta sẽ có ôtômát âm tiết như Hình 15.

Hình 15. Phương pháp xây dựng ôtômát âm tiết

Ôtômát từ vựng được xây dựng tương tự, với điểm khác như sau: thay vì ghi trên mỗi cung chuyển một ký tự, ta ghi một số. Số này là số hiệu của trạng thái (kết) của ôtômát âm tiết tại đó đoán nhận mỗi âm tiết của từ. Với cách tổ chức này, ta làm giảm được kích thước của ôtômát từ vựng mà không làm mất thông tin của nó, bởi vì mỗi âm tiết được xác định bằng một trạng thái kết duy nhất trong ôtômát âm tiết. Ví dụ, với hai từ phương phápphương trình, giả sử khi đưa lần lượt các âm tiết phương,

pháp, trình qua ôtômát âm tiết, ta đến được các trạng thái kết ghi các số n1, n2, n3 thì trên các cung chuyển tương ứng ta ghi các số n1, n2, n3 (Hình 16).

Hình 16. Phương pháp xây dựng ôtômát từ vựng

Sau khi đã xây dựng xong hai ôtômát, ta ghi chúng vào hai tệp định kiểu để dùng trong bước phân tách từ vựng. Đến lúc này, hai từ điển ban đầu không còn cần thiết nữa, mọi dữ liệu của ta nằm trong hai tệp ghi hai ôtômát này. Nếu mỗi ký tự (char) được ghi vào tệp với kích thước 2 byte (mã Unicode), mỗi số nguyên (int) có kích thước 4 byte thì tệp lưu ôtômát âm tiết có kích thước 146KB, tệp ôtômát từ vựng có kích thước 1MB.

Giả sử câu ban đầu là một dãy gồm n+1 âm tiết s0, s1, ..., sn. Ta xây dựng một đồ thị có n+2 đỉnh v0, v1, ..., vn, vn+1, sắp thứ tự trên một đường thẳng từ trái sang phải; trong đó, từ đỉnh vi đến đỉnh vj có cung (i < j) nếu các âm tiết si, si+1, ..., sj-1 theo thứ tự lập thành một từ. Khi đó mỗi cách phân tách câu khác nhau tương ứng với một đường đi trên đồ thị từ đỉnh đầu v0 đến đỉnh cuối vn+1. Trong các cách phân tách câu đó, cách phân tích câu đúng đắn nhất ứng với đường đi qua ít cung nhất trên đồ thị.

Trong trường hợp câu có sự nhập nhằng thì đồ thị sẽ có nhiều hơn một đường đi ngắn nhất từ đỉnh đầu đến đỉnh cuối, ta liệt kê toàn bộ các đường đi ngắn nhất trên đồ thị, từ đó đưa ra tất cả các phương án tách câu có thể và để người dùng quyết định sẽ chọn phương án nào, tuỳ thuộc vào ngữ nghĩa hoặc văn cảnh. Ví dụ, xét một câu có cụm "thuộc địa bàn", ta có đồ thị như sau (Hình 17)

Hình 17. Một tình huống nhập nhằng

Cụm này có sự nhập nhằng giữa thuộc địađịa bàn và ta sẽ có hai kết quả phân tách là "thuộc địa / bàn" và "thuộc / địa bàn". Ta có thể chỉ ra rất nhiều những cụm

Một phần của tài liệu Hoàn thiện hạch toán chi phí sản xuất và tính giá thành sản phẩm tại Công ty (Trang 47)